feat(scanner): ScanFolders honors its context; cancelling never archives
ScanFolders never checked its context, so a cancelled job's walk ran to completion no matter what the worker's cancel did. - ctx checks at every stage boundary (file-counting pass, per folder, before and during the archive pass) and inside the walk callback (fs.SkipAll): the file currently being processed finishes, then the scan aborts with context.Canceled, which processJob classifies as a cancelled job rather than a failed one - the archive/purge pass is skipped entirely when the scan was cancelled. It only visits what the walk visited, so after a partial walk the unvisited (but on-disk) files would be treated as missing, archived, and eventually purged. Cancelling is a UI convenience and must never destroy library data.
This commit is contained in:
1 parent
b2220756e2
commit
30ecdd4bf3
1 file changed
+32
@@ -419,6 +419,14 @@ func (s *MediaScanner) enqueueLibraryScan(rootFolder string) {
|
||||
}
|
||||
|
||||
func (s *MediaScanner) ScanFolders(ctx context.Context) error {
|
||||
// Cancellation is checked at every stage boundary and inside the
|
||||
// walk callback: the in-flight file finishes, then the scan aborts
|
||||
// and reports context.Canceled (processJob classifies that as a
|
||||
// cancelled job, not a failed one).
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
if len(s.folders) == 0 {
|
||||
return fmt.Errorf("no folders set")
|
||||
}
|
||||
@@ -428,6 +436,9 @@ func (s *MediaScanner) ScanFolders(ctx context.Context) error {
|
||||
s.errors = 0
|
||||
|
||||
for _, folder := range s.folders {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if err := filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
|
||||
if !d.IsDir() && s.isScannableFile(path) {
|
||||
s.totalFiles++
|
||||
@@ -445,6 +456,10 @@ func (s *MediaScanner) ScanFolders(ctx context.Context) error {
|
||||
mediaFiles := 0
|
||||
|
||||
for _, folder := range s.folders {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
fmt.Printf("Scanning folder: %s\n", folder)
|
||||
|
||||
if _, err := os.Stat(folder); os.IsNotExist(err) {
|
||||
@@ -454,6 +469,10 @@ func (s *MediaScanner) ScanFolders(ctx context.Context) error {
|
||||
}
|
||||
|
||||
err := filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
|
||||
if ctx.Err() != nil {
|
||||
return fs.SkipAll
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error accessing path %s: %v\n", path, err)
|
||||
s.errors++
|
||||
@@ -502,6 +521,14 @@ func (s *MediaScanner) ScanFolders(ctx context.Context) error {
|
||||
fmt.Printf("Scan completed: %d total files scanned, %d media files found, %d new items, %d errors\n",
|
||||
processedFiles, mediaFiles, s.newItems, s.errors)
|
||||
|
||||
// A cancelled scan must not run the archive lifecycle pass below:
|
||||
// it never visited every folder, so unvisited files would be
|
||||
// treated as missing and archived (and eventually purged). Abort
|
||||
// instead — cancelling never destroys library data.
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// Archive lifecycle pass. Items whose files vanished from disk are
|
||||
// archived after two consecutive missing scans (reading history kept,
|
||||
// item hidden), and purged for good once archived older than the
|
||||
@@ -509,6 +536,11 @@ func (s *MediaScanner) ScanFolders(ctx context.Context) error {
|
||||
// Every branch logs - the previous hard-delete cleanup failed silently
|
||||
// and left orphaned rows undetected.
|
||||
for _, folder := range s.folders {
|
||||
if ctx.Err() != nil {
|
||||
fmt.Printf("[ARCHIVE] Scan cancelled, stopping archive pass\n")
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
lib, err := s.db.GetLibraryByFolder(ctx, folder)
|
||||
if err != nil {
|
||||
fmt.Printf("[ARCHIVE] Warning: no library found for folder %s, skipping archive pass: %v\n", folder, err)
|
||||
|
||||
Reference in new issue
Block a user