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

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

View file

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

View file

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

View file

@ -145,25 +145,100 @@ export default class Util {
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) {
if (!value || typeof value !== "string") {
return "";
}
switch (value) {
case "raw":
return "Unprocessed Sensor Data (RAW)";
case "mov":
case "qt":
return "Apple QuickTime (MOV)";
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 "vvc":
return "Versatile Video Coding (VVC) / H.266";
case "av01":
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":
return "Moving Picture Experts Group (MPEG)";
case "mjpg":
return "Motion JPEG (M-JPEG)";
case "heif":
case "heic":
return "High Efficiency Image File Format (HEIF)";
case "heic":
return "High Efficiency Image Container (HEIC)";
case "1":
return "Uncompressed";
case "2":

View file

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

View file

@ -30,7 +30,7 @@ import Util from "common/util";
import { config } from "app/session";
import { $gettext } from "common/vm";
import download from "common/download";
import { MediaAnimated } from "./photo";
import { MediaImage } from "./photo";
export class File extends RestModel {
getDefaults() {
@ -38,6 +38,9 @@ export class File extends RestModel {
UID: "",
PhotoUID: "",
InstanceID: "",
MediaID: "",
MediaUTC: 0,
TakenAt: "",
Root: "/",
Name: "",
OriginalName: "",
@ -184,18 +187,54 @@ export class File extends RestModel {
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() {
if (this.Type === MediaAnimated) {
return $gettext("Animation");
}
let info = [];
if (this.Video) {
return $gettext("Video");
if (
this.MediaType &&
this.Frames &&
this.MediaType === MediaImage &&
this.Frames &&
this.Frames > 0
) {
info.push($gettext("Animated"));
} 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() {

View file

@ -43,6 +43,7 @@ export const FormatGif = "gif";
export const FormatJpeg = "jpg";
export const MediaImage = "image";
export const MediaAnimated = "animated";
export const MediaSidecar = "sidecar";
export const MediaVideo = "video";
export const MediaLive = "live";
export const MediaRaw = "raw";
@ -535,9 +536,10 @@ export class Photo extends RestModel {
}
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;
}
@ -560,23 +562,30 @@ export class Photo extends RestModel {
return;
}
// Skip sidecar files.
if (file.Sidecar) {
// Originals only?
if (s.download.originals && file.Root.length > 1) {
// 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;
}
// Skip RAW images.
if (!settings.download.raw && file.FileType === MediaRaw) {
if (config.debug) console.log("download: skipped raw", file);
// Skip metadata sidecar files?
if (!s.download.mediaSidecar && (file.MediaType === MediaSidecar || file.Sidecar)) {
// Don't download broken files and sidecars.
if (config.debug) console.log(`download: skipped sidecar file ${file.Name}`);
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
if (this.Type === MediaVideo && !file.Video) {
if (config.debug) console.log("download: skipped image", file);
if (this.Type === MediaVideo && !(file.MediaType === MediaVideo || file.Video)) {
if (config.debug) console.log(`download: skipped video sidecar ${file.Name}`);
return;
}

View file

@ -236,7 +236,21 @@ describe("model/file", () => {
UpdatedAt: "2012-07-08T14:45:39Z",
};
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", () => {

View file

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

View file

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

View file

@ -34,7 +34,7 @@ import (
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
)
var log = event.Log
@ -60,7 +60,7 @@ func UpdateClientConfig() {
func Abort(c *gin.Context, code int, id i18n.Message, params ...interface{}) {
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)
}
@ -70,7 +70,7 @@ func Error(c *gin.Context, code int, err error, id i18n.Message, params ...inter
if err != nil {
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)

View file

@ -14,7 +14,7 @@ import (
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"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.
@ -41,7 +41,7 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
return
}
log.Infof("photos: archiving %s", sanitize.Log(f.String()))
log.Infof("photos: archiving %s", clean.Log(f.String()))
if service.Config().BackupYaml() {
// Fetch selection from index.
@ -105,7 +105,7 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
return
}
log.Infof("photos: restoring %s", sanitize.Log(f.String()))
log.Infof("photos: restoring %s", clean.Log(f.String()))
if service.Config().BackupYaml() {
// Fetch selection from index.
@ -168,7 +168,7 @@ func BatchPhotosApprove(router *gin.RouterGroup) {
return
}
log.Infof("photos: approving %s", sanitize.Log(f.String()))
log.Infof("photos: approving %s", clean.Log(f.String()))
// Fetch selection from index.
photos, err := query.SelectedPhotos(f)
@ -221,7 +221,7 @@ func BatchAlbumsDelete(router *gin.RouterGroup) {
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.PhotoAlbum{})
@ -258,7 +258,7 @@ func BatchPhotosPrivate(router *gin.RouterGroup) {
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",
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
}
log.Infof("labels: deleting %s", sanitize.Log(f.String()))
log.Infof("labels: deleting %s", clean.Log(f.String()))
var labels entity.Labels
@ -364,7 +364,7 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
return
}
log.Infof("photos: deleting %s", sanitize.Log(f.String()))
log.Infof("photos: deleting %s", clean.Log(f.String()))
// Fetch selection from index.
photos, err := query.SelectedPhotos(f)

View file

@ -5,7 +5,7 @@ import (
"os"
"path/filepath"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
"gopkg.in/yaml.v2"
@ -87,13 +87,13 @@ func SaveConfigOptions(router *gin.RouterGroup) {
yamlData, err := os.ReadFile(fileName)
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)
return
}
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)
return
}
@ -122,14 +122,14 @@ func SaveConfigOptions(router *gin.RouterGroup) {
// Write YAML data to file.
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)
return
}
// Reload options.
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)
return
}

View file

@ -5,7 +5,7 @@ import (
"path/filepath"
"time"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism"
@ -38,13 +38,13 @@ func AlbumCover(router *gin.RouterGroup) {
start := time.Now()
conf := service.Config()
thumbName := thumb.Name(sanitize.Token(c.Param("size")))
uid := sanitize.IdString(c.Param("uid"))
thumbName := thumb.Name(clean.Token(c.Param("size")))
uid := clean.IdString(c.Param("uid"))
size, ok := thumb.Sizes[thumbName]
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)
return
}
@ -85,11 +85,11 @@ func AlbumCover(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.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)
// 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))
return
}
@ -150,13 +150,13 @@ func LabelCover(router *gin.RouterGroup) {
start := time.Now()
conf := service.Config()
thumbName := thumb.Name(sanitize.Token(c.Param("size")))
uid := sanitize.IdString(c.Param("uid"))
thumbName := thumb.Name(clean.Token(c.Param("size")))
uid := clean.IdString(c.Param("uid"))
size, ok := thumb.Sizes[thumbName]
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)
return
}
@ -197,7 +197,7 @@ func LabelCover(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.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)
// Set missing flag so that the file doesn't show up in search results anymore.

View file

@ -14,8 +14,8 @@ import (
"github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
)
// DownloadAlbum streams the album contents as zip archive.
@ -36,7 +36,7 @@ func DownloadAlbum(router *gin.RouterGroup) {
}
start := time.Now()
a, err := query.AlbumByUID(sanitize.IdString(c.Param("uid")))
a, err := query.AlbumByUID(clean.IdString(c.Param("uid")))
if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@ -57,26 +57,19 @@ func DownloadAlbum(router *gin.RouterGroup) {
zipWriter := zip.NewWriter(c.Writer)
defer zipWriter.Close()
skipRaw := !conf.Settings().Download.Raw
var aliases = make(map[string]int)
for _, file := range files {
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
} 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
}
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))
log.Debugf("download: skipped sidecar %s", clean.Log(file.FileName))
continue
}
@ -92,17 +85,17 @@ func DownloadAlbum(router *gin.RouterGroup) {
if fs.FileExists(fileName) {
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)
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 {
log.Warnf("download: album file %s is missing", sanitize.Log(file.FileName))
log.Warnf("download: album file %s is missing", clean.Log(file.FileName))
}
}
log.Infof("download: created %s [%s]", sanitize.Log(zipFileName), time.Since(start))
log.Infof("download: created %s [%s]", clean.Log(zipFileName), time.Since(start))
})
}

View file

@ -10,8 +10,8 @@ import (
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
)
// TODO: GET /api/v1/dl/file/:hash
@ -45,7 +45,7 @@ func GetDownload(router *gin.RouterGroup) {
return
}
fileHash := sanitize.Token(c.Param("hash"))
fileHash := clean.Token(c.Param("hash"))
f, err := query.FileByHash(fileHash)
@ -57,7 +57,7 @@ func GetDownload(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.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)
// Set missing flag so that the file doesn't show up in search results anymore.

View file

@ -20,9 +20,9 @@ import (
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/sanitize"
)
// CreateZip creates a zip file archive for download.
@ -57,8 +57,17 @@ func CreateZip(router *gin.RouterGroup) {
return
}
// Select files to be downloaded.
files, err := query.SelectedFiles(f, query.FileSelectionAll())
// Configure file selection based on user settings.
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 {
Error(c, http.StatusBadRequest, err, i18n.ErrZipFailed)
@ -68,53 +77,36 @@ func CreateZip(router *gin.RouterGroup) {
return
}
// Configure file names.
dlName := DownloadName(c)
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)
zipFileName := path.Join(zipPath, zipBaseName)
// Create temp directory.
if err := os.MkdirAll(zipPath, 0700); err != nil {
Error(c, http.StatusInternalServerError, err, i18n.ErrZipFailed)
return
}
newZipFile, err := os.Create(zipFileName)
if err != nil {
// Create new zip file.
var newZipFile *os.File
if newZipFile, err = os.Create(zipFileName); err != nil {
Error(c, http.StatusInternalServerError, err, i18n.ErrZipFailed)
return
} else {
defer newZipFile.Close()
}
defer newZipFile.Close()
// Create zip writer.
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
dlName := DownloadName(c)
skipRaw := !conf.Settings().Download.Raw
var aliases = make(map[string]int)
// Add files to zip.
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)
alias := file.DownloadName(dlName, 0)
key := strings.ToLower(alias)
@ -127,21 +119,21 @@ func CreateZip(router *gin.RouterGroup) {
if fs.FileExists(fileName) {
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)
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 {
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))
}
}
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})
})
@ -158,12 +150,12 @@ func DownloadZip(router *gin.RouterGroup) {
}
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")
zipFileName := path.Join(zipPath, zipBaseName)
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)
return
}
@ -171,7 +163,7 @@ func DownloadZip(router *gin.RouterGroup) {
c.FileAttachment(zipFileName, zipBaseName)
if err := os.Remove(zipFileName); err != nil {
log.Errorf("download: failed removing %s (%s)", sanitize.Log(zipFileName), err.Error())
log.Errorf("download: failed removing %s (%s)", clean.Log(zipFileName), err.Error())
}
})
}

