Files
MeBox/internal/service/scanner_cloud_jobs.go
T

152 lines
4.5 KiB
Go

package service
import (
"context"
"errors"
"fmt"
"strings"
"time"
"go.uber.org/zap"
"github.com/ShukeBta/MediaStationGo/internal/model"
"github.com/ShukeBta/MediaStationGo/internal/repository"
)
func (s *ScannerService) StartCloudLibraryScan(libraryID string, autoScrape bool) (CloudScanStatus, bool, error) {
if s == nil {
return CloudScanStatus{}, false, errors.New("scanner unavailable")
}
lib, err := s.repo.Library.FindByID(context.Background(), libraryID)
if err != nil {
return CloudScanStatus{}, false, err
}
if lib == nil {
return CloudScanStatus{}, false, errors.New("library not found")
}
mount, ok := ParseCloudLibraryMount(lib.Path)
if !ok {
return CloudScanStatus{}, false, errors.New("library is not a cloud mount")
}
if IsDeprecatedNativeCloudProvider(mount.Provider) {
return CloudScanStatus{}, false, fmt.Errorf("cloud provider %q is deprecated; use OpenList or CloudDrive2 bridge", mount.Provider)
}
s.cloudScanMu.Lock()
if entry := s.cloudScans[libraryID]; cloudScanBlocksBegin(entry) {
status := entry.status
s.cloudScanMu.Unlock()
return status, false, nil
}
s.cloudScanMu.Unlock()
go func() {
ctx, cancel := cloudScanContext(context.Background(), cloudScanTimeout(context.Background(), s.repo, 24*time.Hour))
defer cancel()
if autoScrape {
_, err = s.ScanLibrary(ctx, libraryID)
} else {
_, err = s.ScanLibraryWithoutAutoScrape(ctx, libraryID)
}
if err != nil && !errors.Is(err, ErrCloudScanAlreadyRunning) && s.log != nil {
s.log.Warn("cloud library background scan failed", zap.String("library_id", libraryID), zap.Error(err))
}
}()
return newCloudScanEntry(libraryID, mount.Provider, nil).status.withQueuedState(), true, nil
}
func (status CloudScanStatus) withQueuedState() CloudScanStatus {
status.Stage = "queued"
status.State = "queued"
return status
}
func cloudScanContext(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
if timeout <= 0 {
return context.WithCancel(parent)
}
return context.WithTimeout(parent, timeout)
}
func cloudScanTimeout(ctx context.Context, repo *repository.Container, fallback time.Duration) time.Duration {
if repo == nil || repo.Setting == nil {
return fallback
}
value, err := repo.Setting.Get(ctx, "cloud.scan_timeout_hours")
if err != nil || strings.TrimSpace(value) == "" {
return fallback
}
hours := parseIntSettingDefault(strings.TrimSpace(value), int(fallback/time.Hour))
if hours <= 0 {
return 0
}
return time.Duration(hours) * time.Hour
}
func (s *ScannerService) StartAllCloudLibraryScans() ([]CloudScanStatus, error) {
if s == nil {
return nil, errors.New("scanner unavailable")
}
libs, err := s.repo.Library.List(context.Background())
if err != nil {
return nil, err
}
libs = FilterScannableCloudLibraries(context.Background(), s.repo, libs)
statuses := make([]CloudScanStatus, 0, len(libs))
queue := make([]string, 0, len(libs))
for _, lib := range libs {
if !lib.Enabled {
continue
}
mount, ok := ParseCloudLibraryMount(lib.Path)
if !ok {
continue
}
status, queued := s.queueCloudLibraryScan(lib, mount)
if queued {
queue = append(queue, lib.ID)
}
statuses = append(statuses, status)
}
if len(queue) > 0 {
go s.runQueuedCloudLibraryScans(queue)
}
return statuses, nil
}
func (s *ScannerService) queueCloudLibraryScan(lib model.Library, mount CloudMountInfo) (CloudScanStatus, bool) {
status := newCloudScanEntry(lib.ID, mount.Provider, nil).status.withQueuedState()
s.cloudScanMu.Lock()
defer s.cloudScanMu.Unlock()
if s.cloudScans == nil {
s.cloudScans = make(map[string]*cloudScanEntry)
}
if entry := s.cloudScans[lib.ID]; cloudScanActive(entry) {
return entry.status, false
}
s.cloudScans[lib.ID] = &cloudScanEntry{status: status}
return status, true
}
func (s *ScannerService) runQueuedCloudLibraryScans(libraryIDs []string) {
ctx, cancel := cloudScanContext(context.Background(), cloudScanTimeout(context.Background(), s.repo, 24*time.Hour))
defer cancel()
for _, libraryID := range libraryIDs {
if ctx.Err() != nil {
return
}
if s.cloudScanWasCanceled(libraryID) {
continue
}
if _, err := s.ScanLibrary(ctx, libraryID); err != nil && !errors.Is(err, ErrCloudScanAlreadyRunning) && !errors.Is(err, context.Canceled) && s.log != nil {
s.log.Warn("cloud library queued scan failed", zap.String("library_id", libraryID), zap.Error(err))
}
}
}
func (s *ScannerService) cloudScanWasCanceled(libraryID string) bool {
s.cloudScanMu.Lock()
defer s.cloudScanMu.Unlock()
entry := s.cloudScans[libraryID]
return entry != nil && entry.status.State == "canceled"
}