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:
parent
0a9f6a72bc
commit
92e6c4fe1e
|
@ -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 \
|
||||||
|
|
|
@ -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 && \
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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 "";
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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", () => {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
34
internal/config/config_const.go
Normal file
34
internal/config/config_const.go
Normal 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"
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = "/-*&^%$#@!`~"
|
|
@ -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"
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {
|
15
internal/config/log_error.go
Normal file
15
internal/config/log_error.go
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
23
internal/config/settings_download.go
Normal file
23
internal/config/settings_download.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
25
internal/config/settings_feature.go
Normal file
25
internal/config/settings_feature.go
Normal 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"`
|
||||||
|
}
|
7
internal/config/settings_import.go
Normal file
7
internal/config/settings_import.go
Normal 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"`
|
||||||
|
}
|
8
internal/config/settings_index.go
Normal file
8
internal/config/settings_index.go
Normal 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"`
|
||||||
|
}
|
7
internal/config/settings_maps.go
Normal file
7
internal/config/settings_maps.go
Normal 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
Loading…
Reference in a new issue