Download: Add Disabled, Originals, MediaRaw & MediaSidecar Flags #2234

Extends DownloadSettings with 4 additional options:
- Name: File name pattern for downloaded files (existed)
- Disabled: Disables downloads
- Originals: Only download files stored in "originals" folder
- MediaRaw: Include RAW image files
- MediaSidecar: Include metadata sidecar files (JSON, XMP, YAML)
This commit is contained in:
Michael Mayer 2022-04-15 09:42:07 +02:00
parent 0a9f6a72bc
commit 92e6c4fe1e
335 changed files with 3606 additions and 2708 deletions

View file

@ -31,30 +31,13 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
useradd -m -g 1000 -u 1000 -d /photoprism -G video,render photoprism && \ useradd -m -g 1000 -u 1000 -d /photoprism -G video,render photoprism && \
chmod 777 /photoprism && \ chmod 777 /photoprism && \
apt-get update && apt-get -qq dist-upgrade && apt-get -qq install --no-install-recommends \ apt-get update && apt-get -qq dist-upgrade && apt-get -qq install --no-install-recommends \
ca-certificates \ libc6 ca-certificates sudo bash tzdata \
jq \ gpg zip unzip wget curl rsync make nano \
zip \ jq lsof lshw sqlite3 mariadb-client \
gpg \ exiftool darktable rawtherapee libheif-examples librsvg2-bin \
lshw \ ffmpeg ffmpegthumbnailer libavcodec-extra libwebm1 \
wget \ libmatroska7 libdvdread8 libebml5 libgav1-0 libatomic1 \
curl \ libx264-163 libx265-199 && \
make \
sudo \
bash \
sqlite3 \
tzdata \
libc6 \
libatomic1 \
libheif-examples \
librsvg2-bin \
exiftool \
darktable \
rawtherapee \
ffmpeg \
ffmpegthumbnailer \
libavcodec-extra \
mariadb-client \
&& \
install -d -m 0777 -o 1000 -g 1000 \ install -d -m 0777 -o 1000 -g 1000 \
/var/lib/photoprism \ /var/lib/photoprism \
/tmp/photoprism \ /tmp/photoprism \

View file

@ -38,60 +38,20 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
useradd -m -g 1000 -u 1000 -d /photoprism -G video,render photoprism && \ useradd -m -g 1000 -u 1000 -d /photoprism -G video,render photoprism && \
chmod 777 /photoprism && \ chmod 777 /photoprism && \
apt-get update && apt-get -qq dist-upgrade && apt-get -qq install --no-install-recommends \ apt-get update && apt-get -qq dist-upgrade && apt-get -qq install --no-install-recommends \
apt-utils \ libc6 ca-certificates sudo bash tzdata \
gpg \ gpg zip unzip wget curl rsync make nano \
pkg-config \ jq lsof lshw sqlite3 mariadb-client \
software-properties-common \ exiftool darktable rawtherapee libheif-examples librsvg2-bin \
ca-certificates \ ffmpeg ffmpegthumbnailer libavcodec-extra libwebm1 \
build-essential \ libmatroska7 libdvdread8 libebml5 libgav1-0 libatomic1 \
gcc \ libx264-163 libx265-199 && \
g++ \ apt-get -qq install --no-install-recommends \
sudo \ apt-utils pkg-config software-properties-common \
bash \ build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
make \ chromium chromium-driver chromium-sandbox firefox-esr \
nano \ libx264-dev libx265-dev libpng-dev libxft-dev \
lsof \ libc6-dev libhdf5-serial-dev libzmq3-dev libssl-dev libnss3 \
lshw \ libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
wget \
curl \
rsync \
jq \
git \
zip \
unzip \
gettext \
chromium \
chromium-driver \
chromium-sandbox \
firefox-esr \
sqlite3 \
libc6-dev \
libssl-dev \
libxft-dev \
libhdf5-serial-dev \
libpng-dev \
libheif-examples \
librsvg2-bin \
libzmq3-dev \
libx264-dev \
libx265-dev \
libnss3 \
libfreetype6 \
libfreetype6-dev \
libfontconfig1 \
libfontconfig1-dev \
fonts-roboto \
tzdata \
exiftool \
rawtherapee \
ffmpeg \
darktable \
ffmpegthumbnailer \
libavcodec-extra \
davfs2 \
chrpath \
apache2-utils \
mariadb-client \
&& \ && \
/scripts/install-nodejs.sh && \ /scripts/install-nodejs.sh && \
/scripts/install-tensorflow.sh && \ /scripts/install-tensorflow.sh && \

View file

