241 lines
6.9 KiB
Go
241 lines
6.9 KiB
Go
package manga
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"seanime/internal/extension"
|
|
manga_providers "seanime/internal/manga/providers"
|
|
"seanime/internal/util"
|
|
"sync"
|
|
|
|
hibikemanga "seanime/internal/extension/hibike/manga"
|
|
)
|
|
|
|
type (
|
|
// PageContainer is used to display the list of pages from a chapter in the client.
|
|
// It is cached in the file cache bucket with a key of the format: {provider}${mediaId}${chapterId}
|
|
PageContainer struct {
|
|
MediaId int `json:"mediaId"`
|
|
Provider string `json:"provider"`
|
|
ChapterId string `json:"chapterId"`
|
|
Pages []*hibikemanga.ChapterPage `json:"pages"`
|
|
PageDimensions map[int]*PageDimension `json:"pageDimensions"` // Indexed by page number
|
|
IsDownloaded bool `json:"isDownloaded"` // TODO remove
|
|
}
|
|
|
|
// PageDimension is used to store the dimensions of a page.
|
|
// It is used by the client for 'Double Page' mode.
|
|
PageDimension struct {
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
}
|
|
)
|
|
|
|
// GetMangaPageContainer returns the PageContainer for a manga chapter based on the provider.
|
|
func (r *Repository) GetMangaPageContainer(
|
|
provider string,
|
|
mediaId int,
|
|
chapterId string,
|
|
doublePage bool,
|
|
isOffline *bool,
|
|
) (ret *PageContainer, err error) {
|
|
defer util.HandlePanicInModuleWithError("manga/GetMangaPageContainer", &err)
|
|
|
|
// +---------------------+
|
|
// | Downloads |
|
|
// +---------------------+
|
|
|
|
providerExtension, ok := extension.GetExtension[extension.MangaProviderExtension](r.providerExtensionBank, provider)
|
|
if !ok {
|
|
r.logger.Error().Str("provider", provider).Msg("manga: Provider not found")
|
|
return nil, errors.New("manga: Provider not found")
|
|
}
|
|
|
|
_, isLocalProvider := providerExtension.GetProvider().(*manga_providers.Local)
|
|
|
|
if *isOffline && !isLocalProvider {
|
|
ret, err = r.getDownloadedMangaPageContainer(provider, mediaId, chapterId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
if !isLocalProvider {
|
|
ret, _ = r.getDownloadedMangaPageContainer(provider, mediaId, chapterId)
|
|
if ret != nil {
|
|
return ret, nil
|
|
}
|
|
}
|
|
|
|
// +---------------------+
|
|
// | Get Pages |
|
|
// +---------------------+
|
|
|
|
// PageContainer key
|
|
pageContainerKey := fmt.Sprintf("%s$%d$%s", provider, mediaId, chapterId)
|
|
|
|
r.logger.Trace().
|
|
Str("provider", provider).
|
|
Int("mediaId", mediaId).
|
|
Str("key", pageContainerKey).
|
|
Str("chapterId", chapterId).
|
|
Msgf("manga: Getting pages")
|
|
|
|
// +---------------------+
|
|
// | Cache |
|
|
// +---------------------+
|
|
|
|
var container *PageContainer
|
|
|
|
// PageContainer bucket
|
|
// e.g., manga_comick_pages_123
|
|
// -> { "comick$123$10010": PageContainer }, { "comick$123$10011": PageContainer }
|
|
pageBucket := r.getFcProviderBucket(provider, mediaId, bucketTypePage)
|
|
|
|
// Check if the container is in the cache
|
|
if found, _ := r.fileCacher.Get(pageBucket, pageContainerKey, &container); found && !isLocalProvider {
|
|
|
|
// Hydrate page dimensions
|
|
pageDimensions, _ := r.getPageDimensions(doublePage, provider, mediaId, chapterId, container.Pages)
|
|
container.PageDimensions = pageDimensions
|
|
|
|
r.logger.Debug().Str("key", pageContainerKey).Msg("manga: Page Container Cache HIT")
|
|
return container, nil
|
|
}
|
|
|
|
// +---------------------+
|
|
// | Fetch pages |
|
|
// +---------------------+
|
|
|
|
// Search for the chapter in the cache
|
|
containerBucket := r.getFcProviderBucket(provider, mediaId, bucketTypeChapter)
|
|
|
|
chapterContainerKey := getMangaChapterContainerCacheKey(provider, mediaId)
|
|
|
|
var chapterContainer *ChapterContainer
|
|
if found, _ := r.fileCacher.Get(containerBucket, chapterContainerKey, &chapterContainer); !found {
|
|
r.logger.Error().Msg("manga: Chapter Container not found")
|
|
return nil, ErrNoChapters
|
|
}
|
|
|
|
// Get the chapter from the container
|
|
var chapter *hibikemanga.ChapterDetails
|
|
for _, c := range chapterContainer.Chapters {
|
|
if c.ID == chapterId {
|
|
chapter = c
|
|
break
|
|
}
|
|
}
|
|
|
|
if chapter == nil {
|
|
r.logger.Error().Msg("manga: Chapter not found")
|
|
return nil, ErrChapterNotFound
|
|
}
|
|
|
|
// Get the chapter pages
|
|
var pages []*hibikemanga.ChapterPage
|
|
|
|
pages, err = providerExtension.GetProvider().FindChapterPages(chapter.ID)
|
|
if err != nil {
|
|
r.logger.Error().Err(err).Msg("manga: Could not get chapter pages")
|
|
return nil, err
|
|
}
|
|
|
|
if pages == nil || len(pages) == 0 {
|
|
r.logger.Error().Msg("manga: No pages found")
|
|
return nil, fmt.Errorf("manga: No pages found")
|
|
}
|
|
|
|
// Overwrite provider just in case
|
|
for _, page := range pages {
|
|
page.Provider = provider
|
|
}
|
|
|
|
pageDimensions, _ := r.getPageDimensions(doublePage, provider, mediaId, chapterId, pages)
|
|
|
|
container = &PageContainer{
|
|
MediaId: mediaId,
|
|
Provider: provider,
|
|
ChapterId: chapterId,
|
|
Pages: pages,
|
|
PageDimensions: pageDimensions,
|
|
IsDownloaded: false,
|
|
}
|
|
|
|
// Set cache only if not local provider
|
|
if !isLocalProvider {
|
|
err = r.fileCacher.Set(pageBucket, pageContainerKey, container)
|
|
if err != nil {
|
|
r.logger.Warn().Err(err).Msg("manga: Failed to populate cache")
|
|
}
|
|
}
|
|
|
|
r.logger.Debug().Str("key", pageContainerKey).Msg("manga: Retrieved pages")
|
|
|
|
return container, nil
|
|
}
|
|
|
|
func (r *Repository) getPageDimensions(enabled bool, provider string, mediaId int, chapterId string, pages []*hibikemanga.ChapterPage) (ret map[int]*PageDimension, err error) {
|
|
defer util.HandlePanicInModuleWithError("manga/getPageDimensions", &err)
|
|
|
|
if !enabled {
|
|
return nil, nil
|
|
}
|
|
|
|
// e.g. comick$123$10010
|
|
key := fmt.Sprintf("%s$%d$%s", provider, mediaId, chapterId)
|
|
|
|
// Page dimensions bucket
|
|
// e.g., manga_comick_page-dimensions_123
|
|
// -> { "comick$123$10010": PageDimensions }, { "comick$123$10011": PageDimensions }
|
|
dimensionBucket := r.getFcProviderBucket(provider, mediaId, bucketTypePageDimensions)
|
|
|
|
if found, _ := r.fileCacher.Get(dimensionBucket, key, &ret); found {
|
|
r.logger.Debug().Str("key", key).Msg("manga: Page Dimensions Cache HIT")
|
|
return
|
|
}
|
|
|
|
r.logger.Trace().Str("key", key).Msg("manga: Getting page dimensions")
|
|
|
|
// Get the page dimensions
|
|
pageDimensions := make(map[int]*PageDimension)
|
|
mu := sync.Mutex{}
|
|
wg := sync.WaitGroup{}
|
|
for _, page := range pages {
|
|
wg.Add(1)
|
|
go func(page *hibikemanga.ChapterPage) {
|
|
defer wg.Done()
|
|
var buf []byte
|
|
if page.Buf != nil {
|
|
buf = page.Buf
|
|
} else {
|
|
buf, err = manga_providers.GetImageByProxy(page.URL, page.Headers)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
width, height, err := getImageNaturalSizeB(buf)
|
|
if err != nil {
|
|
//r.logger.Warn().Err(err).Int("index", page.Index).Msg("manga: failed to get image size")
|
|
return
|
|
}
|
|
|
|
mu.Lock()
|
|
// DEVNOTE: Index by page index
|
|
pageDimensions[page.Index] = &PageDimension{
|
|
Width: width,
|
|
Height: height,
|
|
}
|
|
mu.Unlock()
|
|
}(page)
|
|
}
|
|
wg.Wait()
|
|
|
|
_ = r.fileCacher.Set(dimensionBucket, key, pageDimensions)
|
|
|
|
r.logger.Info().Str("bucket", dimensionBucket.Name()).Msg("manga: Retrieved page dimensions")
|
|
|
|
return pageDimensions, nil
|
|
}
|