mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-30 03:36:37 +08:00
159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
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
|
|
}
|