feat(scanner): treat reappeared content as a move, not a duplicate
When the SHA-256 dedup found identical content already in the library, the scan skipped the file as a duplicate - and after files moved between folders the old row kept its stale path, cycled missing -> archived, and the new path never took. The archive feature turned the old destructive move behavior into a stuck move instead. Now the dedup branch stats the old location: if it is gone, the book was MOVED, so the row is repointed (file_path, file_size) with archive state cleared and reading history intact. 'Skip as duplicate' only applies when the old path still exists (a true copy). Verified live: a moved EPUB kept its single row, followed the file, and never entered the missing/archive cycle.
This commit is contained in:
@@ -795,6 +795,39 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool,
|
||||
LibraryID: libraryID,
|
||||
})
|
||||
if err == nil && existingByHash.ID.Valid {
|
||||
// Same content at a different path. If the old location is gone,
|
||||
// the book was MOVED: repoint the row so history follows it and
|
||||
// the archive pass doesn't cycle it into missing/archived. Only
|
||||
// treat it as a duplicate copy when the old path still exists.
|
||||
oldPathStillExists := false
|
||||
for _, folder := range s.folders {
|
||||
if _, statErr := os.Stat(filepath.Join(folder, existingByHash.FilePath)); statErr == nil {
|
||||
oldPathStillExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !oldPathStillExists {
|
||||
if err := s.db.MoveMediaItemFilePath(ctx, database.MoveMediaItemFilePathParams{
|
||||
ID: existingByHash.ID,
|
||||
FilePath: s.getRelativePath(path),
|
||||
FileSize: pgtype.Int8{Int64: info.Size(), Valid: true},
|
||||
}); err != nil {
|
||||
fmt.Printf("Warning: failed to repoint moved media item %s -> %s: %v\n", existingByHash.FilePath, path, err)
|
||||
} else {
|
||||
fmt.Printf("[MOVE] Item content moved: %q -> %s (history preserved)\n", existingByHash.FilePath, path)
|
||||
s.logger.LogDelete(fmt.Sprintf("[MOVE] Repointed item '%s' from %q to %s", existingByHash.Title, existingByHash.FilePath, path))
|
||||
}
|
||||
if s.forceRescan {
|
||||
moved := existingByHash
|
||||
moved.FilePath = s.getRelativePath(path)
|
||||
moved.ArchivedAt = pgtype.Timestamptz{}
|
||||
moved.MissingScanCount = 0
|
||||
_ = s.updateMediaItem(ctx, moved, path)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
fmt.Printf("Media item with same SHA-256 already exists in library (path %q), skipping duplicate: %s\n",
|
||||
existingByHash.FilePath, path)
|
||||
// Content returned (possibly at a new path): restore archived rows.
|
||||
|
||||
Reference in New Issue
Block a user