mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-04 20:46:37 +08:00
refactor: split modules and harden scraping workflows
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type cloudImagePrefetchTask struct {
|
||||
typ string
|
||||
ref string
|
||||
stableKey string
|
||||
}
|
||||
|
||||
func (s *ScannerService) cloudImagePrefetchWorker() {
|
||||
for task := range s.cloudImagePrefetchQueue {
|
||||
s.prefetchCloudImage(task)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ScannerService) queueCloudArtworkPrefetch(raw string) {
|
||||
if s == nil || s.storage == nil || s.imageProxy == nil {
|
||||
return
|
||||
}
|
||||
typ, ref, ok := ParseCloudArtworkURL(raw)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
stableKey := typ + ":" + ref
|
||||
if s.imageProxy.CloudImageCached(stableKey) {
|
||||
return
|
||||
}
|
||||
s.cloudImagePrefetchMu.Lock()
|
||||
if _, ok := s.cloudImagePrefetching[stableKey]; ok {
|
||||
s.cloudImagePrefetchMu.Unlock()
|
||||
return
|
||||
}
|
||||
s.cloudImagePrefetching[stableKey] = struct{}{}
|
||||
s.cloudImagePrefetchMu.Unlock()
|
||||
|
||||
task := cloudImagePrefetchTask{typ: typ, ref: ref, stableKey: stableKey}
|
||||
select {
|
||||
case s.cloudImagePrefetchQueue <- task:
|
||||
default:
|
||||
s.cloudImagePrefetchMu.Lock()
|
||||
delete(s.cloudImagePrefetching, stableKey)
|
||||
s.cloudImagePrefetchMu.Unlock()
|
||||
if s.log != nil {
|
||||
s.log.Debug("cloud artwork prefetch queue full", zap.String("provider", typ), zap.String("ref", ref))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ScannerService) prefetchCloudImage(task cloudImagePrefetchTask) {
|
||||
defer func() {
|
||||
s.cloudImagePrefetchMu.Lock()
|
||||
delete(s.cloudImagePrefetching, task.stableKey)
|
||||
s.cloudImagePrefetchMu.Unlock()
|
||||
}()
|
||||
if s == nil || s.storage == nil || s.imageProxy == nil || s.imageProxy.CloudImageCached(task.stableKey) {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
||||
defer cancel()
|
||||
link, err := s.storage.CloudResolve(ctx, task.typ, task.ref, "")
|
||||
if err != nil {
|
||||
if s.log != nil {
|
||||
s.log.Debug("resolve cloud artwork for prefetch failed", zap.String("provider", task.typ), zap.String("ref", task.ref), zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := s.imageProxy.PrefetchCloudResolved(ctx, task.stableKey, link); err != nil && s.log != nil {
|
||||
s.log.Debug("prefetch cloud artwork failed", zap.String("provider", task.typ), zap.String("ref", task.ref), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ScannerService) cacheCloudArtworkNow(ctx context.Context, raw string) {
|
||||
if s == nil || s.storage == nil || s.imageProxy == nil {
|
||||
return
|
||||
}
|
||||
typ, ref, ok := ParseCloudArtworkURL(raw)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
stableKey := typ + ":" + ref
|
||||
if s.imageProxy.CloudImageCached(stableKey) {
|
||||
return
|
||||
}
|
||||
cacheCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
link, err := s.storage.CloudResolve(cacheCtx, typ, ref, "")
|
||||
if err != nil {
|
||||
if s.log != nil {
|
||||
s.log.Debug("resolve cloud artwork for priority cache failed", zap.String("provider", typ), zap.String("ref", ref), zap.Error(err))
|
||||
}
|
||||
s.queueCloudArtworkPrefetch(raw)
|
||||
return
|
||||
}
|
||||
if err := s.imageProxy.PrefetchCloudResolved(cacheCtx, stableKey, link); err != nil {
|
||||
if s.log != nil {
|
||||
s.log.Debug("priority cache cloud artwork failed", zap.String("provider", typ), zap.String("ref", ref), zap.Error(err))
|
||||
}
|
||||
s.queueCloudArtworkPrefetch(raw)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ScannerService) cacheCloudMetadataArtworkNow(ctx context.Context, meta *LocalMetadata) {
|
||||
if meta == nil {
|
||||
return
|
||||
}
|
||||
s.cacheCloudArtworkNow(ctx, meta.PosterURL)
|
||||
s.cacheCloudArtworkNow(ctx, meta.BackdropURL)
|
||||
}
|
||||
|
||||
func ParseCloudArtworkURL(raw string) (string, string, bool) {
|
||||
u, err := url.Parse(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return "", "", false
|
||||
}
|
||||
path := strings.Trim(u.Path, "/")
|
||||
typ := ""
|
||||
for _, prefix := range []string{"api/img/cloud/", "api/cloud/play/"} {
|
||||
if strings.HasPrefix(strings.ToLower(path), prefix) {
|
||||
typ = strings.TrimSpace(path[len(prefix):])
|
||||
break
|
||||
}
|
||||
}
|
||||
if typ == "" {
|
||||
return "", "", false
|
||||
}
|
||||
ref := strings.TrimSpace(u.Query().Get("ref"))
|
||||
if typ == "" || ref == "" || !isCloudArtworkRef(ref) {
|
||||
return "", "", false
|
||||
}
|
||||
return typ, ref, true
|
||||
}
|
||||
|
||||
func CloudArtworkURL(typ, ref string) string {
|
||||
typ = strings.Trim(strings.ReplaceAll(strings.TrimSpace(typ), "\\", "/"), "/")
|
||||
ref = strings.TrimSpace(ref)
|
||||
if typ == "" || ref == "" {
|
||||
return ""
|
||||
}
|
||||
return "/api/img/cloud/" + url.PathEscape(typ) + "?ref=" + url.QueryEscape(ref)
|
||||
}
|
||||
|
||||
func isCloudArtworkRef(ref string) bool {
|
||||
ref = strings.ToLower(strings.TrimSpace(ref))
|
||||
for _, suffix := range []string{".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tbn"} {
|
||||
if strings.HasSuffix(ref, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user