mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-10 15:26:38 +08:00
refactor: split modules and harden scraping workflows
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
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"
|
||||
}
|
||||
Reference in New Issue
Block a user