feat(scanner): add folder hash for smarter quick scan change detection (#4220)

* Simplify folder hash migration

* fix hashing lint

* refactor

Signed-off-by: Deluan <deluan@navidrome.org>

* Update scanner/folder_entry.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Deluan Quintão
2025-06-12 13:17:34 -04:00
committed by GitHub
parent 050aa173cc
commit 0d74d36cec
7 changed files with 559 additions and 70 deletions
-63
View File
@@ -9,78 +9,15 @@ import (
"slices"
"sort"
"strings"
"time"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils"
"github.com/navidrome/navidrome/utils/chrono"
ignore "github.com/sabhiram/go-gitignore"
)
type folderEntry struct {
job *scanJob
elapsed chrono.Meter
path string // Full path
id string // DB ID
modTime time.Time // From FS
updTime time.Time // from DB
audioFiles map[string]fs.DirEntry
imageFiles map[string]fs.DirEntry
numPlaylists int
numSubFolders int
imagesUpdatedAt time.Time
tracks model.MediaFiles
albums model.Albums
albumIDMap map[string]string
artists model.Artists
tags model.TagList
missingTracks []*model.MediaFile
}
func (f *folderEntry) hasNoFiles() bool {
return len(f.audioFiles) == 0 && len(f.imageFiles) == 0 && f.numPlaylists == 0 && f.numSubFolders == 0
}
func (f *folderEntry) isNew() bool {
return f.updTime.IsZero()
}
func (f *folderEntry) toFolder() *model.Folder {
folder := model.NewFolder(f.job.lib, f.path)
folder.NumAudioFiles = len(f.audioFiles)
if core.InPlaylistsPath(*folder) {
folder.NumPlaylists = f.numPlaylists
}
folder.ImageFiles = slices.Collect(maps.Keys(f.imageFiles))
folder.ImagesUpdatedAt = f.imagesUpdatedAt
return folder
}
func newFolderEntry(job *scanJob, path string) *folderEntry {
id := model.FolderID(job.lib, path)
f := &folderEntry{
id: id,
job: job,
path: path,
audioFiles: make(map[string]fs.DirEntry),
imageFiles: make(map[string]fs.DirEntry),
albumIDMap: make(map[string]string),
updTime: job.popLastUpdate(id),
}
return f
}
func (f *folderEntry) isOutdated() bool {
if f.job.lib.FullScanInProgress {
return f.updTime.Before(f.job.lib.LastScanStartedAt)
}
return f.updTime.Before(f.modTime)
}
func walkDirTree(ctx context.Context, job *scanJob) (<-chan *folderEntry, error) {
results := make(chan *folderEntry)
go func() {