1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-12-23 01:27:14 +02:00

improving linting

This commit is contained in:
Patrik J. Braun 2023-09-11 18:57:51 +02:00
parent 19aa1a0e90
commit 81068b6d66
210 changed files with 5138 additions and 5242 deletions

View File

@ -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();

View File

@ -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;

View File

@ -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
)
);
}
}

View File

@ -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()
)
);
}
}

View File

@ -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 {

View File

@ -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
)
);
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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();

View File

@ -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()
)
);
}
}

View File

@ -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
)
);
}
}

View File

@ -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'));
}

View File

@ -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();

View File

@ -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') {

View File

@ -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;
}

View File

@ -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));

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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.',
},
];

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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');
}

View File

@ -1,4 +1,4 @@
import { ParentDirectoryDTO } from '../../../common/entities/DirectoryDTO';
import {ParentDirectoryDTO} from '../../../common/entities/DirectoryDTO';
export interface IObjectManager {
onNewDataVersion?: (changedDir?: ParentDirectoryDTO) => Promise<void>;

View File

@ -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;

View File

@ -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> {

View File

@ -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}}));

View File

@ -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

View File

@ -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');
}

View File

@ -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;

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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,
})

View File

@ -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;

View File

@ -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,
})

View File

@ -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,
})

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,4 +1,4 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm';
@Entity()
export class VersionEntity {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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

View File

@ -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()
);
}
}

View File

@ -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());

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -22,7 +22,7 @@ export class GPXCompressionJob extends FileJob {
protected async shouldProcess(fPath: string): Promise<boolean> {
return !(await GPXProcessing.compressedGPXExist(
fPath
fPath
));
}

View File

@ -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;

View File

@ -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;

View File

@ -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++;

View File

@ -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;

View File

@ -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;

View File

@ -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
));
}

View File

@ -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 {

View File

@ -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
);
}
}

View File

@ -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);

View File

@ -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 {

View File

@ -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>';

View File

@ -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 => {

View File

@ -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
);
}

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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,

View File

@ -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);
});
}
}

View File

@ -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();

View File

@ -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
);
}
}

View File

@ -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
);
}
}

View File

@ -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
);
}
}

View File

@ -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 => {

View File

@ -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
);
}
}

View File

@ -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
);
}
}

View File

@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
confCopy.Server.customHTMLHead
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
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()
);
}
}

View File

@ -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 {

View File

@ -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
);
}
}

View File

@ -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
);
}
}

View File

@ -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
);
}
}

View File

@ -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
);
}

View File

@ -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);
};

View File

@ -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:

View File

@ -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);

View File

@ -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))
);
}

View File

@ -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();

View File

@ -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();

View File

@ -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.`,
})

View File

@ -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);
}

View File

@ -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;

View File

@ -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'];
}

View File

@ -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;

View File

@ -1,4 +1,4 @@
import { MediaDTO } from './MediaDTO';
import {MediaDTO} from './MediaDTO';
export interface DuplicatesDTO {
media: MediaDTO[];

View File

@ -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,

View File

@ -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
) {
}
}

View File

@ -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