Fix indexer and add sort by file name #328

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-05-22 19:05:16 +02:00
parent a7122ff4e1
commit e796d036c2
17 changed files with 113 additions and 21 deletions

View file

@ -13,5 +13,10 @@
"display": "standalone",
"orientation": "portrait",
"theme_color": "#333333",
"background_color": "#333333"
"background_color": "#333333",
"permissions": [
"geolocation",
"downloads",
"storage"
]
}

View file

@ -154,6 +154,7 @@
{value: 'imported', text: this.$gettext('Recently added')},
{value: 'newest', text: this.$gettext('Newest first')},
{value: 'oldest', text: this.$gettext('Oldest first')},
{value: 'name', text: this.$gettext('Sort by file name')},
{value: 'similar', text: this.$gettext('Group by similarity')},
{value: 'relevance', text: this.$gettext('Most relevant')},
],

View file

@ -136,6 +136,14 @@
{{ photo.getPhotoInfo() }}
</button>
</template>
<template v-if="filter.order === 'name' && $config.feature('download')">
<br/>
<button @click.exact="downloadFile(index)"
title="Name">
<v-icon size="14">save</v-icon>
{{ photo.FileName }}
</button>
</template>
<template v-if="showLocation && photo.LocationID">
<br/>
<button @click.exact="openLocation(index)" title="Location">
@ -162,6 +170,7 @@
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
},
data() {
return {
@ -175,6 +184,13 @@
};
},
methods: {
downloadFile(index) {
const photo = this.photos[index];
const link = document.createElement('a')
link.href = "/api/v1/download/" + photo.FileHash;
link.download = photo.FileName;
link.click()
},
onSelect(ev, index) {
if (ev.shiftKey) {
this.selectRange(index);

View file

@ -53,7 +53,11 @@
</button>
</td>
<td class="p-photo-desc hidden-xs-only">
<button v-if="props.item.LocationID && showLocation" @click.stop.prevent="openLocation(props.index)"
<button @click.exact="downloadFile(props.index)"
title="Name" v-if="filter.order === 'name'">
{{ props.item.FileName }}
</button>
<button v-else-if="props.item.LocationID && showLocation" @click.stop.prevent="openLocation(props.index)"
style="user-select: none;">
{{ props.item.getLocation() }}
</button>
@ -86,6 +90,7 @@
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
},
data() {
let m = this.$gettext('Try using other terms and search options such as category, country and camera.');
@ -94,6 +99,8 @@
m += " " + this.$gettext("Non-photographic and low-quality images require a review before they appear in search results.");
}
let showName = this.filter.order === 'name'
return {
notFoundMessage: m,
'selected': [],
@ -102,9 +109,10 @@
{text: this.$gettext('Title'), value: 'PhotoTitle'},
{text: this.$gettext('Taken'), class: 'hidden-xs-only', value: 'TakenAt'},
{text: this.$gettext('Camera'), class: 'hidden-sm-and-down', value: 'CameraModel'},
{text: this.$gettext('Location'), class: 'hidden-xs-only', value: 'LocLabel'},
{text: showName ? this.$gettext('Name') : this.$gettext('Location'), class: 'hidden-xs-only', value: showName ? 'FileName' : 'LocLabel'},
{text: '', value: '', sortable: false, align: 'center'},
],
showName: showName,
showLocation: this.$config.settings().features.places,
hidePrivate: this.$config.settings().features.private,
mouseDown: {
@ -128,6 +136,13 @@
},
},
methods: {
downloadFile(index) {
const photo = this.photos[index];
const link = document.createElement('a')
link.href = "/api/v1/download/" + photo.FileHash;
link.download = photo.FileName;
link.click()
},
onSelect(ev, index) {
if (ev.shiftKey) {
this.selectRange(index);

View file

@ -122,6 +122,7 @@
openPhoto: Function,
editPhoto: Function,
album: Object,
filter: Object,
},
data() {
return {

View file

@ -170,6 +170,7 @@
{value: 'imported', text: this.$gettext('Recently added')},
{value: 'newest', text: this.$gettext('Newest first')},
{value: 'oldest', text: this.$gettext('Oldest first')},
{value: 'name', text: this.$gettext('Sort by file name')},
{value: 'similar', text: this.$gettext('Group by similarity')},
{value: 'relevance', text: this.$gettext('Most relevant')},
],

View file

@ -24,6 +24,7 @@ export class Photo extends RestModel {
TimeZone: "",
PhotoPath: "",
PhotoName: "",
FileName: "",
PhotoTitle: "",
TitleSrc: "",
PhotoDescription: "",

View file

@ -18,12 +18,14 @@
<p-photo-mosaic v-if="settings.view === 'mosaic'"
:photos="results"
:selection="selection"
:filter="filter"
:album="model"
:edit-photo="editPhoto"
:open-photo="openPhoto"></p-photo-mosaic>
<p-photo-list v-else-if="settings.view === 'list'"
:photos="results"
:selection="selection"
:filter="filter"
:album="model"
:open-photo="openPhoto"
:edit-photo="editPhoto"
@ -31,6 +33,7 @@
<p-photo-cards v-else
:photos="results"
:selection="selection"
:filter="filter"
:album="model"
:open-photo="openPhoto"
:edit-photo="editPhoto"

View file

@ -16,17 +16,20 @@
<p-photo-mosaic v-if="settings.view === 'mosaic'"
:photos="results"
:selection="selection"
:filter="filter"
:edit-photo="editPhoto"
:open-photo="openPhoto"></p-photo-mosaic>
<p-photo-list v-else-if="settings.view === 'list'"
:photos="results"
:selection="selection"
:filter="filter"
:open-photo="openPhoto"
:edit-photo="editPhoto"
:open-location="openLocation"></p-photo-list>
<p-photo-cards v-else
:photos="results"
:selection="selection"
:filter="filter"
:open-photo="openPhoto"
:edit-photo="editPhoto"
:open-location="openLocation"></p-photo-cards>

View file

@ -18,6 +18,7 @@ const (
FolderRootUnknown = ""
FolderRootOriginals = "originals"
FolderRootImport = "import"
RootPath = "/"
)
// Folder represents a file system directory.
@ -55,8 +56,8 @@ func NewFolder(root, pathName string, modTime *time.Time) Folder {
pathName = strings.Trim(pathName, string(os.PathSeparator))
if pathName == "" {
pathName = "/"
if pathName == RootPath {
pathName = ""
}
result := Folder{
@ -79,7 +80,7 @@ func (m *Folder) SetTitleFromPath() {
s := m.Path
s = strings.TrimSpace(s)
if s == "" || s == "/" {
if s == "" || s == RootPath {
s = m.Root
} else {
s = path.Base(s)
@ -103,13 +104,23 @@ func (m *Folder) SetTitleFromPath() {
m.FolderTitle = txt.Clip(s, txt.ClipDefault)
}
// Saves the entity using form data and stores it in the database.
func (m *Folder) Save(f form.Folder) error {
// Saves the complete entity in the database.
func (m *Folder) Create() error {
return Db().Create(m).Error
}
// Updates selected properties in the database.
func (m *Folder) Updates(values interface{}) error {
return Db().Model(m).Updates(values).Error
}
// SetForm updates the entity properties based on form values.
func (m *Folder) SetForm(f form.Folder) error {
if err := deepcopier.Copy(m).From(f); err != nil {
return err
}
return Db().Save(m).Error
return nil
}
// Returns a slice of folders in a given directory incl sub directories in recursive mode.

View file

@ -40,9 +40,15 @@ func TestNewFolder(t *testing.T) {
assert.Equal(t, "23 Birthday", folder.FolderTitle)
})
t.Run("name empty", func(t *testing.T) {
t.Run("empty", func(t *testing.T) {
folder := NewFolder(FolderRootOriginals, "", nil)
assert.Equal(t, "/", folder.Path)
assert.Equal(t, "", folder.Path)
assert.Equal(t, "Originals", folder.FolderTitle)
})
t.Run("root", func(t *testing.T) {
folder := NewFolder(FolderRootOriginals, RootPath, nil)
assert.Equal(t, "", folder.Path)
assert.Equal(t, "Originals", folder.FolderTitle)
})
}

View file

@ -116,6 +116,14 @@ func (imp *Import) Start(opt ImportOptions) map[string]bool {
}
if skip, result := fs.SkipWalk(fileName, isDir, isSymlink, done, ignore); skip {
if isDir && result != filepath.SkipDir {
folder := entity.NewFolder(entity.FolderRootImport, fs.RelativeName(fileName, imp.conf.ImportPath()), nil)
if err := folder.Create(); err == nil {
log.Infof("import: added folder /%s", folder.Path)
}
}
return result
}

View file

@ -120,6 +120,14 @@ func (ind *Index) Start(opt IndexOptions) map[string]bool {
isSymlink := info.IsSymlink()
if skip, result := fs.SkipWalk(fileName, isDir, isSymlink, done, ignore); skip {
if isDir && result != filepath.SkipDir {
folder := entity.NewFolder(entity.FolderRootOriginals, fs.RelativeName(fileName, originalsPath), nil)
if err := folder.Create(); err == nil {
log.Infof("index: added folder /%s", folder.Path)
}
}
return result
}

View file

@ -309,17 +309,15 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
photo.SetTakenAt(takenUtc, takenUtc, "", takenSrc)
}
if photo.NoTitle() {
if photo.HasLatLng() {
var locLabels classify.Labels
locKeywords, locLabels = photo.UpdateLocation(ind.conf.GeoCodingApi())
labels = append(labels, locLabels...)
} else {
log.Debugf("index: no coordinates in metadata for %s", txt.Quote(m.RelativeName(ind.originalsPath())))
if photo.HasLatLng() {
var locLabels classify.Labels
locKeywords, locLabels = photo.UpdateLocation(ind.conf.GeoCodingApi())
labels = append(labels, locLabels...)
} else {
log.Debugf("index: no coordinates in metadata for %s", txt.Quote(m.RelativeName(ind.originalsPath())))
photo.Place = &entity.UnknownPlace
photo.PlaceID = entity.UnknownPlace.ID
}
photo.Place = &entity.UnknownPlace
photo.PlaceID = entity.UnknownPlace.ID
}
}

View file

@ -289,6 +289,8 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
case entity.SortOrderSimilar:
s = s.Where("files.file_diff > 0")
s = s.Order("files.file_main_color, photos.location_id, files.file_diff, taken_at DESC, files.file_primary DESC")
case entity.SortOrderName:
s = s.Order("photos.photo_path, photos.photo_name, files.file_primary DESC")
default:
s = s.Order("taken_at DESC, photos.photo_uuid, files.file_primary DESC")
}

View file

@ -7,6 +7,10 @@ import (
// RelativeName returns the file name relative to directory.
func RelativeName(fileName, directory string) string {
if fileName == directory {
return ""
}
if index := strings.Index(fileName, directory); index == 0 {
if index := strings.LastIndex(directory, string(os.PathSeparator)); index == len(directory)-1 {
pos := len(directory)

View file

@ -7,6 +7,15 @@ import (
)
func TestRelativeName(t *testing.T) {
t.Run("same", func(t *testing.T) {
assert.Equal(t, "", RelativeName("/some/path", "/some/path"))
})
t.Run("short", func(t *testing.T) {
assert.Equal(t, "/some/", RelativeName("/some/", "/some/path"))
})
t.Run("empty", func(t *testing.T) {
assert.Equal(t, "", RelativeName("", "/some/path"))
})
t.Run("/some/path", func(t *testing.T) {
assert.Equal(t, "foo/bar.baz", RelativeName("/some/path/foo/bar.baz", "/some/path"))
})