Fix indexer and add sort by file name #328
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
a7122ff4e1
commit
e796d036c2
|
@ -13,5 +13,10 @@
|
|||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"theme_color": "#333333",
|
||||
"background_color": "#333333"
|
||||
"background_color": "#333333",
|
||||
"permissions": [
|
||||
"geolocation",
|
||||
"downloads",
|
||||
"storage"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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')},
|
||||
],
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -122,6 +122,7 @@
|
|||
openPhoto: Function,
|
||||
editPhoto: Function,
|
||||
album: Object,
|
||||
filter: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -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')},
|
||||
],
|
||||
|
|
|
@ -24,6 +24,7 @@ export class Photo extends RestModel {
|
|||
TimeZone: "",
|
||||
PhotoPath: "",
|
||||
PhotoName: "",
|
||||
FileName: "",
|
||||
PhotoTitle: "",
|
||||
TitleSrc: "",
|
||||
PhotoDescription: "",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue