mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-12-23 01:27:14 +02:00
improving linting
This commit is contained in:
parent
19aa1a0e90
commit
81068b6d66
@ -1,5 +1,5 @@
|
||||
import { Config } from '../common/config/private/Config';
|
||||
import { LogLevel } from '../common/config/private/PrivateConfig';
|
||||
import {Config} from '../common/config/private/Config';
|
||||
import {LogLevel} from '../common/config/private/PrivateConfig';
|
||||
|
||||
export type logFN = (...args: (string | number)[]) => void;
|
||||
|
||||
@ -7,7 +7,7 @@ const forcedDebug = process.env['NODE_ENV'] === 'debug';
|
||||
|
||||
if (forcedDebug === true) {
|
||||
console.log(
|
||||
'NODE_ENV environmental variable is set to debug, forcing all logs to print'
|
||||
'NODE_ENV environmental variable is set to debug, forcing all logs to print'
|
||||
);
|
||||
}
|
||||
|
||||
@ -55,10 +55,10 @@ export class Logger {
|
||||
const date = new Date().toLocaleString();
|
||||
let LOG_TAG = '';
|
||||
if (
|
||||
args.length > 0 &&
|
||||
typeof args[0] === 'string' &&
|
||||
args[0].startsWith('[') &&
|
||||
args[0].endsWith(']')
|
||||
args.length > 0 &&
|
||||
typeof args[0] === 'string' &&
|
||||
args[0].startsWith('[') &&
|
||||
args[0].endsWith(']')
|
||||
) {
|
||||
LOG_TAG = args[0];
|
||||
args.shift();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { Config } from '../common/config/private/Config';
|
||||
import {Config} from '../common/config/private/Config';
|
||||
|
||||
class ProjectPathClass {
|
||||
public Root: string;
|
||||
|
@ -6,28 +6,28 @@ import {Config} from '../../common/config/private/Config';
|
||||
|
||||
export class AlbumMWs {
|
||||
public static async listAlbums(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Album.enabled === false) {
|
||||
return next();
|
||||
}
|
||||
try {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().AlbumManager.getAlbums();
|
||||
await ObjectManagers.getInstance().AlbumManager.getAlbums();
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during listing albums', err)
|
||||
new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during listing albums', err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async deleteAlbum(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Album.enabled === false) {
|
||||
return next();
|
||||
@ -37,52 +37,52 @@ export class AlbumMWs {
|
||||
}
|
||||
try {
|
||||
await ObjectManagers.getInstance().AlbumManager.deleteAlbum(
|
||||
parseInt(req.params['id'], 10)
|
||||
parseInt(req.params['id'], 10)
|
||||
);
|
||||
req.resultPipe = 'ok';
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.ALBUM_ERROR,
|
||||
'Error during deleting albums',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.ALBUM_ERROR,
|
||||
'Error during deleting albums',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async createSavedSearch(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Album.enabled === false) {
|
||||
return next();
|
||||
}
|
||||
if (
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.name !== 'string' ||
|
||||
typeof req.body.searchQuery !== 'object'
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.name !== 'string' ||
|
||||
typeof req.body.searchQuery !== 'object'
|
||||
) {
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing')
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing')
|
||||
);
|
||||
}
|
||||
try {
|
||||
await ObjectManagers.getInstance().AlbumManager.addSavedSearch(
|
||||
req.body.name,
|
||||
req.body.searchQuery
|
||||
req.body.name,
|
||||
req.body.searchQuery
|
||||
);
|
||||
req.resultPipe = 'ok';
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.ALBUM_ERROR,
|
||||
'Error during creating saved search albums',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.ALBUM_ERROR,
|
||||
'Error during creating saved search albums',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -21,14 +21,14 @@ import {SortByTypes} from '../../common/entities/SortingMethods';
|
||||
export class GalleryMWs {
|
||||
@ServerTime('1.db', 'List Directory')
|
||||
public static async listDirectory(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
const directoryName = req.params['directory'] || '/';
|
||||
const absoluteDirectoryName = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
directoryName
|
||||
ProjectPath.ImageFolder,
|
||||
directoryName
|
||||
);
|
||||
try {
|
||||
if ((await fsp.stat(absoluteDirectoryName)).isDirectory() === false) {
|
||||
@ -40,57 +40,57 @@ export class GalleryMWs {
|
||||
|
||||
try {
|
||||
const directory =
|
||||
await ObjectManagers.getInstance().GalleryManager.listDirectory(
|
||||
directoryName,
|
||||
parseInt(
|
||||
req.query[QueryParams.gallery.knownLastModified] as string,
|
||||
10
|
||||
),
|
||||
parseInt(
|
||||
req.query[QueryParams.gallery.knownLastScanned] as string,
|
||||
10
|
||||
)
|
||||
);
|
||||
await ObjectManagers.getInstance().GalleryManager.listDirectory(
|
||||
directoryName,
|
||||
parseInt(
|
||||
req.query[QueryParams.gallery.knownLastModified] as string,
|
||||
10
|
||||
),
|
||||
parseInt(
|
||||
req.query[QueryParams.gallery.knownLastScanned] as string,
|
||||
10
|
||||
)
|
||||
);
|
||||
|
||||
if (directory == null) {
|
||||
req.resultPipe = new ContentWrapper(null, null, true);
|
||||
return next();
|
||||
}
|
||||
if (
|
||||
req.session['user'].permissions &&
|
||||
req.session['user'].permissions.length > 0 &&
|
||||
req.session['user'].permissions[0] !== '/*'
|
||||
req.session['user'].permissions &&
|
||||
req.session['user'].permissions.length > 0 &&
|
||||
req.session['user'].permissions[0] !== '/*'
|
||||
) {
|
||||
directory.directories = directory.directories.filter((d): boolean =>
|
||||
UserDTOUtils.isDirectoryAvailable(d, req.session['user'].permissions)
|
||||
UserDTOUtils.isDirectoryAvailable(d, req.session['user'].permissions)
|
||||
);
|
||||
}
|
||||
req.resultPipe = new ContentWrapper(directory, null);
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during listing the directory',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during listing the directory',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ServerTime('1.zip', 'Zip Directory')
|
||||
public static async zipDirectory(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Gallery.NavBar.enableDownloadZip === false) {
|
||||
return next();
|
||||
}
|
||||
const directoryName = req.params['directory'] || '/';
|
||||
const absoluteDirectoryName = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
directoryName
|
||||
ProjectPath.ImageFolder,
|
||||
directoryName
|
||||
);
|
||||
try {
|
||||
if ((await fsp.stat(absoluteDirectoryName)).isDirectory() === false) {
|
||||
@ -133,16 +133,16 @@ export class GalleryMWs {
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error creating zip', err)
|
||||
new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error creating zip', err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ServerTime('3.pack', 'pack result')
|
||||
public static cleanUpGalleryResults(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -157,14 +157,14 @@ export class GalleryMWs {
|
||||
if (cw.directory) {
|
||||
const removeVideos = (dir: ParentDirectoryDTO): void => {
|
||||
dir.media = dir.media.filter(
|
||||
(m): boolean => !MediaDTOUtils.isVideo(m)
|
||||
(m): boolean => !MediaDTOUtils.isVideo(m)
|
||||
);
|
||||
};
|
||||
removeVideos(cw.directory);
|
||||
}
|
||||
if (cw.searchResult) {
|
||||
cw.searchResult.media = cw.searchResult.media.filter(
|
||||
(m): boolean => !MediaDTOUtils.isVideo(m)
|
||||
(m): boolean => !MediaDTOUtils.isVideo(m)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -175,16 +175,16 @@ export class GalleryMWs {
|
||||
}
|
||||
|
||||
public static async loadFile(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.params['mediaPath']) {
|
||||
return next();
|
||||
}
|
||||
const fullMediaPath = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
req.params['mediaPath']
|
||||
ProjectPath.ImageFolder,
|
||||
req.params['mediaPath']
|
||||
);
|
||||
|
||||
// check if file exist
|
||||
@ -194,11 +194,11 @@ export class GalleryMWs {
|
||||
}
|
||||
} catch (e) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'no such file:' + req.params['mediaPath'],
|
||||
'can\'t find file: ' + fullMediaPath
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'no such file:' + req.params['mediaPath'],
|
||||
'can\'t find file: ' + fullMediaPath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -207,9 +207,9 @@ export class GalleryMWs {
|
||||
}
|
||||
|
||||
public static async loadBestFitVideo(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -217,7 +217,7 @@ export class GalleryMWs {
|
||||
const fullMediaPath = req.resultPipe as string;
|
||||
|
||||
const convertedVideo =
|
||||
VideoProcessing.generateConvertedFilePath(fullMediaPath);
|
||||
VideoProcessing.generateConvertedFilePath(fullMediaPath);
|
||||
|
||||
// check if transcoded video exist
|
||||
try {
|
||||
@ -232,52 +232,52 @@ export class GalleryMWs {
|
||||
|
||||
@ServerTime('1.db', 'Search')
|
||||
public static async search(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (
|
||||
Config.Search.enabled === false ||
|
||||
!req.params['searchQueryDTO']
|
||||
Config.Search.enabled === false ||
|
||||
!req.params['searchQueryDTO']
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const query: SearchQueryDTO = JSON.parse(
|
||||
req.params['searchQueryDTO'] as string
|
||||
req.params['searchQueryDTO'] as string
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await ObjectManagers.getInstance().SearchManager.search(
|
||||
query
|
||||
query
|
||||
);
|
||||
|
||||
result.directories.forEach(
|
||||
(dir): MediaDTO[] => (dir.media = dir.media || [])
|
||||
(dir): MediaDTO[] => (dir.media = dir.media || [])
|
||||
);
|
||||
req.resultPipe = new ContentWrapper(null, result);
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (err instanceof LocationLookupException) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.LocationLookUp_ERROR,
|
||||
'Cannot find location: ' + err.location,
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.LocationLookUp_ERROR,
|
||||
'Cannot find location: ' + err.location,
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err)
|
||||
new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ServerTime('1.db', 'Autocomplete')
|
||||
public static async autocomplete(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Search.AutoComplete.enabled === false) {
|
||||
return next();
|
||||
@ -292,53 +292,53 @@ export class GalleryMWs {
|
||||
}
|
||||
try {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SearchManager.autocomplete(
|
||||
req.params['text'],
|
||||
type
|
||||
);
|
||||
await ObjectManagers.getInstance().SearchManager.autocomplete(
|
||||
req.params['text'],
|
||||
type
|
||||
);
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err)
|
||||
new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async getRandomImage(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (
|
||||
Config.RandomPhoto.enabled === false ||
|
||||
!req.params['searchQueryDTO']
|
||||
Config.RandomPhoto.enabled === false ||
|
||||
!req.params['searchQueryDTO']
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
const query: SearchQueryDTO = JSON.parse(
|
||||
req.params['searchQueryDTO'] as string
|
||||
req.params['searchQueryDTO'] as string
|
||||
);
|
||||
|
||||
const photos =
|
||||
await ObjectManagers.getInstance().SearchManager.getNMedia(query, [{method: SortByTypes.Random, ascending: null}], 1, true);
|
||||
await ObjectManagers.getInstance().SearchManager.getNMedia(query, [{method: SortByTypes.Random, ascending: null}], 1, true);
|
||||
if (!photos || photos.length !== 1) {
|
||||
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'No photo found'));
|
||||
}
|
||||
|
||||
req.params['mediaPath'] = path.join(
|
||||
photos[0].directory.path,
|
||||
photos[0].directory.name,
|
||||
photos[0].name
|
||||
photos[0].directory.path,
|
||||
photos[0].directory.name,
|
||||
photos[0].name
|
||||
);
|
||||
return next();
|
||||
} catch (e) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Can\'t get random photo: ' + e.toString()
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Can\'t get random photo: ' + e.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { UserRoles } from '../../common/entities/UserDTO';
|
||||
import { NotificationManager } from '../model/NotifocationManager';
|
||||
import {NextFunction, Request, Response} from 'express';
|
||||
import {UserRoles} from '../../common/entities/UserDTO';
|
||||
import {NotificationManager} from '../model/NotifocationManager';
|
||||
|
||||
export class NotificationMWs {
|
||||
public static list(req: Request, res: Response, next: NextFunction): void {
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { ErrorCodes, ErrorDTO } from '../../common/entities/Error';
|
||||
import { ObjectManagers } from '../model/ObjectManagers';
|
||||
import {
|
||||
PersonDTO,
|
||||
} from '../../common/entities/PersonDTO';
|
||||
import { Utils } from '../../common/Utils';
|
||||
import {NextFunction, Request, Response} from 'express';
|
||||
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
|
||||
import {ObjectManagers} from '../model/ObjectManagers';
|
||||
import {PersonDTO,} from '../../common/entities/PersonDTO';
|
||||
import {Utils} from '../../common/Utils';
|
||||
import {PersonEntry} from '../model/database/enitites/PersonEntry';
|
||||
|
||||
export class PersonMWs {
|
||||
public static async updatePerson(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.params['name']) {
|
||||
return next();
|
||||
@ -19,26 +17,26 @@ export class PersonMWs {
|
||||
|
||||
try {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().PersonManager.updatePerson(
|
||||
req.params['name'] as string,
|
||||
req.body as PersonDTO
|
||||
);
|
||||
await ObjectManagers.getInstance().PersonManager.updatePerson(
|
||||
req.params['name'] as string,
|
||||
req.body as PersonDTO
|
||||
);
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.PERSON_ERROR,
|
||||
'Error during updating a person',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.PERSON_ERROR,
|
||||
'Error during updating a person',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async getPerson(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.params['name']) {
|
||||
return next();
|
||||
@ -46,45 +44,45 @@ export class PersonMWs {
|
||||
|
||||
try {
|
||||
req.resultPipe = await ObjectManagers.getInstance().PersonManager.get(
|
||||
req.params['name'] as string
|
||||
req.params['name'] as string
|
||||
);
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.PERSON_ERROR,
|
||||
'Error during updating a person',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.PERSON_ERROR,
|
||||
'Error during updating a person',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async listPersons(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
try {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().PersonManager.getAll();
|
||||
await ObjectManagers.getInstance().PersonManager.getAll();
|
||||
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.PERSON_ERROR,
|
||||
'Error during listing persons',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.PERSON_ERROR,
|
||||
'Error during listing persons',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async cleanUpPersonResults(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -98,11 +96,11 @@ export class PersonMWs {
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.PERSON_ERROR,
|
||||
'Error during removing sample photo from all persons',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.PERSON_ERROR,
|
||||
'Error during removing sample photo from all persons',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ const forcedDebug = process.env['NODE_ENV'] === 'debug';
|
||||
|
||||
export class RenderingMWs {
|
||||
public static renderResult(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (typeof req.resultPipe === 'undefined') {
|
||||
return next();
|
||||
@ -26,9 +26,9 @@ export class RenderingMWs {
|
||||
}
|
||||
|
||||
public static renderSessionUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (!req.session['user']) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'User not exists'));
|
||||
@ -51,9 +51,9 @@ export class RenderingMWs {
|
||||
}
|
||||
|
||||
public static renderSharing(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -65,9 +65,9 @@ export class RenderingMWs {
|
||||
}
|
||||
|
||||
public static renderSharingList(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -82,9 +82,9 @@ export class RenderingMWs {
|
||||
}
|
||||
|
||||
public static renderFile(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -96,36 +96,36 @@ export class RenderingMWs {
|
||||
}
|
||||
|
||||
public static renderOK(
|
||||
req: Request,
|
||||
res: Response
|
||||
req: Request,
|
||||
res: Response
|
||||
): void {
|
||||
const message = new Message<string>(null, 'ok');
|
||||
res.json(message);
|
||||
}
|
||||
|
||||
public static async renderConfig(
|
||||
req: Request,
|
||||
res: Response
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const originalConf = await Config.original();
|
||||
// These are sensitive information, do not send to the client side
|
||||
originalConf.Server.sessionSecret = null;
|
||||
const message = new Message<PrivateConfigClass>(
|
||||
null,
|
||||
originalConf.toJSON({
|
||||
attachState: true,
|
||||
attachVolatile: true,
|
||||
skipTags: {secret: true} as TAGS
|
||||
}) as PrivateConfigClass
|
||||
null,
|
||||
originalConf.toJSON({
|
||||
attachState: true,
|
||||
attachVolatile: true,
|
||||
skipTags: {secret: true} as TAGS
|
||||
}) as PrivateConfigClass
|
||||
);
|
||||
res.json(message);
|
||||
}
|
||||
|
||||
public static renderError(
|
||||
err: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
err: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (err instanceof ErrorDTO) {
|
||||
if (err.details) {
|
||||
@ -136,12 +136,12 @@ export class RenderingMWs {
|
||||
|
||||
// hide error details for non developers
|
||||
if (
|
||||
!(
|
||||
forcedDebug ||
|
||||
(req.session &&
|
||||
req.session['user'] &&
|
||||
req.session['user'].role >= UserRoles.Developer)
|
||||
)
|
||||
!(
|
||||
forcedDebug ||
|
||||
(req.session &&
|
||||
req.session['user'] &&
|
||||
req.session['user'].role >= UserRoles.Developer)
|
||||
)
|
||||
) {
|
||||
delete err.detailsStr;
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ export class ServerTimeEntry {
|
||||
|
||||
export const ServerTime = (id: string, name: string) => {
|
||||
return (
|
||||
target: unknown,
|
||||
propertyName: string,
|
||||
descriptor: TypedPropertyDescriptor<(req: unknown, res: unknown, next: () => void) => void>
|
||||
target: unknown,
|
||||
propertyName: string,
|
||||
descriptor: TypedPropertyDescriptor<(req: unknown, res: unknown, next: () => void) => void>
|
||||
): void => {
|
||||
if (Config.Server.Log.logServerTiming === false) {
|
||||
return;
|
||||
@ -40,8 +40,8 @@ export const ServerTime = (id: string, name: string) => {
|
||||
});
|
||||
};
|
||||
descriptor.value = new Function(
|
||||
'action',
|
||||
'return function ' + m.name + '(...args){ action(...args) };'
|
||||
'action',
|
||||
'return function ' + m.name + '(...args){ action(...args) };'
|
||||
)(customAction);
|
||||
};
|
||||
};
|
||||
@ -53,19 +53,19 @@ export class ServerTimingMWs {
|
||||
* Add server timing
|
||||
*/
|
||||
public static async addServerTiming(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (
|
||||
(Config.Server.Log.logServerTiming === false && !forcedDebug) ||
|
||||
!req.timing
|
||||
(Config.Server.Log.logServerTiming === false && !forcedDebug) ||
|
||||
!req.timing
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
const l = Object.entries(req.timing)
|
||||
.filter((e) => e[1].endTime)
|
||||
.map((e) => `${e[0]};dur=${e[1].endTime};desc="${e[1].name}"`);
|
||||
.filter((e) => e[1].endTime)
|
||||
.map((e) => `${e[0]};dur=${e[1].endTime};desc="${e[1].name}"`);
|
||||
res.header('Server-Timing', l.join(', '));
|
||||
next();
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ import {UserRoles} from '../../common/entities/UserDTO';
|
||||
|
||||
export class SharingMWs {
|
||||
public static async getSharing(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Sharing.enabled === false) {
|
||||
return next();
|
||||
@ -20,33 +20,33 @@ export class SharingMWs {
|
||||
|
||||
try {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.findOne(sharingKey);
|
||||
await ObjectManagers.getInstance().SharingManager.findOne(sharingKey);
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during retrieving sharing link',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during retrieving sharing link',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async createSharing(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Sharing.enabled === false) {
|
||||
return next();
|
||||
}
|
||||
if (
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.createSharing === 'undefined'
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.createSharing === 'undefined'
|
||||
) {
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'createSharing filed is missing')
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'createSharing filed is missing')
|
||||
);
|
||||
}
|
||||
const createSharing: CreateSharingDTO = req.body.createSharing;
|
||||
@ -71,45 +71,45 @@ export class SharingMWs {
|
||||
password: createSharing.password,
|
||||
creator: req.session['user'],
|
||||
expires:
|
||||
createSharing.valid >= 0 // if === -1 its forever
|
||||
? Date.now() + createSharing.valid
|
||||
: new Date(9999, 0, 1).getTime(), // never expire
|
||||
createSharing.valid >= 0 // if === -1 its forever
|
||||
? Date.now() + createSharing.valid
|
||||
: new Date(9999, 0, 1).getTime(), // never expire
|
||||
includeSubfolders: createSharing.includeSubfolders,
|
||||
timeStamp: Date.now(),
|
||||
};
|
||||
|
||||
try {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.createSharing(
|
||||
sharing
|
||||
);
|
||||
await ObjectManagers.getInstance().SharingManager.createSharing(
|
||||
sharing
|
||||
);
|
||||
return next();
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during creating sharing link',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during creating sharing link',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async updateSharing(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Sharing.enabled === false) {
|
||||
return next();
|
||||
}
|
||||
if (
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.updateSharing === 'undefined'
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.updateSharing === 'undefined'
|
||||
) {
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing')
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing')
|
||||
);
|
||||
}
|
||||
const updateSharing: CreateSharingDTO = req.body.updateSharing;
|
||||
@ -119,14 +119,14 @@ export class SharingMWs {
|
||||
path: directoryName,
|
||||
sharingKey: '',
|
||||
password:
|
||||
updateSharing.password && updateSharing.password !== ''
|
||||
? updateSharing.password
|
||||
: null,
|
||||
updateSharing.password && updateSharing.password !== ''
|
||||
? updateSharing.password
|
||||
: null,
|
||||
creator: req.session['user'],
|
||||
expires:
|
||||
updateSharing.valid >= 0 // if === -1 its forever
|
||||
? Date.now() + updateSharing.valid
|
||||
: new Date(9999, 0, 1).getTime(), // never expire
|
||||
updateSharing.valid >= 0 // if === -1 its forever
|
||||
? Date.now() + updateSharing.valid
|
||||
: new Date(9999, 0, 1).getTime(), // never expire
|
||||
includeSubfolders: updateSharing.includeSubfolders,
|
||||
timeStamp: Date.now(),
|
||||
};
|
||||
@ -134,36 +134,36 @@ export class SharingMWs {
|
||||
try {
|
||||
const forceUpdate = req.session['user'].role >= UserRoles.Admin;
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.updateSharing(
|
||||
sharing,
|
||||
forceUpdate
|
||||
);
|
||||
await ObjectManagers.getInstance().SharingManager.updateSharing(
|
||||
sharing,
|
||||
forceUpdate
|
||||
);
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during updating sharing link',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during updating sharing link',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async deleteSharing(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Sharing.enabled === false) {
|
||||
return next();
|
||||
}
|
||||
if (
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params['sharingKey'] === 'undefined'
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params['sharingKey'] === 'undefined'
|
||||
) {
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'sharingKey is missing')
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'sharingKey is missing')
|
||||
);
|
||||
}
|
||||
const sharingKey: string = req.params['sharingKey'];
|
||||
@ -177,49 +177,49 @@ export class SharingMWs {
|
||||
}
|
||||
}
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.deleteSharing(
|
||||
sharingKey
|
||||
);
|
||||
await ObjectManagers.getInstance().SharingManager.deleteSharing(
|
||||
sharingKey
|
||||
);
|
||||
req.resultPipe = 'ok';
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during deleting sharing',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during deleting sharing',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async listSharing(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Sharing.enabled === false) {
|
||||
return next();
|
||||
}
|
||||
try {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.listAll();
|
||||
await ObjectManagers.getInstance().SharingManager.listAll();
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during listing shares',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during listing shares',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async listSharingForDir(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Sharing.enabled === false) {
|
||||
return next();
|
||||
@ -229,19 +229,19 @@ export class SharingMWs {
|
||||
try {
|
||||
if (req.session['user'].role >= UserRoles.Admin) {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.listAllForDir(dir);
|
||||
await ObjectManagers.getInstance().SharingManager.listAllForDir(dir);
|
||||
} else {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.listAllForDir(dir, req.session['user']);
|
||||
await ObjectManagers.getInstance().SharingManager.listAllForDir(dir, req.session['user']);
|
||||
}
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during listing shares',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error during listing shares',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -249,8 +249,8 @@ export class SharingMWs {
|
||||
private static generateKey(): string {
|
||||
function s4(): string {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
|
||||
return s4() + s4();
|
||||
|
@ -1,30 +1,30 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { ObjectManagers } from '../model/ObjectManagers';
|
||||
import { ErrorCodes, ErrorDTO } from '../../common/entities/Error';
|
||||
import { CustomHeaders } from '../../common/CustomHeaders';
|
||||
import {NextFunction, Request, Response} from 'express';
|
||||
import {ObjectManagers} from '../model/ObjectManagers';
|
||||
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
|
||||
import {CustomHeaders} from '../../common/CustomHeaders';
|
||||
|
||||
export class VersionMWs {
|
||||
/**
|
||||
* This version data is mainly used on the client side to invalidate the cache
|
||||
*/
|
||||
public static async injectGalleryVersion(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
try {
|
||||
res.header(
|
||||
CustomHeaders.dataVersion,
|
||||
await ObjectManagers.getInstance().VersionManager.getDataVersion()
|
||||
CustomHeaders.dataVersion,
|
||||
await ObjectManagers.getInstance().VersionManager.getDataVersion()
|
||||
);
|
||||
next();
|
||||
} catch (err) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Can not get data version',
|
||||
err.toString()
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Can not get data version',
|
||||
err.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { ErrorCodes, ErrorDTO } from '../../../common/entities/Error';
|
||||
import { ObjectManagers } from '../../model/ObjectManagers';
|
||||
import { StatisticDTO } from '../../../common/entities/settings/StatisticDTO';
|
||||
import {NextFunction, Request, Response} from 'express';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
|
||||
import {ObjectManagers} from '../../model/ObjectManagers';
|
||||
import {StatisticDTO} from '../../../common/entities/settings/StatisticDTO';
|
||||
|
||||
export class AdminMWs {
|
||||
public static async loadStatistic(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
|
||||
const galleryManager = ObjectManagers.getInstance()
|
||||
.GalleryManager;
|
||||
.GalleryManager;
|
||||
const personManager = ObjectManagers.getInstance()
|
||||
.PersonManager;
|
||||
.PersonManager;
|
||||
try {
|
||||
req.resultPipe = {
|
||||
directories: await galleryManager.countDirectories(),
|
||||
@ -26,57 +26,57 @@ export class AdminMWs {
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting statistic: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting statistic: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting statistic',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting statistic',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async getDuplicates(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
|
||||
try {
|
||||
req.resultPipe = await ObjectManagers.getInstance()
|
||||
.GalleryManager.getPossibleDuplicates();
|
||||
.GalleryManager.getPossibleDuplicates();
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting duplicates: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting duplicates: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting duplicates',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting duplicates',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async startJob(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
try {
|
||||
const id = req.params['id'];
|
||||
@ -84,29 +84,29 @@ export class AdminMWs {
|
||||
const soloRun: boolean = req.body.soloRun;
|
||||
const allowParallelRun: boolean = req.body.allowParallelRun;
|
||||
await ObjectManagers.getInstance().JobManager.run(
|
||||
id,
|
||||
JobConfig,
|
||||
soloRun,
|
||||
allowParallelRun
|
||||
id,
|
||||
JobConfig,
|
||||
soloRun,
|
||||
allowParallelRun
|
||||
);
|
||||
req.resultPipe = 'ok';
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -120,56 +120,56 @@ export class AdminMWs {
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static getAvailableJobs(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
try {
|
||||
req.resultPipe =
|
||||
ObjectManagers.getInstance().JobManager.getAvailableJobs();
|
||||
ObjectManagers.getInstance().JobManager.getAvailableJobs();
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static getJobProgresses(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
try {
|
||||
req.resultPipe = ObjectManagers.getInstance().JobManager.getProgresses();
|
||||
@ -177,19 +177,19 @@ export class AdminMWs {
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ export class SettingsMWs {
|
||||
*/
|
||||
public static async updateSettings(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||
if ((typeof req.body === 'undefined')
|
||||
|| (typeof req.body.settings === 'undefined')
|
||||
|| (typeof req.body.settingsPath !== 'string')) {
|
||||
|| (typeof req.body.settings === 'undefined')
|
||||
|| (typeof req.body.settingsPath !== 'string')) {
|
||||
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import {NextFunction, Request, Response} from 'express';
|
||||
import * as fs from 'fs';
|
||||
import { PhotoProcessing } from '../../model/fileprocessing/PhotoProcessing';
|
||||
import { Config } from '../../../common/config/private/Config';
|
||||
import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
|
||||
|
||||
export class PhotoConverterMWs {
|
||||
public static async convertPhoto(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -20,8 +20,8 @@ export class PhotoConverterMWs {
|
||||
const fullMediaPath = req.resultPipe as string;
|
||||
|
||||
const convertedVideo = PhotoProcessing.generateConvertedPath(
|
||||
fullMediaPath,
|
||||
Config.Media.Photo.Converting.resolution
|
||||
fullMediaPath,
|
||||
Config.Media.Photo.Converting.resolution
|
||||
);
|
||||
|
||||
// check if converted photo exist
|
||||
@ -33,7 +33,7 @@ export class PhotoConverterMWs {
|
||||
if (Config.Media.Photo.Converting.onTheFly === true) {
|
||||
try {
|
||||
req.resultPipe = await PhotoProcessing.convertPhoto(fullMediaPath);
|
||||
}catch (err){
|
||||
} catch (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.PHOTO_GENERATION_ERROR, err.message));
|
||||
}
|
||||
return next();
|
||||
|
@ -14,13 +14,13 @@ import {PersonEntry} from '../../model/database/enitites/PersonEntry';
|
||||
|
||||
export class ThumbnailGeneratorMWs {
|
||||
private static ThumbnailMapEntries =
|
||||
Config.Media.Thumbnail.generateThumbnailMapEntries();
|
||||
Config.Media.Thumbnail.generateThumbnailMapEntries();
|
||||
|
||||
@ServerTime('2.th', 'Thumbnail decoration')
|
||||
public static async addThumbnailInformation(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -34,7 +34,7 @@ export class ThumbnailGeneratorMWs {
|
||||
|
||||
// regenerate in case the list change since startup
|
||||
ThumbnailGeneratorMWs.ThumbnailMapEntries =
|
||||
Config.Media.Thumbnail.generateThumbnailMapEntries();
|
||||
Config.Media.Thumbnail.generateThumbnailMapEntries();
|
||||
if (cw.directory) {
|
||||
ThumbnailGeneratorMWs.addThInfoTODir(cw.directory);
|
||||
}
|
||||
@ -44,11 +44,11 @@ export class ThumbnailGeneratorMWs {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.SERVER_ERROR,
|
||||
'error during postprocessing result (adding thumbnail info)',
|
||||
error.toString()
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.SERVER_ERROR,
|
||||
'error during postprocessing result (adding thumbnail info)',
|
||||
error.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -57,9 +57,9 @@ export class ThumbnailGeneratorMWs {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/typedef, @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types
|
||||
public static addThumbnailInfoForPersons(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -79,28 +79,28 @@ export class ThumbnailGeneratorMWs {
|
||||
}
|
||||
// load parameters
|
||||
const mediaPath = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.sampleRegion.media.directory.path,
|
||||
item.sampleRegion.media.directory.name,
|
||||
item.sampleRegion.media.name
|
||||
ProjectPath.ImageFolder,
|
||||
item.sampleRegion.media.directory.path,
|
||||
item.sampleRegion.media.directory.name,
|
||||
item.sampleRegion.media.name
|
||||
);
|
||||
|
||||
// generate thumbnail path
|
||||
const thPath = PhotoProcessing.generatePersonThumbnailPath(
|
||||
mediaPath,
|
||||
item.sampleRegion.media.metadata.faces.find(f => f.name === item.name),
|
||||
size
|
||||
mediaPath,
|
||||
item.sampleRegion.media.metadata.faces.find(f => f.name === item.name),
|
||||
size
|
||||
);
|
||||
|
||||
item.missingThumbnail = !fs.existsSync(thPath);
|
||||
}
|
||||
} catch (error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.SERVER_ERROR,
|
||||
`Error during postprocessing result: adding thumbnail info for persons. Failed on: person ${erroredItem?.name}, at ${erroredItem?.sampleRegion?.media?.name} `,
|
||||
error.toString()
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.SERVER_ERROR,
|
||||
`Error during postprocessing result: adding thumbnail info for persons. Failed on: person ${erroredItem?.name}, at ${erroredItem?.sampleRegion?.media?.name} `,
|
||||
error.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -108,9 +108,9 @@ export class ThumbnailGeneratorMWs {
|
||||
}
|
||||
|
||||
public static async generatePersonThumbnail(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -122,22 +122,22 @@ export class ThumbnailGeneratorMWs {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating face thumbnail: ' + person.name,
|
||||
error.toString()
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating face thumbnail: ' + person.name,
|
||||
error.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static generateThumbnailFactory(
|
||||
sourceType: ThumbnailSourceType
|
||||
sourceType: ThumbnailSourceType
|
||||
): (req: Request, res: Response, next: NextFunction) => Promise<void> {
|
||||
return async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -146,8 +146,8 @@ export class ThumbnailGeneratorMWs {
|
||||
// load parameters
|
||||
const mediaPath = req.resultPipe as string;
|
||||
let size: number =
|
||||
parseInt(req.params.size, 10) ||
|
||||
Config.Media.Thumbnail.thumbnailSizes[0];
|
||||
parseInt(req.params.size, 10) ||
|
||||
Config.Media.Thumbnail.thumbnailSizes[0];
|
||||
|
||||
// validate size
|
||||
if (Config.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1) {
|
||||
@ -156,31 +156,31 @@ export class ThumbnailGeneratorMWs {
|
||||
|
||||
try {
|
||||
req.resultPipe = await PhotoProcessing.generateThumbnail(
|
||||
mediaPath,
|
||||
size,
|
||||
sourceType,
|
||||
false
|
||||
mediaPath,
|
||||
size,
|
||||
sourceType,
|
||||
false
|
||||
);
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating thumbnail: ' + mediaPath,
|
||||
error.toString()
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating thumbnail: ' + mediaPath,
|
||||
error.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static generateIconFactory(
|
||||
sourceType: ThumbnailSourceType
|
||||
sourceType: ThumbnailSourceType
|
||||
): (req: Request, res: Response, next: NextFunction) => Promise<void> {
|
||||
return async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -192,26 +192,26 @@ export class ThumbnailGeneratorMWs {
|
||||
|
||||
try {
|
||||
req.resultPipe = await PhotoProcessing.generateThumbnail(
|
||||
mediaPath,
|
||||
size,
|
||||
sourceType,
|
||||
true
|
||||
mediaPath,
|
||||
size,
|
||||
sourceType,
|
||||
true
|
||||
);
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating thumbnail: ' + mediaPath,
|
||||
error.toString()
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating thumbnail: ' + mediaPath,
|
||||
error.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static addThInfoTODir(
|
||||
directory: ParentDirectoryDTO | SubDirectoryDTO
|
||||
directory: ParentDirectoryDTO | SubDirectoryDTO
|
||||
): void {
|
||||
if (typeof directory.media !== 'undefined') {
|
||||
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media, directory);
|
||||
@ -229,16 +229,16 @@ export class ThumbnailGeneratorMWs {
|
||||
|
||||
private static addThInfoToAPhoto(photo: MediaDTO, directory: DirectoryPathDTO): void {
|
||||
const fullMediaPath = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
directory.path,
|
||||
directory.name,
|
||||
photo.name
|
||||
ProjectPath.ImageFolder,
|
||||
directory.path,
|
||||
directory.name,
|
||||
photo.name
|
||||
);
|
||||
for (let i = 0; i < ThumbnailGeneratorMWs.ThumbnailMapEntries.length; ++i) {
|
||||
const entry = ThumbnailGeneratorMWs.ThumbnailMapEntries[i];
|
||||
const thPath = PhotoProcessing.generateConvertedPath(
|
||||
fullMediaPath,
|
||||
entry.size
|
||||
fullMediaPath,
|
||||
entry.size
|
||||
);
|
||||
if (fs.existsSync(thPath) !== true) {
|
||||
if (typeof photo.missingThumbnails === 'undefined') {
|
||||
|
@ -13,9 +13,9 @@ const LOG_TAG = 'AuthenticationMWs';
|
||||
|
||||
export class AuthenticationMWs {
|
||||
public static async tryAuthenticate(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
req.session['user'] = {
|
||||
@ -38,9 +38,9 @@ export class AuthenticationMWs {
|
||||
}
|
||||
|
||||
public static async authenticate(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
req.session['user'] = {
|
||||
@ -67,36 +67,36 @@ export class AuthenticationMWs {
|
||||
if (typeof req.session['user'] === 'undefined') {
|
||||
res.status(401);
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Not authenticated')
|
||||
new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Not authenticated')
|
||||
);
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
public static normalizePathParam(
|
||||
paramName: string
|
||||
paramName: string
|
||||
): (req: Request, res: Response, next: NextFunction) => void {
|
||||
return function normalizePathParam(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
req.params[paramName] = path
|
||||
.normalize(req.params[paramName] || path.sep)
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
.replace(/^(\.\.[\/\\])+/, '');
|
||||
.normalize(req.params[paramName] || path.sep)
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
.replace(/^(\.\.[\/\\])+/, '');
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
||||
public static authorisePath(
|
||||
paramName: string,
|
||||
isDirectory: boolean
|
||||
paramName: string,
|
||||
isDirectory: boolean
|
||||
): (req: Request, res: Response, next: NextFunction) => void {
|
||||
return function authorisePath(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Response | void {
|
||||
let p: string = req.params[paramName];
|
||||
if (!isDirectory) {
|
||||
@ -104,7 +104,7 @@ export class AuthenticationMWs {
|
||||
}
|
||||
|
||||
if (
|
||||
!UserDTOUtils.isDirectoryPathAvailable(p, req.session['user'].permissions)
|
||||
!UserDTOUtils.isDirectoryPathAvailable(p, req.session['user'].permissions)
|
||||
) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
@ -114,12 +114,12 @@ export class AuthenticationMWs {
|
||||
}
|
||||
|
||||
public static authorise(
|
||||
role: UserRoles
|
||||
role: UserRoles
|
||||
): (req: Request, res: Response, next: NextFunction) => void {
|
||||
return function authorise(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (req.session['user'].role < role) {
|
||||
return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED));
|
||||
@ -129,36 +129,36 @@ export class AuthenticationMWs {
|
||||
}
|
||||
|
||||
public static async shareLogin(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Sharing.enabled === false) {
|
||||
return next();
|
||||
}
|
||||
// not enough parameter
|
||||
if (
|
||||
!req.query[QueryParams.gallery.sharingKey_query] &&
|
||||
!req.params[QueryParams.gallery.sharingKey_params]
|
||||
!req.query[QueryParams.gallery.sharingKey_query] &&
|
||||
!req.params[QueryParams.gallery.sharingKey_params]
|
||||
) {
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'no sharing key provided')
|
||||
new ErrorDTO(ErrorCodes.INPUT_ERROR, 'no sharing key provided')
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const password = (req.body ? req.body.password : null) || null;
|
||||
const sharingKey: string =
|
||||
(req.query[QueryParams.gallery.sharingKey_query] as string) ||
|
||||
(req.params[QueryParams.gallery.sharingKey_params] as string);
|
||||
(req.query[QueryParams.gallery.sharingKey_query] as string) ||
|
||||
(req.params[QueryParams.gallery.sharingKey_params] as string);
|
||||
const sharing = await ObjectManagers.getInstance().SharingManager.findOne(sharingKey);
|
||||
|
||||
if (
|
||||
!sharing ||
|
||||
sharing.expires < Date.now() ||
|
||||
(Config.Sharing.passwordProtected === true &&
|
||||
sharing.password &&
|
||||
!PasswordHelper.comparePassword(password, sharing.password))
|
||||
!sharing ||
|
||||
sharing.expires < Date.now() ||
|
||||
(Config.Sharing.passwordProtected === true &&
|
||||
sharing.password &&
|
||||
!PasswordHelper.comparePassword(password, sharing.password))
|
||||
) {
|
||||
Logger.warn(LOG_TAG, 'Failed login with sharing:' + sharing.sharingKey + ', bad password');
|
||||
res.status(401);
|
||||
@ -183,9 +183,9 @@ export class AuthenticationMWs {
|
||||
}
|
||||
|
||||
public static inverseAuthenticate(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (typeof req.session['user'] !== 'undefined') {
|
||||
return next(new ErrorDTO(ErrorCodes.ALREADY_AUTHENTICATED));
|
||||
@ -194,9 +194,9 @@ export class AuthenticationMWs {
|
||||
}
|
||||
|
||||
public static async login(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void | Response> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
return res.sendStatus(404);
|
||||
@ -204,44 +204,44 @@ export class AuthenticationMWs {
|
||||
|
||||
// not enough parameter
|
||||
if (
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.loginCredential === 'undefined' ||
|
||||
typeof req.body.loginCredential.username === 'undefined' ||
|
||||
typeof req.body.loginCredential.password === 'undefined'
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.loginCredential === 'undefined' ||
|
||||
typeof req.body.loginCredential.username === 'undefined' ||
|
||||
typeof req.body.loginCredential.password === 'undefined'
|
||||
) {
|
||||
Logger.warn(LOG_TAG, 'Failed login no user or password provided');
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.INPUT_ERROR,
|
||||
'not all parameters are included for loginCredential'
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.INPUT_ERROR,
|
||||
'not all parameters are included for loginCredential'
|
||||
)
|
||||
);
|
||||
}
|
||||
try {
|
||||
// let's find the user
|
||||
const user = Utils.clone(
|
||||
await ObjectManagers.getInstance().UserManager.findOne({
|
||||
name: req.body.loginCredential.username,
|
||||
password: req.body.loginCredential.password,
|
||||
})
|
||||
await ObjectManagers.getInstance().UserManager.findOne({
|
||||
name: req.body.loginCredential.username,
|
||||
password: req.body.loginCredential.password,
|
||||
})
|
||||
);
|
||||
delete user.password;
|
||||
req.session['user'] = user;
|
||||
if (req.body.loginCredential.rememberMe) {
|
||||
req.sessionOptions.expires = new Date(
|
||||
Date.now() + Config.Server.sessionTimeout
|
||||
Date.now() + Config.Server.sessionTimeout
|
||||
);
|
||||
}
|
||||
return next();
|
||||
} catch (err) {
|
||||
Logger.warn(LOG_TAG, 'Failed login for user:' + req.body.loginCredential.username
|
||||
+ ', bad password');
|
||||
+ ', bad password');
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.CREDENTIAL_NOT_FOUND,
|
||||
'credentials not found during login',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.CREDENTIAL_NOT_FOUND,
|
||||
'credentials not found during login',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -253,21 +253,21 @@ export class AuthenticationMWs {
|
||||
|
||||
private static async getSharingUser(req: Request): Promise<UserDTO> {
|
||||
if (
|
||||
Config.Sharing.enabled === true &&
|
||||
(!!req.query[QueryParams.gallery.sharingKey_query] ||
|
||||
!!req.params[QueryParams.gallery.sharingKey_params])
|
||||
Config.Sharing.enabled === true &&
|
||||
(!!req.query[QueryParams.gallery.sharingKey_query] ||
|
||||
!!req.params[QueryParams.gallery.sharingKey_params])
|
||||
) {
|
||||
const sharingKey: string =
|
||||
(req.query[QueryParams.gallery.sharingKey_query] as string) ||
|
||||
(req.params[QueryParams.gallery.sharingKey_params] as string);
|
||||
(req.query[QueryParams.gallery.sharingKey_query] as string) ||
|
||||
(req.params[QueryParams.gallery.sharingKey_params] as string);
|
||||
const sharing = await ObjectManagers.getInstance().SharingManager.findOne(sharingKey);
|
||||
if (!sharing || sharing.expires < Date.now()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
Config.Sharing.passwordProtected === true &&
|
||||
sharing.password
|
||||
Config.Sharing.passwordProtected === true &&
|
||||
sharing.password
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
@ -6,23 +6,23 @@ import {Config} from '../../../common/config/private/Config';
|
||||
|
||||
export class UserMWs {
|
||||
public static async createUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
|
||||
}
|
||||
if (
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.newUser === 'undefined'
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.newUser === 'undefined'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
await ObjectManagers.getInstance().UserManager.createUser(
|
||||
req.body.newUser
|
||||
req.body.newUser
|
||||
);
|
||||
return next();
|
||||
} catch (err) {
|
||||
@ -31,23 +31,23 @@ export class UserMWs {
|
||||
}
|
||||
|
||||
public static async deleteUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
|
||||
}
|
||||
if (
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined'
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
await ObjectManagers.getInstance().UserManager.deleteUser(
|
||||
parseInt(req.params.id, 10)
|
||||
parseInt(req.params.id, 10)
|
||||
);
|
||||
return next();
|
||||
} catch (err) {
|
||||
@ -56,26 +56,26 @@ export class UserMWs {
|
||||
}
|
||||
|
||||
public static async changeRole(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
|
||||
}
|
||||
if (
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined' ||
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.newRole === 'undefined'
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined' ||
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.newRole === 'undefined'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
await ObjectManagers.getInstance().UserManager.changeRole(
|
||||
parseInt(req.params.id, 10),
|
||||
req.body.newRole
|
||||
parseInt(req.params.id, 10),
|
||||
req.body.newRole
|
||||
);
|
||||
return next();
|
||||
} catch (err) {
|
||||
@ -84,9 +84,9 @@ export class UserMWs {
|
||||
}
|
||||
|
||||
public static async listUsers(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { MoreThanOrEqual } from 'typeorm';
|
||||
import { ErrorCodes, ErrorDTO } from '../../../common/entities/Error';
|
||||
import { UserRoles } from '../../../common/entities/UserDTO';
|
||||
import { ObjectManagers } from '../../model/ObjectManagers';
|
||||
import {NextFunction, Request, Response} from 'express';
|
||||
import {MoreThanOrEqual} from 'typeorm';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
|
||||
import {UserRoles} from '../../../common/entities/UserDTO';
|
||||
import {ObjectManagers} from '../../model/ObjectManagers';
|
||||
|
||||
export class UserRequestConstrainsMWs {
|
||||
public static forceSelfRequest(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined'
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
@ -24,13 +24,13 @@ export class UserRequestConstrainsMWs {
|
||||
}
|
||||
|
||||
public static notSelfRequest(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined'
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
@ -43,13 +43,13 @@ export class UserRequestConstrainsMWs {
|
||||
}
|
||||
|
||||
public static async notSelfRequestOr2Admins(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined'
|
||||
typeof req.params === 'undefined' ||
|
||||
typeof req.params.id === 'undefined'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
@ -1,13 +1,8 @@
|
||||
import {
|
||||
ParentDirectoryDTO,
|
||||
} from '../../common/entities/DirectoryDTO';
|
||||
import {ParentDirectoryDTO,} from '../../common/entities/DirectoryDTO';
|
||||
import {Logger} from '../Logger';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
import {DiskManagerTH} from './threading/ThreadPool';
|
||||
import {
|
||||
DirectoryScanSettings,
|
||||
DiskMangerWorker,
|
||||
} from './threading/DiskMangerWorker';
|
||||
import {DirectoryScanSettings, DiskMangerWorker,} from './threading/DiskMangerWorker';
|
||||
import {FileDTO} from '../../common/entities/FileDTO';
|
||||
|
||||
const LOG_TAG = '[DiskManager]';
|
||||
@ -25,16 +20,16 @@ export class DiskManager {
|
||||
* List all files in a folder as fast as possible
|
||||
*/
|
||||
public static async scanDirectoryNoMetadata(
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
): Promise<ParentDirectoryDTO<FileDTO>> {
|
||||
settings.noMetadata = true;
|
||||
return this.scanDirectory(relativeDirectoryName, settings);
|
||||
}
|
||||
|
||||
public static async scanDirectory(
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
): Promise<ParentDirectoryDTO> {
|
||||
Logger.silly(LOG_TAG, 'scanning directory:', relativeDirectoryName);
|
||||
|
||||
@ -42,13 +37,13 @@ export class DiskManager {
|
||||
|
||||
if (Config.Server.Threading.enabled === true) {
|
||||
directory = await DiskManager.threadPool.execute(
|
||||
relativeDirectoryName,
|
||||
settings
|
||||
relativeDirectoryName,
|
||||
settings
|
||||
);
|
||||
} else {
|
||||
directory = (await DiskMangerWorker.scanDirectory(
|
||||
relativeDirectoryName,
|
||||
settings
|
||||
relativeDirectoryName,
|
||||
settings
|
||||
)) as ParentDirectoryDTO;
|
||||
}
|
||||
return directory;
|
||||
|
@ -8,12 +8,12 @@ export class Localizations {
|
||||
public static init(): void {
|
||||
const notLanguage = ['assets'];
|
||||
const dirCont = fs
|
||||
.readdirSync(ProjectPath.FrontendFolder)
|
||||
.filter((f): boolean =>
|
||||
fs.statSync(path.join(ProjectPath.FrontendFolder, f)).isDirectory()
|
||||
);
|
||||
.readdirSync(ProjectPath.FrontendFolder)
|
||||
.filter((f): boolean =>
|
||||
fs.statSync(path.join(ProjectPath.FrontendFolder, f)).isDirectory()
|
||||
);
|
||||
Config.Server.languages = dirCont.filter(
|
||||
(d): boolean => notLanguage.indexOf(d) === -1
|
||||
(d): boolean => notLanguage.indexOf(d) === -1
|
||||
);
|
||||
Config.Server.languages.sort();
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
import {
|
||||
NotificationDTO,
|
||||
NotificationType,
|
||||
} from '../../common/entities/NotificationDTO';
|
||||
import { Request } from 'express';
|
||||
import {NotificationDTO, NotificationType,} from '../../common/entities/NotificationDTO';
|
||||
import {Request} from 'express';
|
||||
|
||||
export class NotificationManager {
|
||||
public static notifications: NotificationDTO[] = [];
|
||||
@ -10,7 +7,7 @@ export class NotificationManager {
|
||||
{
|
||||
type: NotificationType.info,
|
||||
message:
|
||||
'There are unhandled server notification. Login as Administrator to handle them.',
|
||||
'There are unhandled server notification. Login as Administrator to handle them.',
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -179,8 +179,8 @@ export class ObjectManagers {
|
||||
public static async reset(): Promise<void> {
|
||||
Logger.silly(LOG_TAG, 'Object manager reset begin');
|
||||
if (
|
||||
ObjectManagers.getInstance().IndexingManager &&
|
||||
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress
|
||||
ObjectManagers.getInstance().IndexingManager &&
|
||||
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress
|
||||
) {
|
||||
await ObjectManagers.getInstance().IndexingManager.SavingReady;
|
||||
}
|
||||
@ -214,7 +214,7 @@ export class ObjectManagers {
|
||||
}
|
||||
|
||||
public async onDataChange(
|
||||
changedDir: ParentDirectoryDTO = null
|
||||
changedDir: ParentDirectoryDTO = null
|
||||
): Promise<void> {
|
||||
await this.VersionManager.onNewDataVersion();
|
||||
|
||||
|
@ -7,13 +7,14 @@ export class PasswordHelper {
|
||||
}
|
||||
|
||||
public static comparePassword(
|
||||
password: string,
|
||||
encryptedPassword: string
|
||||
password: string,
|
||||
encryptedPassword: string
|
||||
): boolean {
|
||||
try {
|
||||
return bcrypt.compareSync(password, encryptedPassword);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -19,27 +19,27 @@ export class AlbumManager implements IObjectManager {
|
||||
private static async updateAlbum(album: SavedSearchEntity): Promise<void> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const cover =
|
||||
await ObjectManagers.getInstance().CoverManager.getAlbumCover(album);
|
||||
await ObjectManagers.getInstance().CoverManager.getAlbumCover(album);
|
||||
const count = await
|
||||
ObjectManagers.getInstance().SearchManager.getCount((album as SavedSearchDTO).searchQuery);
|
||||
ObjectManagers.getInstance().SearchManager.getCount((album as SavedSearchDTO).searchQuery);
|
||||
|
||||
await connection
|
||||
.createQueryBuilder()
|
||||
.update(AlbumBaseEntity)
|
||||
.set({cover: cover, count})
|
||||
.where('id = :id', {id: album.id})
|
||||
.execute();
|
||||
.createQueryBuilder()
|
||||
.update(AlbumBaseEntity)
|
||||
.set({cover: cover, count})
|
||||
.where('id = :id', {id: album.id})
|
||||
.execute();
|
||||
}
|
||||
|
||||
public async addIfNotExistSavedSearch(
|
||||
name: string,
|
||||
searchQuery: SearchQueryDTO,
|
||||
lockedAlbum: boolean
|
||||
name: string,
|
||||
searchQuery: SearchQueryDTO,
|
||||
lockedAlbum: boolean
|
||||
): Promise<void> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const album = await connection
|
||||
.getRepository(SavedSearchEntity)
|
||||
.findOneBy({name, searchQuery});
|
||||
.getRepository(SavedSearchEntity)
|
||||
.findOneBy({name, searchQuery});
|
||||
if (album) {
|
||||
return;
|
||||
}
|
||||
@ -47,14 +47,14 @@ export class AlbumManager implements IObjectManager {
|
||||
}
|
||||
|
||||
public async addSavedSearch(
|
||||
name: string,
|
||||
searchQuery: SearchQueryDTO,
|
||||
lockedAlbum?: boolean
|
||||
name: string,
|
||||
searchQuery: SearchQueryDTO,
|
||||
lockedAlbum?: boolean
|
||||
): Promise<void> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const a = await connection
|
||||
.getRepository(SavedSearchEntity)
|
||||
.save({name, searchQuery, locked: lockedAlbum});
|
||||
.getRepository(SavedSearchEntity)
|
||||
.save({name, searchQuery, locked: lockedAlbum});
|
||||
await AlbumManager.updateAlbum(a);
|
||||
}
|
||||
|
||||
@ -62,28 +62,28 @@ export class AlbumManager implements IObjectManager {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
|
||||
if (
|
||||
(await connection
|
||||
.getRepository(AlbumBaseEntity)
|
||||
.countBy({id, locked: false})) !== 1
|
||||
(await connection
|
||||
.getRepository(AlbumBaseEntity)
|
||||
.countBy({id, locked: false})) !== 1
|
||||
) {
|
||||
throw new Error('Could not delete album, id:' + id);
|
||||
}
|
||||
|
||||
await connection
|
||||
.getRepository(AlbumBaseEntity)
|
||||
.delete({id, locked: false});
|
||||
.getRepository(AlbumBaseEntity)
|
||||
.delete({id, locked: false});
|
||||
}
|
||||
|
||||
public async getAlbums(): Promise<AlbumBaseDTO[]> {
|
||||
await this.updateAlbums();
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection
|
||||
.getRepository(AlbumBaseEntity)
|
||||
.createQueryBuilder('album')
|
||||
.leftJoin('album.cover', 'cover')
|
||||
.leftJoin('cover.directory', 'directory')
|
||||
.select(['album', 'cover.name', 'directory.name', 'directory.path'])
|
||||
.getMany();
|
||||
.getRepository(AlbumBaseEntity)
|
||||
.createQueryBuilder('album')
|
||||
.leftJoin('album.cover', 'cover')
|
||||
.leftJoin('cover.directory', 'directory')
|
||||
.select(['album', 'cover.name', 'directory.name', 'directory.path'])
|
||||
.getMany();
|
||||
}
|
||||
|
||||
public async onNewDataVersion(): Promise<void> {
|
||||
@ -114,9 +114,9 @@ export class AlbumManager implements IObjectManager {
|
||||
async deleteAll() {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
await connection
|
||||
.getRepository(AlbumBaseEntity)
|
||||
.createQueryBuilder('album')
|
||||
.delete()
|
||||
.execute();
|
||||
.getRepository(AlbumBaseEntity)
|
||||
.createQueryBuilder('album')
|
||||
.delete()
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
@ -29,21 +29,21 @@ export class CoverManager implements IObjectManager {
|
||||
public async resetCovers(): Promise<void> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
await connection
|
||||
.createQueryBuilder()
|
||||
.update(DirectoryEntity)
|
||||
.set({validCover: false})
|
||||
.execute();
|
||||
.createQueryBuilder()
|
||||
.update(DirectoryEntity)
|
||||
.set({validCover: false})
|
||||
.execute();
|
||||
}
|
||||
|
||||
public async onNewDataVersion(changedDir: ParentDirectoryDTO): Promise<void> {
|
||||
// Invalidating Album cover
|
||||
let fullPath = DiskMangerWorker.normalizeDirPath(
|
||||
path.join(changedDir.path, changedDir.name)
|
||||
path.join(changedDir.path, changedDir.name)
|
||||
);
|
||||
const query = (await SQLConnection.getConnection())
|
||||
.createQueryBuilder()
|
||||
.update(DirectoryEntity)
|
||||
.set({validCover: false});
|
||||
.createQueryBuilder()
|
||||
.update(DirectoryEntity)
|
||||
.set({validCover: false});
|
||||
|
||||
let i = 0;
|
||||
const root = DiskMangerWorker.pathFromRelativeDirName('.');
|
||||
@ -53,25 +53,25 @@ export class CoverManager implements IObjectManager {
|
||||
fullPath = parentPath;
|
||||
++i;
|
||||
query.orWhere(
|
||||
new Brackets((q: WhereExpression) => {
|
||||
const param: { [key: string]: string } = {};
|
||||
param['name' + i] = name;
|
||||
param['path' + i] = parentPath;
|
||||
q.where(`path = :path${i}`, param);
|
||||
q.andWhere(`name = :name${i}`, param);
|
||||
})
|
||||
new Brackets((q: WhereExpression) => {
|
||||
const param: { [key: string]: string } = {};
|
||||
param['name' + i] = name;
|
||||
param['path' + i] = parentPath;
|
||||
q.where(`path = :path${i}`, param);
|
||||
q.andWhere(`name = :name${i}`, param);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
++i;
|
||||
query.orWhere(
|
||||
new Brackets((q: WhereExpression) => {
|
||||
const param: { [key: string]: string } = {};
|
||||
param['name' + i] = DiskMangerWorker.dirName('.');
|
||||
param['path' + i] = DiskMangerWorker.pathFromRelativeDirName('.');
|
||||
q.where(`path = :path${i}`, param);
|
||||
q.andWhere(`name = :name${i}`, param);
|
||||
})
|
||||
new Brackets((q: WhereExpression) => {
|
||||
const param: { [key: string]: string } = {};
|
||||
param['name' + i] = DiskMangerWorker.dirName('.');
|
||||
param['path' + i] = DiskMangerWorker.pathFromRelativeDirName('.');
|
||||
q.where(`path = :path${i}`, param);
|
||||
q.andWhere(`name = :name${i}`, param);
|
||||
})
|
||||
);
|
||||
|
||||
await query.execute();
|
||||
@ -81,34 +81,34 @@ export class CoverManager implements IObjectManager {
|
||||
searchQuery: SearchQueryDTO;
|
||||
}): Promise<CoverPhotoDTOWithID> {
|
||||
const albumQuery: Brackets = await
|
||||
ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(album.searchQuery);
|
||||
ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(album.searchQuery);
|
||||
const connection = await SQLConnection.getConnection();
|
||||
|
||||
const coverQuery = (): SelectQueryBuilder<MediaEntity> => {
|
||||
const query = connection
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.innerJoin('media.directory', 'directory')
|
||||
.select(['media.name', 'media.id', ...CoverManager.DIRECTORY_SELECT])
|
||||
.where(albumQuery);
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.innerJoin('media.directory', 'directory')
|
||||
.select(['media.name', 'media.id', ...CoverManager.DIRECTORY_SELECT])
|
||||
.where(albumQuery);
|
||||
SearchManager.setSorting(query, Config.AlbumCover.Sorting);
|
||||
return query;
|
||||
};
|
||||
let coverMedia = null;
|
||||
if (
|
||||
Config.AlbumCover.SearchQuery &&
|
||||
!Utils.equalsFilter(Config.AlbumCover.SearchQuery, {
|
||||
type: SearchQueryTypes.any_text,
|
||||
text: '',
|
||||
} as TextSearch)
|
||||
Config.AlbumCover.SearchQuery &&
|
||||
!Utils.equalsFilter(Config.AlbumCover.SearchQuery, {
|
||||
type: SearchQueryTypes.any_text,
|
||||
text: '',
|
||||
} as TextSearch)
|
||||
) {
|
||||
try {
|
||||
const coverFilterQuery = await
|
||||
ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(Config.AlbumCover.SearchQuery);
|
||||
ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(Config.AlbumCover.SearchQuery);
|
||||
coverMedia = await coverQuery()
|
||||
.andWhere(coverFilterQuery)
|
||||
.limit(1)
|
||||
.getOne();
|
||||
.andWhere(coverFilterQuery)
|
||||
.limit(1)
|
||||
.getOne();
|
||||
} catch (e) {
|
||||
Logger.error(LOG_TAG, 'Cant get album cover using:', JSON.stringify(album.searchQuery), JSON.stringify(Config.AlbumCover.SearchQuery));
|
||||
throw e;
|
||||
@ -127,15 +127,15 @@ export class CoverManager implements IObjectManager {
|
||||
}
|
||||
|
||||
public async getPartialDirsWithoutCovers(): Promise<
|
||||
{ id: number; name: string; path: string }[]
|
||||
{ id: number; name: string; path: string }[]
|
||||
> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.validCover = :validCover', {validCover: 0}) // 0 === false
|
||||
.select(['name', 'id', 'path'])
|
||||
.getRawMany();
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.validCover = :validCover', {validCover: 0}) // 0 === false
|
||||
.select(['name', 'id', 'path'])
|
||||
.getRawMany();
|
||||
}
|
||||
|
||||
public async setAndGetCoverForDirectory(dir: {
|
||||
@ -146,31 +146,31 @@ export class CoverManager implements IObjectManager {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const coverQuery = (): SelectQueryBuilder<MediaEntity> => {
|
||||
const query = connection
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.innerJoin('media.directory', 'directory')
|
||||
.select(['media.name', 'media.id', ...CoverManager.DIRECTORY_SELECT])
|
||||
.where(
|
||||
new Brackets((q: WhereExpression) => {
|
||||
q.where('media.directory = :dir', {
|
||||
dir: dir.id,
|
||||
});
|
||||
if (Config.Database.type === DatabaseType.mysql) {
|
||||
q.orWhere('directory.path like :path || \'%\'', {
|
||||
path: DiskMangerWorker.pathFromParent(dir),
|
||||
});
|
||||
} else {
|
||||
q.orWhere('directory.path GLOB :path', {
|
||||
path: DiskMangerWorker.pathFromParent(dir) + '*',
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.innerJoin('media.directory', 'directory')
|
||||
.select(['media.name', 'media.id', ...CoverManager.DIRECTORY_SELECT])
|
||||
.where(
|
||||
new Brackets((q: WhereExpression) => {
|
||||
q.where('media.directory = :dir', {
|
||||
dir: dir.id,
|
||||
});
|
||||
if (Config.Database.type === DatabaseType.mysql) {
|
||||
q.orWhere('directory.path like :path || \'%\'', {
|
||||
path: DiskMangerWorker.pathFromParent(dir),
|
||||
});
|
||||
} else {
|
||||
q.orWhere('directory.path GLOB :path', {
|
||||
path: DiskMangerWorker.pathFromParent(dir) + '*',
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
// Select from the directory if any otherwise from any subdirectories.
|
||||
// (There is no priority between subdirectories)
|
||||
query.orderBy(
|
||||
`CASE WHEN directory.id = ${dir.id} THEN 0 ELSE 1 END`,
|
||||
'ASC'
|
||||
`CASE WHEN directory.id = ${dir.id} THEN 0 ELSE 1 END`,
|
||||
'ASC'
|
||||
);
|
||||
|
||||
SearchManager.setSorting(query, Config.AlbumCover.Sorting);
|
||||
@ -179,18 +179,18 @@ export class CoverManager implements IObjectManager {
|
||||
|
||||
let coverMedia: CoverPhotoDTOWithID = null;
|
||||
if (
|
||||
Config.AlbumCover.SearchQuery &&
|
||||
!Utils.equalsFilter(Config.AlbumCover.SearchQuery, {
|
||||
type: SearchQueryTypes.any_text,
|
||||
text: '',
|
||||
} as TextSearch)
|
||||
Config.AlbumCover.SearchQuery &&
|
||||
!Utils.equalsFilter(Config.AlbumCover.SearchQuery, {
|
||||
type: SearchQueryTypes.any_text,
|
||||
text: '',
|
||||
} as TextSearch)
|
||||
) {
|
||||
coverMedia = await coverQuery()
|
||||
.andWhere(
|
||||
await ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(Config.AlbumCover.SearchQuery)
|
||||
)
|
||||
.limit(1)
|
||||
.getOne();
|
||||
.andWhere(
|
||||
await ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(Config.AlbumCover.SearchQuery)
|
||||
)
|
||||
.limit(1)
|
||||
.getOne();
|
||||
}
|
||||
|
||||
if (!coverMedia) {
|
||||
@ -199,13 +199,13 @@ export class CoverManager implements IObjectManager {
|
||||
|
||||
// set validCover bit to true even if there is no cover (to prevent future updates)
|
||||
await connection
|
||||
.createQueryBuilder()
|
||||
.update(DirectoryEntity)
|
||||
.set({cover: coverMedia, validCover: true})
|
||||
.where('id = :dir', {
|
||||
dir: dir.id,
|
||||
})
|
||||
.execute();
|
||||
.createQueryBuilder()
|
||||
.update(DirectoryEntity)
|
||||
.set({cover: coverMedia, validCover: true})
|
||||
.where('id = :dir', {
|
||||
dir: dir.id,
|
||||
})
|
||||
.execute();
|
||||
|
||||
return coverMedia || null;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export class GalleryManager {
|
||||
parent: string;
|
||||
} {
|
||||
relativeDirectoryName = DiskMangerWorker.normalizeDirPath(
|
||||
relativeDirectoryName
|
||||
relativeDirectoryName
|
||||
);
|
||||
return {
|
||||
name: path.basename(relativeDirectoryName),
|
||||
@ -32,12 +32,12 @@ export class GalleryManager {
|
||||
}
|
||||
|
||||
public async listDirectory(
|
||||
relativeDirectoryName: string,
|
||||
knownLastModified?: number,
|
||||
knownLastScanned?: number
|
||||
relativeDirectoryName: string,
|
||||
knownLastModified?: number,
|
||||
knownLastScanned?: number
|
||||
): Promise<ParentDirectoryDTO> {
|
||||
const directoryPath = GalleryManager.parseRelativeDirePath(
|
||||
relativeDirectoryName
|
||||
relativeDirectoryName
|
||||
);
|
||||
|
||||
const connection = await SQLConnection.getConnection();
|
||||
@ -48,35 +48,35 @@ export class GalleryManager {
|
||||
// Return as soon as possible without touching the original data source (hdd)
|
||||
// See https://github.com/bpatrik/pigallery2/issues/613
|
||||
if (
|
||||
Config.Indexing.reIndexingSensitivity ===
|
||||
ReIndexingSensitivity.never
|
||||
Config.Indexing.reIndexingSensitivity ===
|
||||
ReIndexingSensitivity.never
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stat = fs.statSync(
|
||||
path.join(ProjectPath.ImageFolder, relativeDirectoryName)
|
||||
path.join(ProjectPath.ImageFolder, relativeDirectoryName)
|
||||
);
|
||||
const lastModified = DiskMangerWorker.calcLastModified(stat);
|
||||
|
||||
// If it seems that the content did not change, do not work on it
|
||||
if (
|
||||
knownLastModified &&
|
||||
knownLastScanned &&
|
||||
lastModified === knownLastModified &&
|
||||
dir.lastScanned === knownLastScanned
|
||||
knownLastModified &&
|
||||
knownLastScanned &&
|
||||
lastModified === knownLastModified &&
|
||||
dir.lastScanned === knownLastScanned
|
||||
) {
|
||||
if (
|
||||
Config.Indexing.reIndexingSensitivity ===
|
||||
ReIndexingSensitivity.low
|
||||
Config.Indexing.reIndexingSensitivity ===
|
||||
ReIndexingSensitivity.low
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
Date.now() - dir.lastScanned <=
|
||||
Config.Indexing.cachedFolderTimeout &&
|
||||
Config.Indexing.reIndexingSensitivity ===
|
||||
ReIndexingSensitivity.medium
|
||||
Date.now() - dir.lastScanned <=
|
||||
Config.Indexing.cachedFolderTimeout &&
|
||||
Config.Indexing.reIndexingSensitivity ===
|
||||
ReIndexingSensitivity.medium
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@ -84,16 +84,16 @@ export class GalleryManager {
|
||||
|
||||
if (dir.lastModified !== lastModified) {
|
||||
Logger.silly(
|
||||
LOG_TAG,
|
||||
'Reindexing reason: lastModified mismatch: known: ' +
|
||||
dir.lastModified +
|
||||
', current:' +
|
||||
lastModified
|
||||
LOG_TAG,
|
||||
'Reindexing reason: lastModified mismatch: known: ' +
|
||||
dir.lastModified +
|
||||
', current:' +
|
||||
lastModified
|
||||
);
|
||||
const ret =
|
||||
await ObjectManagers.getInstance().IndexingManager.indexDirectory(
|
||||
relativeDirectoryName
|
||||
);
|
||||
await ObjectManagers.getInstance().IndexingManager.indexDirectory(
|
||||
relativeDirectoryName
|
||||
);
|
||||
for (const subDir of ret.directories) {
|
||||
if (!subDir.cover) {
|
||||
// if subdirectories do not have photos, so cannot show a cover, try getting one from DB
|
||||
@ -105,25 +105,25 @@ export class GalleryManager {
|
||||
|
||||
// not indexed since a while, index it in a lazy manner
|
||||
if (
|
||||
(Date.now() - dir.lastScanned >
|
||||
Config.Indexing.cachedFolderTimeout &&
|
||||
(Date.now() - dir.lastScanned >
|
||||
Config.Indexing.cachedFolderTimeout &&
|
||||
Config.Indexing.reIndexingSensitivity >=
|
||||
ReIndexingSensitivity.medium) ||
|
||||
Config.Indexing.reIndexingSensitivity >=
|
||||
ReIndexingSensitivity.medium) ||
|
||||
Config.Indexing.reIndexingSensitivity >=
|
||||
ReIndexingSensitivity.high
|
||||
ReIndexingSensitivity.high
|
||||
) {
|
||||
// on the fly reindexing
|
||||
|
||||
Logger.silly(
|
||||
LOG_TAG,
|
||||
'lazy reindexing reason: cache timeout: lastScanned: ' +
|
||||
(Date.now() - dir.lastScanned) +
|
||||
'ms ago, cachedFolderTimeout:' +
|
||||
Config.Indexing.cachedFolderTimeout
|
||||
LOG_TAG,
|
||||
'lazy reindexing reason: cache timeout: lastScanned: ' +
|
||||
(Date.now() - dir.lastScanned) +
|
||||
'ms ago, cachedFolderTimeout:' +
|
||||
Config.Indexing.cachedFolderTimeout
|
||||
);
|
||||
ObjectManagers.getInstance()
|
||||
.IndexingManager.indexDirectory(relativeDirectoryName)
|
||||
.catch(console.error);
|
||||
.IndexingManager.indexDirectory(relativeDirectoryName)
|
||||
.catch(console.error);
|
||||
}
|
||||
return await this.getParentDirFromId(connection, dir.id);
|
||||
}
|
||||
@ -131,42 +131,42 @@ export class GalleryManager {
|
||||
// never scanned (deep indexed), do it and return with it
|
||||
Logger.silly(LOG_TAG, 'Reindexing reason: never scanned');
|
||||
return ObjectManagers.getInstance().IndexingManager.indexDirectory(
|
||||
relativeDirectoryName
|
||||
relativeDirectoryName
|
||||
);
|
||||
}
|
||||
|
||||
async countDirectories(): Promise<number> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.getCount();
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.getCount();
|
||||
}
|
||||
|
||||
async countMediaSize(): Promise<number> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const {sum} = await connection
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.select('SUM(media.metadata.fileSize)', 'sum')
|
||||
.getRawOne();
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.select('SUM(media.metadata.fileSize)', 'sum')
|
||||
.getRawOne();
|
||||
return sum || 0;
|
||||
}
|
||||
|
||||
async countPhotos(): Promise<number> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection
|
||||
.getRepository(PhotoEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.getCount();
|
||||
.getRepository(PhotoEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.getCount();
|
||||
}
|
||||
|
||||
async countVideos(): Promise<number> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection
|
||||
.getRepository(VideoEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.getCount();
|
||||
.getRepository(VideoEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.getCount();
|
||||
}
|
||||
|
||||
public async getPossibleDuplicates(): Promise<DuplicatesDTO[]> {
|
||||
@ -174,31 +174,31 @@ export class GalleryManager {
|
||||
const mediaRepository = connection.getRepository(MediaEntity);
|
||||
|
||||
let duplicates = await mediaRepository
|
||||
.createQueryBuilder('media')
|
||||
.innerJoin(
|
||||
(query) =>
|
||||
query
|
||||
.from(MediaEntity, 'innerMedia')
|
||||
.select([
|
||||
'innerMedia.name as name',
|
||||
'innerMedia.metadata.fileSize as fileSize',
|
||||
'count(*)',
|
||||
])
|
||||
.groupBy('innerMedia.name, innerMedia.metadata.fileSize')
|
||||
.having('count(*)>1'),
|
||||
'innerMedia',
|
||||
'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize'
|
||||
)
|
||||
.innerJoinAndSelect('media.directory', 'directory')
|
||||
.orderBy('media.name, media.metadata.fileSize')
|
||||
.limit(Config.Duplicates.listingLimit)
|
||||
.getMany();
|
||||
.createQueryBuilder('media')
|
||||
.innerJoin(
|
||||
(query) =>
|
||||
query
|
||||
.from(MediaEntity, 'innerMedia')
|
||||
.select([
|
||||
'innerMedia.name as name',
|
||||
'innerMedia.metadata.fileSize as fileSize',
|
||||
'count(*)',
|
||||
])
|
||||
.groupBy('innerMedia.name, innerMedia.metadata.fileSize')
|
||||
.having('count(*)>1'),
|
||||
'innerMedia',
|
||||
'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize'
|
||||
)
|
||||
.innerJoinAndSelect('media.directory', 'directory')
|
||||
.orderBy('media.name, media.metadata.fileSize')
|
||||
.limit(Config.Duplicates.listingLimit)
|
||||
.getMany();
|
||||
|
||||
const duplicateParis: DuplicatesDTO[] = [];
|
||||
const processDuplicates = (
|
||||
duplicateList: MediaEntity[],
|
||||
equalFn: (a: MediaEntity, b: MediaEntity) => boolean,
|
||||
checkDuplicates = false
|
||||
duplicateList: MediaEntity[],
|
||||
equalFn: (a: MediaEntity, b: MediaEntity) => boolean,
|
||||
checkDuplicates = false
|
||||
): void => {
|
||||
let i = duplicateList.length - 1;
|
||||
while (i >= 0) {
|
||||
@ -216,15 +216,15 @@ export class GalleryManager {
|
||||
if (checkDuplicates) {
|
||||
// ad to group if one already existed
|
||||
const foundDuplicates = duplicateParis.find(
|
||||
(dp): boolean =>
|
||||
!!dp.media.find(
|
||||
(m): boolean => !!list.find((lm): boolean => lm.id === m.id)
|
||||
)
|
||||
(dp): boolean =>
|
||||
!!dp.media.find(
|
||||
(m): boolean => !!list.find((lm): boolean => lm.id === m.id)
|
||||
)
|
||||
);
|
||||
if (foundDuplicates) {
|
||||
list.forEach((lm): void => {
|
||||
if (
|
||||
foundDuplicates.media.find((m): boolean => m.id === lm.id)
|
||||
foundDuplicates.media.find((m): boolean => m.id === lm.id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -239,40 +239,40 @@ export class GalleryManager {
|
||||
};
|
||||
|
||||
processDuplicates(
|
||||
duplicates,
|
||||
(a, b): boolean =>
|
||||
a.name === b.name && a.metadata.fileSize === b.metadata.fileSize
|
||||
duplicates,
|
||||
(a, b): boolean =>
|
||||
a.name === b.name && a.metadata.fileSize === b.metadata.fileSize
|
||||
);
|
||||
|
||||
duplicates = await mediaRepository
|
||||
.createQueryBuilder('media')
|
||||
.innerJoin(
|
||||
(query) =>
|
||||
query
|
||||
.from(MediaEntity, 'innerMedia')
|
||||
.select([
|
||||
'innerMedia.metadata.creationDate as creationDate',
|
||||
'innerMedia.metadata.fileSize as fileSize',
|
||||
'count(*)',
|
||||
])
|
||||
.groupBy(
|
||||
'innerMedia.metadata.creationDate, innerMedia.metadata.fileSize'
|
||||
)
|
||||
.having('count(*)>1'),
|
||||
'innerMedia',
|
||||
'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize'
|
||||
)
|
||||
.innerJoinAndSelect('media.directory', 'directory')
|
||||
.orderBy('media.metadata.creationDate, media.metadata.fileSize')
|
||||
.limit(Config.Duplicates.listingLimit)
|
||||
.getMany();
|
||||
.createQueryBuilder('media')
|
||||
.innerJoin(
|
||||
(query) =>
|
||||
query
|
||||
.from(MediaEntity, 'innerMedia')
|
||||
.select([
|
||||
'innerMedia.metadata.creationDate as creationDate',
|
||||
'innerMedia.metadata.fileSize as fileSize',
|
||||
'count(*)',
|
||||
])
|
||||
.groupBy(
|
||||
'innerMedia.metadata.creationDate, innerMedia.metadata.fileSize'
|
||||
)
|
||||
.having('count(*)>1'),
|
||||
'innerMedia',
|
||||
'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize'
|
||||
)
|
||||
.innerJoinAndSelect('media.directory', 'directory')
|
||||
.orderBy('media.metadata.creationDate, media.metadata.fileSize')
|
||||
.limit(Config.Duplicates.listingLimit)
|
||||
.getMany();
|
||||
|
||||
processDuplicates(
|
||||
duplicates,
|
||||
(a, b): boolean =>
|
||||
a.metadata.creationDate === b.metadata.creationDate &&
|
||||
a.metadata.fileSize === b.metadata.fileSize,
|
||||
true
|
||||
duplicates,
|
||||
(a, b): boolean =>
|
||||
a.metadata.creationDate === b.metadata.creationDate &&
|
||||
a.metadata.fileSize === b.metadata.fileSize,
|
||||
true
|
||||
);
|
||||
|
||||
return duplicateParis;
|
||||
@ -282,20 +282,20 @@ export class GalleryManager {
|
||||
* Returns with the directories only, does not include media or metafiles
|
||||
*/
|
||||
public async selectDirStructure(
|
||||
relativeDirectoryName: string
|
||||
relativeDirectoryName: string
|
||||
): Promise<DirectoryEntity> {
|
||||
const directoryPath = GalleryManager.parseRelativeDirePath(
|
||||
relativeDirectoryName
|
||||
relativeDirectoryName
|
||||
);
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const query = connection
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.name = :name AND directory.path = :path', {
|
||||
name: directoryPath.name,
|
||||
path: directoryPath.parent,
|
||||
})
|
||||
.leftJoinAndSelect('directory.directories', 'directories');
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.name = :name AND directory.path = :path', {
|
||||
name: directoryPath.name,
|
||||
path: directoryPath.parent,
|
||||
})
|
||||
.leftJoinAndSelect('directory.directories', 'directories');
|
||||
|
||||
return await query.getOne();
|
||||
}
|
||||
@ -304,14 +304,14 @@ export class GalleryManager {
|
||||
* Sets cover for the directory and caches it in the DB
|
||||
*/
|
||||
public async fillCoverForSubDir(
|
||||
connection: Connection,
|
||||
dir: SubDirectoryDTO
|
||||
connection: Connection,
|
||||
dir: SubDirectoryDTO
|
||||
): Promise<void> {
|
||||
if (!dir.validCover) {
|
||||
dir.cover =
|
||||
await ObjectManagers.getInstance().CoverManager.setAndGetCoverForDirectory(
|
||||
dir
|
||||
);
|
||||
await ObjectManagers.getInstance().CoverManager.setAndGetCoverForDirectory(
|
||||
dir
|
||||
);
|
||||
}
|
||||
|
||||
dir.media = [];
|
||||
@ -324,48 +324,48 @@ export class GalleryManager {
|
||||
lastModified: number
|
||||
}> {
|
||||
return await connection
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.name = :name AND directory.path = :path', {
|
||||
name: name,
|
||||
path: path,
|
||||
})
|
||||
.select([
|
||||
'directory.id',
|
||||
'directory.lastScanned',
|
||||
'directory.lastModified',
|
||||
]).getOne();
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.name = :name AND directory.path = :path', {
|
||||
name: name,
|
||||
path: path,
|
||||
})
|
||||
.select([
|
||||
'directory.id',
|
||||
'directory.lastScanned',
|
||||
'directory.lastModified',
|
||||
]).getOne();
|
||||
}
|
||||
|
||||
protected async getParentDirFromId(
|
||||
connection: Connection,
|
||||
partialDirId: number
|
||||
connection: Connection,
|
||||
partialDirId: number
|
||||
): Promise<ParentDirectoryDTO> {
|
||||
const query = connection
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.id = :id', {
|
||||
id: partialDirId
|
||||
})
|
||||
.leftJoinAndSelect('directory.directories', 'directories')
|
||||
.leftJoinAndSelect('directory.media', 'media')
|
||||
.leftJoinAndSelect('directories.cover', 'cover')
|
||||
.leftJoinAndSelect('cover.directory', 'coverDirectory')
|
||||
.select([
|
||||
'directory',
|
||||
'directories',
|
||||
'media',
|
||||
'cover.name',
|
||||
'coverDirectory.name',
|
||||
'coverDirectory.path',
|
||||
]);
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.id = :id', {
|
||||
id: partialDirId
|
||||
})
|
||||
.leftJoinAndSelect('directory.directories', 'directories')
|
||||
.leftJoinAndSelect('directory.media', 'media')
|
||||
.leftJoinAndSelect('directories.cover', 'cover')
|
||||
.leftJoinAndSelect('cover.directory', 'coverDirectory')
|
||||
.select([
|
||||
'directory',
|
||||
'directories',
|
||||
'media',
|
||||
'cover.name',
|
||||
'coverDirectory.name',
|
||||
'coverDirectory.path',
|
||||
]);
|
||||
|
||||
// TODO: do better filtering
|
||||
// NOTE: it should not cause an issue as it also do not save to the DB
|
||||
if (
|
||||
Config.MetaFile.gpx === true ||
|
||||
Config.MetaFile.pg2conf === true ||
|
||||
Config.MetaFile.markdown === true
|
||||
Config.MetaFile.gpx === true ||
|
||||
Config.MetaFile.pg2conf === true ||
|
||||
Config.MetaFile.markdown === true
|
||||
) {
|
||||
query.leftJoinAndSelect('directory.metaFile', 'metaFile');
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ParentDirectoryDTO } from '../../../common/entities/DirectoryDTO';
|
||||
import {ParentDirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
|
||||
export interface IObjectManager {
|
||||
onNewDataVersion?: (changedDir?: ParentDirectoryDTO) => Promise<void>;
|
||||
|
@ -5,7 +5,7 @@ import {DiskManager} from '../DiskManger';
|
||||
import {PhotoEntity, PhotoMetadataEntity} from './enitites/PhotoEntity';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
import {PhotoMetadata,} from '../../../common/entities/PhotoDTO';
|
||||
import {Connection, Repository} from 'typeorm';
|
||||
import {Connection, ObjectLiteral, Repository} from 'typeorm';
|
||||
import {MediaEntity} from './enitites/MediaEntity';
|
||||
import {MediaDTO, MediaDTOUtils} from '../../../common/entities/MediaDTO';
|
||||
import {VideoEntity} from './enitites/VideoEntity';
|
||||
@ -38,30 +38,30 @@ export class IndexingManager {
|
||||
}
|
||||
|
||||
private static async processServerSidePG2Conf(
|
||||
parent: DirectoryPathDTO,
|
||||
files: FileDTO[]
|
||||
parent: DirectoryPathDTO,
|
||||
files: FileDTO[]
|
||||
): Promise<void> {
|
||||
for (const f of files) {
|
||||
if (ServerPG2ConfMap[f.name] === ServerSidePG2ConfAction.SAVED_SEARCH) {
|
||||
const fullMediaPath = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
parent.path,
|
||||
parent.name,
|
||||
f.name
|
||||
ProjectPath.ImageFolder,
|
||||
parent.path,
|
||||
parent.name,
|
||||
f.name
|
||||
);
|
||||
|
||||
Logger.silly(
|
||||
LOG_TAG,
|
||||
'Saving saved-searches to DB from:',
|
||||
fullMediaPath
|
||||
LOG_TAG,
|
||||
'Saving saved-searches to DB from:',
|
||||
fullMediaPath
|
||||
);
|
||||
const savedSearches: { name: string; searchQuery: SearchQueryDTO }[] =
|
||||
JSON.parse(await fs.promises.readFile(fullMediaPath, 'utf8'));
|
||||
JSON.parse(await fs.promises.readFile(fullMediaPath, 'utf8'));
|
||||
for (const s of savedSearches) {
|
||||
await ObjectManagers.getInstance().AlbumManager.addIfNotExistSavedSearch(
|
||||
s.name,
|
||||
s.searchQuery,
|
||||
true
|
||||
s.name,
|
||||
s.searchQuery,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -73,7 +73,7 @@ export class IndexingManager {
|
||||
* does not wait for the DB to be saved
|
||||
*/
|
||||
public indexDirectory(
|
||||
relativeDirectoryName: string
|
||||
relativeDirectoryName: string
|
||||
): Promise<ParentDirectoryDTO> {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject): Promise<void> => {
|
||||
@ -87,14 +87,14 @@ export class IndexingManager {
|
||||
}
|
||||
|
||||
const scannedDirectory = await DiskManager.scanDirectory(
|
||||
relativeDirectoryName
|
||||
relativeDirectoryName
|
||||
);
|
||||
|
||||
|
||||
const dirClone = Utils.clone(scannedDirectory);
|
||||
// filter server side only config from returning
|
||||
dirClone.metaFile = dirClone.metaFile.filter(
|
||||
(m) => !ServerPG2ConfMap[m.name]
|
||||
(m) => !ServerPG2ConfMap[m.name]
|
||||
);
|
||||
|
||||
DirectoryDTOUtils.addReferences(dirClone);
|
||||
@ -104,8 +104,8 @@ export class IndexingManager {
|
||||
this.queueForSave(scannedDirectory).catch(console.error);
|
||||
} catch (error) {
|
||||
NotificationManager.warning(
|
||||
'Unknown indexing error for: ' + relativeDirectoryName,
|
||||
error.toString()
|
||||
'Unknown indexing error for: ' + relativeDirectoryName,
|
||||
error.toString()
|
||||
);
|
||||
console.error(error);
|
||||
return reject(error);
|
||||
@ -117,10 +117,10 @@ export class IndexingManager {
|
||||
Logger.info(LOG_TAG, 'Resetting DB');
|
||||
const connection = await SQLConnection.getConnection();
|
||||
await connection
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.delete()
|
||||
.execute();
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.delete()
|
||||
.execute();
|
||||
}
|
||||
|
||||
public async saveToDB(scannedDirectory: ParentDirectoryDTO): Promise<void> {
|
||||
@ -128,14 +128,14 @@ export class IndexingManager {
|
||||
try {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const serverSideConfigs = scannedDirectory.metaFile.filter(
|
||||
(m) => !!ServerPG2ConfMap[m.name]
|
||||
(m) => !!ServerPG2ConfMap[m.name]
|
||||
);
|
||||
scannedDirectory.metaFile = scannedDirectory.metaFile.filter(
|
||||
(m) => !ServerPG2ConfMap[m.name]
|
||||
(m) => !ServerPG2ConfMap[m.name]
|
||||
);
|
||||
const currentDirId: number = await this.saveParentDir(
|
||||
connection,
|
||||
scannedDirectory
|
||||
connection,
|
||||
scannedDirectory
|
||||
);
|
||||
await this.saveChildDirs(connection, currentDirId, scannedDirectory);
|
||||
await this.saveMedia(connection, currentDirId, scannedDirectory.media);
|
||||
@ -152,21 +152,21 @@ export class IndexingManager {
|
||||
* Queues up a directory to save to the DB.
|
||||
*/
|
||||
protected async queueForSave(
|
||||
scannedDirectory: ParentDirectoryDTO
|
||||
scannedDirectory: ParentDirectoryDTO
|
||||
): Promise<void> {
|
||||
// Is this dir already queued for saving?
|
||||
if (
|
||||
this.savingQueue.findIndex(
|
||||
(dir): boolean =>
|
||||
dir.name === scannedDirectory.name &&
|
||||
dir.path === scannedDirectory.path &&
|
||||
dir.lastModified === scannedDirectory.lastModified &&
|
||||
dir.lastScanned === scannedDirectory.lastScanned &&
|
||||
(dir.media || dir.media.length) ===
|
||||
(scannedDirectory.media || scannedDirectory.media.length) &&
|
||||
(dir.metaFile || dir.metaFile.length) ===
|
||||
(scannedDirectory.metaFile || scannedDirectory.metaFile.length)
|
||||
) !== -1
|
||||
this.savingQueue.findIndex(
|
||||
(dir): boolean =>
|
||||
dir.name === scannedDirectory.name &&
|
||||
dir.path === scannedDirectory.path &&
|
||||
dir.lastModified === scannedDirectory.lastModified &&
|
||||
dir.lastScanned === scannedDirectory.lastScanned &&
|
||||
(dir.media || dir.media.length) ===
|
||||
(scannedDirectory.media || scannedDirectory.media.length) &&
|
||||
(dir.metaFile || dir.metaFile.length) ===
|
||||
(scannedDirectory.metaFile || scannedDirectory.metaFile.length)
|
||||
) !== -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -193,18 +193,18 @@ export class IndexingManager {
|
||||
}
|
||||
|
||||
protected async saveParentDir(
|
||||
connection: Connection,
|
||||
scannedDirectory: ParentDirectoryDTO
|
||||
connection: Connection,
|
||||
scannedDirectory: ParentDirectoryDTO
|
||||
): Promise<number> {
|
||||
const directoryRepository = connection.getRepository(DirectoryEntity);
|
||||
|
||||
const currentDir: DirectoryEntity = await directoryRepository
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.name = :name AND directory.path = :path', {
|
||||
name: scannedDirectory.name,
|
||||
path: scannedDirectory.path,
|
||||
})
|
||||
.getOne();
|
||||
.createQueryBuilder('directory')
|
||||
.where('directory.name = :name AND directory.path = :path', {
|
||||
name: scannedDirectory.name,
|
||||
path: scannedDirectory.path,
|
||||
})
|
||||
.getOne();
|
||||
if (currentDir) {
|
||||
// Updated parent dir (if it was in the DB previously)
|
||||
currentDir.lastModified = scannedDirectory.lastModified;
|
||||
@ -216,51 +216,51 @@ export class IndexingManager {
|
||||
return currentDir.id;
|
||||
} else {
|
||||
return (
|
||||
await directoryRepository.insert({
|
||||
mediaCount: scannedDirectory.mediaCount,
|
||||
lastModified: scannedDirectory.lastModified,
|
||||
lastScanned: scannedDirectory.lastScanned,
|
||||
youngestMedia: scannedDirectory.youngestMedia,
|
||||
oldestMedia: scannedDirectory.oldestMedia,
|
||||
name: scannedDirectory.name,
|
||||
path: scannedDirectory.path,
|
||||
} as DirectoryEntity)
|
||||
await directoryRepository.insert({
|
||||
mediaCount: scannedDirectory.mediaCount,
|
||||
lastModified: scannedDirectory.lastModified,
|
||||
lastScanned: scannedDirectory.lastScanned,
|
||||
youngestMedia: scannedDirectory.youngestMedia,
|
||||
oldestMedia: scannedDirectory.oldestMedia,
|
||||
name: scannedDirectory.name,
|
||||
path: scannedDirectory.path,
|
||||
} as DirectoryEntity)
|
||||
).identifiers[0]['id'];
|
||||
}
|
||||
}
|
||||
|
||||
protected async saveChildDirs(
|
||||
connection: Connection,
|
||||
currentDirId: number,
|
||||
scannedDirectory: ParentDirectoryDTO
|
||||
connection: Connection,
|
||||
currentDirId: number,
|
||||
scannedDirectory: ParentDirectoryDTO
|
||||
): Promise<void> {
|
||||
const directoryRepository = connection.getRepository(DirectoryEntity);
|
||||
|
||||
// update subdirectories that does not have a parent
|
||||
await directoryRepository
|
||||
.createQueryBuilder()
|
||||
.update(DirectoryEntity)
|
||||
.set({parent: currentDirId as any})
|
||||
.where('path = :path', {
|
||||
path: DiskMangerWorker.pathFromParent(scannedDirectory),
|
||||
})
|
||||
.andWhere('name NOT LIKE :root', {root: DiskMangerWorker.dirName('.')})
|
||||
.andWhere('parent IS NULL')
|
||||
.execute();
|
||||
.createQueryBuilder()
|
||||
.update(DirectoryEntity)
|
||||
.set({parent: currentDirId as unknown})
|
||||
.where('path = :path', {
|
||||
path: DiskMangerWorker.pathFromParent(scannedDirectory),
|
||||
})
|
||||
.andWhere('name NOT LIKE :root', {root: DiskMangerWorker.dirName('.')})
|
||||
.andWhere('parent IS NULL')
|
||||
.execute();
|
||||
|
||||
// save subdirectories
|
||||
const childDirectories = await directoryRepository
|
||||
.createQueryBuilder('directory')
|
||||
.leftJoinAndSelect('directory.parent', 'parent')
|
||||
.where('directory.parent = :dir', {
|
||||
dir: currentDirId,
|
||||
})
|
||||
.getMany();
|
||||
.createQueryBuilder('directory')
|
||||
.leftJoinAndSelect('directory.parent', 'parent')
|
||||
.where('directory.parent = :dir', {
|
||||
dir: currentDirId,
|
||||
})
|
||||
.getMany();
|
||||
|
||||
for (const directory of scannedDirectory.directories) {
|
||||
// Was this child Dir already indexed before?
|
||||
const dirIndex = childDirectories.findIndex(
|
||||
(d): boolean => d.name === directory.name
|
||||
(d): boolean => d.name === directory.name
|
||||
);
|
||||
|
||||
if (dirIndex !== -1) {
|
||||
@ -271,13 +271,13 @@ export class IndexingManager {
|
||||
directory.parent = {id: currentDirId} as ParentDirectoryDTO;
|
||||
(directory as DirectoryEntity).lastScanned = null; // new child dir, not fully scanned yet
|
||||
const d = await directoryRepository.insert(
|
||||
directory as DirectoryEntity
|
||||
directory as DirectoryEntity
|
||||
);
|
||||
|
||||
await this.saveMedia(
|
||||
connection,
|
||||
d.identifiers[0]['id'],
|
||||
directory.media
|
||||
connection,
|
||||
d.identifiers[0]['id'],
|
||||
directory.media
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -289,19 +289,19 @@ export class IndexingManager {
|
||||
}
|
||||
|
||||
protected async saveMetaFiles(
|
||||
connection: Connection,
|
||||
currentDirID: number,
|
||||
scannedDirectory: ParentDirectoryDTO
|
||||
connection: Connection,
|
||||
currentDirID: number,
|
||||
scannedDirectory: ParentDirectoryDTO
|
||||
): Promise<void> {
|
||||
const fileRepository = connection.getRepository(FileEntity);
|
||||
const MDfileRepository = connection.getRepository(MDFileEntity);
|
||||
// save files
|
||||
const indexedMetaFiles = await fileRepository
|
||||
.createQueryBuilder('file')
|
||||
.where('file.directory = :dir', {
|
||||
dir: currentDirID,
|
||||
})
|
||||
.getMany();
|
||||
.createQueryBuilder('file')
|
||||
.where('file.directory = :dir', {
|
||||
dir: currentDirID,
|
||||
})
|
||||
.getMany();
|
||||
|
||||
const metaFilesToSave = [];
|
||||
for (const item of scannedDirectory.metaFile) {
|
||||
@ -337,20 +337,20 @@ export class IndexingManager {
|
||||
}
|
||||
|
||||
protected async saveMedia(
|
||||
connection: Connection,
|
||||
parentDirId: number,
|
||||
media: MediaDTO[]
|
||||
connection: Connection,
|
||||
parentDirId: number,
|
||||
media: MediaDTO[]
|
||||
): Promise<void> {
|
||||
const mediaRepository = connection.getRepository(MediaEntity);
|
||||
const photoRepository = connection.getRepository(PhotoEntity);
|
||||
const videoRepository = connection.getRepository(VideoEntity);
|
||||
// save media
|
||||
let indexedMedia = await mediaRepository
|
||||
.createQueryBuilder('media')
|
||||
.where('media.directory = :dir', {
|
||||
dir: parentDirId,
|
||||
})
|
||||
.getMany();
|
||||
.createQueryBuilder('media')
|
||||
.where('media.directory = :dir', {
|
||||
dir: parentDirId,
|
||||
})
|
||||
.getMany();
|
||||
|
||||
const mediaChange = {
|
||||
saveP: [] as MediaDTO[], // save/update photo
|
||||
@ -376,7 +376,7 @@ export class IndexingManager {
|
||||
// make the list distinct (some photos may contain the same person multiple times)
|
||||
(media[i].metadata as PhotoMetadataEntity).persons = [
|
||||
...new Set(
|
||||
(media[i].metadata as PhotoMetadata).faces.map((f) => f.name)
|
||||
(media[i].metadata as PhotoMetadata).faces.map((f) => f.name)
|
||||
),
|
||||
];
|
||||
}
|
||||
@ -389,8 +389,8 @@ export class IndexingManager {
|
||||
mediaItem = Utils.clone(media[i]);
|
||||
mediaItem.directory = {id: parentDirId} as DirectoryBaseDTO;
|
||||
(MediaDTOUtils.isPhoto(mediaItem)
|
||||
? mediaChange.insertP
|
||||
: mediaChange.insertV
|
||||
? mediaChange.insertP
|
||||
: mediaChange.insertV
|
||||
).push(mediaItem);
|
||||
} else {
|
||||
// Media already in the DB, only needs to be updated
|
||||
@ -398,8 +398,8 @@ export class IndexingManager {
|
||||
if (!Utils.equalsFilter(mediaItem.metadata, media[i].metadata)) {
|
||||
mediaItem.metadata = media[i].metadata;
|
||||
(MediaDTOUtils.isPhoto(mediaItem)
|
||||
? mediaChange.saveP
|
||||
: mediaChange.saveV
|
||||
? mediaChange.saveP
|
||||
: mediaChange.saveV
|
||||
).push(mediaItem);
|
||||
}
|
||||
}
|
||||
@ -416,20 +416,20 @@ export class IndexingManager {
|
||||
await this.saveChunk(videoRepository, mediaChange.insertV, 100);
|
||||
|
||||
indexedMedia = await mediaRepository
|
||||
.createQueryBuilder('media')
|
||||
.where('media.directory = :dir', {
|
||||
dir: parentDirId,
|
||||
})
|
||||
.select(['media.name', 'media.id'])
|
||||
.getMany();
|
||||
.createQueryBuilder('media')
|
||||
.where('media.directory = :dir', {
|
||||
dir: parentDirId,
|
||||
})
|
||||
.select(['media.name', 'media.id'])
|
||||
.getMany();
|
||||
|
||||
const persons: { name: string; mediaId: number }[] = [];
|
||||
personsPerPhoto.forEach((group): void => {
|
||||
const mIndex = indexedMedia.findIndex(
|
||||
(m): boolean => m.name === group.mediaName
|
||||
(m): boolean => m.name === group.mediaName
|
||||
);
|
||||
group.faces.forEach((sf) =>
|
||||
(sf.mediaId = indexedMedia[mIndex].id)
|
||||
(sf.mediaId = indexedMedia[mIndex].id)
|
||||
);
|
||||
|
||||
persons.push(...group.faces as { name: string; mediaId: number }[]);
|
||||
@ -441,9 +441,9 @@ export class IndexingManager {
|
||||
}
|
||||
|
||||
protected async savePersonsToMedia(
|
||||
connection: Connection,
|
||||
parentDirId: number,
|
||||
scannedFaces: { name: string; mediaId: number }[]
|
||||
connection: Connection,
|
||||
parentDirId: number,
|
||||
scannedFaces: { name: string; mediaId: number }[]
|
||||
): Promise<void> {
|
||||
const personJunctionTable = connection.getRepository(PersonJunctionTable);
|
||||
const personRepository = connection.getRepository(PersonEntry);
|
||||
@ -461,13 +461,13 @@ export class IndexingManager {
|
||||
const savedPersons = await personRepository.find();
|
||||
|
||||
const indexedFaces = await personJunctionTable
|
||||
.createQueryBuilder('face')
|
||||
.leftJoin('face.media', 'media')
|
||||
.where('media.directory = :directory', {
|
||||
directory: parentDirId,
|
||||
})
|
||||
.leftJoinAndSelect('face.person', 'person')
|
||||
.getMany();
|
||||
.createQueryBuilder('face')
|
||||
.leftJoin('face.media', 'media')
|
||||
.where('media.directory = :directory', {
|
||||
directory: parentDirId,
|
||||
})
|
||||
.leftJoinAndSelect('face.person', 'person')
|
||||
.getMany();
|
||||
|
||||
const faceToInsert: { person: { id: number }, media: { id: number } }[] = [];
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
@ -485,7 +485,7 @@ export class IndexingManager {
|
||||
if (face == null) {
|
||||
faceToInsert.push({
|
||||
person: savedPersons.find(
|
||||
(p) => p.name === scannedFaces[i].name
|
||||
(p) => p.name === scannedFaces[i].name
|
||||
),
|
||||
media: {id: scannedFaces[i].mediaId}
|
||||
});
|
||||
@ -499,10 +499,10 @@ export class IndexingManager {
|
||||
});
|
||||
}
|
||||
|
||||
private async saveChunk<T>(
|
||||
repository: Repository<any>,
|
||||
entities: T[],
|
||||
size: number
|
||||
private async saveChunk<T extends ObjectLiteral>(
|
||||
repository: Repository<T>,
|
||||
entities: T[],
|
||||
size: number
|
||||
): Promise<T[]> {
|
||||
if (entities.length === 0) {
|
||||
return [];
|
||||
@ -513,31 +513,31 @@ export class IndexingManager {
|
||||
let list: T[] = [];
|
||||
for (let i = 0; i < entities.length / size; i++) {
|
||||
list = list.concat(
|
||||
await repository.save(entities.slice(i * size, (i + 1) * size))
|
||||
await repository.save(entities.slice(i * size, (i + 1) * size))
|
||||
);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private async insertChunk<T>(
|
||||
repository: Repository<any>,
|
||||
entities: T[],
|
||||
size: number
|
||||
private async insertChunk<T extends ObjectLiteral>(
|
||||
repository: Repository<T>,
|
||||
entities: T[],
|
||||
size: number
|
||||
): Promise<number[]> {
|
||||
if (entities.length === 0) {
|
||||
return [];
|
||||
}
|
||||
if (entities.length < size) {
|
||||
return (await repository.insert(entities)).identifiers.map(
|
||||
(i: any) => i.id
|
||||
(i: { id: number }) => i.id
|
||||
);
|
||||
}
|
||||
let list: number[] = [];
|
||||
for (let i = 0; i < entities.length / size; i++) {
|
||||
list = list.concat(
|
||||
(
|
||||
await repository.insert(entities.slice(i * size, (i + 1) * size))
|
||||
).identifiers.map((ids) => ids['id'])
|
||||
(
|
||||
await repository.insert(entities.slice(i * size, (i + 1) * size))
|
||||
).identifiers.map((ids) => ids['id'])
|
||||
);
|
||||
}
|
||||
return list;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { GPSMetadata } from '../../../common/entities/PhotoDTO';
|
||||
import {GPSMetadata} from '../../../common/entities/PhotoDTO';
|
||||
import * as NodeGeocoder from 'node-geocoder';
|
||||
import { LocationLookupException } from '../../exceptions/LocationLookupException';
|
||||
import { LRU } from '../../../common/Utils';
|
||||
import { IObjectManager } from './IObjectManager';
|
||||
import { ParentDirectoryDTO } from '../../../common/entities/DirectoryDTO';
|
||||
import {LocationLookupException} from '../../exceptions/LocationLookupException';
|
||||
import {LRU} from '../../../common/Utils';
|
||||
import {IObjectManager} from './IObjectManager';
|
||||
import {ParentDirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
|
||||
export class LocationManager implements IObjectManager {
|
||||
// onNewDataVersion only need for TypeScript, otherwise the interface is not implemented.
|
||||
@ -12,7 +12,7 @@ export class LocationManager implements IObjectManager {
|
||||
cache = new LRU<GPSMetadata>(100);
|
||||
|
||||
constructor() {
|
||||
this.geocoder = NodeGeocoder({ provider: 'openstreetmap' });
|
||||
this.geocoder = NodeGeocoder({provider: 'openstreetmap'});
|
||||
}
|
||||
|
||||
async getGPSData(text: string): Promise<GPSMetadata> {
|
||||
|
@ -8,7 +8,7 @@ import {IObjectManager} from './IObjectManager';
|
||||
|
||||
const LOG_TAG = '[PersonManager]';
|
||||
|
||||
export class PersonManager implements IObjectManager{
|
||||
export class PersonManager implements IObjectManager {
|
||||
persons: PersonEntry[] = null;
|
||||
/**
|
||||
* Person table contains denormalized data that needs to update when isDBValid = false
|
||||
@ -18,44 +18,44 @@ export class PersonManager implements IObjectManager{
|
||||
private static async updateCounts(): Promise<void> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
await connection.query(
|
||||
'UPDATE person_entry SET count = ' +
|
||||
' (SELECT COUNT(1) FROM person_junction_table WHERE person_junction_table.personId = person_entry.id)'
|
||||
'UPDATE person_entry SET count = ' +
|
||||
' (SELECT COUNT(1) FROM person_junction_table WHERE person_junction_table.personId = person_entry.id)'
|
||||
);
|
||||
|
||||
// remove persons without photo
|
||||
await connection
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(PersonEntry)
|
||||
.where('count = 0')
|
||||
.execute();
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(PersonEntry)
|
||||
.where('count = 0')
|
||||
.execute();
|
||||
}
|
||||
|
||||
private static async updateSamplePhotos(): Promise<void> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
await connection.query(
|
||||
'update person_entry set sampleRegionId = ' +
|
||||
'(Select person_junction_table.id from media_entity ' +
|
||||
'left join person_junction_table on media_entity.id = person_junction_table.mediaId ' +
|
||||
'where person_junction_table.personId=person_entry.id ' +
|
||||
'update person_entry set sampleRegionId = ' +
|
||||
'(Select person_junction_table.id from media_entity ' +
|
||||
'left join person_junction_table on media_entity.id = person_junction_table.mediaId ' +
|
||||
'where person_junction_table.personId=person_entry.id ' +
|
||||
'order by media_entity.metadataRating desc, ' +
|
||||
'media_entity.metadataCreationdate desc ' +
|
||||
'limit 1)'
|
||||
'limit 1)'
|
||||
);
|
||||
}
|
||||
|
||||
async updatePerson(
|
||||
name: string,
|
||||
partialPerson: PersonDTO
|
||||
name: string,
|
||||
partialPerson: PersonDTO
|
||||
): Promise<PersonEntry> {
|
||||
this.isDBValid = false;
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const repository = connection.getRepository(PersonEntry);
|
||||
const person = await repository
|
||||
.createQueryBuilder('person')
|
||||
.limit(1)
|
||||
.where('person.name LIKE :name COLLATE ' + SQL_COLLATE, {name})
|
||||
.getOne();
|
||||
.createQueryBuilder('person')
|
||||
.limit(1)
|
||||
.where('person.name LIKE :name COLLATE ' + SQL_COLLATE, {name})
|
||||
.getOne();
|
||||
|
||||
if (typeof partialPerson.name !== 'undefined') {
|
||||
person.name = partialPerson.name;
|
||||
@ -83,9 +83,9 @@ export class PersonManager implements IObjectManager{
|
||||
public async countFaces(): Promise<number> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection
|
||||
.getRepository(PersonJunctionTable)
|
||||
.createQueryBuilder('personJunction')
|
||||
.getCount();
|
||||
.getRepository(PersonJunctionTable)
|
||||
.createQueryBuilder('personJunction')
|
||||
.getCount();
|
||||
}
|
||||
|
||||
public async get(name: string): Promise<PersonEntry> {
|
||||
@ -96,7 +96,7 @@ export class PersonManager implements IObjectManager{
|
||||
}
|
||||
|
||||
public async saveAll(
|
||||
persons: { name: string; mediaId: number }[]
|
||||
persons: { name: string; mediaId: number }[]
|
||||
): Promise<void> {
|
||||
const toSave: { name: string; mediaId: number }[] = [];
|
||||
const connection = await SQLConnection.getConnection();
|
||||
@ -107,7 +107,7 @@ export class PersonManager implements IObjectManager{
|
||||
// filter already existing persons
|
||||
for (const personToSave of persons) {
|
||||
const person = savedPersons.find(
|
||||
(p): boolean => p.name === personToSave.name
|
||||
(p): boolean => p.name === personToSave.name
|
||||
);
|
||||
if (!person) {
|
||||
toSave.push(personToSave);
|
||||
@ -119,7 +119,7 @@ export class PersonManager implements IObjectManager{
|
||||
const saving = toSave.slice(i * 200, (i + 1) * 200);
|
||||
// saving person
|
||||
const inserted = await personRepository.insert(
|
||||
saving.map((p) => ({name: p.name}))
|
||||
saving.map((p) => ({name: p.name}))
|
||||
);
|
||||
// saving junction table
|
||||
const junctionTable = inserted.identifiers.map((idObj, j) => ({person: idObj, media: {id: saving[j].mediaId}}));
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'reflect-metadata';
|
||||
import {Connection, createConnection, DataSourceOptions, getConnection,} from 'typeorm';
|
||||
import {Connection, createConnection, DataSourceOptions, getConnection, LoggerOptions,} from 'typeorm';
|
||||
import {UserEntity} from './enitites/UserEntity';
|
||||
import {UserRoles} from '../../../common/entities/UserDTO';
|
||||
import {PhotoEntity} from './enitites/PhotoEntity';
|
||||
@ -26,38 +26,21 @@ import {MDFileEntity} from './enitites/MDFileEntity';
|
||||
|
||||
const LOG_TAG = '[SQLConnection]';
|
||||
|
||||
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
|
||||
export class SQLConnection {
|
||||
private static connection: Connection = null;
|
||||
|
||||
|
||||
public static async getConnection(): Promise<Connection> {
|
||||
if (this.connection == null) {
|
||||
const options: any = this.getDriver(Config.Database);
|
||||
// options.name = 'main';
|
||||
options.entities = [
|
||||
UserEntity,
|
||||
FileEntity,
|
||||
MDFileEntity,
|
||||
PersonJunctionTable,
|
||||
PersonEntry,
|
||||
MediaEntity,
|
||||
PhotoEntity,
|
||||
VideoEntity,
|
||||
DirectoryEntity,
|
||||
SharingEntity,
|
||||
AlbumBaseEntity,
|
||||
SavedSearchEntity,
|
||||
VersionEntity,
|
||||
];
|
||||
options.synchronize = false;
|
||||
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
|
||||
options.logging = SQLLogLevel[Config.Server.Log.sqlLevel];
|
||||
}
|
||||
const options = this.getDriver(Config.Database);
|
||||
|
||||
Logger.debug(
|
||||
LOG_TAG,
|
||||
'Creating connection: ' + DatabaseType[Config.Database.type],
|
||||
', with driver:',
|
||||
options.type
|
||||
LOG_TAG,
|
||||
'Creating connection: ' + DatabaseType[Config.Database.type],
|
||||
', with driver:',
|
||||
options.type
|
||||
);
|
||||
this.connection = await this.createConnection(options);
|
||||
await SQLConnection.schemeSync(this.connection);
|
||||
@ -66,34 +49,15 @@ export class SQLConnection {
|
||||
}
|
||||
|
||||
public static async tryConnection(
|
||||
config: ServerDataBaseConfig
|
||||
config: ServerDataBaseConfig
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await getConnection('test').close();
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (err) {
|
||||
}
|
||||
const options: any = this.getDriver(config);
|
||||
const options = this.getDriver(config);
|
||||
options.name = 'test';
|
||||
options.entities = [
|
||||
UserEntity,
|
||||
FileEntity,
|
||||
MDFileEntity,
|
||||
PersonJunctionTable,
|
||||
PersonEntry,
|
||||
MediaEntity,
|
||||
PhotoEntity,
|
||||
VideoEntity,
|
||||
DirectoryEntity,
|
||||
SharingEntity,
|
||||
AlbumBaseEntity,
|
||||
SavedSearchEntity,
|
||||
VersionEntity,
|
||||
];
|
||||
options.synchronize = false;
|
||||
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
|
||||
options.logging = SQLLogLevel[Config.Server.Log.sqlLevel];
|
||||
}
|
||||
const conn = await this.createConnection(options);
|
||||
await SQLConnection.schemeSync(conn);
|
||||
await conn.close();
|
||||
@ -109,8 +73,8 @@ export class SQLConnection {
|
||||
// Adding enforced users to the db
|
||||
const userRepository = connection.getRepository(UserEntity);
|
||||
if (
|
||||
Array.isArray(Config.Users.enforcedUsers) &&
|
||||
Config.Users.enforcedUsers.length > 0
|
||||
Array.isArray(Config.Users.enforcedUsers) &&
|
||||
Config.Users.enforcedUsers.length > 0
|
||||
) {
|
||||
for (let i = 0; i < Config.Users.enforcedUsers.length; ++i) {
|
||||
const uc = Config.Users.enforcedUsers[i];
|
||||
@ -142,12 +106,12 @@ export class SQLConnection {
|
||||
role: UserRoles.Admin,
|
||||
});
|
||||
if (
|
||||
defAdmin &&
|
||||
PasswordHelper.comparePassword('admin', defAdmin.password)
|
||||
defAdmin &&
|
||||
PasswordHelper.comparePassword('admin', defAdmin.password)
|
||||
) {
|
||||
NotificationManager.error(
|
||||
'Using default admin user!',
|
||||
'You are using the default admin/admin user/password, please change or remove it.'
|
||||
'Using default admin user!',
|
||||
'You are using the default admin/admin user/password, please change or remove it.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -164,12 +128,12 @@ export class SQLConnection {
|
||||
}
|
||||
}
|
||||
|
||||
public static getSQLiteDB(config: ServerDataBaseConfig): any {
|
||||
public static getSQLiteDB(config: ServerDataBaseConfig): string {
|
||||
return path.join(ProjectPath.getAbsolutePath(config.dbFolder), 'sqlite.db');
|
||||
}
|
||||
|
||||
private static async createConnection(
|
||||
options: DataSourceOptions
|
||||
options: DataSourceOptions
|
||||
): Promise<Connection> {
|
||||
if (options.type === 'sqlite' || options.type === 'better-sqlite3') {
|
||||
return await createConnection(options);
|
||||
@ -185,7 +149,7 @@ export class SQLConnection {
|
||||
delete tmpOption.database;
|
||||
const tmpConn = await createConnection(tmpOption);
|
||||
await tmpConn.query(
|
||||
'CREATE DATABASE IF NOT EXISTS ' + options.database
|
||||
'CREATE DATABASE IF NOT EXISTS ' + options.database
|
||||
);
|
||||
await tmpConn.close();
|
||||
return await createConnection(options);
|
||||
@ -213,9 +177,9 @@ export class SQLConnection {
|
||||
let users: UserEntity[] = [];
|
||||
try {
|
||||
users = await connection
|
||||
.getRepository(UserEntity)
|
||||
.createQueryBuilder('user')
|
||||
.getMany();
|
||||
.getRepository(UserEntity)
|
||||
.createQueryBuilder('user')
|
||||
.getMany();
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (ex) {
|
||||
}
|
||||
@ -229,15 +193,16 @@ export class SQLConnection {
|
||||
await connection.synchronize();
|
||||
await connection.getRepository(VersionEntity).save(version);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Could not move users to the new db scheme, deleting them. Details:' +
|
||||
e.toString()
|
||||
LOG_TAG,
|
||||
'Could not move users to the new db scheme, deleting them. Details:' +
|
||||
e.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static getDriver(config: ServerDataBaseConfig): DataSourceOptions {
|
||||
let driver: DataSourceOptions = null;
|
||||
|
||||
private static getDriver(config: ServerDataBaseConfig): Writeable<DataSourceOptions> {
|
||||
let driver: Writeable<DataSourceOptions>;
|
||||
if (config.type === DatabaseType.mysql) {
|
||||
driver = {
|
||||
type: 'mysql',
|
||||
@ -252,11 +217,30 @@ export class SQLConnection {
|
||||
driver = {
|
||||
type: 'better-sqlite3',
|
||||
database: path.join(
|
||||
ProjectPath.getAbsolutePath(config.dbFolder),
|
||||
config.sqlite.DBFileName
|
||||
ProjectPath.getAbsolutePath(config.dbFolder),
|
||||
config.sqlite.DBFileName
|
||||
),
|
||||
};
|
||||
}
|
||||
driver.entities = [
|
||||
UserEntity,
|
||||
FileEntity,
|
||||
MDFileEntity,
|
||||
PersonJunctionTable,
|
||||
PersonEntry,
|
||||
MediaEntity,
|
||||
PhotoEntity,
|
||||
VideoEntity,
|
||||
DirectoryEntity,
|
||||
SharingEntity,
|
||||
AlbumBaseEntity,
|
||||
SavedSearchEntity,
|
||||
VersionEntity,
|
||||
];
|
||||
driver.synchronize = false;
|
||||
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
|
||||
driver.logging = SQLLogLevel[Config.Server.Log.sqlLevel] as LoggerOptions;
|
||||
}
|
||||
return driver;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,18 +10,18 @@ export class SharingManager {
|
||||
private static async removeExpiredLink(): Promise<DeleteResult> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection
|
||||
.getRepository(SharingEntity)
|
||||
.createQueryBuilder('share')
|
||||
.where('expires < :now', {now: Date.now()})
|
||||
.delete()
|
||||
.execute();
|
||||
.getRepository(SharingEntity)
|
||||
.createQueryBuilder('share')
|
||||
.where('expires < :now', {now: Date.now()})
|
||||
.delete()
|
||||
.execute();
|
||||
}
|
||||
|
||||
async deleteSharing(sharingKey: string): Promise<void> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const sharing = await connection
|
||||
.getRepository(SharingEntity)
|
||||
.findOneBy({sharingKey});
|
||||
.getRepository(SharingEntity)
|
||||
.findOneBy({sharingKey});
|
||||
await connection.getRepository(SharingEntity).remove(sharing);
|
||||
}
|
||||
|
||||
@ -29,10 +29,10 @@ export class SharingManager {
|
||||
await SharingManager.removeExpiredLink();
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection
|
||||
.getRepository(SharingEntity)
|
||||
.createQueryBuilder('share')
|
||||
.leftJoinAndSelect('share.creator', 'creator')
|
||||
.getMany();
|
||||
.getRepository(SharingEntity)
|
||||
.createQueryBuilder('share')
|
||||
.leftJoinAndSelect('share.creator', 'creator')
|
||||
.getMany();
|
||||
}
|
||||
|
||||
|
||||
@ -40,10 +40,10 @@ export class SharingManager {
|
||||
await SharingManager.removeExpiredLink();
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const q: SelectQueryBuilder<SharingEntity> = connection
|
||||
.getRepository(SharingEntity)
|
||||
.createQueryBuilder('share')
|
||||
.leftJoinAndSelect('share.creator', 'creator')
|
||||
.where('path = :dir', {dir});
|
||||
.getRepository(SharingEntity)
|
||||
.createQueryBuilder('share')
|
||||
.leftJoinAndSelect('share.creator', 'creator')
|
||||
.where('path = :dir', {dir});
|
||||
if (user) {
|
||||
q.andWhere('share.creator = :user', {user: user.id});
|
||||
}
|
||||
@ -54,10 +54,10 @@ export class SharingManager {
|
||||
await SharingManager.removeExpiredLink();
|
||||
const connection = await SQLConnection.getConnection();
|
||||
return await connection.getRepository(SharingEntity)
|
||||
.createQueryBuilder('share')
|
||||
.leftJoinAndSelect('share.creator', 'creator')
|
||||
.where('share.sharingKey = :sharingKey', {sharingKey})
|
||||
.getOne();
|
||||
.createQueryBuilder('share')
|
||||
.leftJoinAndSelect('share.creator', 'creator')
|
||||
.where('share.sharingKey = :sharingKey', {sharingKey})
|
||||
.getOne();
|
||||
}
|
||||
|
||||
async createSharing(sharing: SharingDTO): Promise<SharingDTO> {
|
||||
@ -70,20 +70,20 @@ export class SharingManager {
|
||||
}
|
||||
|
||||
async updateSharing(
|
||||
inSharing: SharingDTO,
|
||||
forceUpdate: boolean
|
||||
inSharing: SharingDTO,
|
||||
forceUpdate: boolean
|
||||
): Promise<SharingDTO> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
|
||||
const sharing = await connection.getRepository(SharingEntity).findOneBy({
|
||||
id: inSharing.id,
|
||||
creator: inSharing.creator.id as any,
|
||||
creator: inSharing.creator.id as unknown,
|
||||
path: inSharing.path,
|
||||
});
|
||||
|
||||
if (
|
||||
sharing.timeStamp < Date.now() - Config.Sharing.updateTimeout &&
|
||||
forceUpdate !== true
|
||||
sharing.timeStamp < Date.now() - Config.Sharing.updateTimeout &&
|
||||
forceUpdate !== true
|
||||
) {
|
||||
throw new Error('Sharing is locked, can\'t update anymore');
|
||||
}
|
||||
|
@ -23,30 +23,30 @@ export class VersionManager implements IObjectManager {
|
||||
}
|
||||
|
||||
const versionString =
|
||||
DataStructureVersion +
|
||||
'_' +
|
||||
this.latestDirectoryStatus.name +
|
||||
'_' +
|
||||
this.latestDirectoryStatus.lastModified +
|
||||
'_' +
|
||||
this.latestDirectoryStatus.mediaCount +
|
||||
'_' +
|
||||
this.allMediaCount;
|
||||
DataStructureVersion +
|
||||
'_' +
|
||||
this.latestDirectoryStatus.name +
|
||||
'_' +
|
||||
this.latestDirectoryStatus.lastModified +
|
||||
'_' +
|
||||
this.latestDirectoryStatus.mediaCount +
|
||||
'_' +
|
||||
this.allMediaCount;
|
||||
return crypto.createHash('md5').update(versionString).digest('hex');
|
||||
}
|
||||
|
||||
async onNewDataVersion(): Promise<void> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const dir = await connection
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.limit(1)
|
||||
.orderBy('directory.lastModified')
|
||||
.getOne();
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
.limit(1)
|
||||
.orderBy('directory.lastModified')
|
||||
.getOne();
|
||||
this.allMediaCount = await connection
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.getCount();
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.getCount();
|
||||
|
||||
if (!dir) {
|
||||
return;
|
||||
|
@ -1,28 +1,16 @@
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import {
|
||||
ParentDirectoryDTO,
|
||||
SubDirectoryDTO,
|
||||
} from '../../../../common/entities/DirectoryDTO';
|
||||
import { MediaEntity } from './MediaEntity';
|
||||
import { FileEntity } from './FileEntity';
|
||||
import { columnCharsetCS } from './EntityUtils';
|
||||
import { MediaDTO } from '../../../../common/entities/MediaDTO';
|
||||
import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique,} from 'typeorm';
|
||||
import {ParentDirectoryDTO, SubDirectoryDTO,} from '../../../../common/entities/DirectoryDTO';
|
||||
import {MediaEntity} from './MediaEntity';
|
||||
import {FileEntity} from './FileEntity';
|
||||
import {columnCharsetCS} from './EntityUtils';
|
||||
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||
|
||||
@Entity()
|
||||
@Unique(['name', 'path'])
|
||||
export class DirectoryEntity
|
||||
implements ParentDirectoryDTO<MediaDTO>, SubDirectoryDTO<MediaDTO>
|
||||
{
|
||||
implements ParentDirectoryDTO<MediaDTO>, SubDirectoryDTO<MediaDTO> {
|
||||
@Index()
|
||||
@PrimaryGeneratedColumn({ unsigned: true })
|
||||
@PrimaryGeneratedColumn({unsigned: true})
|
||||
id: number;
|
||||
|
||||
@Index()
|
||||
@ -61,7 +49,7 @@ export class DirectoryEntity
|
||||
|
||||
isPartial?: boolean;
|
||||
|
||||
@Column('mediumint', { unsigned: true })
|
||||
@Column('mediumint', {unsigned: true})
|
||||
mediaCount: number;
|
||||
|
||||
@Column('bigint', {
|
||||
@ -83,20 +71,20 @@ export class DirectoryEntity
|
||||
youngestMedia: number;
|
||||
|
||||
@Index()
|
||||
@ManyToOne((type) => DirectoryEntity, (directory) => directory.directories, {
|
||||
@ManyToOne(() => DirectoryEntity, (directory) => directory.directories, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
public parent: DirectoryEntity;
|
||||
|
||||
@OneToMany((type) => DirectoryEntity, (dir) => dir.parent)
|
||||
@OneToMany(() => DirectoryEntity, (dir) => dir.parent)
|
||||
public directories: DirectoryEntity[];
|
||||
|
||||
// not saving to database, it is only assigned when querying the DB
|
||||
@ManyToOne((type) => MediaEntity, { onDelete: 'SET NULL' })
|
||||
@ManyToOne(() => MediaEntity, {onDelete: 'SET NULL'})
|
||||
public cover: MediaEntity;
|
||||
|
||||
// On galley change, cover will be invalid
|
||||
@Column({ type: 'boolean', default: false })
|
||||
@Column({type: 'boolean', default: false})
|
||||
validCover: boolean;
|
||||
|
||||
@OneToMany((type) => MediaEntity, (media) => media.directory)
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { Config } from '../../../../common/config/private/Config';
|
||||
import { ColumnOptions } from 'typeorm/decorator/options/ColumnOptions';
|
||||
import { DatabaseType } from '../../../../common/config/private/PrivateConfig';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {ColumnOptions} from 'typeorm/decorator/options/ColumnOptions';
|
||||
import {DatabaseType} from '../../../../common/config/private/PrivateConfig';
|
||||
|
||||
export class ColumnCharsetCS implements ColumnOptions {
|
||||
public get charset(): string {
|
||||
return Config.Database.type === DatabaseType.mysql
|
||||
? 'utf8mb4'
|
||||
: 'utf8';
|
||||
? 'utf8mb4'
|
||||
: 'utf8';
|
||||
}
|
||||
|
||||
public get collation(): string {
|
||||
return Config.Database.type === DatabaseType.mysql
|
||||
? 'utf8mb4_bin'
|
||||
: null;
|
||||
? 'utf8mb4_bin'
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ export class FileEntity implements FileDTO {
|
||||
name: string;
|
||||
|
||||
@Index()
|
||||
@ManyToOne((type) => DirectoryEntity, (directory) => directory.metaFile, {
|
||||
@ManyToOne(() => DirectoryEntity, (directory) => directory.metaFile, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: false,
|
||||
})
|
||||
|
@ -54,7 +54,7 @@ export class GPSMetadataEntity implements GPSMetadata {
|
||||
}
|
||||
|
||||
export class PositionMetaDataEntity implements PositionMetaData {
|
||||
@Column((type) => GPSMetadataEntity)
|
||||
@Column(() => GPSMetadataEntity)
|
||||
GPSData: GPSMetadataEntity;
|
||||
|
||||
@Column({
|
||||
@ -86,7 +86,7 @@ export class MediaMetadataEntity implements MediaMetadata {
|
||||
@Column('text')
|
||||
caption: string;
|
||||
|
||||
@Column((type) => MediaDimensionEntity)
|
||||
@Column(() => MediaDimensionEntity)
|
||||
size: MediaDimensionEntity;
|
||||
|
||||
/**
|
||||
@ -113,17 +113,17 @@ export class MediaMetadataEntity implements MediaMetadata {
|
||||
})
|
||||
keywords: string[];
|
||||
|
||||
@Column((type) => CameraMetadataEntity)
|
||||
@Column(() => CameraMetadataEntity)
|
||||
cameraData: CameraMetadataEntity;
|
||||
|
||||
@Column((type) => PositionMetaDataEntity)
|
||||
@Column(() => PositionMetaDataEntity)
|
||||
positionData: PositionMetaDataEntity;
|
||||
|
||||
@Column('tinyint', {unsigned: true})
|
||||
@Index()
|
||||
rating: 0 | 1 | 2 | 3 | 4 | 5;
|
||||
|
||||
@OneToMany((type) => PersonJunctionTable, (junctionTable) => junctionTable.media)
|
||||
@OneToMany(() => PersonJunctionTable, (junctionTable) => junctionTable.media)
|
||||
personJunction: PersonJunctionTable[];
|
||||
|
||||
@Column({
|
||||
@ -178,13 +178,13 @@ export abstract class MediaEntity implements MediaDTO {
|
||||
name: string;
|
||||
|
||||
@Index()
|
||||
@ManyToOne((type) => DirectoryEntity, (directory) => directory.media, {
|
||||
@ManyToOne(() => DirectoryEntity, (directory) => directory.media, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: false,
|
||||
})
|
||||
directory: DirectoryEntity;
|
||||
|
||||
@Column((type) => MediaMetadataEntity)
|
||||
@Column(() => MediaMetadataEntity)
|
||||
metadata: MediaMetadataEntity;
|
||||
|
||||
missingThumbnails: number;
|
||||
|
@ -1,15 +1,7 @@
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique,} from 'typeorm';
|
||||
import {PersonJunctionTable} from './PersonJunctionTable';
|
||||
import {columnCharsetCS} from './EntityUtils';
|
||||
import { PersonDTO } from '../../../../common/entities/PersonDTO';
|
||||
import {PersonDTO} from '../../../../common/entities/PersonDTO';
|
||||
|
||||
@Entity()
|
||||
@Unique(['name'])
|
||||
@ -27,10 +19,10 @@ export class PersonEntry implements PersonDTO {
|
||||
@Column({default: false})
|
||||
isFavourite: boolean;
|
||||
|
||||
@OneToMany((type) => PersonJunctionTable, (junctionTable) => junctionTable.person)
|
||||
@OneToMany(() => PersonJunctionTable, (junctionTable) => junctionTable.person)
|
||||
public faces: PersonJunctionTable[];
|
||||
|
||||
@ManyToOne((type) => PersonJunctionTable, {
|
||||
@ManyToOne(() => PersonJunctionTable, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
|
@ -13,14 +13,14 @@ export class PersonJunctionTable {
|
||||
id: number;
|
||||
|
||||
@Index()
|
||||
@ManyToOne((type) => MediaEntity, (media) => media.metadata.faces, {
|
||||
@ManyToOne(() => MediaEntity, (media) => media.metadata.faces, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: false,
|
||||
})
|
||||
media: MediaEntity;
|
||||
|
||||
@Index()
|
||||
@ManyToOne((type) => PersonEntry, (person) => person.faces, {
|
||||
@ManyToOne(() => PersonEntry, (person) => person.faces, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: false,
|
||||
})
|
||||
|
@ -1,13 +1,10 @@
|
||||
import { ChildEntity, Column } from 'typeorm';
|
||||
import {
|
||||
PhotoDTO,
|
||||
PhotoMetadata,
|
||||
} from '../../../../common/entities/PhotoDTO';
|
||||
import { MediaEntity, MediaMetadataEntity } from './MediaEntity';
|
||||
import {ChildEntity, Column} from 'typeorm';
|
||||
import {PhotoDTO, PhotoMetadata,} from '../../../../common/entities/PhotoDTO';
|
||||
import {MediaEntity, MediaMetadataEntity} from './MediaEntity';
|
||||
|
||||
export class PhotoMetadataEntity
|
||||
extends MediaMetadataEntity
|
||||
implements PhotoMetadata {
|
||||
extends MediaMetadataEntity
|
||||
implements PhotoMetadata {
|
||||
/*
|
||||
@Column('simple-array')
|
||||
keywords: string[];
|
||||
@ -25,6 +22,6 @@ export class PhotoMetadataEntity
|
||||
|
||||
@ChildEntity()
|
||||
export class PhotoEntity extends MediaEntity implements PhotoDTO {
|
||||
@Column((type) => PhotoMetadataEntity)
|
||||
@Column(() => PhotoMetadataEntity)
|
||||
metadata: PhotoMetadataEntity;
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { SharingDTO } from '../../../../common/entities/SharingDTO';
|
||||
import { UserEntity } from './UserEntity';
|
||||
import { UserDTO } from '../../../../common/entities/UserDTO';
|
||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import {SharingDTO} from '../../../../common/entities/SharingDTO';
|
||||
import {UserEntity} from './UserEntity';
|
||||
import {UserDTO} from '../../../../common/entities/UserDTO';
|
||||
|
||||
@Entity()
|
||||
export class SharingEntity implements SharingDTO {
|
||||
@PrimaryGeneratedColumn({ unsigned: true })
|
||||
@PrimaryGeneratedColumn({unsigned: true})
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
@ -14,7 +14,7 @@ export class SharingEntity implements SharingDTO {
|
||||
@Column()
|
||||
path: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
@Column({type: 'text', nullable: true})
|
||||
password: string;
|
||||
|
||||
@Column('bigint', {
|
||||
@ -38,6 +38,6 @@ export class SharingEntity implements SharingDTO {
|
||||
@Column()
|
||||
includeSubfolders: boolean;
|
||||
|
||||
@ManyToOne((type) => UserEntity, { onDelete: 'CASCADE', nullable: false })
|
||||
@ManyToOne(() => UserEntity, {onDelete: 'CASCADE', nullable: false})
|
||||
creator: UserDTO;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class VersionEntity {
|
||||
|
@ -1,14 +1,10 @@
|
||||
import { ChildEntity, Column } from 'typeorm';
|
||||
import { MediaEntity, MediaMetadataEntity } from './MediaEntity';
|
||||
import {
|
||||
VideoDTO,
|
||||
VideoMetadata,
|
||||
} from '../../../../common/entities/VideoDTO';
|
||||
import {ChildEntity, Column} from 'typeorm';
|
||||
import {MediaEntity, MediaMetadataEntity} from './MediaEntity';
|
||||
import {VideoDTO, VideoMetadata,} from '../../../../common/entities/VideoDTO';
|
||||
|
||||
export class VideoMetadataEntity
|
||||
extends MediaMetadataEntity
|
||||
implements VideoMetadata
|
||||
{
|
||||
extends MediaMetadataEntity
|
||||
implements VideoMetadata {
|
||||
@Column('int')
|
||||
bitRate: number;
|
||||
|
||||
@ -28,6 +24,6 @@ export class VideoMetadataEntity
|
||||
|
||||
@ChildEntity()
|
||||
export class VideoEntity extends MediaEntity implements VideoDTO {
|
||||
@Column((type) => VideoMetadataEntity)
|
||||
@Column(() => VideoMetadataEntity)
|
||||
metadata: VideoMetadataEntity;
|
||||
}
|
||||
|
@ -23,6 +23,6 @@ export class AlbumBaseEntity implements AlbumBaseDTO {
|
||||
@Column('int', {unsigned: true, default: 0})
|
||||
count: number;
|
||||
|
||||
@ManyToOne((type) => MediaEntity, {onDelete: 'SET NULL', nullable: true})
|
||||
@ManyToOne(() => MediaEntity, {onDelete: 'SET NULL', nullable: true})
|
||||
public cover: MediaEntity;
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { ChildEntity, Column } from 'typeorm';
|
||||
import { AlbumBaseEntity } from './AlbumBaseEntity';
|
||||
import { SavedSearchDTO } from '../../../../../common/entities/album/SavedSearchDTO';
|
||||
import { SearchQueryDTO } from '../../../../../common/entities/SearchQueryDTO';
|
||||
import {ChildEntity, Column} from 'typeorm';
|
||||
import {AlbumBaseEntity} from './AlbumBaseEntity';
|
||||
import {SavedSearchDTO} from '../../../../../common/entities/album/SavedSearchDTO';
|
||||
import {SearchQueryDTO} from '../../../../../common/entities/SearchQueryDTO';
|
||||
|
||||
@ChildEntity()
|
||||
export class SavedSearchEntity
|
||||
extends AlbumBaseEntity
|
||||
implements SavedSearchDTO
|
||||
{
|
||||
extends AlbumBaseEntity
|
||||
implements SavedSearchDTO {
|
||||
@Column({
|
||||
type: 'text',
|
||||
nullable: false,
|
||||
|
@ -31,8 +31,10 @@ const LOG_TAG = '[ConfigDiagnostics]';
|
||||
|
||||
export class ConfigDiagnostics {
|
||||
static testAlbumsConfig(
|
||||
albumConfig: ClientAlbumConfig,
|
||||
original: PrivateConfigClass
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
albumConfig: ClientAlbumConfig,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
original: PrivateConfigClass
|
||||
): void {
|
||||
Logger.debug(LOG_TAG, 'Testing album config');
|
||||
// nothing to check
|
||||
@ -51,27 +53,27 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
|
||||
static async testDatabase(
|
||||
databaseConfig: ServerDataBaseConfig
|
||||
databaseConfig: ServerDataBaseConfig
|
||||
): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing database config');
|
||||
await SQLConnection.tryConnection(databaseConfig);
|
||||
if (databaseConfig.type === DatabaseType.sqlite) {
|
||||
try {
|
||||
await this.checkReadWritePermission(
|
||||
SQLConnection.getSQLiteDB(databaseConfig)
|
||||
SQLConnection.getSQLiteDB(databaseConfig)
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'Cannot read or write sqlite storage file: ' +
|
||||
SQLConnection.getSQLiteDB(databaseConfig)
|
||||
'Cannot read or write sqlite storage file: ' +
|
||||
SQLConnection.getSQLiteDB(databaseConfig)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async testMetaFileConfig(
|
||||
metaFileConfig: ClientMetaFileConfig,
|
||||
config: PrivateConfigClass
|
||||
metaFileConfig: ClientMetaFileConfig,
|
||||
config: PrivateConfigClass
|
||||
): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing meta file config');
|
||||
if (metaFileConfig.gpx === true && config.Map.enabled === false) {
|
||||
@ -96,19 +98,19 @@ export class ConfigDiagnostics {
|
||||
ffmpeg().getAvailableCodecs((err: Error) => {
|
||||
if (err) {
|
||||
return reject(
|
||||
new Error(
|
||||
'Error accessing ffmpeg, cant find executable: ' +
|
||||
err.toString()
|
||||
)
|
||||
new Error(
|
||||
'Error accessing ffmpeg, cant find executable: ' +
|
||||
err.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
ffmpeg(__dirname + '/blank.jpg').ffprobe((err2: Error) => {
|
||||
if (err2) {
|
||||
return reject(
|
||||
new Error(
|
||||
'Error accessing ffmpeg-probe, cant find executable: ' +
|
||||
err2.toString()
|
||||
)
|
||||
new Error(
|
||||
'Error accessing ffmpeg-probe, cant find executable: ' +
|
||||
err2.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
return resolve();
|
||||
@ -156,7 +158,7 @@ export class ConfigDiagnostics {
|
||||
|
||||
|
||||
static async testThumbnailConfig(
|
||||
thumbnailConfig: ServerThumbnailConfig
|
||||
thumbnailConfig: ServerThumbnailConfig
|
||||
): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing thumbnail config');
|
||||
|
||||
@ -167,7 +169,7 @@ export class ConfigDiagnostics {
|
||||
|
||||
if (isNaN(thumbnailConfig.iconSize) || thumbnailConfig.iconSize <= 0) {
|
||||
throw new Error(
|
||||
'IconSize has to be >= 0 integer, got: ' + thumbnailConfig.iconSize
|
||||
'IconSize has to be >= 0 integer, got: ' + thumbnailConfig.iconSize
|
||||
);
|
||||
}
|
||||
|
||||
@ -182,16 +184,18 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
|
||||
static async testTasksConfig(
|
||||
task: ServerJobConfig,
|
||||
config: PrivateConfigClass
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
task: ServerJobConfig,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
config: PrivateConfigClass
|
||||
): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing tasks config');
|
||||
return;
|
||||
}
|
||||
|
||||
static async testFacesConfig(
|
||||
faces: ClientFacesConfig,
|
||||
config: PrivateConfigClass
|
||||
faces: ClientFacesConfig,
|
||||
config: PrivateConfigClass
|
||||
): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing faces config');
|
||||
if (faces.enabled === true) {
|
||||
@ -202,29 +206,33 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
|
||||
static async testSearchConfig(
|
||||
search: ClientSearchConfig,
|
||||
config: PrivateConfigClass
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
search: ClientSearchConfig,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
config: PrivateConfigClass
|
||||
): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing search config');
|
||||
//nothing to check
|
||||
}
|
||||
|
||||
static async testSharingConfig(
|
||||
sharing: ClientSharingConfig,
|
||||
config: PrivateConfigClass
|
||||
sharing: ClientSharingConfig,
|
||||
config: PrivateConfigClass
|
||||
): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing sharing config');
|
||||
if (
|
||||
sharing.enabled === true &&
|
||||
config.Users.authenticationRequired === false
|
||||
sharing.enabled === true &&
|
||||
config.Users.authenticationRequired === false
|
||||
) {
|
||||
throw new Error('In case of no authentication, sharing is not supported');
|
||||
}
|
||||
}
|
||||
|
||||
static async testRandomPhotoConfig(
|
||||
sharing: ClientRandomPhotoConfig,
|
||||
config: PrivateConfigClass
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
sharing: ClientRandomPhotoConfig,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
config: PrivateConfigClass
|
||||
): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing random photo config');
|
||||
//nothing to check
|
||||
@ -236,14 +244,14 @@ export class ConfigDiagnostics {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
map.mapProvider === MapProviders.Mapbox &&
|
||||
(!map.mapboxAccessToken || map.mapboxAccessToken.length === 0)
|
||||
map.mapProvider === MapProviders.Mapbox &&
|
||||
(!map.mapboxAccessToken || map.mapboxAccessToken.length === 0)
|
||||
) {
|
||||
throw new Error('Mapbox needs a valid api key.');
|
||||
}
|
||||
if (
|
||||
map.mapProvider === MapProviders.Custom &&
|
||||
(!map.customLayers || map.customLayers.length === 0)
|
||||
map.mapProvider === MapProviders.Custom &&
|
||||
(!map.customLayers || map.customLayers.length === 0)
|
||||
) {
|
||||
throw new Error('Custom maps need at least one valid layer');
|
||||
}
|
||||
@ -260,10 +268,10 @@ export class ConfigDiagnostics {
|
||||
Logger.debug(LOG_TAG, 'Testing cover config');
|
||||
const sp = new SearchQueryParser();
|
||||
if (
|
||||
!Utils.equalsFilter(
|
||||
sp.parse(sp.stringify(settings.SearchQuery)),
|
||||
settings.SearchQuery
|
||||
)
|
||||
!Utils.equalsFilter(
|
||||
sp.parse(sp.stringify(settings.SearchQuery)),
|
||||
settings.SearchQuery
|
||||
)
|
||||
) {
|
||||
throw new Error('SearchQuery is not valid. Got: ' + JSON.stringify(sp.parse(sp.stringify(settings.SearchQuery))));
|
||||
}
|
||||
@ -303,8 +311,8 @@ export class ConfigDiagnostics {
|
||||
const err: Error = ex;
|
||||
Logger.warn(LOG_TAG, '[SQL error]', err.toString());
|
||||
Logger.error(
|
||||
LOG_TAG,
|
||||
'Error during initializing SQL DB, check DB connection and settings'
|
||||
LOG_TAG,
|
||||
'Error during initializing SQL DB, check DB connection and settings'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@ -315,15 +323,15 @@ export class ConfigDiagnostics {
|
||||
const err: Error = ex;
|
||||
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'[Thumbnail hardware acceleration] module error: ',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'[Thumbnail hardware acceleration] module error: ',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Thumbnail hardware acceleration is not possible.' +
|
||||
' \'sharp\' node module is not found.' +
|
||||
' Falling back temporally to JS based thumbnail generation'
|
||||
LOG_TAG,
|
||||
'Thumbnail hardware acceleration is not possible.' +
|
||||
' \'sharp\' node module is not found.' +
|
||||
' Falling back temporally to JS based thumbnail generation'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@ -341,32 +349,32 @@ export class ConfigDiagnostics {
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Video support error, switching off..',
|
||||
err.toString()
|
||||
'Video support error, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Video support error, switching off..',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Video support error, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Config.Media.Video.enabled = false;
|
||||
}
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.testMetaFileConfig(
|
||||
Config.MetaFile,
|
||||
Config
|
||||
Config.MetaFile,
|
||||
Config
|
||||
);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Meta file support error, switching off gpx..',
|
||||
err.toString()
|
||||
'Meta file support error, switching off gpx..',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Meta file support error, switching off..',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Meta file support error, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Config.MetaFile.gpx = false;
|
||||
}
|
||||
@ -376,13 +384,13 @@ export class ConfigDiagnostics {
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Albums support error, switching off..',
|
||||
err.toString()
|
||||
'Albums support error, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Meta file support error, switching off..',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Meta file support error, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Config.Album.enabled = false;
|
||||
}
|
||||
@ -396,7 +404,7 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
try {
|
||||
await ConfigDiagnostics.testThumbnailConfig(
|
||||
Config.Media.Thumbnail
|
||||
Config.Media.Thumbnail
|
||||
);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
@ -409,14 +417,14 @@ export class ConfigDiagnostics {
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Search is not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
'Search is not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Search is not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Search is not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Config.Search.enabled = false;
|
||||
}
|
||||
@ -426,13 +434,13 @@ export class ConfigDiagnostics {
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Cover settings are not valid, resetting search query',
|
||||
err.toString()
|
||||
'Cover settings are not valid, resetting search query',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Cover settings are not valid, resetting search query',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Cover settings are not valid, resetting search query',
|
||||
err.toString()
|
||||
);
|
||||
Config.AlbumCover.SearchQuery = {
|
||||
type: SearchQueryTypes.any_text,
|
||||
@ -445,14 +453,14 @@ export class ConfigDiagnostics {
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Faces are not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
'Faces are not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Faces are not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Faces are not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Config.Faces.enabled = false;
|
||||
}
|
||||
@ -462,14 +470,14 @@ export class ConfigDiagnostics {
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Some Tasks are not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
'Some Tasks are not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Some Tasks not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Some Tasks not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Config.Faces.enabled = false;
|
||||
}
|
||||
@ -479,34 +487,34 @@ export class ConfigDiagnostics {
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Sharing is not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
'Sharing is not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Sharing is not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Sharing is not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Config.Sharing.enabled = false;
|
||||
}
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.testRandomPhotoConfig(
|
||||
Config.Sharing,
|
||||
Config
|
||||
Config.Sharing,
|
||||
Config
|
||||
);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Random Media is not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
'Random Media is not supported with these settings. Disabling temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Random Media is not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Random Media is not supported with these settings, switching off..',
|
||||
err.toString()
|
||||
);
|
||||
Config.Sharing.enabled = false;
|
||||
}
|
||||
@ -516,15 +524,15 @@ export class ConfigDiagnostics {
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Maps is not supported with these settings. Using open street maps temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
'Maps is not supported with these settings. Using open street maps temporally. ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Maps is not supported with these settings. Using open street maps temporally ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
LOG_TAG,
|
||||
'Maps is not supported with these settings. Using open street maps temporally ' +
|
||||
'Please adjust the config properly.',
|
||||
err.toString()
|
||||
);
|
||||
Config.Map.mapProvider = MapProviders.OpenStreetMap;
|
||||
}
|
||||
|
@ -24,11 +24,11 @@ export class PhotoProcessing {
|
||||
if (Config.Server.Threading.enabled === true) {
|
||||
if (Config.Server.Threading.thumbnailThreads > 0) {
|
||||
Config.Media.Thumbnail.concurrentThumbnailGenerations =
|
||||
Config.Server.Threading.thumbnailThreads;
|
||||
Config.Server.Threading.thumbnailThreads;
|
||||
} else {
|
||||
Config.Media.Thumbnail.concurrentThumbnailGenerations = Math.max(
|
||||
1,
|
||||
os.cpus().length - 1
|
||||
1,
|
||||
os.cpus().length - 1
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@ -36,31 +36,31 @@ export class PhotoProcessing {
|
||||
}
|
||||
|
||||
this.taskQue = new TaskExecuter(
|
||||
Config.Media.Thumbnail.concurrentThumbnailGenerations,
|
||||
(input): Promise<void> => PhotoWorker.render(input)
|
||||
Config.Media.Thumbnail.concurrentThumbnailGenerations,
|
||||
(input): Promise<void> => PhotoWorker.render(input)
|
||||
);
|
||||
|
||||
this.initDone = true;
|
||||
}
|
||||
|
||||
public static async generatePersonThumbnail(
|
||||
person: PersonEntry
|
||||
person: PersonEntry
|
||||
): Promise<string> {
|
||||
// load parameters
|
||||
const photo: PhotoDTO = person.sampleRegion.media;
|
||||
const mediaPath = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
photo.directory.path,
|
||||
photo.directory.name,
|
||||
photo.name
|
||||
ProjectPath.ImageFolder,
|
||||
photo.directory.path,
|
||||
photo.directory.name,
|
||||
photo.name
|
||||
);
|
||||
const size: number = Config.Media.Thumbnail.personThumbnailSize;
|
||||
const faceRegion = person.sampleRegion.media.metadata.faces.find(f => f.name === person.name);
|
||||
// generate thumbnail path
|
||||
const thPath = PhotoProcessing.generatePersonThumbnailPath(
|
||||
mediaPath,
|
||||
faceRegion,
|
||||
size
|
||||
mediaPath,
|
||||
faceRegion,
|
||||
size
|
||||
);
|
||||
|
||||
// check if thumbnail already exist
|
||||
@ -73,12 +73,12 @@ export class PhotoProcessing {
|
||||
|
||||
const margin = {
|
||||
x: Math.round(
|
||||
faceRegion.box.width *
|
||||
Config.Media.Thumbnail.personFaceMargin
|
||||
faceRegion.box.width *
|
||||
Config.Media.Thumbnail.personFaceMargin
|
||||
),
|
||||
y: Math.round(
|
||||
faceRegion.box.height *
|
||||
Config.Media.Thumbnail.personFaceMargin
|
||||
faceRegion.box.height *
|
||||
Config.Media.Thumbnail.personFaceMargin
|
||||
),
|
||||
};
|
||||
|
||||
@ -91,10 +91,10 @@ export class PhotoProcessing {
|
||||
makeSquare: false,
|
||||
cut: {
|
||||
left: Math.round(
|
||||
Math.max(0, faceRegion.box.left - margin.x / 2)
|
||||
Math.max(0, faceRegion.box.left - margin.x / 2)
|
||||
),
|
||||
top: Math.round(
|
||||
Math.max(0, faceRegion.box.top - margin.y / 2)
|
||||
Math.max(0, faceRegion.box.top - margin.y / 2)
|
||||
),
|
||||
width: faceRegion.box.width + margin.x,
|
||||
height: faceRegion.box.height + margin.y,
|
||||
@ -104,12 +104,12 @@ export class PhotoProcessing {
|
||||
smartSubsample: Config.Media.Thumbnail.smartSubsample,
|
||||
} as MediaRendererInput;
|
||||
input.cut.width = Math.min(
|
||||
input.cut.width,
|
||||
photo.metadata.size.width - input.cut.left
|
||||
input.cut.width,
|
||||
photo.metadata.size.width - input.cut.left
|
||||
);
|
||||
input.cut.height = Math.min(
|
||||
input.cut.height,
|
||||
photo.metadata.size.height - input.cut.top
|
||||
input.cut.height,
|
||||
photo.metadata.size.height - input.cut.top
|
||||
);
|
||||
|
||||
await fsp.mkdir(ProjectPath.FacesFolder, {recursive: true});
|
||||
@ -120,34 +120,34 @@ export class PhotoProcessing {
|
||||
public static generateConvertedPath(mediaPath: string, size: number): string {
|
||||
const file = path.basename(mediaPath);
|
||||
return path.join(
|
||||
ProjectPath.TranscodedFolder,
|
||||
ProjectPath.getRelativePathToImages(path.dirname(mediaPath)),
|
||||
file + '_' + size + 'q' + Config.Media.Thumbnail.quality + (Config.Media.Thumbnail.smartSubsample ? 'cs' : '') + PhotoProcessing.CONVERTED_EXTENSION
|
||||
ProjectPath.TranscodedFolder,
|
||||
ProjectPath.getRelativePathToImages(path.dirname(mediaPath)),
|
||||
file + '_' + size + 'q' + Config.Media.Thumbnail.quality + (Config.Media.Thumbnail.smartSubsample ? 'cs' : '') + PhotoProcessing.CONVERTED_EXTENSION
|
||||
);
|
||||
}
|
||||
|
||||
public static generatePersonThumbnailPath(
|
||||
mediaPath: string,
|
||||
faceRegion: FaceRegion,
|
||||
size: number
|
||||
mediaPath: string,
|
||||
faceRegion: FaceRegion,
|
||||
size: number
|
||||
): string {
|
||||
return path.join(
|
||||
ProjectPath.FacesFolder,
|
||||
crypto
|
||||
.createHash('md5')
|
||||
.update(
|
||||
mediaPath +
|
||||
'_' +
|
||||
faceRegion.name +
|
||||
'_' +
|
||||
faceRegion.box.left +
|
||||
'_' +
|
||||
faceRegion.box.top
|
||||
)
|
||||
.digest('hex') +
|
||||
'_' +
|
||||
size +
|
||||
PhotoProcessing.CONVERTED_EXTENSION
|
||||
ProjectPath.FacesFolder,
|
||||
crypto
|
||||
.createHash('md5')
|
||||
.update(
|
||||
mediaPath +
|
||||
'_' +
|
||||
faceRegion.name +
|
||||
'_' +
|
||||
faceRegion.box.left +
|
||||
'_' +
|
||||
faceRegion.box.top
|
||||
)
|
||||
.digest('hex') +
|
||||
'_' +
|
||||
size +
|
||||
PhotoProcessing.CONVERTED_EXTENSION
|
||||
);
|
||||
}
|
||||
|
||||
@ -156,14 +156,14 @@ export class PhotoProcessing {
|
||||
* @param convertedPath
|
||||
*/
|
||||
public static async isValidConvertedPath(
|
||||
convertedPath: string
|
||||
convertedPath: string
|
||||
): Promise<boolean> {
|
||||
const origFilePath = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
path.relative(
|
||||
ProjectPath.TranscodedFolder,
|
||||
convertedPath.substring(0, convertedPath.lastIndexOf('_'))
|
||||
)
|
||||
ProjectPath.ImageFolder,
|
||||
path.relative(
|
||||
ProjectPath.TranscodedFolder,
|
||||
convertedPath.substring(0, convertedPath.lastIndexOf('_'))
|
||||
)
|
||||
);
|
||||
|
||||
if (path.extname(convertedPath) !== PhotoProcessing.CONVERTED_EXTENSION) {
|
||||
@ -171,24 +171,24 @@ export class PhotoProcessing {
|
||||
}
|
||||
|
||||
const sizeStr = convertedPath.substring(
|
||||
convertedPath.lastIndexOf('_') + 1,
|
||||
convertedPath.lastIndexOf('q')
|
||||
convertedPath.lastIndexOf('_') + 1,
|
||||
convertedPath.lastIndexOf('q')
|
||||
);
|
||||
|
||||
const size = parseInt(sizeStr, 10);
|
||||
|
||||
if (
|
||||
(size + '').length !== sizeStr.length ||
|
||||
(Config.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1 &&
|
||||
Config.Media.Photo.Converting.resolution !== size)
|
||||
(size + '').length !== sizeStr.length ||
|
||||
(Config.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1 &&
|
||||
Config.Media.Photo.Converting.resolution !== size)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
let qualityStr = convertedPath.substring(
|
||||
convertedPath.lastIndexOf('q') + 1,
|
||||
convertedPath.length - path.extname(convertedPath).length
|
||||
convertedPath.lastIndexOf('q') + 1,
|
||||
convertedPath.length - path.extname(convertedPath).length
|
||||
);
|
||||
|
||||
|
||||
@ -202,7 +202,7 @@ export class PhotoProcessing {
|
||||
const quality = parseInt(qualityStr, 10);
|
||||
|
||||
if ((quality + '').length !== qualityStr.length ||
|
||||
quality !== Config.Media.Thumbnail.quality) {
|
||||
quality !== Config.Media.Thumbnail.quality) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -217,16 +217,16 @@ export class PhotoProcessing {
|
||||
|
||||
public static async convertPhoto(mediaPath: string): Promise<string> {
|
||||
return this.generateThumbnail(
|
||||
mediaPath,
|
||||
Config.Media.Photo.Converting.resolution,
|
||||
ThumbnailSourceType.Photo,
|
||||
false
|
||||
mediaPath,
|
||||
Config.Media.Photo.Converting.resolution,
|
||||
ThumbnailSourceType.Photo,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
static async convertedPhotoExist(
|
||||
mediaPath: string,
|
||||
size: number
|
||||
mediaPath: string,
|
||||
size: number
|
||||
): Promise<boolean> {
|
||||
// generate thumbnail path
|
||||
const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size);
|
||||
@ -243,10 +243,10 @@ export class PhotoProcessing {
|
||||
|
||||
|
||||
public static async generateThumbnail(
|
||||
mediaPath: string,
|
||||
size: number,
|
||||
sourceType: ThumbnailSourceType,
|
||||
makeSquare: boolean
|
||||
mediaPath: string,
|
||||
size: number,
|
||||
sourceType: ThumbnailSourceType,
|
||||
makeSquare: boolean
|
||||
): Promise<string> {
|
||||
// generate thumbnail path
|
||||
const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size);
|
||||
@ -284,9 +284,9 @@ export class PhotoProcessing {
|
||||
}
|
||||
|
||||
public static async renderSVG(
|
||||
svgString: SVGIconConfig,
|
||||
outPath: string,
|
||||
color = '#000'
|
||||
svgString: SVGIconConfig,
|
||||
outPath: string,
|
||||
color = '#000'
|
||||
): Promise<string> {
|
||||
|
||||
// check if file already exist
|
||||
|
@ -9,33 +9,33 @@ import {SupportedFormats} from '../../../common/SupportedFormats';
|
||||
|
||||
export class VideoProcessing {
|
||||
private static taskQue: ITaskExecuter<VideoConverterInput, void> =
|
||||
new TaskExecuter(
|
||||
1,
|
||||
(input): Promise<void> => VideoConverterWorker.convert(input)
|
||||
);
|
||||
new TaskExecuter(
|
||||
1,
|
||||
(input): Promise<void> => VideoConverterWorker.convert(input)
|
||||
);
|
||||
|
||||
public static generateConvertedFilePath(videoPath: string): string {
|
||||
return path.join(
|
||||
ProjectPath.TranscodedFolder,
|
||||
ProjectPath.getRelativePathToImages(path.dirname(videoPath)),
|
||||
path.basename(videoPath) + '_' + this.getConvertedFilePostFix()
|
||||
ProjectPath.TranscodedFolder,
|
||||
ProjectPath.getRelativePathToImages(path.dirname(videoPath)),
|
||||
path.basename(videoPath) + '_' + this.getConvertedFilePostFix()
|
||||
);
|
||||
}
|
||||
|
||||
public static async isValidConvertedPath(
|
||||
convertedPath: string
|
||||
convertedPath: string
|
||||
): Promise<boolean> {
|
||||
const origFilePath = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
path.relative(
|
||||
ProjectPath.TranscodedFolder,
|
||||
convertedPath.substring(0, convertedPath.lastIndexOf('_'))
|
||||
)
|
||||
ProjectPath.ImageFolder,
|
||||
path.relative(
|
||||
ProjectPath.TranscodedFolder,
|
||||
convertedPath.substring(0, convertedPath.lastIndexOf('_'))
|
||||
)
|
||||
);
|
||||
|
||||
const postfix = convertedPath.substring(
|
||||
convertedPath.lastIndexOf('_') + 1,
|
||||
convertedPath.length
|
||||
convertedPath.lastIndexOf('_') + 1,
|
||||
convertedPath.length
|
||||
);
|
||||
|
||||
if (postfix !== this.getConvertedFilePostFix()) {
|
||||
@ -82,8 +82,8 @@ export class VideoProcessing {
|
||||
output: {
|
||||
path: outPath,
|
||||
codec: Config.Media.Video.transcoding.format === 'mp4' ?
|
||||
Config.Media.Video.transcoding.mp4Codec :
|
||||
Config.Media.Video.transcoding.webmCodec,
|
||||
Config.Media.Video.transcoding.mp4Codec :
|
||||
Config.Media.Video.transcoding.webmCodec,
|
||||
format: Config.Media.Video.transcoding.format,
|
||||
crf: Config.Media.Video.transcoding.crf,
|
||||
preset: Config.Media.Video.transcoding.preset,
|
||||
@ -93,17 +93,17 @@ export class VideoProcessing {
|
||||
|
||||
if (metaData.bitRate > Config.Media.Video.transcoding.bitRate) {
|
||||
renderInput.output.bitRate =
|
||||
Config.Media.Video.transcoding.bitRate;
|
||||
Config.Media.Video.transcoding.bitRate;
|
||||
}
|
||||
if (metaData.fps > Config.Media.Video.transcoding.fps) {
|
||||
renderInput.output.fps = Config.Media.Video.transcoding.fps;
|
||||
}
|
||||
|
||||
if (
|
||||
Config.Media.Video.transcoding.resolution < metaData.size.height
|
||||
Config.Media.Video.transcoding.resolution < metaData.size.height
|
||||
) {
|
||||
renderInput.output.resolution =
|
||||
Config.Media.Video.transcoding.resolution;
|
||||
Config.Media.Video.transcoding.resolution;
|
||||
}
|
||||
|
||||
const outDir = path.dirname(renderInput.output.path);
|
||||
@ -119,14 +119,14 @@ export class VideoProcessing {
|
||||
|
||||
protected static getConvertedFilePostFix(): string {
|
||||
return (
|
||||
Math.round(Config.Media.Video.transcoding.bitRate / 1024) +
|
||||
'k' +
|
||||
(Config.Media.Video.transcoding.format === 'mp4' ?
|
||||
Config.Media.Video.transcoding.mp4Codec :
|
||||
Config.Media.Video.transcoding.webmCodec).toString().toLowerCase() +
|
||||
Config.Media.Video.transcoding.resolution +
|
||||
'.' +
|
||||
Config.Media.Video.transcoding.format.toLowerCase()
|
||||
Math.round(Config.Media.Video.transcoding.bitRate / 1024) +
|
||||
'k' +
|
||||
(Config.Media.Video.transcoding.format === 'mp4' ?
|
||||
Config.Media.Video.transcoding.mp4Codec :
|
||||
Config.Media.Video.transcoding.webmCodec).toString().toLowerCase() +
|
||||
Config.Media.Video.transcoding.resolution +
|
||||
'.' +
|
||||
Config.Media.Video.transcoding.format.toLowerCase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -22,17 +22,17 @@ export class JobManager implements IJobListener {
|
||||
|
||||
protected get JobRunning(): boolean {
|
||||
return (
|
||||
JobRepository.Instance.getAvailableJobs().findIndex(
|
||||
(j): boolean => j.InProgress === true
|
||||
) !== -1
|
||||
JobRepository.Instance.getAvailableJobs().findIndex(
|
||||
(j): boolean => j.InProgress === true
|
||||
) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
protected get JobNoParallelRunning(): boolean {
|
||||
return (
|
||||
JobRepository.Instance.getAvailableJobs().findIndex(
|
||||
(j): boolean => j.InProgress === true && j.allowParallelRun
|
||||
) !== -1
|
||||
JobRepository.Instance.getAvailableJobs().findIndex(
|
||||
(j): boolean => j.InProgress === true && j.allowParallelRun
|
||||
) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
@ -41,14 +41,14 @@ export class JobManager implements IJobListener {
|
||||
}
|
||||
|
||||
async run<T>(
|
||||
jobName: string,
|
||||
config: T,
|
||||
soloRun: boolean,
|
||||
allowParallelRun: boolean
|
||||
jobName: string,
|
||||
config: T,
|
||||
soloRun: boolean,
|
||||
allowParallelRun: boolean
|
||||
): Promise<void> {
|
||||
if (
|
||||
(allowParallelRun === false && this.JobRunning === true) ||
|
||||
this.JobNoParallelRunning === true
|
||||
(allowParallelRun === false && this.JobRunning === true) ||
|
||||
this.JobNoParallelRunning === true
|
||||
) {
|
||||
throw new Error('Can\'t start this job while another is running');
|
||||
}
|
||||
@ -76,42 +76,42 @@ export class JobManager implements IJobListener {
|
||||
};
|
||||
|
||||
onJobFinished = async (
|
||||
job: IJob<any>,
|
||||
state: JobProgressStates,
|
||||
soloRun: boolean
|
||||
job: IJob<unknown>,
|
||||
state: JobProgressStates,
|
||||
soloRun: boolean
|
||||
): Promise<void> => {
|
||||
// if it was not finished peacefully or was a soloRun, do not start the next one
|
||||
if (state !== JobProgressStates.finished || soloRun === true) {
|
||||
return;
|
||||
}
|
||||
const sch = Config.Jobs.scheduled.find(
|
||||
(s): boolean => s.jobName === job.Name
|
||||
(s): boolean => s.jobName === job.Name
|
||||
);
|
||||
if (sch) {
|
||||
const children = Config.Jobs.scheduled.filter(
|
||||
(s): boolean =>
|
||||
s.trigger.type === JobTriggerType.after &&
|
||||
(s.trigger as AfterJobTrigger).afterScheduleName === sch.name
|
||||
(s): boolean =>
|
||||
s.trigger.type === JobTriggerType.after &&
|
||||
(s.trigger as AfterJobTrigger).afterScheduleName === sch.name
|
||||
);
|
||||
for (const item of children) {
|
||||
try {
|
||||
await this.run(
|
||||
item.jobName,
|
||||
item.config,
|
||||
false,
|
||||
item.allowParallelRun
|
||||
item.jobName,
|
||||
item.config,
|
||||
false,
|
||||
item.allowParallelRun
|
||||
);
|
||||
} catch (e) {
|
||||
NotificationManager.warning(
|
||||
'Job running error:' + item.name,
|
||||
e.toString()
|
||||
'Job running error:' + item.name,
|
||||
e.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getAvailableJobs(): IJob<any>[] {
|
||||
getAvailableJobs(): IJob<unknown>[] {
|
||||
return JobRepository.Instance.getAvailableJobs();
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ export class JobManager implements IJobListener {
|
||||
Config.Jobs.scheduled.forEach((s): void => this.runSchedule(s));
|
||||
}
|
||||
|
||||
protected findJob<T = any>(jobName: string): IJob<T> {
|
||||
protected findJob<T = unknown>(jobName: string): IJob<T> {
|
||||
return this.getAvailableJobs().find((t): boolean => t.Name === jobName);
|
||||
}
|
||||
|
||||
@ -138,25 +138,25 @@ export class JobManager implements IJobListener {
|
||||
*/
|
||||
private runSchedule(schedule: JobScheduleDTO): void {
|
||||
const nextDate = JobScheduleDTOUtils.getNextRunningDate(
|
||||
new Date(),
|
||||
schedule
|
||||
new Date(),
|
||||
schedule
|
||||
);
|
||||
if (nextDate && nextDate.getTime() > Date.now()) {
|
||||
Logger.debug(
|
||||
LOG_TAG,
|
||||
'running schedule: ' +
|
||||
schedule.jobName +
|
||||
' at ' +
|
||||
nextDate.toLocaleString(undefined, {hour12: false})
|
||||
LOG_TAG,
|
||||
'running schedule: ' +
|
||||
schedule.jobName +
|
||||
' at ' +
|
||||
nextDate.toLocaleString(undefined, {hour12: false})
|
||||
);
|
||||
|
||||
const timer: NodeJS.Timeout = setTimeout(async (): Promise<void> => {
|
||||
this.timers = this.timers.filter((t): boolean => t.timer !== timer);
|
||||
await this.run(
|
||||
schedule.jobName,
|
||||
schedule.config,
|
||||
false,
|
||||
schedule.allowParallelRun
|
||||
schedule.jobName,
|
||||
schedule.config,
|
||||
false,
|
||||
schedule.allowParallelRun
|
||||
);
|
||||
this.runSchedule(schedule);
|
||||
}, nextDate.getTime() - Date.now());
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { promises as fsp } from 'fs';
|
||||
import {promises as fsp} from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ProjectPath } from '../../ProjectPath';
|
||||
import { Config } from '../../../common/config/private/Config';
|
||||
import {
|
||||
JobProgressDTO,
|
||||
JobProgressStates,
|
||||
} from '../../../common/entities/job/JobProgressDTO';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {JobProgressDTO, JobProgressStates,} from '../../../common/entities/job/JobProgressDTO';
|
||||
|
||||
export class JobProgressManager {
|
||||
private static readonly VERSION = 3;
|
||||
@ -31,7 +28,7 @@ export class JobProgressManager {
|
||||
for (const key of Object.keys(this.db.progresses)) {
|
||||
m[key] = this.db.progresses[key].progress;
|
||||
if (
|
||||
this.db.progresses[key].progress.state === JobProgressStates.running
|
||||
this.db.progresses[key].progress.state === JobProgressStates.running
|
||||
) {
|
||||
m[key].time.end = Date.now();
|
||||
}
|
||||
@ -40,7 +37,7 @@ export class JobProgressManager {
|
||||
}
|
||||
|
||||
onJobProgressUpdate(progress: JobProgressDTO): void {
|
||||
this.db.progresses[progress.HashName] = { progress, timestamp: Date.now() };
|
||||
this.db.progresses[progress.HashName] = {progress, timestamp: Date.now()};
|
||||
this.delayedSave();
|
||||
}
|
||||
|
||||
@ -58,14 +55,14 @@ export class JobProgressManager {
|
||||
this.db = db;
|
||||
|
||||
while (
|
||||
Object.keys(this.db.progresses).length >
|
||||
Config.Jobs.maxSavedProgress
|
||||
) {
|
||||
Object.keys(this.db.progresses).length >
|
||||
Config.Jobs.maxSavedProgress
|
||||
) {
|
||||
let min: string = null;
|
||||
for (const key of Object.keys(this.db.progresses)) {
|
||||
if (
|
||||
min === null ||
|
||||
this.db.progresses[min].timestamp > this.db.progresses[key].timestamp
|
||||
min === null ||
|
||||
this.db.progresses[min].timestamp > this.db.progresses[key].timestamp
|
||||
) {
|
||||
min = key;
|
||||
}
|
||||
@ -75,8 +72,8 @@ export class JobProgressManager {
|
||||
|
||||
for (const key of Object.keys(this.db.progresses)) {
|
||||
if (
|
||||
this.db.progresses[key].progress.state === JobProgressStates.running ||
|
||||
this.db.progresses[key].progress.state === JobProgressStates.cancelling
|
||||
this.db.progresses[key].progress.state === JobProgressStates.running ||
|
||||
this.db.progresses[key].progress.state === JobProgressStates.cancelling
|
||||
) {
|
||||
this.db.progresses[key].progress.state = JobProgressStates.interrupted;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {AlbumCoverRestJob} from './jobs/AlbumCoverResetJob';
|
||||
|
||||
export class JobRepository {
|
||||
private static instance: JobRepository = null;
|
||||
availableJobs: { [key: string]: IJob<any> } = {};
|
||||
availableJobs: { [key: string]: IJob<unknown> } = {};
|
||||
|
||||
public static get Instance(): JobRepository {
|
||||
if (JobRepository.instance == null) {
|
||||
@ -23,11 +23,11 @@ export class JobRepository {
|
||||
return JobRepository.instance;
|
||||
}
|
||||
|
||||
getAvailableJobs(): IJob<any>[] {
|
||||
getAvailableJobs(): IJob<unknown>[] {
|
||||
return Object.values(this.availableJobs).filter((t) => t.Supported);
|
||||
}
|
||||
|
||||
register(job: IJob<any>): void {
|
||||
register(job: IJob<unknown>): void {
|
||||
if (typeof this.availableJobs[job.Name] !== 'undefined') {
|
||||
throw new Error('Job already exist:' + job.Name);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ export class AlbumCoverFillingJob extends Job {
|
||||
if (!this.directoryToSetCover) {
|
||||
this.Progress.log('Loading Directories to process');
|
||||
this.directoryToSetCover =
|
||||
await ObjectManagers.getInstance().CoverManager.getPartialDirsWithoutCovers();
|
||||
await ObjectManagers.getInstance().CoverManager.getPartialDirsWithoutCovers();
|
||||
this.Progress.Left = this.directoryToSetCover.length + 2;
|
||||
return true;
|
||||
}
|
||||
@ -57,7 +57,7 @@ export class AlbumCoverFillingJob extends Job {
|
||||
private async stepDirectoryCover(): Promise<boolean> {
|
||||
if (this.directoryToSetCover.length === 0) {
|
||||
this.directoryToSetCover =
|
||||
await ObjectManagers.getInstance().CoverManager.getPartialDirsWithoutCovers();
|
||||
await ObjectManagers.getInstance().CoverManager.getPartialDirsWithoutCovers();
|
||||
// double check if there is really no more
|
||||
if (this.directoryToSetCover.length > 0) {
|
||||
return true; // continue
|
||||
@ -70,7 +70,7 @@ export class AlbumCoverFillingJob extends Job {
|
||||
this.Progress.Left = this.directoryToSetCover.length;
|
||||
|
||||
await ObjectManagers.getInstance().CoverManager.setAndGetCoverForDirectory(
|
||||
directory
|
||||
directory
|
||||
);
|
||||
this.Progress.Processed++;
|
||||
return true;
|
||||
|
@ -69,10 +69,10 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
|
||||
protected async step(): Promise<boolean> {
|
||||
if (
|
||||
this.fileQueue.length === 0 &&
|
||||
((this.directoryQueue.length === 0 && !this.config.indexedOnly) ||
|
||||
(this.config.indexedOnly &&
|
||||
this.DBProcessing.hasMoreMedia === false))) {
|
||||
this.fileQueue.length === 0 &&
|
||||
((this.directoryQueue.length === 0 && !this.config.indexedOnly) ||
|
||||
(this.config.indexedOnly &&
|
||||
this.DBProcessing.hasMoreMedia === false))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -113,11 +113,11 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Logger.error(
|
||||
LOG_TAG,
|
||||
'Error during processing file:' + filePath + ', ' + e.toString()
|
||||
LOG_TAG,
|
||||
'Error during processing file:' + filePath + ', ' + e.toString()
|
||||
);
|
||||
this.Progress.log(
|
||||
'Error during processing file:' + filePath + ', ' + e.toString()
|
||||
'Error during processing file:' + filePath + ', ' + e.toString()
|
||||
);
|
||||
}
|
||||
|
||||
@ -128,8 +128,8 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
const directory = this.directoryQueue.shift();
|
||||
this.Progress.log('scanning directory: ' + directory);
|
||||
const scanned = await DiskManager.scanDirectoryNoMetadata(
|
||||
directory,
|
||||
this.scanFilter
|
||||
directory,
|
||||
this.scanFilter
|
||||
);
|
||||
for (const item of scanned.directories) {
|
||||
this.directoryQueue.push(path.join(item.path, item.name));
|
||||
@ -144,12 +144,12 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
}
|
||||
for (const item of scannedAndFiltered) {
|
||||
this.fileQueue.push(
|
||||
path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.directory.path,
|
||||
item.directory.name,
|
||||
item.name
|
||||
)
|
||||
path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.directory.path,
|
||||
item.directory.name,
|
||||
item.name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -162,12 +162,12 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
}
|
||||
for (const item of scannedAndFiltered) {
|
||||
this.fileQueue.push(
|
||||
path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.directory.path,
|
||||
item.directory.name,
|
||||
item.name
|
||||
)
|
||||
path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.directory.path,
|
||||
item.directory.name,
|
||||
item.name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -175,8 +175,8 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
|
||||
private async loadMediaFilesFromDB(): Promise<void> {
|
||||
if (this.scanFilter.noVideo === true &&
|
||||
this.scanFilter.noPhoto === true &&
|
||||
this.scanFilter.noMetaFile === true) {
|
||||
this.scanFilter.noPhoto === true &&
|
||||
this.scanFilter.noMetaFile === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -190,7 +190,7 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
};
|
||||
const connection = await SQLConnection.getConnection();
|
||||
if (!this.scanFilter.noVideo ||
|
||||
!this.scanFilter.noPhoto) {
|
||||
!this.scanFilter.noPhoto) {
|
||||
|
||||
let usedEntity = MediaEntity;
|
||||
|
||||
@ -201,13 +201,13 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
}
|
||||
|
||||
const result = await connection
|
||||
.getRepository(usedEntity)
|
||||
.createQueryBuilder('media')
|
||||
.select(['media.name', 'directory.name', 'directory.path'])
|
||||
.leftJoin('media.directory', 'directory')
|
||||
.offset(this.DBProcessing.mediaLoaded)
|
||||
.limit(Config.Jobs.mediaProcessingBatchSize)
|
||||
.getMany();
|
||||
.getRepository(usedEntity)
|
||||
.createQueryBuilder('media')
|
||||
.select(['media.name', 'directory.name', 'directory.path'])
|
||||
.leftJoin('media.directory', 'directory')
|
||||
.offset(this.DBProcessing.mediaLoaded)
|
||||
.limit(Config.Jobs.mediaProcessingBatchSize)
|
||||
.getMany();
|
||||
|
||||
hasMoreFile.media = result.length > 0;
|
||||
this.DBProcessing.mediaLoaded += result.length;
|
||||
@ -219,25 +219,25 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
}
|
||||
for (const item of scannedAndFiltered) {
|
||||
this.fileQueue.push(
|
||||
path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.directory.path,
|
||||
item.directory.name,
|
||||
item.name
|
||||
)
|
||||
path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.directory.path,
|
||||
item.directory.name,
|
||||
item.name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!this.scanFilter.noMetaFile) {
|
||||
|
||||
const result = await connection
|
||||
.getRepository(FileEntity)
|
||||
.createQueryBuilder('file')
|
||||
.select(['file.name', 'directory.name', 'directory.path'])
|
||||
.leftJoin('file.directory', 'directory')
|
||||
.offset(this.DBProcessing.mediaLoaded)
|
||||
.limit(Config.Jobs.mediaProcessingBatchSize)
|
||||
.getMany();
|
||||
.getRepository(FileEntity)
|
||||
.createQueryBuilder('file')
|
||||
.select(['file.name', 'directory.name', 'directory.path'])
|
||||
.leftJoin('file.directory', 'directory')
|
||||
.offset(this.DBProcessing.mediaLoaded)
|
||||
.limit(Config.Jobs.mediaProcessingBatchSize)
|
||||
.getMany();
|
||||
|
||||
|
||||
hasMoreFile.metafile = result.length > 0;
|
||||
@ -250,12 +250,12 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
}
|
||||
for (const item of scannedAndFiltered) {
|
||||
this.fileQueue.push(
|
||||
path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.directory.path,
|
||||
item.directory.name,
|
||||
item.name
|
||||
)
|
||||
path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
item.directory.path,
|
||||
item.directory.name,
|
||||
item.name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -264,14 +264,14 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
|
||||
private async countMediaFromDB(): Promise<number> {
|
||||
if (this.scanFilter.noVideo === true &&
|
||||
this.scanFilter.noPhoto === true &&
|
||||
this.scanFilter.noMetaFile === true) {
|
||||
this.scanFilter.noPhoto === true &&
|
||||
this.scanFilter.noMetaFile === true) {
|
||||
return;
|
||||
}
|
||||
let count = 0;
|
||||
const connection = await SQLConnection.getConnection();
|
||||
if (!this.scanFilter.noVideo ||
|
||||
!this.scanFilter.noPhoto) {
|
||||
!this.scanFilter.noPhoto) {
|
||||
|
||||
let usedEntity = MediaEntity;
|
||||
|
||||
@ -282,16 +282,16 @@ export abstract class FileJob<S extends { indexedOnly?: boolean } = { indexedOnl
|
||||
}
|
||||
|
||||
count += await connection
|
||||
.getRepository(usedEntity)
|
||||
.createQueryBuilder('media')
|
||||
.getCount();
|
||||
.getRepository(usedEntity)
|
||||
.createQueryBuilder('media')
|
||||
.getCount();
|
||||
}
|
||||
if (!this.scanFilter.noMetaFile) {
|
||||
|
||||
count += await connection
|
||||
.getRepository(FileEntity)
|
||||
.createQueryBuilder('file')
|
||||
.getCount();
|
||||
.getRepository(FileEntity)
|
||||
.createQueryBuilder('file')
|
||||
.getCount();
|
||||
|
||||
}
|
||||
return count;
|
||||
|
@ -22,7 +22,7 @@ export class GPXCompressionJob extends FileJob {
|
||||
|
||||
protected async shouldProcess(fPath: string): Promise<boolean> {
|
||||
return !(await GPXProcessing.compressedGPXExist(
|
||||
fPath
|
||||
fPath
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { JobDTO } from '../../../../common/entities/job/JobDTO';
|
||||
import { JobProgress } from './JobProgress';
|
||||
import { IJobListener } from './IJobListener';
|
||||
import {JobDTO} from '../../../../common/entities/job/JobDTO';
|
||||
import {JobProgress} from './JobProgress';
|
||||
import {IJobListener} from './IJobListener';
|
||||
|
||||
export interface IJob<T> extends JobDTO {
|
||||
Name: string;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { JobProgress } from './JobProgress';
|
||||
import { IJob } from './IJob';
|
||||
import { JobProgressStates } from '../../../../common/entities/job/JobProgressDTO';
|
||||
import {JobProgress} from './JobProgress';
|
||||
import {IJob} from './IJob';
|
||||
import {JobProgressStates} from '../../../../common/entities/job/JobProgressDTO';
|
||||
|
||||
export interface IJobListener {
|
||||
onJobFinished(
|
||||
job: IJob<any>,
|
||||
state: JobProgressStates,
|
||||
soloRun: boolean
|
||||
job: IJob<unknown>,
|
||||
state: JobProgressStates,
|
||||
soloRun: boolean
|
||||
): void;
|
||||
|
||||
onProgressUpdate(progress: JobProgress): void;
|
||||
|
@ -14,7 +14,7 @@ import {FileDTO} from '../../../../common/entities/FileDTO';
|
||||
const LOG_TAG = '[IndexingJob]';
|
||||
|
||||
export class IndexingJob<
|
||||
S extends { indexChangesOnly: boolean } = { indexChangesOnly: boolean }
|
||||
S extends { indexChangesOnly: boolean } = { indexChangesOnly: boolean }
|
||||
> extends Job<S> {
|
||||
public readonly Name = DefaultsJobs[DefaultsJobs.Indexing];
|
||||
directoriesToIndex: string[] = [];
|
||||
@ -66,9 +66,9 @@ export class IndexingJob<
|
||||
scanned = await ObjectManagers.getInstance().GalleryManager.selectDirStructure(directory);
|
||||
// If not modified and it was scanned before, dir is up-to-date
|
||||
if (
|
||||
scanned &&
|
||||
scanned.lastModified === lastModified &&
|
||||
scanned.lastScanned != null
|
||||
scanned &&
|
||||
scanned.lastModified === lastModified &&
|
||||
scanned.lastScanned != null
|
||||
) {
|
||||
dirChanged = false;
|
||||
}
|
||||
@ -80,9 +80,9 @@ export class IndexingJob<
|
||||
this.Progress.log('Indexing: ' + directory);
|
||||
this.Progress.Processed++;
|
||||
scanned =
|
||||
await ObjectManagers.getInstance().IndexingManager.indexDirectory(
|
||||
directory
|
||||
);
|
||||
await ObjectManagers.getInstance().IndexingManager.indexDirectory(
|
||||
directory
|
||||
);
|
||||
} else {
|
||||
this.Progress.log('Skipping. No change for: ' + directory);
|
||||
this.Progress.Skipped++;
|
||||
|
@ -10,7 +10,7 @@ declare const global: any;
|
||||
|
||||
const LOG_TAG = '[JOB]';
|
||||
|
||||
export abstract class Job<T extends Record<string, any> = Record<string, any>> implements IJob<T> {
|
||||
export abstract class Job<T extends Record<string, unknown> = Record<string, unknown>> implements IJob<T> {
|
||||
public allowParallelRun: boolean = null;
|
||||
protected progress: JobProgress = null;
|
||||
protected config: T;
|
||||
@ -35,36 +35,36 @@ export abstract class Job<T extends Record<string, any> = Record<string, any>> i
|
||||
|
||||
public get InProgress(): boolean {
|
||||
return (
|
||||
this.Progress !== null &&
|
||||
(this.Progress.State === JobProgressStates.running ||
|
||||
this.Progress.State === JobProgressStates.cancelling)
|
||||
this.Progress !== null &&
|
||||
(this.Progress.State === JobProgressStates.running ||
|
||||
this.Progress.State === JobProgressStates.cancelling)
|
||||
);
|
||||
}
|
||||
|
||||
public start(
|
||||
config: T,
|
||||
soloRun = false,
|
||||
allowParallelRun = false
|
||||
config: T,
|
||||
soloRun = false,
|
||||
allowParallelRun = false
|
||||
): Promise<void> {
|
||||
if (this.InProgress === false && this.Supported === true) {
|
||||
Logger.info(
|
||||
LOG_TAG,
|
||||
'Running job ' + (soloRun === true ? 'solo' : '') + ': ' + this.Name
|
||||
LOG_TAG,
|
||||
'Running job ' + (soloRun === true ? 'solo' : '') + ': ' + this.Name
|
||||
);
|
||||
this.soloRun = soloRun;
|
||||
this.allowParallelRun = allowParallelRun;
|
||||
this.config = {} as T;
|
||||
if (this.ConfigTemplate) {
|
||||
this.ConfigTemplate.forEach(ct => (this.config as any)[ct.id] = ct.defaultValue);
|
||||
this.ConfigTemplate.forEach(ct => (this.config as Record<string, unknown>)[ct.id] = ct.defaultValue);
|
||||
}
|
||||
if (config) {
|
||||
for (const key of Object.keys(config)) {
|
||||
(this.config as any)[key] = config[key];
|
||||
(this.config as Record<string, unknown>)[key] = config[key];
|
||||
}
|
||||
}
|
||||
this.progress = new JobProgress(
|
||||
this.Name,
|
||||
JobDTOUtils.getHashName(this.Name, this.config)
|
||||
this.Name,
|
||||
JobDTOUtils.getHashName(this.Name, this.config)
|
||||
);
|
||||
this.progress.OnChange = this.jobListener.onProgressUpdate;
|
||||
const pr = new Promise<void>((resolve): void => {
|
||||
@ -79,11 +79,11 @@ export abstract class Job<T extends Record<string, any> = Record<string, any>> i
|
||||
return pr;
|
||||
} else {
|
||||
Logger.info(
|
||||
LOG_TAG,
|
||||
'Job already running or not supported: ' + this.Name
|
||||
LOG_TAG,
|
||||
'Job already running or not supported: ' + this.Name
|
||||
);
|
||||
return Promise.reject(
|
||||
'Job already running or not supported: ' + this.Name
|
||||
'Job already running or not supported: ' + this.Name
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -137,8 +137,8 @@ export abstract class Job<T extends Record<string, any> = Record<string, any>> i
|
||||
process.nextTick(async (): Promise<void> => {
|
||||
try {
|
||||
if (
|
||||
this.Progress == null ||
|
||||
this.Progress.State !== JobProgressStates.running
|
||||
this.Progress == null ||
|
||||
this.Progress.State !== JobProgressStates.running
|
||||
) {
|
||||
this.onFinish();
|
||||
return;
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
JobProgressDTO,
|
||||
JobProgressLogDTO,
|
||||
JobProgressStates,
|
||||
} from '../../../../common/entities/job/JobProgressDTO';
|
||||
import {JobProgressDTO, JobProgressLogDTO, JobProgressStates,} from '../../../../common/entities/job/JobProgressDTO';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
|
||||
export class JobProgress {
|
||||
@ -20,9 +16,10 @@ export class JobProgress {
|
||||
private logs: { id: number; timestamp: string; comment: string }[] = [];
|
||||
|
||||
constructor(
|
||||
public readonly jobName: string,
|
||||
public readonly HashName: string
|
||||
) {}
|
||||
public readonly jobName: string,
|
||||
public readonly HashName: string
|
||||
) {
|
||||
}
|
||||
|
||||
set OnChange(val: (progress: JobProgress) => void) {
|
||||
this.onChange = val;
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Config } from '../../../../common/config/private/Config';
|
||||
import { DefaultsJobs } from '../../../../common/entities/job/JobDTO';
|
||||
import { FileJob } from './FileJob';
|
||||
import { PhotoProcessing } from '../../fileprocessing/PhotoProcessing';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {DefaultsJobs} from '../../../../common/entities/job/JobDTO';
|
||||
import {FileJob} from './FileJob';
|
||||
import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing';
|
||||
|
||||
export class PhotoConvertingJob extends FileJob {
|
||||
public readonly Name = DefaultsJobs[DefaultsJobs['Photo Converting']];
|
||||
|
||||
constructor() {
|
||||
super({ noVideo: true, noMetaFile: true });
|
||||
super({noVideo: true, noMetaFile: true});
|
||||
}
|
||||
|
||||
public get Supported(): boolean {
|
||||
@ -16,8 +16,8 @@ export class PhotoConvertingJob extends FileJob {
|
||||
|
||||
protected async shouldProcess(mPath: string): Promise<boolean> {
|
||||
return !(await PhotoProcessing.convertedPhotoExist(
|
||||
mPath,
|
||||
Config.Media.Photo.Converting.resolution
|
||||
mPath,
|
||||
Config.Media.Photo.Converting.resolution
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,8 @@ export class TempFolderCleaningJob extends Job {
|
||||
|
||||
protected async isValidDirectory(filePath: string): Promise<boolean> {
|
||||
const originalPath = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
path.relative(ProjectPath.TranscodedFolder, filePath)
|
||||
ProjectPath.ImageFolder,
|
||||
path.relative(ProjectPath.TranscodedFolder, filePath)
|
||||
);
|
||||
try {
|
||||
await fs.promises.access(originalPath);
|
||||
@ -52,7 +52,7 @@ export class TempFolderCleaningJob extends Job {
|
||||
|
||||
protected async readDir(dirPath: string): Promise<string[]> {
|
||||
return (await fs.promises.readdir(dirPath)).map((f) =>
|
||||
path.normalize(path.join(dirPath, f))
|
||||
path.normalize(path.join(dirPath, f))
|
||||
);
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ export class TempFolderCleaningJob extends Job {
|
||||
this.Progress.log('skipping: ' + filePath);
|
||||
this.Progress.Skipped++;
|
||||
this.directoryQueue = this.directoryQueue.concat(
|
||||
await this.readDir(filePath)
|
||||
await this.readDir(filePath)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -29,9 +29,9 @@ export class ThumbnailGenerationJob extends FileJob<{
|
||||
}
|
||||
|
||||
start(
|
||||
config: { sizes?: number[]; indexedOnly?: boolean },
|
||||
soloRun = false,
|
||||
allowParallelRun = false
|
||||
config: { sizes?: number[]; indexedOnly?: boolean },
|
||||
soloRun = false,
|
||||
allowParallelRun = false
|
||||
): Promise<void> {
|
||||
if (!config || !config.sizes || !Array.isArray(config.sizes) || config.sizes.length === 0) {
|
||||
config = config || {};
|
||||
@ -40,9 +40,9 @@ export class ThumbnailGenerationJob extends FileJob<{
|
||||
for (const item of config.sizes) {
|
||||
if (Config.Media.Thumbnail.thumbnailSizes.indexOf(item) === -1) {
|
||||
throw new Error(
|
||||
'unknown thumbnails size: ' +
|
||||
item +
|
||||
'. Add it to the possible thumbnail sizes.'
|
||||
'unknown thumbnails size: ' +
|
||||
item +
|
||||
'. Add it to the possible thumbnail sizes.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -70,12 +70,12 @@ export class ThumbnailGenerationJob extends FileJob<{
|
||||
protected async processFile(mPath: string): Promise<void> {
|
||||
for (const item of this.config.sizes) {
|
||||
await PhotoProcessing.generateThumbnail(
|
||||
mPath,
|
||||
item,
|
||||
MediaDTOUtils.isVideoPath(mPath)
|
||||
? ThumbnailSourceType.Video
|
||||
: ThumbnailSourceType.Photo,
|
||||
false
|
||||
mPath,
|
||||
item,
|
||||
MediaDTOUtils.isVideoPath(mPath)
|
||||
? ThumbnailSourceType.Video
|
||||
: ThumbnailSourceType.Photo,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -85,15 +85,15 @@ export class TopPickSendJob extends Job<{
|
||||
this.mediaList = [];
|
||||
for (let i = 0; i < this.config.mediaPick.length; ++i) {
|
||||
const media = await ObjectManagers.getInstance().SearchManager
|
||||
.getNMedia(this.config.mediaPick[i].searchQuery, this.config.mediaPick[i].sortBy, this.config.mediaPick[i].pick);
|
||||
.getNMedia(this.config.mediaPick[i].searchQuery, this.config.mediaPick[i].sortBy, this.config.mediaPick[i].pick);
|
||||
this.Progress.log('Find ' + media.length + ' photos and videos from ' + (i + 1) + '. load');
|
||||
this.mediaList = this.mediaList.concat(media);
|
||||
}
|
||||
|
||||
// make the list unique
|
||||
this.mediaList = this.mediaList
|
||||
.filter((value, index, arr) =>
|
||||
arr.findIndex(m => MediaDTOUtils.equals(m, value)) === index);
|
||||
.filter((value, index, arr) =>
|
||||
arr.findIndex(m => MediaDTOUtils.equals(m, value)) === index);
|
||||
|
||||
this.Progress.Processed++;
|
||||
// console.log(this.mediaList);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Config } from '../../../../common/config/private/Config';
|
||||
import { DefaultsJobs } from '../../../../common/entities/job/JobDTO';
|
||||
import { FileJob } from './FileJob';
|
||||
import { VideoProcessing } from '../../fileprocessing/VideoProcessing';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {DefaultsJobs} from '../../../../common/entities/job/JobDTO';
|
||||
import {FileJob} from './FileJob';
|
||||
import {VideoProcessing} from '../../fileprocessing/VideoProcessing';
|
||||
|
||||
declare const global: any;
|
||||
|
||||
@ -9,7 +9,7 @@ export class VideoConvertingJob extends FileJob {
|
||||
public readonly Name = DefaultsJobs[DefaultsJobs['Video Converting']];
|
||||
|
||||
constructor() {
|
||||
super({ noPhoto: true, noMetaFile: true });
|
||||
super({noPhoto: true, noMetaFile: true});
|
||||
}
|
||||
|
||||
public get Supported(): boolean {
|
||||
|
@ -13,24 +13,24 @@ export class EmailMediaMessenger {
|
||||
transporter: Transporter;
|
||||
|
||||
constructor() {
|
||||
this.transporter = createTransport({
|
||||
host: Config.Messaging.Email.smtp.host,
|
||||
port: Config.Messaging.Email.smtp.port,
|
||||
secure: Config.Messaging.Email.smtp.secure,
|
||||
requireTLS: Config.Messaging.Email.smtp.requireTLS,
|
||||
auth: {
|
||||
user: Config.Messaging.Email.smtp.user,
|
||||
pass: Config.Messaging.Email.smtp.password
|
||||
}
|
||||
});
|
||||
this.transporter = createTransport({
|
||||
host: Config.Messaging.Email.smtp.host,
|
||||
port: Config.Messaging.Email.smtp.port,
|
||||
secure: Config.Messaging.Email.smtp.secure,
|
||||
requireTLS: Config.Messaging.Email.smtp.requireTLS,
|
||||
auth: {
|
||||
user: Config.Messaging.Email.smtp.user,
|
||||
pass: Config.Messaging.Email.smtp.password
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getThumbnail(m: MediaDTO) {
|
||||
return await PhotoProcessing.generateThumbnail(
|
||||
path.join(ProjectPath.ImageFolder, m.directory.path, m.directory.name, m.name),
|
||||
Config.Media.Thumbnail.thumbnailSizes[0],
|
||||
MediaDTOUtils.isPhoto(m) ? ThumbnailSourceType.Photo : ThumbnailSourceType.Video,
|
||||
false
|
||||
path.join(ProjectPath.ImageFolder, m.directory.path, m.directory.name, m.name),
|
||||
Config.Media.Thumbnail.thumbnailSizes[0],
|
||||
MediaDTOUtils.isPhoto(m) ? ThumbnailSourceType.Photo : ThumbnailSourceType.Video,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@ -42,22 +42,22 @@ export class EmailMediaMessenger {
|
||||
|
||||
const attachments = [];
|
||||
const htmlStart = '<h1 style="text-align: center; margin-bottom: 2em">' + Config.Server.applicationTitle + '</h1>\n' +
|
||||
'<h3>' + mailSettings.text + '</h3>\n' +
|
||||
'<table style="margin-left: auto; margin-right: auto;">\n' +
|
||||
' <tbody>\n';
|
||||
'<h3>' + mailSettings.text + '</h3>\n' +
|
||||
'<table style="margin-left: auto; margin-right: auto;">\n' +
|
||||
' <tbody>\n';
|
||||
const htmlEnd = ' </tr>\n' +
|
||||
' </tbody>\n' +
|
||||
'</table>';
|
||||
' </tbody>\n' +
|
||||
'</table>';
|
||||
let htmlMiddle = '';
|
||||
const numberOfColumns = media.length >= 6 ? 3 : 2;
|
||||
for (let i = 0; i < media.length; ++i) {
|
||||
const thPath = await this.getThumbnail(media[i]);
|
||||
const linkUrl = Utils.concatUrls(Config.Server.publicUrl, '/gallery/', encodeURIComponent(path.join(media[i].directory.path, media[i].directory.name))) +
|
||||
'?' + QueryParams.gallery.photo + '=' + encodeURIComponent(media[i].name);
|
||||
'?' + QueryParams.gallery.photo + '=' + encodeURIComponent(media[i].name);
|
||||
const location = (media[i].metadata as PhotoMetadata).positionData?.country ?
|
||||
(media[i].metadata as PhotoMetadata).positionData?.country :
|
||||
((media[i].metadata as PhotoMetadata).positionData?.city ?
|
||||
(media[i].metadata as PhotoMetadata).positionData?.city : '');
|
||||
(media[i].metadata as PhotoMetadata).positionData?.country :
|
||||
((media[i].metadata as PhotoMetadata).positionData?.city ?
|
||||
(media[i].metadata as PhotoMetadata).positionData?.city : '');
|
||||
const caption = (new Date(media[i].metadata.creationDate)).getFullYear() + (location ? ', ' + location : '');
|
||||
attachments.push({
|
||||
filename: media[i].name,
|
||||
@ -68,9 +68,9 @@ export class EmailMediaMessenger {
|
||||
htmlMiddle += '<tr>';
|
||||
}
|
||||
htmlMiddle += '<td>\n' +
|
||||
' <a style="display: block;text-align: center;" href="' + linkUrl + '"><img alt="' + media[i].name + '" style="max-width: 200px; max-height: 150px; height:auto; width:auto;" src="cid:img' + i + '"/></a>\n' +
|
||||
caption +
|
||||
' </td>\n';
|
||||
' <a style="display: block;text-align: center;" href="' + linkUrl + '"><img alt="' + media[i].name + '" style="max-width: 200px; max-height: 150px; height:auto; width:auto;" src="cid:img' + i + '"/></a>\n' +
|
||||
caption +
|
||||
' </td>\n';
|
||||
|
||||
if (i % numberOfColumns == (numberOfColumns - 1) || i === media.length - 1) {
|
||||
htmlMiddle += '</tr>';
|
||||
|
@ -25,15 +25,15 @@ export class DiskMangerWorker {
|
||||
|
||||
public static pathFromRelativeDirName(relativeDirectoryName: string): string {
|
||||
return path.join(
|
||||
path.dirname(this.normalizeDirPath(relativeDirectoryName)),
|
||||
path.sep
|
||||
path.dirname(this.normalizeDirPath(relativeDirectoryName)),
|
||||
path.sep
|
||||
);
|
||||
}
|
||||
|
||||
public static pathFromParent(parent: { path: string; name: string }): string {
|
||||
return path.join(
|
||||
this.normalizeDirPath(path.join(parent.path, parent.name)),
|
||||
path.sep
|
||||
this.normalizeDirPath(path.join(parent.path, parent.name)),
|
||||
path.sep
|
||||
);
|
||||
}
|
||||
|
||||
@ -45,13 +45,13 @@ export class DiskMangerWorker {
|
||||
}
|
||||
|
||||
public static async excludeDir(
|
||||
name: string,
|
||||
relativeDirectoryName: string,
|
||||
absoluteDirectoryName: string
|
||||
name: string,
|
||||
relativeDirectoryName: string,
|
||||
absoluteDirectoryName: string
|
||||
): Promise<boolean> {
|
||||
if (
|
||||
Config.Indexing.excludeFolderList.length === 0 &&
|
||||
Config.Indexing.excludeFileList.length === 0
|
||||
Config.Indexing.excludeFolderList.length === 0 &&
|
||||
Config.Indexing.excludeFileList.length === 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -87,30 +87,30 @@ export class DiskMangerWorker {
|
||||
}
|
||||
|
||||
public static async scanDirectoryNoMetadata(
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
): Promise<ParentDirectoryDTO<FileDTO>> {
|
||||
settings.noMetadata = true;
|
||||
return (await this.scanDirectory(
|
||||
relativeDirectoryName,
|
||||
settings
|
||||
relativeDirectoryName,
|
||||
settings
|
||||
)) as ParentDirectoryDTO<FileDTO>;
|
||||
}
|
||||
|
||||
public static async scanDirectory(
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
): Promise<ParentDirectoryDTO> {
|
||||
relativeDirectoryName = this.normalizeDirPath(relativeDirectoryName);
|
||||
const directoryName = DiskMangerWorker.dirName(relativeDirectoryName);
|
||||
const directoryParent = this.pathFromRelativeDirName(relativeDirectoryName);
|
||||
const absoluteDirectoryName = path.join(
|
||||
ProjectPath.ImageFolder,
|
||||
relativeDirectoryName
|
||||
ProjectPath.ImageFolder,
|
||||
relativeDirectoryName
|
||||
);
|
||||
|
||||
const stat = await fsp.stat(
|
||||
path.join(ProjectPath.ImageFolder, relativeDirectoryName)
|
||||
path.join(ProjectPath.ImageFolder, relativeDirectoryName)
|
||||
);
|
||||
const directory: ParentDirectoryDTO = {
|
||||
id: null,
|
||||
@ -132,36 +132,36 @@ export class DiskMangerWorker {
|
||||
|
||||
// nothing to scan, we are here for the empty dir
|
||||
if (
|
||||
settings.noPhoto === true &&
|
||||
settings.noMetaFile === true &&
|
||||
settings.noVideo === true
|
||||
settings.noPhoto === true &&
|
||||
settings.noMetaFile === true &&
|
||||
settings.noVideo === true
|
||||
) {
|
||||
return directory;
|
||||
}
|
||||
const list = await fsp.readdir(absoluteDirectoryName);
|
||||
for (const file of list) {
|
||||
const fullFilePath = path.normalize(
|
||||
path.join(absoluteDirectoryName, file)
|
||||
path.join(absoluteDirectoryName, file)
|
||||
);
|
||||
if ((await fsp.stat(fullFilePath)).isDirectory()) {
|
||||
if (
|
||||
settings.noDirectory === true ||
|
||||
settings.coverOnly === true ||
|
||||
(await DiskMangerWorker.excludeDir(
|
||||
file,
|
||||
relativeDirectoryName,
|
||||
absoluteDirectoryName
|
||||
))
|
||||
settings.noDirectory === true ||
|
||||
settings.coverOnly === true ||
|
||||
(await DiskMangerWorker.excludeDir(
|
||||
file,
|
||||
relativeDirectoryName,
|
||||
absoluteDirectoryName
|
||||
))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// create cover directory
|
||||
const d = (await DiskMangerWorker.scanDirectory(
|
||||
path.join(relativeDirectoryName, file),
|
||||
{
|
||||
coverOnly: true,
|
||||
}
|
||||
path.join(relativeDirectoryName, file),
|
||||
{
|
||||
coverOnly: true,
|
||||
}
|
||||
)) as SubDirectoryDTO;
|
||||
|
||||
directory.directories.push(d);
|
||||
@ -174,9 +174,9 @@ export class DiskMangerWorker {
|
||||
name: file,
|
||||
directory: null,
|
||||
metadata:
|
||||
settings.noMetadata === true
|
||||
? null
|
||||
: await MetadataLoader.loadPhotoMetadata(fullFilePath),
|
||||
settings.noMetadata === true
|
||||
? null
|
||||
: await MetadataLoader.loadPhotoMetadata(fullFilePath),
|
||||
} as PhotoDTO;
|
||||
|
||||
if (!directory.cover) {
|
||||
@ -197,9 +197,9 @@ export class DiskMangerWorker {
|
||||
}
|
||||
} else if (VideoProcessing.isVideo(fullFilePath)) {
|
||||
if (
|
||||
Config.Media.Video.enabled === false ||
|
||||
settings.noVideo === true ||
|
||||
settings.coverOnly === true
|
||||
Config.Media.Video.enabled === false ||
|
||||
settings.noVideo === true ||
|
||||
settings.coverOnly === true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@ -208,23 +208,23 @@ export class DiskMangerWorker {
|
||||
name: file,
|
||||
directory: null,
|
||||
metadata:
|
||||
settings.noMetadata === true
|
||||
? null
|
||||
: await MetadataLoader.loadVideoMetadata(fullFilePath),
|
||||
settings.noMetadata === true
|
||||
? null
|
||||
: await MetadataLoader.loadVideoMetadata(fullFilePath),
|
||||
} as VideoDTO);
|
||||
} catch (e) {
|
||||
Logger.warn(
|
||||
'Media loading error, skipping: ' +
|
||||
file +
|
||||
', reason: ' +
|
||||
e.toString()
|
||||
'Media loading error, skipping: ' +
|
||||
file +
|
||||
', reason: ' +
|
||||
e.toString()
|
||||
);
|
||||
}
|
||||
} else if (GPXProcessing.isMetaFile(fullFilePath)) {
|
||||
if (
|
||||
!DiskMangerWorker.isEnabledMetaFile(fullFilePath) ||
|
||||
settings.noMetaFile === true ||
|
||||
settings.coverOnly === true
|
||||
!DiskMangerWorker.isEnabledMetaFile(fullFilePath) ||
|
||||
settings.noMetaFile === true ||
|
||||
settings.coverOnly === true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@ -242,9 +242,9 @@ export class DiskMangerWorker {
|
||||
directory.oldestMedia = Number.MIN_SAFE_INTEGER;
|
||||
|
||||
directory.media.forEach((m) => {
|
||||
directory.youngestMedia = Math.min(m.metadata.creationDate, directory.youngestMedia);
|
||||
directory.oldestMedia = Math.max(m.metadata.creationDate, directory.oldestMedia);
|
||||
}
|
||||
directory.youngestMedia = Math.min(m.metadata.creationDate, directory.youngestMedia);
|
||||
directory.oldestMedia = Math.max(m.metadata.creationDate, directory.oldestMedia);
|
||||
}
|
||||
);
|
||||
|
||||
directory.metaFile.forEach(mf => {
|
||||
|
@ -50,8 +50,8 @@ export class MetadataLoader {
|
||||
metadata.size.height = stream.height;
|
||||
|
||||
if (
|
||||
Utils.isInt32(parseInt('' + stream.rotation, 10)) &&
|
||||
(Math.abs(parseInt('' + stream.rotation, 10)) / 90) % 2 === 1
|
||||
Utils.isInt32(parseInt('' + stream.rotation, 10)) &&
|
||||
(Math.abs(parseInt('' + stream.rotation, 10)) / 90) % 2 === 1
|
||||
) {
|
||||
// noinspection JSSuspiciousNameCombination
|
||||
metadata.size.width = stream.height;
|
||||
@ -60,10 +60,10 @@ export class MetadataLoader {
|
||||
}
|
||||
|
||||
if (
|
||||
Utils.isInt32(Math.floor(parseFloat(stream.duration) * 1000))
|
||||
Utils.isInt32(Math.floor(parseFloat(stream.duration) * 1000))
|
||||
) {
|
||||
metadata.duration = Math.floor(
|
||||
parseFloat(stream.duration) * 1000
|
||||
parseFloat(stream.duration) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
@ -74,39 +74,39 @@ export class MetadataLoader {
|
||||
metadata.fps = parseInt(stream.avg_frame_rate, 10) || null;
|
||||
}
|
||||
metadata.creationDate =
|
||||
Date.parse(stream.tags.creation_time) ||
|
||||
metadata.creationDate;
|
||||
Date.parse(stream.tags.creation_time) ||
|
||||
metadata.creationDate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// For some filetypes (for instance Matroska), bitrate and duration are stored in
|
||||
// the format section, not in the stream section.
|
||||
|
||||
|
||||
// Only use duration from container header if necessary (stream duration is usually more accurate)
|
||||
if (
|
||||
metadata.duration === 0 &&
|
||||
data.format.duration !== undefined &&
|
||||
Utils.isInt32(Math.floor(data.format.duration * 1000))
|
||||
metadata.duration === 0 &&
|
||||
data.format.duration !== undefined &&
|
||||
Utils.isInt32(Math.floor(data.format.duration * 1000))
|
||||
) {
|
||||
metadata.duration = Math.floor(data.format.duration * 1000);
|
||||
}
|
||||
|
||||
|
||||
// Prefer bitrate from container header (includes video and audio)
|
||||
if (
|
||||
data.format.bit_rate !== undefined &&
|
||||
Utils.isInt32(data.format.bit_rate)
|
||||
data.format.bit_rate !== undefined &&
|
||||
Utils.isInt32(data.format.bit_rate)
|
||||
) {
|
||||
metadata.bitRate = data.format.bit_rate;
|
||||
}
|
||||
|
||||
if (
|
||||
data.format.tags !== undefined &&
|
||||
typeof data.format.tags.creation_time === 'string'
|
||||
data.format.tags !== undefined &&
|
||||
typeof data.format.tags.creation_time === 'string'
|
||||
) {
|
||||
metadata.creationDate =
|
||||
Date.parse(data.format.tags.creation_time) ||
|
||||
metadata.creationDate;
|
||||
Date.parse(data.format.tags.creation_time) ||
|
||||
metadata.creationDate;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-empty
|
||||
@ -158,13 +158,13 @@ export class MetadataLoader {
|
||||
try {
|
||||
const exif = ExifParserFactory.create(data).parse();
|
||||
if (
|
||||
exif.tags.ISO ||
|
||||
exif.tags.Model ||
|
||||
exif.tags.Make ||
|
||||
exif.tags.FNumber ||
|
||||
exif.tags.ExposureTime ||
|
||||
exif.tags.FocalLength ||
|
||||
exif.tags.LensModel
|
||||
exif.tags.ISO ||
|
||||
exif.tags.Model ||
|
||||
exif.tags.Make ||
|
||||
exif.tags.FNumber ||
|
||||
exif.tags.ExposureTime ||
|
||||
exif.tags.FocalLength ||
|
||||
exif.tags.LensModel
|
||||
) {
|
||||
if (exif.tags.Model && exif.tags.Model !== '') {
|
||||
metadata.cameraData = metadata.cameraData || {};
|
||||
@ -185,50 +185,50 @@ export class MetadataLoader {
|
||||
if (Utils.isFloat32(exif.tags.FocalLength)) {
|
||||
metadata.cameraData = metadata.cameraData || {};
|
||||
metadata.cameraData.focalLength = parseFloat(
|
||||
'' + exif.tags.FocalLength
|
||||
'' + exif.tags.FocalLength
|
||||
);
|
||||
}
|
||||
if (Utils.isFloat32(exif.tags.ExposureTime)) {
|
||||
metadata.cameraData = metadata.cameraData || {};
|
||||
metadata.cameraData.exposure = parseFloat(
|
||||
parseFloat('' + exif.tags.ExposureTime).toFixed(6)
|
||||
parseFloat('' + exif.tags.ExposureTime).toFixed(6)
|
||||
);
|
||||
}
|
||||
if (Utils.isFloat32(exif.tags.FNumber)) {
|
||||
metadata.cameraData = metadata.cameraData || {};
|
||||
metadata.cameraData.fStop = parseFloat(
|
||||
parseFloat('' + exif.tags.FNumber).toFixed(2)
|
||||
parseFloat('' + exif.tags.FNumber).toFixed(2)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
!isNaN(exif.tags.GPSLatitude) ||
|
||||
exif.tags.GPSLongitude ||
|
||||
exif.tags.GPSAltitude
|
||||
!isNaN(exif.tags.GPSLatitude) ||
|
||||
exif.tags.GPSLongitude ||
|
||||
exif.tags.GPSAltitude
|
||||
) {
|
||||
metadata.positionData = metadata.positionData || {};
|
||||
metadata.positionData.GPSData = {};
|
||||
|
||||
if (Utils.isFloat32(exif.tags.GPSLongitude)) {
|
||||
metadata.positionData.GPSData.longitude = parseFloat(
|
||||
exif.tags.GPSLongitude.toFixed(6)
|
||||
exif.tags.GPSLongitude.toFixed(6)
|
||||
);
|
||||
}
|
||||
if (Utils.isFloat32(exif.tags.GPSLatitude)) {
|
||||
metadata.positionData.GPSData.latitude = parseFloat(
|
||||
exif.tags.GPSLatitude.toFixed(6)
|
||||
exif.tags.GPSLatitude.toFixed(6)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
exif.tags.CreateDate ||
|
||||
exif.tags.DateTimeOriginal ||
|
||||
exif.tags.ModifyDate
|
||||
exif.tags.CreateDate ||
|
||||
exif.tags.DateTimeOriginal ||
|
||||
exif.tags.ModifyDate
|
||||
) {
|
||||
metadata.creationDate =
|
||||
(exif.tags.DateTimeOriginal ||
|
||||
exif.tags.CreateDate ||
|
||||
exif.tags.ModifyDate) * 1000;
|
||||
(exif.tags.DateTimeOriginal ||
|
||||
exif.tags.CreateDate ||
|
||||
exif.tags.ModifyDate) * 1000;
|
||||
}
|
||||
if (exif.imageSize) {
|
||||
metadata.size = {
|
||||
@ -236,16 +236,16 @@ export class MetadataLoader {
|
||||
height: exif.imageSize.height,
|
||||
};
|
||||
} else if (
|
||||
exif.tags.RelatedImageWidth &&
|
||||
exif.tags.RelatedImageHeight
|
||||
exif.tags.RelatedImageWidth &&
|
||||
exif.tags.RelatedImageHeight
|
||||
) {
|
||||
metadata.size = {
|
||||
width: exif.tags.RelatedImageWidth,
|
||||
height: exif.tags.RelatedImageHeight,
|
||||
};
|
||||
} else if (
|
||||
exif.tags.ImageWidth &&
|
||||
exif.tags.ImageHeight
|
||||
exif.tags.ImageWidth &&
|
||||
exif.tags.ImageHeight
|
||||
) {
|
||||
metadata.size = {
|
||||
width: exif.tags.ImageWidth,
|
||||
@ -270,21 +270,21 @@ export class MetadataLoader {
|
||||
if (iptcData.country_or_primary_location_name) {
|
||||
metadata.positionData = metadata.positionData || {};
|
||||
metadata.positionData.country =
|
||||
iptcData.country_or_primary_location_name
|
||||
.replace(/\0/g, '')
|
||||
.trim();
|
||||
iptcData.country_or_primary_location_name
|
||||
.replace(/\0/g, '')
|
||||
.trim();
|
||||
}
|
||||
if (iptcData.province_or_state) {
|
||||
metadata.positionData = metadata.positionData || {};
|
||||
metadata.positionData.state = iptcData.province_or_state
|
||||
.replace(/\0/g, '')
|
||||
.trim();
|
||||
.replace(/\0/g, '')
|
||||
.trim();
|
||||
}
|
||||
if (iptcData.city) {
|
||||
metadata.positionData = metadata.positionData || {};
|
||||
metadata.positionData.city = iptcData.city
|
||||
.replace(/\0/g, '')
|
||||
.trim();
|
||||
.replace(/\0/g, '')
|
||||
.trim();
|
||||
}
|
||||
if (iptcData.caption) {
|
||||
metadata.caption = iptcData.caption.replace(/\0/g, '').trim();
|
||||
@ -316,9 +316,9 @@ export class MetadataLoader {
|
||||
}
|
||||
}
|
||||
if (
|
||||
exif.subject &&
|
||||
exif.subject.value &&
|
||||
exif.subject.value.length > 0
|
||||
exif.subject &&
|
||||
exif.subject.value &&
|
||||
exif.subject.value.length > 0
|
||||
) {
|
||||
if (metadata.keywords === undefined) {
|
||||
metadata.keywords = [];
|
||||
@ -332,8 +332,8 @@ export class MetadataLoader {
|
||||
let orientation = OrientationTypes.TOP_LEFT;
|
||||
if (exif.Orientation) {
|
||||
orientation = parseInt(
|
||||
exif.Orientation.value as any,
|
||||
10
|
||||
exif.Orientation.value as any,
|
||||
10
|
||||
) as number;
|
||||
}
|
||||
if (OrientationTypes.BOTTOM_LEFT < orientation) {
|
||||
@ -347,18 +347,18 @@ export class MetadataLoader {
|
||||
if (Config.Faces.enabled) {
|
||||
const faces: FaceRegion[] = [];
|
||||
if (
|
||||
((exif.Regions as any)?.value?.RegionList)?.value
|
||||
((exif.Regions as any)?.value?.RegionList)?.value
|
||||
) {
|
||||
for (const regionRoot of (exif.Regions as any).value.RegionList
|
||||
.value as any[]) {
|
||||
.value as any[]) {
|
||||
let type;
|
||||
let name;
|
||||
let box;
|
||||
const createFaceBox = (
|
||||
w: string,
|
||||
h: string,
|
||||
x: string,
|
||||
y: string
|
||||
w: string,
|
||||
h: string,
|
||||
x: string,
|
||||
y: string
|
||||
) => {
|
||||
if (OrientationTypes.BOTTOM_LEFT < orientation) {
|
||||
[x, y] = [y, x];
|
||||
@ -392,10 +392,10 @@ export class MetadataLoader {
|
||||
|
||||
/* Adobe Lightroom based face region structure */
|
||||
if (
|
||||
regionRoot.value &&
|
||||
regionRoot.value['rdf:Description'] &&
|
||||
regionRoot.value['rdf:Description'].value &&
|
||||
regionRoot.value['rdf:Description'].value['mwg-rs:Area']
|
||||
regionRoot.value &&
|
||||
regionRoot.value['rdf:Description'] &&
|
||||
regionRoot.value['rdf:Description'].value &&
|
||||
regionRoot.value['rdf:Description'].value['mwg-rs:Area']
|
||||
) {
|
||||
const region = regionRoot.value['rdf:Description'];
|
||||
const regionBox = region.value['mwg-rs:Area'].attributes;
|
||||
@ -403,25 +403,25 @@ export class MetadataLoader {
|
||||
name = region.attributes['mwg-rs:Name'];
|
||||
type = region.attributes['mwg-rs:Type'];
|
||||
box = createFaceBox(
|
||||
regionBox['stArea:w'],
|
||||
regionBox['stArea:h'],
|
||||
regionBox['stArea:x'],
|
||||
regionBox['stArea:y']
|
||||
regionBox['stArea:w'],
|
||||
regionBox['stArea:h'],
|
||||
regionBox['stArea:x'],
|
||||
regionBox['stArea:y']
|
||||
);
|
||||
/* Load exiftool edited face region structure, see github issue #191 */
|
||||
} else if (
|
||||
regionRoot.Area &&
|
||||
regionRoot.Name &&
|
||||
regionRoot.Type
|
||||
regionRoot.Area &&
|
||||
regionRoot.Name &&
|
||||
regionRoot.Type
|
||||
) {
|
||||
const regionBox = regionRoot.Area.value;
|
||||
name = regionRoot.Name.value;
|
||||
type = regionRoot.Type.value;
|
||||
box = createFaceBox(
|
||||
regionBox.w.value,
|
||||
regionBox.h.value,
|
||||
regionBox.x.value,
|
||||
regionBox.y.value
|
||||
regionBox.w.value,
|
||||
regionBox.h.value,
|
||||
regionBox.x.value,
|
||||
regionBox.y.value
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -92,21 +92,21 @@ export class VideoRendererFactory {
|
||||
const folder = path.dirname(input.outPath);
|
||||
let executedCmd = '';
|
||||
command
|
||||
.on('start', (cmd): void => {
|
||||
executedCmd = cmd;
|
||||
})
|
||||
.on('end', (): void => {
|
||||
resolve();
|
||||
})
|
||||
.on('error', (e): void => {
|
||||
reject('[FFmpeg] ' + e.toString() + ' executed: ' + executedCmd);
|
||||
})
|
||||
.outputOptions(['-qscale:v 4']);
|
||||
.on('start', (cmd): void => {
|
||||
executedCmd = cmd;
|
||||
})
|
||||
.on('end', (): void => {
|
||||
resolve();
|
||||
})
|
||||
.on('error', (e): void => {
|
||||
reject('[FFmpeg] ' + e.toString() + ' executed: ' + executedCmd);
|
||||
})
|
||||
.outputOptions(['-qscale:v 4']);
|
||||
if (input.makeSquare === false) {
|
||||
const newSize =
|
||||
width < height
|
||||
? Math.min(input.size, width) + 'x?'
|
||||
: '?x' + Math.min(input.size, height);
|
||||
width < height
|
||||
? Math.min(input.size, width) + 'x?'
|
||||
: '?x' + Math.min(input.size, height);
|
||||
command.takeScreenshots({
|
||||
timemarks: ['10%'],
|
||||
size: newSize,
|
||||
@ -134,22 +134,22 @@ export class ImageRendererFactory {
|
||||
let image: Sharp;
|
||||
if ((input as MediaRendererInput).mediaPath) {
|
||||
Logger.silly(
|
||||
'[SharpRenderer] rendering photo:' +
|
||||
(input as MediaRendererInput).mediaPath +
|
||||
', size:' +
|
||||
input.size
|
||||
'[SharpRenderer] rendering photo:' +
|
||||
(input as MediaRendererInput).mediaPath +
|
||||
', size:' +
|
||||
input.size
|
||||
);
|
||||
image = sharp((input as MediaRendererInput).mediaPath, {failOnError: false});
|
||||
} else {
|
||||
const svg_buffer = Buffer.from((input as SvgRendererInput).svgString);
|
||||
image = sharp(svg_buffer, { density: 450 });
|
||||
image = sharp(svg_buffer, {density: 450});
|
||||
}
|
||||
image.rotate();
|
||||
const metadata: Metadata = await image.metadata();
|
||||
const kernel =
|
||||
input.useLanczos3 === true
|
||||
? sharp.kernel.lanczos3
|
||||
: sharp.kernel.nearest;
|
||||
input.useLanczos3 === true
|
||||
? sharp.kernel.lanczos3
|
||||
: sharp.kernel.nearest;
|
||||
|
||||
if (input.cut) {
|
||||
image.extract(input.cut);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { TaskQue } from './TaskQue';
|
||||
import {TaskQue} from './TaskQue';
|
||||
|
||||
export interface ITaskExecuter<I, O> {
|
||||
execute(input: I): Promise<O>;
|
||||
@ -23,7 +23,8 @@ export class TaskExecuter<I, O> implements ITaskExecuter<I, O> {
|
||||
process.nextTick(this.run);
|
||||
};
|
||||
|
||||
constructor(private size: number, private worker: (input: I) => Promise<O>) {}
|
||||
constructor(private size: number, private worker: (input: I) => Promise<O>) {
|
||||
}
|
||||
|
||||
execute(input: I): Promise<O> {
|
||||
const promise = this.taskQue.add(input).promise.obj;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Utils } from '../../../common/Utils';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
|
||||
export interface TaskQueEntry<I, O> {
|
||||
data: I;
|
||||
@ -37,9 +37,9 @@ export class TaskQue<I, O> {
|
||||
|
||||
private getSameTask(input: I): TaskQueEntry<I, O> {
|
||||
return (this.tasks.find((t) => Utils.equalsFilter(t.data, input)) ||
|
||||
this.processing.find((t) =>
|
||||
Utils.equalsFilter(t.data, input)
|
||||
)) as TaskQueEntry<I, O>;
|
||||
this.processing.find((t) =>
|
||||
Utils.equalsFilter(t.data, input)
|
||||
)) as TaskQueEntry<I, O>;
|
||||
}
|
||||
|
||||
private putNewTask(input: I): TaskQueEntry<I, O> {
|
||||
@ -53,10 +53,10 @@ export class TaskQue<I, O> {
|
||||
};
|
||||
this.tasks.push(taskEntry);
|
||||
taskEntry.promise.obj = new Promise<O>(
|
||||
(resolve: (ret: O) => void, reject: (err: any) => void) => {
|
||||
taskEntry.promise.reject = reject;
|
||||
taskEntry.promise.resolve = resolve;
|
||||
}
|
||||
(resolve: (ret: O) => void, reject: (err: any) => void) => {
|
||||
taskEntry.promise.reject = reject;
|
||||
taskEntry.promise.resolve = resolve;
|
||||
}
|
||||
);
|
||||
return taskEntry;
|
||||
}
|
||||
|
@ -64,23 +64,23 @@ export class ThreadPool<O> {
|
||||
worker.worker.on('online', (): void => {
|
||||
ThreadPool.WorkerCount++;
|
||||
Logger.debug(
|
||||
LOG_TAG,
|
||||
'Worker ' + worker.worker.process.pid + ' is online, worker count:',
|
||||
ThreadPool.WorkerCount
|
||||
LOG_TAG,
|
||||
'Worker ' + worker.worker.process.pid + ' is online, worker count:',
|
||||
ThreadPool.WorkerCount
|
||||
);
|
||||
});
|
||||
worker.worker.on('exit', (code, signal): void => {
|
||||
ThreadPool.WorkerCount--;
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Worker ' +
|
||||
worker.worker.process.pid +
|
||||
' died with code: ' +
|
||||
code +
|
||||
', and signal: ' +
|
||||
signal +
|
||||
', worker count:',
|
||||
ThreadPool.WorkerCount
|
||||
LOG_TAG,
|
||||
'Worker ' +
|
||||
worker.worker.process.pid +
|
||||
' died with code: ' +
|
||||
code +
|
||||
', and signal: ' +
|
||||
signal +
|
||||
', worker count:',
|
||||
ThreadPool.WorkerCount
|
||||
);
|
||||
Logger.debug(LOG_TAG, 'Starting a new worker');
|
||||
this.startWorker();
|
||||
@ -103,11 +103,11 @@ export class ThreadPool<O> {
|
||||
}
|
||||
|
||||
export class DiskManagerTH
|
||||
extends ThreadPool<ParentDirectoryDTO>
|
||||
implements ITaskExecuter<string, ParentDirectoryDTO> {
|
||||
extends ThreadPool<ParentDirectoryDTO>
|
||||
implements ITaskExecuter<string, ParentDirectoryDTO> {
|
||||
execute(
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
relativeDirectoryName: string,
|
||||
settings: DirectoryScanSettings = {}
|
||||
): Promise<ParentDirectoryDTO> {
|
||||
return super.executeTask({
|
||||
type: WorkerTaskTypes.diskManager,
|
||||
|
@ -43,20 +43,20 @@ export class VideoConverterWorker {
|
||||
const command: FfmpegCommand = this.ffmpeg(input.videoPath);
|
||||
let executedCmd = '';
|
||||
command
|
||||
.on('start', (cmd: string) => {
|
||||
Logger.silly('[FFmpeg] running:' + cmd);
|
||||
executedCmd = cmd;
|
||||
})
|
||||
.on('end', () => {
|
||||
resolve();
|
||||
})
|
||||
.on('error', (e: any) => {
|
||||
reject('[FFmpeg] ' + e.toString() + ' executed: ' + executedCmd);
|
||||
})
|
||||
.on('stderr', function (line: string) {
|
||||
// Although this is under `stderr` event, all of ffmpeg output come here.
|
||||
Logger.debug('[FFmpeg] ' + line);
|
||||
});
|
||||
.on('start', (cmd: string) => {
|
||||
Logger.silly('[FFmpeg] running:' + cmd);
|
||||
executedCmd = cmd;
|
||||
})
|
||||
.on('end', () => {
|
||||
resolve();
|
||||
})
|
||||
.on('error', (e: any) => {
|
||||
reject('[FFmpeg] ' + e.toString() + ' executed: ' + executedCmd);
|
||||
})
|
||||
.on('stderr', function(line: string) {
|
||||
// Although this is under `stderr` event, all of ffmpeg output come here.
|
||||
Logger.debug('[FFmpeg] ' + line);
|
||||
});
|
||||
|
||||
// set custom input options
|
||||
if (input.input.customOptions) {
|
||||
@ -93,9 +93,9 @@ export class VideoConverterWorker {
|
||||
|
||||
// set output format to force
|
||||
command
|
||||
.format(input.output.format)
|
||||
// save to file
|
||||
.save(input.output.path);
|
||||
.format(input.output.format)
|
||||
// save to file
|
||||
.save(input.output.path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ export class Worker {
|
||||
switch (task.type) {
|
||||
case WorkerTaskTypes.diskManager:
|
||||
result = await DiskMangerWorker.scanDirectory(
|
||||
(task as DiskManagerTask).relativeDirectoryName,
|
||||
(task as DiskManagerTask).settings
|
||||
(task as DiskManagerTask).relativeDirectoryName,
|
||||
(task as DiskManagerTask).settings
|
||||
);
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
|
@ -16,46 +16,46 @@ export class AlbumRouter {
|
||||
|
||||
private static addListAlbums(app: Express): void {
|
||||
app.get(
|
||||
[Config.Server.apiPath + '/albums'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
[Config.Server.apiPath + '/albums'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
AlbumMWs.listAlbums,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
// specific part
|
||||
AlbumMWs.listAlbums,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
private static addDeleteAlbum(app: Express): void {
|
||||
app.delete(
|
||||
[Config.Server.apiPath + '/albums/:id'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
[Config.Server.apiPath + '/albums/:id'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
AlbumMWs.deleteAlbum,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
// specific part
|
||||
AlbumMWs.deleteAlbum,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
private static addAddSavedSearch(app: Express): void {
|
||||
app.put(
|
||||
[Config.Server.apiPath + '/albums/saved-searches'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
[Config.Server.apiPath + '/albums/saved-searches'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
AlbumMWs.createSavedSearch,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
// specific part
|
||||
AlbumMWs.createSavedSearch,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { RenderingMWs } from '../middlewares/RenderingMWs';
|
||||
import { ErrorCodes, ErrorDTO } from '../../common/entities/Error';
|
||||
import { Logger } from '../Logger';
|
||||
import { Express, NextFunction, Request, Response } from 'express';
|
||||
import {RenderingMWs} from '../middlewares/RenderingMWs';
|
||||
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
|
||||
import {Logger} from '../Logger';
|
||||
import {Express, NextFunction, Request, Response} from 'express';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
|
||||
export class ErrorRouter {
|
||||
@ -16,42 +16,42 @@ export class ErrorRouter {
|
||||
|
||||
private static addGenericHandler(app: Express): void {
|
||||
app.use(
|
||||
(err: any, req: Request, res: Response, next: NextFunction): any => {
|
||||
if (err.name === 'UnauthorizedError') {
|
||||
// jwt authentication error
|
||||
res.status(401);
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Invalid token')
|
||||
);
|
||||
}
|
||||
if (err.name === 'ForbiddenError' && err.code === 'EBADCSRFTOKEN') {
|
||||
// jwt authentication error
|
||||
res.status(401);
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.NOT_AUTHENTICATED,
|
||||
'Invalid CSRF token',
|
||||
err,
|
||||
req
|
||||
)
|
||||
);
|
||||
}
|
||||
(err: any, req: Request, res: Response, next: NextFunction): any => {
|
||||
if (err.name === 'UnauthorizedError') {
|
||||
// jwt authentication error
|
||||
res.status(401);
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Invalid token')
|
||||
);
|
||||
}
|
||||
if (err.name === 'ForbiddenError' && err.code === 'EBADCSRFTOKEN') {
|
||||
// jwt authentication error
|
||||
res.status(401);
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.NOT_AUTHENTICATED,
|
||||
'Invalid CSRF token',
|
||||
err,
|
||||
req
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
console.log(err);
|
||||
console.log(err);
|
||||
|
||||
// Flush out the stack to the console
|
||||
Logger.error('Unexpected error:');
|
||||
console.error(err);
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.SERVER_ERROR,
|
||||
'Unknown server side error',
|
||||
err,
|
||||
req
|
||||
)
|
||||
);
|
||||
},
|
||||
RenderingMWs.renderError
|
||||
// Flush out the stack to the console
|
||||
Logger.error('Unexpected error:');
|
||||
console.error(err);
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.SERVER_ERROR,
|
||||
'Unknown server side error',
|
||||
err,
|
||||
req
|
||||
)
|
||||
);
|
||||
},
|
||||
RenderingMWs.renderError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -34,270 +34,270 @@ export class GalleryRouter {
|
||||
|
||||
protected static addDirectoryList(app: Express): void {
|
||||
app.get(
|
||||
[Config.Server.apiPath + '/gallery/content/:directory(*)', Config.Server.apiPath + '/gallery/', Config.Server.apiPath + '/gallery//'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('directory'),
|
||||
AuthenticationMWs.authorisePath('directory', true),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
[Config.Server.apiPath + '/gallery/content/:directory(*)', Config.Server.apiPath + '/gallery/', Config.Server.apiPath + '/gallery//'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('directory'),
|
||||
AuthenticationMWs.authorisePath('directory', true),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
GalleryMWs.listDirectory,
|
||||
ThumbnailGeneratorMWs.addThumbnailInformation,
|
||||
GalleryMWs.cleanUpGalleryResults,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
// specific part
|
||||
GalleryMWs.listDirectory,
|
||||
ThumbnailGeneratorMWs.addThumbnailInformation,
|
||||
GalleryMWs.cleanUpGalleryResults,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
protected static addDirectoryZip(app: Express): void {
|
||||
app.get(
|
||||
[Config.Server.apiPath + '/gallery/zip/:directory(*)'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('directory'),
|
||||
AuthenticationMWs.authorisePath('directory', true),
|
||||
[Config.Server.apiPath + '/gallery/zip/:directory(*)'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('directory'),
|
||||
AuthenticationMWs.authorisePath('directory', true),
|
||||
|
||||
// specific part
|
||||
ServerTimingMWs.addServerTiming,
|
||||
GalleryMWs.zipDirectory
|
||||
// specific part
|
||||
ServerTimingMWs.addServerTiming,
|
||||
GalleryMWs.zipDirectory
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetImage(app: Express): void {
|
||||
app.get(
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Photos.join('|') +
|
||||
'))',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Photos.join('|') +
|
||||
'))',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetBestFitImage(app: Express): void {
|
||||
app.get(
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Photos.join('|') +
|
||||
'))/bestFit',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Photos.join('|') +
|
||||
'))/bestFit',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
PhotoConverterMWs.convertPhoto,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
PhotoConverterMWs.convertPhoto,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetVideo(app: Express): void {
|
||||
app.get(
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Videos.join('|') +
|
||||
'))',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Videos.join('|') +
|
||||
'))',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetBestFitVideo(app: Express): void {
|
||||
app.get(
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Videos.join('|') +
|
||||
'))/bestFit',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Videos.join('|') +
|
||||
'))/bestFit',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
GalleryMWs.loadBestFitVideo,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
GalleryMWs.loadBestFitVideo,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetMetaFile(app: Express): void {
|
||||
app.get(
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.MetaFiles.join('|') +
|
||||
'))',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.MetaFiles.join('|') +
|
||||
'))',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetBestFitMetaFile(app: Express): void {
|
||||
app.get(
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.MetaFiles.join('|') +
|
||||
'))/bestFit',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
[
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.MetaFiles.join('|') +
|
||||
'))/bestFit',
|
||||
],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
MetaFileMWs.compressGPX,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
MetaFileMWs.compressGPX,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addRandom(app: Express): void {
|
||||
app.get(
|
||||
[Config.Server.apiPath + '/gallery/random/:searchQueryDTO'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
[Config.Server.apiPath + '/gallery/random/:searchQueryDTO'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
GalleryMWs.getRandomImage,
|
||||
GalleryMWs.loadFile,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.getRandomImage,
|
||||
GalleryMWs.loadFile,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetPhotoThumbnail(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Photos.join('|') +
|
||||
'))/thumbnail/:size?',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Photos.join('|') +
|
||||
'))/thumbnail/:size?',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Photo),
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Photo),
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetVideoThumbnail(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Videos.join('|') +
|
||||
'))/thumbnail/:size?',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Videos.join('|') +
|
||||
'))/thumbnail/:size?',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video),
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video),
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetVideoIcon(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Videos.join('|') +
|
||||
'))/icon',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Videos.join('|') +
|
||||
'))/icon',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Video),
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Video),
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetImageIcon(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Photos.join('|') +
|
||||
'))/icon',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' +
|
||||
SupportedFormats.Photos.join('|') +
|
||||
'))/icon',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Photo),
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Photo),
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
protected static addSearch(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/search/:searchQueryDTO(*)',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
Config.Server.apiPath + '/search/:searchQueryDTO(*)',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
GalleryMWs.search,
|
||||
ThumbnailGeneratorMWs.addThumbnailInformation,
|
||||
GalleryMWs.cleanUpGalleryResults,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
// specific part
|
||||
GalleryMWs.search,
|
||||
ThumbnailGeneratorMWs.addThumbnailInformation,
|
||||
GalleryMWs.cleanUpGalleryResults,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
protected static addAutoComplete(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/autocomplete/:text(*)',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
Config.Server.apiPath + '/autocomplete/:text(*)',
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
GalleryMWs.autocomplete,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
// specific part
|
||||
GalleryMWs.autocomplete,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Express, NextFunction, Request, Response } from 'express';
|
||||
import { logFN, Logger } from '../Logger';
|
||||
import {Express, NextFunction, Request, Response} from 'express';
|
||||
import {logFN, Logger} from '../Logger';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
|
||||
declare global {
|
||||
@ -26,10 +26,10 @@ export class LoggerRouter {
|
||||
res.end = end;
|
||||
res.end(a, b, c);
|
||||
loggerFn(
|
||||
req.method,
|
||||
req.url,
|
||||
res.statusCode,
|
||||
Date.now() - req._startTime + 'ms'
|
||||
req.method,
|
||||
req.url,
|
||||
res.statusCode,
|
||||
Date.now() - req._startTime + 'ms'
|
||||
);
|
||||
return res;
|
||||
};
|
||||
@ -48,11 +48,11 @@ export class LoggerRouter {
|
||||
});
|
||||
|
||||
app.get(
|
||||
'/node_modules*',
|
||||
(req: Request, res: Response, next: NextFunction): any => {
|
||||
LoggerRouter.log(Logger.silly, req, res);
|
||||
return next();
|
||||
}
|
||||
'/node_modules*',
|
||||
(req: Request, res: Response, next: NextFunction): any => {
|
||||
LoggerRouter.log(Logger.silly, req, res);
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
app.use((req: Request, res: Response, next: NextFunction): any => {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { UserRoles } from '../../common/entities/UserDTO';
|
||||
import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs';
|
||||
import { RenderingMWs } from '../middlewares/RenderingMWs';
|
||||
import { NotificationMWs } from '../middlewares/NotificationMWs';
|
||||
import { Express } from 'express';
|
||||
import { VersionMWs } from '../middlewares/VersionMWs';
|
||||
import { ServerTimingMWs } from '../middlewares/ServerTimingMWs';
|
||||
import {UserRoles} from '../../common/entities/UserDTO';
|
||||
import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs';
|
||||
import {RenderingMWs} from '../middlewares/RenderingMWs';
|
||||
import {NotificationMWs} from '../middlewares/NotificationMWs';
|
||||
import {Express} from 'express';
|
||||
import {VersionMWs} from '../middlewares/VersionMWs';
|
||||
import {ServerTimingMWs} from '../middlewares/ServerTimingMWs';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
|
||||
export class NotificationRouter {
|
||||
@ -14,13 +14,13 @@ export class NotificationRouter {
|
||||
|
||||
private static addGetNotifications(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/notifications',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
NotificationMWs.list,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
Config.Server.apiPath + '/notifications',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
NotificationMWs.list,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs';
|
||||
import { Express } from 'express';
|
||||
import { RenderingMWs } from '../middlewares/RenderingMWs';
|
||||
import { UserRoles } from '../../common/entities/UserDTO';
|
||||
import { PersonMWs } from '../middlewares/PersonMWs';
|
||||
import { ThumbnailGeneratorMWs } from '../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
||||
import { VersionMWs } from '../middlewares/VersionMWs';
|
||||
import { Config } from '../../common/config/private/Config';
|
||||
import { ServerTimingMWs } from '../middlewares/ServerTimingMWs';
|
||||
import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs';
|
||||
import {Express} from 'express';
|
||||
import {RenderingMWs} from '../middlewares/RenderingMWs';
|
||||
import {UserRoles} from '../../common/entities/UserDTO';
|
||||
import {PersonMWs} from '../middlewares/PersonMWs';
|
||||
import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
||||
import {VersionMWs} from '../middlewares/VersionMWs';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
import {ServerTimingMWs} from '../middlewares/ServerTimingMWs';
|
||||
|
||||
export class PersonRouter {
|
||||
public static route(app: Express): void {
|
||||
@ -17,50 +17,50 @@ export class PersonRouter {
|
||||
|
||||
protected static updatePerson(app: Express): void {
|
||||
app.post(
|
||||
[Config.Server.apiPath + '/person/:name'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(Config.Faces.writeAccessMinRole),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
[Config.Server.apiPath + '/person/:name'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(Config.Faces.writeAccessMinRole),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
PersonMWs.updatePerson,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
// specific part
|
||||
PersonMWs.updatePerson,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
protected static addGetPersons(app: Express): void {
|
||||
app.get(
|
||||
[Config.Server.apiPath + '/person'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(Config.Faces.readAccessMinRole),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
[Config.Server.apiPath + '/person'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(Config.Faces.readAccessMinRole),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
PersonMWs.listPersons,
|
||||
// PersonMWs.addSamplePhotoForAll,
|
||||
ThumbnailGeneratorMWs.addThumbnailInfoForPersons,
|
||||
PersonMWs.cleanUpPersonResults,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
// specific part
|
||||
PersonMWs.listPersons,
|
||||
// PersonMWs.addSamplePhotoForAll,
|
||||
ThumbnailGeneratorMWs.addThumbnailInfoForPersons,
|
||||
PersonMWs.cleanUpPersonResults,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
protected static getPersonThumbnail(app: Express): void {
|
||||
app.get(
|
||||
[Config.Server.apiPath + '/person/:name/thumbnail'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
[Config.Server.apiPath + '/person/:name/thumbnail'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
PersonMWs.getPerson,
|
||||
ThumbnailGeneratorMWs.generatePersonThumbnail,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
// specific part
|
||||
PersonMWs.getPerson,
|
||||
ThumbnailGeneratorMWs.generatePersonThumbnail,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export class PublicRouter {
|
||||
let selectedLocale = req.locale;
|
||||
if (req.cookies && req.cookies[CookieNames.lang]) {
|
||||
if (
|
||||
Config.Server.languages.indexOf(req.cookies[CookieNames.lang]) !== -1
|
||||
Config.Server.languages.indexOf(req.cookies[CookieNames.lang]) !== -1
|
||||
) {
|
||||
selectedLocale = req.cookies[CookieNames.lang];
|
||||
}
|
||||
@ -48,14 +48,14 @@ export class PublicRouter {
|
||||
// index.html should not be cached as it contains template that can change
|
||||
const renderIndex = (req: Request, res: Response, next: NextFunction) => {
|
||||
ejs.renderFile(
|
||||
path.join(ProjectPath.FrontendFolder, req.localePath, 'index.html'),
|
||||
res.tpl,
|
||||
(err, str) => {
|
||||
if (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, err.message));
|
||||
path.join(ProjectPath.FrontendFolder, req.localePath, 'index.html'),
|
||||
res.tpl,
|
||||
(err, str) => {
|
||||
if (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, err.message));
|
||||
}
|
||||
res.send(str);
|
||||
}
|
||||
res.send(str);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@ -94,12 +94,12 @@ export class PublicRouter {
|
||||
}) as unknown as ClientConfig;
|
||||
// Escaping html tags, like <script></script>
|
||||
confCopy.Server.customHTMLHead =
|
||||
confCopy.Server.customHTMLHead
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
confCopy.Server.customHTMLHead
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
res.tpl.Config = confCopy;
|
||||
res.tpl.customHTMLHead = Config.Server.customHTMLHead;
|
||||
const selectedTheme = Config.Gallery.Themes.availableThemes.find(th => th.name === Config.Gallery.Themes.selectedTheme)?.theme || '';
|
||||
@ -140,7 +140,7 @@ export class PublicRouter {
|
||||
'photo'
|
||||
],
|
||||
start_url:
|
||||
Config.Server.publicUrl === '' ? '.' : Config.Server.publicUrl,
|
||||
Config.Server.publicUrl === '' ? '.' : Config.Server.publicUrl,
|
||||
background_color: '#000000',
|
||||
theme_color: '#000000',
|
||||
});
|
||||
@ -185,40 +185,40 @@ export class PublicRouter {
|
||||
y: vBs[1]
|
||||
};
|
||||
return '<svg ' +
|
||||
' xmlns="http://www.w3.org/2000/svg"' +
|
||||
' viewBox="' + vBs.join(' ') + '">' +
|
||||
(theme === 'auto' ? ('<style>' +
|
||||
' path, circle {' +
|
||||
' fill: black;' +
|
||||
' }' +
|
||||
' circle.bg,rect.bg {' +
|
||||
' fill: white;' +
|
||||
' }' +
|
||||
' @media (prefers-color-scheme: dark) {' +
|
||||
' path, circle {' +
|
||||
' fill: white;' +
|
||||
' }' +
|
||||
' circle.bg,rect.bg {' +
|
||||
' fill: black;' +
|
||||
' }' +
|
||||
' }' +
|
||||
' </style>') :
|
||||
(theme != null ?
|
||||
('<style>' +
|
||||
' path, circle {' +
|
||||
' fill: ' + theme + ';' +
|
||||
' }' +
|
||||
' circle.bg {' +
|
||||
' fill: black;' +
|
||||
' }' +
|
||||
' </style>')
|
||||
: '<style>' +
|
||||
' circle.bg,rect.bg {' +
|
||||
' fill: white;' +
|
||||
' }' +
|
||||
' </style>')) +
|
||||
`<rect class="bg" x="${canvasStart.x}" y="${canvasStart.y}" width="${canvasSize}" height="${canvasSize}" rx="15" />` +
|
||||
Config.Server.svgIcon.items + '</svg>';
|
||||
' xmlns="http://www.w3.org/2000/svg"' +
|
||||
' viewBox="' + vBs.join(' ') + '">' +
|
||||
(theme === 'auto' ? ('<style>' +
|
||||
' path, circle {' +
|
||||
' fill: black;' +
|
||||
' }' +
|
||||
' circle.bg,rect.bg {' +
|
||||
' fill: white;' +
|
||||
' }' +
|
||||
' @media (prefers-color-scheme: dark) {' +
|
||||
' path, circle {' +
|
||||
' fill: white;' +
|
||||
' }' +
|
||||
' circle.bg,rect.bg {' +
|
||||
' fill: black;' +
|
||||
' }' +
|
||||
' }' +
|
||||
' </style>') :
|
||||
(theme != null ?
|
||||
('<style>' +
|
||||
' path, circle {' +
|
||||
' fill: ' + theme + ';' +
|
||||
' }' +
|
||||
' circle.bg {' +
|
||||
' fill: black;' +
|
||||
' }' +
|
||||
' </style>')
|
||||
: '<style>' +
|
||||
' circle.bg,rect.bg {' +
|
||||
' fill: white;' +
|
||||
' }' +
|
||||
' </style>')) +
|
||||
`<rect class="bg" x="${canvasStart.x}" y="${canvasStart.y}" width="${canvasSize}" height="${canvasSize}" rx="15" />` +
|
||||
Config.Server.svgIcon.items + '</svg>';
|
||||
};
|
||||
|
||||
app.get('/icon.svg', (req: Request, res: Response) => {
|
||||
@ -276,44 +276,44 @@ export class PublicRouter {
|
||||
});
|
||||
|
||||
app.get(
|
||||
[
|
||||
'/',
|
||||
'/login',
|
||||
'/gallery*',
|
||||
'/share/:' + QueryParams.gallery.sharingKey_params,
|
||||
'/shareLogin',
|
||||
'/admin',
|
||||
'/duplicates',
|
||||
'/faces',
|
||||
'/albums',
|
||||
'/search*',
|
||||
],
|
||||
AuthenticationMWs.tryAuthenticate,
|
||||
addTPl, // add template after authentication was successful
|
||||
setLocale,
|
||||
renderIndex
|
||||
[
|
||||
'/',
|
||||
'/login',
|
||||
'/gallery*',
|
||||
'/share/:' + QueryParams.gallery.sharingKey_params,
|
||||
'/shareLogin',
|
||||
'/admin',
|
||||
'/duplicates',
|
||||
'/faces',
|
||||
'/albums',
|
||||
'/search*',
|
||||
],
|
||||
AuthenticationMWs.tryAuthenticate,
|
||||
addTPl, // add template after authentication was successful
|
||||
setLocale,
|
||||
renderIndex
|
||||
);
|
||||
Config.Server.languages.forEach((l) => {
|
||||
app.get(
|
||||
[
|
||||
'/' + l + '/',
|
||||
'/' + l + '/login',
|
||||
'/' + l + '/gallery*',
|
||||
'/' + l + '/share*',
|
||||
'/' + l + '/admin',
|
||||
'/' + l + '/search*',
|
||||
],
|
||||
redirectToBase(l)
|
||||
[
|
||||
'/' + l + '/',
|
||||
'/' + l + '/login',
|
||||
'/' + l + '/gallery*',
|
||||
'/' + l + '/share*',
|
||||
'/' + l + '/admin',
|
||||
'/' + l + '/search*',
|
||||
],
|
||||
redirectToBase(l)
|
||||
);
|
||||
});
|
||||
|
||||
const renderFile = (subDir = '') => {
|
||||
return (req: Request, res: Response) => {
|
||||
const file = path.join(
|
||||
ProjectPath.FrontendFolder,
|
||||
req.localePath,
|
||||
subDir,
|
||||
req.params.file
|
||||
ProjectPath.FrontendFolder,
|
||||
req.localePath,
|
||||
subDir,
|
||||
req.params.file
|
||||
);
|
||||
if (!fs.existsSync(file)) {
|
||||
return res.sendStatus(404);
|
||||
@ -326,16 +326,16 @@ export class PublicRouter {
|
||||
};
|
||||
|
||||
app.get(
|
||||
'/assets/:file(*)',
|
||||
setLocale,
|
||||
AuthenticationMWs.normalizePathParam('file'),
|
||||
renderFile('assets')
|
||||
'/assets/:file(*)',
|
||||
setLocale,
|
||||
AuthenticationMWs.normalizePathParam('file'),
|
||||
renderFile('assets')
|
||||
);
|
||||
app.get(
|
||||
'/:file',
|
||||
setLocale,
|
||||
AuthenticationMWs.normalizePathParam('file'),
|
||||
renderFile()
|
||||
'/:file',
|
||||
setLocale,
|
||||
AuthenticationMWs.normalizePathParam('file'),
|
||||
renderFile()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Express } from 'express';
|
||||
import { PublicRouter } from './PublicRouter';
|
||||
import { UserRouter } from './UserRouter';
|
||||
import { GalleryRouter } from './GalleryRouter';
|
||||
import { PersonRouter } from './PersonRouter';
|
||||
import { SharingRouter } from './SharingRouter';
|
||||
import { AdminRouter } from './admin/AdminRouter';
|
||||
import { SettingsRouter } from './admin/SettingsRouter';
|
||||
import { NotificationRouter } from './NotificationRouter';
|
||||
import { ErrorRouter } from './ErrorRouter';
|
||||
import { AlbumRouter } from './AlbumRouter';
|
||||
import {Express} from 'express';
|
||||
import {PublicRouter} from './PublicRouter';
|
||||
import {UserRouter} from './UserRouter';
|
||||
import {GalleryRouter} from './GalleryRouter';
|
||||
import {PersonRouter} from './PersonRouter';
|
||||
import {SharingRouter} from './SharingRouter';
|
||||
import {AdminRouter} from './admin/AdminRouter';
|
||||
import {SettingsRouter} from './admin/SettingsRouter';
|
||||
import {NotificationRouter} from './NotificationRouter';
|
||||
import {ErrorRouter} from './ErrorRouter';
|
||||
import {AlbumRouter} from './AlbumRouter';
|
||||
|
||||
export class Router {
|
||||
public static route(app: Express): void {
|
||||
|
@ -20,79 +20,79 @@ export class SharingRouter {
|
||||
|
||||
private static addShareLogin(app: express.Express): void {
|
||||
app.post(
|
||||
Config.Server.apiPath + '/share/login',
|
||||
AuthenticationMWs.inverseAuthenticate,
|
||||
AuthenticationMWs.shareLogin,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSessionUser
|
||||
Config.Server.apiPath + '/share/login',
|
||||
AuthenticationMWs.inverseAuthenticate,
|
||||
AuthenticationMWs.shareLogin,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSessionUser
|
||||
);
|
||||
}
|
||||
|
||||
private static addGetSharing(app: express.Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/share/:' + QueryParams.gallery.sharingKey_params,
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.LimitedGuest),
|
||||
SharingMWs.getSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharing
|
||||
Config.Server.apiPath + '/share/:' + QueryParams.gallery.sharingKey_params,
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.LimitedGuest),
|
||||
SharingMWs.getSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharing
|
||||
);
|
||||
}
|
||||
|
||||
private static addCreateSharing(app: express.Express): void {
|
||||
app.post(
|
||||
[Config.Server.apiPath + '/share/:directory(*)', Config.Server.apiPath + '/share/', Config.Server.apiPath + '/share//'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
SharingMWs.createSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharing
|
||||
[Config.Server.apiPath + '/share/:directory(*)', Config.Server.apiPath + '/share/', Config.Server.apiPath + '/share//'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
SharingMWs.createSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharing
|
||||
);
|
||||
}
|
||||
|
||||
private static addUpdateSharing(app: express.Express): void {
|
||||
app.put(
|
||||
[Config.Server.apiPath + '/share/:directory(*)', Config.Server.apiPath + '/share/', Config.Server.apiPath + '/share//'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
SharingMWs.updateSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharing
|
||||
[Config.Server.apiPath + '/share/:directory(*)', Config.Server.apiPath + '/share/', Config.Server.apiPath + '/share//'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
SharingMWs.updateSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharing
|
||||
);
|
||||
}
|
||||
|
||||
private static addDeleteSharing(app: express.Express): void {
|
||||
app.delete(
|
||||
[Config.Server.apiPath + '/share/:' + QueryParams.gallery.sharingKey_params],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
SharingMWs.deleteSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
[Config.Server.apiPath + '/share/:' + QueryParams.gallery.sharingKey_params],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
SharingMWs.deleteSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
private static addListSharing(app: express.Express): void {
|
||||
app.get(
|
||||
[Config.Server.apiPath + '/share/listAll'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
SharingMWs.listSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharingList
|
||||
[Config.Server.apiPath + '/share/listAll'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
SharingMWs.listSharing,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharingList
|
||||
);
|
||||
}
|
||||
|
||||
private static addListSharingForDir(app: express.Express): void {
|
||||
app.get(
|
||||
[Config.Server.apiPath + '/share/list/:directory(*)',
|
||||
Config.Server.apiPath + '/share/list//',
|
||||
Config.Server.apiPath + '/share/list'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
SharingMWs.listSharingForDir,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharingList
|
||||
[Config.Server.apiPath + '/share/list/:directory(*)',
|
||||
Config.Server.apiPath + '/share/list//',
|
||||
Config.Server.apiPath + '/share/list'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.User),
|
||||
SharingMWs.listSharingForDir,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSharingList
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { UserMWs } from '../middlewares/user/UserMWs';
|
||||
import { Express } from 'express';
|
||||
import { UserRoles } from '../../common/entities/UserDTO';
|
||||
import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs';
|
||||
import { UserRequestConstrainsMWs } from '../middlewares/user/UserRequestConstrainsMWs';
|
||||
import { RenderingMWs } from '../middlewares/RenderingMWs';
|
||||
import { ServerTimingMWs } from '../middlewares/ServerTimingMWs';
|
||||
import { Config } from '../../common/config/private/Config';
|
||||
import {UserMWs} from '../middlewares/user/UserMWs';
|
||||
import {Express} from 'express';
|
||||
import {UserRoles} from '../../common/entities/UserDTO';
|
||||
import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs';
|
||||
import {UserRequestConstrainsMWs} from '../middlewares/user/UserRequestConstrainsMWs';
|
||||
import {RenderingMWs} from '../middlewares/RenderingMWs';
|
||||
import {ServerTimingMWs} from '../middlewares/ServerTimingMWs';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
|
||||
export class UserRouter {
|
||||
public static route(app: Express): void {
|
||||
@ -21,75 +21,75 @@ export class UserRouter {
|
||||
|
||||
private static addLogin(app: Express): void {
|
||||
app.post(
|
||||
Config.Server.apiPath + '/user/login',
|
||||
AuthenticationMWs.inverseAuthenticate,
|
||||
AuthenticationMWs.login,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSessionUser
|
||||
Config.Server.apiPath + '/user/login',
|
||||
AuthenticationMWs.inverseAuthenticate,
|
||||
AuthenticationMWs.login,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSessionUser
|
||||
);
|
||||
}
|
||||
|
||||
private static addLogout(app: Express): void {
|
||||
app.post(
|
||||
Config.Server.apiPath + '/user/logout',
|
||||
AuthenticationMWs.logout,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderOK
|
||||
Config.Server.apiPath + '/user/logout',
|
||||
AuthenticationMWs.logout,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderOK
|
||||
);
|
||||
}
|
||||
|
||||
private static addGetSessionUser(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/user/me',
|
||||
AuthenticationMWs.authenticate,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSessionUser
|
||||
Config.Server.apiPath + '/user/me',
|
||||
AuthenticationMWs.authenticate,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderSessionUser
|
||||
);
|
||||
}
|
||||
|
||||
private static addCreateUser(app: Express): void {
|
||||
app.put(
|
||||
Config.Server.apiPath + '/user',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
UserMWs.createUser,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderOK
|
||||
Config.Server.apiPath + '/user',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
UserMWs.createUser,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderOK
|
||||
);
|
||||
}
|
||||
|
||||
private static addDeleteUser(app: Express): void {
|
||||
app.delete(
|
||||
Config.Server.apiPath + '/user/:id',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
UserRequestConstrainsMWs.notSelfRequest,
|
||||
UserMWs.deleteUser,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderOK
|
||||
Config.Server.apiPath + '/user/:id',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
UserRequestConstrainsMWs.notSelfRequest,
|
||||
UserMWs.deleteUser,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderOK
|
||||
);
|
||||
}
|
||||
|
||||
private static addListUsers(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/user/list',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
UserMWs.listUsers,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
Config.Server.apiPath + '/user/list',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
UserMWs.listUsers,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
private static addChangeRole(app: Express): void {
|
||||
app.post(
|
||||
Config.Server.apiPath + '/user/:id/role',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
UserRequestConstrainsMWs.notSelfRequestOr2Admins,
|
||||
UserMWs.changeRole,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderOK
|
||||
Config.Server.apiPath + '/user/:id/role',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
UserRequestConstrainsMWs.notSelfRequestOr2Admins,
|
||||
UserMWs.changeRole,
|
||||
ServerTimingMWs.addServerTiming,
|
||||
RenderingMWs.renderOK
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { AuthenticationMWs } from '../../middlewares/user/AuthenticationMWs';
|
||||
import { UserRoles } from '../../../common/entities/UserDTO';
|
||||
import { RenderingMWs } from '../../middlewares/RenderingMWs';
|
||||
import { AdminMWs } from '../../middlewares/admin/AdminMWs';
|
||||
import { Express } from 'express';
|
||||
import { Config } from '../../../common/config/private/Config';
|
||||
import {AuthenticationMWs} from '../../middlewares/user/AuthenticationMWs';
|
||||
import {UserRoles} from '../../../common/entities/UserDTO';
|
||||
import {RenderingMWs} from '../../middlewares/RenderingMWs';
|
||||
import {AdminMWs} from '../../middlewares/admin/AdminMWs';
|
||||
import {Express} from 'express';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
|
||||
export class AdminRouter {
|
||||
public static route(app: Express): void {
|
||||
@ -14,52 +14,52 @@ export class AdminRouter {
|
||||
|
||||
private static addGetStatistic(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/admin/statistic',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.loadStatistic,
|
||||
RenderingMWs.renderResult
|
||||
Config.Server.apiPath + '/admin/statistic',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.loadStatistic,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
private static addGetDuplicates(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/admin/duplicates',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.getDuplicates,
|
||||
RenderingMWs.renderResult
|
||||
Config.Server.apiPath + '/admin/duplicates',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.getDuplicates,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
private static addJobs(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/admin/jobs/available',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.getAvailableJobs,
|
||||
RenderingMWs.renderResult
|
||||
Config.Server.apiPath + '/admin/jobs/available',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.getAvailableJobs,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
app.get(
|
||||
Config.Server.apiPath + '/admin/jobs/scheduled/progress',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.getJobProgresses,
|
||||
RenderingMWs.renderResult
|
||||
Config.Server.apiPath + '/admin/jobs/scheduled/progress',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.getJobProgresses,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
app.post(
|
||||
Config.Server.apiPath + '/admin/jobs/scheduled/:id/start',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.startJob,
|
||||
RenderingMWs.renderResult
|
||||
Config.Server.apiPath + '/admin/jobs/scheduled/:id/start',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.startJob,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
app.post(
|
||||
Config.Server.apiPath + '/admin/jobs/scheduled/:id/stop',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.stopJob,
|
||||
RenderingMWs.renderResult
|
||||
Config.Server.apiPath + '/admin/jobs/scheduled/:id/stop',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.stopJob,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { AuthenticationMWs } from '../../middlewares/user/AuthenticationMWs';
|
||||
import { UserRoles } from '../../../common/entities/UserDTO';
|
||||
import { RenderingMWs } from '../../middlewares/RenderingMWs';
|
||||
import { Express } from 'express';
|
||||
import { SettingsMWs } from '../../middlewares/admin/SettingsMWs';
|
||||
import { Config } from '../../../common/config/private/Config';
|
||||
import {AuthenticationMWs} from '../../middlewares/user/AuthenticationMWs';
|
||||
import {UserRoles} from '../../../common/entities/UserDTO';
|
||||
import {RenderingMWs} from '../../middlewares/RenderingMWs';
|
||||
import {Express} from 'express';
|
||||
import {SettingsMWs} from '../../middlewares/admin/SettingsMWs';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
|
||||
export class SettingsRouter {
|
||||
public static route(app: Express): void {
|
||||
@ -12,18 +12,18 @@ export class SettingsRouter {
|
||||
|
||||
private static addSettings(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/settings',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
RenderingMWs.renderConfig
|
||||
Config.Server.apiPath + '/settings',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
RenderingMWs.renderConfig
|
||||
);
|
||||
|
||||
app.put(
|
||||
Config.Server.apiPath + '/settings',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
SettingsMWs.updateSettings,
|
||||
RenderingMWs.renderOK
|
||||
Config.Server.apiPath + '/settings',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
SettingsMWs.updateSettings,
|
||||
RenderingMWs.renderOK
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import {Event} from '../common/event/Event';
|
||||
import {QueryParams} from '../common/QueryParams';
|
||||
import {ConfigClassBuilder} from 'typeconfig/node';
|
||||
import {ConfigClassOptions} from 'typeconfig/src/decorators/class/IConfigClass';
|
||||
import {DatabaseType, ServerConfig} from '../common/config/private/PrivateConfig';
|
||||
import {ServerConfig} from '../common/config/private/PrivateConfig';
|
||||
import {unless} from 'express-unless';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@ -39,8 +39,8 @@ export class Server {
|
||||
constructor() {
|
||||
if (!(process.env.NODE_ENV === 'production')) {
|
||||
Logger.info(
|
||||
LOG_TAG,
|
||||
'Running in DEBUG mode, set env variable NODE_ENV=production to disable '
|
||||
LOG_TAG,
|
||||
'Running in DEBUG mode, set env variable NODE_ENV=production to disable '
|
||||
);
|
||||
}
|
||||
this.init().catch(console.error);
|
||||
@ -54,13 +54,13 @@ export class Server {
|
||||
Logger.info(LOG_TAG, 'running diagnostics...');
|
||||
await ConfigDiagnostics.runDiagnostics();
|
||||
Logger.verbose(
|
||||
LOG_TAG,
|
||||
'using config from ' +
|
||||
(
|
||||
ConfigClassBuilder.attachPrivateInterface(Config)
|
||||
.__options as ConfigClassOptions<ServerConfig>
|
||||
).configPath +
|
||||
':'
|
||||
LOG_TAG,
|
||||
'using config from ' +
|
||||
(
|
||||
ConfigClassBuilder.attachPrivateInterface(Config)
|
||||
.__options as ConfigClassOptions<ServerConfig>
|
||||
).configPath +
|
||||
':'
|
||||
);
|
||||
Logger.verbose(LOG_TAG, JSON.stringify(Config.toJSON({attachDescription: false}), null, '\t'));
|
||||
|
||||
@ -75,10 +75,10 @@ export class Server {
|
||||
*/
|
||||
|
||||
this.app.use(
|
||||
session({
|
||||
name: CookieNames.session,
|
||||
keys: Config.Server.sessionSecret,
|
||||
})
|
||||
session({
|
||||
name: CookieNames.session,
|
||||
keys: Config.Server.sessionSecret,
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
@ -90,26 +90,26 @@ export class Server {
|
||||
const csuf: any = _csrf();
|
||||
csuf.unless = unless;
|
||||
this.app.use(
|
||||
csuf.unless((req: Request) => {
|
||||
return (
|
||||
Config.Users.authenticationRequired === false ||
|
||||
[Config.Server.apiPath + '/user/login', Config.Server.apiPath + '/user/logout', Config.Server.apiPath + '/share/login'].indexOf(
|
||||
req.originalUrl
|
||||
) !== -1 ||
|
||||
(Config.Sharing.enabled === true &&
|
||||
!!req.query[QueryParams.gallery.sharingKey_query])
|
||||
);
|
||||
})
|
||||
csuf.unless((req: Request) => {
|
||||
return (
|
||||
Config.Users.authenticationRequired === false ||
|
||||
[Config.Server.apiPath + '/user/login', Config.Server.apiPath + '/user/logout', Config.Server.apiPath + '/share/login'].indexOf(
|
||||
req.originalUrl
|
||||
) !== -1 ||
|
||||
(Config.Sharing.enabled === true &&
|
||||
!!req.query[QueryParams.gallery.sharingKey_query])
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// enable token generation but do not check it
|
||||
this.app.post(
|
||||
[Config.Server.apiPath + '/user/login', Config.Server.apiPath + '/share/login'],
|
||||
_csrf({ignoreMethods: ['POST']})
|
||||
[Config.Server.apiPath + '/user/login', Config.Server.apiPath + '/share/login'],
|
||||
_csrf({ignoreMethods: ['POST']})
|
||||
);
|
||||
this.app.get(
|
||||
[Config.Server.apiPath + '/user/me', Config.Server.apiPath + '/share/:' + QueryParams.gallery.sharingKey_params],
|
||||
_csrf({ignoreMethods: ['GET']})
|
||||
[Config.Server.apiPath + '/user/me', Config.Server.apiPath + '/share/:' + QueryParams.gallery.sharingKey_params],
|
||||
_csrf({ignoreMethods: ['GET']})
|
||||
);
|
||||
|
||||
DiskManager.init();
|
||||
@ -176,7 +176,7 @@ export class Server {
|
||||
private onListening = () => {
|
||||
const addr = this.server.address();
|
||||
const bind =
|
||||
typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
|
||||
typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
|
||||
Logger.info(LOG_TAG, 'Listening on ' + bind);
|
||||
};
|
||||
|
||||
|
@ -100,8 +100,8 @@ export class SearchQueryParser {
|
||||
}
|
||||
|
||||
public static stringifyText(
|
||||
text: string,
|
||||
matchType = TextSearchQueryMatchTypes.like
|
||||
text: string,
|
||||
matchType = TextSearchQueryMatchTypes.like
|
||||
): string {
|
||||
if (matchType === TextSearchQueryMatchTypes.exact_match) {
|
||||
return '"' + text + '"';
|
||||
@ -127,8 +127,8 @@ export class SearchQueryParser {
|
||||
text = text.substring(1);
|
||||
}
|
||||
if (
|
||||
text.charAt(text.length - 1) === '"' ||
|
||||
text.charAt(text.length - 1) === ')'
|
||||
text.charAt(text.length - 1) === '"' ||
|
||||
text.charAt(text.length - 1) === ')'
|
||||
) {
|
||||
text = text.substring(0, text.length - 1);
|
||||
}
|
||||
@ -167,9 +167,9 @@ export class SearchQueryParser {
|
||||
|
||||
public parse(str: string, implicitAND = true): SearchQueryDTO {
|
||||
str = str
|
||||
.replace(/\s\s+/g, ' ') // remove double spaces
|
||||
.replace(/:\s+/g, ':')
|
||||
.trim();
|
||||
.replace(/\s\s+/g, ' ') // remove double spaces
|
||||
.replace(/:\s+/g, ':')
|
||||
.trim();
|
||||
|
||||
|
||||
const intFromRegexp = (str: string) => {
|
||||
@ -200,9 +200,9 @@ export class SearchQueryParser {
|
||||
}
|
||||
|
||||
if (
|
||||
quotationMark === false &&
|
||||
bracketIn.length === 0 &&
|
||||
str.charAt(i) === ' '
|
||||
quotationMark === false &&
|
||||
bracketIn.length === 0 &&
|
||||
str.charAt(i) === ' '
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
@ -216,38 +216,38 @@ export class SearchQueryParser {
|
||||
if (tokenEnd !== str.length - 1) {
|
||||
if (str.startsWith(' ' + this.keywords.and, tokenEnd)) {
|
||||
const rest = this.parse(
|
||||
str.slice(tokenEnd + (' ' + this.keywords.and).length),
|
||||
implicitAND
|
||||
str.slice(tokenEnd + (' ' + this.keywords.and).length),
|
||||
implicitAND
|
||||
);
|
||||
return {
|
||||
type: SearchQueryTypes.AND,
|
||||
list: [
|
||||
this.parse(str.slice(0, tokenEnd), implicitAND), // trim brackets
|
||||
...(rest.type === SearchQueryTypes.AND
|
||||
? (rest as SearchListQuery).list
|
||||
: [rest]),
|
||||
? (rest as SearchListQuery).list
|
||||
: [rest]),
|
||||
],
|
||||
} as ANDSearchQuery;
|
||||
} else if (str.startsWith(' ' + this.keywords.or, tokenEnd)) {
|
||||
const rest = this.parse(
|
||||
str.slice(tokenEnd + (' ' + this.keywords.or).length),
|
||||
implicitAND
|
||||
str.slice(tokenEnd + (' ' + this.keywords.or).length),
|
||||
implicitAND
|
||||
);
|
||||
return {
|
||||
type: SearchQueryTypes.OR,
|
||||
list: [
|
||||
this.parse(str.slice(0, tokenEnd), implicitAND), // trim brackets
|
||||
...(rest.type === SearchQueryTypes.OR
|
||||
? (rest as SearchListQuery).list
|
||||
: [rest]),
|
||||
? (rest as SearchListQuery).list
|
||||
: [rest]),
|
||||
],
|
||||
} as ORSearchQuery;
|
||||
} else {
|
||||
// Relation cannot be detected
|
||||
const t =
|
||||
implicitAND === true
|
||||
? SearchQueryTypes.AND
|
||||
: SearchQueryTypes.UNKNOWN_RELATION;
|
||||
implicitAND === true
|
||||
? SearchQueryTypes.AND
|
||||
: SearchQueryTypes.UNKNOWN_RELATION;
|
||||
const rest = this.parse(str.slice(tokenEnd), implicitAND);
|
||||
return {
|
||||
type: t,
|
||||
@ -259,12 +259,12 @@ export class SearchQueryParser {
|
||||
}
|
||||
}
|
||||
if (
|
||||
str.startsWith(this.keywords.someOf + ':') ||
|
||||
new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').test(str)
|
||||
str.startsWith(this.keywords.someOf + ':') ||
|
||||
new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').test(str)
|
||||
) {
|
||||
const prefix = str.startsWith(this.keywords.someOf + ':')
|
||||
? this.keywords.someOf + ':'
|
||||
: new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').exec(str)[0];
|
||||
? this.keywords.someOf + ':'
|
||||
: new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').exec(str)[0];
|
||||
let tmpList: SearchQueryDTO | SearchQueryDTO[] = this.parse(str.slice(prefix.length + 1, -1), false); // trim brackets
|
||||
|
||||
const unfoldList = (q: SearchListQuery): SearchQueryDTO[] => {
|
||||
@ -318,11 +318,11 @@ export class SearchQueryParser {
|
||||
};
|
||||
|
||||
const range = addValueRangeParser(this.keywords.minRating, SearchQueryTypes.min_rating) ||
|
||||
addValueRangeParser(this.keywords.maxRating, SearchQueryTypes.max_rating) ||
|
||||
addValueRangeParser(this.keywords.minResolution, SearchQueryTypes.min_resolution) ||
|
||||
addValueRangeParser(this.keywords.maxResolution, SearchQueryTypes.max_resolution) ||
|
||||
addValueRangeParser(this.keywords.minPersonCount, SearchQueryTypes.min_person_count) ||
|
||||
addValueRangeParser(this.keywords.maxPersonCount, SearchQueryTypes.max_person_count);
|
||||
addValueRangeParser(this.keywords.maxRating, SearchQueryTypes.max_rating) ||
|
||||
addValueRangeParser(this.keywords.minResolution, SearchQueryTypes.min_resolution) ||
|
||||
addValueRangeParser(this.keywords.maxResolution, SearchQueryTypes.max_resolution) ||
|
||||
addValueRangeParser(this.keywords.minPersonCount, SearchQueryTypes.min_person_count) ||
|
||||
addValueRangeParser(this.keywords.maxPersonCount, SearchQueryTypes.max_person_count);
|
||||
|
||||
if (range) {
|
||||
return range;
|
||||
@ -331,11 +331,11 @@ export class SearchQueryParser {
|
||||
|
||||
if (new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').test(str)) {
|
||||
let from = str.slice(
|
||||
new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').exec(str)[0].length
|
||||
new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').exec(str)[0].length
|
||||
);
|
||||
if (
|
||||
(from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') ||
|
||||
(from.charAt(0) === '"' && from.charAt(from.length - 1) === '"')
|
||||
(from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') ||
|
||||
(from.charAt(0) === '"' && from.charAt(from.length - 1) === '"')
|
||||
) {
|
||||
from = from.slice(1, from.length - 1);
|
||||
}
|
||||
@ -353,14 +353,14 @@ export class SearchQueryParser {
|
||||
return {
|
||||
type: SearchQueryTypes.orientation,
|
||||
landscape:
|
||||
str.slice((this.keywords.orientation + ':').length) ===
|
||||
this.keywords.landscape,
|
||||
str.slice((this.keywords.orientation + ':').length) ===
|
||||
this.keywords.landscape,
|
||||
} as OrientationSearch;
|
||||
}
|
||||
|
||||
|
||||
if (kwStartsWith(str, this.keywords.sameDay) ||
|
||||
new RegExp('^' + SearchQueryParser.humanToRegexpStr(this.keywords.lastNDays) + '!?:').test(str)) {
|
||||
new RegExp('^' + SearchQueryParser.humanToRegexpStr(this.keywords.lastNDays) + '!?:').test(str)) {
|
||||
|
||||
const freqStr = str.indexOf('!:') === -1 ? str.slice(str.indexOf(':') + 1) : str.slice(str.indexOf('!:') + 2);
|
||||
let freq: DatePatternFrequency = null;
|
||||
@ -391,7 +391,7 @@ export class SearchQueryParser {
|
||||
daysLength: kwStartsWith(str, this.keywords.sameDay) ? 0 : intFromRegexp(str),
|
||||
frequency: freq,
|
||||
...((new RegExp('^' + SearchQueryParser.humanToRegexpStr(this.keywords.lastNDays) + '!:').test(str) ||
|
||||
str.startsWith(this.keywords.sameDay + '!:')) && {
|
||||
str.startsWith(this.keywords.sameDay + '!:')) && {
|
||||
negate: true
|
||||
}),
|
||||
...(!isNaN(ago) && {agoNumber: ago})
|
||||
@ -404,25 +404,25 @@ export class SearchQueryParser {
|
||||
key: (this.keywords as any)[SearchQueryTypes[type]] + ':',
|
||||
queryTemplate: {type, text: ''} as TextSearch,
|
||||
})).concat(
|
||||
TextSearchQueryTypes.map((type) => ({
|
||||
key: (this.keywords as any)[SearchQueryTypes[type]] + '!:',
|
||||
queryTemplate: {type, text: '', negate: true} as TextSearch,
|
||||
}))
|
||||
TextSearchQueryTypes.map((type) => ({
|
||||
key: (this.keywords as any)[SearchQueryTypes[type]] + '!:',
|
||||
queryTemplate: {type, text: '', negate: true} as TextSearch,
|
||||
}))
|
||||
);
|
||||
for (const typeTmp of tmp) {
|
||||
if (str.startsWith(typeTmp.key)) {
|
||||
const ret: TextSearch = Utils.clone(typeTmp.queryTemplate);
|
||||
// exact match
|
||||
if (
|
||||
str.charAt(typeTmp.key.length) === '"' &&
|
||||
str.charAt(str.length - 1) === '"'
|
||||
str.charAt(typeTmp.key.length) === '"' &&
|
||||
str.charAt(str.length - 1) === '"'
|
||||
) {
|
||||
ret.text = str.slice(typeTmp.key.length + 1, str.length - 1);
|
||||
ret.matchType = TextSearchQueryMatchTypes.exact_match;
|
||||
// like match
|
||||
} else if (
|
||||
str.charAt(typeTmp.key.length) === '(' &&
|
||||
str.charAt(str.length - 1) === ')'
|
||||
str.charAt(typeTmp.key.length) === '(' &&
|
||||
str.charAt(str.length - 1) === ')'
|
||||
) {
|
||||
ret.text = str.slice(typeTmp.key.length + 1, str.length - 1);
|
||||
} else {
|
||||
@ -451,42 +451,42 @@ export class SearchQueryParser {
|
||||
switch (query.type) {
|
||||
case SearchQueryTypes.AND:
|
||||
return (
|
||||
'(' +
|
||||
(query as SearchListQuery).list
|
||||
.map((q) => this.stringifyOnEntry(q))
|
||||
.join(' ' + this.keywords.and + ' ') +
|
||||
')'
|
||||
'(' +
|
||||
(query as SearchListQuery).list
|
||||
.map((q) => this.stringifyOnEntry(q))
|
||||
.join(' ' + this.keywords.and + ' ') +
|
||||
')'
|
||||
);
|
||||
|
||||
case SearchQueryTypes.OR:
|
||||
return (
|
||||
'(' +
|
||||
(query as SearchListQuery).list
|
||||
.map((q) => this.stringifyOnEntry(q))
|
||||
.join(' ' + this.keywords.or + ' ') +
|
||||
')'
|
||||
'(' +
|
||||
(query as SearchListQuery).list
|
||||
.map((q) => this.stringifyOnEntry(q))
|
||||
.join(' ' + this.keywords.or + ' ') +
|
||||
')'
|
||||
);
|
||||
|
||||
case SearchQueryTypes.SOME_OF:
|
||||
if ((query as SomeOfSearchQuery).min) {
|
||||
return (
|
||||
(query as SomeOfSearchQuery).min +
|
||||
'-' +
|
||||
this.keywords.NSomeOf +
|
||||
':(' +
|
||||
(query as SearchListQuery).list
|
||||
.map((q) => this.stringifyOnEntry(q))
|
||||
.join(' ') +
|
||||
')'
|
||||
(query as SomeOfSearchQuery).min +
|
||||
'-' +
|
||||
this.keywords.NSomeOf +
|
||||
':(' +
|
||||
(query as SearchListQuery).list
|
||||
.map((q) => this.stringifyOnEntry(q))
|
||||
.join(' ') +
|
||||
')'
|
||||
);
|
||||
}
|
||||
return (
|
||||
this.keywords.someOf +
|
||||
':(' +
|
||||
(query as SearchListQuery).list
|
||||
.map((q) => this.stringifyOnEntry(q))
|
||||
.join(' ') +
|
||||
')'
|
||||
this.keywords.someOf +
|
||||
':(' +
|
||||
(query as SearchListQuery).list
|
||||
.map((q) => this.stringifyOnEntry(q))
|
||||
.join(' ') +
|
||||
')'
|
||||
);
|
||||
|
||||
|
||||
@ -495,93 +495,93 @@ export class SearchQueryParser {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
this.keywords.from +
|
||||
colon +
|
||||
SearchQueryParser.stringifyDate((query as FromDateSearch).value)
|
||||
this.keywords.from +
|
||||
colon +
|
||||
SearchQueryParser.stringifyDate((query as FromDateSearch).value)
|
||||
);
|
||||
case SearchQueryTypes.to_date:
|
||||
if (!(query as ToDateSearch).value) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
this.keywords.to +
|
||||
colon +
|
||||
SearchQueryParser.stringifyDate((query as ToDateSearch).value)
|
||||
this.keywords.to +
|
||||
colon +
|
||||
SearchQueryParser.stringifyDate((query as ToDateSearch).value)
|
||||
);
|
||||
case SearchQueryTypes.min_rating:
|
||||
return (
|
||||
this.keywords.minRating +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
this.keywords.minRating +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
);
|
||||
case SearchQueryTypes.max_rating:
|
||||
return (
|
||||
this.keywords.maxRating +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
this.keywords.maxRating +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
);
|
||||
case SearchQueryTypes.min_person_count:
|
||||
return (
|
||||
this.keywords.minPersonCount +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
this.keywords.minPersonCount +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
);
|
||||
case SearchQueryTypes.max_person_count:
|
||||
return (
|
||||
this.keywords.maxPersonCount +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
this.keywords.maxPersonCount +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
);
|
||||
case SearchQueryTypes.min_resolution:
|
||||
return (
|
||||
this.keywords.minResolution +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
this.keywords.minResolution +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
);
|
||||
case SearchQueryTypes.max_resolution:
|
||||
return (
|
||||
this.keywords.maxResolution +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
this.keywords.maxResolution +
|
||||
colon +
|
||||
(isNaN((query as RangeSearch).value)
|
||||
? ''
|
||||
: (query as RangeSearch).value)
|
||||
);
|
||||
case SearchQueryTypes.distance:
|
||||
if ((query as DistanceSearch).from.text.indexOf(' ') !== -1) {
|
||||
return (
|
||||
(query as DistanceSearch).distance +
|
||||
'-' +
|
||||
this.keywords.kmFrom +
|
||||
colon +
|
||||
'(' +
|
||||
(query as DistanceSearch).from.text +
|
||||
')'
|
||||
);
|
||||
}
|
||||
return (
|
||||
(query as DistanceSearch).distance +
|
||||
'-' +
|
||||
this.keywords.kmFrom +
|
||||
colon +
|
||||
'(' +
|
||||
(query as DistanceSearch).from.text +
|
||||
')'
|
||||
);
|
||||
}
|
||||
return (
|
||||
(query as DistanceSearch).distance +
|
||||
'-' +
|
||||
this.keywords.kmFrom +
|
||||
colon +
|
||||
(query as DistanceSearch).from.text
|
||||
(query as DistanceSearch).from.text
|
||||
);
|
||||
case SearchQueryTypes.orientation:
|
||||
return (
|
||||
this.keywords.orientation +
|
||||
':' +
|
||||
((query as OrientationSearch).landscape
|
||||
? this.keywords.landscape
|
||||
: this.keywords.portrait)
|
||||
this.keywords.orientation +
|
||||
':' +
|
||||
((query as OrientationSearch).landscape
|
||||
? this.keywords.landscape
|
||||
: this.keywords.portrait)
|
||||
);
|
||||
case SearchQueryTypes.date_pattern: {
|
||||
const q = (query as DatePatternSearch);
|
||||
@ -624,17 +624,17 @@ export class SearchQueryParser {
|
||||
case SearchQueryTypes.any_text:
|
||||
if (!(query as TextSearch).negate) {
|
||||
return SearchQueryParser.stringifyText(
|
||||
(query as TextSearch).text,
|
||||
(query as TextSearch).matchType
|
||||
(query as TextSearch).text,
|
||||
(query as TextSearch).matchType
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
(this.keywords as any)[SearchQueryTypes[query.type]] +
|
||||
colon +
|
||||
SearchQueryParser.stringifyText(
|
||||
(query as TextSearch).text,
|
||||
(query as TextSearch).matchType
|
||||
)
|
||||
(this.keywords as any)[SearchQueryTypes[query.type]] +
|
||||
colon +
|
||||
SearchQueryParser.stringifyText(
|
||||
(query as TextSearch).text,
|
||||
(query as TextSearch).matchType
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -648,12 +648,12 @@ export class SearchQueryParser {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
(this.keywords as any)[SearchQueryTypes[query.type]] +
|
||||
colon +
|
||||
SearchQueryParser.stringifyText(
|
||||
(query as TextSearch).text,
|
||||
(query as TextSearch).matchType
|
||||
)
|
||||
(this.keywords as any)[SearchQueryTypes[query.type]] +
|
||||
colon +
|
||||
SearchQueryParser.stringifyText(
|
||||
(query as TextSearch).text,
|
||||
(query as TextSearch).matchType
|
||||
)
|
||||
);
|
||||
|
||||
default:
|
||||
|
@ -36,18 +36,18 @@ export const SupportedFormats = {
|
||||
};
|
||||
|
||||
SupportedFormats.Photos = SupportedFormats.Photos.concat(
|
||||
SupportedFormats.TranscodeNeed.Photos
|
||||
SupportedFormats.TranscodeNeed.Photos
|
||||
);
|
||||
SupportedFormats.Videos = SupportedFormats.Videos.concat(
|
||||
SupportedFormats.TranscodeNeed.Videos
|
||||
SupportedFormats.TranscodeNeed.Videos
|
||||
);
|
||||
SupportedFormats.WithDots.Photos = SupportedFormats.Photos.map((f) => '.' + f);
|
||||
SupportedFormats.WithDots.Videos = SupportedFormats.Videos.map((f) => '.' + f);
|
||||
SupportedFormats.WithDots.MetaFiles = SupportedFormats.MetaFiles.map(
|
||||
(f) => '.' + f
|
||||
(f) => '.' + f
|
||||
);
|
||||
SupportedFormats.WithDots.TranscodeNeed.Photos =
|
||||
SupportedFormats.TranscodeNeed.Photos.map((f) => '.' + f);
|
||||
SupportedFormats.TranscodeNeed.Photos.map((f) => '.' + f);
|
||||
SupportedFormats.WithDots.TranscodeNeed.Videos =
|
||||
SupportedFormats.TranscodeNeed.Videos.map((f) => '.' + f);
|
||||
SupportedFormats.TranscodeNeed.Videos.map((f) => '.' + f);
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
export class Utils {
|
||||
static GUID(): string {
|
||||
const s4 = (): string =>
|
||||
Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
|
||||
return s4() + s4() + '-' + s4() + s4();
|
||||
}
|
||||
@ -135,8 +135,8 @@ export class Utils {
|
||||
|
||||
public static canonizePath(path: string): string {
|
||||
return path
|
||||
.replace(new RegExp('\\\\', 'g'), '/')
|
||||
.replace(new RegExp('/+', 'g'), '/');
|
||||
.replace(new RegExp('\\\\', 'g'), '/')
|
||||
.replace(new RegExp('/+', 'g'), '/');
|
||||
}
|
||||
|
||||
static concatUrls(...args: string[]): string {
|
||||
@ -262,9 +262,9 @@ export class Utils {
|
||||
const E = Math.pow(10, 38);
|
||||
const nE = Math.pow(10, -38);
|
||||
return (
|
||||
!isNaN(value) &&
|
||||
((value >= -3.402823466 * E && value <= -1.175494351 * nE) ||
|
||||
(value <= 3.402823466 * E && value >= 1.175494351 * nE))
|
||||
!isNaN(value) &&
|
||||
((value >= -3.402823466 * E && value <= -1.175494351 * nE) ||
|
||||
(value <= 3.402823466 * E && value >= 1.175494351 * nE))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ const upTime = new Date().toISOString();
|
||||
// TODO: Refactor Config to be injectable globally.
|
||||
// This is a bad habit to let the Config know if its in a testing env.
|
||||
const isTesting = process.env['NODE_ENV'] == true || ['afterEach', 'after', 'beforeEach', 'before', 'describe', 'it']
|
||||
.every((fn) => (global as any)[fn] instanceof Function);
|
||||
.every((fn) => (global as any)[fn] instanceof Function);
|
||||
|
||||
@ConfigClass<IConfigClass<TAGS> & ServerConfig>({
|
||||
configPath: path.join(__dirname, !isTesting ? './../../../../config.json' : './../../../../test/backend/tmp/config.json'),
|
||||
@ -76,11 +76,11 @@ export class PrivateConfigClass extends ServerConfig {
|
||||
}
|
||||
|
||||
this.Environment.appVersion =
|
||||
require('../../../../package.json').version;
|
||||
require('../../../../package.json').version;
|
||||
this.Environment.buildTime =
|
||||
require('../../../../package.json').buildTime;
|
||||
require('../../../../package.json').buildTime;
|
||||
this.Environment.buildCommitHash =
|
||||
require('../../../../package.json').buildCommitHash;
|
||||
require('../../../../package.json').buildCommitHash;
|
||||
this.Environment.upTime = upTime;
|
||||
this.Environment.isDocker = !!process.env.PI_DOCKER;
|
||||
}
|
||||
@ -89,7 +89,7 @@ export class PrivateConfigClass extends ServerConfig {
|
||||
const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass());
|
||||
try {
|
||||
await pc.load();
|
||||
}catch (e){
|
||||
} catch (e) {
|
||||
console.error('Error during loading original config. Reverting to defaults.');
|
||||
console.error(e);
|
||||
}
|
||||
@ -99,7 +99,7 @@ export class PrivateConfigClass extends ServerConfig {
|
||||
}
|
||||
|
||||
export const Config = ConfigClassBuilder.attachInterface(
|
||||
new PrivateConfigClass()
|
||||
new PrivateConfigClass()
|
||||
);
|
||||
try {
|
||||
Config.loadSync();
|
||||
|
@ -2,7 +2,6 @@
|
||||
import {SubConfigClass} from '../../../../node_modules/typeconfig/src/decorators/class/SubConfigClass';
|
||||
import {ConfigPriority, TAGS} from '../public/ClientConfig';
|
||||
import {ConfigProperty} from '../../../../node_modules/typeconfig/src/decorators/property/ConfigPropoerty';
|
||||
import {ServerConfig} from './PrivateConfig';
|
||||
|
||||
declare let $localize: (s: TemplateStringsArray) => string;
|
||||
|
||||
@ -80,19 +79,19 @@ export class EmailMessagingConfig {
|
||||
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Sender email`,
|
||||
priority: ConfigPriority.advanced,
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Sender email`,
|
||||
priority: ConfigPriority.advanced,
|
||||
} as TAGS,
|
||||
description: $localize`Some services do not allow sending from random e-mail addresses. Set this accordingly.`
|
||||
})
|
||||
emailFrom: string = 'noreply@pigallery2.com';
|
||||
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`SMTP`,
|
||||
}
|
||||
{
|
||||
name: $localize`SMTP`,
|
||||
}
|
||||
})
|
||||
smtp?: EmailSMTPMessagingConfig = new EmailSMTPMessagingConfig();
|
||||
|
||||
@ -102,9 +101,9 @@ export class EmailMessagingConfig {
|
||||
export class MessagingConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Email`,
|
||||
},
|
||||
{
|
||||
name: $localize`Email`,
|
||||
},
|
||||
description: $localize`The app uses Nodemailer in the background for sending e-mails. Refer to https://nodemailer.com/usage/ if some options are not clear.`
|
||||
})
|
||||
Email: EmailMessagingConfig = new EmailMessagingConfig();
|
||||
|
@ -87,14 +87,14 @@ export enum FFmpegPresets {
|
||||
|
||||
export type videoCodecType = 'libvpx-vp9' | 'libx264' | 'libvpx' | 'libx265';
|
||||
export type videoResolutionType =
|
||||
| 240
|
||||
| 360
|
||||
| 480
|
||||
| 720
|
||||
| 1080
|
||||
| 1440
|
||||
| 2160
|
||||
| 4320;
|
||||
| 240
|
||||
| 360
|
||||
| 480
|
||||
| 720
|
||||
| 1080
|
||||
| 1440
|
||||
| 2160
|
||||
| 4320;
|
||||
export type videoFormatType = 'mp4' | 'webm';
|
||||
|
||||
@SubConfigClass({softReadonly: true})
|
||||
@ -102,51 +102,51 @@ export class MySQLConfig {
|
||||
@ConfigProperty({
|
||||
envAlias: 'MYSQL_HOST',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Host`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
{
|
||||
name: $localize`Host`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
})
|
||||
host: string = 'localhost';
|
||||
@ConfigProperty({
|
||||
envAlias: 'MYSQL_PORT', min: 0, max: 65535,
|
||||
tags:
|
||||
{
|
||||
name: $localize`Port`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
{
|
||||
name: $localize`Port`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
})
|
||||
port: number = 3306;
|
||||
@ConfigProperty({
|
||||
envAlias: 'MYSQL_DATABASE',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Database`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
{
|
||||
name: $localize`Database`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
})
|
||||
database: string = 'pigallery2';
|
||||
@ConfigProperty({
|
||||
envAlias: 'MYSQL_USERNAME',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Username`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
{
|
||||
name: $localize`Username`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
})
|
||||
username: string = '';
|
||||
@ConfigProperty({
|
||||
envAlias: 'MYSQL_PASSWORD', type: 'password',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Password`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
}
|
||||
{
|
||||
name: $localize`Password`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
}
|
||||
})
|
||||
password: string = '';
|
||||
}
|
||||
@ -155,11 +155,11 @@ export class MySQLConfig {
|
||||
export class SQLiteConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Sqlite db filename`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Sqlite db filename`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`Sqlite will save the db with this filename.`,
|
||||
})
|
||||
DBFileName: string = 'sqlite.db';
|
||||
@ -169,51 +169,51 @@ export class SQLiteConfig {
|
||||
export class UserConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Name`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
}
|
||||
{
|
||||
name: $localize`Name`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
}
|
||||
})
|
||||
name: string;
|
||||
|
||||
@ConfigProperty({
|
||||
type: UserRoles,
|
||||
tags:
|
||||
{
|
||||
name: $localize`Role`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Role`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
})
|
||||
role: UserRoles = UserRoles.User;
|
||||
|
||||
@ConfigProperty<string, ServerConfig, TAGS>({
|
||||
type: 'string',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Password`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
relevant: (c: UserConfig) => !c.encrypted
|
||||
},
|
||||
{
|
||||
name: $localize`Password`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
relevant: (c: UserConfig) => !c.encrypted
|
||||
},
|
||||
description: $localize`Unencrypted, temporary password. App will encrypt it and delete this.`
|
||||
})
|
||||
password: string;
|
||||
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Encrypted password`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
secret: true
|
||||
},
|
||||
{
|
||||
name: $localize`Encrypted password`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
secret: true
|
||||
},
|
||||
})
|
||||
encryptedPassword: string | undefined;
|
||||
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
priority: ConfigPriority.underTheHood,
|
||||
relevant: () => false // never render this on UI. Only used to indicate that encryption is done.
|
||||
} as TAGS,
|
||||
{
|
||||
priority: ConfigPriority.underTheHood,
|
||||
relevant: () => false // never render this on UI. Only used to indicate that encryption is done.
|
||||
} as TAGS,
|
||||
})
|
||||
encrypted: boolean;
|
||||
|
||||
@ -235,44 +235,44 @@ export class ServerDataBaseConfig {
|
||||
@ConfigProperty<DatabaseType, ServerConfig>({
|
||||
type: DatabaseType,
|
||||
tags:
|
||||
{
|
||||
name: $localize`Type`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiResetNeeded: {db: true},
|
||||
githubIssue: 573
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Type`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiResetNeeded: {db: true},
|
||||
githubIssue: 573
|
||||
} as TAGS,
|
||||
description: $localize`SQLite is recommended.`
|
||||
})
|
||||
type: DatabaseType = DatabaseType.sqlite;
|
||||
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Database folder`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
{
|
||||
name: $localize`Database folder`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
description: $localize`All file-based data will be stored here (sqlite database, job history data).`,
|
||||
})
|
||||
dbFolder: string = 'db';
|
||||
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`SQLite`,
|
||||
uiResetNeeded: {db: true},
|
||||
relevant: (c: any) => c.type === DatabaseType.sqlite,
|
||||
}
|
||||
{
|
||||
name: $localize`SQLite`,
|
||||
uiResetNeeded: {db: true},
|
||||
relevant: (c: any) => c.type === DatabaseType.sqlite,
|
||||
}
|
||||
})
|
||||
sqlite?: SQLiteConfig = new SQLiteConfig();
|
||||
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`MySQL`,
|
||||
uiResetNeeded: {db: true},
|
||||
relevant: (c: any) => c.type === DatabaseType.mysql,
|
||||
}
|
||||
{
|
||||
name: $localize`MySQL`,
|
||||
uiResetNeeded: {db: true},
|
||||
relevant: (c: any) => c.type === DatabaseType.mysql,
|
||||
}
|
||||
})
|
||||
mysql?: MySQLConfig = new MySQLConfig();
|
||||
|
||||
@ -285,13 +285,13 @@ export class ServerUserConfig extends ClientUserConfig {
|
||||
@ConfigProperty({
|
||||
arrayType: UserConfig,
|
||||
tags:
|
||||
{
|
||||
name: $localize`Enforced users`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiResetNeeded: {server: true},
|
||||
uiOptional: true,
|
||||
githubIssue: 575
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Enforced users`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiResetNeeded: {server: true},
|
||||
uiOptional: true,
|
||||
githubIssue: 575
|
||||
} as TAGS,
|
||||
description: $localize`Creates these users in the DB during startup if they do not exist. If a user with this name exist, it won't be overwritten, even if the role is different.`,
|
||||
})
|
||||
enforcedUsers: UserConfig[] = [];
|
||||
@ -302,40 +302,40 @@ export class ServerUserConfig extends ClientUserConfig {
|
||||
export class ServerThumbnailConfig extends ClientThumbnailConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Enforced users`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Enforced users`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`if true, 'lanczos3' will used to scale photos, otherwise faster but lower quality 'nearest'.`
|
||||
})
|
||||
useLanczos3: boolean = true;
|
||||
@ConfigProperty({
|
||||
max: 100, min: 1, type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Converted photo and thumbnail quality`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Converted photo and thumbnail quality`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`Between 0-100.`
|
||||
})
|
||||
quality = 80;
|
||||
@ConfigProperty({
|
||||
type: 'boolean',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Use chroma subsampling`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Use chroma subsampling`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`Use high quality chroma subsampling in webp. See: https://sharp.pixelplumbing.com/api-output#webp.`
|
||||
})
|
||||
smartSubsample = true;
|
||||
@ConfigProperty({
|
||||
type: 'ratio',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Person face margin`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Person face margin`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`Person face size ratio on the face thumbnail.`
|
||||
})
|
||||
personFaceMargin: number = 0.6; // in ration [0-1]
|
||||
@ -345,47 +345,47 @@ export class ServerThumbnailConfig extends ClientThumbnailConfig {
|
||||
export class ServerGPXCompressingConfig extends ClientGPXCompressingConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`OnTheFly *.gpx compression`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
|
||||
},
|
||||
{
|
||||
name: $localize`OnTheFly *.gpx compression`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
|
||||
},
|
||||
description: $localize`Enables on the fly *.gpx compression.`,
|
||||
})
|
||||
onTheFly: boolean = true;
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Min distance`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'm',
|
||||
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Min distance`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'm',
|
||||
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
|
||||
} as TAGS,
|
||||
description: $localize`Filters out entry that are closer than this to each other in meters.`
|
||||
})
|
||||
minDistance: number = 5;
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Max middle point deviance`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'm',
|
||||
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Max middle point deviance`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'm',
|
||||
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
|
||||
} as TAGS,
|
||||
description: $localize`Filters out entry that would fall on the line if we would just connect the previous and the next points. This setting sets the sensitivity for that (higher number, more points are filtered).`
|
||||
})
|
||||
maxMiddleDeviance: number = 5;
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Min time delta`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'ms',
|
||||
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Min time delta`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'ms',
|
||||
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
|
||||
} as TAGS,
|
||||
description: $localize`Filters out entry that are closer than this in time in milliseconds.`
|
||||
})
|
||||
minTimeDistance: number = 5000;
|
||||
@ -395,17 +395,17 @@ export class ServerGPXCompressingConfig extends ClientGPXCompressingConfig {
|
||||
export class ServerMetaFileConfig extends ClientMetaFileConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`GPX compression`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiJob: [{
|
||||
job: DefaultsJobs[DefaultsJobs['GPX Compression']],
|
||||
relevant: (c) => c.MetaFile.GPXCompressing.enabled
|
||||
}, {
|
||||
job: DefaultsJobs[DefaultsJobs['Delete Compressed GPX']],
|
||||
relevant: (c) => c.MetaFile.GPXCompressing.enabled
|
||||
}]
|
||||
} as TAGS
|
||||
{
|
||||
name: $localize`GPX compression`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiJob: [{
|
||||
job: DefaultsJobs[DefaultsJobs['GPX Compression']],
|
||||
relevant: (c) => c.MetaFile.GPXCompressing.enabled
|
||||
}, {
|
||||
job: DefaultsJobs[DefaultsJobs['Delete Compressed GPX']],
|
||||
relevant: (c) => c.MetaFile.GPXCompressing.enabled
|
||||
}]
|
||||
} as TAGS
|
||||
})
|
||||
GPXCompressing: ServerGPXCompressingConfig = new ServerGPXCompressingConfig();
|
||||
}
|
||||
@ -416,11 +416,11 @@ export class ServerSharingConfig extends ClientSharingConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Update timeout`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'ms'
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Update timeout`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'ms'
|
||||
} as TAGS,
|
||||
description: $localize`After creating a sharing link, it can be updated for this long.`
|
||||
})
|
||||
updateTimeout: number = 1000 * 60 * 5;
|
||||
@ -431,47 +431,47 @@ export class ServerIndexingConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Index cache timeout`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'ms'
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Index cache timeout`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
unit: 'ms'
|
||||
} as TAGS,
|
||||
description: $localize`If there was no indexing in this time, it reindexes. (skipped if indexes are in DB and sensitivity is low).`
|
||||
})
|
||||
cachedFolderTimeout: number = 1000 * 60 * 60; // Do not rescans the folder if seems ok
|
||||
@ConfigProperty({
|
||||
type: ReIndexingSensitivity,
|
||||
tags:
|
||||
{
|
||||
name: $localize`Folder reindexing sensitivity`,
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
{
|
||||
name: $localize`Folder reindexing sensitivity`,
|
||||
priority: ConfigPriority.advanced
|
||||
},
|
||||
description: $localize`Set the reindexing sensitivity. High value check the folders for change more often. Setting to never only indexes if never indexed or explicit running the Indexing Job.`
|
||||
})
|
||||
reIndexingSensitivity: ReIndexingSensitivity = ReIndexingSensitivity.low;
|
||||
@ConfigProperty({
|
||||
arrayType: 'string',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Exclude Folder List`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiResetNeeded: {server: true, db: true},
|
||||
uiOptional: true,
|
||||
uiAllowSpaces: true
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Exclude Folder List`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiResetNeeded: {server: true, db: true},
|
||||
uiOptional: true,
|
||||
uiAllowSpaces: true
|
||||
} as TAGS,
|
||||
description: $localize`Folders to exclude from indexing. If an entry starts with '/' it is treated as an absolute path. If it doesn't start with '/' but contains a '/', the path is relative to the image directory. If it doesn't contain a '/', any folder with this name will be excluded.`,
|
||||
})
|
||||
excludeFolderList: string[] = ['.Trash-1000', '.dtrash', '$RECYCLE.BIN'];
|
||||
@ConfigProperty({
|
||||
arrayType: 'string',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Exclude File List`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiResetNeeded: {server: true, db: true},
|
||||
uiOptional: true,
|
||||
hint: $localize`.ignore;.pg2ignore`
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Exclude File List`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiResetNeeded: {server: true, db: true},
|
||||
uiOptional: true,
|
||||
hint: $localize`.ignore;.pg2ignore`
|
||||
} as TAGS,
|
||||
description: $localize`Files that mark a folder to be excluded from indexing. Any folder that contains a file with this name will be excluded from indexing.`,
|
||||
})
|
||||
excludeFileList: string[] = [];
|
||||
@ -481,21 +481,21 @@ export class ServerIndexingConfig {
|
||||
export class ServerThreadingConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Threading`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.underTheHood,
|
||||
} as TAGS,
|
||||
{
|
||||
name: $localize`Threading`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.underTheHood,
|
||||
} as TAGS,
|
||||
description: $localize`[Deprecated, will be removed in the next release] Runs directory scanning and thumbnail generation in a different thread.`
|
||||
})
|
||||
enabled: boolean = false;
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Thumbnail threads`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Thumbnail threads`,
|
||||
uiResetNeeded: {server: true},
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`Number of threads that are used to generate thumbnails. If 0, number of 'CPU cores -1' threads will be used.`,
|
||||
})
|
||||
thumbnailThreads: number = 0; // if zero-> CPU count -1
|
||||
@ -506,10 +506,10 @@ export class ServerDuplicatesConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Max duplicates`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Max duplicates`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`Maximum number of duplicates to list.`
|
||||
})
|
||||
listingLimit: number = 1000;
|
||||
@ -610,21 +610,21 @@ export class JobScheduleConfig implements JobScheduleDTO {
|
||||
},
|
||||
})
|
||||
trigger:
|
||||
| AfterJobTriggerConfig
|
||||
| NeverJobTriggerConfig
|
||||
| PeriodicJobTriggerConfig
|
||||
| ScheduledJobTriggerConfig;
|
||||
|
||||
constructor(
|
||||
name: string,
|
||||
jobName: string,
|
||||
trigger:
|
||||
| AfterJobTriggerConfig
|
||||
| NeverJobTriggerConfig
|
||||
| PeriodicJobTriggerConfig
|
||||
| ScheduledJobTriggerConfig,
|
||||
config: any = {},
|
||||
allowParallelRun: boolean = false
|
||||
| ScheduledJobTriggerConfig;
|
||||
|
||||
constructor(
|
||||
name: string,
|
||||
jobName: string,
|
||||
trigger:
|
||||
| AfterJobTriggerConfig
|
||||
| NeverJobTriggerConfig
|
||||
| PeriodicJobTriggerConfig
|
||||
| ScheduledJobTriggerConfig,
|
||||
config: any = {},
|
||||
allowParallelRun: boolean = false
|
||||
) {
|
||||
this.name = name;
|
||||
this.jobName = jobName;
|
||||
@ -639,20 +639,20 @@ export class ServerJobConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Max saved progress`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Max saved progress`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`Job history size.`
|
||||
})
|
||||
maxSavedProgress: number = 20;
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Processing batch size`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
{
|
||||
name: $localize`Processing batch size`,
|
||||
priority: ConfigPriority.underTheHood
|
||||
},
|
||||
description: $localize`Jobs load this many photos or videos form the DB for processing at once.`
|
||||
})
|
||||
mediaProcessingBatchSize: number = 1000;
|
||||
@ -665,46 +665,46 @@ export class ServerJobConfig {
|
||||
})
|
||||
scheduled: JobScheduleConfig[] = [
|
||||
new JobScheduleConfig(
|
||||
DefaultsJobs[DefaultsJobs.Indexing],
|
||||
DefaultsJobs[DefaultsJobs.Indexing],
|
||||
new NeverJobTriggerConfig(),
|
||||
{indexChangesOnly: true} // set config explicitly, so it is not undefined on the UI
|
||||
DefaultsJobs[DefaultsJobs.Indexing],
|
||||
DefaultsJobs[DefaultsJobs.Indexing],
|
||||
new NeverJobTriggerConfig(),
|
||||
{indexChangesOnly: true} // set config explicitly, so it is not undefined on the UI
|
||||
),
|
||||
new JobScheduleConfig(
|
||||
DefaultsJobs[DefaultsJobs['Album Cover Filling']],
|
||||
DefaultsJobs[DefaultsJobs['Album Cover Filling']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Indexing']]),
|
||||
{}
|
||||
DefaultsJobs[DefaultsJobs['Album Cover Filling']],
|
||||
DefaultsJobs[DefaultsJobs['Album Cover Filling']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Indexing']]),
|
||||
{}
|
||||
),
|
||||
new JobScheduleConfig(
|
||||
DefaultsJobs[DefaultsJobs['Thumbnail Generation']],
|
||||
DefaultsJobs[DefaultsJobs['Thumbnail Generation']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Album Cover Filling']]),
|
||||
{sizes: [240], indexedOnly: true}
|
||||
DefaultsJobs[DefaultsJobs['Thumbnail Generation']],
|
||||
DefaultsJobs[DefaultsJobs['Thumbnail Generation']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Album Cover Filling']]),
|
||||
{sizes: [240], indexedOnly: true}
|
||||
),
|
||||
new JobScheduleConfig(
|
||||
DefaultsJobs[DefaultsJobs['Photo Converting']],
|
||||
DefaultsJobs[DefaultsJobs['Photo Converting']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Thumbnail Generation']]),
|
||||
{indexedOnly: true}
|
||||
DefaultsJobs[DefaultsJobs['Photo Converting']],
|
||||
DefaultsJobs[DefaultsJobs['Photo Converting']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Thumbnail Generation']]),
|
||||
{indexedOnly: true}
|
||||
),
|
||||
new JobScheduleConfig(
|
||||
DefaultsJobs[DefaultsJobs['Video Converting']],
|
||||
DefaultsJobs[DefaultsJobs['Video Converting']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Photo Converting']]),
|
||||
{indexedOnly: true}
|
||||
DefaultsJobs[DefaultsJobs['Video Converting']],
|
||||
DefaultsJobs[DefaultsJobs['Video Converting']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Photo Converting']]),
|
||||
{indexedOnly: true}
|
||||
),
|
||||
new JobScheduleConfig(
|
||||
DefaultsJobs[DefaultsJobs['GPX Compression']],
|
||||
DefaultsJobs[DefaultsJobs['GPX Compression']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Video Converting']]),
|
||||
{indexedOnly: true}
|
||||
DefaultsJobs[DefaultsJobs['GPX Compression']],
|
||||
DefaultsJobs[DefaultsJobs['GPX Compression']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['Video Converting']]),
|
||||
{indexedOnly: true}
|
||||
),
|
||||
new JobScheduleConfig(
|
||||
DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']],
|
||||
DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['GPX Compression']]),
|
||||
{indexedOnly: true}
|
||||
DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']],
|
||||
DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']],
|
||||
new AfterJobTriggerConfig(DefaultsJobs[DefaultsJobs['GPX Compression']]),
|
||||
{indexedOnly: true}
|
||||
),
|
||||
];
|
||||
}
|
||||
@ -714,73 +714,73 @@ export class VideoTranscodingConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Bit rate`,
|
||||
priority: ConfigPriority.advanced,
|
||||
unit: 'bps'
|
||||
},
|
||||
{
|
||||
name: $localize`Bit rate`,
|
||||
priority: ConfigPriority.advanced,
|
||||
unit: 'bps'
|
||||
},
|
||||
description: $localize`Target bit rate of the output video will be scaled down this this. This should be less than the upload rate of your home server.`
|
||||
})
|
||||
bitRate: number = 5 * 1024 * 1024;
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
tags:
|
||||
{
|
||||
name: $localize`Resolution`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiOptions: [720, 1080, 1440, 2160, 4320],
|
||||
unit: 'px'
|
||||
},
|
||||
{
|
||||
name: $localize`Resolution`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiOptions: [720, 1080, 1440, 2160, 4320],
|
||||
unit: 'px'
|
||||
},
|
||||
description: $localize`The height of the output video will be scaled down to this, while keeping the aspect ratio.`
|
||||
})
|
||||
resolution: videoResolutionType = 720;
|
||||
@ConfigProperty({
|
||||
type: 'positiveFloat',
|
||||
tags:
|
||||
{
|
||||
name: $localize`FPS`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiOptions: [24, 25, 30, 48, 50, 60]
|
||||
},
|
||||
{
|
||||
name: $localize`FPS`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiOptions: [24, 25, 30, 48, 50, 60]
|
||||
},
|
||||
description: $localize`Target frame per second (fps) of the output video will be scaled down this this.`
|
||||
})
|
||||
fps: number = 25;
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Format`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiOptions: ['mp4', 'webm']
|
||||
}
|
||||
{
|
||||
name: $localize`Format`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiOptions: ['mp4', 'webm']
|
||||
}
|
||||
})
|
||||
format: videoFormatType = 'mp4';
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`MP4 codec`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiOptions: ['libx264', 'libx265'],
|
||||
relevant: (c: any) => c.format === 'mp4'
|
||||
}
|
||||
{
|
||||
name: $localize`MP4 codec`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiOptions: ['libx264', 'libx265'],
|
||||
relevant: (c: any) => c.format === 'mp4'
|
||||
}
|
||||
})
|
||||
mp4Codec: videoCodecType = 'libx264';
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Webm Codec`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiOptions: ['libvpx', 'libvpx-vp9'],
|
||||
relevant: (c: any) => c.format === 'webm'
|
||||
}
|
||||
{
|
||||
name: $localize`Webm Codec`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiOptions: ['libvpx', 'libvpx-vp9'],
|
||||
relevant: (c: any) => c.format === 'webm'
|
||||
}
|
||||
})
|
||||
webmCodec: videoCodecType = 'libvpx';
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt', max: 51,
|
||||
tags:
|
||||
{
|
||||
name: $localize`CRF`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
},
|
||||
{
|
||||
name: $localize`CRF`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
},
|
||||
description: $localize`The range of the Constant Rate Factor (CRF) scale is 0–51, where 0 is lossless, 23 is the default, and 51 is worst quality possible.`,
|
||||
|
||||
})
|
||||
@ -788,10 +788,10 @@ export class VideoTranscodingConfig {
|
||||
@ConfigProperty({
|
||||
type: FFmpegPresets,
|
||||
tags:
|
||||
{
|
||||
name: $localize`Preset`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
{
|
||||
name: $localize`Preset`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
description: $localize`A preset is a collection of options that will provide a certain encoding speed to compression ratio. A slower preset will provide better compression (compression is quality per filesize).`,
|
||||
})
|
||||
preset: FFmpegPresets = FFmpegPresets.medium;
|
||||
@ -843,7 +843,7 @@ export class PhotoConvertingConfig extends ClientPhotoConvertingConfig {
|
||||
name: $localize`On the fly converting`,
|
||||
priority: ConfigPriority.underTheHood,
|
||||
uiDisabled: (sc: PhotoConvertingConfig) =>
|
||||
!sc.enabled
|
||||
!sc.enabled
|
||||
|
||||
},
|
||||
description: $localize`Converts photos on the fly, when they are requested.`,
|
||||
@ -857,7 +857,7 @@ export class PhotoConvertingConfig extends ClientPhotoConvertingConfig {
|
||||
uiOptions: [720, 1080, 1440, 2160, 4320],
|
||||
unit: 'px',
|
||||
uiDisabled: (sc: PhotoConvertingConfig) =>
|
||||
!sc.enabled
|
||||
!sc.enabled
|
||||
},
|
||||
description: $localize`The shorter edge of the converted photo will be scaled down to this, while keeping the aspect ratio.`,
|
||||
})
|
||||
|
@ -17,11 +17,11 @@ declare namespace ServerInject {
|
||||
}
|
||||
|
||||
export const Config: IWebConfigClass & ClientClass =
|
||||
WebConfigClassBuilder.attachInterface(new ClientClass());
|
||||
WebConfigClassBuilder.attachInterface(new ClientClass());
|
||||
|
||||
if (
|
||||
typeof ServerInject !== 'undefined' &&
|
||||
typeof ServerInject.ConfigInject !== 'undefined'
|
||||
typeof ServerInject !== 'undefined' &&
|
||||
typeof ServerInject.ConfigInject !== 'undefined'
|
||||
) {
|
||||
Config.load(ServerInject.ConfigInject);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { SearchQueryTypes } from './SearchQueryDTO';
|
||||
import {SearchQueryTypes} from './SearchQueryDTO';
|
||||
|
||||
export interface IAutoCompleteItem {
|
||||
text: string;
|
||||
@ -6,7 +6,8 @@ export interface IAutoCompleteItem {
|
||||
}
|
||||
|
||||
export class AutoCompleteItem implements IAutoCompleteItem {
|
||||
constructor(public text: string, public type: SearchQueryTypes = null) {}
|
||||
constructor(public text: string, public type: SearchQueryTypes = null) {
|
||||
}
|
||||
|
||||
equals(other: AutoCompleteItem): boolean {
|
||||
return this.text === other.text && this.type === other.type;
|
||||
|
@ -28,9 +28,9 @@ export class ContentWrapper {
|
||||
public notModified?: boolean;
|
||||
|
||||
constructor(
|
||||
directory: ParentDirectoryDTO = null,
|
||||
searchResult: SearchResultDTO = null,
|
||||
notModified?: boolean
|
||||
directory: ParentDirectoryDTO = null,
|
||||
searchResult: SearchResultDTO = null,
|
||||
notModified?: boolean
|
||||
) {
|
||||
if (directory) {
|
||||
this.directory = directory;
|
||||
@ -138,15 +138,15 @@ export class ContentWrapper {
|
||||
if ((media as PhotoDTO).metadata.cameraData) {
|
||||
if ((media as PhotoDTO).metadata.cameraData.lens) {
|
||||
mapifyOne(cw.map.lens, cw.reverseMap.lens,
|
||||
(media as PhotoDTO).metadata.cameraData, 'lens', 'l');
|
||||
(media as PhotoDTO).metadata.cameraData, 'lens', 'l');
|
||||
}
|
||||
if ((media as PhotoDTO).metadata.cameraData.make) {
|
||||
mapifyOne(cw.map.camera, cw.reverseMap.camera,
|
||||
(media as PhotoDTO).metadata.cameraData, 'make', 'm');
|
||||
(media as PhotoDTO).metadata.cameraData, 'make', 'm');
|
||||
}
|
||||
if ((media as PhotoDTO).metadata.cameraData.model) {
|
||||
mapifyOne(cw.map.camera, cw.reverseMap.camera,
|
||||
(media as PhotoDTO).metadata.cameraData, 'model', 'o');
|
||||
(media as PhotoDTO).metadata.cameraData, 'model', 'o');
|
||||
}
|
||||
|
||||
if ((media as PhotoDTO).metadata.cameraData.ISO) {
|
||||
@ -177,15 +177,15 @@ export class ContentWrapper {
|
||||
if ((media as PhotoDTO).metadata.positionData) {
|
||||
if ((media as PhotoDTO).metadata.positionData.country) {
|
||||
mapifyOne(cw.map.keywords, cw.reverseMap.keywords,
|
||||
(media as PhotoDTO).metadata.positionData, 'country', 'c');
|
||||
(media as PhotoDTO).metadata.positionData, 'country', 'c');
|
||||
}
|
||||
if ((media as PhotoDTO).metadata.positionData.city) {
|
||||
mapifyOne(cw.map.keywords, cw.reverseMap.keywords,
|
||||
(media as PhotoDTO).metadata.positionData, 'city', 'cy');
|
||||
(media as PhotoDTO).metadata.positionData, 'city', 'cy');
|
||||
}
|
||||
if ((media as PhotoDTO).metadata.positionData.state) {
|
||||
mapifyOne(cw.map.keywords, cw.reverseMap.keywords,
|
||||
(media as PhotoDTO).metadata.positionData, 'state', 's');
|
||||
(media as PhotoDTO).metadata.positionData, 'state', 's');
|
||||
}
|
||||
|
||||
if ((media as PhotoDTO).metadata.positionData.GPSData) {
|
||||
@ -412,17 +412,17 @@ export class ContentWrapper {
|
||||
// @ts-ignore
|
||||
if (typeof (media as PhotoDTO).metadata.cameraData.l !== 'undefined') {
|
||||
deMapifyOne(cw.map.lens,
|
||||
(media as PhotoDTO).metadata.cameraData, 'lens', 'l');
|
||||
(media as PhotoDTO).metadata.cameraData, 'lens', 'l');
|
||||
}
|
||||
// @ts-ignore
|
||||
if (typeof (media as PhotoDTO).metadata.cameraData.m !== 'undefined') {
|
||||
deMapifyOne(cw.map.camera,
|
||||
(media as PhotoDTO).metadata.cameraData, 'make', 'm');
|
||||
(media as PhotoDTO).metadata.cameraData, 'make', 'm');
|
||||
}
|
||||
// @ts-ignore
|
||||
if (typeof (media as PhotoDTO).metadata.cameraData['o'] !== 'undefined') {
|
||||
deMapifyOne(cw.map.camera,
|
||||
(media as PhotoDTO).metadata.cameraData, 'model', 'o');
|
||||
(media as PhotoDTO).metadata.cameraData, 'model', 'o');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
@ -464,28 +464,28 @@ export class ContentWrapper {
|
||||
// @ts-ignore
|
||||
if (typeof (media as PhotoDTO).metadata.positionData.c !== 'undefined') {
|
||||
deMapifyOne(cw.map.keywords,
|
||||
(media as PhotoDTO).metadata.positionData, 'country', 'c');
|
||||
(media as PhotoDTO).metadata.positionData, 'country', 'c');
|
||||
}
|
||||
// @ts-ignore
|
||||
if (typeof (media as PhotoDTO).metadata.positionData.cy !== 'undefined') {
|
||||
deMapifyOne(cw.map.keywords,
|
||||
(media as PhotoDTO).metadata.positionData, 'city', 'cy');
|
||||
(media as PhotoDTO).metadata.positionData, 'city', 'cy');
|
||||
}
|
||||
// @ts-ignore
|
||||
if (typeof (media as PhotoDTO).metadata.positionData.s !== 'undefined') {
|
||||
deMapifyOne(cw.map.keywords,
|
||||
(media as PhotoDTO).metadata.positionData, 'state', 's');
|
||||
(media as PhotoDTO).metadata.positionData, 'state', 's');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if ((media as PhotoDTO).metadata.positionData['g']) {
|
||||
(media as PhotoDTO).metadata.positionData.GPSData =
|
||||
{
|
||||
// @ts-ignore
|
||||
latitude: (media as PhotoDTO).metadata.positionData['g'][0],
|
||||
// @ts-ignore
|
||||
longitude: (media as PhotoDTO).metadata.positionData['g'][1]
|
||||
};
|
||||
{
|
||||
// @ts-ignore
|
||||
latitude: (media as PhotoDTO).metadata.positionData['g'][0],
|
||||
// @ts-ignore
|
||||
longitude: (media as PhotoDTO).metadata.positionData['g'][1]
|
||||
};
|
||||
// @ts-ignore
|
||||
delete (media as PhotoDTO).metadata.positionData['g'];
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { MediaDTO, MediaDTOUtils } from './MediaDTO';
|
||||
import { FileDTO } from './FileDTO';
|
||||
import { PhotoDTO, CoverPhotoDTO } from './PhotoDTO';
|
||||
import { Utils } from '../Utils';
|
||||
import {MediaDTO} from './MediaDTO';
|
||||
import {FileDTO} from './FileDTO';
|
||||
import {CoverPhotoDTO} from './PhotoDTO';
|
||||
import {Utils} from '../Utils';
|
||||
|
||||
export interface DirectoryPathDTO {
|
||||
name: string;
|
||||
@ -9,9 +9,8 @@ export interface DirectoryPathDTO {
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface DirectoryBaseDTO<S extends FileDTO = MediaDTO>
|
||||
extends DirectoryPathDTO {
|
||||
extends DirectoryPathDTO {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
@ -30,7 +29,7 @@ export interface DirectoryBaseDTO<S extends FileDTO = MediaDTO>
|
||||
}
|
||||
|
||||
export interface ParentDirectoryDTO<S extends FileDTO = MediaDTO>
|
||||
extends DirectoryBaseDTO<S> {
|
||||
extends DirectoryBaseDTO<S> {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
@ -47,7 +46,7 @@ export interface ParentDirectoryDTO<S extends FileDTO = MediaDTO>
|
||||
}
|
||||
|
||||
export interface SubDirectoryDTO<S extends FileDTO = MediaDTO>
|
||||
extends DirectoryBaseDTO<S> {
|
||||
extends DirectoryBaseDTO<S> {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { MediaDTO } from './MediaDTO';
|
||||
import {MediaDTO} from './MediaDTO';
|
||||
|
||||
export interface DuplicatesDTO {
|
||||
media: MediaDTO[];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Request } from 'express';
|
||||
import {Request} from 'express';
|
||||
|
||||
export enum ErrorCodes {
|
||||
NOT_AUTHENTICATED = 1,
|
||||
@ -33,16 +33,16 @@ export class ErrorDTO {
|
||||
public request: {
|
||||
method: string;
|
||||
url: string;
|
||||
} = { method: '', url: '' };
|
||||
} = {method: '', url: ''};
|
||||
|
||||
constructor(
|
||||
public code: ErrorCodes,
|
||||
public message?: string,
|
||||
public details?: any,
|
||||
req?: Request
|
||||
public code: ErrorCodes,
|
||||
public message?: string,
|
||||
public details?: any,
|
||||
req?: Request
|
||||
) {
|
||||
this.detailsStr =
|
||||
(this.details ? this.details.toString() : '') || ErrorCodes[code];
|
||||
(this.details ? this.details.toString() : '') || ErrorCodes[code];
|
||||
if (req) {
|
||||
this.request = {
|
||||
method: req.method,
|
||||
|
@ -1,7 +1,8 @@
|
||||
export class LoginCredential {
|
||||
constructor(
|
||||
public username: string = '',
|
||||
public password: string = '',
|
||||
public rememberMe: boolean = true
|
||||
) {}
|
||||
public username: string = '',
|
||||
public password: string = '',
|
||||
public rememberMe: boolean = true
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
@ -25,15 +25,15 @@ export interface MediaDimension {
|
||||
export const MediaDTOUtils = {
|
||||
hasPositionData: (media: MediaDTO): boolean => {
|
||||
return (
|
||||
!!(media as PhotoDTO).metadata.positionData &&
|
||||
!!(
|
||||
(media as PhotoDTO).metadata.positionData.city ||
|
||||
(media as PhotoDTO).metadata.positionData.state ||
|
||||
(media as PhotoDTO).metadata.positionData.country ||
|
||||
((media as PhotoDTO).metadata.positionData.GPSData &&
|
||||
(media as PhotoDTO).metadata.positionData.GPSData.latitude &&
|
||||
(media as PhotoDTO).metadata.positionData.GPSData.longitude)
|
||||
)
|
||||
!!(media as PhotoDTO).metadata.positionData &&
|
||||
!!(
|
||||
(media as PhotoDTO).metadata.positionData.city ||
|
||||
(media as PhotoDTO).metadata.positionData.state ||
|
||||
(media as PhotoDTO).metadata.positionData.country ||
|
||||
((media as PhotoDTO).metadata.positionData.GPSData &&
|
||||
(media as PhotoDTO).metadata.positionData.GPSData.latitude &&
|
||||
(media as PhotoDTO).metadata.positionData.GPSData.longitude)
|
||||
)
|
||||
);
|
||||
},
|
||||
isPhoto: (media: FileDTO): boolean => {
|
||||
@ -76,7 +76,7 @@ export const MediaDTOUtils = {
|
||||
|
||||
equals: (a: MediaDTO, b: MediaDTO): boolean => {
|
||||
return a.directory.path === b.directory.path &&
|
||||
a.directory.name === b.directory.name &&
|
||||
a.name === b.name;
|
||||
a.directory.name === b.directory.name &&
|
||||
a.name === b.name;
|
||||
}
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user