Files
MeBox/internal/service/scanner_cloud_artwork.go
T

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
}