Albums: Improve folder indexing

This commit is contained in:
Michael Mayer 2020-12-09 13:10:21 +01:00
parent b7cd2facb9
commit 004400b118
10 changed files with 88 additions and 23 deletions

View file

@ -135,7 +135,7 @@ export default [
path: "/folders",
component: Albums,
meta: {title: $gettext("Folders"), auth: true},
props: {view: "folder", staticFilter: {type: "folder", order: "slug"}},
props: {view: "folder", staticFilter: {type: "folder", order: "default"}},
},
{
name: "folder",

View file

@ -173,7 +173,7 @@ func TestNewFolderAlbum(t *testing.T) {
assert.Equal(t, "label:dog", album.AlbumFilter)
})
t.Run("title empty", func(t *testing.T) {
album := NewFolderAlbum("", "dogs", "label:dog")
album := NewFolderAlbum("", "dogs", "label:dog")
assert.Nil(t, album)
})
}

View file

@ -38,7 +38,7 @@ type Folder struct {
FileCount int `gorm:"-" json:"FileCount" yaml:"-"`
CreatedAt time.Time `json:"-" yaml:"-"`
UpdatedAt time.Time `json:"-" yaml:"-"`
ModifiedAt *time.Time `json:"ModifiedAt,omitempty" yaml:"-"`
ModifiedAt time.Time `json:"ModifiedAt,omitempty" yaml:"-"`
DeletedAt *time.Time `sql:"index" json:"-"`
}
@ -52,7 +52,7 @@ func (m *Folder) BeforeCreate(scope *gorm.Scope) error {
}
// NewFolder creates a new file system directory entity.
func NewFolder(root, pathName string, modTime *time.Time) Folder {
func NewFolder(root, pathName string, modTime time.Time) Folder {
now := Timestamp()
pathName = strings.Trim(pathName, string(os.PathSeparator))
@ -61,6 +61,14 @@ func NewFolder(root, pathName string, modTime *time.Time) Folder {
pathName = ""
}
year := 0
month := 0
if !modTime.IsZero() {
year = modTime.Year()
month = int(modTime.Month())
}
result := Folder{
FolderUID: rnd.PPID('d'),
Root: root,
@ -68,9 +76,9 @@ func NewFolder(root, pathName string, modTime *time.Time) Folder {
FolderType: TypeDefault,
FolderOrder: SortOrderName,
FolderCountry: UnknownCountry.ID,
FolderYear: 0,
FolderMonth: 0,
ModifiedAt: modTime,
FolderYear: year,
FolderMonth: month,
ModifiedAt: modTime.UTC(),
CreatedAt: now,
UpdatedAt: now,
}
@ -95,7 +103,11 @@ func (m *Folder) SetValuesFromPath() {
}
} else {
m.FolderCountry = txt.CountryCode(s)
m.FolderYear = txt.Year(s)
if year := txt.Year(s); year > 0 {
m.FolderYear = year
}
s = path.Base(s)
}

View file

@ -2,6 +2,7 @@ package entity
import (
"testing"
"time"
"github.com/photoprism/photoprism/internal/form"
@ -10,7 +11,7 @@ import (
func TestNewFolder(t *testing.T) {
t.Run("2020/05", func(t *testing.T) {
folder := NewFolder(RootOriginals, "2020/05", nil)
folder := NewFolder(RootOriginals, "2020/05", time.Now().UTC())
assert.Equal(t, RootOriginals, folder.Root)
assert.Equal(t, "2020/05", folder.Path)
assert.Equal(t, "May 2020", folder.FolderTitle)
@ -27,7 +28,7 @@ func TestNewFolder(t *testing.T) {
})
t.Run("/2020/05/01/", func(t *testing.T) {
folder := NewFolder(RootOriginals, "/2020/05/01/", nil)
folder := NewFolder(RootOriginals, "/2020/05/01/", time.Now().UTC())
assert.Equal(t, "2020/05/01", folder.Path)
assert.Equal(t, "May 2020", folder.FolderTitle)
assert.Equal(t, 2020, folder.FolderYear)
@ -36,7 +37,7 @@ func TestNewFolder(t *testing.T) {
})
t.Run("/2020/05/23/", func(t *testing.T) {
folder := NewFolder(RootImport, "/2020/05/23/", nil)
folder := NewFolder(RootImport, "/2020/05/23/", time.Now().UTC())
assert.Equal(t, "2020/05/23", folder.Path)
assert.Equal(t, "May 23, 2020", folder.FolderTitle)
assert.Equal(t, 2020, folder.FolderYear)
@ -45,7 +46,7 @@ func TestNewFolder(t *testing.T) {
})
t.Run("/2020/05/23/Iceland 2020", func(t *testing.T) {
folder := NewFolder(RootOriginals, "/2020/05/23/Iceland 2020", nil)
folder := NewFolder(RootOriginals, "/2020/05/23/Iceland 2020", time.Now().UTC())
assert.Equal(t, "2020/05/23/Iceland 2020", folder.Path)
assert.Equal(t, "Iceland 2020", folder.FolderTitle)
assert.Equal(t, 2020, folder.FolderYear)
@ -54,7 +55,7 @@ func TestNewFolder(t *testing.T) {
})
t.Run("/London/2020/05/23", func(t *testing.T) {
folder := NewFolder(RootOriginals, "/London/2020/05/23", nil)
folder := NewFolder(RootOriginals, "/London/2020/05/23", time.Now().UTC())
assert.Equal(t, "London/2020/05/23", folder.Path)
assert.Equal(t, "May 23, 2020", folder.FolderTitle)
assert.Equal(t, 2020, folder.FolderYear)
@ -63,7 +64,7 @@ func TestNewFolder(t *testing.T) {
})
t.Run("empty", func(t *testing.T) {
folder := NewFolder(RootOriginals, "", nil)
folder := NewFolder(RootOriginals, "", time.Time{})
assert.Equal(t, "", folder.Path)
assert.Equal(t, "Originals", folder.FolderTitle)
assert.Equal(t, 0, folder.FolderYear)
@ -72,7 +73,7 @@ func TestNewFolder(t *testing.T) {
})
t.Run("root", func(t *testing.T) {
folder := NewFolder(RootOriginals, RootPath, nil)
folder := NewFolder(RootOriginals, RootPath, time.Time{})
assert.Equal(t, "", folder.Path)
assert.Equal(t, "Originals", folder.FolderTitle)
assert.Equal(t, 0, folder.FolderYear)
@ -81,13 +82,13 @@ func TestNewFolder(t *testing.T) {
})
t.Run("pathName equals root path", func(t *testing.T) {
folder := NewFolder("", "", nil)
folder := NewFolder("", "", time.Now().UTC())
assert.Equal(t, "", folder.Path)
})
}
func TestFirstOrCreateFolder(t *testing.T) {
folder := NewFolder(RootOriginals, RootPath, nil)
folder := NewFolder(RootOriginals, RootPath, time.Now().UTC())
result := FirstOrCreateFolder(&folder)
if result == nil {
@ -119,7 +120,7 @@ func TestFirstOrCreateFolder(t *testing.T) {
func TestFolder_SetValuesFromPath(t *testing.T) {
t.Run("/", func(t *testing.T) {
folder := NewFolder("new", "", nil)
folder := NewFolder("new", "", time.Now().UTC())
folder.SetValuesFromPath()
assert.Equal(t, "New", folder.FolderTitle)
})
@ -147,7 +148,7 @@ func TestFindFolder(t *testing.T) {
func TestFolder_Updates(t *testing.T) {
t.Run("success", func(t *testing.T) {
folder := NewFolder("oldRoot", "oldPath", nil)
folder := NewFolder("oldRoot", "oldPath", time.Now().UTC())
assert.Equal(t, "oldRoot", folder.Root)
assert.Equal(t, "oldPath", folder.Path)
@ -168,7 +169,7 @@ func TestFolder_SetForm(t *testing.T) {
folderForm, err := form.NewFolder(formValues)
folder := NewFolder("oldRoot", "oldPath", nil)
folder := NewFolder("oldRoot", "oldPath", time.Now().UTC())
assert.Equal(t, "oldRoot", folder.Root)
assert.Equal(t, "oldPath", folder.Path)

View file

@ -124,7 +124,7 @@ func (imp *Import) Start(opt ImportOptions) fs.Done {
if skip, result := fs.SkipWalk(fileName, isDir, isSymlink, done, ignore); skip {
if isDir && result != filepath.SkipDir {
folder := entity.NewFolder(entity.RootImport, fs.RelName(fileName, imp.conf.ImportPath()), nil)
folder := entity.NewFolder(entity.RootImport, fs.RelName(fileName, imp.conf.ImportPath()), fs.BirthTime(fileName))
if err := folder.Create(); err == nil {
log.Infof("import: added folder /%s", folder.Path)

View file

@ -135,7 +135,7 @@ func (ind *Index) Start(opt IndexOptions) fs.Done {
if skip, result := fs.SkipWalk(fileName, isDir, isSymlink, done, ignore); skip {
if (isSymlink || isDir) && result != filepath.SkipDir {
folder := entity.NewFolder(entity.RootOriginals, relName, nil)
folder := entity.NewFolder(entity.RootOriginals, relName, fs.BirthTime(fileName))
if err := folder.Create(); err == nil {
log.Infof("index: added folder /%s", folder.Path)

View file

@ -224,6 +224,14 @@ func (m *Moments) Start() (err error) {
}
}
if err := query.UpdateFolderDates(); err != nil {
log.Errorf("moments: %s (update folder dates)", err.Error())
}
if err := query.UpdateAlbumDates(); err != nil {
log.Errorf("moments: %s (update album dates)", err.Error())
}
return nil
}

View file

@ -178,3 +178,14 @@ func AlbumSearch(f form.AlbumSearch) (results AlbumResults, err error) {
return results, nil
}
// UpdateAlbumDates updates album year, month and day based on indexed photo metadata.
func UpdateAlbumDates() error {
return UnscopedDb().Exec(`UPDATE albums
INNER JOIN
(SELECT photo_path, MAX(photo_year) AS max_photo_year, MAX(photo_month) AS max_photo_month, MAX(photo_day) AS max_photo_day
FROM photos WHERE taken_src = 'meta' AND photos.photo_quality >= 3 AND photos.deleted_at IS NULL
GROUP BY photo_path) AS p ON albums.album_path = p.photo_path
SET albums.album_year = p.max_photo_year, albums.album_month = p.max_photo_month, albums.album_day = p.max_photo_day
WHERE albums.album_type = 'folder' AND p.max_photo_year IS NOT NULL AND p.max_photo_year > 0`).Error
}

View file

@ -18,7 +18,7 @@ func FoldersByPath(rootName, rootPath, path string, recursive bool) (folders ent
folders = make(entity.Folders, len(dirs))
for i, dir := range dirs {
newFolder := entity.NewFolder(rootName, filepath.Join(path, dir), nil)
newFolder := entity.NewFolder(rootName, filepath.Join(path, dir), fs.BirthTime(filepath.Join(rootPath, dir)))
if err := newFolder.Create(); err == nil {
folders[i] = newFolder
@ -46,3 +46,14 @@ func AlbumFolders(threshold int) (folders entity.Folders, err error) {
return folders, nil
}
// UpdateFolderDates updates folder year, month and day based on indexed photo metadata.
func UpdateFolderDates() error {
return UnscopedDb().Exec(`UPDATE folders
INNER JOIN
(SELECT photo_path, MAX(photo_year) AS max_photo_year, MAX(photo_month) AS max_photo_month, MAX(photo_day) AS max_photo_day
FROM photos WHERE taken_src = 'meta' AND photos.photo_quality >= 3 AND photos.deleted_at IS NULL
GROUP BY photo_path) AS p ON folders.path = p.photo_path
SET folders.folder_year = p.max_photo_year, folders.folder_month = p.max_photo_month, folders.folder_day = p.max_photo_day
WHERE p.max_photo_year IS NOT NULL AND p.max_photo_year > 0`).Error
}

22
pkg/fs/birthtime.go Normal file
View file

@ -0,0 +1,22 @@
package fs
import (
"time"
"github.com/djherbis/times"
)
// BirthTime returns the create time of a file or folder.
func BirthTime(fileName string) time.Time {
s, err := times.Stat(fileName)
if err != nil {
return time.Now()
}
if s.HasBirthTime() {
return s.BirthTime()
}
return s.ModTime()
}