View file

@ -31,7 +31,7 @@ func GetErrors(router *gin.RouterGroup) {
// Find and return matching logs.
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
} else {
AddCountHeader(c, len(resp))

View file

@ -3,7 +3,7 @@ package api
import (
"net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
@ -58,7 +58,7 @@ func UpdateFace(router *gin.RouterGroup) {
return
}
faceId := sanitize.Token(c.Param("id"))
faceId := clean.Token(c.Param("id"))
m := entity.FindFace(faceId)
if m == nil {
@ -70,7 +70,7 @@ func UpdateFace(router *gin.RouterGroup) {
if !f.FaceHidden && f.FaceHidden == m.FaceHidden {
// Do nothing.
} 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
}
@ -78,7 +78,7 @@ func UpdateFace(router *gin.RouterGroup) {
if f.SubjUID == "" {
// Do nothing.
} 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
}

View file

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

View file

@ -4,7 +4,7 @@ import (
"net/http"
"path/filepath"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl"
@ -36,8 +36,8 @@ func DeleteFile(router *gin.RouterGroup) {
return
}
photoUID := sanitize.IdString(c.Param("uid"))
fileUID := sanitize.IdString(c.Param("file_uid"))
photoUID := clean.IdString(c.Param("uid"))
fileUID := clean.IdString(c.Param("file_uid"))
file, err := query.FileByUID(fileUID)
@ -59,17 +59,17 @@ func DeleteFile(router *gin.RouterGroup) {
mediaFile, err := photoprism.NewMediaFile(fileName)
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)
return
}
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 {
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)
return
}

View file

@ -5,7 +5,7 @@ import (
"path/filepath"
"time"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism"
@ -37,7 +37,7 @@ func FolderCover(router *gin.RouterGroup) {
start := time.Now()
conf := service.Config()
uid := c.Param("uid")
thumbName := thumb.Name(sanitize.Token(c.Param("size")))
thumbName := thumb.Name(clean.Token(c.Param("size")))
download := c.Query("download") != ""
size, ok := thumb.Sizes[thumbName]
@ -98,7 +98,7 @@ func FolderCover(router *gin.RouterGroup) {
c.Data(http.StatusOK, "image/svg+xml", folderIconSvg)
// 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))
return
}

View file

@ -18,8 +18,8 @@ import (
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"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.
@ -53,7 +53,7 @@ func StartImport(router *gin.RouterGroup) {
subPath := ""
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)
path = filepath.Join(path, subPath)
} else if f.Path != "" {
@ -70,15 +70,15 @@ func StartImport(router *gin.RouterGroup) {
var opt photoprism.ImportOptions
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)
} else {
event.InfoMsg(i18n.MsgCopyingFilesFrom, sanitize.Log(filepath.Base(path)))
event.InfoMsg(i18n.MsgCopyingFilesFrom, clean.Log(filepath.Base(path)))
opt = photoprism.ImportOptionsCopy(path)
}
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
}
@ -86,9 +86,9 @@ func StartImport(router *gin.RouterGroup) {
if subPath != "" && path != conf.ImportPath() && fs.IsEmpty(path) {
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 {
log.Infof("import: deleted empty folder %s", sanitize.Log(path))
log.Infof("import: deleted empty folder %s", clean.Log(path))
}
}

View file

@ -13,7 +13,7 @@ import (
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"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)
if len(indOpt.Path) > 1 {
event.InfoMsg(i18n.MsgIndexingFiles, sanitize.Log(indOpt.Path))
event.InfoMsg(i18n.MsgIndexingFiles, clean.Log(indOpt.Path))
} else {
event.InfoMsg(i18n.MsgIndexingOriginals)
}
@ -70,7 +70,7 @@ func StartIndexing(router *gin.RouterGroup) {
}
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
} else if len(files) > 0 || len(photos) > 0 {
event.InfoMsg(i18n.MsgRemovedFilesAndPhotos, len(files), len(photos))

View file

@ -3,7 +3,7 @@ package api
import (
"net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
@ -35,7 +35,7 @@ func UpdateLabel(router *gin.RouterGroup) {
return
}
id := sanitize.IdString(c.Param("uid"))
id := clean.IdString(c.Param("uid"))
m, err := query.LabelByUID(id)
if err != nil {
@ -69,16 +69,16 @@ func LikeLabel(router *gin.RouterGroup) {
return
}
id := sanitize.IdString(c.Param("uid"))
id := clean.IdString(c.Param("uid"))
label, err := query.LabelByUID(id)
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
}
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
}
@ -109,16 +109,16 @@ func DislikeLabel(router *gin.RouterGroup) {
return
}
id := sanitize.IdString(c.Param("uid"))
id := clean.IdString(c.Param("uid"))
label, err := query.LabelByUID(id)
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
}
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
}

View file

@ -13,7 +13,7 @@ import (
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt"
)
@ -33,7 +33,7 @@ func UpdateLink(c *gin.Context) {
return
}
link := entity.FindLink(sanitize.Token(c.Param("link")))
link := entity.FindLink(clean.Token(c.Param("link")))
link.SetSlug(f.ShareSlug)
link.MaxViews = f.MaxViews
@ -45,13 +45,13 @@ func UpdateLink(c *gin.Context) {
if f.Password != "" {
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
}
}
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
}
@ -73,10 +73,10 @@ func DeleteLink(c *gin.Context) {
return
}
link := entity.FindLink(sanitize.Token(c.Param("link")))
link := entity.FindLink(clean.Token(c.Param("link")))
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
}
@ -105,7 +105,7 @@ func CreateLink(c *gin.Context) {
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.MaxViews = f.MaxViews
@ -113,13 +113,13 @@ func CreateLink(c *gin.Context) {
if f.Password != "" {
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
}
}
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
}
@ -135,7 +135,7 @@ func CreateLink(c *gin.Context) {
// POST /api/v1/albums/:uid/links
func CreateAlbumLink(router *gin.RouterGroup) {
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)
return
}
@ -161,7 +161,7 @@ func DeleteAlbumLink(router *gin.RouterGroup) {
// GET /api/v1/albums/:uid/links
func GetAlbumLinks(router *gin.RouterGroup) {
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 {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@ -175,7 +175,7 @@ func GetAlbumLinks(router *gin.RouterGroup) {
// POST /api/v1/photos/:uid/links
func CreatePhotoLink(router *gin.RouterGroup) {
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)
return
}
@ -201,7 +201,7 @@ func DeletePhotoLink(router *gin.RouterGroup) {
// GET /api/v1/photos/:uid/links
func GetPhotoLinks(router *gin.RouterGroup) {
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 {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@ -215,7 +215,7 @@ func GetPhotoLinks(router *gin.RouterGroup) {
// POST /api/v1/labels/:uid/links
func CreateLabelLink(router *gin.RouterGroup) {
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)
return
}
@ -241,7 +241,7 @@ func DeleteLabelLink(router *gin.RouterGroup) {
// GET /api/v1/labels/:uid/links
func GetLabelLinks(router *gin.RouterGroup) {
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 {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)

View file

@ -23,7 +23,7 @@ func GetMomentsTime(router *gin.RouterGroup) {
result, err := query.MomentsTime(1)
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
}

View file

@ -15,8 +15,8 @@ import (
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
)
// SavePhotoAsYaml saves photo data as YAML file.
@ -33,7 +33,7 @@ func SavePhotoAsYaml(p entity.Photo) {
if err := p.SaveAsYaml(fileName); err != nil {
log.Errorf("photo: %s (update yaml)", err)
} 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
}
p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid")))
p, err := query.PhotoPreloadByUID(clean.IdString(c.Param("uid")))
if err != nil {
AbortEntityNotFound(c)
@ -74,7 +74,7 @@ func UpdatePhoto(router *gin.RouterGroup) {
return
}
uid := sanitize.IdString(c.Param("uid"))
uid := clean.IdString(c.Param("uid"))
m, err := query.PhotoByUID(uid)
if err != nil {
@ -136,7 +136,7 @@ func GetPhotoDownload(router *gin.RouterGroup) {
return
}
f, err := query.FileByPhotoUID(sanitize.IdString(c.Param("uid")))
f, err := query.FileByPhotoUID(clean.IdString(c.Param("uid")))
if err != nil {
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
@ -146,7 +146,7 @@ func GetPhotoDownload(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.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)
// 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
}
p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid")))
p, err := query.PhotoPreloadByUID(clean.IdString(c.Param("uid")))
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
@ -187,7 +187,7 @@ func GetPhotoYaml(router *gin.RouterGroup) {
}
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)
@ -207,7 +207,7 @@ func ApprovePhoto(router *gin.RouterGroup) {
return
}
id := sanitize.IdString(c.Param("uid"))
id := clean.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id)
if err != nil {
@ -242,7 +242,7 @@ func LikePhoto(router *gin.RouterGroup) {
return
}
id := sanitize.IdString(c.Param("uid"))
id := clean.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id)
if err != nil {
@ -277,7 +277,7 @@ func DislikePhoto(router *gin.RouterGroup) {
return
}
id := sanitize.IdString(c.Param("uid"))
id := clean.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id)
if err != nil {
@ -313,8 +313,8 @@ func PhotoPrimary(router *gin.RouterGroup) {
return
}
uid := sanitize.IdString(c.Param("uid"))
fileUID := sanitize.IdString(c.Param("file_uid"))
uid := clean.IdString(c.Param("uid"))
fileUID := clean.IdString(c.Param("file_uid"))
err := query.SetPhotoPrimary(uid, fileUID)
if err != nil {

View file

@ -4,7 +4,7 @@ import (
"net/http"
"strconv"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl"
@ -29,7 +29,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
return
}
m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid")))
m, err := query.PhotoByUID(clean.IdString(c.Param("uid")))
if err != nil {
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 {
AbortEntityNotFound(c)
@ -78,7 +78,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
}
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
}
@ -104,24 +104,24 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
return
}
m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid")))
m, err := query.PhotoByUID(clean.IdString(c.Param("uid")))
if err != nil {
AbortEntityNotFound(c)
return
}
labelId, err := strconv.Atoi(sanitize.Token(c.Param("id")))
labelId, err := strconv.Atoi(clean.Token(c.Param("id")))
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
}
label, err := query.PhotoLabel(m.ID, uint(labelId))
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
}
@ -132,7 +132,7 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
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 {
AbortEntityNotFound(c)
@ -142,11 +142,11 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
logError("label", p.RemoveKeyword(label.Label.LabelName))
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
}
PublishPhotoEvent(EntityUpdated, sanitize.IdString(c.Param("uid")), c)
PublishPhotoEvent(EntityUpdated, clean.IdString(c.Param("uid")), c)
event.Success("label removed")
@ -170,24 +170,24 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
// 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 {
AbortEntityNotFound(c)
return
}
labelId, err := strconv.Atoi(sanitize.Token(c.Param("id")))
labelId, err := strconv.Atoi(clean.Token(c.Param("id")))
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
}
label, err := query.PhotoLabel(m.ID, uint(labelId))
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
}
@ -197,11 +197,11 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
}
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
}
p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid")))
p, err := query.PhotoPreloadByUID(clean.IdString(c.Param("uid")))
if err != nil {
AbortEntityNotFound(c)
@ -209,11 +209,11 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
}
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
}
PublishPhotoEvent(EntityUpdated, sanitize.IdString(c.Param("uid")), c)
PublishPhotoEvent(EntityUpdated, clean.IdString(c.Param("uid")), c)
event.Success("label saved")

View file

@ -5,7 +5,7 @@ import (
"net/http"
"path/filepath"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl"
@ -34,7 +34,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
}
conf := service.Config()
fileUID := sanitize.IdString(c.Param("file_uid"))
fileUID := clean.IdString(c.Param("file_uid"))
file, err := query.FileByUID(fileUID)
if err != nil {
@ -63,7 +63,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
unstackFile, err := photoprism.NewMediaFile(fileName)
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)
return
} else if file.Photo == nil {
@ -76,7 +76,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
stackPrimary, err := stackPhoto.PrimaryFile()
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)
return
}
@ -87,15 +87,15 @@ func PhotoUnstack(router *gin.RouterGroup) {
related, err := unstackFile.RelatedFiles(false)
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)
return
} 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)
return
} 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)
return
}
@ -105,7 +105,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
if unstackFile.BasePrefix(false) == stackPhoto.PhotoName {
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)
return
}
@ -113,7 +113,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
destName := fmt.Sprintf("%s.%s%s", unstackFile.AbsPrefix(false), unstackFile.Checksum(), unstackFile.Extension())
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)
return
}
@ -130,7 +130,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
newPhoto.PhotoName = unstackFile.BasePrefix(false)
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)
return
}
@ -150,17 +150,17 @@ func PhotoUnstack(router *gin.RouterGroup) {
newPhoto.ID, newPhoto.PhotoUID, r.RootRelName(),
relName, relRoot).Error; err != nil {
// 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.
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.
if unstackSingle {
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.
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)
return
}
// Reset type for existing photo stack to image.
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)
return
}
// Re-index existing photo stack.
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)
return
}

