Rename "thumbnails" to "thumbs" and group api functions in one file

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-07-13 17:25:27 +02:00
parent dac846ba01
commit 4d42222caf
31 changed files with 662 additions and 654 deletions

View file

@ -33,7 +33,7 @@ import PhotoSwipeUI_Default from "photoswipe/dist/photoswipe-ui-default.js";
import Event from "pubsub-js";
import stripHtml from "string-strip-html";
const thumbs = window.__CONFIG__.thumbnails;
const thumbs = window.__CONFIG__.thumbs;
class Viewer {
constructor() {

View file

@ -278,7 +278,7 @@
model: Object,
},
data() {
const thumbs = this.$config.values.thumbnails;
const thumbs = this.$config.values.thumbs;
return {
options: options,

View file

@ -33,7 +33,7 @@ import Api from "common/api";
import {config} from "../session";
import {$gettext} from "common/vm";
const thumbs = window.__CONFIG__.thumbnails;
const thumbs = window.__CONFIG__.thumbs;
export class Thumb extends Model {
getDefaults() {

Binary file not shown.

View file

@ -58,7 +58,7 @@ msgstr "Auf den Link klicken, um ihn zu kopieren."
msgid "Account"
msgstr "Zugang"
#: src/dialog/photo/info.vue:122
#: src/dialog/photo/info.vue:128
msgid "Accuracy"
msgstr "Genauigkeit"
@ -78,7 +78,7 @@ msgstr "Link hinzufügen"
msgid "Add photos or videos from search results by selecting them."
msgstr "Fotos und Videos können über das Kontextmenü hinzugefügt werden."
#: src/dialog/account/add.vue:5 src/pages/settings/sync.vue:40
#: src/dialog/account/add.vue:5 src/pages/settings/sync.vue:46
msgid "Add Server"
msgstr "Server hinzufügen"
@ -204,7 +204,7 @@ msgstr ""
"Alternativ können Dateien auch direkt auf kompatible WebDAV-Server "
"hochgeladen z.B. Nextcloud."
#: src/dialog/photo/info.vue:114
#: src/dialog/photo/info.vue:120
msgid "Altitude"
msgstr "Höhe"
@ -244,7 +244,7 @@ msgstr "Übernehmen"
msgid "Archive"
msgstr "Archiv"
#: src/dialog/photo/info.vue:162
#: src/dialog/photo/info.vue:168
msgid "Archived"
msgstr "Archiviert"
@ -310,7 +310,7 @@ msgstr "Kalender"
msgid "Camera"
msgstr "Kamera"
#: src/dialog/photo/info.vue:67
#: src/dialog/photo/info.vue:73
msgid "Camera Serial"
msgstr "Kamera-Seriennummer"
@ -354,7 +354,7 @@ msgstr "Als privat markieren"
msgid "Change Status"
msgstr "Status ändern"
#: src/dialog/photo/info.vue:154
#: src/dialog/photo/info.vue:160
msgid "Checked"
msgstr "Geprüft"
@ -387,7 +387,7 @@ msgstr "Wahrscheinlichkeit"
msgid "Connect"
msgstr "Verbinden"
#: src/dialog/webdav.vue:4 src/pages/settings/sync.vue:45
#: src/dialog/webdav.vue:4 src/pages/settings/sync.vue:42
msgid "Connect via WebDAV"
msgstr "Mit WebDAV verbinden"
@ -439,7 +439,7 @@ msgstr "Land"
msgid "Create album"
msgstr "Album erstellen"
#: src/dialog/photo/info.vue:130
#: src/dialog/photo/info.vue:136
msgid "Created"
msgstr "Hinzugefügt"
@ -501,7 +501,7 @@ msgstr "Auflösung"
#: src/dialog/account/edit.vue:61 src/dialog/account/edit.vue:120
msgid "Disabled"
msgstr "Aus"
msgstr "Deaktiviert"
#: src/routes.js:290 src/routes.js:297 src/routes.js:304 src/routes.js:311
msgid "Discover"
@ -568,7 +568,7 @@ msgstr "Account bearbeiten"
msgid "Edit Photo"
msgstr "Foto bearbeiten"
#: src/dialog/photo/info.vue:146
#: src/dialog/photo/info.vue:152
msgid "Edited"
msgstr "Bearbeitet"
@ -632,7 +632,7 @@ msgstr "Fehler beim Importieren der hochgeladenen Dateien"
msgid "Fast"
msgstr "Schnell"
#: src/dialog/photo/info.vue:74
#: src/dialog/photo/info.vue:80
msgid "Favorite"
msgstr "Favorit"
@ -666,8 +666,8 @@ msgid ""
"Files with sequential names like 'IMG_1234 (2)' or 'IMG_1234 copy 2' belong "
"to the same photo."
msgstr ""
"Dateien mit Namen wie IMG_1234 (2) oder IMG_1234 copy 2 werden "
"zusammengefasst."
"Dateien mit Namen wie IMG_1234 (2) oder IMG_1234 copy 2 werden als "
"Stapel zusammengefasst."
#: src/dialog/photo/details.vue:448
msgid "Focal Length"
@ -856,11 +856,11 @@ msgstr "Kategorien gelöscht"
msgid "Language"
msgstr "Sprache"
#: src/pages/settings/sync.vue:111
#: src/pages/settings/sync.vue:113
msgid "Last Backup"
msgstr "Letztes Backup"
#: src/dialog/photo/details.vue:443 src/dialog/photo/info.vue:98
#: src/dialog/photo/details.vue:443 src/dialog/photo/info.vue:104
msgid "Latitude"
msgstr "Breitengrad"
@ -938,7 +938,7 @@ msgstr "Abmelden"
msgid "Logs"
msgstr "Logs"
#: src/dialog/photo/details.vue:444 src/dialog/photo/info.vue:106
#: src/dialog/photo/details.vue:444 src/dialog/photo/info.vue:112
msgid "Longitude"
msgstr "Längengrad"
@ -1032,7 +1032,7 @@ msgstr "Name"
msgid "Name too long"
msgstr "Name zu lang"
#: src/pages/settings/sync.vue:122 src/resources/options.js:178
#: src/pages/settings/sync.vue:129 src/resources/options.js:178
#: src/resources/options.js:188
msgid "Never"
msgstr "Nie"
@ -1046,8 +1046,8 @@ msgstr "Neues Passwort"
msgid "Newest first"
msgstr "Neueste zuerst"
#: src/dialog/photo/archive.vue:13 src/dialog/photo/info.vue:187
#: src/dialog/photo/info.vue:218 src/dialog/photo/info.vue:245
#: src/dialog/photo/archive.vue:13 src/dialog/photo/info.vue:160
#: src/dialog/photo/info.vue:187 src/dialog/photo/info.vue:214
msgid "No"
msgstr "Nein"
@ -1113,7 +1113,7 @@ msgstr ""
"sollen, ist kein Import notwendig. Die Ordner müssen in diesem Fall manuell "
"verwaltet und indiziert werden."
#: src/pages/settings/sync.vue:32
#: src/pages/settings/sync.vue:34
msgid "Note:"
msgstr "Hinweis:"
@ -1299,7 +1299,7 @@ msgstr "Voransicht"
msgid "Primary"
msgstr "Hauptdatei"
#: src/component/navigation.vue:129 src/dialog/photo/info.vue:82
#: src/component/navigation.vue:129 src/dialog/photo/info.vue:88
#: src/routes.js:171
msgid "Private"
msgstr "Privat"
@ -1312,7 +1312,7 @@ msgstr "Purpur"
msgid "Quality Filter"
msgstr "Qualitätsfilter"
#: src/dialog/photo/info.vue:53
#: src/dialog/photo/info.vue:59
msgid "Quality Score"
msgstr "Qualität"
@ -1370,7 +1370,7 @@ msgstr ""
msgid "Request failed - invalid response"
msgstr "Anfrage fehlgeschlagen - ungültige Antwort"
#: src/dialog/photo/info.vue:61
#: src/dialog/photo/info.vue:67
msgid "Resolution"
msgstr "Auflösung"
@ -1395,7 +1395,7 @@ msgstr "Russisch"
msgid "Save"
msgstr "Speichern"
#: src/dialog/photo/info.vue:90
#: src/dialog/photo/info.vue:96
msgid "Scan"
msgstr "Scan"
@ -1440,7 +1440,7 @@ msgstr "Auswahl archiviert"
msgid "Selection restored"
msgstr "Auswahl wiederhergestellt"
#: src/pages/settings/sync.vue:108
#: src/pages/settings/sync.vue:109
msgid "Server"
msgstr "Server"
@ -1561,7 +1561,7 @@ msgid ""
"Support for additional services, like Google Drive, will be added over time."
msgstr "Die Unterstützung weiterer Dienste, wie Google Drive, ist geplant."
#: src/pages/settings/sync.vue:110
#: src/pages/settings/sync.vue:111
msgid "Sync"
msgstr "Sync"
@ -1569,7 +1569,8 @@ msgstr "Sync"
msgid "Sync raw images"
msgstr "RAW-Dateien sichern"
#: src/component/photo/list.vue:114 src/share/photo/list.vue:93
#: src/component/photo/list.vue:114 src/dialog/photo/info.vue:53
#: src/share/photo/list.vue:93
msgid "Taken"
msgstr "Aufgenommen"
@ -1605,7 +1606,7 @@ msgstr ""
msgid "Theme"
msgstr "Theme"
#: src/dialog/webdav.vue:17 src/pages/settings/sync.vue:34
#: src/dialog/webdav.vue:17 src/pages/settings/sync.vue:36
msgid ""
"This mounts the originals folder as a network drive and allows you to open, "
"edit, and delete files from your computer or smartphone as if they were "
@ -1669,7 +1670,7 @@ msgstr "Typ"
msgid "UID"
msgstr "UID"
#: src/dialog/photo/details.vue:423 src/dialog/photo/info.vue:221
#: src/dialog/photo/details.vue:423 src/dialog/photo/info.vue:227
#: src/model/album.js:122 src/model/photo.js:399 src/model/photo.js:413
#: src/model/photo.js:436 src/model/photo.js:448 src/model/photo.js:525
#: src/model/photo.js:538 src/pages/library/errors.vue:196
@ -1688,13 +1689,13 @@ msgstr "Unsortiert"
msgid "Unstack"
msgstr "Aus Stapel entfernen"
#: src/dialog/photo/files.vue:120 src/dialog/photo/info.vue:138
#: src/dialog/photo/files.vue:120 src/dialog/photo/info.vue:144
msgid "Updated"
msgstr "Geändert"
#: src/dialog/share/upload.vue:35 src/dialog/upload.vue:8
#: src/dialog/upload.vue:54 src/pages/library/import.vue:38
#: src/pages/settings/general.vue:319 src/pages/settings/sync.vue:109
#: src/pages/settings/general.vue:319 src/pages/settings/sync.vue:110
msgid "Upload"
msgstr "Upload"
@ -1766,7 +1767,7 @@ msgid "WebDAV clients can connect to PhotoPrism using the following URL:"
msgstr ""
"WebDAV-Clients können sich über die folgende URL mit PhotoPrism verbinden:"
#: src/pages/settings/sync.vue:33
#: src/pages/settings/sync.vue:35
msgid ""
"WebDAV clients, like Microsofts Windows Explorer or Apple's Finder, can "
"connect directly to PhotoPrism."
@ -1801,8 +1802,8 @@ msgstr "Gelb"
#: src/dialog/photo/archive.vue:15 src/dialog/photo/files.vue:80
#: src/dialog/photo/files.vue:86 src/dialog/photo/files.vue:104
#: src/dialog/photo/files.vue:110 src/dialog/photo/info.vue:186
#: src/dialog/photo/info.vue:217 src/dialog/photo/info.vue:244
#: src/dialog/photo/files.vue:110 src/dialog/photo/info.vue:159
#: src/dialog/photo/info.vue:186 src/dialog/photo/info.vue:213
msgid "Yes"
msgstr "Ja"

File diff suppressed because one or more lines are too long

View file

@ -53,7 +53,7 @@ msgstr ""
msgid "Account"
msgstr ""
#: src/dialog/photo/info.vue:122
#: src/dialog/photo/info.vue:128
msgid "Accuracy"
msgstr ""
@ -74,7 +74,7 @@ msgid "Add photos or videos from search results by selecting them."
msgstr ""
#: src/dialog/account/add.vue:5
#: src/pages/settings/sync.vue:40
#: src/pages/settings/sync.vue:46
msgid "Add Server"
msgstr ""
@ -206,7 +206,7 @@ msgstr ""
msgid "Alternatively, you can upload files directly to WebDAV servers like Nextcloud."
msgstr ""
#: src/dialog/photo/info.vue:114
#: src/dialog/photo/info.vue:120
msgid "Altitude"
msgstr ""
@ -248,7 +248,7 @@ msgstr ""
msgid "Archive"
msgstr ""
#: src/dialog/photo/info.vue:162
#: src/dialog/photo/info.vue:168
msgid "Archived"
msgstr ""
@ -319,7 +319,7 @@ msgstr ""
msgid "Camera"
msgstr ""
#: src/dialog/photo/info.vue:67
#: src/dialog/photo/info.vue:73
msgid "Camera Serial"
msgstr ""
@ -375,7 +375,7 @@ msgstr ""
msgid "Change Status"
msgstr ""
#: src/dialog/photo/info.vue:154
#: src/dialog/photo/info.vue:160
msgid "Checked"
msgstr ""
@ -411,7 +411,7 @@ msgid "Connect"
msgstr ""
#: src/dialog/webdav.vue:4
#: src/pages/settings/sync.vue:45
#: src/pages/settings/sync.vue:42
msgid "Connect via WebDAV"
msgstr ""
@ -474,7 +474,7 @@ msgstr ""
msgid "Create album"
msgstr ""
#: src/dialog/photo/info.vue:130
#: src/dialog/photo/info.vue:136
msgid "Created"
msgstr ""
@ -619,7 +619,7 @@ msgstr ""
msgid "Edit Photo"
msgstr ""
#: src/dialog/photo/info.vue:146
#: src/dialog/photo/info.vue:152
msgid "Edited"
msgstr ""
@ -682,7 +682,7 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/dialog/photo/info.vue:74
#: src/dialog/photo/info.vue:80
msgid "Favorite"
msgstr ""
@ -903,12 +903,12 @@ msgstr ""
msgid "Language"
msgstr ""
#: src/pages/settings/sync.vue:111
#: src/pages/settings/sync.vue:113
msgid "Last Backup"
msgstr ""
#: src/dialog/photo/details.vue:443
#: src/dialog/photo/info.vue:98
#: src/dialog/photo/info.vue:104
msgid "Latitude"
msgstr ""
@ -991,7 +991,7 @@ msgid "Logs"
msgstr ""
#: src/dialog/photo/details.vue:444
#: src/dialog/photo/info.vue:106
#: src/dialog/photo/info.vue:112
msgid "Longitude"
msgstr ""
@ -1102,7 +1102,7 @@ msgstr ""
msgid "Name too long"
msgstr ""
#: src/pages/settings/sync.vue:122
#: src/pages/settings/sync.vue:129
#: src/resources/options.js:178
#: src/resources/options.js:188
msgid "Never"
@ -1119,9 +1119,9 @@ msgid "Newest first"
msgstr ""
#: src/dialog/photo/archive.vue:13
#: src/dialog/photo/info.vue:160
#: src/dialog/photo/info.vue:187
#: src/dialog/photo/info.vue:218
#: src/dialog/photo/info.vue:245
#: src/dialog/photo/info.vue:214
msgid "No"
msgstr ""
@ -1182,7 +1182,7 @@ msgstr ""
msgid "Note that you can as well manage and re-index your originals manually."
msgstr ""
#: src/pages/settings/sync.vue:32
#: src/pages/settings/sync.vue:34
msgid "Note:"
msgstr ""
@ -1373,7 +1373,7 @@ msgid "Primary"
msgstr ""
#: src/component/navigation.vue:129
#: src/dialog/photo/info.vue:82
#: src/dialog/photo/info.vue:88
#: src/routes.js:171
msgid "Private"
msgstr ""
@ -1386,7 +1386,7 @@ msgstr ""
msgid "Quality Filter"
msgstr ""
#: src/dialog/photo/info.vue:53
#: src/dialog/photo/info.vue:59
msgid "Quality Score"
msgstr ""
@ -1444,7 +1444,7 @@ msgstr ""
msgid "Request failed - invalid response"
msgstr ""
#: src/dialog/photo/info.vue:61
#: src/dialog/photo/info.vue:67
msgid "Resolution"
msgstr ""
@ -1472,7 +1472,7 @@ msgstr ""
msgid "Save"
msgstr ""
#: src/dialog/photo/info.vue:90
#: src/dialog/photo/info.vue:96
msgid "Scan"
msgstr ""
@ -1524,7 +1524,7 @@ msgstr ""
msgid "Selection restored"
msgstr ""
#: src/pages/settings/sync.vue:108
#: src/pages/settings/sync.vue:109
msgid "Server"
msgstr ""
@ -1655,7 +1655,7 @@ msgstr ""
msgid "Support for additional services, like Google Drive, will be added over time."
msgstr ""
#: src/pages/settings/sync.vue:110
#: src/pages/settings/sync.vue:111
msgid "Sync"
msgstr ""
@ -1664,6 +1664,7 @@ msgid "Sync raw images"
msgstr ""
#: src/component/photo/list.vue:114
#: src/dialog/photo/info.vue:53
#: src/share/photo/list.vue:93
msgid "Taken"
msgstr ""
@ -1699,7 +1700,7 @@ msgid "Theme"
msgstr ""
#: src/dialog/webdav.vue:17
#: src/pages/settings/sync.vue:34
#: src/pages/settings/sync.vue:36
msgid "This mounts the originals folder as a network drive and allows you to open, edit, and delete files from your computer or smartphone as if they were local."
msgstr ""
@ -1769,7 +1770,7 @@ msgid "UID"
msgstr ""
#: src/dialog/photo/details.vue:423
#: src/dialog/photo/info.vue:221
#: src/dialog/photo/info.vue:227
#: src/model/album.js:122
#: src/model/photo.js:399
#: src/model/photo.js:413
@ -1798,7 +1799,7 @@ msgid "Unstack"
msgstr ""
#: src/dialog/photo/files.vue:120
#: src/dialog/photo/info.vue:138
#: src/dialog/photo/info.vue:144
msgid "Updated"
msgstr ""
@ -1807,7 +1808,7 @@ msgstr ""
#: src/dialog/upload.vue:54
#: src/pages/library/import.vue:38
#: src/pages/settings/general.vue:319
#: src/pages/settings/sync.vue:109
#: src/pages/settings/sync.vue:110
msgid "Upload"
msgstr ""
@ -1885,7 +1886,7 @@ msgstr ""
msgid "WebDAV clients can connect to PhotoPrism using the following URL:"
msgstr ""
#: src/pages/settings/sync.vue:33
#: src/pages/settings/sync.vue:35
msgid "WebDAV clients, like Microsofts Windows Explorer or Apple's Finder, can connect directly to PhotoPrism."
msgstr ""
@ -1921,9 +1922,9 @@ msgstr ""
#: src/dialog/photo/files.vue:86
#: src/dialog/photo/files.vue:104
#: src/dialog/photo/files.vue:110
#: src/dialog/photo/info.vue:159
#: src/dialog/photo/info.vue:186
#: src/dialog/photo/info.vue:217
#: src/dialog/photo/info.vue:244
#: src/dialog/photo/info.vue:213
msgid "Yes"
msgstr ""

View file

@ -1,7 +1,7 @@
import Api from "common/api";
import MockAdapter from "axios-mock-adapter";
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbnails":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbs":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
let chai = require("chai/chai");
let assert = chai.assert;

View file

@ -2,7 +2,7 @@ import Clipboard from "common/clipboard";
import Photo from "model/photo";
import Album from "model/album";
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbnails":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbs":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
let chai = require("chai/chai");
let assert = chai.assert;

View file

@ -33,7 +33,7 @@ window.__CONFIG__ = {
"Slug": "iceland",
"Name": "Iceland"
}, {"ID": "zz", "Slug": "zz", "Name": "Unknown"}],
"thumbnails": [{"Name": "fit_720", "Width": 720, "Height": 720}, {
"thumbs": [{"Name": "fit_720", "Width": 720, "Height": 720}, {
"Name": "fit_2048",
"Width": 2048,
"Height": 2048

View file

@ -30,7 +30,7 @@ window.__CONFIG__ = {
"Slug": "iceland",
"Name": "Iceland"
}, {"ID": "zz", "Slug": "zz", "Name": "Unknown"}],
"thumbnails": [{"Name": "fit_720", "Width": 720, "Height": 720}, {
"thumbs": [{"Name": "fit_720", "Width": 720, "Height": 720}, {
"Name": "fit_2048",
"Width": 2048,
"Height": 2048

View file

@ -1,7 +1,7 @@
import Util from "common/util";
import MockAdapter from "axios-mock-adapter";
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbnails":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbs":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
let chai = require("chai/chai");
let assert = chai.assert;
@ -55,4 +55,4 @@ describe("common/util", () => {
const result = Util.truncate("teststring for mocha", 5, "ng");
assert.equal(result, "tesng");
});
});
});

View file

@ -2,7 +2,7 @@ import Label from "model/label";
import MockAdapter from "axios-mock-adapter";
import Api from "common/api";
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbnails":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbs":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
let chai = require("chai/chai");
let assert = chai.assert;

View file

@ -2,7 +2,7 @@ import Photo from "model/photo";
import MockAdapter from "axios-mock-adapter";
import Api from "common/api";
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbnails":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
window.__CONFIG__ = {"name":"PhotoPrism","version":"200531-4684f66-Linux-x86_64-DEBUG","copyright":"(c) 2018-2020 PhotoPrism.org \u003chello@photoprism.org\u003e","flags":"public debug experimental settings","siteUrl":"http://localhost:2342/","siteTitle":"PhotoPrism","siteCaption":"Browse your life","siteDescription":"Personal Photo Management powered by Go and Google TensorFlow. Free and open-source.","siteAuthor":"Anonymous","debug":true,"readonly":false,"uploadNSFW":false,"public":true,"experimental":true,"disableSettings":false,"albumCategories":null,"albums":[],"cameras":[{"ID":2,"Slug":"olympus-c2500l","Name":"Olympus C2500L","Make":"Olympus","Model":"C2500L"},{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown"}],"lenses":[{"ID":1,"Slug":"zz","Name":"Unknown","Make":"","Model":"Unknown","Type":""}],"countries":[{"ID":"de","Slug":"germany","Name":"Germany"},{"ID":"is","Slug":"iceland","Name":"Iceland"},{"ID":"zz","Slug":"zz","Name":"Unknown"}],"thumbs":[{"Name":"fit_720","Width":720,"Height":720},{"Name":"fit_2048","Width":2048,"Height":2048},{"Name":"fit_1280","Width":1280,"Height":1024},{"Name":"fit_1920","Width":1920,"Height":1200},{"Name":"fit_2560","Width":2560,"Height":1600},{"Name":"fit_3840","Width":3840,"Height":2400}],"downloadToken":"1uhovi0e","previewToken":"static","jsHash":"0fd34136","cssHash":"2b327230","settings":{"theme":"default","language":"en","templates":{"default":"index.tmpl"},"maps":{"animate":0,"style":"streets"},"features":{"archive":true,"private":true,"review":true,"upload":true,"import":true,"files":true,"moments":true,"labels":true,"places":true,"download":true,"edit":true,"share":true,"logs":true},"import":{"path":"/","move":false},"index":{"path":"/","convert":true,"rescan":false,"group":true}},"count":{"cameras":1,"lenses":0,"countries":2,"photos":126,"videos":0,"hidden":3,"favorites":1,"private":0,"review":0,"stories":0,"albums":0,"moments":0,"months":0,"folders":0,"files":255,"places":0,"labels":13,"labelMaxPhotos":1},"pos":{"uid":"","loc":"","utc":"0001-01-01T00:00:00Z","lat":0,"lng":0},"years":[2003,2002],"colors":[{"Example":"#AB47BC","Name":"Purple","Slug":"purple"},{"Example":"#FF00FF","Name":"Magenta","Slug":"magenta"},{"Example":"#EC407A","Name":"Pink","Slug":"pink"},{"Example":"#EF5350","Name":"Red","Slug":"red"},{"Example":"#FFA726","Name":"Orange","Slug":"orange"},{"Example":"#D4AF37","Name":"Gold","Slug":"gold"},{"Example":"#FDD835","Name":"Yellow","Slug":"yellow"},{"Example":"#CDDC39","Name":"Lime","Slug":"lime"},{"Example":"#66BB6A","Name":"Green","Slug":"green"},{"Example":"#009688","Name":"Teal","Slug":"teal"},{"Example":"#00BCD4","Name":"Cyan","Slug":"cyan"},{"Example":"#2196F3","Name":"Blue","Slug":"blue"},{"Example":"#A1887F","Name":"Brown","Slug":"brown"},{"Example":"#F5F5F5","Name":"White","Slug":"white"},{"Example":"#9E9E9E","Name":"Grey","Slug":"grey"},{"Example":"#212121","Name":"Black","Slug":"black"}],"categories":[{"UID":"lqb6y631re96cper","Slug":"animal","Name":"Animal"},{"UID":"lqb6y5gvo9avdfx5","Slug":"architecture","Name":"Architecture"},{"UID":"lqb6y633nhfj1uzt","Slug":"bird","Name":"Bird"},{"UID":"lqb6y633g3hxg1aq","Slug":"farm","Name":"Farm"},{"UID":"lqb6y4i1ez9cw5bi","Slug":"nature","Name":"Nature"},{"UID":"lqb6y4f2v7dw8irs","Slug":"plant","Name":"Plant"},{"UID":"lqb6y6s2ohhmu0fn","Slug":"reptile","Name":"Reptile"},{"UID":"lqb6y6ctgsq2g2np","Slug":"water","Name":"Water"}],"clip":160,"server":{"cores":2,"routines":23,"memory":{"used":1224531272,"reserved":1416904088,"info":"Used 1.2 GB / Reserved 1.4 GB"}}};
let chai = require("chai/chai");
let assert = chai.assert;

View file

@ -2,12 +2,10 @@ package api
import (
"archive/zip"
"encoding/json"
"fmt"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@ -20,7 +18,6 @@ import (
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd"
@ -503,115 +500,3 @@ func DownloadAlbum(router *gin.RouterGroup) {
}
})
}
// GET /api/v1/albums/:uid/t/:token/:type
//
// Parameters:
// uid: string Album UID
// type: string Thumbnail type, see photoprism.ThumbnailTypes
func AlbumThumbnail(router *gin.RouterGroup) {
router.GET("/albums/:uid/t/:token/:type", func(c *gin.Context) {
if InvalidPreviewToken(c) {
c.Data(http.StatusForbidden, "image/svg+xml", albumIconSvg)
return
}
start := time.Now()
conf := service.Config()
typeName := c.Param("type")
uid := c.Param("uid")
thumbType, ok := thumb.Types[typeName]
if !ok {
log.Errorf("album-thumbnail: invalid type %s", typeName)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
cache := service.Cache()
cacheKey := fmt.Sprintf("album-thumbnail:%s:%s", uid, typeName)
if cacheData, err := cache.Get(cacheKey); err == nil {
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
var cached ThumbCache
if err := json.Unmarshal(cacheData, &cached); err != nil {
log.Errorf("album-thumbnail: %s not found", uid)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
if !fs.FileExists(cached.FileName) {
log.Errorf("album-thumbnail: %s not found", uid)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
if c.Query("download") != "" {
c.FileAttachment(cached.FileName, cached.ShareName)
} else {
c.File(cached.FileName)
}
return
}
f, err := query.AlbumCoverByUID(uid)
if err != nil {
log.Debugf("album-thumbnail: no photos yet, using generic image for %s", uid)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("album-thumbnail: could not find original for %s", fileName)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore.
log.Warnf("album-thumbnail: %s is missing", txt.Quote(f.FileName))
logError("album-thumbnail", f.Update("FileMissing", true))
return
}
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.ExceedsLimit() && c.Query("download") == "" {
log.Debugf("album-thumbnail: using original, size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
c.File(fileName)
return
}
var thumbnail string
if conf.ThumbUncached() || thumbType.OnDemand() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
}
if err != nil {
log.Errorf("album: %s", err)
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return
} else if thumbnail == "" {
log.Errorf("album-thumbnail: %s has empty thumb name - bug?", filepath.Base(fileName))
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
if cached, err := json.Marshal(ThumbCache{thumbnail, f.ShareFileName()}); err == nil {
logError("album-thumbnail", cache.Set(cacheKey, cached))
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
}
if c.Query("download") != "" {
c.FileAttachment(thumbnail, f.ShareFileName())
} else {
c.File(thumbnail)
}
})
}

View file

@ -266,25 +266,3 @@ func TestDownloadAlbum(t *testing.T) {
assert.Equal(t, http.StatusOK, r.Code)
})
}
func TestAlbumThumbnail(t *testing.T) {
t.Run("invalid type", func(t *testing.T) {
app, router, conf := NewApiTest()
AlbumThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba7/t/"+conf.PreviewToken()+"/xxx")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("album has no photo (because is not existing)", func(t *testing.T) {
app, router, conf := NewApiTest()
AlbumThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/albums/987-986435/t/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("album: could not find original", func(t *testing.T) {
app, router, conf := NewApiTest()
AlbumThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8/t/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -1,12 +1,8 @@
package api
import (
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
@ -15,11 +11,7 @@ import (
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
)
@ -168,117 +160,3 @@ func DislikeLabel(router *gin.RouterGroup) {
c.JSON(http.StatusOK, http.Response{})
})
}
// GET /api/v1/labels/:uid/t/:token/:type
//
// Parameters:
// uid: string Label UID
// type: string Thumbnail type, see photoprism.ThumbnailTypes
func LabelThumbnail(router *gin.RouterGroup) {
router.GET("/labels/:uid/t/:token/:type", func(c *gin.Context) {
if InvalidPreviewToken(c) {
c.Data(http.StatusForbidden, "image/svg+xml", labelIconSvg)
return
}
start := time.Now()
conf := service.Config()
typeName := c.Param("type")
uid := c.Param("uid")
thumbType, ok := thumb.Types[typeName]
if !ok {
log.Errorf("label-thumbnail: invalid type %s", txt.Quote(typeName))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
cache := service.Cache()
cacheKey := fmt.Sprintf("label-thumbnail:%s:%s", uid, typeName)
if cacheData, err := cache.Get(cacheKey); err == nil {
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
var cached ThumbCache
if err := json.Unmarshal(cacheData, &cached); err != nil {
log.Errorf("label-thumbnail: %s not found", uid)
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
if !fs.FileExists(cached.FileName) {
log.Errorf("label-thumbnail: %s not found", uid)
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
if c.Query("download") != "" {
c.FileAttachment(cached.FileName, cached.ShareName)
} else {
c.File(cached.FileName)
}
return
}
f, err := query.LabelThumbByUID(uid)
if err != nil {
log.Errorf(err.Error())
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("label-thumbnail: file %s is missing", txt.Quote(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore.
logError("label-thumbnail", f.Update("FileMissing", true))
return
}
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.ExceedsLimit() {
log.Debugf("label-thumbnail: using original, size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
c.File(fileName)
return
}
var thumbnail string
if conf.ThumbUncached() || thumbType.OnDemand() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
}
if err != nil {
log.Errorf("label-thumbnail: %s", err)
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
} else if thumbnail == "" {
log.Errorf("label-thumbnail: %s has empty thumb name - bug?", filepath.Base(fileName))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
if cached, err := json.Marshal(ThumbCache{thumbnail, f.ShareFileName()}); err == nil {
logError("label-thumbnail", cache.Set(cacheKey, cached))
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
}
if c.Query("download") != "" {
c.FileAttachment(thumbnail, f.ShareFileName())
} else {
c.File(thumbnail)
}
})
}

View file

@ -108,25 +108,3 @@ func TestDislikeLabel(t *testing.T) {
assert.Equal(t, "false", val2.String())
})
}
func TestLabelThumbnail(t *testing.T) {
t.Run("invalid type", func(t *testing.T) {
app, router, conf := NewApiTest()
LabelThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/labels/lt9k3pw1wowuy3c2/t/"+conf.PreviewToken()+"/xxx")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("invalid label", func(t *testing.T) {
app, router, conf := NewApiTest()
LabelThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/labels/xxx/t/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("could not find original", func(t *testing.T) {
app, router, conf := NewApiTest()
LabelThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/labels/lt9k3pw1wowuy3c3/t/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -329,85 +329,3 @@ func PhotoFilePrimary(router *gin.RouterGroup) {
c.JSON(http.StatusOK, p)
})
}
// POST /api/v1/photos/:uid/files/:file_uid/unstack
//
// Parameters:
// uid: string Photo UID as returned by the API
// file_uid: string File UID as returned by the API
func PhotoFileUnstack(router *gin.RouterGroup) {
router.POST("/photos/:uid/files/:file_uid/unstack", func(c *gin.Context) {
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionUpdate)
if s.Invalid() {
AbortUnauthorized(c)
return
}
photoUID := c.Param("uid")
fileUID := c.Param("file_uid")
file, err := query.FileByUID(fileUID)
if err != nil {
log.Errorf("photo: %s (unstack)", err)
AbortEntityNotFound(c)
return
}
if file.FilePrimary {
log.Errorf("photo: can't unstack primary file")
AbortBadRequest(c)
return
}
existingPhoto := *file.Photo
newPhoto := entity.NewPhoto()
if err := newPhoto.Create(); err != nil {
log.Errorf("photo: %s (unstack)", err.Error())
AbortSaveFailed(c)
return
}
file.Photo = &newPhoto
file.PhotoID = newPhoto.ID
file.PhotoUID = newPhoto.PhotoUID
if err := file.Save(); err != nil {
log.Errorf("photo: %s (unstack)", err.Error())
AbortSaveFailed(c)
return
}
fileName := photoprism.FileName(file.FileRoot, file.FileName)
f, err := photoprism.NewMediaFile(fileName)
if err != nil {
log.Errorf("photo: %s (unstack)", err)
AbortEntityNotFound(c)
return
}
if err := service.Index().MediaFile(f, photoprism.IndexOptions{Rescan: true}, existingPhoto.OriginalName).Error; err != nil {
log.Errorf("photo: %s (unstack)", err)
AbortSaveFailed(c)
return
}
PublishPhotoEvent(EntityCreated, file.PhotoUID, c)
PublishPhotoEvent(EntityUpdated, photoUID, c)
event.SuccessMsg(i18n.MsgFileUnstacked)
p, err := query.PhotoPreloadByUID(photoUID)
if err != nil {
AbortEntityNotFound(c)
return
}
c.JSON(http.StatusOK, p)
})
}

View file

@ -1,162 +0,0 @@
package api
import (
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
)
type ThumbCache struct {
FileName string
ShareName string
}
type ByteCache struct {
Data []byte
}
// GET /api/v1/t/:hash/:token/:type
//
// Parameters:
// hash: string The file hash as returned by the search API
// type: string Thumbnail type, see photoprism.ThumbnailTypes
func GetThumbnail(router *gin.RouterGroup) {
router.GET("/t/:hash/:token/:type", func(c *gin.Context) {
if InvalidPreviewToken(c) {
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
return
}
start := time.Now()
conf := service.Config()
fileHash := c.Param("hash")
typeName := c.Param("type")
thumbType, ok := thumb.Types[typeName]
if !ok {
log.Errorf("thumbnail: invalid type %s", txt.Quote(typeName))
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return
}
cache := service.Cache()
cacheKey := fmt.Sprintf("thumbnail:%s:%s", fileHash, typeName)
if cacheData, err := cache.Get(cacheKey); err == nil {
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
var cached ThumbCache
if err := json.Unmarshal(cacheData, &cached); err != nil {
log.Errorf("thumbnail: %s not found", fileHash)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
if !fs.FileExists(cached.FileName) {
log.Errorf("thumbnail: %s not found", fileHash)
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
}
if c.Query("download") != "" {
c.FileAttachment(cached.FileName, cached.ShareName)
} else {
c.File(cached.FileName)
}
return
}
f, err := query.FileByHash(fileHash)
if err != nil {
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return
}
// Find fallback if file is not a JPEG image.
if f.NoJPEG() {
f, err = query.FileByPhotoUID(f.PhotoUID)
if err != nil {
c.Data(http.StatusOK, "image/svg+xml", fileIconSvg)
return
}
}
// Return SVG icon as placeholder if file has errors.
if f.FileError != "" {
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
}
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("thumbnail: file %s is missing", txt.Quote(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore.
logError("thumbnail", f.Update("FileMissing", true))
if f.AllFilesMissing() {
log.Infof("thumbnail: deleting photo, all files missing for %s", txt.Quote(f.FileName))
logError("thumbnail", f.RelatedPhoto().Delete(false))
}
return
}
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.ExceedsLimit() && c.Query("download") == "" {
log.Debugf("thumbnail: using original, size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
c.File(fileName)
return
}
var thumbnail string
if conf.ThumbUncached() || thumbType.OnDemand() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
}
if err != nil {
log.Errorf("thumbnail: %s", err)
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
} else if thumbnail == "" {
log.Errorf("thumbnail: %s has empty thumb name - bug?", filepath.Base(fileName))
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
}
// Cache thumbnail filename.
if cached, err := json.Marshal(ThumbCache{thumbnail, f.ShareFileName()}); err == nil {
logError("thumbnail", cache.Set(cacheKey, cached))
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
}
if c.Query("download") != "" {
c.FileAttachment(thumbnail, f.ShareFileName())
} else {
c.File(thumbnail)
}
})
}

View file

@ -1,31 +0,0 @@
package api
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetThumbnail(t *testing.T) {
t.Run("invalid type", func(t *testing.T) {
app, router, conf := NewApiTest()
GetThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/t/1/"+conf.PreviewToken()+"/xxx")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("invalid hash", func(t *testing.T) {
app, router, conf := NewApiTest()
GetThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/t/1/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("could not find original", func(t *testing.T) {
app, router, conf := NewApiTest()
GetThumbnail(router)
r := PerformRequest(app, "GET", "/api/v1/t/2cad9168fa6acc5c5c2965ddf6ec465ca42fd818/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -0,0 +1,96 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
)
// POST /api/v1/photos/:uid/files/:file_uid/unstack
//
// Parameters:
// uid: string Photo UID as returned by the API
// file_uid: string File UID as returned by the API
func PhotoFileUnstack(router *gin.RouterGroup) {
router.POST("/photos/:uid/files/:file_uid/unstack", func(c *gin.Context) {
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionUpdate)
if s.Invalid() {
AbortUnauthorized(c)
return
}
photoUID := c.Param("uid")
fileUID := c.Param("file_uid")
file, err := query.FileByUID(fileUID)
if err != nil {
log.Errorf("photo: %s (unstack)", err)
AbortEntityNotFound(c)
return
}
if file.FilePrimary {
log.Errorf("photo: can't unstack primary file")
AbortBadRequest(c)
return
}
existingPhoto := *file.Photo
newPhoto := entity.NewPhoto()
if err := newPhoto.Create(); err != nil {
log.Errorf("photo: %s (unstack)", err.Error())
AbortSaveFailed(c)
return
}
file.Photo = &newPhoto
file.PhotoID = newPhoto.ID
file.PhotoUID = newPhoto.PhotoUID
if err := file.Save(); err != nil {
log.Errorf("photo: %s (unstack)", err.Error())
AbortSaveFailed(c)
return
}
fileName := photoprism.FileName(file.FileRoot, file.FileName)
f, err := photoprism.NewMediaFile(fileName)
if err != nil {
log.Errorf("photo: %s (unstack)", err)
AbortEntityNotFound(c)
return
}
if err := service.Index().MediaFile(f, photoprism.IndexOptions{Rescan: true}, existingPhoto.OriginalName).Error; err != nil {
log.Errorf("photo: %s (unstack)", err)
AbortSaveFailed(c)
return
}
PublishPhotoEvent(EntityCreated, file.PhotoUID, c)
PublishPhotoEvent(EntityUpdated, photoUID, c)
event.SuccessMsg(i18n.MsgFileUnstacked)
p, err := query.PhotoPreloadByUID(photoUID)
if err != nil {
AbortEntityNotFound(c)
return
}
c.JSON(http.StatusOK, p)
})
}

391
internal/api/thumbs.go Normal file
View file

@ -0,0 +1,391 @@
package api
import (
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
)
type ThumbCache struct {
FileName string
ShareName string
}
type ByteCache struct {
Data []byte
}
// GET /api/v1/t/:hash/:token/:type
//
// Parameters:
// hash: string file hash as returned by the search API
// token: string security token (see config)
// type: string thumb type, see photoprism.ThumbnailTypes
func GetThumb(router *gin.RouterGroup) {
router.GET("/t/:hash/:token/:type", func(c *gin.Context) {
if InvalidPreviewToken(c) {
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
return
}
start := time.Now()
conf := service.Config()
fileHash := c.Param("hash")
typeName := c.Param("type")
thumbType, ok := thumb.Types[typeName]
if !ok {
log.Errorf("thumbs: invalid type %s", txt.Quote(typeName))
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return
}
cache := service.Cache()
cacheKey := fmt.Sprintf("thumbs:%s:%s", fileHash, typeName)
if cacheData, err := cache.Get(cacheKey); err == nil {
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
var cached ThumbCache
if err := json.Unmarshal(cacheData, &cached); err != nil {
log.Errorf("thumbs: %s not found", fileHash)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
if !fs.FileExists(cached.FileName) {
log.Errorf("thumbs: %s not found", fileHash)
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
}
if c.Query("download") != "" {
c.FileAttachment(cached.FileName, cached.ShareName)
} else {
c.File(cached.FileName)
}
return
}
f, err := query.FileByHash(fileHash)
if err != nil {
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return
}
// Find fallback if file is not a JPEG image.
if f.NoJPEG() {
f, err = query.FileByPhotoUID(f.PhotoUID)
if err != nil {
c.Data(http.StatusOK, "image/svg+xml", fileIconSvg)
return
}
}
// Return SVG icon as placeholder if file has errors.
if f.FileError != "" {
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
}
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("thumbs: file %s is missing", txt.Quote(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore.
logError("thumbnail", f.Update("FileMissing", true))
if f.AllFilesMissing() {
log.Infof("thumbs: deleting photo, all files missing for %s", txt.Quote(f.FileName))
logError("thumbnail", f.RelatedPhoto().Delete(false))
}
return
}
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.ExceedsLimit() && c.Query("download") == "" {
log.Debugf("thumbs: using original, size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
c.File(fileName)
return
}
var thumbnail string
if conf.ThumbUncached() || thumbType.OnDemand() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
}
if err != nil {
log.Errorf("thumbs: %s", err)
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
} else if thumbnail == "" {
log.Errorf("thumbs: %s has empty thumb name - bug?", filepath.Base(fileName))
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
}
// Cache thumbnail filename.
if cached, err := json.Marshal(ThumbCache{thumbnail, f.ShareFileName()}); err == nil {
logError("thumbnail", cache.Set(cacheKey, cached))
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
}
if c.Query("download") != "" {
c.FileAttachment(thumbnail, f.ShareFileName())
} else {
c.File(thumbnail)
}
})
}
// GET /api/v1/albums/:uid/t/:token/:type
//
// Parameters:
// uid: string album uid
// token: string security token (see config)
// type: string thumb type, see photoprism.ThumbnailTypes
func AlbumThumb(router *gin.RouterGroup) {
router.GET("/albums/:uid/t/:token/:type", func(c *gin.Context) {
if InvalidPreviewToken(c) {
c.Data(http.StatusForbidden, "image/svg+xml", albumIconSvg)
return
}
start := time.Now()
conf := service.Config()
typeName := c.Param("type")
uid := c.Param("uid")
thumbType, ok := thumb.Types[typeName]
if !ok {
log.Errorf("album-thumbs: invalid type %s", typeName)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
cache := service.Cache()
cacheKey := fmt.Sprintf("album-thumbs:%s:%s", uid, typeName)
if cacheData, err := cache.Get(cacheKey); err == nil {
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
var cached ThumbCache
if err := json.Unmarshal(cacheData, &cached); err != nil {
log.Errorf("album-thumbs: %s not found", uid)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
if !fs.FileExists(cached.FileName) {
log.Errorf("album-thumbs: %s not found", uid)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
if c.Query("download") != "" {
c.FileAttachment(cached.FileName, cached.ShareName)
} else {
c.File(cached.FileName)
}
return
}
f, err := query.AlbumCoverByUID(uid)
if err != nil {
log.Debugf("album-thumbs: no photos yet, using generic image for %s", uid)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("album-thumbs: could not find original for %s", fileName)
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore.
log.Warnf("album-thumbs: %s is missing", txt.Quote(f.FileName))
logError("album-thumbnail", f.Update("FileMissing", true))
return
}
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.ExceedsLimit() && c.Query("download") == "" {
log.Debugf("album-thumbs: using original, size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
c.File(fileName)
return
}
var thumbnail string
if conf.ThumbUncached() || thumbType.OnDemand() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
}
if err != nil {
log.Errorf("album: %s", err)
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return
} else if thumbnail == "" {
log.Errorf("album-thumbs: %s has empty thumb name - bug?", filepath.Base(fileName))
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return
}
if cached, err := json.Marshal(ThumbCache{thumbnail, f.ShareFileName()}); err == nil {
logError("album-thumbnail", cache.Set(cacheKey, cached))
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
}
if c.Query("download") != "" {
c.FileAttachment(thumbnail, f.ShareFileName())
} else {
c.File(thumbnail)
}
})
}
// GET /api/v1/labels/:uid/t/:token/:type
//
// Parameters:
// uid: string label uid
// token: string security token (see config)
// type: string thumb type, see photoprism.ThumbnailTypes
func LabelThumb(router *gin.RouterGroup) {
router.GET("/labels/:uid/t/:token/:type", func(c *gin.Context) {
if InvalidPreviewToken(c) {
c.Data(http.StatusForbidden, "image/svg+xml", labelIconSvg)
return
}
start := time.Now()
conf := service.Config()
typeName := c.Param("type")
uid := c.Param("uid")
thumbType, ok := thumb.Types[typeName]
if !ok {
log.Errorf("label-thumbs: invalid type %s", txt.Quote(typeName))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
cache := service.Cache()
cacheKey := fmt.Sprintf("label-thumbs:%s:%s", uid, typeName)
if cacheData, err := cache.Get(cacheKey); err == nil {
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
var cached ThumbCache
if err := json.Unmarshal(cacheData, &cached); err != nil {
log.Errorf("label-thumbs: %s not found", uid)
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
if !fs.FileExists(cached.FileName) {
log.Errorf("label-thumbs: %s not found", uid)
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
if c.Query("download") != "" {
c.FileAttachment(cached.FileName, cached.ShareName)
} else {
c.File(cached.FileName)
}
return
}
f, err := query.LabelThumbByUID(uid)
if err != nil {
log.Errorf(err.Error())
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("label-thumbs: file %s is missing", txt.Quote(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore.
logError("label-thumbnail", f.Update("FileMissing", true))
return
}
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.ExceedsLimit() {
log.Debugf("label-thumbs: using original, size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
c.File(fileName)
return
}
var thumbnail string
if conf.ThumbUncached() || thumbType.OnDemand() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
}
if err != nil {
log.Errorf("label-thumbs: %s", err)
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
} else if thumbnail == "" {
log.Errorf("label-thumbs: %s has empty thumb name - bug?", filepath.Base(fileName))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return
}
if cached, err := json.Marshal(ThumbCache{thumbnail, f.ShareFileName()}); err == nil {
logError("label-thumbnail", cache.Set(cacheKey, cached))
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
}
if c.Query("download") != "" {
c.FileAttachment(thumbnail, f.ShareFileName())
} else {
c.File(thumbnail)
}
})
}

View file

@ -0,0 +1,75 @@
package api
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetThumb(t *testing.T) {
t.Run("invalid type", func(t *testing.T) {
app, router, conf := NewApiTest()
GetThumb(router)
r := PerformRequest(app, "GET", "/api/v1/t/1/"+conf.PreviewToken()+"/xxx")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("invalid hash", func(t *testing.T) {
app, router, conf := NewApiTest()
GetThumb(router)
r := PerformRequest(app, "GET", "/api/v1/t/1/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("could not find original", func(t *testing.T) {
app, router, conf := NewApiTest()
GetThumb(router)
r := PerformRequest(app, "GET", "/api/v1/t/2cad9168fa6acc5c5c2965ddf6ec465ca42fd818/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
}
func TestAlbumThumb(t *testing.T) {
t.Run("invalid type", func(t *testing.T) {
app, router, conf := NewApiTest()
AlbumThumb(router)
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba7/t/"+conf.PreviewToken()+"/xxx")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("album has no photo (because is not existing)", func(t *testing.T) {
app, router, conf := NewApiTest()
AlbumThumb(router)
r := PerformRequest(app, "GET", "/api/v1/albums/987-986435/t/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("album: could not find original", func(t *testing.T) {
app, router, conf := NewApiTest()
AlbumThumb(router)
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8/t/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
}
func TestLabelThumb(t *testing.T) {
t.Run("invalid type", func(t *testing.T) {
app, router, conf := NewApiTest()
LabelThumb(router)
r := PerformRequest(app, "GET", "/api/v1/labels/lt9k3pw1wowuy3c2/t/"+conf.PreviewToken()+"/xxx")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("invalid label", func(t *testing.T) {
app, router, conf := NewApiTest()
LabelThumb(router)
r := PerformRequest(app, "GET", "/api/v1/labels/xxx/t/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("could not find original", func(t *testing.T) {
app, router, conf := NewApiTest()
LabelThumb(router)
r := PerformRequest(app, "GET", "/api/v1/labels/lt9k3pw1wowuy3c3/t/"+conf.PreviewToken()+"/tile_500")
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -100,7 +100,7 @@ func configAction(ctx *cli.Context) error {
// Places / Geocoding API configuration.
fmt.Printf("%-25s %s\n", "geocoding-api", conf.GeoCodingApi())
// Thumbnails, resampling and download security token.
// Thumbs, resampling and download security token.
fmt.Printf("%-25s %s\n", "download-token", conf.DownloadToken())
fmt.Printf("%-25s %s\n", "preview-token", conf.PreviewToken())
fmt.Printf("%-25s %s\n", "thumb-filter", conf.ThumbFilter())

View file

@ -33,7 +33,7 @@ type ClientConfig struct {
Cameras []entity.Camera `json:"cameras"`
Lenses []entity.Lens `json:"lenses"`
Countries []entity.Country `json:"countries"`
Thumbnails []Thumbnail `json:"thumbnails"`
Thumbs []Thumb `json:"thumbs"`
DownloadToken string `json:"downloadToken"`
PreviewToken string `json:"previewToken"`
JSHash string `json:"jsHash"`
@ -138,7 +138,7 @@ func (c *Config) PublicConfig() ClientConfig {
ReadOnly: c.ReadOnly(),
Public: c.Public(),
Experimental: c.Experimental(),
Thumbnails: Thumbnails,
Thumbs: Thumbs,
Colors: colors.All.List(),
JSHash: fs.Checksum(c.BuildPath() + "/app.js"),
CSSHash: fs.Checksum(c.BuildPath() + "/app.css"),
@ -178,7 +178,7 @@ func (c *Config) GuestConfig() ClientConfig {
Public: true,
Experimental: false,
Colors: colors.All.List(),
Thumbnails: Thumbnails,
Thumbs: Thumbs,
DownloadToken: c.DownloadToken(),
PreviewToken: c.PreviewToken(),
JSHash: fs.Checksum(c.BuildPath() + "/share.js"),
@ -210,7 +210,7 @@ func (c *Config) UserConfig() ClientConfig {
Public: c.Public(),
Experimental: c.Experimental(),
Colors: colors.All.List(),
Thumbnails: Thumbnails,
Thumbs: Thumbs,
DownloadToken: c.DownloadToken(),
PreviewToken: c.PreviewToken(),
JSHash: fs.Checksum(c.BuildPath() + "/app.js"),

View file

@ -37,7 +37,7 @@ func init() {
t := thumb.Types[size]
if t.Public {
Thumbnails = append(Thumbnails, Thumbnail{Size: size, Use: t.Use, Width: t.Width, Height: t.Height})
Thumbs = append(Thumbs, Thumb{Size: size, Use: t.Use, Width: t.Width, Height: t.Height})
}
}
}

View file

@ -314,7 +314,7 @@ func TestConfig_ClientConfig(t *testing.T) {
assert.NotEmpty(t, cc.Name)
assert.NotEmpty(t, cc.Version)
assert.NotEmpty(t, cc.Copyright)
assert.NotEmpty(t, cc.Thumbnails)
assert.NotEmpty(t, cc.Thumbs)
assert.NotEmpty(t, cc.JSHash)
assert.NotEmpty(t, cc.CSSHash)
assert.Equal(t, true, cc.Debug)

View file

@ -1,12 +0,0 @@
package config
// Thumbnail gives direct access to width and height for a thumbnail setting
type Thumbnail struct {
Size string `json:"size"`
Use string `json:"use"`
Width int `json:"w"`
Height int `json:"h"`
}
// Thumbnails is a list of default thumbnail size available for the app
var Thumbnails []Thumbnail

12
internal/config/thumbs.go Normal file
View file

@ -0,0 +1,12 @@
package config
// Thumb represents thumbnail info for use in client apps.
type Thumb struct {
Size string `json:"size"`
Use string `json:"use"`
Width int `json:"w"`
Height int `json:"h"`
}
// Thumbs is a list of thumbnails for use in client apps.
var Thumbs []Thumb

View file

@ -29,7 +29,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
api.CreateSession(v1)
api.DeleteSession(v1)
api.GetThumbnail(v1)
api.GetThumb(v1)
api.GetDownload(v1)
api.GetVideo(v1)
api.CreateZip(v1)
@ -64,7 +64,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
api.DeleteLabelLink(v1)
api.LikeLabel(v1)
api.DislikeLabel(v1)
api.LabelThumbnail(v1)
api.LabelThumb(v1)
api.GetFoldersOriginals(v1)
api.GetFoldersImport(v1)
@ -93,7 +93,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
api.DeleteAlbumLink(v1)
api.LikeAlbum(v1)
api.DislikeAlbum(v1)
api.AlbumThumbnail(v1)
api.AlbumThumb(v1)
api.CloneAlbums(v1)
api.AddPhotosToAlbum(v1)
api.RemovePhotosFromAlbum(v1)