187 lines
5.1 KiB
Go
187 lines
5.1 KiB
Go
package manga
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"image"
|
|
_ "image/jpeg" // Register JPEG format
|
|
_ "image/png" // Register PNG format
|
|
"net/http"
|
|
"seanime/internal/database/db"
|
|
"seanime/internal/database/models"
|
|
"seanime/internal/events"
|
|
"seanime/internal/extension"
|
|
"seanime/internal/util/filecache"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
_ "golang.org/x/image/bmp" // Register BMP format
|
|
_ "golang.org/x/image/tiff" // Register Tiff format
|
|
_ "golang.org/x/image/webp" // Register WebP format
|
|
)
|
|
|
|
var (
|
|
ErrNoResults = errors.New("no results found for this media")
|
|
ErrNoChapters = errors.New("no manga chapters found")
|
|
ErrChapterNotFound = errors.New("chapter not found")
|
|
ErrChapterNotDownloaded = errors.New("chapter not downloaded")
|
|
ErrNoTitlesProvided = errors.New("no titles provided")
|
|
)
|
|
|
|
type (
|
|
Repository struct {
|
|
logger *zerolog.Logger
|
|
fileCacher *filecache.Cacher
|
|
cacheDir string
|
|
providerExtensionBank *extension.UnifiedBank
|
|
serverUri string
|
|
wsEventManager events.WSEventManagerInterface
|
|
mu sync.Mutex
|
|
downloadDir string
|
|
db *db.Database
|
|
|
|
settings *models.Settings
|
|
}
|
|
|
|
NewRepositoryOptions struct {
|
|
Logger *zerolog.Logger
|
|
CacheDir string
|
|
FileCacher *filecache.Cacher
|
|
ServerURI string
|
|
WsEventManager events.WSEventManagerInterface
|
|
DownloadDir string
|
|
Database *db.Database
|
|
}
|
|
)
|
|
|
|
func NewRepository(opts *NewRepositoryOptions) *Repository {
|
|
r := &Repository{
|
|
logger: opts.Logger,
|
|
fileCacher: opts.FileCacher,
|
|
cacheDir: opts.CacheDir,
|
|
serverUri: opts.ServerURI,
|
|
wsEventManager: opts.WsEventManager,
|
|
downloadDir: opts.DownloadDir,
|
|
providerExtensionBank: extension.NewUnifiedBank(),
|
|
db: opts.Database,
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *Repository) SetSettings(settings *models.Settings) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.settings = settings
|
|
}
|
|
|
|
func (r *Repository) InitExtensionBank(bank *extension.UnifiedBank) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.providerExtensionBank = bank
|
|
r.logger.Debug().Msg("manga: Initialized provider extension bank")
|
|
}
|
|
|
|
func (r *Repository) RemoveProvider(id string) {
|
|
r.providerExtensionBank.Delete(id)
|
|
}
|
|
|
|
func (r *Repository) GetProviderExtensionBank() *extension.UnifiedBank {
|
|
return r.providerExtensionBank
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// File Cache
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type bucketType string
|
|
|
|
const (
|
|
bucketTypeChapterKey = "1"
|
|
bucketTypeChapter bucketType = "chapters"
|
|
bucketTypePage bucketType = "pages"
|
|
bucketTypePageDimensions bucketType = "page-dimensions"
|
|
)
|
|
|
|
// getFcProviderBucket returns a bucket for the provider and mediaId.
|
|
//
|
|
// e.g., manga_comick_chapters_123, manga_mangasee_pages_456
|
|
//
|
|
// Note: Each bucket contains only 1 key-value pair.
|
|
func (r *Repository) getFcProviderBucket(provider string, mediaId int, bucketType bucketType) filecache.Bucket {
|
|
return filecache.NewBucket("manga_"+provider+"_"+string(bucketType)+"_"+strconv.Itoa(mediaId), time.Hour*24*7)
|
|
}
|
|
|
|
// EmptyMangaCache deletes all manga buckets associated with the given mediaId.
|
|
func (r *Repository) EmptyMangaCache(mediaId int) (err error) {
|
|
// Empty the manga cache
|
|
err = r.fileCacher.RemoveAllBy(func(filename string) bool {
|
|
return strings.HasPrefix(filename, "manga_") && strings.Contains(filename, strconv.Itoa(mediaId))
|
|
})
|
|
return
|
|
}
|
|
|
|
func ParseChapterContainerFileName(filename string) (provider string, bucketType bucketType, mediaId int, ok bool) {
|
|
filename = strings.TrimSuffix(filename, ".json")
|
|
filename = strings.TrimSuffix(filename, ".cache")
|
|
filename = strings.TrimSuffix(filename, ".txt")
|
|
parts := strings.Split(filename, "_")
|
|
if len(parts) != 4 {
|
|
return "", "", 0, false
|
|
}
|
|
|
|
provider = parts[1]
|
|
var err error
|
|
mediaId, err = strconv.Atoi(parts[3])
|
|
if err != nil {
|
|
return "", "", 0, false
|
|
}
|
|
|
|
switch parts[2] {
|
|
case "chapters":
|
|
bucketType = bucketTypeChapter
|
|
case "pages":
|
|
bucketType = bucketTypePage
|
|
case "page-dimensions":
|
|
bucketType = bucketTypePageDimensions
|
|
default:
|
|
return "", "", 0, false
|
|
}
|
|
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
func getImageNaturalSize(url string) (int, int, error) {
|
|
// Fetch the image
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Decode the image
|
|
img, _, err := image.DecodeConfig(resp.Body)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
// Return the natural size
|
|
return img.Width, img.Height, nil
|
|
}
|
|
|
|
func getImageNaturalSizeB(data []byte) (int, int, error) {
|
|
// Decode the image
|
|
img, _, err := image.DecodeConfig(bytes.NewReader(data))
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
// Return the natural size
|
|
return img.Width, img.Height, nil
|
|
}
|