Backend: Refactor file indexing / skipping

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-07-18 20:58:35 +02:00
parent ee8d9ad919
commit 24cfa1aea2
12 changed files with 69 additions and 48 deletions

View file

@ -211,10 +211,10 @@ func (m *Photo) Save() error {
if err := UnscopedDb().Save(m).Error; err == nil { if err := UnscopedDb().Save(m).Error; err == nil {
// Nothing to do. // Nothing to do.
} else if !strings.Contains(strings.ToLower(err.Error()), "lock") { } else if !strings.Contains(strings.ToLower(err.Error()), "lock") {
log.Errorf("photo: %s (save %s)", err, m.PhotoUID) log.Debugf("photo: %s (save %s)", err, m.PhotoUID)
return err return err
} else if err := UnscopedDb().Save(m).Error; err != nil { } else if err := UnscopedDb().Save(m).Error; err != nil {
log.Errorf("photo: %s (save %s after deadlock)", err, m.PhotoUID) log.Debugf("photo: %s (save %s after deadlock)", err, m.PhotoUID)
return err return err
} }

View file

@ -53,7 +53,7 @@ func (c *Convert) Start(path string) error {
}() }()
} }
done := make(map[string]bool) done := make(fs.Done)
ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false) ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false)
if err := ignore.Dir(path); err != nil { if err := ignore.Dir(path); err != nil {
@ -89,7 +89,7 @@ func (c *Convert) Start(path string) error {
return nil return nil
} }
done[fileName] = true done[fileName] = fs.Processed
jobs <- ConvertJob{ jobs <- ConvertJob{
image: mf, image: mf,

View file

@ -48,9 +48,9 @@ func (imp *Import) thumbPath() string {
} }
// Start imports media files from a directory and converts/indexes them as needed. // Start imports media files from a directory and converts/indexes them as needed.
func (imp *Import) Start(opt ImportOptions) map[string]bool { func (imp *Import) Start(opt ImportOptions) fs.Done {
var directories []string var directories []string
done := make(map[string]bool) done := make(fs.Done)
ind := imp.index ind := imp.index
importPath := opt.Path importPath := opt.Path
@ -145,15 +145,15 @@ func (imp *Import) Start(opt ImportOptions) map[string]bool {
var files MediaFiles var files MediaFiles
for _, f := range related.Files { for _, f := range related.Files {
if done[f.FileName()] { if done[f.FileName()].Processed() {
continue continue
} }
files = append(files, f) files = append(files, f)
done[f.FileName()] = true done[f.FileName()] = fs.Processed
} }
done[fileName] = true done[fileName] = fs.Processed
related.Files = files related.Files = files

View file

@ -55,14 +55,14 @@ func (ind *Index) Cancel() {
} }
// Start indexes media files in the originals directory. // Start indexes media files in the originals directory.
func (ind *Index) Start(opt IndexOptions) map[string]bool { func (ind *Index) Start(opt IndexOptions) fs.Done {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.Errorf("index: %s (panic)\nstack: %s", r, debug.Stack()) log.Errorf("index: %s (panic)\nstack: %s", r, debug.Stack())
} }
}() }()
done := make(map[string]bool) done := make(fs.Done)
originalsPath := ind.originalsPath() originalsPath := ind.originalsPath()
optionsPath := filepath.Join(originalsPath, opt.Path) optionsPath := filepath.Join(originalsPath, opt.Path)
@ -134,7 +134,7 @@ func (ind *Index) Start(opt IndexOptions) map[string]bool {
return result return result
} }
done[fileName] = true done[fileName] = fs.Found
if !fs.FileExt.Media(fileName) { if !fs.FileExt.Media(fileName) {
return nil return nil
@ -166,12 +166,17 @@ func (ind *Index) Start(opt IndexOptions) map[string]bool {
var files MediaFiles var files MediaFiles
for _, f := range related.Files { for _, f := range related.Files {
if done[f.FileName()].Processed() {
continue
}
files = append(files, f) files = append(files, f)
filesIndexed++ filesIndexed++
done[f.FileName()] = true done[f.FileName()] = fs.Processed
} }
filesIndexed++ filesIndexed++
done[fileName] = fs.Processed
related.Files = files related.Files = files

View file

@ -635,7 +635,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
file.UpdatedIn = int64(time.Since(start)) file.UpdatedIn = int64(time.Since(start))
if err := file.Save(); err != nil { if err := file.Save(); err != nil {
log.Errorf("index: %s in %s (save file)", err, logName) log.Errorf("index: failed updating file %s", logName)
result.Status = IndexFailed result.Status = IndexFailed
result.Err = err result.Err = err
return result return result
@ -644,7 +644,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
file.CreatedIn = int64(time.Since(start)) file.CreatedIn = int64(time.Since(start))
if err := file.Create(); err != nil { if err := file.Create(); err != nil {
log.Errorf("index: %s in %s (create file)", err, logName) log.Errorf("index: failed adding file %s", logName)
result.Status = IndexFailed result.Status = IndexFailed
result.Err = err result.Err = err
return result return result

View file

@ -37,12 +37,12 @@ func (prg *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPh
} }
}() }()
var ignore map[string]bool var ignore fs.Done
if opt.Ignore != nil { if opt.Ignore != nil {
ignore = opt.Ignore ignore = opt.Ignore
} else { } else {
ignore = make(map[string]bool) ignore = make(fs.Done)
} }
purgedFiles = make(map[string]bool) purgedFiles = make(map[string]bool)
@ -76,7 +76,7 @@ func (prg *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPh
fileName := FileName(file.FileRoot, file.FileName) fileName := FileName(file.FileRoot, file.FileName)
if ignore[fileName] || purgedFiles[fileName] { if ignore[fileName].Exists() || purgedFiles[fileName] {
continue continue
} }

View file

@ -1,8 +1,10 @@
package photoprism package photoprism
import "github.com/photoprism/photoprism/pkg/fs"
type PurgeOptions struct { type PurgeOptions struct {
Path string Path string
Ignore map[string]bool Ignore fs.Done
Dry bool Dry bool
Hard bool Hard bool
} }

View file

@ -55,7 +55,7 @@ func (rs *Resample) Start(force bool) (err error) {
}() }()
} }
done := make(map[string]bool) done := make(fs.Done)
ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false) ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false)
if err := ignore.Dir(originalsPath); err != nil { if err := ignore.Dir(originalsPath); err != nil {
@ -91,7 +91,7 @@ func (rs *Resample) Start(force bool) (err error) {
return nil return nil
} }
done[fileName] = true done[fileName] = fs.Processed
relativeName := mf.RelName(originalsPath) relativeName := mf.RelName(originalsPath)

View file

@ -18,18 +18,14 @@ func FoldersByPath(rootName, rootPath, path string, recursive bool) (folders ent
folders = make(entity.Folders, len(dirs)) folders = make(entity.Folders, len(dirs))
for i, dir := range dirs { for i, dir := range dirs {
folder := entity.FindFolder(rootName, filepath.Join(path, dir)) newFolder := entity.NewFolder(rootName, filepath.Join(path, dir), nil)
if folder == nil { if err := newFolder.Create(); err == nil {
newFolder := entity.NewFolder(rootName, filepath.Join(path, dir), nil) folders[i] = newFolder
} else if folder := entity.FindFolder(rootName, filepath.Join(path, dir)); folder != nil {
if err := newFolder.Create(); err != nil {
log.Errorf("folders: %s (create folder)", err.Error())
} else {
folders[i] = newFolder
}
} else {
folders[i] = *folder folders[i] = *folder
} else {
log.Errorf("folders: %s (create folder)", err)
} }
} }

18
pkg/fs/done.go Normal file
View file

@ -0,0 +1,18 @@
package fs
type Status int8
const (
Found Status = 1
Processed Status = 2
)
type Done map[string]Status
func (s Status) Exists() bool {
return s > 0
}
func (s Status) Processed() bool {
return s >= Processed
}

View file

@ -6,8 +6,8 @@ import (
) )
// SkipWalk returns true if the file or directory should be skipped in godirwalk.Walk() // SkipWalk returns true if the file or directory should be skipped in godirwalk.Walk()
func SkipWalk(fileName string, isDir, isSymlink bool, done map[string]bool, ignore *IgnoreList) (skip bool, result error) { func SkipWalk(fileName string, isDir, isSymlink bool, done Done, ignore *IgnoreList) (skip bool, result error) {
isDone := done[fileName] isDone := done[fileName].Exists()
isIgnored := ignore.Ignore(fileName) isIgnored := ignore.Ignore(fileName)
if isSymlink { if isSymlink {
@ -18,11 +18,11 @@ func SkipWalk(fileName string, isDir, isSymlink bool, done map[string]bool, igno
if link, err := os.Stat(fileName); err == nil && link.IsDir() { if link, err := os.Stat(fileName); err == nil && link.IsDir() {
resolved, err := filepath.EvalSymlinks(fileName) resolved, err := filepath.EvalSymlinks(fileName)
if err != nil || isIgnored || isDone || done[resolved] { if err != nil || isIgnored || isDone || done[resolved].Exists() {
result = filepath.SkipDir result = filepath.SkipDir
} else { } else {
// Don't traverse symlinks if they are hidden or already done... // Don't traverse symlinks if they are hidden or already done...
done[resolved] = true done[resolved] = Found
} }
} }
} else if isDir { } else if isDir {
@ -38,7 +38,7 @@ func SkipWalk(fileName string, isDir, isSymlink bool, done map[string]bool, igno
} }
if skip { if skip {
done[fileName] = true done[fileName] = Found
} }
return skip, result return skip, result

View file

@ -9,10 +9,10 @@ import (
func TestSkipWalk(t *testing.T) { func TestSkipWalk(t *testing.T) {
t.Run("done", func(t *testing.T) { t.Run("done", func(t *testing.T) {
done := make(map[string]bool) done := make(Done)
ignore := NewIgnoreList(".ppignore", true, false) ignore := NewIgnoreList(".ppignore", true, false)
done["foo.jpg"] = true done["foo.jpg"] = Found
if skip, result := SkipWalk("testdata/directory", true, false, done, ignore); skip { if skip, result := SkipWalk("testdata/directory", true, false, done, ignore); skip {
assert.Nil(t, result) assert.Nil(t, result)
@ -20,13 +20,13 @@ func TestSkipWalk(t *testing.T) {
t.Fatal("should be skipped") t.Fatal("should be skipped")
} }
assert.True(t, done["foo.jpg"]) assert.True(t, done["foo.jpg"].Exists())
assert.True(t, done["testdata/directory"]) assert.True(t, done["testdata/directory"].Exists())
assert.Equal(t, 2, len(done)) assert.Equal(t, 2, len(done))
}) })
t.Run("symlink", func(t *testing.T) { t.Run("symlink", func(t *testing.T) {
done := make(map[string]bool) done := make(Done)
ignore := NewIgnoreList(".ppignore", true, false) ignore := NewIgnoreList(".ppignore", true, false)
if skip, result := SkipWalk("testdata/directory/subdirectory/symlink", false, true, done, ignore); skip { if skip, result := SkipWalk("testdata/directory/subdirectory/symlink", false, true, done, ignore); skip {
@ -47,15 +47,15 @@ func TestSkipWalk(t *testing.T) {
t.Fatal("should be skipped") t.Fatal("should be skipped")
} }
assert.True(t, done["testdata/linked"]) assert.True(t, done["testdata/linked"].Exists())
assert.True(t, done["testdata/directory/subdirectory/symlink"]) assert.True(t, done["testdata/directory/subdirectory/symlink"].Exists())
assert.True(t, done["testdata/directory/subdirectory/symlink/self"]) assert.True(t, done["testdata/directory/subdirectory/symlink/self"].Exists())
assert.True(t, done["testdata/directory/subdirectory/symlink/self/self"]) assert.True(t, done["testdata/directory/subdirectory/symlink/self/self"].Exists())
assert.Equal(t, 4, len(done)) assert.Equal(t, 4, len(done))
}) })
t.Run("godirwalk", func(t *testing.T) { t.Run("godirwalk", func(t *testing.T) {
done := make(map[string]bool) done := make(Done)
var skipped []string var skipped []string
var skippedDirs []string var skippedDirs []string
testPath := "testdata" testPath := "testdata"
@ -75,10 +75,10 @@ func TestSkipWalk(t *testing.T) {
return result return result
} }
done[fileName] = true done[fileName] = Found
if textName := TypeText.Find(fileName, false); textName != "" { if textName := TypeText.Find(fileName, false); textName != "" {
done[textName] = true done[textName] = Found
} }
return nil return nil