@ -145,25 +145,100 @@ export default class Util {
start = now; start = now;
} }
static capitalize(s) {
if (!s || s === "") {
return "";
}
return s.replace(/\w\S*/g, (w) => w.replace(/^\w/, (c) => c.toUpperCase()));
}
static fileType(value) {
if (!value || typeof value !== "string") {
return "";
}
switch (value) {
case "raw":
return "Unprocessed Sensor Data (RAW)";
case "mov":
case "qt":
return "Apple QuickTime";
case "bmp":
return "Bitmap";
case "png":
return "Portable Network Graphics";
case "tiff":
return "TIFF";
case "gif":
return "GIF";
case "avc":
case "avc1":
return "Advanced Video Coding (AVC) / H.264";
case "hevc":
case "hvc":
case "hvc1":
return "High Efficiency Video Coding (HEVC) / H.265";
case "mkv":
return "Matroska Multimedia Container";
case "webp":
return "Google WebP";
case "webm":
return "Google WebM";
case "flv":
return "Flash";
case "mpg":
return "MPEG";
case "mjpg":
return "Motion JPEG";
case "ogg":
case "ogv":
return "Ogg Media";
case "wmv":
return "Windows Media";
default:
return value.toUpperCase();
}
}
static codecName(value) { static codecName(value) {
if (!value || typeof value !== "string") { if (!value || typeof value !== "string") {
return ""; return "";
} }
switch (value) { switch (value) {
case "raw":
return "Unprocessed Sensor Data (RAW)";
case "mov":
case "qt":
return "Apple QuickTime (MOV)";
case "avc":
case "avc1": case "avc1":
return "Advanced Video Coding (AVC) / H.264"; return "Advanced Video Coding (AVC) / H.264";
case "hevc":
case "hvc":
case "hvc1": case "hvc1":
return "High Efficiency Video Coding (HEVC) / H.265"; return "High Efficiency Video Coding (HEVC) / H.265";
case "vvc":
return "Versatile Video Coding (VVC) / H.266";
case "av01": case "av01":
return "AOMedia Video 1 (AV1)"; return "AOMedia Video 1 (AV1)";
case "gif":
return "Graphics Interchange Format (GIF)";
case "mkv":
return "Matroska Multimedia Container (MKV)";
case "webp":
return "Google WebP";
case "webm":
return "Google WebM";
case "mpeg": case "mpeg":
return "Moving Picture Experts Group (MPEG)"; return "Moving Picture Experts Group (MPEG)";
case "mjpg": case "mjpg":
return "Motion JPEG (M-JPEG)"; return "Motion JPEG (M-JPEG)";
case "heif": case "heif":
case "heic":
return "High Efficiency Image File Format (HEIF)"; return "High Efficiency Image File Format (HEIF)";
case "heic":
return "High Efficiency Image Container (HEIC)";
case "1": case "1":
return "Uncompressed"; return "Uncompressed";
case "2": case "2":

View file

@ -82,18 +82,18 @@
</td> </td>
<td>{{ file.Hash }}</td> <td>{{ file.Hash }}</td>
</tr> </tr>
<tr v-if="file.Root.length > 1">
<td>
<translate>Storage Folder</translate>
</td>
<td>{{ file.Root | capitalize }}</td>
</tr>
<tr v-if="file.Name"> <tr v-if="file.Name">
<td> <td>
<translate>Name</translate> <translate>Filename</translate>
</td> </td>
<td>{{ file.Name }}</td> <td>{{ file.Name }}</td>
</tr> </tr>
<tr v-if="file.Root">
<td>
<translate>Storage</translate>
</td>
<td>{{ file.storageInfo() }}</td>
</tr>
<tr v-if="file.OriginalName"> <tr v-if="file.OriginalName">
<td> <td>
<translate>Original Name</translate> <translate>Original Name</translate>
@ -112,12 +112,18 @@
</td> </td>
<td>{{ file.typeInfo() }}</td> <td>{{ file.typeInfo() }}</td>
</tr> </tr>
<tr v-if="file.Codec"> <tr v-if="file.Codec && file.Codec !== file.FileType">
<td> <td>
<translate>Codec</translate> <translate>Codec</translate>
</td> </td>
<td>{{ codecName(file) }}</td> <td>{{ codecName(file) }}</td>
</tr> </tr>
<tr v-if="file.Duration && file.Duration > 0">
<td>
<translate>Duration</translate>
</td>
<td>{{ formatDuration(file) }}</td>
</tr>
<tr v-if="file.Frames"> <tr v-if="file.Frames">
<td> <td>
<translate>Frames</translate> <translate>Frames</translate>
@ -278,6 +284,20 @@ export default {
}, },
computed: {}, computed: {},
methods: { methods: {
formatDuration(file) {
if (!file || !file.Duration) {
return "";
}
return Util.duration(file.Duration);
},
fileType(file) {
if (!file || !file.FileType) {
return "";
}
return Util.fileType(file.FileType);
},
codecName(file) { codecName(file) {
if (!file || !file.Codec) { if (!file || !file.Codec) {
return ""; return "";

View file

@ -30,7 +30,7 @@ import Util from "common/util";
import { config } from "app/session"; import { config } from "app/session";
import { $gettext } from "common/vm"; import { $gettext } from "common/vm";
import download from "common/download"; import download from "common/download";
import { MediaAnimated } from "./photo"; import { MediaImage } from "./photo";
export class File extends RestModel { export class File extends RestModel {
getDefaults() { getDefaults() {
@ -38,6 +38,9 @@ export class File extends RestModel {
UID: "", UID: "",
PhotoUID: "", PhotoUID: "",
InstanceID: "", InstanceID: "",
MediaID: "",
MediaUTC: 0,
TakenAt: "",
Root: "/", Root: "/",
Name: "", Name: "",
OriginalName: "", OriginalName: "",
@ -184,18 +187,54 @@ export class File extends RestModel {
return info.join(", "); return info.join(", ");
} }
storageInfo() {
if (!this.Root || this.Root === "") {
return "";
}
if (this.Root.length === 1) {
return $gettext("Originals");
} else {
return Util.capitalize(this.Root);
}
}
typeInfo() { typeInfo() {
if (this.Type === MediaAnimated) { let info = [];
return $gettext("Animation");
}
if (this.Video) { if (
return $gettext("Video"); this.MediaType &&
this.Frames &&
this.MediaType === MediaImage &&
this.Frames &&
this.Frames > 0
) {
info.push($gettext("Animated"));
} else if (this.Sidecar) { } else if (this.Sidecar) {
return $gettext("Sidecar"); info.push($gettext("Sidecar"));
} }
return this.FileType.toUpperCase(); if (this.Primary && !this.MediaType) {
info.push($gettext("Image"));
return info.join(" ");
} else if (this.Video && !this.MediaType) {
info.push($gettext("Video"));
return info.join(" ");
} else {
const format = Util.fileType(this.FileType);
if (format) {
info.push(format);
}
if (this.MediaType && this.MediaType !== this.FileType) {
const media = Util.capitalize(this.MediaType);
if (media) {
info.push(media);
}
}
return info.join(" ");
}
} }
sizeInfo() { sizeInfo() {

View file

@ -43,6 +43,7 @@ export const FormatGif = "gif";
export const FormatJpeg = "jpg"; export const FormatJpeg = "jpg";
export const MediaImage = "image"; export const MediaImage = "image";
export const MediaAnimated = "animated"; export const MediaAnimated = "animated";
export const MediaSidecar = "sidecar";
export const MediaVideo = "video"; export const MediaVideo = "video";
export const MediaLive = "live"; export const MediaLive = "live";
export const MediaRaw = "raw"; export const MediaRaw = "raw";
@ -535,9 +536,10 @@ export class Photo extends RestModel {
} }
downloadAll() { downloadAll() {
const settings = config.settings(); const s = config.settings();
if (!settings || !settings.features || !settings.download || !settings.features.download) { if (!s || !s.features || !s.download || !s.features.download || s.download.disabled) {
console.log("download: disabled in settings", s.features, s.download);
return; return;
} }
@ -560,23 +562,30 @@ export class Photo extends RestModel {
return; return;
} }
// Skip sidecar files. // Originals only?
if (file.Sidecar) { if (s.download.originals && file.Root.length > 1) {
// Don't download broken files and sidecars. // Don't download broken files and sidecars.
if (config.debug) console.log("download: skipped sidecar", file); if (config.debug) console.log(`download: skipped ${file.Root} file ${file.Name}`);
return; return;
} }
// Skip RAW images. // Skip metadata sidecar files?
if (!settings.download.raw && file.FileType === MediaRaw) { if (!s.download.mediaSidecar && (file.MediaType === MediaSidecar || file.Sidecar)) {
if (config.debug) console.log("download: skipped raw", file); // Don't download broken files and sidecars.
if (config.debug) console.log(`download: skipped sidecar file ${file.Name}`);
return; return;
} }
// Skip related images if video. // Skip RAW images?
if (!s.download.mediaRaw && (file.MediaType === MediaRaw || file.FileType === MediaRaw)) {
if (config.debug) console.log(`download: skipped raw file ${file.Name}`);
return;
}
// If this is a video, always skip stacked images...
// see https://github.com/photoprism/photoprism/issues/1436 // see https://github.com/photoprism/photoprism/issues/1436
if (this.Type === MediaVideo && !file.Video) { if (this.Type === MediaVideo && !(file.MediaType === MediaVideo || file.Video)) {
if (config.debug) console.log("download: skipped image", file); if (config.debug) console.log(`download: skipped video sidecar ${file.Name}`);
return; return;
} }

View file

@ -236,7 +236,21 @@ describe("model/file", () => {
UpdatedAt: "2012-07-08T14:45:39Z", UpdatedAt: "2012-07-08T14:45:39Z",
}; };
const file3 = new File(values3); const file3 = new File(values3);
assert.equal(file3.typeInfo(), "Sidecar"); assert.equal(file3.typeInfo(), "Sidecar JPG");
const values4 = {
InstanceID: 5,
UID: "ABC123",
Hash: "54ghtfd",
FileType: "gif",
MediaType: "image",
Duration: 8009,
Name: "1/2/IMG123.jpg",
Sidecar: true,
CreatedAt: "2012-07-08T14:45:39Z",
UpdatedAt: "2012-07-08T14:45:39Z",
};
const file4 = new File(values4);
assert.equal(file4.typeInfo(), "Sidecar GIF Image");
}); });
it("should get size info", () => { it("should get size info", () => {

View file

@ -18,8 +18,8 @@ import (
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/workers" "github.com/photoprism/photoprism/internal/workers"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// Namespaces for caching and logs. // Namespaces for caching and logs.
@ -49,7 +49,7 @@ func GetAccount(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdUint(c.Param("id")) id := clean.IdUint(c.Param("id"))
if m, err := query.AccountByID(id); err == nil { if m, err := query.AccountByID(id); err == nil {
c.JSON(http.StatusOK, m) c.JSON(http.StatusOK, m)
@ -82,7 +82,7 @@ func GetAccountFolders(router *gin.RouterGroup) {
} }
start := time.Now() start := time.Now()
id := sanitize.IdUint(c.Param("id")) id := clean.IdUint(c.Param("id"))
cache := service.FolderCache() cache := service.FolderCache()
cacheKey := fmt.Sprintf("%s:%d", accountFolder, id) cacheKey := fmt.Sprintf("%s:%d", accountFolder, id)
@ -132,7 +132,7 @@ func ShareWithAccount(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdUint(c.Param("id")) id := clean.IdUint(c.Param("id"))
m, err := query.AccountByID(id) m, err := query.AccountByID(id)
@ -150,13 +150,9 @@ func ShareWithAccount(router *gin.RouterGroup) {
folder := f.Folder folder := f.Folder
// Select files to be shared. // Find files to share.
o := query.FileSelection{ selection := query.ShareSelection(m.ShareOriginals())
Video: true, files, err := query.SelectedFiles(f.Selection, selection)
OriginalsOnly: m.ShareOriginals(),
PrimaryOnly: !m.ShareOriginals(),
}
files, err := query.SelectedFiles(f.Selection, o)
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@ -252,7 +248,7 @@ func UpdateAccount(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdUint(c.Param("id")) id := clean.IdUint(c.Param("id"))
m, err := query.AccountByID(id) m, err := query.AccountByID(id)
@ -323,7 +319,7 @@ func DeleteAccount(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdUint(c.Param("id")) id := clean.IdUint(c.Param("id"))
m, err := query.AccountByID(id) m, err := query.AccountByID(id)

View file

@ -16,7 +16,7 @@ import (
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
var albumMutex = sync.Mutex{} var albumMutex = sync.Mutex{}
@ -35,7 +35,7 @@ func SaveAlbumAsYaml(a entity.Album) {
if err := a.SaveAsYaml(fileName); err != nil { if err := a.SaveAsYaml(fileName); err != nil {
log.Errorf("album: %s (update yaml)", err) log.Errorf("album: %s (update yaml)", err)
} else { } else {
log.Debugf("album: updated yaml file %s", sanitize.Log(filepath.Base(fileName))) log.Debugf("album: updated yaml file %s", clean.Log(filepath.Base(fileName)))
} }
} }
@ -51,7 +51,7 @@ func GetAlbum(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
a, err := query.AlbumByUID(id) a, err := query.AlbumByUID(id)
if err != nil { if err != nil {
@ -96,7 +96,7 @@ func CreateAlbum(router *gin.RouterGroup) {
// Create new album. // Create new album.
if err := a.Create(); err != nil { if err := a.Create(); err != nil {
AbortAlreadyExists(c, sanitize.Log(a.AlbumTitle)) AbortAlreadyExists(c, clean.Log(a.AlbumTitle))
return return
} }
@ -124,7 +124,7 @@ func UpdateAlbum(router *gin.RouterGroup) {
return return
} }
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
a, err := query.AlbumByUID(uid) a, err := query.AlbumByUID(uid)
if err != nil { if err != nil {
@ -179,7 +179,7 @@ func DeleteAlbum(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
a, err := query.AlbumByUID(id) a, err := query.AlbumByUID(id)
@ -212,7 +212,7 @@ func DeleteAlbum(router *gin.RouterGroup) {
SaveAlbumAsYaml(a) SaveAlbumAsYaml(a)
event.SuccessMsg(i18n.MsgAlbumDeleted, sanitize.Log(a.AlbumTitle)) event.SuccessMsg(i18n.MsgAlbumDeleted, clean.Log(a.AlbumTitle))
c.JSON(http.StatusOK, a) c.JSON(http.StatusOK, a)
}) })
@ -233,7 +233,7 @@ func LikeAlbum(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
a, err := query.AlbumByUID(id) a, err := query.AlbumByUID(id)
if err != nil { if err != nil {
@ -271,7 +271,7 @@ func DislikeAlbum(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
a, err := query.AlbumByUID(id) a, err := query.AlbumByUID(id)
if err != nil { if err != nil {
@ -306,7 +306,7 @@ func CloneAlbums(router *gin.RouterGroup) {
return return
} }
a, err := query.AlbumByUID(sanitize.IdString(c.Param("uid"))) a, err := query.AlbumByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@ -341,7 +341,7 @@ func CloneAlbums(router *gin.RouterGroup) {
} }
if len(added) > 0 { if len(added) > 0 {
event.SuccessMsg(i18n.MsgSelectionAddedTo, sanitize.Log(a.Title())) event.SuccessMsg(i18n.MsgSelectionAddedTo, clean.Log(a.Title()))
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c) PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
@ -371,7 +371,7 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
return return
} }
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
a, err := query.AlbumByUID(uid) a, err := query.AlbumByUID(uid)
if err != nil { if err != nil {
@ -392,9 +392,9 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
if len(added) > 0 { if len(added) > 0 {
if len(added) == 1 { if len(added) == 1 {
event.SuccessMsg(i18n.MsgEntryAddedTo, sanitize.Log(a.Title())) event.SuccessMsg(i18n.MsgEntryAddedTo, clean.Log(a.Title()))
} else { } else {
event.SuccessMsg(i18n.MsgEntriesAddedTo, len(added), sanitize.Log(a.Title())) event.SuccessMsg(i18n.MsgEntriesAddedTo, len(added), clean.Log(a.Title()))
} }
RemoveFromAlbumCoverCache(a.AlbumUID) RemoveFromAlbumCoverCache(a.AlbumUID)
@ -432,7 +432,7 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup) {
return return
} }
a, err := query.AlbumByUID(sanitize.IdString(c.Param("uid"))) a, err := query.AlbumByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@ -443,9 +443,9 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup) {
if len(removed) > 0 { if len(removed) > 0 {
if len(removed) == 1 { if len(removed) == 1 {
event.SuccessMsg(i18n.MsgEntryRemovedFrom, sanitize.Log(a.Title())) event.SuccessMsg(i18n.MsgEntryRemovedFrom, clean.Log(a.Title()))
} else { } else {
event.SuccessMsg(i18n.MsgEntriesRemovedFrom, len(removed), sanitize.Log(sanitize.Log(a.Title()))) event.SuccessMsg(i18n.MsgEntriesRemovedFrom, len(removed), clean.Log(clean.Log(a.Title())))
} }
RemoveFromAlbumCoverCache(a.AlbumUID) RemoveFromAlbumCoverCache(a.AlbumUID)

View file

@ -34,7 +34,7 @@ import (
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
var log = event.Log var log = event.Log
@ -60,7 +60,7 @@ func UpdateClientConfig() {
func Abort(c *gin.Context, code int, id i18n.Message, params ...interface{}) { func Abort(c *gin.Context, code int, id i18n.Message, params ...interface{}) {
resp := i18n.NewResponse(code, id, params...) resp := i18n.NewResponse(code, id, params...)
log.Debugf("api: abort %s with code %d (%s)", sanitize.Log(c.FullPath()), code, resp.String()) log.Debugf("api: abort %s with code %d (%s)", clean.Log(c.FullPath()), code, resp.String())
c.AbortWithStatusJSON(code, resp) c.AbortWithStatusJSON(code, resp)
} }
@ -70,7 +70,7 @@ func Error(c *gin.Context, code int, err error, id i18n.Message, params ...inter
if err != nil { if err != nil {
resp.Details = err.Error() resp.Details = err.Error()
log.Errorf("api: error %s with code %d in %s (%s)", sanitize.Log(err.Error()), code, sanitize.Log(c.FullPath()), resp.String()) log.Errorf("api: error %s with code %d in %s (%s)", clean.Log(err.Error()), code, clean.Log(c.FullPath()), resp.String())
} }
c.AbortWithStatusJSON(code, resp) c.AbortWithStatusJSON(code, resp)

View file

@ -14,7 +14,7 @@ import (
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
// BatchPhotosArchive moves multiple photos to the archive. // BatchPhotosArchive moves multiple photos to the archive.
@ -41,7 +41,7 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: archiving %s", sanitize.Log(f.String())) log.Infof("photos: archiving %s", clean.Log(f.String()))
if service.Config().BackupYaml() { if service.Config().BackupYaml() {
// Fetch selection from index. // Fetch selection from index.
@ -105,7 +105,7 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: restoring %s", sanitize.Log(f.String())) log.Infof("photos: restoring %s", clean.Log(f.String()))
if service.Config().BackupYaml() { if service.Config().BackupYaml() {
// Fetch selection from index. // Fetch selection from index.
@ -168,7 +168,7 @@ func BatchPhotosApprove(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: approving %s", sanitize.Log(f.String())) log.Infof("photos: approving %s", clean.Log(f.String()))
// Fetch selection from index. // Fetch selection from index.
photos, err := query.SelectedPhotos(f) photos, err := query.SelectedPhotos(f)
@ -221,7 +221,7 @@ func BatchAlbumsDelete(router *gin.RouterGroup) {
return return
} }
log.Infof("albums: deleting %s", sanitize.Log(f.String())) log.Infof("albums: deleting %s", clean.Log(f.String()))
entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.Album{}) entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.Album{})
entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.PhotoAlbum{}) entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.PhotoAlbum{})
@ -258,7 +258,7 @@ func BatchPhotosPrivate(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: updating private flag for %s", sanitize.Log(f.String())) log.Infof("photos: updating private flag for %s", clean.Log(f.String()))
if err := entity.Db().Model(entity.Photo{}).Where("photo_uid IN (?)", f.Photos).UpdateColumn("photo_private", if err := entity.Db().Model(entity.Photo{}).Where("photo_uid IN (?)", f.Photos).UpdateColumn("photo_private",
gorm.Expr("CASE WHEN photo_private > 0 THEN 0 ELSE 1 END")).Error; err != nil { gorm.Expr("CASE WHEN photo_private > 0 THEN 0 ELSE 1 END")).Error; err != nil {
@ -312,7 +312,7 @@ func BatchLabelsDelete(router *gin.RouterGroup) {
return return
} }
log.Infof("labels: deleting %s", sanitize.Log(f.String())) log.Infof("labels: deleting %s", clean.Log(f.String()))
var labels entity.Labels var labels entity.Labels
@ -364,7 +364,7 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: deleting %s", sanitize.Log(f.String())) log.Infof("photos: deleting %s", clean.Log(f.String()))
// Fetch selection from index. // Fetch selection from index.
photos, err := query.SelectedPhotos(f) photos, err := query.SelectedPhotos(f)

View file

@ -5,7 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -87,13 +87,13 @@ func SaveConfigOptions(router *gin.RouterGroup) {
yamlData, err := os.ReadFile(fileName) yamlData, err := os.ReadFile(fileName)
if err != nil { if err != nil {
log.Errorf("config: failed loading values from %s (%s)", sanitize.Log(fileName), err) log.Errorf("config: failed loading values from %s (%s)", clean.Log(fileName), err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err) c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return return
} }
if err := yaml.Unmarshal(yamlData, v); err != nil { if err := yaml.Unmarshal(yamlData, v); err != nil {
log.Warnf("config: failed parsing values in %s (%s)", sanitize.Log(fileName), err) log.Warnf("config: failed parsing values in %s (%s)", clean.Log(fileName), err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err) c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return return
} }
@ -122,14 +122,14 @@ func SaveConfigOptions(router *gin.RouterGroup) {
// Write YAML data to file. // Write YAML data to file.
if err := os.WriteFile(fileName, yamlData, os.ModePerm); err != nil { if err := os.WriteFile(fileName, yamlData, os.ModePerm); err != nil {
log.Errorf("config: failed writing values to %s (%s)", sanitize.Log(fileName), err) log.Errorf("config: failed writing values to %s (%s)", clean.Log(fileName), err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err) c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return return
} }
// Reload options. // Reload options.
if err := conf.Options().Load(fileName); err != nil { if err := conf.Options().Load(fileName); err != nil {
log.Warnf("config: failed loading values from %s (%s)", sanitize.Log(fileName), err) log.Warnf("config: failed loading values from %s (%s)", clean.Log(fileName), err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err) c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return return
} }

View file

@ -5,7 +5,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
@ -38,13 +38,13 @@ func AlbumCover(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
conf := service.Config() conf := service.Config()
thumbName := thumb.Name(sanitize.Token(c.Param("size"))) thumbName := thumb.Name(clean.Token(c.Param("size")))
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
size, ok := thumb.Sizes[thumbName] size, ok := thumb.Sizes[thumbName]
if !ok { if !ok {
log.Errorf("%s: invalid size %s", albumCover, sanitize.Log(thumbName.String())) log.Errorf("%s: invalid size %s", albumCover, clean.Log(thumbName.String()))
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg) c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return return
} }
@ -85,11 +85,11 @@ func AlbumCover(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("%s: found no original for %s", albumCover, sanitize.Log(fileName)) log.Errorf("%s: found no original for %s", albumCover, clean.Log(fileName))
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg) c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
log.Warnf("%s: %s is missing", albumCover, sanitize.Log(f.FileName)) log.Warnf("%s: %s is missing", albumCover, clean.Log(f.FileName))
logError(albumCover, f.Update("FileMissing", true)) logError(albumCover, f.Update("FileMissing", true))
return return
} }
@ -150,13 +150,13 @@ func LabelCover(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
conf := service.Config() conf := service.Config()
thumbName := thumb.Name(sanitize.Token(c.Param("size"))) thumbName := thumb.Name(clean.Token(c.Param("size")))
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
size, ok := thumb.Sizes[thumbName] size, ok := thumb.Sizes[thumbName]
if !ok { if !ok {
log.Errorf("%s: invalid size %s", labelCover, sanitize.Log(thumbName.String())) log.Errorf("%s: invalid size %s", labelCover, clean.Log(thumbName.String()))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg) c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return return
} }
@ -197,7 +197,7 @@ func LabelCover(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("%s: file %s is missing", labelCover, sanitize.Log(f.FileName)) log.Errorf("%s: file %s is missing", labelCover, clean.Log(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg) c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.

View file

@ -14,8 +14,8 @@ import (
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// DownloadAlbum streams the album contents as zip archive. // DownloadAlbum streams the album contents as zip archive.
@ -36,7 +36,7 @@ func DownloadAlbum(router *gin.RouterGroup) {
} }
start := time.Now() start := time.Now()
a, err := query.AlbumByUID(sanitize.IdString(c.Param("uid"))) a, err := query.AlbumByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@ -57,26 +57,19 @@ func DownloadAlbum(router *gin.RouterGroup) {
zipWriter := zip.NewWriter(c.Writer) zipWriter := zip.NewWriter(c.Writer)
defer zipWriter.Close() defer zipWriter.Close()
skipRaw := !conf.Settings().Download.Raw
var aliases = make(map[string]int) var aliases = make(map[string]int)
for _, file := range files { for _, file := range files {
if file.FileHash == "" { if file.FileHash == "" {
log.Warnf("download: empty file hash, skipped %s", sanitize.Log(file.FileName)) log.Warnf("download: empty file hash, skipped %s", clean.Log(file.FileName))
continue continue
} else if file.FileName == "" { } else if file.FileName == "" {
log.Warnf("download: empty file name, skipped %s", sanitize.Log(file.FileUID)) log.Warnf("download: empty file name, skipped %s", clean.Log(file.FileUID))
continue continue
} }
if file.FileSidecar { if file.FileSidecar {
log.Debugf("download: skipped sidecar %s", sanitize.Log(file.FileName)) log.Debugf("download: skipped sidecar %s", clean.Log(file.FileName))
continue
}
if skipRaw && fs.FormatRaw.Is(file.FileType) {
log.Debugf("download: skipped raw %s", sanitize.Log(file.FileName))
continue continue
} }
@ -92,17 +85,17 @@ func DownloadAlbum(router *gin.RouterGroup) {
if fs.FileExists(fileName) { if fs.FileExists(fileName) {
if err := addFileToZip(zipWriter, fileName, alias); err != nil { if err := addFileToZip(zipWriter, fileName, alias); err != nil {
log.Errorf("download: failed adding %s to album zip (%s)", sanitize.Log(file.FileName), err) log.Errorf("download: failed adding %s to album zip (%s)", clean.Log(file.FileName), err)
Abort(c, http.StatusInternalServerError, i18n.ErrZipFailed) Abort(c, http.StatusInternalServerError, i18n.ErrZipFailed)
return return
} }
log.Infof("download: added %s as %s", sanitize.Log(file.FileName), sanitize.Log(alias)) log.Infof("download: added %s as %s", clean.Log(file.FileName), clean.Log(alias))
} else { } else {
log.Warnf("download: album file %s is missing", sanitize.Log(file.FileName)) log.Warnf("download: album file %s is missing", clean.Log(file.FileName))
} }
} }
log.Infof("download: created %s [%s]", sanitize.Log(zipFileName), time.Since(start)) log.Infof("download: created %s [%s]", clean.Log(zipFileName), time.Since(start))
}) })
} }

View file

@ -10,8 +10,8 @@ import (
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// TODO: GET /api/v1/dl/file/:hash // TODO: GET /api/v1/dl/file/:hash
@ -45,7 +45,7 @@ func GetDownload(router *gin.RouterGroup) {
return return
} }
fileHash := sanitize.Token(c.Param("hash")) fileHash := clean.Token(c.Param("hash"))
f, err := query.FileByHash(fileHash) f, err := query.FileByHash(fileHash)
@ -57,7 +57,7 @@ func GetDownload(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("download: file %s is missing", sanitize.Log(f.FileName)) log.Errorf("download: file %s is missing", clean.Log(f.FileName))
c.Data(404, "image/svg+xml", brokenIconSvg) c.Data(404, "image/svg+xml", brokenIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.

View file

@ -20,9 +20,9 @@ import (
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// CreateZip creates a zip file archive for download. // CreateZip creates a zip file archive for download.
@ -57,8 +57,17 @@ func CreateZip(router *gin.RouterGroup) {
return return
} }
// Select files to be downloaded. // Configure file selection based on user settings.
files, err := query.SelectedFiles(f, query.FileSelectionAll()) var selection query.FileSelection
if dl := conf.Settings().Download; dl.Disabled {
AbortFeatureDisabled(c)
return
} else {
selection = query.DownloadSelection(dl.MediaRaw, dl.MediaSidecar, dl.Originals)
}
// Find files to download.
files, err := query.SelectedFiles(f, selection)
if err != nil { if err != nil {
Error(c, http.StatusBadRequest, err, i18n.ErrZipFailed) Error(c, http.StatusBadRequest, err, i18n.ErrZipFailed)
@ -68,53 +77,36 @@ func CreateZip(router *gin.RouterGroup) {
return return
} }
// Configure file names.
dlName := DownloadName(c)
zipPath := path.Join(conf.TempPath(), "zip") zipPath := path.Join(conf.TempPath(), "zip")
zipToken := rnd.Token(8) zipToken := rnd.GenerateToken(8)
zipBaseName := fmt.Sprintf("photoprism-download-%s-%s.zip", time.Now().Format("20060102-150405"), zipToken) zipBaseName := fmt.Sprintf("photoprism-download-%s-%s.zip", time.Now().Format("20060102-150405"), zipToken)
zipFileName := path.Join(zipPath, zipBaseName) zipFileName := path.Join(zipPath, zipBaseName)
// Create temp directory.
if err := os.MkdirAll(zipPath, 0700); err != nil { if err := os.MkdirAll(zipPath, 0700); err != nil {
Error(c, http.StatusInternalServerError, err, i18n.ErrZipFailed) Error(c, http.StatusInternalServerError, err, i18n.ErrZipFailed)
return return
} }
newZipFile, err := os.Create(zipFileName) // Create new zip file.
var newZipFile *os.File
if err != nil { if newZipFile, err = os.Create(zipFileName); err != nil {
Error(c, http.StatusInternalServerError, err, i18n.ErrZipFailed) Error(c, http.StatusInternalServerError, err, i18n.ErrZipFailed)
return return
} else {
defer newZipFile.Close()
} }
defer newZipFile.Close() // Create zip writer.
zipWriter := zip.NewWriter(newZipFile) zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close() defer zipWriter.Close()
dlName := DownloadName(c)
skipRaw := !conf.Settings().Download.Raw
var aliases = make(map[string]int) var aliases = make(map[string]int)
// Add files to zip.
for _, file := range files { for _, file := range files {
if file.FileHash == "" {
log.Warnf("download: empty file hash, skipped %s", sanitize.Log(file.FileName))
continue
} else if file.FileName == "" {
log.Warnf("download: empty file name, skipped %s", sanitize.Log(file.FileUID))
continue
}
if file.FileSidecar {
log.Debugf("download: skipped sidecar %s", sanitize.Log(file.FileName))
continue
}
if skipRaw && fs.FormatRaw.Is(file.FileType) {
log.Debugf("download: skipped raw %s", sanitize.Log(file.FileName))
continue
}
fileName := photoprism.FileName(file.FileRoot, file.FileName) fileName := photoprism.FileName(file.FileRoot, file.FileName)
alias := file.DownloadName(dlName, 0) alias := file.DownloadName(dlName, 0)
key := strings.ToLower(alias) key := strings.ToLower(alias)
@ -127,21 +119,21 @@ func CreateZip(router *gin.RouterGroup) {
if fs.FileExists(fileName) { if fs.FileExists(fileName) {
if err := addFileToZip(zipWriter, fileName, alias); err != nil { if err := addFileToZip(zipWriter, fileName, alias); err != nil {
log.Errorf("download: failed adding %s to zip (%s)", sanitize.Log(file.FileName), err) log.Errorf("download: failed adding %s to zip (%s)", clean.Log(file.FileName), err)
Abort(c, http.StatusInternalServerError, i18n.ErrZipFailed) Abort(c, http.StatusInternalServerError, i18n.ErrZipFailed)
return return
} }
log.Infof("download: added %s as %s", sanitize.Log(file.FileName), sanitize.Log(alias)) log.Infof("download: added %s as %s", clean.Log(file.FileName), clean.Log(alias))
} else { } else {
log.Warnf("download: media file %s is missing", sanitize.Log(file.FileName)) log.Warnf("download: media file %s is missing", clean.Log(file.FileName))
logError("download", file.Update("FileMissing", true)) logError("download", file.Update("FileMissing", true))
} }
} }
elapsed := int(time.Since(start).Seconds()) elapsed := int(time.Since(start).Seconds())
log.Infof("download: created %s [%s]", sanitize.Log(zipBaseName), time.Since(start)) log.Infof("download: created %s [%s]", clean.Log(zipBaseName), time.Since(start))
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgZipCreatedIn, elapsed), "filename": zipBaseName}) c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgZipCreatedIn, elapsed), "filename": zipBaseName})
}) })
@ -158,12 +150,12 @@ func DownloadZip(router *gin.RouterGroup) {
} }
conf := service.Config() conf := service.Config()
zipBaseName := sanitize.FileName(filepath.Base(c.Param("filename"))) zipBaseName := clean.FileName(filepath.Base(c.Param("filename")))
zipPath := path.Join(conf.TempPath(), "zip") zipPath := path.Join(conf.TempPath(), "zip")
zipFileName := path.Join(zipPath, zipBaseName) zipFileName := path.Join(zipPath, zipBaseName)
if !fs.FileExists(zipFileName) { if !fs.FileExists(zipFileName) {
log.Errorf("could not find zip file: %s", sanitize.Log(zipFileName)) log.Errorf("could not find zip file: %s", clean.Log(zipFileName))
c.Data(404, "image/svg+xml", photoIconSvg) c.Data(404, "image/svg+xml", photoIconSvg)
return return
} }
@ -171,7 +163,7 @@ func DownloadZip(router *gin.RouterGroup) {
c.FileAttachment(zipFileName, zipBaseName) c.FileAttachment(zipFileName, zipBaseName)
if err := os.Remove(zipFileName); err != nil { if err := os.Remove(zipFileName); err != nil {
log.Errorf("download: failed removing %s (%s)", sanitize.Log(zipFileName), err.Error()) log.Errorf("download: failed removing %s (%s)", clean.Log(zipFileName), err.Error())
} }
}) })
} }

View file

@ -31,7 +31,7 @@ func GetErrors(router *gin.RouterGroup) {
// Find and return matching logs. // Find and return matching logs.
if resp, err := query.Errors(limit, offset, c.Query("q")); err != nil { if resp, err := query.Errors(limit, offset, c.Query("q")); err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} else { } else {
AddCountHeader(c, len(resp)) AddCountHeader(c, len(resp))

View file

@ -3,7 +3,7 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -58,7 +58,7 @@ func UpdateFace(router *gin.RouterGroup) {
return return
} }
faceId := sanitize.Token(c.Param("id")) faceId := clean.Token(c.Param("id"))
m := entity.FindFace(faceId) m := entity.FindFace(faceId)
if m == nil { if m == nil {
@ -70,7 +70,7 @@ func UpdateFace(router *gin.RouterGroup) {
if !f.FaceHidden && f.FaceHidden == m.FaceHidden { if !f.FaceHidden && f.FaceHidden == m.FaceHidden {
// Do nothing. // Do nothing.
} else if err := m.Update("FaceHidden", f.FaceHidden); err != nil { } else if err := m.Update("FaceHidden", f.FaceHidden); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -78,7 +78,7 @@ func UpdateFace(router *gin.RouterGroup) {
if f.SubjUID == "" { if f.SubjUID == "" {
// Do nothing. // Do nothing.
} else if err := m.SetSubjectUID(f.SubjUID); err != nil { } else if err := m.SetSubjectUID(f.SubjUID); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -3,7 +3,7 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@ -24,7 +24,7 @@ func GetFile(router *gin.RouterGroup) {
return return
} }
p, err := query.FileByHash(sanitize.Token(c.Param("hash"))) p, err := query.FileByHash(clean.Token(c.Param("hash")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)

View file

@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"path/filepath" "path/filepath"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@ -36,8 +36,8 @@ func DeleteFile(router *gin.RouterGroup) {
return return
} }
photoUID := sanitize.IdString(c.Param("uid")) photoUID := clean.IdString(c.Param("uid"))
fileUID := sanitize.IdString(c.Param("file_uid")) fileUID := clean.IdString(c.Param("file_uid"))
file, err := query.FileByUID(fileUID) file, err := query.FileByUID(fileUID)
@ -59,17 +59,17 @@ func DeleteFile(router *gin.RouterGroup) {
mediaFile, err := photoprism.NewMediaFile(fileName) mediaFile, err := photoprism.NewMediaFile(fileName)
if err != nil { if err != nil {
log.Errorf("photo: %s (delete %s)", err, sanitize.Log(baseName)) log.Errorf("photo: %s (delete %s)", err, clean.Log(baseName))
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} }
if err := mediaFile.Remove(); err != nil { if err := mediaFile.Remove(); err != nil {
log.Errorf("photo: %s (delete %s from folder)", err, sanitize.Log(baseName)) log.Errorf("photo: %s (delete %s from folder)", err, clean.Log(baseName))
} }
if err := file.Delete(true); err != nil { if err := file.Delete(true); err != nil {
log.Errorf("photo: %s (delete %s from index)", err, sanitize.Log(baseName)) log.Errorf("photo: %s (delete %s from index)", err, clean.Log(baseName))
AbortDeleteFailed(c) AbortDeleteFailed(c)
return return
} }

View file

@ -5,7 +5,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
@ -37,7 +37,7 @@ func FolderCover(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
conf := service.Config() conf := service.Config()
uid := c.Param("uid") uid := c.Param("uid")
thumbName := thumb.Name(sanitize.Token(c.Param("size"))) thumbName := thumb.Name(clean.Token(c.Param("size")))
download := c.Query("download") != "" download := c.Query("download") != ""
size, ok := thumb.Sizes[thumbName] size, ok := thumb.Sizes[thumbName]
@ -98,7 +98,7 @@ func FolderCover(router *gin.RouterGroup) {
c.Data(http.StatusOK, "image/svg+xml", folderIconSvg) c.Data(http.StatusOK, "image/svg+xml", folderIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
log.Warnf("%s: %s is missing", folderCover, sanitize.Log(f.FileName)) log.Warnf("%s: %s is missing", folderCover, clean.Log(f.FileName))
logError(folderCover, f.Update("FileMissing", true)) logError(folderCover, f.Update("FileMissing", true))
return return
} }

View file

@ -18,8 +18,8 @@ import (
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// StartImport imports media files from a directory and converts/indexes them as needed. // StartImport imports media files from a directory and converts/indexes them as needed.
@ -53,7 +53,7 @@ func StartImport(router *gin.RouterGroup) {
subPath := "" subPath := ""
path := conf.ImportPath() path := conf.ImportPath()
if subPath = sanitize.Path(c.Param("path")); subPath != "" && subPath != "/" { if subPath = clean.Path(c.Param("path")); subPath != "" && subPath != "/" {
subPath = strings.Replace(subPath, ".", "", -1) subPath = strings.Replace(subPath, ".", "", -1)
path = filepath.Join(path, subPath) path = filepath.Join(path, subPath)
} else if f.Path != "" { } else if f.Path != "" {
@ -70,15 +70,15 @@ func StartImport(router *gin.RouterGroup) {
var opt photoprism.ImportOptions var opt photoprism.ImportOptions
if f.Move { if f.Move {
event.InfoMsg(i18n.MsgMovingFilesFrom, sanitize.Log(filepath.Base(path))) event.InfoMsg(i18n.MsgMovingFilesFrom, clean.Log(filepath.Base(path)))
opt = photoprism.ImportOptionsMove(path) opt = photoprism.ImportOptionsMove(path)
} else { } else {
event.InfoMsg(i18n.MsgCopyingFilesFrom, sanitize.Log(filepath.Base(path))) event.InfoMsg(i18n.MsgCopyingFilesFrom, clean.Log(filepath.Base(path)))
opt = photoprism.ImportOptionsCopy(path) opt = photoprism.ImportOptionsCopy(path)
} }
if len(f.Albums) > 0 { if len(f.Albums) > 0 {
log.Debugf("import: adding files to album %s", sanitize.Log(strings.Join(f.Albums, " and "))) log.Debugf("import: adding files to album %s", clean.Log(strings.Join(f.Albums, " and ")))
opt.Albums = f.Albums opt.Albums = f.Albums
} }
@ -86,9 +86,9 @@ func StartImport(router *gin.RouterGroup) {
if subPath != "" && path != conf.ImportPath() && fs.IsEmpty(path) { if subPath != "" && path != conf.ImportPath() && fs.IsEmpty(path) {
if err := os.Remove(path); err != nil { if err := os.Remove(path); err != nil {
log.Errorf("import: failed deleting empty folder %s: %s", sanitize.Log(path), err) log.Errorf("import: failed deleting empty folder %s: %s", clean.Log(path), err)
} else { } else {
log.Infof("import: deleted empty folder %s", sanitize.Log(path)) log.Infof("import: deleted empty folder %s", clean.Log(path))
} }
} }

View file

@ -13,7 +13,7 @@ import (
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@ -53,7 +53,7 @@ func StartIndexing(router *gin.RouterGroup) {
indOpt := photoprism.NewIndexOptions(filepath.Clean(f.Path), f.Rescan, convert, true, false) indOpt := photoprism.NewIndexOptions(filepath.Clean(f.Path), f.Rescan, convert, true, false)
if len(indOpt.Path) > 1 { if len(indOpt.Path) > 1 {
event.InfoMsg(i18n.MsgIndexingFiles, sanitize.Log(indOpt.Path)) event.InfoMsg(i18n.MsgIndexingFiles, clean.Log(indOpt.Path))
} else { } else {
event.InfoMsg(i18n.MsgIndexingOriginals) event.InfoMsg(i18n.MsgIndexingOriginals)
} }
@ -70,7 +70,7 @@ func StartIndexing(router *gin.RouterGroup) {
} }
if files, photos, err := prg.Start(prgOpt); err != nil { if files, photos, err := prg.Start(prgOpt); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} else if len(files) > 0 || len(photos) > 0 { } else if len(files) > 0 || len(photos) > 0 {
event.InfoMsg(i18n.MsgRemovedFilesAndPhotos, len(files), len(photos)) event.InfoMsg(i18n.MsgRemovedFilesAndPhotos, len(files), len(photos))

View file

@ -3,7 +3,7 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -35,7 +35,7 @@ func UpdateLabel(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
m, err := query.LabelByUID(id) m, err := query.LabelByUID(id)
if err != nil { if err != nil {
@ -69,16 +69,16 @@ func LikeLabel(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
label, err := query.LabelByUID(id) label, err := query.LabelByUID(id)
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
if err := label.Update("LabelFavorite", true); err != nil { if err := label.Update("LabelFavorite", true); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -109,16 +109,16 @@ func DislikeLabel(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
label, err := query.LabelByUID(id) label, err := query.LabelByUID(id)
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
if err := label.Update("LabelFavorite", false); err != nil { if err := label.Update("LabelFavorite", false); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -13,7 +13,7 @@ import (
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@ -33,7 +33,7 @@ func UpdateLink(c *gin.Context) {
return return
} }
link := entity.FindLink(sanitize.Token(c.Param("link"))) link := entity.FindLink(clean.Token(c.Param("link")))
link.SetSlug(f.ShareSlug) link.SetSlug(f.ShareSlug)
link.MaxViews = f.MaxViews link.MaxViews = f.MaxViews
@ -45,13 +45,13 @@ func UpdateLink(c *gin.Context) {
if f.Password != "" { if f.Password != "" {
if err := link.SetPassword(f.Password); err != nil { if err := link.SetPassword(f.Password); err != nil {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
} }
if err := link.Save(); err != nil { if err := link.Save(); err != nil {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -73,10 +73,10 @@ func DeleteLink(c *gin.Context) {
return return
} }
link := entity.FindLink(sanitize.Token(c.Param("link"))) link := entity.FindLink(clean.Token(c.Param("link")))
if err := link.Delete(); err != nil { if err := link.Delete(); err != nil {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -105,7 +105,7 @@ func CreateLink(c *gin.Context) {
return return
} }
link := entity.NewLink(sanitize.IdString(c.Param("uid")), f.CanComment, f.CanEdit) link := entity.NewLink(clean.IdString(c.Param("uid")), f.CanComment, f.CanEdit)
link.SetSlug(f.ShareSlug) link.SetSlug(f.ShareSlug)
link.MaxViews = f.MaxViews link.MaxViews = f.MaxViews
@ -113,13 +113,13 @@ func CreateLink(c *gin.Context) {
if f.Password != "" { if f.Password != "" {
if err := link.SetPassword(f.Password); err != nil { if err := link.SetPassword(f.Password); err != nil {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
} }
if err := link.Save(); err != nil { if err := link.Save(); err != nil {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -135,7 +135,7 @@ func CreateLink(c *gin.Context) {
// POST /api/v1/albums/:uid/links // POST /api/v1/albums/:uid/links
func CreateAlbumLink(router *gin.RouterGroup) { func CreateAlbumLink(router *gin.RouterGroup) {
router.POST("/albums/:uid/links", func(c *gin.Context) { router.POST("/albums/:uid/links", func(c *gin.Context) {
if _, err := query.AlbumByUID(sanitize.IdString(c.Param("uid"))); err != nil { if _, err := query.AlbumByUID(clean.IdString(c.Param("uid"))); err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
return return
} }
@ -161,7 +161,7 @@ func DeleteAlbumLink(router *gin.RouterGroup) {
// GET /api/v1/albums/:uid/links // GET /api/v1/albums/:uid/links
func GetAlbumLinks(router *gin.RouterGroup) { func GetAlbumLinks(router *gin.RouterGroup) {
router.GET("/albums/:uid/links", func(c *gin.Context) { router.GET("/albums/:uid/links", func(c *gin.Context) {
m, err := query.AlbumByUID(sanitize.IdString(c.Param("uid"))) m, err := query.AlbumByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@ -175,7 +175,7 @@ func GetAlbumLinks(router *gin.RouterGroup) {
// POST /api/v1/photos/:uid/links // POST /api/v1/photos/:uid/links
func CreatePhotoLink(router *gin.RouterGroup) { func CreatePhotoLink(router *gin.RouterGroup) {
router.POST("/photos/:uid/links", func(c *gin.Context) { router.POST("/photos/:uid/links", func(c *gin.Context) {
if _, err := query.PhotoByUID(sanitize.IdString(c.Param("uid"))); err != nil { if _, err := query.PhotoByUID(clean.IdString(c.Param("uid"))); err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} }
@ -201,7 +201,7 @@ func DeletePhotoLink(router *gin.RouterGroup) {
// GET /api/v1/photos/:uid/links // GET /api/v1/photos/:uid/links
func GetPhotoLinks(router *gin.RouterGroup) { func GetPhotoLinks(router *gin.RouterGroup) {
router.GET("/photos/:uid/links", func(c *gin.Context) { router.GET("/photos/:uid/links", func(c *gin.Context) {
m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid"))) m, err := query.PhotoByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@ -215,7 +215,7 @@ func GetPhotoLinks(router *gin.RouterGroup) {
// POST /api/v1/labels/:uid/links // POST /api/v1/labels/:uid/links
func CreateLabelLink(router *gin.RouterGroup) { func CreateLabelLink(router *gin.RouterGroup) {
router.POST("/labels/:uid/links", func(c *gin.Context) { router.POST("/labels/:uid/links", func(c *gin.Context) {
if _, err := query.LabelByUID(sanitize.IdString(c.Param("uid"))); err != nil { if _, err := query.LabelByUID(clean.IdString(c.Param("uid"))); err != nil {
Abort(c, http.StatusNotFound, i18n.ErrLabelNotFound) Abort(c, http.StatusNotFound, i18n.ErrLabelNotFound)
return return
} }
@ -241,7 +241,7 @@ func DeleteLabelLink(router *gin.RouterGroup) {
// GET /api/v1/labels/:uid/links // GET /api/v1/labels/:uid/links
func GetLabelLinks(router *gin.RouterGroup) { func GetLabelLinks(router *gin.RouterGroup) {
router.GET("/labels/:uid/links", func(c *gin.Context) { router.GET("/labels/:uid/links", func(c *gin.Context) {
m, err := query.LabelByUID(sanitize.IdString(c.Param("uid"))) m, err := query.LabelByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)

View file

@ -23,7 +23,7 @@ func GetMomentsTime(router *gin.RouterGroup) {
result, err := query.MomentsTime(1) result, err := query.MomentsTime(1)
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -15,8 +15,8 @@ import (
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// SavePhotoAsYaml saves photo data as YAML file. // SavePhotoAsYaml saves photo data as YAML file.
@ -33,7 +33,7 @@ func SavePhotoAsYaml(p entity.Photo) {
if err := p.SaveAsYaml(fileName); err != nil { if err := p.SaveAsYaml(fileName); err != nil {
log.Errorf("photo: %s (update yaml)", err) log.Errorf("photo: %s (update yaml)", err)
} else { } else {
log.Debugf("photo: updated yaml file %s", sanitize.Log(filepath.Base(fileName))) log.Debugf("photo: updated yaml file %s", clean.Log(filepath.Base(fileName)))
} }
} }
@ -51,7 +51,7 @@ func GetPhoto(router *gin.RouterGroup) {
return return
} }
p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid"))) p, err := query.PhotoPreloadByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@ -74,7 +74,7 @@ func UpdatePhoto(router *gin.RouterGroup) {
return return
} }
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
m, err := query.PhotoByUID(uid) m, err := query.PhotoByUID(uid)
if err != nil { if err != nil {
@ -136,7 +136,7 @@ func GetPhotoDownload(router *gin.RouterGroup) {
return return
} }
f, err := query.FileByPhotoUID(sanitize.IdString(c.Param("uid"))) f, err := query.FileByPhotoUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg) c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
@ -146,7 +146,7 @@ func GetPhotoDownload(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("photo: file %s is missing", sanitize.Log(f.FileName)) log.Errorf("photo: file %s is missing", clean.Log(f.FileName))
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg) c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
@ -172,7 +172,7 @@ func GetPhotoYaml(router *gin.RouterGroup) {
return return
} }
p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid"))) p, err := query.PhotoPreloadByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusNotFound) c.AbortWithStatus(http.StatusNotFound)
@ -187,7 +187,7 @@ func GetPhotoYaml(router *gin.RouterGroup) {
} }
if c.Query("download") != "" { if c.Query("download") != "" {
AddDownloadHeader(c, sanitize.IdString(c.Param("uid"))+fs.YamlExt) AddDownloadHeader(c, clean.IdString(c.Param("uid"))+fs.ExtYAML)
} }
c.Data(http.StatusOK, "text/x-yaml; charset=utf-8", data) c.Data(http.StatusOK, "text/x-yaml; charset=utf-8", data)
@ -207,7 +207,7 @@ func ApprovePhoto(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id) m, err := query.PhotoByUID(id)
if err != nil { if err != nil {
@ -242,7 +242,7 @@ func LikePhoto(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id) m, err := query.PhotoByUID(id)
if err != nil { if err != nil {
@ -277,7 +277,7 @@ func DislikePhoto(router *gin.RouterGroup) {
return return
} }
id := sanitize.IdString(c.Param("uid")) id := clean.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id) m, err := query.PhotoByUID(id)
if err != nil { if err != nil {
@ -313,8 +313,8 @@ func PhotoPrimary(router *gin.RouterGroup) {
return return
} }
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
fileUID := sanitize.IdString(c.Param("file_uid")) fileUID := clean.IdString(c.Param("file_uid"))
err := query.SetPhotoPrimary(uid, fileUID) err := query.SetPhotoPrimary(uid, fileUID)
if err != nil { if err != nil {

View file

@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@ -29,7 +29,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
return return
} }
m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid"))) m, err := query.PhotoByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@ -70,7 +70,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
} }
} }
p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid"))) p, err := query.PhotoPreloadByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@ -78,7 +78,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
} }
if err := p.SaveLabels(); err != nil { if err := p.SaveLabels(); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -104,24 +104,24 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
return return
} }
m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid"))) m, err := query.PhotoByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} }
labelId, err := strconv.Atoi(sanitize.Token(c.Param("id"))) labelId, err := strconv.Atoi(clean.Token(c.Param("id")))
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
label, err := query.PhotoLabel(m.ID, uint(labelId)) label, err := query.PhotoLabel(m.ID, uint(labelId))
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -132,7 +132,7 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
logError("label", entity.Db().Save(&label).Error) logError("label", entity.Db().Save(&label).Error)
} }
p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid"))) p, err := query.PhotoPreloadByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@ -142,11 +142,11 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
logError("label", p.RemoveKeyword(label.Label.LabelName)) logError("label", p.RemoveKeyword(label.Label.LabelName))
if err := p.SaveLabels(); err != nil { if err := p.SaveLabels(); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
PublishPhotoEvent(EntityUpdated, sanitize.IdString(c.Param("uid")), c) PublishPhotoEvent(EntityUpdated, clean.IdString(c.Param("uid")), c)
event.Success("label removed") event.Success("label removed")
@ -170,24 +170,24 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
// TODO: Code clean-up, simplify // TODO: Code clean-up, simplify
m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid"))) m, err := query.PhotoByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} }
labelId, err := strconv.Atoi(sanitize.Token(c.Param("id"))) labelId, err := strconv.Atoi(clean.Token(c.Param("id")))
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
label, err := query.PhotoLabel(m.ID, uint(labelId)) label, err := query.PhotoLabel(m.ID, uint(labelId))
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -197,11 +197,11 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
} }
if err := label.Save(); err != nil { if err := label.Save(); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid"))) p, err := query.PhotoPreloadByUID(clean.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@ -209,11 +209,11 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
} }
if err := p.SaveLabels(); err != nil { if err := p.SaveLabels(); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
PublishPhotoEvent(EntityUpdated, sanitize.IdString(c.Param("uid")), c) PublishPhotoEvent(EntityUpdated, clean.IdString(c.Param("uid")), c)
event.Success("label saved") event.Success("label saved")

View file

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"path/filepath" "path/filepath"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@ -34,7 +34,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
} }
conf := service.Config() conf := service.Config()
fileUID := sanitize.IdString(c.Param("file_uid")) fileUID := clean.IdString(c.Param("file_uid"))
file, err := query.FileByUID(fileUID) file, err := query.FileByUID(fileUID)
if err != nil { if err != nil {
@ -63,7 +63,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
unstackFile, err := photoprism.NewMediaFile(fileName) unstackFile, err := photoprism.NewMediaFile(fileName)
if err != nil { if err != nil {
log.Errorf("photo: %s (unstack %s)", err, sanitize.Log(baseName)) log.Errorf("photo: %s (unstack %s)", err, clean.Log(baseName))
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} else if file.Photo == nil { } else if file.Photo == nil {
@ -76,7 +76,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
stackPrimary, err := stackPhoto.PrimaryFile() stackPrimary, err := stackPhoto.PrimaryFile()
if err != nil { if err != nil {
log.Errorf("photo: cannot find primary file for %s (unstack)", sanitize.Log(baseName)) log.Errorf("photo: cannot find primary file for %s (unstack)", clean.Log(baseName))
AbortUnexpected(c) AbortUnexpected(c)
return return
} }
@ -87,15 +87,15 @@ func PhotoUnstack(router *gin.RouterGroup) {
related, err := unstackFile.RelatedFiles(false) related, err := unstackFile.RelatedFiles(false)
if err != nil { if err != nil {
log.Errorf("photo: %s (unstack %s)", err, sanitize.Log(baseName)) log.Errorf("photo: %s (unstack %s)", err, clean.Log(baseName))
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} else if related.Len() == 0 { } else if related.Len() == 0 {
log.Errorf("photo: found no files for %s (unstack)", sanitize.Log(baseName)) log.Errorf("photo: found no files for %s (unstack)", clean.Log(baseName))
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} else if related.Main == nil { } else if related.Main == nil {
log.Errorf("photo: found no main file for %s (unstack)", sanitize.Log(baseName)) log.Errorf("photo: found no main file for %s (unstack)", clean.Log(baseName))
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} }
@ -105,7 +105,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
if unstackFile.BasePrefix(false) == stackPhoto.PhotoName { if unstackFile.BasePrefix(false) == stackPhoto.PhotoName {
if conf.ReadOnly() { if conf.ReadOnly() {
log.Errorf("photo: cannot rename files in read only mode (unstack %s)", sanitize.Log(baseName)) log.Errorf("photo: cannot rename files in read only mode (unstack %s)", clean.Log(baseName))
AbortFeatureDisabled(c) AbortFeatureDisabled(c)
return return
} }
@ -113,7 +113,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
destName := fmt.Sprintf("%s.%s%s", unstackFile.AbsPrefix(false), unstackFile.Checksum(), unstackFile.Extension()) destName := fmt.Sprintf("%s.%s%s", unstackFile.AbsPrefix(false), unstackFile.Checksum(), unstackFile.Extension())
if err := unstackFile.Move(destName); err != nil { if err := unstackFile.Move(destName); err != nil {
log.Errorf("photo: cannot rename %s to %s (unstack)", sanitize.Log(unstackFile.BaseName()), sanitize.Log(filepath.Base(destName))) log.Errorf("photo: cannot rename %s to %s (unstack)", clean.Log(unstackFile.BaseName()), clean.Log(filepath.Base(destName)))
AbortUnexpected(c) AbortUnexpected(c)
return return
} }
@ -130,7 +130,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
newPhoto.PhotoName = unstackFile.BasePrefix(false) newPhoto.PhotoName = unstackFile.BasePrefix(false)
if err := newPhoto.Create(); err != nil { if err := newPhoto.Create(); err != nil {
log.Errorf("photo: %s (unstack %s)", err.Error(), sanitize.Log(baseName)) log.Errorf("photo: %s (unstack %s)", err.Error(), clean.Log(baseName))
AbortSaveFailed(c) AbortSaveFailed(c)
return return
} }
@ -150,17 +150,17 @@ func PhotoUnstack(router *gin.RouterGroup) {
newPhoto.ID, newPhoto.PhotoUID, r.RootRelName(), newPhoto.ID, newPhoto.PhotoUID, r.RootRelName(),
relName, relRoot).Error; err != nil { relName, relRoot).Error; err != nil {
// Handle error... // Handle error...
log.Errorf("photo: %s (unstack %s)", err.Error(), sanitize.Log(r.BaseName())) log.Errorf("photo: %s (unstack %s)", err.Error(), clean.Log(r.BaseName()))
// Remove new photo from index. // Remove new photo from index.
if _, err := newPhoto.Delete(true); err != nil { if _, err := newPhoto.Delete(true); err != nil {
log.Errorf("photo: %s (unstack %s)", err.Error(), sanitize.Log(r.BaseName())) log.Errorf("photo: %s (unstack %s)", err.Error(), clean.Log(r.BaseName()))
} }
// Revert file rename. // Revert file rename.
if unstackSingle { if unstackSingle {
if err := r.Move(photoprism.FileName(relRoot, relName)); err != nil { if err := r.Move(photoprism.FileName(relRoot, relName)); err != nil {
log.Errorf("photo: %s (unstack %s)", err.Error(), sanitize.Log(r.BaseName())) log.Errorf("photo: %s (unstack %s)", err.Error(), clean.Log(r.BaseName()))
} }
} }
@ -173,21 +173,21 @@ func PhotoUnstack(router *gin.RouterGroup) {
// Index unstacked files. // Index unstacked files.
if res := ind.FileName(unstackFile.FileName(), photoprism.IndexOptionsSingle()); res.Failed() { if res := ind.FileName(unstackFile.FileName(), photoprism.IndexOptionsSingle()); res.Failed() {
log.Errorf("photo: %s (unstack %s)", res.Err, sanitize.Log(baseName)) log.Errorf("photo: %s (unstack %s)", res.Err, clean.Log(baseName))
AbortSaveFailed(c) AbortSaveFailed(c)
return return
} }
// Reset type for existing photo stack to image. // Reset type for existing photo stack to image.
if err := stackPhoto.Update("PhotoType", entity.MediaImage); err != nil { if err := stackPhoto.Update("PhotoType", entity.MediaImage); err != nil {
log.Errorf("photo: %s (unstack %s)", err, sanitize.Log(baseName)) log.Errorf("photo: %s (unstack %s)", err, clean.Log(baseName))
AbortUnexpected(c) AbortUnexpected(c)
return return
} }
// Re-index existing photo stack. // Re-index existing photo stack.
if res := ind.FileName(photoprism.FileName(stackPrimary.FileRoot, stackPrimary.FileName), photoprism.IndexOptionsSingle()); res.Failed() { if res := ind.FileName(photoprism.FileName(stackPrimary.FileRoot, stackPrimary.FileName), photoprism.IndexOptionsSingle()); res.Failed() {
log.Errorf("photo: %s (unstack %s)", res.Err, sanitize.Log(baseName)) log.Errorf("photo: %s (unstack %s)", res.Err, clean.Log(baseName))
AbortSaveFailed(c) AbortSaveFailed(c)
return return
} }

View file

@ -41,7 +41,7 @@ func SearchAlbums(router *gin.RouterGroup) {
result, err := search.Albums(f) result, err := search.Albums(f)
if err != nil { if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -36,7 +36,7 @@ func SearchFaces(router *gin.RouterGroup) {
result, err := search.Faces(f) result, err := search.Faces(f)
if err != nil { if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -6,7 +6,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
@ -68,7 +68,7 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
listFiles := f.Files listFiles := f.Files
uncached := listFiles || f.Uncached uncached := listFiles || f.Uncached
resp := FoldersResponse{Root: rootName, Recursive: recursive, Cached: !uncached} resp := FoldersResponse{Root: rootName, Recursive: recursive, Cached: !uncached}
path := sanitize.Path(c.Param("path")) path := clean.Path(c.Param("path"))
cacheKey := fmt.Sprintf("folder:%s:%t:%t", filepath.Join(rootName, path), recursive, listFiles) cacheKey := fmt.Sprintf("folder:%s:%t:%t", filepath.Join(rootName, path), recursive, listFiles)

View file

@ -11,7 +11,7 @@ import (
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@ -66,7 +66,7 @@ func SearchGeo(router *gin.RouterGroup) {
var resp []byte var resp []byte
// Render JSON response. // Render JSON response.
switch sanitize.Token(c.Param("format")) { switch clean.Token(c.Param("format")) {
case "view": case "view":
conf := service.Config() conf := service.Config()
resp, err = photos.ViewerJSON(conf.ContentUri(), conf.ApiUri(), conf.PreviewToken(), conf.DownloadToken()) resp, err = photos.ViewerJSON(conf.ContentUri(), conf.ApiUri(), conf.PreviewToken(), conf.DownloadToken())
@ -75,7 +75,7 @@ func SearchGeo(router *gin.RouterGroup) {
} }
if err != nil { if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -35,7 +35,7 @@ func SearchLabels(router *gin.RouterGroup) {
result, err := search.Labels(f) result, err := search.Labels(f)
if err != nil { if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -36,7 +36,7 @@ func SearchSubjects(router *gin.RouterGroup) {
result, err := search.Subjects(f) result, err := search.Subjects(f)
if err != nil { if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -3,7 +3,7 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@ -91,7 +91,7 @@ func CreateSession(router *gin.RouterGroup) {
// DELETE /api/v1/session/:id // DELETE /api/v1/session/:id
func DeleteSession(router *gin.RouterGroup) { func DeleteSession(router *gin.RouterGroup) {
router.DELETE("/session/:id", func(c *gin.Context) { router.DELETE("/session/:id", func(c *gin.Context) {
id := sanitize.Token(c.Param("id")) id := clean.Token(c.Param("id"))
service.Session().Delete(id) service.Session().Delete(id)
@ -128,10 +128,10 @@ func Auth(id string, resource acl.Resource, action acl.Action) session.Data {
// InvalidPreviewToken returns true if the token is invalid. // InvalidPreviewToken returns true if the token is invalid.
func InvalidPreviewToken(c *gin.Context) bool { func InvalidPreviewToken(c *gin.Context) bool {
token := sanitize.Token(c.Param("token")) token := clean.Token(c.Param("token"))
if token == "" { if token == "" {
token = sanitize.Token(c.Query("t")) token = clean.Token(c.Query("t"))
} }
return service.Config().InvalidPreviewToken(token) return service.Config().InvalidPreviewToken(token)
@ -139,5 +139,5 @@ func InvalidPreviewToken(c *gin.Context) bool {
// InvalidDownloadToken returns true if the token is invalid. // InvalidDownloadToken returns true if the token is invalid.
func InvalidDownloadToken(c *gin.Context) bool { func InvalidDownloadToken(c *gin.Context) bool {
return service.Config().InvalidDownloadToken(sanitize.Token(c.Query("t"))) return service.Config().InvalidDownloadToken(clean.Token(c.Query("t")))
} }

View file

@ -9,7 +9,7 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
// GET /s/:token/... // GET /s/:token/...
@ -17,7 +17,7 @@ func Shares(router *gin.RouterGroup) {
router.GET("/:token", func(c *gin.Context) { router.GET("/:token", func(c *gin.Context) {
conf := service.Config() conf := service.Config()
token := sanitize.Token(c.Param("token")) token := clean.Token(c.Param("token"))
links := entity.FindValidLinks(token, "") links := entity.FindValidLinks(token, "")
@ -36,8 +36,8 @@ func Shares(router *gin.RouterGroup) {
router.GET("/:token/:share", func(c *gin.Context) { router.GET("/:token/:share", func(c *gin.Context) {
conf := service.Config() conf := service.Config()
token := sanitize.Token(c.Param("token")) token := clean.Token(c.Param("token"))
share := sanitize.Token(c.Param("share")) share := clean.Token(c.Param("share"))
links := entity.FindValidLinks(token, share) links := entity.FindValidLinks(token, share)

View file

@ -9,7 +9,7 @@ import (
"path" "path"
"time" "time"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -30,8 +30,8 @@ func SharePreview(router *gin.RouterGroup) {
router.GET("/:token/:share/preview", func(c *gin.Context) { router.GET("/:token/:share/preview", func(c *gin.Context) {
conf := service.Config() conf := service.Config()
token := sanitize.Token(c.Param("token")) token := clean.Token(c.Param("token"))
share := sanitize.Token(c.Param("share")) share := clean.Token(c.Param("share"))
links := entity.FindLinks(token, share) links := entity.FindLinks(token, share)
if len(links) != 1 { if len(links) != 1 {
@ -52,13 +52,13 @@ func SharePreview(router *gin.RouterGroup) {
yesterday := time.Now().Add(-24 * time.Hour) yesterday := time.Now().Add(-24 * time.Hour)
if info, err := os.Stat(previewFilename); err != nil { if info, err := os.Stat(previewFilename); err != nil {
log.Debugf("share: creating new preview for %s", sanitize.Log(share)) log.Debugf("share: creating new preview for %s", clean.Log(share))
} else if info.ModTime().After(yesterday) { } else if info.ModTime().After(yesterday) {
log.Debugf("share: using cached preview for %s", sanitize.Log(share)) log.Debugf("share: using cached preview for %s", clean.Log(share))
c.File(previewFilename) c.File(previewFilename)
return return
} else if err := os.Remove(previewFilename); err != nil { } else if err := os.Remove(previewFilename); err != nil {
log.Errorf("share: could not remove old preview of %s", sanitize.Log(share)) log.Errorf("share: could not remove old preview of %s", clean.Log(share))
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview()) c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return return
} }
@ -96,7 +96,7 @@ func SharePreview(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("share: file %s is missing (preview)", sanitize.Log(f.FileName)) log.Errorf("share: file %s is missing (preview)", clean.Log(f.FileName))
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview()) c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return return
} }
@ -126,7 +126,7 @@ func SharePreview(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("share: file %s is missing (preview)", sanitize.Log(f.FileName)) log.Errorf("share: file %s is missing (preview)", clean.Log(f.FileName))
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview()) c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return return
} }

View file

@ -3,7 +3,7 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -28,7 +28,7 @@ func GetSubject(router *gin.RouterGroup) {
return return
} }
if subj := entity.FindSubject(sanitize.IdString(c.Param("uid"))); subj == nil { if subj := entity.FindSubject(clean.IdString(c.Param("uid"))); subj == nil {
Abort(c, http.StatusNotFound, i18n.ErrSubjectNotFound) Abort(c, http.StatusNotFound, i18n.ErrSubjectNotFound)
return return
} else { } else {
@ -56,7 +56,7 @@ func UpdateSubject(router *gin.RouterGroup) {
return return
} }
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
m := entity.FindSubject(uid) m := entity.FindSubject(uid)
if m == nil { if m == nil {
@ -109,7 +109,7 @@ func LikeSubject(router *gin.RouterGroup) {
return return
} }
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
subj := entity.FindSubject(uid) subj := entity.FindSubject(uid)
if subj == nil { if subj == nil {
@ -118,7 +118,7 @@ func LikeSubject(router *gin.RouterGroup) {
} }
if err := subj.Update("SubjFavorite", true); err != nil { if err := subj.Update("SubjFavorite", true); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }
@ -143,7 +143,7 @@ func DislikeSubject(router *gin.RouterGroup) {
return return
} }
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
subj := entity.FindSubject(uid) subj := entity.FindSubject(uid)
if subj == nil { if subj == nil {
@ -152,7 +152,7 @@ func DislikeSubject(router *gin.RouterGroup) {
} }
if err := subj.Update("SubjFavorite", false); err != nil { if err := subj.Update("SubjFavorite", false); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
return return
} }

View file

@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// GetThumb returns a thumbnail image matching the file hash, crop area, and type. // GetThumb returns a thumbnail image matching the file hash, crop area, and type.
@ -37,16 +37,16 @@ func GetThumb(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
conf := service.Config() conf := service.Config()
download := c.Query("download") != "" download := c.Query("download") != ""
fileHash, cropArea := crop.ParseThumb(sanitize.Token(c.Param("thumb"))) fileHash, cropArea := crop.ParseThumb(clean.Token(c.Param("thumb")))
// Is cropped thumbnail? // Is cropped thumbnail?
if cropArea != "" { if cropArea != "" {
cropName := crop.Name(sanitize.Token(c.Param("size"))) cropName := crop.Name(clean.Token(c.Param("size")))
cropSize, ok := crop.Sizes[cropName] cropSize, ok := crop.Sizes[cropName]
if !ok { if !ok {
log.Errorf("%s: invalid size %s", logPrefix, sanitize.Log(string(cropName))) log.Errorf("%s: invalid size %s", logPrefix, clean.Log(string(cropName)))
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg) c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return return
} }
@ -73,12 +73,12 @@ func GetThumb(router *gin.RouterGroup) {
return return
} }
thumbName := thumb.Name(sanitize.Token(c.Param("size"))) thumbName := thumb.Name(clean.Token(c.Param("size")))
size, ok := thumb.Sizes[thumbName] size, ok := thumb.Sizes[thumbName]
if !ok { if !ok {
log.Errorf("%s: invalid size %s", logPrefix, sanitize.Log(thumbName.String())) log.Errorf("%s: invalid size %s", logPrefix, clean.Log(thumbName.String()))
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg) c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return return
} }
@ -153,17 +153,17 @@ func GetThumb(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("%s: file %s is missing", logPrefix, sanitize.Log(f.FileName)) log.Errorf("%s: file %s is missing", logPrefix, clean.Log(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg) c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
logError(logPrefix, f.Update("FileMissing", true)) logError(logPrefix, f.Update("FileMissing", true))
if f.AllFilesMissing() { if f.AllFilesMissing() {
log.Infof("%s: deleting photo, all files missing for %s", logPrefix, sanitize.Log(f.FileName)) log.Infof("%s: deleting photo, all files missing for %s", logPrefix, clean.Log(f.FileName))
if _, err := f.RelatedPhoto().Delete(false); err != nil { if _, err := f.RelatedPhoto().Delete(false); err != nil {
log.Errorf("%s: %s while deleting %s", logPrefix, err, sanitize.Log(f.FileName)) log.Errorf("%s: %s while deleting %s", logPrefix, err, clean.Log(f.FileName))
} }
} }

View file

@ -12,7 +12,7 @@ import (
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
// POST /api/v1/upload/:path // POST /api/v1/upload/:path
@ -32,7 +32,7 @@ func Upload(router *gin.RouterGroup) {
} }
start := time.Now() start := time.Now()
subPath := sanitize.Path(c.Param("path")) subPath := clean.Path(c.Param("path"))
f, err := c.MultipartForm() f, err := c.MultipartForm()
@ -51,7 +51,7 @@ func Upload(router *gin.RouterGroup) {
p := path.Join(conf.ImportPath(), "upload", subPath) p := path.Join(conf.ImportPath(), "upload", subPath)
if err := os.MkdirAll(p, os.ModePerm); err != nil { if err := os.MkdirAll(p, os.ModePerm); err != nil {
log.Errorf("upload: failed creating folder %s", sanitize.Log(subPath)) log.Errorf("upload: failed creating folder %s", clean.Log(subPath))
AbortBadRequest(c) AbortBadRequest(c)
return return
} }
@ -59,10 +59,10 @@ func Upload(router *gin.RouterGroup) {
for _, file := range files { for _, file := range files {
filename := path.Join(p, filepath.Base(file.Filename)) filename := path.Join(p, filepath.Base(file.Filename))
log.Debugf("upload: saving file %s", sanitize.Log(file.Filename)) log.Debugf("upload: saving file %s", clean.Log(file.Filename))
if err := c.SaveUploadedFile(file, filename); err != nil { if err := c.SaveUploadedFile(file, filename); err != nil {
log.Errorf("upload: failed saving file %s", sanitize.Log(filepath.Base(file.Filename))) log.Errorf("upload: failed saving file %s", clean.Log(filepath.Base(file.Filename)))
AbortBadRequest(c) AbortBadRequest(c)
return return
} }
@ -87,7 +87,7 @@ func Upload(router *gin.RouterGroup) {
continue continue
} }
log.Infof("nsfw: %s might be offensive", sanitize.Log(filename)) log.Infof("nsfw: %s might be offensive", clean.Log(filename))
containsNSFW = true containsNSFW = true
} }
@ -95,7 +95,7 @@ func Upload(router *gin.RouterGroup) {
if containsNSFW { if containsNSFW {
for _, filename := range uploads { for _, filename := range uploads {
if err := os.Remove(filename); err != nil { if err := os.Remove(filename); err != nil {
log.Errorf("nsfw: could not delete %s", sanitize.Log(filename)) log.Errorf("nsfw: could not delete %s", clean.Log(filename))
} }
} }

View file

@ -3,7 +3,7 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@ -30,7 +30,7 @@ func ChangePassword(router *gin.RouterGroup) {
return return
} }
uid := sanitize.IdString(c.Param("uid")) uid := clean.IdString(c.Param("uid"))
m := entity.FindUserByUID(uid) m := entity.FindUserByUID(uid)
if s.User.UserUID != m.UserUID { if s.User.UserUID != m.UserUID {

View file

@ -3,13 +3,14 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/video"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/video" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// GetVideo streams videos. // GetVideo streams videos.
@ -26,13 +27,13 @@ func GetVideo(router *gin.RouterGroup) {
return return
} }
fileHash := sanitize.Token(c.Param("hash")) fileHash := clean.Token(c.Param("hash"))
formatName := sanitize.Token(c.Param("format")) formatName := clean.Token(c.Param("format"))
format, ok := video.Formats[formatName] format, ok := video.Types[formatName]
if !ok { if !ok {
log.Errorf("video: invalid format %s", sanitize.Log(formatName)) log.Errorf("video: invalid format %s", clean.Log(formatName))
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg) c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
return return
} }
@ -64,7 +65,7 @@ func GetVideo(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if mf, err := photoprism.NewMediaFile(fileName); err != nil { if mf, err := photoprism.NewMediaFile(fileName); err != nil {
log.Errorf("video: file %s is missing", sanitize.Log(f.FileName)) log.Errorf("video: file %s is missing", clean.Log(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg) c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
@ -75,7 +76,7 @@ func GetVideo(router *gin.RouterGroup) {
conv := service.Convert() conv := service.Convert()
if avcFile, err := conv.ToAvc(mf, service.Config().FFmpegEncoder(), false, false); err != nil { if avcFile, err := conv.ToAvc(mf, service.Config().FFmpegEncoder(), false, false); err != nil {
log.Errorf("video: transcoding %s failed", sanitize.Log(f.FileName)) log.Errorf("video: transcoding %s failed", clean.Log(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg) c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
return return
} else { } else {

View file

@ -12,7 +12,7 @@ import (
"github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
var autoImport = time.Time{} var autoImport = time.Time{}
@ -66,7 +66,7 @@ func Import() error {
api.RemoveFromFolderCache(entity.RootImport) api.RemoveFromFolderCache(entity.RootImport)
event.InfoMsg(i18n.MsgCopyingFilesFrom, sanitize.Log(filepath.Base(path))) event.InfoMsg(i18n.MsgCopyingFilesFrom, clean.Log(filepath.Base(path)))
var opt photoprism.ImportOptions var opt photoprism.ImportOptions

View file

@ -11,8 +11,8 @@ import (
"text/template" "text/template"
"unicode" "unicode"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -34,7 +34,7 @@ func main() {
fileName := "rules.yml" fileName := "rules.yml"
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Panicf("classify: found no label rules in %s", sanitize.Log(filepath.Base(fileName))) log.Panicf("classify: found no label rules in %s", clean.Log(filepath.Base(fileName)))
} }
yamlConfig, err := os.ReadFile(fileName) yamlConfig, err := os.ReadFile(fileName)

View file

@ -14,7 +14,7 @@ import (
"strings" "strings"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
tf "github.com/tensorflow/tensorflow/tensorflow/go" tf "github.com/tensorflow/tensorflow/tensorflow/go"
) )
@ -148,7 +148,7 @@ func (t *TensorFlow) loadModel() error {
modelPath := path.Join(t.modelsPath, t.modelName) modelPath := path.Join(t.modelsPath, t.modelName)
log.Infof("classify: loading %s", sanitize.Log(filepath.Base(modelPath))) log.Infof("classify: loading %s", clean.Log(filepath.Base(modelPath)))
// Load model // Load model
model, err := tf.LoadSavedModel(modelPath, t.modelTags, nil) model, err := tf.LoadSavedModel(modelPath, t.modelTags, nil)

View file

@ -16,8 +16,8 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
const backupDescription = "A user-defined SQL dump FILENAME or - for stdout can be passed as the first argument. " + const backupDescription = "A user-defined SQL dump FILENAME or - for stdout can be passed as the first argument. " +
@ -113,7 +113,7 @@ func backupAction(ctx *cli.Context) error {
} }
} }
log.Infof("writing SQL dump to %s", sanitize.Log(indexFileName)) log.Infof("writing SQL dump to %s", clean.Log(indexFileName))
} }
var cmd *exec.Cmd var cmd *exec.Cmd
@ -178,7 +178,7 @@ func backupAction(ctx *cli.Context) error {
albumsPath = conf.AlbumsPath() albumsPath = conf.AlbumsPath()
} }
log.Infof("saving albums in %s", sanitize.Log(albumsPath)) log.Infof("saving albums in %s", clean.Log(albumsPath))
if count, err := photoprism.BackupAlbums(albumsPath, true); err != nil { if count, err := photoprism.BackupAlbums(albumsPath, true); err != nil {
return err return err

View file

@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
// ConvertCommand registers the convert cli command. // ConvertCommand registers the convert cli command.
@ -54,7 +54,7 @@ func convertAction(ctx *cli.Context) error {
convertPath = filepath.Join(convertPath, subPath) convertPath = filepath.Join(convertPath, subPath)
} }
log.Infof("converting originals in %s", sanitize.Log(convertPath)) log.Infof("converting originals in %s", clean.Log(convertPath))
w := service.Convert() w := service.Convert()

View file

@ -14,8 +14,8 @@ import (
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// FacesCommand registers the facial recognition subcommands. // FacesCommand registers the facial recognition subcommands.
@ -239,9 +239,9 @@ func facesIndexAction(ctx *cli.Context) error {
subPath := strings.TrimSpace(ctx.Args().First()) subPath := strings.TrimSpace(ctx.Args().First())
if subPath == "" { if subPath == "" {
log.Infof("finding faces in %s", sanitize.Log(conf.OriginalsPath())) log.Infof("finding faces in %s", clean.Log(conf.OriginalsPath()))
} else { } else {
log.Infof("finding faces in %s", sanitize.Log(filepath.Join(conf.OriginalsPath(), subPath))) log.Infof("finding faces in %s", clean.Log(filepath.Join(conf.OriginalsPath(), subPath)))
} }
if conf.ReadOnly() { if conf.ReadOnly() {

View file

@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// IndexCommand registers the index cli command. // IndexCommand registers the index cli command.
@ -57,9 +57,9 @@ func indexAction(ctx *cli.Context) error {
subPath := strings.TrimSpace(ctx.Args().First()) subPath := strings.TrimSpace(ctx.Args().First())
if subPath == "" { if subPath == "" {
log.Infof("indexing originals in %s", sanitize.Log(conf.OriginalsPath())) log.Infof("indexing originals in %s", clean.Log(conf.OriginalsPath()))
} else { } else {
log.Infof("indexing originals in %s", sanitize.Log(filepath.Join(conf.OriginalsPath(), subPath))) log.Infof("indexing originals in %s", clean.Log(filepath.Join(conf.OriginalsPath(), subPath)))
} }
if conf.ReadOnly() { if conf.ReadOnly() {

View file

@ -14,7 +14,7 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
// PasswdCommand updates a password. // PasswdCommand updates a password.
@ -39,7 +39,7 @@ func passwdAction(ctx *cli.Context) error {
user := entity.Admin user := entity.Admin
log.Infof("please enter a new password for %s (at least 6 characters)\n", sanitize.Log(user.Username())) log.Infof("please enter a new password for %s (at least 6 characters)\n", clean.Log(user.Username()))
newPassword := getPassword("New Password: ") newPassword := getPassword("New Password: ")
@ -57,7 +57,7 @@ func passwdAction(ctx *cli.Context) error {
return err return err
} }
log.Infof("changed password for %s\n", sanitize.Log(user.Username())) log.Infof("changed password for %s\n", clean.Log(user.Username()))
conf.Shutdown() conf.Shutdown()

View file

@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// PurgeCommand registers the index cli command. // PurgeCommand registers the index cli command.
@ -56,9 +56,9 @@ func purgeAction(ctx *cli.Context) error {
subPath := strings.TrimSpace(ctx.Args().First()) subPath := strings.TrimSpace(ctx.Args().First())
if subPath == "" { if subPath == "" {
log.Infof("purge: removing missing files in %s", sanitize.Log(filepath.Base(conf.OriginalsPath()))) log.Infof("purge: removing missing files in %s", clean.Log(filepath.Base(conf.OriginalsPath())))
} else { } else {
log.Infof("purge: removing missing files in %s", sanitize.Log(fs.RelName(filepath.Join(conf.OriginalsPath(), subPath), filepath.Dir(conf.OriginalsPath())))) log.Infof("purge: removing missing files in %s", clean.Log(fs.RelName(filepath.Join(conf.OriginalsPath(), subPath), filepath.Dir(conf.OriginalsPath()))))
} }
if conf.ReadOnly() { if conf.ReadOnly() {

View file

@ -18,8 +18,8 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
const restoreDescription = "A user-defined SQL dump FILENAME can be passed as the first argument. " + const restoreDescription = "A user-defined SQL dump FILENAME can be passed as the first argument. " +
@ -124,7 +124,7 @@ func restoreAction(ctx *cli.Context) error {
log.Warnf("replacing existing index with %d photos", counts.Photos) log.Warnf("replacing existing index with %d photos", counts.Photos)
} }
log.Infof("restoring index from %s", sanitize.Log(indexFileName)) log.Infof("restoring index from %s", clean.Log(indexFileName))
sqlBackup, err := os.ReadFile(indexFileName) sqlBackup, err := os.ReadFile(indexFileName)
@ -203,9 +203,9 @@ func restoreAction(ctx *cli.Context) error {
} }
if !fs.PathExists(albumsPath) { if !fs.PathExists(albumsPath) {
log.Warnf("album files path %s not found", sanitize.Log(albumsPath)) log.Warnf("album files path %s not found", clean.Log(albumsPath))
} else { } else {
log.Infof("restoring albums from %s", sanitize.Log(albumsPath)) log.Infof("restoring albums from %s", clean.Log(albumsPath))
if count, err := photoprism.RestoreAlbums(albumsPath, true); err != nil { if count, err := photoprism.RestoreAlbums(albumsPath, true); err != nil {
return err return err

View file

@ -6,6 +6,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/report" "github.com/photoprism/photoprism/pkg/report"
) )
@ -15,8 +16,8 @@ var ShowFormatsCommand = cli.Command{
Usage: "Lists supported media and sidecar file formats", Usage: "Lists supported media and sidecar file formats",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
Name: "compact, c", Name: "short, s",
Usage: "hide format descriptions to make the output more compact", Usage: "hides format descriptions",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "md, m", Name: "md, m",
@ -28,8 +29,7 @@ var ShowFormatsCommand = cli.Command{
// showFormatsAction lists supported media and sidecar file formats. // showFormatsAction lists supported media and sidecar file formats.
func showFormatsAction(ctx *cli.Context) error { func showFormatsAction(ctx *cli.Context) error {
rows, cols := fs.Extensions.Formats(true).Report(!ctx.Bool("compact"), true, true) rows, cols := media.Report(fs.Extensions.Types(true), !ctx.Bool("short"), true, true)
fmt.Println(report.Table(rows, cols, ctx.Bool("md"))) fmt.Println(report.Table(rows, cols, ctx.Bool("md")))
return nil return nil

View file

@ -18,8 +18,8 @@ import (
"github.com/photoprism/photoprism/internal/server" "github.com/photoprism/photoprism/internal/server"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/workers" "github.com/photoprism/photoprism/internal/workers"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// StartCommand registers the start cli command. // StartCommand registers the start cli command.
@ -95,7 +95,7 @@ func startAction(ctx *cli.Context) error {
if child != nil { if child != nil {
if !fs.Overwrite(conf.PIDFilename(), []byte(strconv.Itoa(child.Pid))) { if !fs.Overwrite(conf.PIDFilename(), []byte(strconv.Itoa(child.Pid))) {
log.Fatalf("failed writing process id to %s", sanitize.Log(conf.PIDFilename())) log.Fatalf("failed writing process id to %s", clean.Log(conf.PIDFilename()))
} }
log.Infof("daemon started with process id %v\n", child.Pid) log.Infof("daemon started with process id %v\n", child.Pid)

View file

@ -7,7 +7,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
// StopCommand registers the stop cli command. // StopCommand registers the stop cli command.
@ -22,7 +22,7 @@ var StopCommand = cli.Command{
func stopAction(ctx *cli.Context) error { func stopAction(ctx *cli.Context) error {
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
log.Infof("looking for pid in %s", sanitize.Log(conf.PIDFilename())) log.Infof("looking for pid in %s", clean.Log(conf.PIDFilename()))
dcxt := new(daemon.Context) dcxt := new(daemon.Context)
dcxt.PidFileName = conf.PIDFilename() dcxt.PidFileName = conf.PIDFilename()

View file

@ -7,7 +7,7 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
// ThumbsCommand registers the resample cli command. // ThumbsCommand registers the resample cli command.
@ -34,7 +34,7 @@ func thumbsAction(ctx *cli.Context) error {
return err return err
} }
log.Infof("creating thumbnails in %s", sanitize.Log(conf.ThumbPath())) log.Infof("creating thumbnails in %s", clean.Log(conf.ThumbPath()))
rs := service.Resample() rs := service.Resample()

View file

@ -14,7 +14,7 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/sanitize" "github.com/photoprism/photoprism/pkg/clean"
) )
// UsersCommand registers user management subcommands. // UsersCommand registers user management subcommands.
@ -183,7 +183,7 @@ func usersDeleteAction(ctx *cli.Context) error {
} }
actionPrompt := promptui.Prompt{ actionPrompt := promptui.Prompt{
Label: fmt.Sprintf("Delete %s?", sanitize.Log(userName)), Label: fmt.Sprintf("Delete %s?", clean.Log(userName)),
IsConfirm: true, IsConfirm: true,
} }
@ -193,7 +193,7 @@ func usersDeleteAction(ctx *cli.Context) error {
} else if err := m.Delete(); err != nil { } else if err := m.Delete(); err != nil {
return err return err
} else { } else {
log.Infof("%s deleted", sanitize.Log(userName)) log.Infof("%s deleted", clean.Log(userName))
} }
} else { } else {
log.Infof("keeping user") log.Infof("keeping user")
@ -242,7 +242,7 @@ func usersUpdateAction(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("password successfully changed: %s\n", sanitize.Log(u.Username())) fmt.Printf("password successfully changed: %s\n", clean.Log(u.Username()))
} }
if ctx.IsSet("fullname") { if ctx.IsSet("fullname") {
@ -261,7 +261,7 @@ func usersUpdateAction(ctx *cli.Context) error {
return err return err
} }
fmt.Printf("user successfully updated: %s\n", sanitize.Log(u.Username())) fmt.Printf("user successfully updated: %s\n", clean.Log(u.Username()))
return nil return nil
}) })

View file

@ -6,7 +6,9 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/colors" "github.com/photoprism/photoprism/pkg/colors"
"github.com/photoprism/photoprism/pkg/env"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@ -64,7 +66,7 @@ type ClientConfig struct {
Colors []map[string]string `json:"colors"` Colors []map[string]string `json:"colors"`
Categories CategoryLabels `json:"categories"` Categories CategoryLabels `json:"categories"`
Clip int `json:"clip"` Clip int `json:"clip"`
Server RuntimeInfo `json:"server"` Server env.Resources `json:"server"`
} }
// Years represents a list of years. // Years represents a list of years.
@ -384,7 +386,7 @@ func (c *Config) UserConfig() ClientConfig {
PreviewToken: c.PreviewToken(), PreviewToken: c.PreviewToken(),
ManifestUri: c.ClientManifestUri(), ManifestUri: c.ClientManifestUri(),
Clip: txt.ClipDefault, Clip: txt.ClipDefault,
Server: NewRuntimeInfo(), Server: env.Info(),
} }
c.Db(). c.Db().
@ -409,12 +411,12 @@ func (c *Config) UserConfig() ClientConfig {
c.Db(). c.Db().
Table("photos"). Table("photos").
Select("SUM(photo_type = 'video' AND photo_quality >= 0 AND photo_private = 0) AS videos, " + Select("SUM(photo_type = 'video' AND photo_quality > -1 AND photo_private = 0) AS videos, " +
"SUM(photo_type = 'live' AND photo_quality >= 0 AND photo_private = 0) AS live, " + "SUM(photo_type = 'live' AND photo_quality > -1 AND photo_private = 0) AS live, " +
"SUM(photo_quality = -1) AS hidden, SUM(photo_type IN ('image','raw','animated') AND photo_private = 0 AND photo_quality >= 0) AS photos, " + "SUM(photo_quality = -1) AS hidden, SUM(photo_type IN ('image','raw','animated') AND photo_private = 0 AND photo_quality > -1) AS photos, " +
"SUM(photo_type IN ('image','raw','live','animated') AND photo_quality < 3 AND photo_quality >= 0 AND photo_private = 0) AS review, " + "SUM(photo_type IN ('image','raw','live','animated') AND photo_quality < 3 AND photo_quality > -1 AND photo_private = 0) AS review, " +
"SUM(photo_favorite = 1 AND photo_private = 0 AND photo_quality >= 0) AS favorites, " + "SUM(photo_favorite = 1 AND photo_private = 0 AND photo_quality > -1) AS favorites, " +
"SUM(photo_private = 1 AND photo_quality >= 0) AS private"). "SUM(photo_private = 1 AND photo_quality > -1) AS private").
Where("photos.id NOT IN (SELECT photo_id FROM files WHERE file_primary = 1 AND (file_missing = 1 OR file_error <> ''))"). Where("photos.id NOT IN (SELECT photo_id FROM files WHERE file_primary = 1 AND (file_missing = 1 OR file_error <> ''))").
Where("deleted_at IS NULL"). Where("deleted_at IS NULL").
Take(&result.Count) Take(&result.Count)
@ -460,7 +462,7 @@ func (c *Config) UserConfig() ClientConfig {
result.People, _ = query.People() result.People, _ = query.People()
c.Db(). c.Db().
Where("id IN (SELECT photos.camera_id FROM photos WHERE photos.photo_quality >= 0 OR photos.deleted_at IS NULL)"). Where("id IN (SELECT photos.camera_id FROM photos WHERE photos.photo_quality > -1 OR photos.deleted_at IS NULL)").
Where("deleted_at IS NULL"). Where("deleted_at IS NULL").
Limit(10000).Order("camera_slug"). Limit(10000).Order("camera_slug").
Find(&result.Cameras) Find(&result.Cameras)
@ -477,7 +479,7 @@ func (c *Config) UserConfig() ClientConfig {
c.Db(). c.Db().
Table("photos"). Table("photos").
Where("photo_year > 0 AND (photos.photo_quality >= 0 OR photos.deleted_at IS NULL)"). Where("photo_year > 0 AND (photos.photo_quality > -1 OR photos.deleted_at IS NULL)").
Order("photo_year DESC"). Order("photo_year DESC").
Pluck("DISTINCT photo_year", &result.Years) Pluck("DISTINCT photo_year", &result.Years)

View file

@ -29,9 +29,9 @@ import (
"github.com/photoprism/photoprism/internal/hub/places" "github.com/photoprism/photoprism/internal/hub/places"
"github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
var log = event.Log var log = event.Log
@ -39,37 +39,6 @@ var once sync.Once
var LowMem = false var LowMem = false
var TotalMem uint64 var TotalMem uint64
const MsgSponsor = "PhotoPrism® needs your support!"
const SignUpURL = "https://docs.photoprism.app/funding/"
const MsgSignUp = "Visit " + SignUpURL + " to learn more."
const MsgSponsorCommand = "Since running this command puts additional load on our infrastructure," +
" we unfortunately can only offer it to sponsors."
const ApiUri = "/api/v1" // REST API
const StaticUri = "/static" // Static Content
const DefaultAutoIndexDelay = int(5 * 60) // 5 Minutes
const DefaultAutoImportDelay = int(3 * 60) // 3 Minutes
const DefaultWakeupIntervalSeconds = int(15 * 60) // 15 Minutes
const DefaultWakeupInterval = time.Second * time.Duration(DefaultWakeupIntervalSeconds)
const MaxWakeupInterval = time.Hour * 24 // 1 Day
// Megabyte in bytes.
const Megabyte = 1000 * 1000
// Gigabyte in bytes.
const Gigabyte = Megabyte * 1000
// MinMem is the minimum amount of system memory required.
const MinMem = Gigabyte
// RecommendedMem is the recommended amount of system memory.
const RecommendedMem = 3 * Gigabyte
// serialName is the name of the unique storage serial.
const serialName = "serial"
// Config holds database, cache and all parameters of photoprism // Config holds database, cache and all parameters of photoprism
type Config struct { type Config struct {
once sync.Once once sync.Once
@ -125,16 +94,16 @@ func NewConfig(ctx *cli.Context) *Config {
// Initialize options from config file and CLI context. // Initialize options from config file and CLI context.
c := &Config{ c := &Config{
options: NewOptions(ctx), options: NewOptions(ctx),
token: rnd.Token(8), token: rnd.GenerateToken(8),
env: os.Getenv("DOCKER_ENV"), env: os.Getenv("DOCKER_ENV"),
} }
// Overwrite values with options.yml from config path. // Overwrite values with options.yml from config path.
if optionsYaml := c.OptionsYaml(); fs.FileExists(optionsYaml) { if optionsYaml := c.OptionsYaml(); fs.FileExists(optionsYaml) {
if err := c.options.Load(optionsYaml); err != nil { if err := c.options.Load(optionsYaml); err != nil {
log.Warnf("config: failed loading values from %s (%s)", sanitize.Log(optionsYaml), err) log.Warnf("config: failed loading values from %s (%s)", clean.Log(optionsYaml), err)
} else { } else {
log.Debugf("config: overriding config with values from %s", sanitize.Log(optionsYaml)) log.Debugf("config: overriding config with values from %s", clean.Log(optionsYaml))
} }
} }
@ -210,7 +179,7 @@ func (c *Config) Init() error {
} }
if cpuName := cpuid.CPU.BrandName; cpuName != "" { if cpuName := cpuid.CPU.BrandName; cpuName != "" {
log.Debugf("config: running on %s, %s memory detected", sanitize.Log(cpuid.CPU.BrandName), humanize.Bytes(TotalMem)) log.Debugf("config: running on %s, %s memory detected", clean.Log(cpuid.CPU.BrandName), humanize.Bytes(TotalMem))
} }
// Exit if less than 128 MB RAM was detected. // Exit if less than 128 MB RAM was detected.
@ -261,7 +230,7 @@ func (c *Config) readSerial() string {
if data, err := os.ReadFile(storageName); err == nil && len(data) == 16 { if data, err := os.ReadFile(storageName); err == nil && len(data) == 16 {
return string(data) return string(data)
} else { } else {
log.Tracef("config: could not read %s (%s)", sanitize.Log(storageName), err) log.Tracef("config: could not read %s (%s)", clean.Log(storageName), err)
} }
} }
@ -269,7 +238,7 @@ func (c *Config) readSerial() string {
if data, err := os.ReadFile(backupName); err == nil && len(data) == 16 { if data, err := os.ReadFile(backupName); err == nil && len(data) == 16 {
return string(data) return string(data)
} else { } else {
log.Tracef("config: could not read %s (%s)", sanitize.Log(backupName), err) log.Tracef("config: could not read %s (%s)", clean.Log(backupName), err)
} }
} }
@ -282,7 +251,7 @@ func (c *Config) initSerial() (err error) {
return nil return nil
} }
c.serial = rnd.PPID('z') c.serial = rnd.GenerateUID('z')
storageName := filepath.Join(c.StoragePath(), serialName) storageName := filepath.Join(c.StoragePath(), serialName)
backupName := filepath.Join(c.BackupPath(), serialName) backupName := filepath.Join(c.BackupPath(), serialName)

View file

@ -35,7 +35,7 @@ func (c *Config) InvalidDownloadToken(t string) bool {
// DownloadToken returns the DOWNLOAD api token (you can optionally use a static value for permanent caching). // DownloadToken returns the DOWNLOAD api token (you can optionally use a static value for permanent caching).
func (c *Config) DownloadToken() string { func (c *Config) DownloadToken() string {
if c.options.DownloadToken == "" { if c.options.DownloadToken == "" {
c.options.DownloadToken = rnd.Token(8) c.options.DownloadToken = rnd.GenerateToken(8)
} }
return c.options.DownloadToken return c.options.DownloadToken

View file

@ -0,0 +1,34 @@
package config
import "time"
const MsgSponsor = "PhotoPrism® needs your support!"
const SignUpURL = "https://docs.photoprism.app/funding/"
const MsgSignUp = "Visit " + SignUpURL + " to learn more."
const MsgSponsorCommand = "Since running this command puts additional load on our infrastructure," +
" we unfortunately can only offer it to sponsors."
const ApiUri = "/api/v1" // REST API
const StaticUri = "/static" // Static Content
const DefaultAutoIndexDelay = int(5 * 60) // 5 Minutes
const DefaultAutoImportDelay = int(3 * 60) // 3 Minutes
const DefaultWakeupIntervalSeconds = int(15 * 60) // 15 Minutes
const DefaultWakeupInterval = time.Second * time.Duration(DefaultWakeupIntervalSeconds)
const MaxWakeupInterval = time.Hour * 24 // 1 Day
// Megabyte in bytes.
const Megabyte = 1000 * 1000
// Gigabyte in bytes.
const Gigabyte = Megabyte * 1000
// MinMem is the minimum amount of system memory required.
const MinMem = Gigabyte
// RecommendedMem is the recommended amount of system memory.
const RecommendedMem = 3 * Gigabyte
// serialName is the name of the unique storage serial.
const serialName = "serial"

View file

@ -7,8 +7,8 @@ import (
"os/user" "os/user"
"path/filepath" "path/filepath"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// binPaths stores known executable paths. // binPaths stores known executable paths.
@ -48,9 +48,9 @@ func findExecutable(configBin, defaultBin string) (binPath string) {
func (c *Config) CreateDirectories() error { func (c *Config) CreateDirectories() error {
createError := func(path string, err error) (result error) { createError := func(path string, err error) (result error) {
if fs.FileExists(path) { if fs.FileExists(path) {
result = fmt.Errorf("directory path %s is a file, please check your configuration", sanitize.Log(path)) result = fmt.Errorf("directory path %s is a file, please check your configuration", clean.Log(path))
} else { } else {
result = fmt.Errorf("failed to create the directory %s, check configuration and permissions", sanitize.Log(path)) result = fmt.Errorf("failed to create the directory %s, check configuration and permissions", clean.Log(path))
} }
log.Debug(err) log.Debug(err)
@ -59,7 +59,7 @@ func (c *Config) CreateDirectories() error {
} }
notFoundError := func(name string) error { notFoundError := func(name string) error {
return fmt.Errorf("invalid %s path, check configuration and permissions", sanitize.Log(name)) return fmt.Errorf("invalid %s path, check configuration and permissions", clean.Log(name))
} }
if c.AssetsPath() == "" { if c.AssetsPath() == "" {
@ -154,11 +154,11 @@ func (c *Config) CreateDirectories() error {
if c.DarktableEnabled() { if c.DarktableEnabled() {
if cachePath, err := c.CreateDarktableCachePath(); err != nil { if cachePath, err := c.CreateDarktableCachePath(); err != nil {
return fmt.Errorf("could not create darktable cache path %s", sanitize.Log(cachePath)) return fmt.Errorf("could not create darktable cache path %s", clean.Log(cachePath))
} }
if configPath, err := c.CreateDarktableConfigPath(); err != nil { if configPath, err := c.CreateDarktableConfigPath(); err != nil {
return fmt.Errorf("could not create darktable cache path %s", sanitize.Log(configPath)) return fmt.Errorf("could not create darktable cache path %s", clean.Log(configPath))
} }
} }

View file

@ -86,7 +86,7 @@ func TestConfig_CreateDirectories(t *testing.T) {
c := &Config{ c := &Config{
options: NewTestOptions("config"), options: NewTestOptions("config"),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
if err := c.CreateDirectories(); err != nil { if err := c.CreateDirectories(); err != nil {
@ -106,7 +106,7 @@ func TestConfig_CreateDirectories2(t *testing.T) {
defer testConfigMutex.Unlock() defer testConfigMutex.Unlock()
c := &Config{ c := &Config{
options: NewTestOptions(), options: NewTestOptions(),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
c.options.AssetsPath = "" c.options.AssetsPath = ""
@ -130,7 +130,7 @@ func TestConfig_CreateDirectories2(t *testing.T) {
defer testConfigMutex.Unlock() defer testConfigMutex.Unlock()
c := &Config{ c := &Config{
options: NewTestOptions(), options: NewTestOptions(),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
c.options.StoragePath = "/-*&^%$#@!`~" c.options.StoragePath = "/-*&^%$#@!`~"
@ -147,7 +147,7 @@ func TestConfig_CreateDirectories2(t *testing.T) {
defer testConfigMutex.Unlock() defer testConfigMutex.Unlock()
c := &Config{ c := &Config{
options: NewTestOptions(), options: NewTestOptions(),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
c.options.OriginalsPath = "" c.options.OriginalsPath = ""
@ -172,7 +172,7 @@ func TestConfig_CreateDirectories2(t *testing.T) {
defer testConfigMutex.Unlock() defer testConfigMutex.Unlock()
c := &Config{ c := &Config{
options: NewTestOptions(), options: NewTestOptions(),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
c.options.ImportPath = "" c.options.ImportPath = ""
@ -197,7 +197,7 @@ func TestConfig_CreateDirectories2(t *testing.T) {
defer testConfigMutex.Unlock() defer testConfigMutex.Unlock()
c := &Config{ c := &Config{
options: NewTestOptions(), options: NewTestOptions(),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
c.options.SidecarPath = "/-*&^%$#@!`~" c.options.SidecarPath = "/-*&^%$#@!`~"
@ -214,7 +214,7 @@ func TestConfig_CreateDirectories2(t *testing.T) {
defer testConfigMutex.Unlock() defer testConfigMutex.Unlock()
c := &Config{ c := &Config{
options: NewTestOptions(), options: NewTestOptions(),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
c.options.CachePath = "/-*&^%$#@!`~" c.options.CachePath = "/-*&^%$#@!`~"
@ -231,7 +231,7 @@ func TestConfig_CreateDirectories2(t *testing.T) {
defer testConfigMutex.Unlock() defer testConfigMutex.Unlock()
c := &Config{ c := &Config{
options: NewTestOptions(), options: NewTestOptions(),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
c.options.ConfigPath = "/-*&^%$#@!`~" c.options.ConfigPath = "/-*&^%$#@!`~"
@ -248,7 +248,7 @@ func TestConfig_CreateDirectories2(t *testing.T) {
defer testConfigMutex.Unlock() defer testConfigMutex.Unlock()
c := &Config{ c := &Config{
options: NewTestOptions(), options: NewTestOptions(),
token: rnd.Token(8), token: rnd.GenerateToken(8),
} }
c.options.TempPath = "/-*&^%$#@!`~" c.options.TempPath = "/-*&^%$#@!`~"

View file

@ -4,10 +4,30 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
// DefaultTheme returns the default user interface theme name.
func (c *Config) DefaultTheme() string {
if c.options.DefaultTheme == "" || !c.Sponsor() {
return "default"
}
return c.options.DefaultTheme
}
// DefaultLocale returns the default user interface language locale name.
func (c *Config) DefaultLocale() string {
if c.options.DefaultLocale == "" {
return i18n.Default.Locale()
}
return c.options.DefaultLocale
}
// AppIcon returns the app icon when installed on a device. // AppIcon returns the app icon when installed on a device.
func (c *Config) AppIcon() string { func (c *Config) AppIcon() string {
defaultIcon := "logo" defaultIcon := "logo"

View file

@ -7,6 +7,31 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestConfig_DefaultTheme(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, "default", c.DefaultTheme())
c.options.Sponsor = false
c.options.DefaultTheme = "grayscale"
assert.Equal(t, "default", c.DefaultTheme())
c.options.Sponsor = true
assert.Equal(t, "grayscale", c.DefaultTheme())
c.options.DefaultTheme = ""
assert.Equal(t, "default", c.DefaultTheme())
c.options.Sponsor = false
assert.Equal(t, "default", c.DefaultTheme())
}
func TestConfig_DefaultLocale(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, "en", c.DefaultLocale())
c.options.DefaultLocale = "de"
assert.Equal(t, "de", c.DefaultLocale())
c.options.DefaultLocale = ""
assert.Equal(t, "en", c.DefaultLocale())
}
func TestConfig_AppIcon(t *testing.T) { func TestConfig_AppIcon(t *testing.T) {
c := NewConfig(CliTestContext()) c := NewConfig(CliTestContext())

View file

@ -1,23 +0,0 @@
package config
import (
"github.com/photoprism/photoprism/internal/i18n"
)
// DefaultTheme returns the default user interface theme name.
func (c *Config) DefaultTheme() string {
if c.options.DefaultTheme == "" || !c.Sponsor() {
return "default"
}
return c.options.DefaultTheme
}
// DefaultLocale returns the default user interface language locale name.
func (c *Config) DefaultLocale() string {
if c.options.DefaultLocale == "" {
return i18n.Default.Locale()
}
return c.options.DefaultLocale
}

View file

@ -1,32 +0,0 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestConfig_DefaultTheme(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, "default", c.DefaultTheme())
c.options.Sponsor = false
c.options.DefaultTheme = "grayscale"
assert.Equal(t, "default", c.DefaultTheme())
c.options.Sponsor = true
assert.Equal(t, "grayscale", c.DefaultTheme())
c.options.DefaultTheme = ""
assert.Equal(t, "default", c.DefaultTheme())
c.options.Sponsor = false
assert.Equal(t, "default", c.DefaultTheme())
}
func TestConfig_DefaultLocale(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, "en", c.DefaultLocale())
c.options.DefaultLocale = "de"
assert.Equal(t, "de", c.DefaultLocale())
c.options.DefaultLocale = ""
assert.Equal(t, "en", c.DefaultLocale())
}

View file

@ -1,18 +0,0 @@
package config
import (
"errors"
)
// Define photoprism specific errors
var (
ErrReadOnly = errors.New("not available in read-only mode")
ErrUnauthorized = errors.New("please log in and try again")
ErrUploadNSFW = errors.New("upload might be offensive")
)
func LogError(err error) {
if err != nil {
log.Errorf("config: %s", err.Error())
}
}

View file

@ -10,8 +10,8 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@ -150,9 +150,9 @@ func NewOptions(ctx *cli.Context) *Options {
if defaultsYaml := ctx.GlobalString("defaults-yaml"); defaultsYaml == "" { if defaultsYaml := ctx.GlobalString("defaults-yaml"); defaultsYaml == "" {
log.Tracef("config: defaults yaml file not specified") log.Tracef("config: defaults yaml file not specified")
} else if c.DefaultsYaml = fs.Abs(defaultsYaml); !fs.FileExists(c.DefaultsYaml) { } else if c.DefaultsYaml = fs.Abs(defaultsYaml); !fs.FileExists(c.DefaultsYaml) {
log.Tracef("config: defaults file %s does not exist", sanitize.Log(c.DefaultsYaml)) log.Tracef("config: defaults file %s does not exist", clean.Log(c.DefaultsYaml))
} else if err := c.Load(c.DefaultsYaml); err != nil { } else if err := c.Load(c.DefaultsYaml); err != nil {
log.Warnf("config: failed loading defaults from %s (%s)", sanitize.Log(c.DefaultsYaml), err) log.Warnf("config: failed loading defaults from %s (%s)", clean.Log(c.DefaultsYaml), err)
} }
if err := c.SetContext(ctx); err != nil { if err := c.SetContext(ctx); err != nil {

View file

@ -0,0 +1,15 @@
package config
import (
"errors"
)
var (
ErrReadOnly = errors.New("not available in read-only mode")
)
func LogError(err error) {
if err != nil {
log.Errorf("config: %s", err.Error())
}
}

View file

@ -8,89 +8,10 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// UISettings represents user interface settings.
type UISettings struct {
Scrollbar bool `json:"scrollbar" yaml:"Scrollbar"`
Zoom bool `json:"zoom" yaml:"Zoom"`
Theme string `json:"theme" yaml:"Theme"`
Language string `json:"language" yaml:"Language"`
}
// SearchSettings represents search UI preferences.
type SearchSettings struct {
BatchSize int `json:"batchSize" yaml:"BatchSize"`
}
// TemplateSettings represents template settings for the UI and messaging.
type TemplateSettings struct {
Default string `json:"default" yaml:"Default"`
}
// MapsSettings represents maps settings (for places).
type MapsSettings struct {
Animate int `json:"animate" yaml:"Animate"`
Style string `json:"style" yaml:"Style"`
}
// FeatureSettings represents feature flags, mainly for the Web UI.
type FeatureSettings struct {
Upload bool `json:"upload" yaml:"Upload"`
Download bool `json:"download" yaml:"Download"`
Private bool `json:"private" yaml:"Private"`
Review bool `json:"review" yaml:"Review"`
Files bool `json:"files" yaml:"Files"`
Videos bool `json:"videos" yaml:"Videos"`
Folders bool `json:"folders" yaml:"Folders"`
Albums bool `json:"albums" yaml:"Albums"`
Moments bool `json:"moments" yaml:"Moments"`
Estimates bool `json:"estimates" yaml:"Estimates"`
People bool `json:"people" yaml:"People"`
Labels bool `json:"labels" yaml:"Labels"`
Places bool `json:"places" yaml:"Places"`
Edit bool `json:"edit" yaml:"Edit"`
Archive bool `json:"archive" yaml:"Archive"`
Delete bool `json:"delete" yaml:"Delete"`
Share bool `json:"share" yaml:"Share"`
Library bool `json:"library" yaml:"Library"`
Import bool `json:"import" yaml:"Import"`
Logs bool `json:"logs" yaml:"Logs"`
}
// ImportSettings represents import settings.
type ImportSettings struct {
Path string `json:"path" yaml:"Path"`
Move bool `json:"move" yaml:"Move"`
}
// IndexSettings represents indexing settings.
type IndexSettings struct {
Path string `json:"path" yaml:"Path"`
Convert bool `json:"convert" yaml:"Convert"`
Rescan bool `json:"rescan" yaml:"Rescan"`
}
// StackSettings represents settings for files that belong to the same photo.
type StackSettings struct {
UUID bool `json:"uuid" yaml:"UUID"`
Meta bool `json:"meta" yaml:"Meta"`
Name bool `json:"name" yaml:"Name"`
}
// ShareSettings represents content sharing settings.
type ShareSettings struct {
Title string `json:"title" yaml:"Title"`
}
// DownloadSettings represents content download settings.
type DownloadSettings struct {
Name entity.DownloadName `json:"name" yaml:"Name"`
Raw bool `json:"raw" yaml:"Raw"`
}
// Settings represents user settings for Web UI, indexing, and import. // Settings represents user settings for Web UI, indexing, and import.
type Settings struct { type Settings struct {
UI UISettings `json:"ui" yaml:"UI"` UI UISettings `json:"ui" yaml:"UI"`
@ -159,9 +80,7 @@ func NewSettings(c *Config) *Settings {
Share: ShareSettings{ Share: ShareSettings{
Title: "", Title: "",
}, },
Download: DownloadSettings{ Download: NewDownloadSettings(),
Name: entity.DownloadNameDefault,
},
Templates: TemplateSettings{ Templates: TemplateSettings{
Default: "index.tmpl", Default: "index.tmpl",
}, },
@ -191,7 +110,7 @@ func (s Settings) StackMeta() bool {
// Load user settings from file. // Load user settings from file.
func (s *Settings) Load(fileName string) error { func (s *Settings) Load(fileName string) error {
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
return fmt.Errorf("settings file not found: %s", sanitize.Log(fileName)) return fmt.Errorf("settings file not found: %s", clean.Log(fileName))
} }
yamlConfig, err := os.ReadFile(fileName) yamlConfig, err := os.ReadFile(fileName)

View file

@ -0,0 +1,23 @@
package config
import "github.com/photoprism/photoprism/internal/entity"
// DownloadSettings represents content download settings.
type DownloadSettings struct {
Name entity.DownloadName `json:"name" yaml:"Name"`
Disabled bool `json:"disabled" yaml:"Disabled"`
Originals bool `json:"originals" yaml:"Originals"`
MediaRaw bool `json:"mediaRaw" yaml:"MediaRaw"`
MediaSidecar bool `json:"mediaSidecar" yaml:"MediaSidecar"`
}
// NewDownloadSettings creates download settings with defaults.
func NewDownloadSettings() DownloadSettings {
return DownloadSettings{
Name: entity.DownloadNameDefault,
Disabled: false,
Originals: true,
MediaRaw: false,
MediaSidecar: false,
}
}

View file

@ -0,0 +1,25 @@
package config
// FeatureSettings represents feature flags, mainly for the Web UI.
type FeatureSettings struct {
Upload bool `json:"upload" yaml:"Upload"`
Download bool `json:"download" yaml:"Download"`
Private bool `json:"private" yaml:"Private"`
Review bool `json:"review" yaml:"Review"`
Files bool `json:"files" yaml:"Files"`
Videos bool `json:"videos" yaml:"Videos"`
Folders bool `json:"folders" yaml:"Folders"`
Albums bool `json:"albums" yaml:"Albums"`
Moments bool `json:"moments" yaml:"Moments"`
Estimates bool `json:"estimates" yaml:"Estimates"`
People bool `json:"people" yaml:"People"`
Labels bool `json:"labels" yaml:"Labels"`
Places bool `json:"places" yaml:"Places"`
Edit bool `json:"edit" yaml:"Edit"`
Archive bool `json:"archive" yaml:"Archive"`
Delete bool `json:"delete" yaml:"Delete"`
Share bool `json:"share" yaml:"Share"`
Library bool `json:"library" yaml:"Library"`
Import bool `json:"import" yaml:"Import"`
Logs bool `json:"logs" yaml:"Logs"`
}

View file

@ -0,0 +1,7 @@
package config
// ImportSettings represents import settings.
type ImportSettings struct {
Path string `json:"path" yaml:"Path"`
Move bool `json:"move" yaml:"Move"`
}

View file

@ -0,0 +1,8 @@
package config
// IndexSettings represents indexing settings.
type IndexSettings struct {
Path string `json:"path" yaml:"Path"`
Convert bool `json:"convert" yaml:"Convert"`
Rescan bool `json:"rescan" yaml:"Rescan"`
}

View file

@ -0,0 +1,7 @@
package config
// MapsSettings represents maps settings (for places).
type MapsSettings struct {
Animate int `json:"animate" yaml:"Animate"`
Style string `json:"style" yaml:"Style"`
}

Some files were not shown because too many files have changed in this diff Show more