View file

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

View file

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

View file

@ -6,7 +6,7 @@ import (
"path/filepath"
"time"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
@ -68,7 +68,7 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
listFiles := f.Files
uncached := listFiles || f.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)

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ package api
import (
"net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl"
@ -91,7 +91,7 @@ func CreateSession(router *gin.RouterGroup) {
// DELETE /api/v1/session/:id
func DeleteSession(router *gin.RouterGroup) {
router.DELETE("/session/:id", func(c *gin.Context) {
id := sanitize.Token(c.Param("id"))
id := clean.Token(c.Param("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.
func InvalidPreviewToken(c *gin.Context) bool {
token := sanitize.Token(c.Param("token"))
token := clean.Token(c.Param("token"))
if token == "" {
token = sanitize.Token(c.Query("t"))
token = clean.Token(c.Query("t"))
}
return service.Config().InvalidPreviewToken(token)
@ -139,5 +139,5 @@ func InvalidPreviewToken(c *gin.Context) bool {
// InvalidDownloadToken returns true if the token is invalid.
func InvalidDownloadToken(c *gin.Context) bool {
return service.Config().InvalidDownloadToken(sanitize.Token(c.Query("t")))
return service.Config().InvalidDownloadToken(clean.Token(c.Query("t")))
}

View file

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

View file

@ -9,7 +9,7 @@ import (
"path"
"time"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/disintegration/imaging"
"github.com/gin-gonic/gin"
@ -30,8 +30,8 @@ func SharePreview(router *gin.RouterGroup) {
router.GET("/:token/:share/preview", func(c *gin.Context) {
conf := service.Config()
token := sanitize.Token(c.Param("token"))
share := sanitize.Token(c.Param("share"))
token := clean.Token(c.Param("token"))
share := clean.Token(c.Param("share"))
links := entity.FindLinks(token, share)
if len(links) != 1 {
@ -52,13 +52,13 @@ func SharePreview(router *gin.RouterGroup) {
yesterday := time.Now().Add(-24 * time.Hour)
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) {
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)
return
} 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())
return
}
@ -96,7 +96,7 @@ func SharePreview(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.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())
return
}
@ -126,7 +126,7 @@ func SharePreview(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.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())
return
}

View file

@ -3,7 +3,7 @@ package api
import (
"net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/gin-gonic/gin"
@ -28,7 +28,7 @@ func GetSubject(router *gin.RouterGroup) {
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)
return
} else {
@ -56,7 +56,7 @@ func UpdateSubject(router *gin.RouterGroup) {
return
}
uid := sanitize.IdString(c.Param("uid"))
uid := clean.IdString(c.Param("uid"))
m := entity.FindSubject(uid)
if m == nil {
@ -109,7 +109,7 @@ func LikeSubject(router *gin.RouterGroup) {
return
}
uid := sanitize.IdString(c.Param("uid"))
uid := clean.IdString(c.Param("uid"))
subj := entity.FindSubject(uid)
if subj == nil {
@ -118,7 +118,7 @@ func LikeSubject(router *gin.RouterGroup) {
}
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
}
@ -143,7 +143,7 @@ func DislikeSubject(router *gin.RouterGroup) {
return
}
uid := sanitize.IdString(c.Param("uid"))
uid := clean.IdString(c.Param("uid"))
subj := entity.FindSubject(uid)
if subj == nil {
@ -152,7 +152,7 @@ func DislikeSubject(router *gin.RouterGroup) {
}
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
}

View file

@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean"
"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.
@ -37,16 +37,16 @@ func GetThumb(router *gin.RouterGroup) {
start := time.Now()
conf := service.Config()
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?
if cropArea != "" {
cropName := crop.Name(sanitize.Token(c.Param("size")))
cropName := crop.Name(clean.Token(c.Param("size")))
cropSize, ok := crop.Sizes[cropName]
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)
return
}
@ -73,12 +73,12 @@ func GetThumb(router *gin.RouterGroup) {
return
}
thumbName := thumb.Name(sanitize.Token(c.Param("size")))
thumbName := thumb.Name(clean.Token(c.Param("size")))
size, ok := thumb.Sizes[thumbName]
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)
return
}
@ -153,17 +153,17 @@ func GetThumb(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.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)
// Set missing flag so that the file doesn't show up in search results anymore.
logError(logPrefix, f.Update("FileMissing", true))
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 {
log.Errorf("%s: %s while deleting %s", logPrefix, err, sanitize.Log(f.FileName))
log.Errorf("%s: %s while deleting %s", logPrefix, err, clean.Log(f.FileName))
}
}

View file

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

View file

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

View file

@ -3,13 +3,14 @@ package api
import (
"net/http"
"github.com/photoprism/photoprism/pkg/video"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/video"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
)
// GetVideo streams videos.
@ -26,13 +27,13 @@ func GetVideo(router *gin.RouterGroup) {
return
}
fileHash := sanitize.Token(c.Param("hash"))
formatName := sanitize.Token(c.Param("format"))
fileHash := clean.Token(c.Param("hash"))
formatName := clean.Token(c.Param("format"))
format, ok := video.Formats[formatName]
format, ok := video.Types[formatName]
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)
return
}
@ -64,7 +65,7 @@ func GetVideo(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName)
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)
// 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()
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)
return
} else {

View file

@ -12,7 +12,7 @@ import (
"github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
)
var autoImport = time.Time{}
@ -66,7 +66,7 @@ func Import() error {
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

View file

@ -11,8 +11,8 @@ import (
"text/template"
"unicode"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
"gopkg.in/yaml.v2"
)
@ -34,7 +34,7 @@ func main() {
fileName := "rules.yml"
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)

View file

@ -14,7 +14,7 @@ import (
"strings"
"github.com/disintegration/imaging"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)
@ -148,7 +148,7 @@ func (t *TensorFlow) loadModel() error {
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
model, err := tf.LoadSavedModel(modelPath, t.modelTags, nil)

View file

@ -16,8 +16,8 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"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. " +
@ -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
@ -178,7 +178,7 @@ func backupAction(ctx *cli.Context) error {
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 {
return err

View file

@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
)
// ConvertCommand registers the convert cli command.
@ -54,7 +54,7 @@ func convertAction(ctx *cli.Context) error {
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()

View file

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

View file

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

View file

@ -14,7 +14,7 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
)
// PasswdCommand updates a password.
@ -39,7 +39,7 @@ func passwdAction(ctx *cli.Context) error {
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: ")
@ -57,7 +57,7 @@ func passwdAction(ctx *cli.Context) error {
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()

View file

@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
)
// PurgeCommand registers the index cli command.
@ -56,9 +56,9 @@ func purgeAction(ctx *cli.Context) error {
subPath := strings.TrimSpace(ctx.Args().First())
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 {
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() {

View file

@ -18,8 +18,8 @@ import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean"
"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. " +
@ -124,7 +124,7 @@ func restoreAction(ctx *cli.Context) error {
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)
@ -203,9 +203,9 @@ func restoreAction(ctx *cli.Context) error {
}
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 {
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 {
return err

View file

@ -6,6 +6,7 @@ import (
"github.com/urfave/cli"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/report"
)
@ -15,8 +16,8 @@ var ShowFormatsCommand = cli.Command{
Usage: "Lists supported media and sidecar file formats",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "compact, c",
Usage: "hide format descriptions to make the output more compact",
Name: "short, s",
Usage: "hides format descriptions",
},
cli.BoolFlag{
Name: "md, m",
@ -28,8 +29,7 @@ var ShowFormatsCommand = cli.Command{
// showFormatsAction lists supported media and sidecar file formats.
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")))
return nil

View file

@ -18,8 +18,8 @@ import (
"github.com/photoprism/photoprism/internal/server"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/workers"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
)
// StartCommand registers the start cli command.
@ -95,7 +95,7 @@ func startAction(ctx *cli.Context) error {
if child != nil {
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)

View file

@ -7,7 +7,7 @@ import (
"github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
)
// StopCommand registers the stop cli command.
@ -22,7 +22,7 @@ var StopCommand = cli.Command{
func stopAction(ctx *cli.Context) error {
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.PidFileName = conf.PIDFilename()

View file

@ -7,7 +7,7 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/clean"
)
// ThumbsCommand registers the resample cli command.
@ -34,7 +34,7 @@ func thumbsAction(ctx *cli.Context) error {
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()

View file

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

View file

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

View file

@ -29,9 +29,9 @@ import (
"github.com/photoprism/photoprism/internal/hub/places"
"github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/sanitize"
)
var log = event.Log
@ -39,37 +39,6 @@ var once sync.Once
var LowMem = false
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
type Config struct {
once sync.Once
@ -125,16 +94,16 @@ func NewConfig(ctx *cli.Context) *Config {
// Initialize options from config file and CLI context.
c := &Config{
options: NewOptions(ctx),
token: rnd.Token(8),
token: rnd.GenerateToken(8),
env: os.Getenv("DOCKER_ENV"),
}
// Overwrite values with options.yml from config path.
if optionsYaml := c.OptionsYaml(); fs.FileExists(optionsYaml) {
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 {
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 != "" {
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.
@ -261,7 +230,7 @@ func (c *Config) readSerial() string {
if data, err := os.ReadFile(storageName); err == nil && len(data) == 16 {
return string(data)
} 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 {
return string(data)
} 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
}
c.serial = rnd.PPID('z')
c.serial = rnd.GenerateUID('z')
storageName := filepath.Join(c.StoragePath(), serialName)
backupName := filepath.Join(c.BackupPath(), serialName)

View file

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

View file

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

View file

@ -7,8 +7,8 @@ import (
"os/user"
"path/filepath"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
)
// binPaths stores known executable paths.
@ -48,9 +48,9 @@ func findExecutable(configBin, defaultBin string) (binPath string) {
func (c *Config) CreateDirectories() error {
createError := func(path string, err error) (result error) {
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 {
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)
@ -59,7 +59,7 @@ func (c *Config) CreateDirectories() 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() == "" {
@ -154,11 +154,11 @@ func (c *Config) CreateDirectories() error {
if c.DarktableEnabled() {
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 {
return fmt.Errorf("could not create darktable cache path %s", sanitize.Log(configPath))
return fmt.Errorf("could not create darktable cache path %s", clean.Log(configPath))
}
}

View file

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

View file

@ -4,10 +4,30 @@ import (
"path/filepath"
"strings"
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/pkg/fs"
"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.
func (c *Config) AppIcon() string {
defaultIcon := "logo"

View file

@ -7,6 +7,31 @@ import (
"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) {
c := NewConfig(CliTestContext())

View file

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

View file

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

View file

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

View file

@ -10,8 +10,8 @@ import (
"github.com/urfave/cli"
"gopkg.in/yaml.v2"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/txt"
)
@ -150,9 +150,9 @@ func NewOptions(ctx *cli.Context) *Options {
if defaultsYaml := ctx.GlobalString("defaults-yaml"); defaultsYaml == "" {
log.Tracef("config: defaults yaml file not specified")
} 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 {
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 {

View file

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

View file

@ -8,89 +8,10 @@ import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/pkg/clean"
"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.
type Settings struct {
UI UISettings `json:"ui" yaml:"UI"`
@ -159,9 +80,7 @@ func NewSettings(c *Config) *Settings {
Share: ShareSettings{
Title: "",
},
Download: DownloadSettings{
Name: entity.DownloadNameDefault,
},
Download: NewDownloadSettings(),
Templates: TemplateSettings{
Default: "index.tmpl",
},
@ -191,7 +110,7 @@ func (s Settings) StackMeta() bool {
// Load user settings from file.
func (s *Settings) Load(fileName string) error {
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)

View file

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

View file

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

View file

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

View file

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

View file

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

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