diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go index de60791..6bf8fc9 100644 --- a/internal/services/media_scanner.go +++ b/internal/services/media_scanner.go @@ -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.