1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-09-16 09:16:27 +02:00

fixing linting erros. adding tslint to pretest

This commit is contained in:
Patrik J. Braun
2021-04-18 15:48:35 +02:00
parent 2223c87b9e
commit 085d7c2cd9
194 changed files with 3226 additions and 3277 deletions

View File

@@ -14,4 +14,4 @@ last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

View File

@@ -10,6 +10,10 @@ trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_prefer_as_type_cast = true
ij_typescript_prefer_explicit_types_function_returns = true
ij_typescript_prefer_explicit_types_function_expression_returns = true
[*.md]
max_line_length = off

View File

@@ -1,6 +1,6 @@
import {BenchmarkResult} from './BenchmarkRunner';
import {ContentWrapper} from '../src/common/entities/ConentWrapper';
import {Express} from 'express';
import {Express, NextFunction} from 'express';
import {Utils} from '../src/common/Utils';
import {Message} from '../src/common/entities/Message';
@@ -20,30 +20,30 @@ class BMExpressApp {
this.benchmark = benchmark;
}
get(match: string | string[], ...functions: ((req: any, res: any, next: Function) => void)[]) {
functions.forEach(f => {
get(match: string | string[], ...functions: ((req: any, res: any, next: NextFunction) => void)[]): void {
functions.forEach((f): void => {
this.benchmark.addAStep({
name: this.camelToSpaceSeparated(f.name),
fn: (request: any) => this.nextToPromise(f, request)
fn: (request: any): Promise<void> => this.nextToPromise(f, request)
});
});
}
private camelToSpaceSeparated(text: string) {
private camelToSpaceSeparated(text: string): string {
const result = (text.replace(/([A-Z])/g, ' $1')).toLocaleLowerCase();
return result.charAt(0).toUpperCase() + result.slice(1);
}
private nextToPromise(fn: (req: any, res: any, next: Function) => void, request: any) {
return new Promise<void>((resolve, reject) => {
private nextToPromise(fn: (req: any, res: any, next: NextFunction) => void, request: any): Promise<void> {
return new Promise<void>((resolve, reject): void => {
const response = {
header: () => {
header: (): void => {
},
json: (data: any) => {
json: (data: any): void => {
resolve(data);
}
};
fn(request, response, (err?: any) => {
fn(request, response, (err?: any): void => {
if (err) {
return reject(err);
}
@@ -76,7 +76,7 @@ export class Benchmark {
}
get BmExpressApp(): Express {
return (<unknown>this.bmExpressApp) as Express;
return (this.bmExpressApp as unknown) as Express;
}
async run(RUNS: number): Promise<BenchmarkResult> {
@@ -106,7 +106,7 @@ export class Benchmark {
const ret = this.outputToBMResult(this.name, scanned[scanned.length - 1]);
ret.duration = duration;
ret.subBenchmarks = scanned.map((o, i) => {
ret.subBenchmarks = scanned.map((o, i): BenchmarkResult => {
const stepBm = this.outputToBMResult(this.steps[i].name, o);
stepBm.duration = stepTimer[i] / RUNS;
return stepBm;
@@ -120,7 +120,7 @@ export class Benchmark {
if (output) {
if (Array.isArray(output)) {
return {
name: name,
name,
duration: null,
items: output.length,
};
@@ -128,7 +128,7 @@ export class Benchmark {
if (output instanceof ContentWrapper) {
return {
name: name,
name,
duration: null,
contentWrapper: output
};
@@ -137,7 +137,7 @@ export class Benchmark {
const msg = output.result;
if (Array.isArray(msg)) {
return {
name: name,
name,
duration: null,
items: msg.length,
};
@@ -145,7 +145,7 @@ export class Benchmark {
if (msg instanceof ContentWrapper) {
return {
name: name,
name,
duration: null,
contentWrapper: msg
};
@@ -154,7 +154,7 @@ export class Benchmark {
}
return {
name: name,
name,
duration: null
};
}
@@ -188,7 +188,7 @@ export class Benchmark {
return stepTimer;
}
addAStep(step: BenchmarkStep) {
addAStep(step: BenchmarkStep): void {
this.steps.push(step);
}

View File

@@ -6,7 +6,7 @@ import * as path from 'path';
import * as fs from 'fs';
import {Utils} from '../src/common/Utils';
import {DirectoryDTO} from '../src/common/entities/DirectoryDTO';
import {ServerConfig} from '../src/common/config/private/PrivateConfig';
import {DatabaseType, ReIndexingSensitivity} from '../src/common/config/private/PrivateConfig';
import {ProjectPath} from '../src/backend/ProjectPath';
import {Benchmark} from './Benchmark';
import {IndexingJob} from '../src/backend/model/jobs/jobs/IndexingJob';
@@ -40,21 +40,21 @@ export class BMIndexingManager extends IndexingManager {
class BMGalleryRouter extends GalleryRouter {
public static addDirectoryList(app: Express) {
public static addDirectoryList(app: Express): void {
GalleryRouter.addDirectoryList(app);
}
public static addSearch(app: Express) {
public static addSearch(app: Express): void {
GalleryRouter.addSearch(app);
}
public static addAutoComplete(app: Express) {
public static addAutoComplete(app: Express): void {
GalleryRouter.addAutoComplete(app);
}
}
class BMPersonRouter extends PersonRouter {
public static addGetPersons(app: Express) {
public static addGetPersons(app: Express): void {
PersonRouter.addGetPersons(app);
}
}
@@ -77,10 +77,10 @@ export class BenchmarkRunner {
await this.init();
await this.resetDB();
const dir = await DiskMangerWorker.scanDirectory(this.biggestDirPath);
const bm = new Benchmark('Saving directory to DB', null, () => this.resetDB());
const bm = new Benchmark('Saving directory to DB', null, (): Promise<void> => this.resetDB());
bm.addAStep({
name: 'Saving directory to DB',
fn: () => {
fn: (): Promise<void> => {
const im = new BMIndexingManager();
return im.saveToDB(dir);
}
@@ -93,19 +93,19 @@ export class BenchmarkRunner {
const bm = new Benchmark('Scanning directory');
bm.addAStep({
name: 'Scanning directory',
fn: async () => new ContentWrapper(await DiskMangerWorker.scanDirectory(this.biggestDirPath))
fn: async (): Promise<ContentWrapper> => new ContentWrapper(await DiskMangerWorker.scanDirectory(this.biggestDirPath))
});
return await bm.run(this.RUNS);
}
async bmListDirectory(): Promise<BenchmarkResult> {
Config.Server.Indexing.reIndexingSensitivity = ServerConfig.ReIndexingSensitivity.low;
Config.Server.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
await this.init();
await this.setupDB();
const req = Utils.clone(this.requestTemplate);
req.params.directory = this.biggestDirPath;
const bm = new Benchmark('List directory', req,
async () => {
async (): Promise<void> => {
await ObjectManagers.reset();
await ObjectManagers.InitSQLManagers();
});
@@ -115,8 +115,8 @@ export class BenchmarkRunner {
async bmListPersons(): Promise<BenchmarkResult> {
await this.setupDB();
Config.Server.Indexing.reIndexingSensitivity = ServerConfig.ReIndexingSensitivity.low;
const bm = new Benchmark('Listing Faces', Utils.clone(this.requestTemplate), async () => {
Config.Server.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
const bm = new Benchmark('Listing Faces', Utils.clone(this.requestTemplate), async (): Promise<void> => {
await ObjectManagers.reset();
await ObjectManagers.InitSQLManagers();
});
@@ -126,16 +126,16 @@ export class BenchmarkRunner {
async bmAllSearch(text: string): Promise<{ result: BenchmarkResult, searchType: SearchQueryTypes }[]> {
await this.setupDB();
const types = Utils.enumToArray(SearchQueryTypes).map(a => a.key).concat([null]);
const types = Utils.enumToArray(SearchQueryTypes).map((a): number => a.key).concat([null]);
const results: { result: BenchmarkResult, searchType: SearchQueryTypes }[] = [];
for (let i = 0; i < types.length; i++) {
for (const type of types) {
const req = Utils.clone(this.requestTemplate);
req.query[QueryParams.gallery.search.query] = <TextSearch>{type: types[i], text: text};
const bm = new Benchmark('Searching for `' + text + '` as `' + (types[i] ? SearchQueryTypes[types[i]] : 'any') + '`', req);
req.query[QueryParams.gallery.search.query] = ({type, text} as TextSearch);
const bm = new Benchmark('Searching for `' + text + '` as `' + (type ? SearchQueryTypes[type] : 'any') + '`', req);
BMGalleryRouter.addSearch(bm.BmExpressApp);
results.push({result: await bm.run(this.RUNS), searchType: types[i]});
results.push({result: await bm.run(this.RUNS), searchType: type});
}
return results;
}
@@ -150,7 +150,7 @@ export class BenchmarkRunner {
return await bm.run(this.RUNS);
}
async getStatistic() {
async getStatistic(): Promise<string> {
await this.setupDB();
const gm = new GalleryManager();
const pm = new PersonManager();
@@ -165,7 +165,7 @@ export class BenchmarkRunner {
}
private async init() {
private async init(): Promise<string> {
if (this.inited === false) {
await this.setupDB();
@@ -176,7 +176,7 @@ export class BenchmarkRunner {
while (queue.length > 0) {
const dirPath = queue.shift();
const dir = await gm.listDirectory(dirPath);
dir.directories.forEach(d => queue.push(path.join(d.path + d.name)));
dir.directories.forEach((d): number => queue.push(path.join(d.path + d.name)));
if (biggest < dir.media.length) {
biggestPath = path.join(dir.path + dir.name);
biggest = dir.media.length;
@@ -191,11 +191,11 @@ export class BenchmarkRunner {
}
private resetDB = async () => {
private resetDB = async (): Promise<void> => {
Config.Server.Threading.enabled = false;
await ObjectManagers.reset();
await fs.promises.rmdir(ProjectPath.DBFolder, {recursive: true});
Config.Server.Database.type = ServerConfig.DatabaseType.sqlite;
Config.Server.Database.type = DatabaseType.sqlite;
Config.Server.Jobs.scheduled = [];
await ObjectManagers.InitSQLManagers();
};
@@ -203,16 +203,16 @@ export class BenchmarkRunner {
private async setupDB(): Promise<void> {
Config.Server.Threading.enabled = false;
await this.resetDB();
await new Promise<void>((resolve, reject) => {
await new Promise<void>((resolve, reject): void => {
try {
const indexingJob = new IndexingJob();
indexingJob.JobListener = {
onJobFinished: (job: IJob<any>, state: JobProgressStates, soloRun: boolean) => {
onJobFinished: (job: IJob<any>, state: JobProgressStates, soloRun: boolean): void => {
resolve();
},
onProgressUpdate: (progress: JobProgress) => {
onProgressUpdate: (progress: JobProgress): void => {
}
};
indexingJob.start().catch(console.error);

View File

@@ -51,8 +51,8 @@ const printResult = (result: BenchmarkResult, isSubResult = false) => {
printLine('| **' + result.name + '** | | **' + (result.duration).toFixed(1) + ' ms** | **' + details + '** |');
}
if (result.subBenchmarks && result.subBenchmarks.length > 1) {
for (let i = 0; i < result.subBenchmarks.length; i++) {
printResult(result.subBenchmarks[i], true);
for (const item of result.subBenchmarks) {
printResult(item, true);
}
}
};

View File

@@ -35,7 +35,7 @@ const getSwitch = (name: string, def: string = null): string => {
return def;
};
gulp.task('build-backend', function() {
gulp.task('build-backend', function(): any {
return gulp.src([
'src/common/**/*.ts',
'src/backend/**/*.ts',
@@ -47,18 +47,18 @@ gulp.task('build-backend', function() {
});
const createDynamicTranslationFile = async (language: string) => {
const createDynamicTranslationFile = async (language: string): Promise<void> => {
// load
const folder = './src/frontend/' + translationFolder;
const data: string = await fsp.readFile(path.join(folder, `messages.${language}.xlf`), 'utf-8');
const translationXml: XLIFF.Root = await xml2js.parseStringPromise(data);
// clean translations, keep only .ts transaltions
const hasTsTranslation = (cg: XLIFF.ContextGroup) =>
cg.context.findIndex((c: any) => c.$['context-type'] === 'sourcefile' && c._.endsWith('.ts')) !== -1;
const hasTsTranslation = (cg: XLIFF.ContextGroup): boolean =>
cg.context.findIndex((c: any): boolean => c.$['context-type'] === 'sourcefile' && c._.endsWith('.ts')) !== -1;
const translations = translationXml.xliff.file[0].body[0]['trans-unit'];
const filtered = translations.filter(tr => tr['context-group'].findIndex(hasTsTranslation) !== -1);
filtered.forEach(tr => delete tr['context-group']);
const filtered = translations.filter((tr): boolean => tr['context-group'].findIndex(hasTsTranslation) !== -1);
filtered.forEach((tr): boolean => delete tr['context-group']);
translationXml.xliff.file[0].body[0]['trans-unit'] = filtered;
// save
@@ -68,13 +68,13 @@ const createDynamicTranslationFile = async (language: string) => {
};
const removeDynamicTranslationFile = async (language: string) => {
const removeDynamicTranslationFile = async (language: string): Promise<void> => {
const translationFile = path.join('./src/frontend/', translationFolder, `ts-only-msg.${language}.xlf`);
fsp.unlink(translationFile);
};
const setDynTransFileAtAppModule = async (language: string) => {
const setDynTransFileAtAppModule = async (language: string): Promise<void> => {
const file = './src/frontend/app/app.module.ts';
let data: string = await fsp.readFile(file, 'utf-8');
const from = 'messages.${locale}.xlf';
@@ -83,7 +83,7 @@ const setDynTransFileAtAppModule = async (language: string) => {
await fsp.writeFile(file, data);
};
const resetAppModule = async (language: string) => {
const resetAppModule = async (language: string): Promise<void> => {
const file = './src/frontend/app/app.module.ts';
let data: string = await fsp.readFile(file, 'utf-8');
const from = 'messages.${locale}.xlf';
@@ -93,8 +93,8 @@ const resetAppModule = async (language: string) => {
};
const createFrontendTask = (type: string, language: string, script: string) => {
gulp.task(type, async (cb) => {
const createFrontendTask = (type: string, language: string, script: string): void => {
gulp.task(type, async (cb): Promise<void> => {
try {
const {stdout, stderr} = await execPr(script);
console.log(stdout);
@@ -107,12 +107,12 @@ const createFrontendTask = (type: string, language: string, script: string) => {
};
const getLanguages = () => {
const getLanguages = (): any[] | string[] => {
if (!fs.existsSync('./src/frontend/' + translationFolder)) {
return [];
}
const dirCont = fs.readdirSync('./src/frontend/' + translationFolder);
const files: string[] = dirCont.filter((elm) => {
const files: string[] = dirCont.filter((elm): any => {
return elm.match(/.*\.[a-zA-Z]+\.(xlf)/ig);
});
@@ -122,19 +122,19 @@ const getLanguages = () => {
languageFilter = getSwitch('languages').split(',');
}
let languages = files.map((f: string) => {
let languages = files.map((f: string): string => {
return f.split('.')[1];
});
if (languageFilter !== null) {
languages = languages.filter((l) => {
languages = languages.filter((l): boolean => {
return languageFilter.indexOf(l) !== -1;
});
}
return languages;
};
gulp.task('build-frontend', (() => {
gulp.task('build-frontend', ((): any => {
const tasks = [];
createFrontendTask('build-frontend-release default', 'all',
'ng build --prod --no-progress');
@@ -142,7 +142,7 @@ gulp.task('build-frontend', (() => {
return gulp.series(...tasks);
})());
gulp.task('copy-static', function() {
gulp.task('copy-static', function(): any {
return gulp.src([
'src/backend/model/diagnostics/blank.jpg',
'README.md',
@@ -151,7 +151,7 @@ gulp.task('copy-static', function() {
.pipe(gulp.dest('./release'));
});
gulp.task('copy-package', function() {
gulp.task('copy-package', function(): any {
return gulp.src([
'package.json'], {base: '.'})
.pipe(jeditor((json: {
@@ -161,7 +161,7 @@ gulp.task('copy-package', function() {
optionalDependencies: { [key: string]: string },
buildTime: string,
buildCommitHash: string
}) => {
}): { devDependencies: { [p: string]: string }; scripts: { [p: string]: string }; dependencies: { [p: string]: string }; optionalDependencies: { [p: string]: string }; buildTime: string; buildCommitHash: string } => {
delete json.devDependencies;
json.scripts = {start: 'node ./src/backend/index.js'};
@@ -195,7 +195,7 @@ gulp.task('copy-package', function() {
});
gulp.task('zip-release', function() {
gulp.task('zip-release', function(): any {
return gulp.src(['release/**/*'], {base: './release'})
.pipe(zip('pigallery2.zip'))
.pipe(gulp.dest('.'));
@@ -204,7 +204,7 @@ gulp.task('zip-release', function() {
gulp.task('create-release', gulp.series('build-frontend', 'build-backend', 'copy-static', 'copy-package', 'zip-release'));
const simpleBuild = (isProd: boolean) => {
const simpleBuild = (isProd: boolean): any => {
const tasks = [];
let cmd = 'ng build ';
if (isProd) {
@@ -219,7 +219,7 @@ const simpleBuild = (isProd: boolean) => {
return gulp.series(...tasks);
};
gulp.task('extract-locale', async (cb) => {
gulp.task('extract-locale', async (cb): Promise<any> => {
console.log('creating source translation file: locale.source.xlf');
try {
{
@@ -240,7 +240,7 @@ gulp.task('extract-locale', async (cb) => {
}
});
const translate = async (list: any[], cb: (err?: any) => void) => {
const translate = async (list: any[], cb: (err?: any) => void): Promise<void> => {
try {
const localsStr = '"[\\"' + list.join('\\",\\"') + '\\"]"';
const {stdout, stderr} = await execPr('xlf-google-translate ' +
@@ -256,7 +256,7 @@ const translate = async (list: any[], cb: (err?: any) => void) => {
return cb(e);
}
};
const merge = async (list: any[], cb: (err?: any) => void) => {
const merge = async (list: any[], cb: (err?: any) => void): Promise<void> => {
try {
const localsStr = '"[\\"' + list.join('\\",\\"') + '\\"]"';
const command = 'xlf-google-translate ' +
@@ -277,10 +277,10 @@ const merge = async (list: any[], cb: (err?: any) => void) => {
}
};
gulp.task('update-translation-only', function(cb) {
gulp.task('update-translation-only', function(cb): void {
translate(getLanguages(), cb).catch(console.error);
});
gulp.task('merge-translation-only', function(cb) {
gulp.task('merge-translation-only', function(cb): void {
merge(getLanguages(), cb).catch(console.error);
});
@@ -289,7 +289,7 @@ gulp.task('update-translation', gulp.series('extract-locale', 'update-translatio
gulp.task('merge-new-translation', gulp.series('extract-locale', 'merge-translation-only'));
gulp.task('add-translation-only', (cb) => {
gulp.task('add-translation-only', (cb): any => {
const languages = getLanguages();
let lng = null;
for (let i = 0; i < process.argv.length - 1; i++) {
@@ -308,7 +308,7 @@ gulp.task('add-translation-only', (cb) => {
translate([lng], cb);
});
gulp.task('generate-man', async (cb) => {
gulp.task('generate-man', async (cb): Promise<void> => {
const defCFG = ConfigClassBuilder.attachInterface(new PrivateConfigClass());
defCFG.Server.sessionSecret = [];
let txt = '# Pigallery 2 man page\n';

370
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "pigallery2",
"version": "1.8.4",
"version": "1.8.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2813,110 +2813,6 @@
"tslib": ">=1.7.1"
}
},
"@ngx-translate/i18n-polyfill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ngx-translate/i18n-polyfill/-/i18n-polyfill-1.0.0.tgz",
"integrity": "sha512-+UKmSr6cWBJiMDex6w2FwVjEeVnlEsINDGYvTgRaFRI3/IKZrsSVcfISDcBX2wWr6m4jumfOyCcimIl2TxcaoA==",
"dev": true,
"requires": {
"glob": "7.1.2",
"tslib": "^1.9.0",
"yargs": "10.0.3"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
"dev": true,
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wrap-ansi": "^2.0.0"
},
"dependencies": {
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
}
}
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
"yargs": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz",
"integrity": "sha512-DqBpQ8NAUX4GyPP/ijDGHsJya4tYqLQrjPr95HNsr1YwL3+daCfvBwg7+gIC6IdJhR2kATh3hb61vjzMWEtjdw==",
"dev": true,
"requires": {
"cliui": "^3.2.0",
"decamelize": "^1.1.1",
"find-up": "^2.1.0",
"get-caller-file": "^1.0.1",
"os-locale": "^2.0.0",
"require-directory": "^2.1.1",
"require-main-filename": "^1.0.1",
"set-blocking": "^2.0.0",
"string-width": "^2.0.0",
"which-module": "^2.0.0",
"y18n": "^3.2.1",
"yargs-parser": "^8.0.0"
}
}
}
},
"@nodelib/fs.scandir": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
@@ -3274,6 +3170,11 @@
}
}
},
"@polka/url": {
"version": "1.0.0-next.12",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.12.tgz",
"integrity": "sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ=="
},
"@schematics/angular": {
"version": "11.2.8",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-11.2.8.tgz",
@@ -4025,6 +3926,11 @@
"integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==",
"dev": true
},
"acorn-walk": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.2.tgz",
"integrity": "sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A=="
},
"adjust-sourcemap-loader": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz",
@@ -5388,12 +5294,6 @@
"integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
"dev": true
},
"camelcase": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"dev": true
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
@@ -7957,6 +7857,11 @@
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
},
"duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="
},
"duplexer2": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
@@ -8579,34 +8484,6 @@
"safe-buffer": "^5.1.1"
}
},
"execa": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
"integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
"dev": true,
"requires": {
"cross-spawn": "^5.0.1",
"get-stream": "^3.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
},
"dependencies": {
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
"dev": true,
"requires": {
"lru-cache": "^4.0.1",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
}
}
},
"exif-parser": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
@@ -9078,15 +8955,6 @@
"pkg-dir": "^4.1.0"
}
},
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
"locate-path": "^2.0.0"
}
},
"findup-sync": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
@@ -9393,12 +9261,6 @@
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
"dev": true
},
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
},
"get-value": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -10609,6 +10471,14 @@
"glogg": "^1.0.0"
}
},
"gzip-size": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
"integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
"requires": {
"duplexer": "^0.1.2"
}
},
"hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
@@ -12994,16 +12864,6 @@
"resolved": "https://registry.npmjs.org/locale/-/locale-0.1.0.tgz",
"integrity": "sha1-O1v3BhT9q0isPj+8ZIFHy2VEO94="
},
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
"dev": true,
"requires": {
"p-locate": "^2.0.0",
"path-exists": "^3.0.0"
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
@@ -13272,16 +13132,6 @@
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
"dev": true
},
"lru-cache": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
"integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==",
"dev": true,
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
@@ -13500,15 +13350,6 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"mem": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -13725,12 +13566,6 @@
"mime-db": "~1.37.0"
}
},
"mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"dev": true
},
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
@@ -15781,6 +15616,11 @@
"integrity": "sha1-nc/Ix808Yc20ojaxo0eJTJetwMY=",
"dev": true
},
"opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
},
"opn": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
@@ -15940,17 +15780,6 @@
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
},
"os-locale": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
"integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
"dev": true,
"requires": {
"execa": "^0.7.0",
"lcid": "^1.0.0",
"mem": "^1.1.0"
}
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -15977,24 +15806,6 @@
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
},
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
"dev": true,
"requires": {
"p-try": "^1.0.0"
}
},
"p-locate": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
"dev": true,
"requires": {
"p-limit": "^1.1.0"
}
},
"p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
@@ -16013,12 +15824,6 @@
"retry": "^0.12.0"
}
},
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
"dev": true
},
"package-hash": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz",
@@ -20525,6 +20330,23 @@
"is-arrayish": "^0.3.1"
}
},
"sirv": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz",
"integrity": "sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==",
"requires": {
"@polka/url": "^1.0.0-next.9",
"mime": "^2.3.1",
"totalist": "^1.0.0"
},
"dependencies": {
"mime": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
"integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg=="
}
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -22118,6 +21940,11 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"totalist": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz",
"integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g=="
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
@@ -23303,6 +23130,11 @@
"defaults": "^1.0.3"
}
},
"web-animations-js": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.2.tgz",
"integrity": "sha512-TOMFWtQdxzjWp8qx4DAraTWTsdhxVSiWa6NkPFSaPtZ1diKUxTn4yTix73A1euG1WbSOMMPcY51cnjTIHrGtDA=="
},
"webdriver-js-extender": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz",
@@ -23667,6 +23499,87 @@
}
}
},
"webpack-bundle-analyzer": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz",
"integrity": "sha512-j5m7WgytCkiVBoOGavzNokBOqxe6Mma13X1asfVYtKWM3wxBiRRu1u1iG0Iol5+qp9WgyhkMmBAcvjEfJ2bdDw==",
"requires": {
"acorn": "^8.0.4",
"acorn-walk": "^8.0.0",
"chalk": "^4.1.0",
"commander": "^6.2.0",
"gzip-size": "^6.0.0",
"lodash": "^4.17.20",
"opener": "^1.5.2",
"sirv": "^1.0.7",
"ws": "^7.3.1"
},
"dependencies": {
"acorn": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.1.tgz",
"integrity": "sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
},
"ws": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw=="
}
}
},
"webpack-dev-middleware": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz",
@@ -24560,15 +24473,6 @@
}
}
},
"yargs-parser": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz",
"integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==",
"dev": true,
"requires": {
"camelcase": "^4.1.0"
}
},
"yargs-unparser": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",

View File

@@ -12,12 +12,14 @@
"build-en": "tsc && gulp build-prod --languages=en",
"create-release": "gulp create-release",
"build-backend": "tsc",
"pretest": "tsc",
"pretest": "npm run lint && tsc",
"test": "ng test && nyc mocha --recursive test",
"lint": "tslint --project tsconfig.json src/**/**.ts test/**/**.ts",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"start": "node ./src/backend/index",
"run-dev": "ng build --configuration=dev",
"build-stats": "ng build --aot --prod --stats-json --output-path=./dist --i18n-locale en --i18n-file src/frontend/translate/messages.en.xlf --i18n-missing-translation warning",
"build-stats": "ng build --stats-json",
"analyze": "webpack-bundle-analyzer dist/en/stats.json",
"merge-new-translation": "gulp merge-new-translation",
"merge-translation-only": "gulp merge-translation-only",
"add-translation": "gulp add-translation",
@@ -52,6 +54,8 @@
"ts-node-iptc": "1.0.11",
"typeconfig": "2.0.14",
"typeorm": "0.2.29",
"web-animations-js": "^2.3.2",
"webpack-bundle-analyzer": "^4.4.1",
"winston": "2.4.4"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
import * as winston from 'winston';
import {Config} from '../common/config/private/Config';
import {ServerConfig} from '../common/config/private/PrivateConfig';
import {LogLevel} from '../common/config/private/PrivateConfig';
const forcedDebug = process.env.NODE_ENV === 'debug';
@@ -9,23 +9,23 @@ if (forcedDebug === true) {
}
export const winstonSettings = {
transports: [
new winston.transports.Console(<any>{
level: forcedDebug === true ? ServerConfig.LogLevel[ServerConfig.LogLevel.silly] : ServerConfig.LogLevel[Config.Server.Log.level],
new winston.transports.Console({
level: forcedDebug === true ? LogLevel[LogLevel.silly] : LogLevel[Config.Server.Log.level],
handleExceptions: true,
json: false,
colorize: true,
timestamp: function () {
timestamp(): string {
return (new Date()).toLocaleString();
},
label: 'innerLabel',
formatter: (options: any) => {
// Return string will be passed to logger.
return options.timestamp() + '[' + (<any>winston)['config']['colorize'](options.level, options.level.toUpperCase()) + '] ' +
return options.timestamp() + '[' + (winston as any).config.colorize(options.level, options.level.toUpperCase()) + '] ' +
(undefined !== options.message ? options.message : '') +
(options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : '');
},
debugStdout: true
})
} as any)
],
exitOnError: false
};
@@ -39,4 +39,4 @@ export const Logger: {
verbose: logFN,
debug: logFN,
silly: logFN
} = new (<any>winston).Logger(winstonSettings);
} = new (winston as any).Logger(winstonSettings);

View File

@@ -15,7 +15,7 @@ class ProjectPathClass {
this.reset();
}
normalizeRelative(pathStr: string) {
normalizeRelative(pathStr: string): any {
return path.join(pathStr, path.sep);
}
@@ -27,7 +27,7 @@ class ProjectPathClass {
return path.relative(this.ImageFolder, pathStr);
}
reset() {
reset(): void {
this.Root = path.join(__dirname, '/../../');
this.FrontendFolder = path.join(this.Root, 'dist');
this.ImageFolder = this.getAbsolutePath(Config.Server.Media.folder);

View File

@@ -2,14 +2,14 @@ import * as path from 'path';
import {promises as fsp} from 'fs';
import {NextFunction, Request, Response} from 'express';
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
import {DirectoryDTO} from '../../common/entities/DirectoryDTO';
import {DirectoryDTO, DirectoryDTOUtils} from '../../common/entities/DirectoryDTO';
import {ObjectManagers} from '../model/ObjectManagers';
import {ContentWrapper} from '../../common/entities/ConentWrapper';
import {PhotoDTO} from '../../common/entities/PhotoDTO';
import {ProjectPath} from '../ProjectPath';
import {Config} from '../../common/config/private/Config';
import {UserDTO} from '../../common/entities/UserDTO';
import {MediaDTO} from '../../common/entities/MediaDTO';
import {UserDTO, UserDTOUtils} from '../../common/entities/UserDTO';
import {MediaDTO, MediaDTOUtils} from '../../common/entities/MediaDTO';
import {VideoDTO} from '../../common/entities/VideoDTO';
import {Utils} from '../../common/Utils';
import {QueryParams} from '../../common/QueryParams';
@@ -21,7 +21,7 @@ import {LocationLookupException} from '../exceptions/LocationLookupException';
export class GalleryMWs {
public static async listDirectory(req: Request, res: Response, next: NextFunction) {
public static async listDirectory(req: Request, res: Response, next: NextFunction): Promise<any> {
const directoryName = req.params.directory || '/';
const absoluteDirectoryName = path.join(ProjectPath.ImageFolder, directoryName);
try {
@@ -35,8 +35,8 @@ export class GalleryMWs {
try {
const directory = await ObjectManagers.getInstance()
.GalleryManager.listDirectory(directoryName,
parseInt(<string>req.query[QueryParams.gallery.knownLastModified], 10),
parseInt(<string>req.query[QueryParams.gallery.knownLastScanned], 10));
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);
@@ -45,8 +45,8 @@ export class GalleryMWs {
if (req.session.user.permissions &&
req.session.user.permissions.length > 0 &&
req.session.user.permissions[0] !== '/*') {
(<DirectoryDTO>directory).directories = (<DirectoryDTO>directory).directories.filter(d =>
UserDTO.isDirectoryAvailable(d, req.session.user.permissions));
(directory as DirectoryDTO).directories = (directory as DirectoryDTO).directories.filter((d): boolean =>
UserDTOUtils.isDirectoryAvailable(d, req.session.user.permissions));
}
req.resultPipe = new ContentWrapper(directory, null);
return next();
@@ -57,7 +57,7 @@ export class GalleryMWs {
}
public static cleanUpGalleryResults(req: Request, res: Response, next: NextFunction) {
public static cleanUpGalleryResults(req: Request, res: Response, next: NextFunction): any {
if (!req.resultPipe) {
return next();
}
@@ -67,26 +67,26 @@ export class GalleryMWs {
return next();
}
const cleanUpMedia = (media: MediaDTO[]) => {
media.forEach(m => {
if (MediaDTO.isPhoto(m)) {
delete (<VideoDTO>m).metadata.bitRate;
delete (<VideoDTO>m).metadata.duration;
} else if (MediaDTO.isVideo(m)) {
delete (<PhotoDTO>m).metadata.rating;
delete (<PhotoDTO>m).metadata.caption;
delete (<PhotoDTO>m).metadata.cameraData;
delete (<PhotoDTO>m).metadata.orientation;
delete (<PhotoDTO>m).metadata.orientation;
delete (<PhotoDTO>m).metadata.keywords;
delete (<PhotoDTO>m).metadata.positionData;
const cleanUpMedia = (media: MediaDTO[]): void => {
media.forEach((m): void => {
if (MediaDTOUtils.isPhoto(m)) {
delete (m as VideoDTO).metadata.bitRate;
delete (m as VideoDTO).metadata.duration;
} else if (MediaDTOUtils.isVideo(m)) {
delete (m as PhotoDTO).metadata.rating;
delete (m as PhotoDTO).metadata.caption;
delete (m as PhotoDTO).metadata.cameraData;
delete (m as PhotoDTO).metadata.orientation;
delete (m as PhotoDTO).metadata.orientation;
delete (m as PhotoDTO).metadata.keywords;
delete (m as PhotoDTO).metadata.positionData;
}
Utils.removeNullOrEmptyObj(m);
});
};
if (cw.directory) {
DirectoryDTO.packDirectory(cw.directory);
DirectoryDTOUtils.packDirectory(cw.directory);
// TODO: remove when typeorm inheritance is fixed (and handles proper inheritance)
cleanUpMedia(cw.directory.media);
}
@@ -97,16 +97,16 @@ export class GalleryMWs {
if (Config.Client.Media.Video.enabled === false) {
if (cw.directory) {
const removeVideos = (dir: DirectoryDTO) => {
dir.media = dir.media.filter(m => !MediaDTO.isVideo(m));
const removeVideos = (dir: DirectoryDTO): void => {
dir.media = dir.media.filter((m): boolean => !MediaDTOUtils.isVideo(m));
if (dir.directories) {
dir.directories.forEach(d => removeVideos(d));
dir.directories.forEach((d): void => removeVideos(d));
}
};
removeVideos(cw.directory);
}
if (cw.searchResult) {
cw.searchResult.media = cw.searchResult.media.filter(m => !MediaDTO.isVideo(m));
cw.searchResult.media = cw.searchResult.media.filter((m): boolean => !MediaDTOUtils.isVideo(m));
}
}
@@ -114,7 +114,7 @@ export class GalleryMWs {
}
public static async loadFile(req: Request, res: Response, next: NextFunction) {
public static async loadFile(req: Request, res: Response, next: NextFunction): Promise<any> {
if (!(req.params.mediaPath)) {
return next();
}
@@ -134,7 +134,7 @@ export class GalleryMWs {
return next();
}
public static async loadBestFitVideo(req: Request, res: Response, next: NextFunction) {
public static async loadBestFitVideo(req: Request, res: Response, next: NextFunction): Promise<any> {
if (!(req.resultPipe)) {
return next();
}
@@ -154,17 +154,17 @@ export class GalleryMWs {
}
public static async search(req: Request, res: Response, next: NextFunction) {
public static async search(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.Search.enabled === false || !(req.params.searchQueryDTO)) {
return next();
}
const query: SearchQueryDTO = JSON.parse(<any>req.params.searchQueryDTO);
const query: SearchQueryDTO = JSON.parse(req.params.searchQueryDTO as any);
try {
const result = await ObjectManagers.getInstance().SearchManager.search(query);
result.directories.forEach(dir => dir.media = dir.media || []);
result.directories.forEach((dir): MediaDTO[] => dir.media = dir.media || []);
req.resultPipe = new ContentWrapper(null, result);
return next();
} catch (err) {
@@ -176,7 +176,7 @@ export class GalleryMWs {
}
public static async autocomplete(req: Request, res: Response, next: NextFunction) {
public static async autocomplete(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.Search.AutoComplete.enabled === false) {
return next();
}
@@ -186,7 +186,7 @@ export class GalleryMWs {
let type: SearchQueryTypes = SearchQueryTypes.any_text;
if (req.query[QueryParams.gallery.search.type]) {
type = parseInt(<string>req.query[QueryParams.gallery.search.type], 10);
type = parseInt(req.query[QueryParams.gallery.search.type] as string, 10);
}
try {
req.resultPipe = await ObjectManagers.getInstance().SearchManager.autocomplete(req.params.text, type);
@@ -198,13 +198,13 @@ export class GalleryMWs {
}
public static async getRandomImage(req: Request, res: Response, next: NextFunction) {
public static async getRandomImage(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.RandomPhoto.enabled === false || !(req.params.searchQueryDTO)) {
return next();
}
try {
const query: SearchQueryDTO = JSON.parse(<any>req.params.searchQueryDTO);
const query: SearchQueryDTO = JSON.parse(req.params.searchQueryDTO as any);
const photo = await ObjectManagers.getInstance()
.SearchManager.getRandomPhoto(query);

View File

@@ -6,7 +6,7 @@ import {NotificationManager} from '../model/NotifocationManager';
export class NotificationMWs {
public static list(req: Request, res: Response, next: NextFunction) {
public static list(req: Request, res: Response, next: NextFunction): any {
if (req.session.user.role >= UserRoles.Admin) {
req.resultPipe = NotificationManager.notifications;

View File

@@ -8,7 +8,7 @@ import {Utils} from '../../common/Utils';
export class PersonMWs {
public static async updatePerson(req: Request, res: Response, next: NextFunction) {
public static async updatePerson(req: Request, res: Response, next: NextFunction): Promise<any> {
if (!req.params.name) {
return next();
}
@@ -24,7 +24,7 @@ export class PersonMWs {
}
}
public static async getPerson(req: Request, res: Response, next: NextFunction) {
public static async getPerson(req: Request, res: Response, next: NextFunction): Promise<any> {
if (!req.params.name) {
return next();
}
@@ -40,7 +40,7 @@ export class PersonMWs {
}
public static async listPersons(req: Request, res: Response, next: NextFunction) {
public static async listPersons(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
req.resultPipe = await ObjectManagers.getInstance()
.PersonManager.getAll();
@@ -53,14 +53,14 @@ export class PersonMWs {
}
public static async cleanUpPersonResults(req: Request, res: Response, next: NextFunction) {
public static async cleanUpPersonResults(req: Request, res: Response, next: NextFunction): Promise<any> {
if (!req.resultPipe) {
return next();
}
try {
const persons = Utils.clone(req.resultPipe as PersonWithSampleRegion[]);
for (let i = 0; i < persons.length; i++) {
delete persons[i].sampleRegion;
for (const item of persons) {
delete item.sampleRegion;
}
req.resultPipe = persons;
return next();

View File

@@ -11,7 +11,7 @@ import {LoggerRouter} from '../routes/LoggerRouter';
export class RenderingMWs {
public static renderResult(req: Request, res: Response, next: NextFunction) {
public static renderResult(req: Request, res: Response, next: NextFunction): any {
if (typeof req.resultPipe === 'undefined') {
return next();
}
@@ -20,19 +20,19 @@ export class RenderingMWs {
}
public static renderSessionUser(req: Request, res: Response, next: NextFunction) {
public static renderSessionUser(req: Request, res: Response, next: NextFunction): any {
if (!(req.session.user)) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'User not exists'));
}
const user = <UserDTO>{
const user = {
id: req.session.user.id,
name: req.session.user.name,
csrfToken: req.session.user.csrfToken || req.csrfToken(),
role: req.session.user.role,
usedSharingKey: req.session.user.usedSharingKey,
permissions: req.session.user.permissions
};
} as UserDTO;
if (!user.csrfToken && req.csrfToken) {
user.csrfToken = req.csrfToken();
@@ -41,7 +41,7 @@ export class RenderingMWs {
RenderingMWs.renderMessage(res, user);
}
public static renderSharing(req: Request, res: Response, next: NextFunction) {
public static renderSharing(req: Request, res: Response, next: NextFunction): any {
if (!req.resultPipe) {
return next();
}
@@ -51,39 +51,39 @@ export class RenderingMWs {
}
public static renderSharingList(req: Request, res: Response, next: NextFunction) {
public static renderSharingList(req: Request, res: Response, next: NextFunction): any {
if (!req.resultPipe) {
return next();
}
const shares: SharingDTO[] = Utils.clone(req.resultPipe);
shares.forEach(s => {
shares.forEach((s): void => {
delete s.password;
delete s.creator.password;
});
return RenderingMWs.renderMessage(res, shares);
}
public static renderFile(req: Request, res: Response, next: NextFunction) {
public static renderFile(req: Request, res: Response, next: NextFunction): any {
if (!req.resultPipe) {
return next();
}
return res.sendFile(req.resultPipe, {maxAge: 31536000, dotfiles: 'allow'});
}
public static renderOK(req: Request, res: Response, next: NextFunction) {
public static renderOK(req: Request, res: Response, next: NextFunction): void {
const message = new Message<string>(null, 'ok');
res.json(message);
}
public static async renderConfig(req: Request, res: Response, next: NextFunction) {
public static async renderConfig(req: Request, res: Response, next: NextFunction): Promise<void> {
const originalConf = await Config.original();
originalConf.Server.sessionSecret = null;
const message = new Message<PrivateConfigClass>(null, <any>originalConf.toJSON({
const message = new Message<PrivateConfigClass>(null, originalConf.toJSON({
attachState: true,
attachVolatile: true
}));
}) as any);
res.json(message);
}
@@ -110,7 +110,7 @@ export class RenderingMWs {
}
protected static renderMessage<T>(res: Response, content: T) {
protected static renderMessage<T>(res: Response, content: T): void {
const message = new Message<T>(null, content);
res.json(message);
}

View File

@@ -11,14 +11,14 @@ import {UserRoles} from '../../common/entities/UserDTO';
export class SharingMWs {
public static async getSharing(req: Request, res: Response, next: NextFunction) {
public static async getSharing(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.Sharing.enabled === false) {
return next();
}
const sharingKey = req.params[QueryParams.gallery.sharingKey_params];
try {
req.resultPipe = await ObjectManagers.getInstance().SharingManager.findOne({sharingKey: sharingKey});
req.resultPipe = await ObjectManagers.getInstance().SharingManager.findOne({sharingKey});
return next();
} catch (err) {
@@ -27,7 +27,7 @@ export class SharingMWs {
}
public static async createSharing(req: Request, res: Response, next: NextFunction) {
public static async createSharing(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.Sharing.enabled === false) {
return next();
}
@@ -40,7 +40,7 @@ export class SharingMWs {
// create one not yet used
while (true) {
try {
await ObjectManagers.getInstance().SharingManager.findOne({sharingKey: sharingKey});
await ObjectManagers.getInstance().SharingManager.findOne({sharingKey});
sharingKey = this.generateKey();
} catch (err) {
break;
@@ -51,7 +51,7 @@ export class SharingMWs {
const directoryName = path.normalize(req.params.directory || '/');
const sharing: SharingDTO = {
id: null,
sharingKey: sharingKey,
sharingKey,
path: directoryName,
password: createSharing.password,
creator: req.session.user,
@@ -71,7 +71,7 @@ export class SharingMWs {
}
}
public static async updateSharing(req: Request, res: Response, next: NextFunction) {
public static async updateSharing(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.Sharing.enabled === false) {
return next();
}
@@ -102,7 +102,7 @@ export class SharingMWs {
}
public static async deleteSharing(req: Request, res: Response, next: NextFunction) {
public static async deleteSharing(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.Sharing.enabled === false) {
return next();
}
@@ -120,7 +120,7 @@ export class SharingMWs {
}
public static async listSharing(req: Request, res: Response, next: NextFunction) {
public static async listSharing(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.Sharing.enabled === false) {
return next();
}
@@ -133,7 +133,7 @@ export class SharingMWs {
}
private static generateKey(): string {
function s4() {
function s4(): string {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);

View File

@@ -9,11 +9,8 @@ export class VersionMWs {
/**
* This version data is mainly used on the client side to invalidate the cache
* @param req
* @param res
* @param next
*/
public static async injectGalleryVersion(req: Request, res: Response, next: NextFunction) {
public static async injectGalleryVersion(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
res.header(CustomHeaders.dataVersion, await ObjectManagers.getInstance().VersionManager.getDataVersion());
next();

View File

@@ -3,29 +3,29 @@ import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
import {ObjectManagers} from '../../model/ObjectManagers';
import {Config} from '../../../common/config/private/Config';
import {ISQLGalleryManager} from '../../model/database/sql/IGalleryManager';
import {ServerConfig} from '../../../common/config/private/PrivateConfig';
import {DatabaseType, ServerConfig} from '../../../common/config/private/PrivateConfig';
import {ISQLPersonManager} from '../../model/database/sql/IPersonManager';
import {StatisticDTO} from '../../../common/entities/settings/StatisticDTO';
export class AdminMWs {
public static async loadStatistic(req: Request, res: Response, next: NextFunction) {
if (Config.Server.Database.type === ServerConfig.DatabaseType.memory) {
public static async loadStatistic(req: Request, res: Response, next: NextFunction): Promise<void> {
if (Config.Server.Database.type === DatabaseType.memory) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Statistic is only available for indexed content'));
}
const galleryManager = <ISQLGalleryManager>ObjectManagers.getInstance().GalleryManager;
const personManager = <ISQLPersonManager>ObjectManagers.getInstance().PersonManager;
const galleryManager = ObjectManagers.getInstance().GalleryManager as ISQLGalleryManager;
const personManager = ObjectManagers.getInstance().PersonManager as ISQLPersonManager;
try {
req.resultPipe = <StatisticDTO>{
req.resultPipe = {
directories: await galleryManager.countDirectories(),
photos: await galleryManager.countPhotos(),
videos: await galleryManager.countVideos(),
diskUsage: await galleryManager.countMediaSize(),
persons: await personManager.countFaces(),
};
} as StatisticDTO;
return next();
} catch (err) {
if (err instanceof Error) {
@@ -35,13 +35,13 @@ export class AdminMWs {
}
}
public static async getDuplicates(req: Request, res: Response, next: NextFunction) {
if (Config.Server.Database.type === ServerConfig.DatabaseType.memory) {
public static async getDuplicates(req: Request, res: Response, next: NextFunction): Promise<void> {
if (Config.Server.Database.type === DatabaseType.memory) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Statistic is only available for indexed content'));
}
const galleryManager = <ISQLGalleryManager>ObjectManagers.getInstance().GalleryManager;
const galleryManager = ObjectManagers.getInstance().GalleryManager as ISQLGalleryManager;
try {
req.resultPipe = await galleryManager.getPossibleDuplicates();
return next();
@@ -54,7 +54,7 @@ export class AdminMWs {
}
public static async startJob(req: Request, res: Response, next: NextFunction) {
public static async startJob(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const id = req.params.id;
const JobConfig: any = req.body.config;
@@ -71,7 +71,7 @@ export class AdminMWs {
}
}
public static stopJob(req: Request, res: Response, next: NextFunction) {
public static stopJob(req: Request, res: Response, next: NextFunction): void {
try {
const id = req.params.id;
ObjectManagers.getInstance().JobManager.stop(id);
@@ -85,7 +85,7 @@ export class AdminMWs {
}
}
public static getAvailableJobs(req: Request, res: Response, next: NextFunction) {
public static getAvailableJobs(req: Request, res: Response, next: NextFunction): void {
try {
req.resultPipe = ObjectManagers.getInstance().JobManager.getAvailableJobs();
return next();
@@ -97,7 +97,7 @@ export class AdminMWs {
}
}
public static getJobProgresses(req: Request, res: Response, next: NextFunction) {
public static getJobProgresses(req: Request, res: Response, next: NextFunction): void {
try {
req.resultPipe = ObjectManagers.getInstance().JobManager.getProgresses();
return next();

View File

@@ -8,31 +8,49 @@ import {ConfigDiagnostics} from '../../model/diagnostics/ConfigDiagnostics';
import {BasicConfigDTO} from '../../../common/entities/settings/BasicConfigDTO';
import {OtherConfigDTO} from '../../../common/entities/settings/OtherConfigDTO';
import {ProjectPath} from '../../ProjectPath';
import {ServerConfig} from '../../../common/config/private/PrivateConfig';
import {ClientConfig} from '../../../common/config/public/ClientConfig';
import {
DatabaseType,
ServerDataBaseConfig,
ServerIndexingConfig,
ServerJobConfig,
ServerPhotoConfig,
ServerThumbnailConfig,
ServerVideoConfig
} from '../../../common/config/private/PrivateConfig';
import {
ClientFacesConfig,
ClientMapConfig,
ClientMetaFileConfig,
ClientPhotoConfig,
ClientRandomPhotoConfig,
ClientSearchConfig,
ClientSharingConfig,
ClientThumbnailConfig,
ClientVideoConfig
} from '../../../common/config/public/ClientConfig';
const LOG_TAG = '[SettingsMWs]';
export class SettingsMWs {
public static async updateDatabaseSettings(req: Request, res: Response, next: NextFunction) {
public static async updateDatabaseSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
const databaseSettings = <ServerConfig.DataBaseConfig>req.body.settings;
const databaseSettings = req.body.settings as ServerDataBaseConfig;
try {
if (databaseSettings.type !== ServerConfig.DatabaseType.memory) {
if (databaseSettings.type !== DatabaseType.memory) {
await ConfigDiagnostics.testDatabase(databaseSettings);
}
Config.Server.Database = databaseSettings;
// only updating explicitly set config (not saving config set by the diagnostics)
const original = await Config.original();
original.Server.Database = databaseSettings;
if (databaseSettings.type === ServerConfig.DatabaseType.memory) {
if (databaseSettings.type === DatabaseType.memory) {
original.Client.Sharing.enabled = false;
original.Client.Search.enabled = false;
}
@@ -42,7 +60,7 @@ export class SettingsMWs {
Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t'));
await ObjectManagers.reset();
if (Config.Server.Database.type !== ServerConfig.DatabaseType.memory) {
if (Config.Server.Database.type !== DatabaseType.memory) {
await ObjectManagers.InitSQLManagers();
} else {
await ObjectManagers.InitMemoryManagers();
@@ -57,18 +75,18 @@ export class SettingsMWs {
}
}
public static async updateMapSettings(req: Request, res: Response, next: NextFunction) {
public static async updateMapSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
try {
await ConfigDiagnostics.testMapConfig(<ClientConfig.MapConfig>req.body.settings);
await ConfigDiagnostics.testMapConfig(req.body.settings as ClientMapConfig);
Config.Client.Map = <ClientConfig.MapConfig>req.body.settings;
Config.Client.Map = (req.body.settings as ClientMapConfig);
// only updating explicitly set config (not saving config set by the diagnostics)
const original = await Config.original();
original.Client.Map = <ClientConfig.MapConfig>req.body.settings;
original.Client.Map = (req.body.settings as ClientMapConfig);
original.save();
await ConfigDiagnostics.runDiagnostics();
Logger.info(LOG_TAG, 'new config:');
@@ -82,15 +100,15 @@ export class SettingsMWs {
}
}
public static async updateVideoSettings(req: Request, res: Response, next: NextFunction) {
public static async updateVideoSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
try {
const settings: {
server: ServerConfig.VideoConfig,
client: ClientConfig.VideoConfig
server: ServerVideoConfig,
client: ClientVideoConfig
} = req.body.settings;
@@ -115,19 +133,19 @@ export class SettingsMWs {
}
}
public static async updateMetaFileSettings(req: Request, res: Response, next: NextFunction) {
public static async updateMetaFileSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
try {
const original = await Config.original();
await ConfigDiagnostics.testMetaFileConfig(<ClientConfig.MetaFileConfig>req.body.settings, original);
await ConfigDiagnostics.testMetaFileConfig(req.body.settings as ClientMetaFileConfig, original);
Config.Client.MetaFile = <ClientConfig.MetaFileConfig>req.body.settings;
Config.Client.MetaFile = (req.body.settings as ClientMetaFileConfig);
// only updating explicitly set config (not saving config set by the diagnostics)
original.Client.MetaFile = <ClientConfig.MetaFileConfig>req.body.settings;
original.Client.MetaFile = (req.body.settings as ClientMetaFileConfig);
original.save();
await ConfigDiagnostics.runDiagnostics();
Logger.info(LOG_TAG, 'new config:');
@@ -141,7 +159,7 @@ export class SettingsMWs {
}
}
public static async updateShareSettings(req: Request, res: Response, next: NextFunction) {
public static async updateShareSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
@@ -149,10 +167,10 @@ export class SettingsMWs {
try {
// only updating explicitly set config (not saving config set by the diagnostics)
const original = await Config.original();
await ConfigDiagnostics.testSharingConfig(<ClientConfig.SharingConfig>req.body.settings, original);
await ConfigDiagnostics.testSharingConfig(req.body.settings as ClientSharingConfig, original);
Config.Client.Sharing = <ClientConfig.SharingConfig>req.body.settings;
original.Client.Sharing = <ClientConfig.SharingConfig>req.body.settings;
Config.Client.Sharing = (req.body.settings as ClientSharingConfig);
original.Client.Sharing = (req.body.settings as ClientSharingConfig);
original.save();
await ConfigDiagnostics.runDiagnostics();
Logger.info(LOG_TAG, 'new config:');
@@ -166,7 +184,7 @@ export class SettingsMWs {
}
}
public static async updateRandomPhotoSettings(req: Request, res: Response, next: NextFunction) {
public static async updateRandomPhotoSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
@@ -174,10 +192,10 @@ export class SettingsMWs {
try {
// only updating explicitly set config (not saving config set by the diagnostics)
const original = await Config.original();
await ConfigDiagnostics.testRandomPhotoConfig(<ClientConfig.RandomPhotoConfig>req.body.settings, original);
await ConfigDiagnostics.testRandomPhotoConfig(req.body.settings as ClientRandomPhotoConfig, original);
Config.Client.RandomPhoto = <ClientConfig.RandomPhotoConfig>req.body.settings;
original.Client.RandomPhoto = <ClientConfig.RandomPhotoConfig>req.body.settings;
Config.Client.RandomPhoto = (req.body.settings as ClientRandomPhotoConfig);
original.Client.RandomPhoto = (req.body.settings as ClientRandomPhotoConfig);
original.save();
await ConfigDiagnostics.runDiagnostics();
Logger.info(LOG_TAG, 'new config:');
@@ -191,7 +209,7 @@ export class SettingsMWs {
}
}
public static async updateSearchSettings(req: Request, res: Response, next: NextFunction) {
public static async updateSearchSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
@@ -199,10 +217,10 @@ export class SettingsMWs {
try {
// only updating explicitly set config (not saving config set by the diagnostics)
const original = await Config.original();
await ConfigDiagnostics.testSearchConfig(<ClientConfig.SearchConfig>req.body.settings, original);
await ConfigDiagnostics.testSearchConfig(req.body.settings as ClientSearchConfig, original);
Config.Client.Search = <ClientConfig.SearchConfig>req.body.settings;
original.Client.Search = <ClientConfig.SearchConfig>req.body.settings;
Config.Client.Search = (req.body.settings as ClientSearchConfig);
original.Client.Search = (req.body.settings as ClientSearchConfig);
original.save();
await ConfigDiagnostics.runDiagnostics();
Logger.info(LOG_TAG, 'new config:');
@@ -216,7 +234,7 @@ export class SettingsMWs {
}
}
public static async updateFacesSettings(req: Request, res: Response, next: NextFunction) {
public static async updateFacesSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
@@ -224,10 +242,10 @@ export class SettingsMWs {
try {
// only updating explicitly set config (not saving config set by the diagnostics)
const original = await Config.original();
await ConfigDiagnostics.testFacesConfig(<ClientConfig.FacesConfig>req.body.settings, original);
await ConfigDiagnostics.testFacesConfig(req.body.settings as ClientFacesConfig, original);
Config.Client.Faces = <ClientConfig.FacesConfig>req.body.settings;
original.Client.Faces = <ClientConfig.FacesConfig>req.body.settings;
Config.Client.Faces = (req.body.settings as ClientFacesConfig);
original.Client.Faces = (req.body.settings as ClientFacesConfig);
original.save();
await ConfigDiagnostics.runDiagnostics();
Logger.info(LOG_TAG, 'new config:');
@@ -241,16 +259,16 @@ export class SettingsMWs {
}
}
public static async updateAuthenticationSettings(req: Request, res: Response, next: NextFunction) {
public static async updateAuthenticationSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
try {
Config.Client.authenticationRequired = <boolean>req.body.settings;
Config.Client.authenticationRequired = (req.body.settings as boolean);
// only updating explicitly set config (not saving config set by the diagnostics)
const original = await Config.original();
original.Client.authenticationRequired = <boolean>req.body.settings;
original.Client.authenticationRequired = (req.body.settings as boolean);
if (original.Client.authenticationRequired === false) {
original.Client.Sharing.enabled = false;
}
@@ -267,15 +285,15 @@ export class SettingsMWs {
}
}
public static async updateThumbnailSettings(req: Request, res: Response, next: NextFunction) {
public static async updateThumbnailSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
try {
const settings: {
server: ServerConfig.ThumbnailConfig,
client: ClientConfig.ThumbnailConfig
server: ServerThumbnailConfig,
client: ClientThumbnailConfig
} = req.body.settings;
await ConfigDiagnostics.testServerThumbnailConfig(settings.server);
@@ -300,15 +318,15 @@ export class SettingsMWs {
}
}
public static async updatePhotoSettings(req: Request, res: Response, next: NextFunction) {
public static async updatePhotoSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
try {
const settings: {
server: ServerConfig.PhotoConfig,
client: ClientConfig.PhotoConfig
server: ServerPhotoConfig,
client: ClientPhotoConfig
} = req.body.settings;
await ConfigDiagnostics.testServerPhotoConfig(settings.server);
@@ -333,7 +351,7 @@ export class SettingsMWs {
}
}
public static async updateBasicSettings(req: Request, res: Response, next: NextFunction) {
public static async updateBasicSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
@@ -369,7 +387,7 @@ export class SettingsMWs {
}
}
public static async updateOtherSettings(req: Request, res: Response, next: NextFunction) {
public static async updateOtherSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
@@ -396,13 +414,13 @@ export class SettingsMWs {
}
}
public static async updateIndexingSettings(req: Request, res: Response, next: NextFunction) {
public static async updateIndexingSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
try {
const settings: ServerConfig.IndexingConfig = req.body.settings;
const settings: ServerIndexingConfig = req.body.settings;
Config.Server.Indexing = settings;
// only updating explicitly set config (not saving config set by the diagnostics)
@@ -421,7 +439,7 @@ export class SettingsMWs {
}
}
public static async updateJobSettings(req: Request, res: Response, next: NextFunction) {
public static async updateJobSettings(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}
@@ -429,7 +447,7 @@ export class SettingsMWs {
try {
// only updating explicitly set config (not saving config set by the diagnostics)
const settings: ServerConfig.JobConfig = req.body.settings;
const settings: ServerJobConfig = req.body.settings;
const original = await Config.original();
await ConfigDiagnostics.testTasksConfig(settings, original);

View File

@@ -5,7 +5,7 @@ import {Config} from '../../../common/config/private/Config';
export class PhotoConverterMWs {
public static async convertPhoto(req: Request, res: Response, next: NextFunction) {
public static async convertPhoto(req: Request, res: Response, next: NextFunction): Promise<any> {
if (!req.resultPipe) {
return next();
}

View File

@@ -13,7 +13,7 @@ import {PersonWithSampleRegion} from '../../../common/entities/PersonDTO';
export class ThumbnailGeneratorMWs {
public static async addThumbnailInformation(req: Request, res: Response, next: NextFunction) {
public static async addThumbnailInformation(req: Request, res: Response, next: NextFunction): Promise<any> {
if (!req.resultPipe) {
return next();
}
@@ -41,7 +41,8 @@ export class ThumbnailGeneratorMWs {
}
public static addThumbnailInfoForPersons(req: Request, res: Response, next: NextFunction) {
// tslint:disable-next-line:typedef
public static addThumbnailInfoForPersons(req: Request, res: Response, next: NextFunction): any {
if (!req.resultPipe) {
return next();
}
@@ -50,16 +51,16 @@ export class ThumbnailGeneratorMWs {
const size: number = Config.Client.Media.Thumbnail.personThumbnailSize;
const persons: PersonWithSampleRegion[] = req.resultPipe;
for (let i = 0; i < persons.length; i++) {
for (const item of persons) {
// load parameters
const mediaPath = path.join(ProjectPath.ImageFolder,
persons[i].sampleRegion.media.directory.path,
persons[i].sampleRegion.media.directory.name, persons[i].sampleRegion.media.name);
item.sampleRegion.media.directory.path,
item.sampleRegion.media.directory.name, item.sampleRegion.media.name);
// generate thumbnail path
const thPath = PhotoProcessing.generatePersonThumbnailPath(mediaPath, persons[i].sampleRegion, size);
const thPath = PhotoProcessing.generatePersonThumbnailPath(mediaPath, item.sampleRegion, size);
persons[i].readyThumbnail = fs.existsSync(thPath);
item.readyThumbnail = fs.existsSync(thPath);
}
} catch (error) {
@@ -73,7 +74,7 @@ export class ThumbnailGeneratorMWs {
}
public static async generatePersonThumbnail(req: Request, res: Response, next: NextFunction) {
public static async generatePersonThumbnail(req: Request, res: Response, next: NextFunction): Promise<any> {
if (!req.resultPipe) {
return next();
}
@@ -90,8 +91,9 @@ export class ThumbnailGeneratorMWs {
}
public static generateThumbnailFactory(sourceType: ThumbnailSourceType) {
return async (req: Request, res: Response, next: NextFunction) => {
public static generateThumbnailFactory(sourceType: ThumbnailSourceType):
(req: Request, res: Response, next: NextFunction) => Promise<any> {
return async (req: Request, res: Response, next: NextFunction): Promise<any> => {
if (!req.resultPipe) {
return next();
}
@@ -116,8 +118,9 @@ export class ThumbnailGeneratorMWs {
};
}
public static generateIconFactory(sourceType: ThumbnailSourceType) {
return async (req: Request, res: Response, next: NextFunction) => {
public static generateIconFactory(sourceType: ThumbnailSourceType):
(req: Request, res: Response, next: NextFunction) => Promise<any> {
return async (req: Request, res: Response, next: NextFunction): Promise<any> => {
if (!req.resultPipe) {
return next();
}
@@ -137,30 +140,29 @@ export class ThumbnailGeneratorMWs {
}
private static addThInfoTODir(directory: DirectoryDTO) {
private static addThInfoTODir(directory: DirectoryDTO): void {
if (typeof directory.media !== 'undefined') {
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media);
}
if (directory.preview) {
ThumbnailGeneratorMWs.addThInfoToAPhoto(directory.preview);
ThumbnailGeneratorMWs.addThInfoToAPhoto(directory.preview);
}
if (typeof directory.directories !== 'undefined') {
for (let i = 0; i < directory.directories.length; i++) {
ThumbnailGeneratorMWs.addThInfoTODir(directory.directories[i]);
for (const item of directory.directories) {
ThumbnailGeneratorMWs.addThInfoTODir(item);
}
}
}
private static addThInfoToPhotos(photos: MediaDTO[]) {
for (let i = 0; i < photos.length; i++) {
this.addThInfoToAPhoto(photos[i]);
private static addThInfoToPhotos(photos: MediaDTO[]): void {
for (const item of photos) {
this.addThInfoToAPhoto(item);
}
}
private static addThInfoToAPhoto(photo: MediaBaseDTO) {
private static addThInfoToAPhoto(photo: MediaBaseDTO): void {
const fullMediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name);
for (let j = 0; j < Config.Client.Media.Thumbnail.thumbnailSizes.length; j++) {
const size = Config.Client.Media.Thumbnail.thumbnailSizes[j];
for (const size of Config.Client.Media.Thumbnail.thumbnailSizes) {
const thPath = PhotoProcessing.generateConvertedPath(fullMediaPath, size);
if (fs.existsSync(thPath) === true) {
if (typeof photo.readyThumbnails === 'undefined') {

View File

@@ -1,6 +1,6 @@
import {NextFunction, Request, Response} from 'express';
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
import {UserDTO, UserRoles} from '../../../common/entities/UserDTO';
import {UserDTO, UserDTOUtils, UserRoles} from '../../../common/entities/UserDTO';
import {ObjectManagers} from '../../model/ObjectManagers';
import {Config} from '../../../common/config/private/Config';
import {PasswordHelper} from '../../model/PasswordHelper';
@@ -10,9 +10,12 @@ import * as path from 'path';
export class AuthenticationMWs {
public static async tryAuthenticate(req: Request, res: Response, next: NextFunction) {
public static async tryAuthenticate(req: Request, res: Response, next: NextFunction): Promise<void> {
if (Config.Client.authenticationRequired === false) {
req.session.user = <UserDTO>{name: UserRoles[Config.Client.unAuthenticatedUserRole], role: Config.Client.unAuthenticatedUserRole};
req.session.user = {
name: UserRoles[Config.Client.unAuthenticatedUserRole],
role: Config.Client.unAuthenticatedUserRole
} as UserDTO;
return next();
}
try {
@@ -28,9 +31,12 @@ export class AuthenticationMWs {
}
public static async authenticate(req: Request, res: Response, next: NextFunction) {
public static async authenticate(req: Request, res: Response, next: NextFunction): Promise<void> {
if (Config.Client.authenticationRequired === false) {
req.session.user = <UserDTO>{name: UserRoles[Config.Client.unAuthenticatedUserRole], role: Config.Client.unAuthenticatedUserRole};
req.session.user = {
name: UserRoles[Config.Client.unAuthenticatedUserRole],
role: Config.Client.unAuthenticatedUserRole
} as UserDTO;
return next();
}
@@ -56,21 +62,21 @@ export class AuthenticationMWs {
}
public static normalizePathParam(paramName: string) {
return function normalizePathParam(req: Request, res: Response, next: NextFunction) {
public static normalizePathParam(paramName: string): (req: Request, res: Response, next: NextFunction) => void {
return function normalizePathParam(req: Request, res: Response, next: NextFunction): void {
req.params[paramName] = path.normalize(req.params[paramName] || path.sep).replace(/^(\.\.[\/\\])+/, '');
return next();
};
}
public static authorisePath(paramName: string, isDirectory: boolean) {
return function authorisePath(req: Request, res: Response, next: NextFunction) {
public static authorisePath(paramName: string, isDirectory: boolean): (req: Request, res: Response, next: NextFunction) => void {
return function authorisePath(req: Request, res: Response, next: NextFunction): Response | void {
let p: string = req.params[paramName];
if (!isDirectory) {
p = path.dirname(p);
}
if (!UserDTO.isDirectoryPathAvailable(p, req.session.user.permissions)) {
if (!UserDTOUtils.isDirectoryPathAvailable(p, req.session.user.permissions)) {
return res.sendStatus(403);
}
@@ -79,8 +85,8 @@ export class AuthenticationMWs {
}
public static authorise(role: UserRoles) {
return function authorise(req: Request, res: Response, next: NextFunction) {
public static authorise(role: UserRoles): (req: Request, res: Response, next: NextFunction) => void {
return function authorise(req: Request, res: Response, next: NextFunction): void {
if (req.session.user.role < role) {
return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED));
}
@@ -88,7 +94,7 @@ export class AuthenticationMWs {
};
}
public static async shareLogin(req: Request, res: Response, next: NextFunction) {
public static async shareLogin(req: Request, res: Response, next: NextFunction): Promise<void> {
if (Config.Client.Sharing.enabled === false) {
return next();
@@ -100,9 +106,10 @@ export class AuthenticationMWs {
try {
const password = (req.body ? req.body.password : null) || null;
const sharingKey: string = <string>req.query[QueryParams.gallery.sharingKey_query] || <string>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;
const sharing = await ObjectManagers.getInstance().SharingManager.findOne({
sharingKey: sharingKey
sharingKey
});
if (!sharing || sharing.expires < Date.now() ||
@@ -118,12 +125,12 @@ export class AuthenticationMWs {
sharingPath += '*';
}
req.session.user = <UserDTO>{
req.session.user = {
name: 'Guest',
role: UserRoles.LimitedGuest,
permissions: [sharingPath],
usedSharingKey: sharing.sharingKey
};
} as UserDTO;
return next();
} catch (err) {
@@ -132,14 +139,14 @@ export class AuthenticationMWs {
}
public static inverseAuthenticate(req: Request, res: Response, next: NextFunction) {
public static inverseAuthenticate(req: Request, res: Response, next: NextFunction): void {
if (typeof req.session.user !== 'undefined') {
return next(new ErrorDTO(ErrorCodes.ALREADY_AUTHENTICATED));
}
return next();
}
public static async login(req: Request, res: Response, next: NextFunction) {
public static async login(req: Request, res: Response, next: NextFunction): Promise<void | Response> {
if (Config.Client.authenticationRequired === false) {
return res.sendStatus(404);
@@ -173,17 +180,18 @@ export class AuthenticationMWs {
}
public static logout(req: Request, res: Response, next: NextFunction) {
public static logout(req: Request, res: Response, next: NextFunction): void {
delete req.session.user;
return next();
}
private static async getSharingUser(req: Request) {
private static async getSharingUser(req: Request): Promise<UserDTO> {
if (Config.Client.Sharing.enabled === true &&
(!!req.query[QueryParams.gallery.sharingKey_query] || !!req.params[QueryParams.gallery.sharingKey_params])) {
const sharingKey: string = <string>req.query[QueryParams.gallery.sharingKey_query] || <string>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;
const sharing = await ObjectManagers.getInstance().SharingManager.findOne({
sharingKey: sharingKey
sharingKey
});
if (!sharing || sharing.expires < Date.now()) {
return null;
@@ -197,12 +205,12 @@ export class AuthenticationMWs {
if (sharing.includeSubfolders === true) {
sharingPath += '*';
}
return <UserDTO>{
return {
name: 'Guest',
role: UserRoles.LimitedGuest,
permissions: [sharingPath],
usedSharingKey: sharing.sharingKey
};
} as UserDTO;
}
return null;

View File

@@ -6,7 +6,7 @@ import {Config} from '../../../common/config/private/Config';
export class UserMWs {
public static async changePassword(req: Request, res: Response, next: NextFunction) {
public static async changePassword(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.authenticationRequired === false) {
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
@@ -27,7 +27,7 @@ export class UserMWs {
}
public static async createUser(req: Request, res: Response, next: NextFunction) {
public static async createUser(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.authenticationRequired === false) {
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
@@ -46,7 +46,7 @@ export class UserMWs {
}
public static async deleteUser(req: Request, res: Response, next: NextFunction) {
public static async deleteUser(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.authenticationRequired === false) {
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
@@ -64,7 +64,7 @@ export class UserMWs {
}
}
public static async changeRole(req: Request, res: Response, next: NextFunction) {
public static async changeRole(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.authenticationRequired === false) {
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
@@ -83,7 +83,7 @@ export class UserMWs {
}
public static async listUsers(req: Request, res: Response, next: NextFunction) {
public static async listUsers(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.authenticationRequired === false) {
return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
@@ -91,8 +91,8 @@ export class UserMWs {
try {
let result = await ObjectManagers.getInstance().UserManager.find({});
result = Utils.clone(result);
for (let i = 0; i < result.length; i++) {
result[i].password = '';
for (const item of result) {
item.password = '';
}
req.resultPipe = result;
next();

View File

@@ -5,7 +5,7 @@ import {ObjectManagers} from '../../model/ObjectManagers';
export class UserRequestConstrainsMWs {
public static forceSelfRequest(req: Request, res: Response, next: NextFunction) {
public static forceSelfRequest(req: Request, res: Response, next: NextFunction): any {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
@@ -17,7 +17,7 @@ export class UserRequestConstrainsMWs {
}
public static notSelfRequest(req: Request, res: Response, next: NextFunction) {
public static notSelfRequest(req: Request, res: Response, next: NextFunction): any {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
@@ -29,7 +29,7 @@ export class UserRequestConstrainsMWs {
return next();
}
public static async notSelfRequestOr2Admins(req: Request, res: Response, next: NextFunction) {
public static async notSelfRequestOr2Admins(req: Request, res: Response, next: NextFunction): Promise<any> {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}

View File

@@ -1,8 +1,8 @@
import {DirectoryDTO} from '../../common/entities/DirectoryDTO';
import {DirectoryDTO, DirectoryDTOUtils} from '../../common/entities/DirectoryDTO';
import {Logger} from '../Logger';
import {Config} from '../../common/config/private/Config';
import {DiskManagerTH} from './threading/ThreadPool';
import {DiskMangerWorker} from './threading/DiskMangerWorker';
import {DirectoryScanSettings, DiskMangerWorker} from './threading/DiskMangerWorker';
import {FileDTO} from '../../common/entities/FileDTO';
@@ -11,7 +11,7 @@ const LOG_TAG = '[DiskManager]';
export class DiskManager {
static threadPool: DiskManagerTH = null;
public static init() {
public static init(): void {
if (Config.Server.Threading.enabled === true) {
DiskManager.threadPool = new DiskManagerTH(1);
}
@@ -19,13 +19,13 @@ export class DiskManager {
public static async scanDirectoryNoMetadata(relativeDirectoryName: string,
settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise<DirectoryDTO<FileDTO>> {
settings: DirectoryScanSettings = {}): Promise<DirectoryDTO<FileDTO>> {
settings.noMetadata = true;
return this.scanDirectory(relativeDirectoryName, settings);
}
public static async scanDirectory(relativeDirectoryName: string,
settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise<DirectoryDTO> {
settings: DirectoryScanSettings = {}): Promise<DirectoryDTO> {
Logger.silly(LOG_TAG, 'scanning directory:', relativeDirectoryName);
@@ -36,7 +36,7 @@ export class DiskManager {
} else {
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings);
}
DirectoryDTO.unpackDirectory(directory);
DirectoryDTOUtils.unpackDirectory(directory);
return directory;
}

View File

@@ -1,5 +1,5 @@
export class FFmpegFactory {
public static get() {
public static get(): any {
const ffmpeg = require('fluent-ffmpeg');
try {
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;

View File

@@ -8,11 +8,11 @@ export class Localizations {
constructor() {
}
public static init() {
public static init(): void {
const notLanguage = ['assets'];
const dirCont = fs.readdirSync(ProjectPath.FrontendFolder)
.filter(f => fs.statSync(path.join(ProjectPath.FrontendFolder, f)).isDirectory());
Config.Client.languages = dirCont.filter(d => notLanguage.indexOf(d) === -1);
.filter((f): any => fs.statSync(path.join(ProjectPath.FrontendFolder, f)).isDirectory());
Config.Client.languages = dirCont.filter((d): boolean => notLanguage.indexOf(d) === -1);
Config.Client.languages.push('en');
}

View File

@@ -12,11 +12,11 @@ export class NotificationManager {
];
public static error(message: string, details?: any, req?: Request) {
public static error(message: string, details?: any, req?: Request): void {
const noti: NotificationDTO = {
type: NotificationType.error,
message: message,
details: details
message,
details
};
if (req) {
noti.request = {
@@ -28,11 +28,11 @@ export class NotificationManager {
NotificationManager.notifications.push(noti);
}
public static warning(message: string, details?: any, req?: Request) {
public static warning(message: string, details?: any, req?: Request): void {
const noti: NotificationDTO = {
type: NotificationType.warning,
message: message,
details: details
message,
details
};
if (req) {
noti.request = {

View File

@@ -12,100 +12,100 @@ import {LocationManager} from './database/LocationManager';
export class ObjectManagers {
private static _instance: ObjectManagers = null;
private static instance: ObjectManagers = null;
private _galleryManager: IGalleryManager;
private _userManager: IUserManager;
private _searchManager: ISearchManager;
private _sharingManager: ISharingManager;
private _indexingManager: IIndexingManager;
private _personManager: IPersonManager;
private _versionManager: IVersionManager;
private _jobManager: IJobManager;
private _locationManager: LocationManager;
private galleryManager: IGalleryManager;
private userManager: IUserManager;
private searchManager: ISearchManager;
private sharingManager: ISharingManager;
private indexingManager: IIndexingManager;
private personManager: IPersonManager;
private versionManager: IVersionManager;
private jobManager: IJobManager;
private locationManager: LocationManager;
get VersionManager(): IVersionManager {
return this._versionManager;
return this.versionManager;
}
set VersionManager(value: IVersionManager) {
this._versionManager = value;
this.versionManager = value;
}
get LocationManager(): LocationManager {
return this._locationManager;
return this.locationManager;
}
set LocationManager(value: LocationManager) {
this._locationManager = value;
this.locationManager = value;
}
get PersonManager(): IPersonManager {
return this._personManager;
return this.personManager;
}
set PersonManager(value: IPersonManager) {
this._personManager = value;
this.personManager = value;
}
get IndexingManager(): IIndexingManager {
return this._indexingManager;
return this.indexingManager;
}
set IndexingManager(value: IIndexingManager) {
this._indexingManager = value;
this.indexingManager = value;
}
get GalleryManager(): IGalleryManager {
return this._galleryManager;
return this.galleryManager;
}
set GalleryManager(value: IGalleryManager) {
this._galleryManager = value;
this.galleryManager = value;
}
get UserManager(): IUserManager {
return this._userManager;
return this.userManager;
}
set UserManager(value: IUserManager) {
this._userManager = value;
this.userManager = value;
}
get SearchManager(): ISearchManager {
return this._searchManager;
return this.searchManager;
}
set SearchManager(value: ISearchManager) {
this._searchManager = value;
this.searchManager = value;
}
get SharingManager(): ISharingManager {
return this._sharingManager;
return this.sharingManager;
}
set SharingManager(value: ISharingManager) {
this._sharingManager = value;
this.sharingManager = value;
}
get JobManager(): IJobManager {
return this._jobManager;
return this.jobManager;
}
set JobManager(value: IJobManager) {
this._jobManager = value;
this.jobManager = value;
}
public static getInstance() {
if (this._instance === null) {
this._instance = new ObjectManagers();
public static getInstance(): ObjectManagers {
if (this.instance === null) {
this.instance = new ObjectManagers();
}
return this._instance;
return this.instance;
}
public static async reset() {
public static async reset(): Promise<void> {
if (ObjectManagers.getInstance().IndexingManager &&
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) {
await ObjectManagers.getInstance().IndexingManager.SavingReady;
@@ -114,16 +114,16 @@ export class ObjectManagers {
ObjectManagers.getInstance().JobManager.stopSchedules();
}
await SQLConnection.close();
this._instance = null;
this.instance = null;
}
public static async InitCommonManagers() {
public static async InitCommonManagers(): Promise<void> {
const JobManager = require('./jobs/JobManager').JobManager;
ObjectManagers.getInstance().JobManager = new JobManager();
}
public static async InitMemoryManagers() {
public static async InitMemoryManagers(): Promise<void> {
await ObjectManagers.reset();
const GalleryManager = require('./database/memory/GalleryManager').GalleryManager;
const UserManager = require('./database/memory/UserManager').UserManager;
@@ -143,7 +143,7 @@ export class ObjectManagers {
this.InitCommonManagers();
}
public static async InitSQLManagers() {
public static async InitSQLManagers(): Promise<void> {
await ObjectManagers.reset();
await SQLConnection.init();
const GalleryManager = require('./database/sql/GalleryManager').GalleryManager;

View File

@@ -1,11 +1,11 @@
import {GPSMetadata} from '../../../common/entities/PhotoDTO';
import * as NodeGeocoder from 'node-geocoder';
import {LocationLookupException} from '../../exceptions/LocationLookupException';
import {Utils} from '../../../common/Utils';
import {LRU} from '../../../common/Utils';
export class LocationManager {
readonly geocoder: NodeGeocoder.Geocoder;
cache = new Utils.LRU<GPSMetadata>(100);
cache = new LRU<GPSMetadata>(100);
constructor() {
this.geocoder = NodeGeocoder({provider: 'openstreetmap'});

View File

@@ -6,7 +6,7 @@ import {DiskManager} from '../../DiskManger';
import {ProjectPath} from '../../../ProjectPath';
import {Config} from '../../../../common/config/private/Config';
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import {ReIndexingSensitivity} from '../../../../common/config/private/PrivateConfig';
export class GalleryManager implements IGalleryManager {
@@ -17,7 +17,7 @@ export class GalleryManager implements IGalleryManager {
const lastModified = DiskMangerWorker.calcLastModified(stat);
if (Date.now() - knownLastScanned <= Config.Server.Indexing.cachedFolderTimeout &&
lastModified === knownLastModified &&
Config.Server.Indexing.reIndexingSensitivity < ServerConfig.ReIndexingSensitivity.high) {
Config.Server.Indexing.reIndexingSensitivity < ReIndexingSensitivity.high) {
return Promise.resolve(null);
}
}

View File

@@ -5,7 +5,6 @@ import {Utils} from '../../../../common/Utils';
import * as fs from 'fs';
import * as path from 'path';
import {PasswordHelper} from '../../PasswordHelper';
import {Config} from '../../../../common/config/private/Config';
export class UserManager implements IUserManager {
@@ -26,14 +25,14 @@ export class UserManager implements IUserManager {
if (!this.db.users) {
this.db.users = [];
// TODO: remove defaults
this.createUser(<UserDTO>{name: 'admin', password: 'admin', role: UserRoles.Admin});
this.createUser({name: 'admin', password: 'admin', role: UserRoles.Admin} as UserDTO);
}
this.saveDB();
}
public async findOne(filter: any) {
public async findOne(filter: any): Promise<UserDTO> {
const result = await this.find(filter);
if (result.length === 0) {
@@ -42,7 +41,7 @@ export class UserManager implements IUserManager {
return result[0];
}
public async find(filter: any) {
public async find(filter: any): Promise<UserDTO[]> {
const pass = filter.password;
delete filter.password;
const users = this.db.users.slice();
@@ -59,7 +58,7 @@ export class UserManager implements IUserManager {
return users;
}
public async createUser(user: UserDTO) {
public async createUser(user: UserDTO): Promise<UserDTO> {
user.id = this.db.idCounter++;
user.password = PasswordHelper.cryptPassword(user.password);
this.db.users.push(user);
@@ -67,9 +66,9 @@ export class UserManager implements IUserManager {
return user;
}
public async deleteUser(id: number) {
const deleted = this.db.users.filter((u: UserDTO) => u.id === id);
this.db.users = this.db.users.filter((u: UserDTO) => u.id !== id);
public async deleteUser(id: number): Promise<null | UserDTO> {
const deleted = this.db.users.filter((u: UserDTO): boolean => u.id === id);
this.db.users = this.db.users.filter((u: UserDTO): boolean => u.id !== id);
this.saveDB();
if (deleted.length > 0) {
return deleted[0];
@@ -78,25 +77,25 @@ export class UserManager implements IUserManager {
}
public async changeRole(id: number, newRole: UserRoles): Promise<UserDTO> {
for (let i = 0; i < this.db.users.length; i++) {
if (this.db.users[i].id === id) {
this.db.users[i].role = newRole;
for (const item of this.db.users) {
if (item.id === id) {
item.role = newRole;
this.saveDB();
return this.db.users[i];
return item;
}
}
}
public async changePassword(request: any) {
public async changePassword(request: any): Promise<void> {
throw new Error('not implemented'); // TODO: implement
}
private loadDB() {
private loadDB(): void {
const data = fs.readFileSync(this.dbPath, 'utf8');
this.db = JSON.parse(data);
}
private saveDB() {
private saveDB(): void {
fs.writeFileSync(this.dbPath, JSON.stringify(this.db));
}

View File

@@ -17,8 +17,8 @@ import {Logger} from '../../../Logger';
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {ObjectManagers} from '../../ObjectManagers';
import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import DatabaseType = ServerConfig.DatabaseType;
import {DatabaseType, ReIndexingSensitivity} from '../../../../common/config/private/PrivateConfig';
const LOG_TAG = '[GalleryManager]';
@@ -47,11 +47,11 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
if (knownLastModified && knownLastScanned
&& lastModified === knownLastModified &&
dir.lastScanned === knownLastScanned) {
if (Config.Server.Indexing.reIndexingSensitivity === ServerConfig.ReIndexingSensitivity.low) {
if (Config.Server.Indexing.reIndexingSensitivity === ReIndexingSensitivity.low) {
return null;
}
if (Date.now() - dir.lastScanned <= Config.Server.Indexing.cachedFolderTimeout &&
Config.Server.Indexing.reIndexingSensitivity === ServerConfig.ReIndexingSensitivity.medium) {
Config.Server.Indexing.reIndexingSensitivity === ReIndexingSensitivity.medium) {
return null;
}
}
@@ -66,13 +66,13 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
// not indexed since a while, index it in a lazy manner
if ((Date.now() - dir.lastScanned > Config.Server.Indexing.cachedFolderTimeout &&
Config.Server.Indexing.reIndexingSensitivity >= ServerConfig.ReIndexingSensitivity.medium) ||
Config.Server.Indexing.reIndexingSensitivity >= ServerConfig.ReIndexingSensitivity.high) {
Config.Server.Indexing.reIndexingSensitivity >= ReIndexingSensitivity.medium) ||
Config.Server.Indexing.reIndexingSensitivity >= ReIndexingSensitivity.high) {
// on the fly reindexing
Logger.silly(LOG_TAG, 'lazy reindexing reason: cache timeout: lastScanned: '
+ (Date.now() - dir.lastScanned) + ' ms ago, cachedFolderTimeout:' + Config.Server.Indexing.cachedFolderTimeout);
ObjectManagers.getInstance().IndexingManager.indexDirectory(relativeDirectoryName).catch((err) => {
ObjectManagers.getInstance().IndexingManager.indexDirectory(relativeDirectoryName).catch((err): void => {
console.error(err);
});
}
@@ -117,12 +117,12 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
.getCount();
}
public async getPossibleDuplicates() {
public async getPossibleDuplicates(): Promise<DuplicatesDTO[]> {
const connection = await SQLConnection.getConnection();
const mediaRepository = connection.getRepository(MediaEntity);
let duplicates = await mediaRepository.createQueryBuilder('media')
.innerJoin(query => query.from(MediaEntity, 'innerMedia')
.innerJoin((query): any => query.from(MediaEntity, 'innerMedia')
.select(['innerMedia.name as name', 'innerMedia.metadata.fileSize as fileSize', 'count(*)'])
.groupBy('innerMedia.name, innerMedia.metadata.fileSize')
.having('count(*)>1'),
@@ -136,7 +136,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
const duplicateParis: DuplicatesDTO[] = [];
const processDuplicates = (duplicateList: MediaEntity[],
equalFn: (a: MediaEntity, b: MediaEntity) => boolean,
checkDuplicates: boolean = false) => {
checkDuplicates: boolean = false): void => {
let i = duplicateList.length - 1;
while (i >= 0) {
const list = [duplicateList[i]];
@@ -152,12 +152,12 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
}
if (checkDuplicates) {
// ad to group if one already existed
const foundDuplicates = duplicateParis.find(dp =>
!!dp.media.find(m =>
!!list.find(lm => lm.id === m.id)));
const foundDuplicates = duplicateParis.find((dp): boolean =>
!!dp.media.find((m): boolean =>
!!list.find((lm): boolean => lm.id === m.id)));
if (foundDuplicates) {
list.forEach(lm => {
if (!!foundDuplicates.media.find(m => m.id === lm.id)) {
list.forEach((lm): void => {
if (!!foundDuplicates.media.find((m): boolean => m.id === lm.id)) {
return;
}
foundDuplicates.media.push(lm);
@@ -171,12 +171,12 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
};
processDuplicates(duplicates,
(a, b) => a.name === b.name &&
(a, b): boolean => a.name === b.name &&
a.metadata.fileSize === b.metadata.fileSize);
duplicates = await mediaRepository.createQueryBuilder('media')
.innerJoin(query => query.from(MediaEntity, 'innerMedia')
.innerJoin((query): any => 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'),
@@ -187,7 +187,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
.limit(Config.Server.Duplicates.listingLimit).getMany();
processDuplicates(duplicates,
(a, b) => a.metadata.creationDate === b.metadata.creationDate &&
(a, b): boolean => a.metadata.creationDate === b.metadata.creationDate &&
a.metadata.fileSize === b.metadata.fileSize, true);
return duplicateParis;
@@ -252,13 +252,13 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
'face.box.top', 'face.box.width', 'face.box.height',
'media.id', 'person.name', 'person.id'])
.getMany();
for (let i = 0; i < dir.media.length; i++) {
dir.media[i].directory = dir;
dir.media[i].readyThumbnails = [];
dir.media[i].readyIcon = false;
(<PhotoDTO>dir.media[i]).metadata.faces = indexedFaces
.filter(fe => fe.media.id === dir.media[i].id)
.map(f => ({box: f.box, name: f.person.name}));
for (const item of dir.media) {
item.directory = dir;
item.readyThumbnails = [];
item.readyIcon = false;
(item as PhotoDTO).metadata.faces = indexedFaces
.filter((fe): boolean => fe.media.id === item.id)
.map((f): { name: any; box: any } => ({box: f.box, name: f.person.name}));
}
if (dir.media.length > 0) {
dir.preview = dir.media[0];
@@ -267,27 +267,27 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
}
}
if (dir.directories) {
for (let i = 0; i < dir.directories.length; i++) {
for (const item of dir.directories) {
dir.directories[i].media = [];
dir.directories[i].preview = await connection
item.media = [];
item.preview = await connection
.getRepository(MediaEntity)
.createQueryBuilder('media')
.innerJoinAndSelect('media.directory', 'directory')
.where('media.directory = :dir', {
dir: dir.directories[i].id
dir: item.id
})
.orderBy('media.metadata.creationDate', 'DESC')
.limit(1)
.getOne();
dir.directories[i].isPartial = true;
item.isPartial = true;
if (dir.directories[i].preview) {
dir.directories[i].preview.directory = dir.directories[i];
dir.directories[i].preview.readyThumbnails = [];
dir.directories[i].preview.readyIcon = false;
if (item.preview) {
item.preview.directory = item;
item.preview.readyThumbnails = [];
item.preview.readyIcon = false;
} else {
await this.fillPreviewFromSubDir(connection, dir.directories[i]);
await this.fillPreviewFromSubDir(connection, item);
}
}
}

View File

@@ -7,7 +7,7 @@ import {Utils} from '../../../../common/Utils';
import {FaceRegion, PhotoMetadata} from '../../../../common/entities/PhotoDTO';
import {Connection, Repository} from 'typeorm';
import {MediaEntity} from './enitites/MediaEntity';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {MediaDTO, MediaDTOUtils} from '../../../../common/entities/MediaDTO';
import {VideoEntity} from './enitites/VideoEntity';
import {FileEntity} from './enitites/FileEntity';
import {FileDTO} from '../../../../common/entities/FileDTO';
@@ -27,12 +27,12 @@ export class IndexingManager implements IIndexingManager {
private savingQueue: DirectoryDTO[] = [];
private isSaving = false;
get IsSavingInProgress() {
get IsSavingInProgress(): boolean {
return this.SavingReady !== null;
}
public indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> {
return new Promise(async (resolve, reject) => {
return new Promise(async (resolve, reject): Promise<void> => {
try {
const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName);
@@ -40,7 +40,7 @@ export class IndexingManager implements IIndexingManager {
if (scannedDirectory.preview) {
scannedDirectory.preview.readyThumbnails = [];
}
scannedDirectory.media.forEach(p => p.readyThumbnails = []);
scannedDirectory.media.forEach((p): any[] => p.readyThumbnails = []);
resolve(scannedDirectory);
this.queueForSave(scannedDirectory).catch(console.error);
@@ -61,13 +61,13 @@ export class IndexingManager implements IIndexingManager {
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.delete()
.execute().then(() => {
.execute().then((): void => {
});
}
// Todo fix it, once typeorm support connection pools for sqlite
protected async queueForSave(scannedDirectory: DirectoryDTO) {
if (this.savingQueue.findIndex(dir => dir.name === scannedDirectory.name &&
protected async queueForSave(scannedDirectory: DirectoryDTO): Promise<void> {
if (this.savingQueue.findIndex((dir): boolean => dir.name === scannedDirectory.name &&
dir.path === scannedDirectory.path &&
dir.lastModified === scannedDirectory.lastModified &&
dir.lastScanned === scannedDirectory.lastScanned &&
@@ -77,7 +77,7 @@ export class IndexingManager implements IIndexingManager {
}
this.savingQueue.push(scannedDirectory);
if (!this.SavingReady) {
this.SavingReady = new Promise<void>((resolve) => {
this.SavingReady = new Promise<void>((resolve): void => {
this.SavingReadyPR = resolve;
});
}
@@ -115,24 +115,24 @@ export class IndexingManager implements IIndexingManager {
return currentDir.id;
} else {
return (await directoryRepository.insert(<DirectoryEntity>{
return (await directoryRepository.insert({
mediaCount: scannedDirectory.mediaCount,
lastModified: scannedDirectory.lastModified,
lastScanned: scannedDirectory.lastScanned,
name: scannedDirectory.name,
path: scannedDirectory.path
})).identifiers[0].id;
} as DirectoryEntity)).identifiers[0].id;
}
}
protected async saveChildDirs(connection: Connection, currentDirId: number, scannedDirectory: DirectoryDTO) {
protected async saveChildDirs(connection: Connection, currentDirId: number, scannedDirectory: DirectoryDTO): Promise<void> {
const directoryRepository = connection.getRepository(DirectoryEntity);
// update subdirectories that does not have a parent
await directoryRepository
.createQueryBuilder()
.update(DirectoryEntity)
.set({parent: <any>currentDirId})
.set({parent: currentDirId as any})
.where('path = :path',
{path: DiskMangerWorker.pathFromParent(scannedDirectory)})
.andWhere('name NOT LIKE :root', {root: DiskMangerWorker.dirName('.')})
@@ -146,18 +146,18 @@ export class IndexingManager implements IIndexingManager {
dir: currentDirId
}).getMany();
for (let i = 0; i < scannedDirectory.directories.length; i++) {
for (const directory of scannedDirectory.directories) {
// Was this child Dir already indexed before?
const dirIndex = childDirectories.findIndex(d => d.name === scannedDirectory.directories[i].name);
const dirIndex = childDirectories.findIndex((d): boolean => d.name === directory.name);
if (dirIndex !== -1) { // directory found
childDirectories.splice(dirIndex, 1);
} else { // dir does not exists yet
scannedDirectory.directories[i].parent = <any>{id: currentDirId};
(<DirectoryEntity>scannedDirectory.directories[i]).lastScanned = null; // new child dir, not fully scanned yet
const d = await directoryRepository.insert(<DirectoryEntity>scannedDirectory.directories[i]);
directory.parent = ({id: currentDirId} as any);
(directory as DirectoryEntity).lastScanned = null; // new child dir, not fully scanned yet
const d = await directoryRepository.insert(directory as DirectoryEntity);
await this.saveMedia(connection, d.identifiers[0].id, scannedDirectory.directories[i].media);
await this.saveMedia(connection, d.identifiers[0].id, directory.media);
}
}
@@ -176,20 +176,20 @@ export class IndexingManager implements IIndexingManager {
const metaFilesToSave = [];
for (let i = 0; i < scannedDirectory.metaFile.length; i++) {
for (const item of scannedDirectory.metaFile) {
let metaFile: FileDTO = null;
for (let j = 0; j < indexedMetaFiles.length; j++) {
if (indexedMetaFiles[j].name === scannedDirectory.metaFile[i].name) {
if (indexedMetaFiles[j].name === item.name) {
metaFile = indexedMetaFiles[j];
indexedMetaFiles.splice(j, 1);
break;
}
}
if (metaFile == null) { // not in DB yet
scannedDirectory.metaFile[i].directory = null;
metaFile = Utils.clone(scannedDirectory.metaFile[i]);
scannedDirectory.metaFile[i].directory = scannedDirectory;
metaFile.directory = <any>{id: currentDirID};
item.directory = null;
metaFile = Utils.clone(item);
item.directory = scannedDirectory;
metaFile.directory = ({id: currentDirID} as any);
metaFilesToSave.push(metaFile);
}
}
@@ -197,7 +197,7 @@ export class IndexingManager implements IIndexingManager {
await fileRepository.remove(indexedMetaFiles, {chunk: Math.max(Math.ceil(indexedMetaFiles.length / 500), 1)});
}
protected async saveMedia(connection: Connection, parentDirId: number, media: MediaDTO[]) {
protected async saveMedia(connection: Connection, parentDirId: number, media: MediaDTO[]): Promise<void> {
const mediaRepository = connection.getRepository(MediaEntity);
const photoRepository = connection.getRepository(PhotoEntity);
const videoRepository = connection.getRepository(VideoEntity);
@@ -215,6 +215,7 @@ export class IndexingManager implements IIndexingManager {
insertV: [] // insert video
};
const facesPerPhoto: { faces: FaceRegionEntry[], mediaName: string }[] = [];
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < media.length; i++) {
let mediaItem: MediaEntity = null;
for (let j = 0; j < indexedMedia.length; j++) {
@@ -225,22 +226,22 @@ export class IndexingManager implements IIndexingManager {
}
}
const scannedFaces = (<PhotoMetadata>media[i].metadata).faces || [];
if ((<PhotoMetadata>media[i].metadata).faces) { // if it has faces, cache them
(<PhotoMetadataEntity>media[i].metadata).persons = (<PhotoMetadata>media[i].metadata).faces.map(f => f.name);
const scannedFaces = (media[i].metadata as PhotoMetadata).faces || [];
if ((media[i].metadata as PhotoMetadata).faces) { // if it has faces, cache them
(media[i].metadata as PhotoMetadataEntity).persons = (media[i].metadata as PhotoMetadata).faces.map(f => f.name);
}
delete (<PhotoMetadata>media[i].metadata).faces; // this is a separated DB, lets save separately
delete (media[i].metadata as PhotoMetadata).faces; // this is a separated DB, lets save separately
if (mediaItem == null) { // not in DB yet
media[i].directory = null;
mediaItem = <any>Utils.clone(media[i]);
mediaItem.directory = <any>{id: parentDirId};
(MediaDTO.isPhoto(mediaItem) ? mediaChange.insertP : mediaChange.insertV).push(mediaItem);
mediaItem = (Utils.clone(media[i]) as any);
mediaItem.directory = ({id: parentDirId} as any);
(MediaDTOUtils.isPhoto(mediaItem) ? mediaChange.insertP : mediaChange.insertV).push(mediaItem);
} else { // already in the DB, only needs to be updated
delete (<PhotoMetadata>mediaItem.metadata).faces;
delete (mediaItem.metadata as PhotoMetadata).faces;
if (!Utils.equalsFilter(mediaItem.metadata, media[i].metadata)) {
mediaItem.metadata = <any>media[i].metadata;
(MediaDTO.isPhoto(mediaItem) ? mediaChange.saveP : mediaChange.saveV).push(mediaItem);
mediaItem.metadata = (media[i].metadata as any);
(MediaDTOUtils.isPhoto(mediaItem) ? mediaChange.saveP : mediaChange.saveV).push(mediaItem);
}
}
@@ -261,9 +262,9 @@ export class IndexingManager implements IIndexingManager {
.getMany());
const faces: FaceRegionEntry[] = [];
facesPerPhoto.forEach(group => {
const mIndex = indexedMedia.findIndex(m => m.name === group.mediaName);
group.faces.forEach((sf: FaceRegionEntry) => sf.media = <any>{id: indexedMedia[mIndex].id});
facesPerPhoto.forEach((group): void => {
const mIndex = indexedMedia.findIndex((m): boolean => m.name === group.mediaName);
group.faces.forEach((sf: FaceRegionEntry): any => sf.media = ({id: indexedMedia[mIndex].id} as any));
faces.push(...group.faces);
indexedMedia.splice(mIndex, 1);
@@ -273,14 +274,14 @@ export class IndexingManager implements IIndexingManager {
await mediaRepository.remove(indexedMedia);
}
protected async saveFaces(connection: Connection, parentDirId: number, scannedFaces: FaceRegion[]) {
protected async saveFaces(connection: Connection, parentDirId: number, scannedFaces: FaceRegion[]): Promise<void> {
const faceRepository = connection.getRepository(FaceRegionEntry);
const persons: string[] = [];
for (let i = 0; i < scannedFaces.length; i++) {
if (persons.indexOf(scannedFaces[i].name) === -1) {
persons.push(scannedFaces[i].name);
for (const face of scannedFaces) {
if (persons.indexOf(face.name) === -1) {
persons.push(face.name);
}
}
await ObjectManagers.getInstance().PersonManager.saveAll(persons);
@@ -296,6 +297,7 @@ export class IndexingManager implements IIndexingManager {
const faceToInsert = [];
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < scannedFaces.length; i++) {
let face: FaceRegionEntry = null;
for (let j = 0; j < indexedFaces.length; j++) {
@@ -311,7 +313,7 @@ export class IndexingManager implements IIndexingManager {
}
if (face == null) {
(<FaceRegionEntry>scannedFaces[i]).person = await ObjectManagers.getInstance().PersonManager.get(scannedFaces[i].name);
(scannedFaces[i] as FaceRegionEntry).person = await ObjectManagers.getInstance().PersonManager.get(scannedFaces[i].name);
faceToInsert.push(scannedFaces[i]);
}
}

View File

@@ -14,7 +14,7 @@ export class PersonManager implements ISQLPersonManager {
const repository = connection.getRepository(PersonEntry);
const person = await repository.createQueryBuilder('person')
.limit(1)
.where('person.name LIKE :name COLLATE utf8_general_ci', {name: name}).getOne();
.where('person.name LIKE :name COLLATE utf8_general_ci', {name}).getOne();
if (typeof partialPerson.name !== 'undefined') {
@@ -63,7 +63,7 @@ export class PersonManager implements ISQLPersonManager {
if (this.persons === null) {
await this.loadAll();
}
return this.persons.find(p => p.name === name);
return this.persons.find((p): boolean => p.name === name);
}
@@ -73,11 +73,11 @@ export class PersonManager implements ISQLPersonManager {
const personRepository = connection.getRepository(PersonEntry);
await this.loadAll();
for (let i = 0; i < names.length; i++) {
for (const item of names) {
const person = this.persons.find(p => p.name === names[i]);
const person = this.persons.find((p): boolean => p.name === item);
if (!person) {
toSave.push({name: names[i]});
toSave.push({name: item});
}
}
@@ -91,13 +91,13 @@ export class PersonManager implements ISQLPersonManager {
}
public async onGalleryIndexUpdate() {
public async onGalleryIndexUpdate(): Promise<void> {
await this.updateCounts();
await this.updateSamplePhotos();
}
private async updateCounts() {
private async updateCounts(): Promise<void> {
const connection = await SQLConnection.getConnection();
await connection.query('UPDATE person_entry SET count = ' +
' (SELECT COUNT(1) FROM face_region_entry WHERE face_region_entry.personId = person_entry.id)');
@@ -111,7 +111,7 @@ export class PersonManager implements ISQLPersonManager {
.execute();
}
private async updateSamplePhotos() {
private async updateSamplePhotos(): Promise<void> {
const connection = await SQLConnection.getConnection();
await connection.query('update person_entry set sampleRegionId = ' +
'(Select face_region_entry.id from media_entity ' +

View File

@@ -18,8 +18,7 @@ import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {PersonEntry} from './enitites/PersonEntry';
import {Utils} from '../../../../common/Utils';
import * as path from 'path';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import DatabaseType = ServerConfig.DatabaseType;
import {DatabaseType, ServerDataBaseConfig, SQLLogLevel} from '../../../../common/config/private/PrivateConfig';
export class SQLConnection {
@@ -47,8 +46,8 @@ export class SQLConnection {
VersionEntity
];
options.synchronize = false;
if (Config.Server.Log.sqlLevel !== ServerConfig.SQLLogLevel.none) {
options.logging = ServerConfig.SQLLogLevel[Config.Server.Log.sqlLevel];
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
options.logging = SQLLogLevel[Config.Server.Log.sqlLevel];
}
this.connection = await this.createConnection(options);
@@ -57,7 +56,7 @@ export class SQLConnection {
return this.connection;
}
public static async tryConnection(config: ServerConfig.DataBaseConfig) {
public static async tryConnection(config: ServerDataBaseConfig): Promise<boolean> {
try {
await getConnection('test').close();
} catch (err) {
@@ -77,8 +76,8 @@ export class SQLConnection {
VersionEntity
];
options.synchronize = false;
if (Config.Server.Log.sqlLevel !== ServerConfig.SQLLogLevel.none) {
options.logging = ServerConfig.SQLLogLevel[Config.Server.Log.sqlLevel];
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
options.logging = SQLLogLevel[Config.Server.Log.sqlLevel];
}
const conn = await this.createConnection(options);
await SQLConnection.schemeSync(conn);
@@ -102,7 +101,7 @@ export class SQLConnection {
}
public static async close() {
public static async close(): Promise<void> {
try {
if (this.connection != null) {
await this.connection.close();
@@ -114,11 +113,11 @@ export class SQLConnection {
}
}
public static getSQLiteDB(config: ServerConfig.DataBaseConfig) {
public static getSQLiteDB(config: ServerDataBaseConfig): any {
return path.join(ProjectPath.getAbsolutePath(config.dbFolder), 'sqlite.db');
}
private static async createConnection(options: ConnectionOptions) {
private static async createConnection(options: ConnectionOptions): Promise<Connection> {
if (options.type === 'sqlite') {
return await createConnection(options);
}
@@ -139,7 +138,7 @@ export class SQLConnection {
}
}
private static async schemeSync(connection: Connection) {
private static async schemeSync(connection: Connection): Promise<void> {
let version = null;
try {
version = await connection.getRepository(VersionEntity).findOne();
@@ -172,9 +171,9 @@ export class SQLConnection {
}
}
private static getDriver(config: ServerConfig.DataBaseConfig): ConnectionOptions {
private static getDriver(config: ServerDataBaseConfig): ConnectionOptions {
let driver: ConnectionOptions = null;
if (config.type === ServerConfig.DatabaseType.mysql) {
if (config.type === DatabaseType.mysql) {
driver = {
type: 'mysql',
host: config.mysql.host,
@@ -184,7 +183,7 @@ export class SQLConnection {
database: config.mysql.database,
charset: 'utf8'
};
} else if (config.type === ServerConfig.DatabaseType.sqlite) {
} else if (config.type === DatabaseType.sqlite) {
driver = {
type: 'sqlite',
database: path.join(ProjectPath.getAbsolutePath(config.dbFolder), config.sqlite.DBFileName)

View File

@@ -31,7 +31,7 @@ import {GalleryManager} from './GalleryManager';
import {ObjectManagers} from '../../ObjectManagers';
import {Utils} from '../../../../common/Utils';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import {DatabaseType} from '../../../../common/config/private/PrivateConfig';
export class SearchManager implements ISearchManager {
@@ -66,10 +66,10 @@ export class SearchManager implements ISearchManager {
.where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany())
.map(r => <Array<string>>(<string>r.metadataKeywords).split(','))
.forEach(keywords => {
.map((r): Array<string> => (r.metadataKeywords as string).split(',') as Array<string>)
.forEach((keywords): void => {
result = result.concat(this.encapsulateAutoComplete(keywords
.filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.keyword));
.filter((k): boolean => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.keyword));
});
}
@@ -95,11 +95,11 @@ export class SearchManager implements ISearchManager {
.groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city')
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany())
.filter(pm => !!pm)
.map(pm => <Array<string>>[pm.city || '', pm.country || '', pm.state || ''])
.forEach(positions => {
.filter((pm): boolean => !!pm)
.map((pm): Array<string> => [pm.city || '', pm.country || '', pm.state || ''] as Array<string>)
.forEach((positions): void => {
result = result.concat(this.encapsulateAutoComplete(positions
.filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1),
.filter((p): boolean => p.toLowerCase().indexOf(text.toLowerCase()) !== -1),
type === SearchQueryTypes.distance ? type : SearchQueryTypes.position));
});
}
@@ -137,7 +137,7 @@ export class SearchManager implements ISearchManager {
return SearchManager.autoCompleteItemsUnique(result);
}
async search(queryIN: SearchQueryDTO) {
async search(queryIN: SearchQueryDTO): Promise<SearchResultDTO> {
let query = this.flattenSameOfQueries(queryIN);
query = await this.getGPSData(query);
const connection = await SQLConnection.getConnection();
@@ -152,7 +152,7 @@ export class SearchManager implements ISearchManager {
const sqlQuery = await connection.getRepository(MediaEntity).createQueryBuilder('media')
.innerJoin(q => {
.innerJoin((q): any => {
const subQuery = q.from(MediaEntity, 'media')
.select('distinct media.id')
.limit(Config.Client.Search.maxMediaResult + 1);
@@ -198,23 +198,23 @@ export class SearchManager implements ISearchManager {
.where(this.buildWhereQuery(query));
if (Config.Server.Database.type === ServerConfig.DatabaseType.mysql) {
if (Config.Server.Database.type === DatabaseType.mysql) {
return await sqlQuery.groupBy('RAND(), media.id').limit(1).getOne();
}
return await sqlQuery.groupBy('RANDOM()').limit(1).getOne();
}
private async getGPSData(query: SearchQueryDTO) {
private async getGPSData(query: SearchQueryDTO): Promise<SearchQueryDTO> {
if ((query as ANDSearchQuery | ORSearchQuery).list) {
for (let i = 0; i < (query as ANDSearchQuery | ORSearchQuery).list.length; ++i) {
(query as ANDSearchQuery | ORSearchQuery).list[i] =
await this.getGPSData((query as ANDSearchQuery | ORSearchQuery).list[i]);
}
}
if (query.type === SearchQueryTypes.distance && (<DistanceSearch>query).from.text) {
(<DistanceSearch>query).from.GPSData =
await ObjectManagers.getInstance().LocationManager.getGPSData((<DistanceSearch>query).from.text);
if (query.type === SearchQueryTypes.distance && (query as DistanceSearch).from.text) {
(query as DistanceSearch).from.GPSData =
await ObjectManagers.getInstance().LocationManager.getGPSData((query as DistanceSearch).from.text);
}
return query;
}
@@ -222,13 +222,13 @@ export class SearchManager implements ISearchManager {
private buildWhereQuery(query: SearchQueryDTO, paramCounter = {value: 0}): Brackets {
switch (query.type) {
case SearchQueryTypes.AND:
return new Brackets(q => {
(<ANDSearchQuery>query).list.forEach(sq => q.andWhere(this.buildWhereQuery(sq, paramCounter)));
return new Brackets((q): any => {
(query as ANDSearchQuery).list.forEach((sq): any => q.andWhere(this.buildWhereQuery(sq, paramCounter)));
return q;
});
case SearchQueryTypes.OR:
return new Brackets(q => {
(<ANDSearchQuery>query).list.forEach(sq => q.orWhere(this.buildWhereQuery(sq, paramCounter)));
return new Brackets((q): any => {
(query as ANDSearchQuery).list.forEach((sq): any => q.orWhere(this.buildWhereQuery(sq, paramCounter)));
return q;
});
@@ -238,31 +238,33 @@ export class SearchManager implements ISearchManager {
* This is a best effort calculation, not fully accurate in order to have higher performance.
* see: https://stackoverflow.com/a/50506609
*/
const earth = 6378.137, // radius of the earth in kilometer
latDelta = (1 / ((2 * Math.PI / 360) * earth)), // 1 km in degree
lonDelta = (1 / ((2 * Math.PI / 360) * earth)); // 1 km in degree
const earth = 6378.137; // radius of the earth in kilometer
const latDelta = (1 / ((2 * Math.PI / 360) * earth)); // 1 km in degree
const lonDelta = (1 / ((2 * Math.PI / 360) * earth)); // 1 km in degree
// TODO: properly handle latitude / longitude boundaries
const trimRange = (value: number, min: number, max: number) => {
const trimRange = (value: number, min: number, max: number): number => {
return Math.min(Math.max(value, min), max);
};
const minLat = trimRange((<DistanceSearch>query).from.GPSData.latitude - ((<DistanceSearch>query).distance * latDelta), -90, 90),
maxLat = trimRange((<DistanceSearch>query).from.GPSData.latitude + ((<DistanceSearch>query).distance * latDelta), -90, 90),
minLon = trimRange((<DistanceSearch>query).from.GPSData.longitude -
((<DistanceSearch>query).distance * lonDelta) / Math.cos(minLat * (Math.PI / 180)), -180, 180),
maxLon = trimRange((<DistanceSearch>query).from.GPSData.longitude +
((<DistanceSearch>query).distance * lonDelta) / Math.cos(maxLat * (Math.PI / 180)), -180, 180);
const minLat = trimRange((query as DistanceSearch).from.GPSData.latitude -
((query as DistanceSearch).distance * latDelta), -90, 90);
const maxLat = trimRange((query as DistanceSearch).from.GPSData.latitude +
((query as DistanceSearch).distance * latDelta), -90, 90);
const minLon = trimRange((query as DistanceSearch).from.GPSData.longitude -
((query as DistanceSearch).distance * lonDelta) / Math.cos(minLat * (Math.PI / 180)), -180, 180);
const maxLon = trimRange((query as DistanceSearch).from.GPSData.longitude +
((query as DistanceSearch).distance * lonDelta) / Math.cos(maxLat * (Math.PI / 180)), -180, 180);
return new Brackets(q => {
return new Brackets((q): any => {
const textParam: any = {};
paramCounter.value++;
textParam['maxLat' + paramCounter.value] = maxLat;
textParam['minLat' + paramCounter.value] = minLat;
textParam['maxLon' + paramCounter.value] = maxLon;
textParam['minLon' + paramCounter.value] = minLon;
if (!(<DistanceSearch>query).negate) {
if (!(query as DistanceSearch).negate) {
q.where(`media.metadata.positionData.GPSData.latitude < :maxLat${paramCounter.value}`, textParam);
q.andWhere(`media.metadata.positionData.GPSData.latitude > :minLat${paramCounter.value}`, textParam);
q.andWhere(`media.metadata.positionData.GPSData.longitude < :maxLon${paramCounter.value}`, textParam);
@@ -277,14 +279,14 @@ export class SearchManager implements ISearchManager {
});
case SearchQueryTypes.from_date:
return new Brackets(q => {
if (typeof (<FromDateSearch>query).value === 'undefined') {
return new Brackets((q): any => {
if (typeof (query as FromDateSearch).value === 'undefined') {
throw new Error('Invalid search query: Date Query should contain from value');
}
const relation = (<TextSearch>query).negate ? '<' : '>=';
const relation = (query as TextSearch).negate ? '<' : '>=';
const textParam: any = {};
textParam['from' + paramCounter.value] = (<FromDateSearch>query).value;
textParam['from' + paramCounter.value] = (query as FromDateSearch).value;
q.where(`media.metadata.creationDate ${relation} :from${paramCounter.value}`, textParam);
@@ -293,14 +295,14 @@ export class SearchManager implements ISearchManager {
});
case SearchQueryTypes.to_date:
return new Brackets(q => {
if (typeof (<ToDateSearch>query).value === 'undefined') {
return new Brackets((q): any => {
if (typeof (query as ToDateSearch).value === 'undefined') {
throw new Error('Invalid search query: Date Query should contain to value');
}
const relation = (<TextSearch>query).negate ? '>' : '<=';
const relation = (query as TextSearch).negate ? '>' : '<=';
const textParam: any = {};
textParam['to' + paramCounter.value] = (<ToDateSearch>query).value;
textParam['to' + paramCounter.value] = (query as ToDateSearch).value;
q.where(`media.metadata.creationDate ${relation} :to${paramCounter.value}`, textParam);
paramCounter.value++;
@@ -308,31 +310,31 @@ export class SearchManager implements ISearchManager {
});
case SearchQueryTypes.min_rating:
return new Brackets(q => {
if (typeof (<MinRatingSearch>query).value === 'undefined') {
return new Brackets((q): any => {
if (typeof (query as MinRatingSearch).value === 'undefined') {
throw new Error('Invalid search query: Rating Query should contain minvalue');
}
const relation = (<TextSearch>query).negate ? '<' : '>=';
const relation = (query as TextSearch).negate ? '<' : '>=';
const textParam: any = {};
textParam['min' + paramCounter.value] = (<MinRatingSearch>query).value;
textParam['min' + paramCounter.value] = (query as MinRatingSearch).value;
q.where(`media.metadata.rating ${relation} :min${paramCounter.value}`, textParam);
paramCounter.value++;
return q;
});
case SearchQueryTypes.max_rating:
return new Brackets(q => {
if (typeof (<MaxRatingSearch>query).value === 'undefined') {
return new Brackets((q): any => {
if (typeof (query as MaxRatingSearch).value === 'undefined') {
throw new Error('Invalid search query: Rating Query should contain max value');
}
const relation = (<TextSearch>query).negate ? '>' : '<=';
const relation = (query as TextSearch).negate ? '>' : '<=';
if (typeof (<MaxRatingSearch>query).value !== 'undefined') {
if (typeof (query as MaxRatingSearch).value !== 'undefined') {
const textParam: any = {};
textParam['max' + paramCounter.value] = (<MaxRatingSearch>query).value;
textParam['max' + paramCounter.value] = (query as MaxRatingSearch).value;
q.where(`media.metadata.rating ${relation} :max${paramCounter.value}`, textParam);
}
paramCounter.value++;
@@ -340,15 +342,15 @@ export class SearchManager implements ISearchManager {
});
case SearchQueryTypes.min_resolution:
return new Brackets(q => {
if (typeof (<MinResolutionSearch>query).value === 'undefined') {
return new Brackets((q): any => {
if (typeof (query as MinResolutionSearch).value === 'undefined') {
throw new Error('Invalid search query: Resolution Query should contain min value');
}
const relation = (<TextSearch>query).negate ? '<' : '>=';
const relation = (query as TextSearch).negate ? '<' : '>=';
const textParam: any = {};
textParam['min' + paramCounter.value] = (<MinResolutionSearch>query).value * 1000 * 1000;
textParam['min' + paramCounter.value] = (query as MinResolutionSearch).value * 1000 * 1000;
q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :min${paramCounter.value}`, textParam);
@@ -357,15 +359,15 @@ export class SearchManager implements ISearchManager {
});
case SearchQueryTypes.max_resolution:
return new Brackets(q => {
if (typeof (<MaxResolutionSearch>query).value === 'undefined') {
return new Brackets((q): any => {
if (typeof (query as MaxResolutionSearch).value === 'undefined') {
throw new Error('Invalid search query: Rating Query should contain min or max value');
}
const relation = (<TextSearch>query).negate ? '>' : '<=';
const relation = (query as TextSearch).negate ? '>' : '<=';
const textParam: any = {};
textParam['max' + paramCounter.value] = (<MaxResolutionSearch>query).value * 1000 * 1000;
textParam['max' + paramCounter.value] = (query as MaxResolutionSearch).value * 1000 * 1000;
q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :max${paramCounter.value}`, textParam);
paramCounter.value++;
@@ -373,8 +375,8 @@ export class SearchManager implements ISearchManager {
});
case SearchQueryTypes.orientation:
return new Brackets(q => {
if ((<OrientationSearch>query).landscape) {
return new Brackets((q): any => {
if ((query as OrientationSearch).landscape) {
q.where('media.metadata.size.width >= media.metadata.size.height');
} else {
q.where('media.metadata.size.width <= media.metadata.size.height');
@@ -391,22 +393,22 @@ export class SearchManager implements ISearchManager {
return new Brackets((q: WhereExpression) => {
const createMatchString = (str: string) => {
return (<TextSearch>query).matchType === TextSearchQueryMatchTypes.exact_match ? str : `%${str}%`;
const createMatchString = (str: string): string => {
return (query as TextSearch).matchType === TextSearchQueryMatchTypes.exact_match ? str : `%${str}%`;
};
const LIKE = (<TextSearch>query).negate ? 'NOT LIKE' : 'LIKE';
const LIKE = (query as TextSearch).negate ? 'NOT LIKE' : 'LIKE';
// if the expression is negated, we use AND instead of OR as nowhere should that match
const whereFN = (<TextSearch>query).negate ? 'andWhere' : 'orWhere';
const whereFNRev = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
const whereFN = (query as TextSearch).negate ? 'andWhere' : 'orWhere';
const whereFNRev = (query as TextSearch).negate ? 'orWhere' : 'andWhere';
const textParam: any = {};
paramCounter.value++;
textParam['text' + paramCounter.value] = createMatchString((<TextSearch>query).text);
textParam['text' + paramCounter.value] = createMatchString((query as TextSearch).text);
if (query.type === SearchQueryTypes.any_text ||
query.type === SearchQueryTypes.directory) {
const dirPathStr = ((<TextSearch>query).text).replace(new RegExp('\\\\', 'g'), '/');
const dirPathStr = ((query as TextSearch).text).replace(new RegExp('\\\\', 'g'), '/');
textParam['fullPath' + paramCounter.value] = createMatchString(dirPathStr);
@@ -414,7 +416,7 @@ export class SearchManager implements ISearchManager {
textParam);
const directoryPath = GalleryManager.parseRelativeDirePath(dirPathStr);
q[whereFN](new Brackets(dq => {
q[whereFN](new Brackets((dq): any => {
textParam['dirName' + paramCounter.value] = createMatchString(directoryPath.name);
dq[whereFNRev](`directory.name ${LIKE} :dirName${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
@@ -447,17 +449,17 @@ export class SearchManager implements ISearchManager {
}
// Matching for array type fields
const matchArrayField = (fieldName: string) => {
q[whereFN](new Brackets(qbr => {
if ((<TextSearch>query).matchType !== TextSearchQueryMatchTypes.exact_match) {
const matchArrayField = (fieldName: string): void => {
q[whereFN](new Brackets((qbr): void => {
if ((query as TextSearch).matchType !== TextSearchQueryMatchTypes.exact_match) {
qbr[whereFN](`${fieldName} ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
} else {
qbr[whereFN](new Brackets(qb => {
textParam['CtextC' + paramCounter.value] = `%,${(<TextSearch>query).text},%`;
textParam['Ctext' + paramCounter.value] = `%,${(<TextSearch>query).text}`;
textParam['textC' + paramCounter.value] = `${(<TextSearch>query).text},%`;
textParam['text_exact' + paramCounter.value] = `${(<TextSearch>query).text}`;
qbr[whereFN](new Brackets((qb): void => {
textParam['CtextC' + paramCounter.value] = `%,${(query as TextSearch).text},%`;
textParam['Ctext' + paramCounter.value] = `%,${(query as TextSearch).text}`;
textParam['textC' + paramCounter.value] = `${(query as TextSearch).text},%`;
textParam['text_exact' + paramCounter.value] = `${(query as TextSearch).text}`;
qb[whereFN](`${fieldName} ${LIKE} :CtextC${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
@@ -469,7 +471,7 @@ export class SearchManager implements ISearchManager {
textParam);
}));
}
if ((<TextSearch>query).negate) {
if ((query as TextSearch).negate) {
qbr.orWhere(`${fieldName} IS NULL`);
}
}));
@@ -491,72 +493,73 @@ export class SearchManager implements ISearchManager {
switch (query.type) {
case SearchQueryTypes.AND:
case SearchQueryTypes.OR:
return <SearchListQuery>{
return {
type: query.type,
list: (<SearchListQuery>query).list.map(q => this.flattenSameOfQueries(q))
};
list: (query as SearchListQuery).list.map((q): SearchQueryDTO => this.flattenSameOfQueries(q))
} as SearchListQuery;
case SearchQueryTypes.SOME_OF:
const someOfQ = <SomeOfSearchQuery>query;
const someOfQ = query as SomeOfSearchQuery;
someOfQ.min = someOfQ.min || 1;
if (someOfQ.min === 1) {
return this.flattenSameOfQueries(<ORSearchQuery>{
return this.flattenSameOfQueries({
type: SearchQueryTypes.OR,
list: (<SearchListQuery>someOfQ).list
});
list: (someOfQ as SearchListQuery).list
} as ORSearchQuery);
}
if (someOfQ.min === (<SearchListQuery>query).list.length) {
return this.flattenSameOfQueries(<ANDSearchQuery>{
if (someOfQ.min === (query as SearchListQuery).list.length) {
return this.flattenSameOfQueries({
type: SearchQueryTypes.AND,
list: (<SearchListQuery>someOfQ).list
});
list: (someOfQ as SearchListQuery).list
} as ANDSearchQuery);
}
const combinations: SearchQueryDTO[][] = Utils.getAnyX(someOfQ.min, (<SearchListQuery>query).list);
const combinations: SearchQueryDTO[][] = Utils.getAnyX(someOfQ.min, (query as SearchListQuery).list);
return this.flattenSameOfQueries(<ORSearchQuery>{
return this.flattenSameOfQueries({
type: SearchQueryTypes.OR,
list: combinations.map(c => <ANDSearchQuery>{
list: combinations.map((c): ANDSearchQuery => ({
type: SearchQueryTypes.AND, list: c
})
});
} as ANDSearchQuery))
} as ORSearchQuery);
}
return query;
}
private encapsulateAutoComplete(values: string[], type: SearchQueryTypes): Array<AutoCompleteItem> {
const res: AutoCompleteItem[] = [];
values.forEach((value) => {
values.forEach((value): void => {
res.push(new AutoCompleteItem(value, type));
});
return res;
}
private async loadMediaWithFaces(query: SelectQueryBuilder<MediaEntity>) {
private async loadMediaWithFaces(query: SelectQueryBuilder<MediaEntity>): Promise<MediaEntity[]> {
const rawAndEntities = await query.orderBy('media.id').getRawAndEntities();
const media: MediaEntity[] = rawAndEntities.entities;
let rawIndex = 0;
for (let i = 0; i < media.length; i++) {
for (const item of media) {
if (rawAndEntities.raw[rawIndex].media_id !== media[i].id) {
if (rawAndEntities.raw[rawIndex].media_id !== item.id) {
throw new Error('index mismatch');
}
// media without a face
if (rawAndEntities.raw[rawIndex].faces_id === null) {
delete media[i].metadata.faces;
delete item.metadata.faces;
rawIndex++;
continue;
}
// process all faces for one media
media[i].metadata.faces = [];
item.metadata.faces = [];
while (rawAndEntities.raw[rawIndex].media_id === media[i].id) {
media[i].metadata.faces.push(<any>FaceRegionEntry.fromRawToDTO(rawAndEntities.raw[rawIndex]));
while (rawAndEntities.raw[rawIndex].media_id === item.id) {
item.metadata.faces.push(FaceRegionEntry.fromRawToDTO(rawAndEntities.raw[rawIndex]) as any);
rawIndex++;
if (rawIndex >= rawAndEntities.raw.length) {
return media;

View File

@@ -20,7 +20,7 @@ export class SharingManager implements ISharingManager {
async deleteSharing(sharingKey: string): Promise<void> {
const connection = await SQLConnection.getConnection();
const sharing = await connection.getRepository(SharingEntity).findOne({sharingKey: sharingKey});
const sharing = await connection.getRepository(SharingEntity).findOne({sharingKey});
await connection.getRepository(SharingEntity).remove(sharing);
}
@@ -52,7 +52,7 @@ export class SharingManager implements ISharingManager {
const sharing = await connection.getRepository(SharingEntity).findOne({
id: inSharing.id,
creator: <any>inSharing.creator.id,
creator: inSharing.creator.id as any,
path: inSharing.path
});

View File

@@ -11,7 +11,7 @@ export class UserManager implements IUserManager {
}
public async findOne(filter: any) {
public async findOne(filter: any): Promise<any> {
const connection = await SQLConnection.getConnection();
const pass = filter.password;
delete filter.password;
@@ -24,34 +24,34 @@ export class UserManager implements IUserManager {
}
public async find(filter: any) {
public async find(filter: any): Promise<any> {
const connection = await SQLConnection.getConnection();
return await connection.getRepository(UserEntity).find(filter);
}
public async createUser(user: UserDTO) {
public async createUser(user: UserDTO): Promise<any> {
const connection = await SQLConnection.getConnection();
user.password = PasswordHelper.cryptPassword(user.password);
return connection.getRepository(UserEntity).save(user);
}
public async deleteUser(id: number) {
public async deleteUser(id: number): Promise<any> {
const connection = await SQLConnection.getConnection();
const user = await connection.getRepository(UserEntity).findOne({id: id});
const user = await connection.getRepository(UserEntity).findOne({id});
return await connection.getRepository(UserEntity).remove(user);
}
public async changeRole(id: number, newRole: UserRoles) {
public async changeRole(id: number, newRole: UserRoles): Promise<any> {
const connection = await SQLConnection.getConnection();
const userRepository = connection.getRepository(UserEntity);
const user = await userRepository.findOne({id: id});
const user = await userRepository.findOne({id});
user.role = newRole;
return userRepository.save(user);
}
public async changePassword(request: any) {
public async changePassword(request: any): Promise<void> {
throw new Error('not implemented'); // TODO: implement
}

View File

@@ -31,7 +31,7 @@ export class VersionManager implements IVersionManager {
return crypto.createHash('md5').update(versionString).digest('hex');
}
async updateDataVersion() {
async updateDataVersion(): Promise<void> {
const connection = await SQLConnection.getConnection();
const dir = await connection.getRepository(DirectoryEntity)
.createQueryBuilder('directory')

View File

@@ -1,15 +1,15 @@
import {Config} from '../../../../../common/config/private/Config';
import {ColumnOptions} from 'typeorm/decorator/options/ColumnOptions';
import {ServerConfig} from '../../../../../common/config/private/PrivateConfig';
import {DatabaseType} from '../../../../../common/config/private/PrivateConfig';
export class ColumnCharsetCS implements ColumnOptions {
public get charset(): string {
return Config.Server.Database.type === ServerConfig.DatabaseType.mysql ? 'utf8' : 'utf8';
return Config.Server.Database.type === DatabaseType.mysql ? 'utf8' : 'utf8';
}
public get collation(): string {
return Config.Server.Database.type === ServerConfig.DatabaseType.mysql ? 'utf8_bin' : null;
return Config.Server.Database.type === DatabaseType.mysql ? 'utf8_bin' : null;
}
}

View File

@@ -34,9 +34,11 @@ export class MediaMetadataEntity implements MediaMetadata {
@Column('int', {unsigned: true})
fileSize: number;
@Column({type: 'simple-array',
@Column({
type: 'simple-array',
charset: columnCharsetCS.charset,
collation: columnCharsetCS.collation})
collation: columnCharsetCS.collation
})
keywords: string[];
@Column(type => CameraMetadataEntity)
@@ -54,9 +56,11 @@ export class MediaMetadataEntity implements MediaMetadata {
@OneToMany(type => FaceRegionEntry, faceRegion => faceRegion.media)
faces: FaceRegionEntry[];
@Column({type: 'simple-array', select: false, nullable: true,
@Column({
type: 'simple-array', select: false, nullable: true,
charset: columnCharsetCS.charset,
collation: columnCharsetCS.collation})
collation: columnCharsetCS.collation
})
persons: string[]; // Caches the list of persons. Only used for searching
@Column('int', {unsigned: true})

View File

@@ -4,9 +4,27 @@ import {NotificationManager} from '../NotifocationManager';
import {SQLConnection} from '../database/sql/SQLConnection';
import * as fs from 'fs';
import {FFmpegFactory} from '../FFmpegFactory';
import {ClientConfig} from '../../../common/config/public/ClientConfig';
import {IPrivateConfig, ServerConfig} from '../../../common/config/private/PrivateConfig';
import MapLayers = ClientConfig.MapLayers;
import {
ClientFacesConfig,
ClientMapConfig,
ClientMetaFileConfig,
ClientPhotoConfig,
ClientRandomPhotoConfig,
ClientSearchConfig,
ClientSharingConfig,
ClientThumbnailConfig,
ClientVideoConfig,
MapLayers,
MapProviders
} from '../../../common/config/public/ClientConfig';
import {
DatabaseType,
IPrivateConfig,
ServerDataBaseConfig,
ServerJobConfig,
ServerPhotoConfig,
ServerVideoConfig
} from '../../../common/config/private/PrivateConfig';
const LOG_TAG = '[ConfigDiagnostics]';
@@ -25,11 +43,11 @@ export class ConfigDiagnostics {
});
}
static async testDatabase(databaseConfig: ServerConfig.DataBaseConfig) {
if (databaseConfig.type !== ServerConfig.DatabaseType.memory) {
static async testDatabase(databaseConfig: ServerDataBaseConfig): Promise<void> {
if (databaseConfig.type !== DatabaseType.memory) {
await SQLConnection.tryConnection(databaseConfig);
}
if (databaseConfig.type === ServerConfig.DatabaseType.sqlite) {
if (databaseConfig.type === DatabaseType.sqlite) {
try {
await this.checkReadWritePermission(SQLConnection.getSQLiteDB(databaseConfig));
} catch (e) {
@@ -39,7 +57,7 @@ export class ConfigDiagnostics {
}
static async testMetaFileConfig(metaFileConfig: ClientConfig.MetaFileConfig, config: IPrivateConfig) {
static async testMetaFileConfig(metaFileConfig: ClientMetaFileConfig, config: IPrivateConfig): Promise<void> {
// TODO: now we have metadata for pg2conf files too not only gpx that also runs without map
if (metaFileConfig.enabled === true &&
config.Client.Map.enabled === false) {
@@ -48,7 +66,7 @@ export class ConfigDiagnostics {
}
static testClientVideoConfig(videoConfig: ClientConfig.VideoConfig): Promise<void> {
static testClientVideoConfig(videoConfig: ClientVideoConfig): Promise<void> {
return new Promise((resolve, reject) => {
try {
if (videoConfig.enabled === true) {
@@ -73,7 +91,7 @@ export class ConfigDiagnostics {
});
}
static async testServerVideoConfig(videoConfig: ServerConfig.VideoConfig, config: IPrivateConfig) {
static async testServerVideoConfig(videoConfig: ServerVideoConfig, config: IPrivateConfig): Promise<void> {
if (config.Client.Media.Video.enabled === true) {
if (videoConfig.transcoding.fps <= 0) {
throw new Error('fps should be grater than 0');
@@ -81,13 +99,13 @@ export class ConfigDiagnostics {
}
}
static async testSharp() {
static async testSharp(): Promise<void> {
const sharp = require('sharp');
sharp();
}
static async testTempFolder(folder: string) {
static async testTempFolder(folder: string): Promise<void> {
await this.checkReadWritePermission(folder);
}
@@ -106,21 +124,22 @@ export class ConfigDiagnostics {
}
static async testServerPhotoConfig(server: ServerConfig.PhotoConfig) {
static async testServerPhotoConfig(server: ServerPhotoConfig): Promise<void> {
}
static async testClientPhotoConfig(client: ClientConfig.PhotoConfig) {
static async testClientPhotoConfig(client: ClientPhotoConfig): Promise<void> {
}
public static async testServerThumbnailConfig(server: ServerConfig.ThumbnailConfig) {
// @ts-ignore
public static async testServerThumbnailConfig(server: ServerThumbnailConfig): Promise<void> {
if (server.personFaceMargin < 0 || server.personFaceMargin > 1) {
throw new Error('personFaceMargin should be between 0 and 1');
}
}
static async testClientThumbnailConfig(thumbnailConfig: ClientConfig.ThumbnailConfig) {
static async testClientThumbnailConfig(thumbnailConfig: ClientThumbnailConfig): Promise<void> {
if (isNaN(thumbnailConfig.iconSize) || thumbnailConfig.iconSize <= 0) {
throw new Error('IconSize has to be >= 0 integer, got: ' + thumbnailConfig.iconSize);
}
@@ -128,21 +147,21 @@ export class ConfigDiagnostics {
if (!thumbnailConfig.thumbnailSizes.length) {
throw new Error('At least one thumbnail size is needed');
}
for (let i = 0; i < thumbnailConfig.thumbnailSizes.length; i++) {
if (isNaN(thumbnailConfig.thumbnailSizes[i]) || thumbnailConfig.thumbnailSizes[i] <= 0) {
throw new Error('Thumbnail size has to be >= 0 integer, got: ' + thumbnailConfig.thumbnailSizes[i]);
for (const item of thumbnailConfig.thumbnailSizes) {
if (isNaN(item) || item <= 0) {
throw new Error('Thumbnail size has to be >= 0 integer, got: ' + item);
}
}
}
static async testTasksConfig(task: ServerConfig.JobConfig, config: IPrivateConfig) {
static async testTasksConfig(task: ServerJobConfig, config: IPrivateConfig): Promise<void> {
}
static async testFacesConfig(faces: ClientConfig.FacesConfig, config: IPrivateConfig) {
static async testFacesConfig(faces: ClientFacesConfig, config: IPrivateConfig): Promise<void> {
if (faces.enabled === true) {
if (config.Server.Database.type === ServerConfig.DatabaseType.memory) {
if (config.Server.Database.type === DatabaseType.memory) {
throw new Error('Memory Database do not support faces');
}
if (config.Client.Search.enabled === false) {
@@ -151,17 +170,17 @@ export class ConfigDiagnostics {
}
}
static async testSearchConfig(search: ClientConfig.SearchConfig, config: IPrivateConfig) {
static async testSearchConfig(search: ClientSearchConfig, config: IPrivateConfig): Promise<void> {
if (search.enabled === true &&
config.Server.Database.type === ServerConfig.DatabaseType.memory) {
config.Server.Database.type === DatabaseType.memory) {
throw new Error('Memory Database do not support searching');
}
}
static async testSharingConfig(sharing: ClientConfig.SharingConfig, config: IPrivateConfig) {
static async testSharingConfig(sharing: ClientSharingConfig, config: IPrivateConfig): Promise<void> {
if (sharing.enabled === true &&
config.Server.Database.type === ServerConfig.DatabaseType.memory) {
config.Server.Database.type === DatabaseType.memory) {
throw new Error('Memory Database do not support sharing');
}
if (sharing.enabled === true &&
@@ -170,27 +189,27 @@ export class ConfigDiagnostics {
}
}
static async testRandomPhotoConfig(sharing: ClientConfig.RandomPhotoConfig, config: IPrivateConfig) {
static async testRandomPhotoConfig(sharing: ClientRandomPhotoConfig, config: IPrivateConfig): Promise<void> {
if (sharing.enabled === true &&
config.Server.Database.type === ServerConfig.DatabaseType.memory) {
config.Server.Database.type === DatabaseType.memory) {
throw new Error('Memory Database do not support random photo');
}
}
static async testMapConfig(map: ClientConfig.MapConfig): Promise<void> {
static async testMapConfig(map: ClientMapConfig): Promise<void> {
if (map.enabled === false) {
return;
}
if (map.mapProvider === ClientConfig.MapProviders.Mapbox &&
if (map.mapProvider === MapProviders.Mapbox &&
(!map.mapboxAccessToken || map.mapboxAccessToken.length === 0)) {
throw new Error('Mapbox needs a valid api key.');
}
if (map.mapProvider === ClientConfig.MapProviders.Custom &&
if (map.mapProvider === MapProviders.Custom &&
(!map.customLayers || map.customLayers.length === 0)) {
throw new Error('Custom maps need at least one valid layer');
}
if (map.mapProvider === ClientConfig.MapProviders.Custom) {
if (map.mapProvider === MapProviders.Custom) {
map.customLayers.forEach((l: MapLayers) => {
if (!l.url || l.url.length === 0) {
throw new Error('Custom maps url need to be a valid layer');
@@ -200,9 +219,9 @@ export class ConfigDiagnostics {
}
static async runDiagnostics() {
static async runDiagnostics(): Promise<void> {
if (Config.Server.Database.type !== ServerConfig.DatabaseType.memory) {
if (Config.Server.Database.type !== DatabaseType.memory) {
try {
await ConfigDiagnostics.testDatabase(Config.Server.Database);
} catch (ex) {
@@ -331,7 +350,7 @@ export class ConfigDiagnostics {
'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());
Config.Client.Map.mapProvider = ClientConfig.MapProviders.OpenStreetMap;
Config.Client.Map.mapProvider = MapProviders.OpenStreetMap;
}
}

View File

@@ -4,12 +4,10 @@ import * as os from 'os';
import * as crypto from 'crypto';
import {ProjectPath} from '../../ProjectPath';
import {Config} from '../../../common/config/private/Config';
import {ThumbnailTH} from '../threading/ThreadPool';
import {PhotoWorker, RendererInput, ThumbnailSourceType} from '../threading/PhotoWorker';
import {ITaskExecuter, TaskExecuter} from '../threading/TaskExecuter';
import {FaceRegion, PhotoDTO} from '../../../common/entities/PhotoDTO';
import {SupportedFormats} from '../../../common/SupportedFormats';
import {ServerConfig} from '../../../common/config/private/PrivateConfig';
import {PersonWithSampleRegion} from '../../../common/entities/PersonDTO';
@@ -18,7 +16,7 @@ export class PhotoProcessing {
private static initDone = false;
private static taskQue: ITaskExecuter<RendererInput, void> = null;
public static init() {
public static init(): void {
if (this.initDone === true) {
return;
}
@@ -36,13 +34,13 @@ export class PhotoProcessing {
this.taskQue = new TaskExecuter(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations,
(input => PhotoWorker.render(input)));
((input): Promise<void> => PhotoWorker.render(input)));
this.initDone = true;
}
public static async generatePersonThumbnail(person: PersonWithSampleRegion) {
public static async generatePersonThumbnail(person: PersonWithSampleRegion): Promise<string> {
// load parameters
const photo: PhotoDTO = person.sampleRegion.media;
@@ -67,10 +65,10 @@ export class PhotoProcessing {
// run on other thread
const input = <RendererInput>{
const input = {
type: ThumbnailSourceType.Photo,
mediaPath: mediaPath,
size: size,
mediaPath,
size,
outPath: thPath,
makeSquare: false,
cut: {
@@ -80,7 +78,7 @@ export class PhotoProcessing {
height: person.sampleRegion.box.height + margin.y
},
qualityPriority: Config.Server.Media.Thumbnail.qualityPriority
};
} as RendererInput;
input.cut.width = Math.min(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);
@@ -131,7 +129,7 @@ export class PhotoProcessing {
}
public static async convertPhoto(mediaPath: string) {
public static async convertPhoto(mediaPath: string): Promise<string> {
return this.generateThumbnail(mediaPath,
Config.Server.Media.Photo.Converting.resolution,
ThumbnailSourceType.Photo,
@@ -139,7 +137,7 @@ export class PhotoProcessing {
}
static async convertedPhotoExist(mediaPath: string, size: number) {
static async convertedPhotoExist(mediaPath: string, size: number): Promise<boolean> {
// generate thumbnail path
const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size);
@@ -171,14 +169,14 @@ export class PhotoProcessing {
// run on other thread
const input = <RendererInput>{
const input = {
type: sourceType,
mediaPath: mediaPath,
size: size,
outPath: outPath,
makeSquare: makeSquare,
mediaPath,
size,
outPath,
makeSquare,
qualityPriority: Config.Server.Media.Thumbnail.qualityPriority
};
} as RendererInput;
const outDir = path.dirname(input.outPath);
@@ -187,7 +185,7 @@ export class PhotoProcessing {
return outPath;
}
public static isPhoto(fullPath: string) {
public static isPhoto(fullPath: string): boolean {
const extension = path.extname(fullPath).toLowerCase();
return SupportedFormats.WithDots.Photos.indexOf(extension) !== -1;
}

View File

@@ -10,7 +10,7 @@ import {SupportedFormats} from '../../../common/SupportedFormats';
export class VideoProcessing {
private static taskQue: ITaskExecuter<VideoConverterInput, void> =
new TaskExecuter(1, (input => VideoConverterWorker.convert(input)));
new TaskExecuter(1, ((input): Promise<void> => VideoConverterWorker.convert(input)));
public static generateConvertedFilePath(videoPath: string): string {
return path.join(ProjectPath.TranscodedFolder,
@@ -67,7 +67,7 @@ export class VideoProcessing {
const metaData = await MetadataLoader.loadVideoMetadata(videoPath);
const renderInput: VideoConverterInput = {
videoPath: videoPath,
videoPath,
output: {
path: outPath,
codec: Config.Server.Media.Video.transcoding.codec,
@@ -96,7 +96,7 @@ export class VideoProcessing {
}
public static isVideo(fullPath: string) {
public static isVideo(fullPath: string): boolean {
const extension = path.extname(fullPath).toLowerCase();
return SupportedFormats.WithDots.Videos.indexOf(extension) !== -1;
}

View File

@@ -3,7 +3,7 @@ import {JobProgressDTO, JobProgressStates} from '../../../common/entities/job/Jo
import {IJob} from './jobs/IJob';
import {JobRepository} from './JobRepository';
import {Config} from '../../../common/config/private/Config';
import {AfterJobTrigger, JobScheduleDTO, JobTriggerType} from '../../../common/entities/job/JobScheduleDTO';
import {AfterJobTrigger, JobScheduleDTO, JobScheduleDTOUtils, JobTriggerType} from '../../../common/entities/job/JobScheduleDTO';
import {Logger} from '../../Logger';
import {NotificationManager} from '../NotifocationManager';
import {IJobListener} from './jobs/IJobListener';
@@ -23,12 +23,12 @@ export class JobManager implements IJobManager, IJobListener {
}
protected get JobRunning(): boolean {
return JobRepository.Instance.getAvailableJobs().findIndex(j => j.InProgress === true) !== -1;
return JobRepository.Instance.getAvailableJobs().findIndex((j): boolean => j.InProgress === true) !== -1;
}
protected get JobNoParallelRunning(): boolean {
return JobRepository.Instance.getAvailableJobs()
.findIndex(j => j.InProgress === true && j.allowParallelRun) !== -1;
.findIndex((j): boolean => j.InProgress === true && j.allowParallelRun) !== -1;
}
getProgresses(): { [id: string]: JobProgressDTO } {
@@ -67,15 +67,15 @@ export class JobManager implements IJobManager, IJobListener {
if (state !== JobProgressStates.finished || soloRun === true) {
return;
}
const sch = Config.Server.Jobs.scheduled.find(s => s.jobName === job.Name);
const sch = Config.Server.Jobs.scheduled.find((s): boolean => s.jobName === job.Name);
if (sch) {
const children = Config.Server.Jobs.scheduled.filter(s => s.trigger.type === JobTriggerType.after &&
(<AfterJobTrigger>s.trigger).afterScheduleName === sch.name);
for (let i = 0; i < children.length; ++i) {
const children = Config.Server.Jobs.scheduled.filter((s): boolean => s.trigger.type === JobTriggerType.after &&
(s.trigger as AfterJobTrigger).afterScheduleName === sch.name);
for (const item of children) {
try {
await this.run(children[i].jobName, children[i].config, false, children[i].allowParallelRun);
await this.run(item.jobName, item.config, false, item.allowParallelRun);
} catch (e) {
NotificationManager.warning('Job running error:' + children[i].name, e.toString());
NotificationManager.warning('Job running error:' + item.name, e.toString());
}
}
}
@@ -86,32 +86,32 @@ export class JobManager implements IJobManager, IJobListener {
}
public stopSchedules(): void {
this.timers.forEach(t => clearTimeout(t.timer));
this.timers.forEach((t): void => clearTimeout(t.timer));
this.timers = [];
}
public runSchedules(): void {
this.stopSchedules();
Logger.info(LOG_TAG, 'Running job schedules');
Config.Server.Jobs.scheduled.forEach(s => this.runSchedule(s));
Config.Server.Jobs.scheduled.forEach((s): void => this.runSchedule(s));
}
protected findJob<T = any>(jobName: string): IJob<T> {
return this.getAvailableJobs().find(t => t.Name === jobName);
return this.getAvailableJobs().find((t): boolean => t.Name === jobName);
}
private runSchedule(schedule: JobScheduleDTO) {
const nextDate = JobScheduleDTO.getNextRunningDate(new Date(), schedule);
private runSchedule(schedule: JobScheduleDTO): void {
const nextDate = JobScheduleDTOUtils.getNextRunningDate(new Date(), schedule);
if (nextDate && nextDate.getTime() > Date.now()) {
Logger.debug(LOG_TAG, 'running schedule: ' + schedule.jobName +
' at ' + nextDate.toLocaleString(undefined, {hour12: false}));
const timer: NodeJS.Timeout = setTimeout(async () => {
this.timers = this.timers.filter(t => t.timer !== timer);
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);
this.runSchedule(schedule);
}, nextDate.getTime() - Date.now());
this.timers.push({schedule: schedule, timer: timer});
this.timers.push({schedule, timer});
} else {
Logger.debug(LOG_TAG, 'skipping schedule:' + schedule.jobName);

View File

@@ -33,12 +33,12 @@ export class JobProgressManager {
}
onJobProgressUpdate(progress: JobProgressDTO) {
this.db.progresses[progress.HashName] = {progress: progress, timestamp: Date.now()};
onJobProgressUpdate(progress: JobProgressDTO): void {
this.db.progresses[progress.HashName] = {progress, timestamp: Date.now()};
this.delayedSave();
}
private async loadDB() {
private async loadDB(): Promise<void> {
try {
await fsp.access(this.dbPath);
} catch (e) {
@@ -69,15 +69,15 @@ export class JobProgressManager {
}
}
private async saveDB() {
private async saveDB(): Promise<void> {
await fsp.writeFile(this.dbPath, JSON.stringify(this.db));
}
private delayedSave() {
private delayedSave(): void {
if (this.timer !== null) {
return;
}
this.timer = setTimeout(async () => {
this.timer = setTimeout(async (): Promise<void> => {
this.saveDB().catch(console.error);
this.timer = null;
}, 5000);

View File

@@ -2,7 +2,7 @@ import {ObjectManagers} from '../../ObjectManagers';
import {Config} from '../../../../common/config/private/Config';
import {ConfigTemplateEntry, DefaultsJobs} from '../../../../common/entities/job/JobDTO';
import {Job} from './Job';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import {DatabaseType} from '../../../../common/config/private/PrivateConfig';
export class DBRestJob extends Job {
@@ -11,10 +11,10 @@ export class DBRestJob extends Job {
protected readonly IsInstant = true;
public get Supported(): boolean {
return Config.Server.Database.type !== ServerConfig.DatabaseType.memory;
return Config.Server.Database.type !== DatabaseType.memory;
}
protected async init() {
protected async init(): Promise<void> {
}
protected async step(): Promise<boolean> {

View File

@@ -2,7 +2,7 @@ import {ConfigTemplateEntry} from '../../../../common/entities/job/JobDTO';
import {Job} from './Job';
import * as path from 'path';
import {DiskManager} from '../../DiskManger';
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
import {DirectoryScanSettings} from '../../threading/DiskMangerWorker';
import {Logger} from '../../../Logger';
import {Config} from '../../../../common/config/private/Config';
import {FileDTO} from '../../../../common/entities/FileDTO';
@@ -12,7 +12,7 @@ import {PhotoEntity} from '../../database/sql/enitites/PhotoEntity';
import {VideoEntity} from '../../database/sql/enitites/VideoEntity';
import {backendTexts} from '../../../../common/BackendTexts';
import {ProjectPath} from '../../../ProjectPath';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import {DatabaseType} from '../../../../common/config/private/PrivateConfig';
declare var global: NodeJS.Global;
@@ -26,10 +26,10 @@ export abstract class FileJob<S extends { indexedOnly: boolean } = { indexedOnly
fileQueue: string[] = [];
protected constructor(private scanFilter: DiskMangerWorker.DirectoryScanSettings) {
protected constructor(private scanFilter: DirectoryScanSettings) {
super();
this.scanFilter.noChildDirPhotos = true;
if (Config.Server.Database.type !== ServerConfig.DatabaseType.memory) {
if (Config.Server.Database.type !== DatabaseType.memory) {
this.ConfigTemplate.push({
id: 'indexedOnly',
type: 'boolean',
@@ -40,7 +40,7 @@ export abstract class FileJob<S extends { indexedOnly: boolean } = { indexedOnly
}
}
protected async init() {
protected async init(): Promise<void> {
this.directoryQueue = [];
this.fileQueue = [];
this.directoryQueue.push('/');
@@ -67,7 +67,7 @@ export abstract class FileJob<S extends { indexedOnly: boolean } = { indexedOnly
if (this.directoryQueue.length > 0) {
if (this.config.indexedOnly === true &&
Config.Server.Database.type !== ServerConfig.DatabaseType.memory) {
Config.Server.Database.type !== DatabaseType.memory) {
await this.loadAllMediaFilesFromDB();
this.directoryQueue = [];
} else {
@@ -94,12 +94,12 @@ export abstract class FileJob<S extends { indexedOnly: boolean } = { indexedOnly
return true;
}
private async loadADirectoryFromDisk() {
private async loadADirectoryFromDisk(): Promise<void> {
const directory = this.directoryQueue.shift();
this.Progress.log('scanning directory: ' + directory);
const scanned = await DiskManager.scanDirectoryNoMetadata(directory, this.scanFilter);
for (let i = 0; i < scanned.directories.length; i++) {
this.directoryQueue.push(path.join(scanned.directories[i].path, scanned.directories[i].name));
for (const item of scanned.directories) {
this.directoryQueue.push(path.join(item.path, item.name));
}
if (this.scanFilter.noPhoto !== true || this.scanFilter.noVideo !== true) {
this.fileQueue.push(...(await this.filterMediaFiles(scanned.media))
@@ -111,7 +111,7 @@ export abstract class FileJob<S extends { indexedOnly: boolean } = { indexedOnly
}
}
private async loadAllMediaFilesFromDB() {
private async loadAllMediaFilesFromDB(): Promise<void> {
if (this.scanFilter.noVideo === true && this.scanFilter.noPhoto === true) {
return;

View File

@@ -4,7 +4,7 @@ import {Config} from '../../../../common/config/private/Config';
import {Job} from './Job';
import {ConfigTemplateEntry, DefaultsJobs} from '../../../../common/entities/job/JobDTO';
import {JobProgressStates} from '../../../../common/entities/job/JobProgressDTO';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import {DatabaseType, ServerConfig} from '../../../../common/config/private/PrivateConfig';
export class IndexingJob extends Job {
@@ -13,11 +13,11 @@ export class IndexingJob extends Job {
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
public get Supported(): boolean {
return Config.Server.Database.type !== ServerConfig.DatabaseType.memory;
return Config.Server.Database.type !== DatabaseType.memory;
}
protected async init() {
protected async init(): Promise<void> {
this.directoriesToIndex.push('/');
}
@@ -36,8 +36,8 @@ export class IndexingJob extends Job {
return false;
}
this.Progress.Processed++;
for (let i = 0; i < scanned.directories.length; i++) {
this.directoriesToIndex.push(path.join(scanned.directories[i].path, scanned.directories[i].name));
for (const item of scanned.directories) {
this.directoriesToIndex.push(path.join(item.path, item.name));
}
return true;
}

View File

@@ -1,6 +1,6 @@
import {Logger} from '../../../Logger';
import {IJob} from './IJob';
import {ConfigTemplateEntry, JobDTO} from '../../../../common/entities/job/JobDTO';
import {ConfigTemplateEntry, JobDTO, JobDTOUtils} from '../../../../common/entities/job/JobDTO';
import {JobProgress} from './JobProgress';
import {IJobListener} from './IJobListener';
import {JobProgressStates} from '../../../../common/entities/job/JobProgressDTO';
@@ -45,9 +45,9 @@ export abstract class Job<T = void> implements IJob<T> {
this.soloRun = soloRun;
this.allowParallelRun = allowParallelRun;
this.config = config;
this.progress = new JobProgress(this.Name, JobDTO.getHashName(this.Name, this.config));
this.progress = new JobProgress(this.Name, JobDTOUtils.getHashName(this.Name, this.config));
this.progress.OnChange = this.jobListener.onProgressUpdate;
const pr = new Promise<void>((resolve) => {
const pr = new Promise<void>((resolve): void => {
this.prResolve = resolve;
});
this.init().catch(console.error);
@@ -103,8 +103,8 @@ export abstract class Job<T = void> implements IJob<T> {
this.jobListener.onJobFinished(this, finishState, this.soloRun);
}
private run() {
process.nextTick(async () => {
private run(): void {
process.nextTick(async (): Promise<void> => {
try {
if (this.Progress == null || this.Progress.State !== JobProgressStates.running) {
this.onFinish();

View File

@@ -9,8 +9,8 @@ export class JobProgress {
};
private state = JobProgressStates.running;
private time = {
start: <number>Date.now(),
end: <number>null,
start: Date.now() as number,
end: null as number,
};
private logCounter = 0;
private logs: { id: number, timestamp: string, comment: string }[] = [];
@@ -77,10 +77,10 @@ export class JobProgress {
return this.logs;
}
onChange = (progress: JobProgress) => {
onChange = (progress: JobProgress): void => {
};
log(log: string) {
log(log: string): void {
while (this.logs.length > 10) {
this.logs.shift();
}

View File

@@ -15,7 +15,7 @@ export class TempFolderCleaningJob extends Job {
private tempRootCleaned = false;
protected async init() {
protected async init(): Promise<void> {
this.tempRootCleaned = false;
this.directoryQueue = [];
this.directoryQueue.push(ProjectPath.TranscodedFolder);
@@ -49,20 +49,20 @@ export class TempFolderCleaningJob extends Job {
return (await fs.promises.readdir(dirPath)).map(f => path.normalize(path.join(dirPath, f)));
}
protected async stepTempDirectory() {
protected async stepTempDirectory(): Promise<boolean> {
const files = await this.readDir(ProjectPath.TempFolder);
const validFiles = [ProjectPath.TranscodedFolder, ProjectPath.FacesFolder];
for (let i = 0; i < files.length; ++i) {
if (validFiles.indexOf(files[i]) === -1) {
this.Progress.log('processing: ' + files[i]);
for (const file of files) {
if (validFiles.indexOf(file) === -1) {
this.Progress.log('processing: ' + file);
this.Progress.Processed++;
if ((await fs.promises.stat(files[i])).isDirectory()) {
await fs.promises.rmdir(files[i], {recursive: true});
if ((await fs.promises.stat(file)).isDirectory()) {
await fs.promises.rmdir(file, {recursive: true});
} else {
await fs.promises.unlink(files[i]);
await fs.promises.unlink(file);
}
} else {
this.Progress.log('skipping: ' + files[i]);
this.Progress.log('skipping: ' + file);
this.Progress.Skipped++;
}
}
@@ -73,7 +73,7 @@ export class TempFolderCleaningJob extends Job {
}
protected async stepConvertedDirectory() {
protected async stepConvertedDirectory(): Promise<boolean> {
const filePath = this.directoryQueue.shift();
const stat = await fs.promises.stat(filePath);

View File

@@ -3,7 +3,7 @@ import {DefaultsJobs} from '../../../../common/entities/job/JobDTO';
import {FileJob} from './FileJob';
import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing';
import {ThumbnailSourceType} from '../../threading/PhotoWorker';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {MediaDTO, MediaDTOUtils} from '../../../../common/entities/MediaDTO';
import {FileDTO} from '../../../../common/entities/FileDTO';
import {backendTexts} from '../../../../common/BackendTexts';
@@ -28,9 +28,9 @@ export class ThumbnailGenerationJob extends FileJob<{ sizes: number[], indexedOn
}
start(config: { sizes: number[], indexedOnly: boolean }, soloRun = false, allowParallelRun = false): Promise<void> {
for (let i = 0; i < config.sizes.length; ++i) {
if (Config.Client.Media.Thumbnail.thumbnailSizes.indexOf(config.sizes[i]) === -1) {
throw new Error('unknown thumbnails size: ' + config.sizes[i] + '. Add it to the possible thumbnail sizes.');
for (const item of config.sizes) {
if (Config.Client.Media.Thumbnail.thumbnailSizes.indexOf(item) === -1) {
throw new Error('unknown thumbnails size: ' + item + '. Add it to the possible thumbnail sizes.');
}
}
@@ -46,18 +46,18 @@ export class ThumbnailGenerationJob extends FileJob<{ sizes: number[], indexedOn
}
protected async shouldProcess(mPath: string): Promise<boolean> {
for (let i = 0; i < this.config.sizes.length; ++i) {
if (!(await PhotoProcessing.convertedPhotoExist(mPath, this.config.sizes[i]))) {
for (const item of this.config.sizes) {
if (!(await PhotoProcessing.convertedPhotoExist(mPath, item))) {
return true;
}
}
}
protected async processFile(mPath: string): Promise<void> {
for (let i = 0; i < this.config.sizes.length; ++i) {
for (const item of this.config.sizes) {
await PhotoProcessing.generateThumbnail(mPath,
this.config.sizes[i],
MediaDTO.isVideoPath(mPath) ? ThumbnailSourceType.Video : ThumbnailSourceType.Photo,
item,
MediaDTOUtils.isVideoPath(mPath) ? ThumbnailSourceType.Video : ThumbnailSourceType.Photo,
false);
}

View File

@@ -17,7 +17,7 @@ import {Utils} from '../../../common/Utils';
export class DiskMangerWorker {
public static calcLastModified(stat: Stats) {
public static calcLastModified(stat: Stats): number {
return Math.max(stat.ctime.getTime(), stat.mtime.getTime());
}
@@ -34,14 +34,14 @@ export class DiskMangerWorker {
return path.join(this.normalizeDirPath(path.join(parent.path, parent.name)), path.sep);
}
public static dirName(name: string) {
public static dirName(name: string): any {
if (name.trim().length === 0) {
return '.';
}
return path.basename(name);
}
public static async excludeDir(name: string, relativeDirectoryName: string, absoluteDirectoryName: string) {
public static async excludeDir(name: string, relativeDirectoryName: string, absoluteDirectoryName: string): Promise<boolean> {
if (Config.Server.Indexing.excludeFolderList.length === 0 &&
Config.Server.Indexing.excludeFileList.length === 0) {
return false;
@@ -49,9 +49,7 @@ export class DiskMangerWorker {
const absoluteName = path.normalize(path.join(absoluteDirectoryName, name));
const relativeName = path.normalize(path.join(relativeDirectoryName, name));
for (let j = 0; j < Config.Server.Indexing.excludeFolderList.length; j++) {
const exclude = Config.Server.Indexing.excludeFolderList[j];
for (const exclude of Config.Server.Indexing.excludeFolderList) {
if (exclude.startsWith('/')) {
if (exclude === absoluteName) {
return true;
@@ -67,9 +65,7 @@ export class DiskMangerWorker {
}
}
// exclude dirs that have the given files (like .ignore)
for (let j = 0; j < Config.Server.Indexing.excludeFileList.length; j++) {
const exclude = Config.Server.Indexing.excludeFileList[j];
for (const exclude of Config.Server.Indexing.excludeFileList) {
try {
await fsp.access(path.join(absoluteName, exclude));
return true;
@@ -81,13 +77,13 @@ export class DiskMangerWorker {
}
public static async scanDirectoryNoMetadata(relativeDirectoryName: string,
settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise<DirectoryDTO<FileDTO>> {
settings: DirectoryScanSettings = {}): Promise<DirectoryDTO<FileDTO>> {
settings.noMetadata = true;
return this.scanDirectory(relativeDirectoryName, settings);
}
public static async scanDirectory(relativeDirectoryName: string,
settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise<DirectoryDTO> {
settings: DirectoryScanSettings = {}): Promise<DirectoryDTO> {
relativeDirectoryName = this.normalizeDirPath(relativeDirectoryName);
const directoryName = DiskMangerWorker.dirName(relativeDirectoryName);
@@ -115,8 +111,7 @@ export class DiskMangerWorker {
return directory;
}
const list = await fsp.readdir(absoluteDirectoryName);
for (let i = 0; i < list.length; i++) {
const file = list[i];
for (const file of list) {
const fullFilePath = path.normalize(path.join(absoluteDirectoryName, file));
if ((await fsp.stat(fullFilePath)).isDirectory()) {
if (settings.noDirectory === true || settings.previewOnly === true ||
@@ -141,11 +136,11 @@ export class DiskMangerWorker {
continue;
}
const photo = <PhotoDTO>{
const photo = {
name: file,
directory: null,
metadata: settings.noMetadata === true ? null : await MetadataLoader.loadPhotoMetadata(fullFilePath)
};
} as PhotoDTO;
if (!directory.preview) {
directory.preview = Utils.clone(photo);
@@ -169,11 +164,11 @@ export class DiskMangerWorker {
continue;
}
try {
directory.media.push(<VideoDTO>{
directory.media.push({
name: file,
directory: null,
metadata: settings.noMetadata === true ? null : await MetadataLoader.loadVideoMetadata(fullFilePath)
});
} as VideoDTO);
} catch (e) {
Logger.warn('Media loading error, skipping: ' + file + ', reason: ' + e.toString());
}
@@ -183,10 +178,10 @@ export class DiskMangerWorker {
continue;
}
directory.metaFile.push(<FileDTO>{
directory.metaFile.push({
name: file,
directory: null,
});
} as FileDTO);
}
}
@@ -197,21 +192,19 @@ export class DiskMangerWorker {
}
private static isMetaFile(fullPath: string) {
private static isMetaFile(fullPath: string): boolean {
const extension = path.extname(fullPath).toLowerCase();
return SupportedFormats.WithDots.MetaFiles.indexOf(extension) !== -1;
}
}
export namespace DiskMangerWorker {
export interface DirectoryScanSettings {
previewOnly?: boolean;
noMetaFile?: boolean;
noVideo?: boolean;
noPhoto?: boolean;
noDirectory?: boolean;
noMetadata?: boolean;
noChildDirPhotos?: boolean;
}
export interface DirectoryScanSettings {
previewOnly?: boolean;
noMetaFile?: boolean;
noVideo?: boolean;
noPhoto?: boolean;
noDirectory?: boolean;
noMetadata?: boolean;
noChildDirPhotos?: boolean;
}

View File

@@ -45,30 +45,30 @@ export class MetadataLoader {
try {
for (let i = 0; i < data.streams.length; i++) {
if (data.streams[i].width) {
metadata.size.width = data.streams[i].width;
metadata.size.height = data.streams[i].height;
for (const stream of data.streams) {
if (stream.width) {
metadata.size.width = stream.width;
metadata.size.height = stream.height;
if (Utils.isInt32(parseInt('' + data.streams[i].rotation, 10)) &&
(Math.abs(parseInt('' + data.streams[i].rotation, 10)) / 90) % 2 === 1) {
if (Utils.isInt32(parseInt('' + stream.rotation, 10)) &&
(Math.abs(parseInt('' + stream.rotation, 10)) / 90) % 2 === 1) {
// noinspection JSSuspiciousNameCombination
metadata.size.width = data.streams[i].height;
metadata.size.width = stream.height;
// noinspection JSSuspiciousNameCombination
metadata.size.height = data.streams[i].width;
metadata.size.height = stream.width;
}
if (Utils.isInt32(Math.floor(parseFloat(data.streams[i].duration) * 1000))) {
metadata.duration = Math.floor(parseFloat(data.streams[i].duration) * 1000);
if (Utils.isInt32(Math.floor(parseFloat(stream.duration) * 1000))) {
metadata.duration = Math.floor(parseFloat(stream.duration) * 1000);
}
if (Utils.isInt32(parseInt(data.streams[i].bit_rate, 10))) {
metadata.bitRate = parseInt(data.streams[i].bit_rate, 10) || null;
if (Utils.isInt32(parseInt(stream.bit_rate, 10))) {
metadata.bitRate = parseInt(stream.bit_rate, 10) || null;
}
if (Utils.isInt32(parseInt(data.streams[i].avg_frame_rate, 10))) {
metadata.fps = parseInt(data.streams[i].avg_frame_rate, 10) || null;
if (Utils.isInt32(parseInt(stream.avg_frame_rate, 10))) {
metadata.fps = parseInt(stream.avg_frame_rate, 10) || null;
}
metadata.creationDate = Date.parse(data.streams[i].tags.creation_time) || metadata.creationDate;
metadata.creationDate = Date.parse(stream.tags.creation_time) || metadata.creationDate;
break;
}
}
@@ -201,7 +201,7 @@ export class MetadataLoader {
}
metadata.keywords = iptcData.keywords || [];
metadata.creationDate = <number>(iptcData.date_time ? iptcData.date_time.getTime() : metadata.creationDate);
metadata.creationDate = ((iptcData.date_time ? iptcData.date_time.getTime() : metadata.creationDate) as number);
} catch (err) {
// Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err);
@@ -214,11 +214,11 @@ export class MetadataLoader {
// and keep the minimum amount only
const exif = ExifReader.load(data);
if (exif.Rating) {
metadata.rating = <any>parseInt(exif.Rating.value, 10);
metadata.rating = (parseInt(exif.Rating.value, 10) as any);
}
if (exif.Orientation) {
metadata.orientation = <any>parseInt(<any>exif.Orientation.value, 10);
metadata.orientation = (parseInt(exif.Orientation.value as any, 10) as any);
if (OrientationTypes.BOTTOM_LEFT < metadata.orientation) {
// noinspection JSSuspiciousNameCombination
const height = metadata.size.width;
@@ -230,10 +230,11 @@ export class MetadataLoader {
if (Config.Client.Faces.enabled) {
const faces: FaceRegion[] = [];
if (exif.Regions && exif.Regions.value.RegionList && exif.Regions.value.RegionList.value) {
for (let i = 0; i < exif.Regions.value.RegionList.value.length; i++) {
for (const regionRoot of exif.Regions.value.RegionList.value as any[]) {
let type, name, box;
const regionRoot = exif.Regions.value.RegionList.value[i] as any;
let type;
let name;
let box;
const createFaceBox = (w: string, h: string, x: string, y: string) => {
return {
width: Math.round(parseFloat(w) * metadata.size.width),
@@ -276,7 +277,7 @@ export class MetadataLoader {
// convert center base box to corner based box
box.left = Math.max(0, box.left - box.width / 2);
box.top = Math.max(0, box.top - box.height / 2);
faces.push({name: name, box: box});
faces.push({name, box});
}
}
if (Config.Client.Faces.keywordsToPersons && faces.length > 0) {

View File

@@ -59,21 +59,21 @@ export class VideoRendererFactory {
const ffmpeg = FFmpegFactory.get();
const path = require('path');
return (input: RendererInput): Promise<void> => {
return new Promise((resolve, reject) => {
return new Promise((resolve, reject): void => {
Logger.silly('[FFmpeg] rendering thumbnail: ' + input.mediaPath);
ffmpeg(input.mediaPath).ffprobe((err: any, data: FfprobeData) => {
ffmpeg(input.mediaPath).ffprobe((err: any, data: FfprobeData): void => {
if (!!err || data === null) {
return reject('[FFmpeg] ' + err.toString());
}
let width = null;
let height = null;
for (let i = 0; i < data.streams.length; i++) {
if (data.streams[i].width) {
width = data.streams[i].width;
height = data.streams[i].height;
for (const stream of data.streams) {
if (stream.width) {
width = stream.width;
height = stream.height;
break;
}
}
@@ -85,26 +85,26 @@ export class VideoRendererFactory {
const folder = path.dirname(input.outPath);
let executedCmd = '';
command
.on('start', (cmd) => {
.on('start', (cmd): void => {
executedCmd = cmd;
})
.on('end', () => {
.on('end', (): void => {
resolve();
})
.on('error', (e) => {
.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);
command.takeScreenshots({
timemarks: ['10%'], size: newSize, filename: fileName, folder: folder
timemarks: ['10%'], size: newSize, filename: fileName, folder
});
} else {
command.takeScreenshots({
timemarks: ['10%'], size: input.size + 'x' + input.size, filename: fileName, folder: folder
timemarks: ['10%'], size: input.size + 'x' + input.size, filename: fileName, folder
});
}
});
@@ -119,7 +119,7 @@ export class ImageRendererFactory {
return ImageRendererFactory.Sharp();
}
public static Sharp() {
public static Sharp(): (input: RendererInput) => Promise<void> {
const sharp = require('sharp');
sharp.cache(false);
return async (input: RendererInput): Promise<void> => {
@@ -136,11 +136,11 @@ export class ImageRendererFactory {
if (input.makeSquare === false) {
if (metadata.height > metadata.width) {
image.resize(Math.min(input.size, metadata.width), null, {
kernel: kernel
kernel
});
} else {
image.resize(null, Math.min(input.size, metadata.height), {
kernel: kernel
kernel
});
}
@@ -148,7 +148,7 @@ export class ImageRendererFactory {
} else {
image
.resize(input.size, input.size, {
kernel: kernel,
kernel,
position: sharp.gravity.centre,
fit: 'cover'
});

View File

@@ -1,9 +1,10 @@
import {Utils} from '../../../common/Utils';
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
export interface TaskQueEntry<I, O> {
data: I;
promise: { obj: Promise<O>, resolve: Function, reject: Function };
promise: { obj: Promise<O>, resolve: (ret: O) => void, reject: (err: any) => void };
}
@@ -15,29 +16,6 @@ export class TaskQue<I, O> {
constructor() {
}
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));
}
private putNewTask(input: I): TaskQueEntry<I, O> {
const taskEntry: TaskQueEntry<I, O> = {
data: input,
promise: {
obj: null,
resolve: null,
reject: null
}
};
this.tasks.push(taskEntry);
taskEntry.promise.obj = new Promise<O>((resolve: Function, reject: Function) => {
taskEntry.promise.reject = reject;
taskEntry.promise.resolve = resolve;
});
return taskEntry;
}
public isEmpty(): boolean {
return this.tasks.length === 0;
}
@@ -59,4 +37,26 @@ export class TaskQue<I, O> {
}
this.processing.splice(index, 1);
}
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));
}
private putNewTask(input: I): TaskQueEntry<I, O> {
const taskEntry: TaskQueEntry<I, O> = {
data: input,
promise: {
obj: null,
resolve: null,
reject: null
}
};
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;
});
return taskEntry;
}
}

View File

@@ -3,10 +3,9 @@ import {Logger} from '../../Logger';
import {DiskManagerTask, ThumbnailTask, WorkerMessage, WorkerTask, WorkerTaskTypes} from './Worker';
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
import {RendererInput} from './PhotoWorker';
import {Config} from '../../../common/config/private/Config';
import {TaskQue, TaskQueEntry} from './TaskQue';
import {ITaskExecuter} from './TaskExecuter';
import {DiskMangerWorker} from './DiskMangerWorker';
import {DirectoryScanSettings, DiskMangerWorker} from './DiskMangerWorker';
interface WorkerWrapper<O> {
@@ -33,7 +32,7 @@ export class ThreadPool<O> {
return promise;
}
private run = () => {
private run = (): void => {
if (this.taskQue.isEmpty()) {
return;
}
@@ -47,23 +46,23 @@ export class ThreadPool<O> {
worker.worker.send(poolTask.data);
};
private getFreeWorker() {
for (let i = 0; i < this.workers.length; i++) {
if (this.workers[i].poolTask == null) {
return this.workers[i];
private getFreeWorker(): null | WorkerWrapper<O> {
for (const worker of this.workers) {
if (worker.poolTask == null) {
return worker;
}
}
return null;
}
private startWorker() {
const worker = <WorkerWrapper<O>>{poolTask: null, worker: cluster.fork()};
private startWorker(): void {
const worker = {poolTask: null, worker: cluster.fork()} as WorkerWrapper<O>;
this.workers.push(worker);
worker.worker.on('online', () => {
worker.worker.on('online', (): void => {
ThreadPool.WorkerCount++;
Logger.debug('Worker ' + worker.worker.process.pid + ' is online, worker count:', ThreadPool.WorkerCount);
});
worker.worker.on('exit', (code, signal) => {
worker.worker.on('exit', (code, signal): void => {
ThreadPool.WorkerCount--;
Logger.warn('Worker ' + worker.worker.process.pid + ' died with code: ' + code +
', and signal: ' + signal + ', worker count:', ThreadPool.WorkerCount);
@@ -71,7 +70,7 @@ export class ThreadPool<O> {
this.startWorker();
});
worker.worker.on('message', (msg: WorkerMessage) => {
worker.worker.on('message', (msg: WorkerMessage<O>): void => {
if (worker.poolTask == null) {
throw new Error('No worker task after worker task is completed');
}
@@ -89,20 +88,20 @@ export class ThreadPool<O> {
}
export class DiskManagerTH extends ThreadPool<DirectoryDTO> implements ITaskExecuter<string, DirectoryDTO> {
execute(relativeDirectoryName: string, settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise<DirectoryDTO> {
return super.executeTask(<DiskManagerTask>{
execute(relativeDirectoryName: string, settings: DirectoryScanSettings = {}): Promise<DirectoryDTO> {
return super.executeTask({
type: WorkerTaskTypes.diskManager,
relativeDirectoryName: relativeDirectoryName,
settings: settings
});
relativeDirectoryName,
settings
} as DiskManagerTask);
}
}
export class ThumbnailTH extends ThreadPool<void> implements ITaskExecuter<RendererInput, void> {
execute(input: RendererInput): Promise<void> {
return super.executeTask(<ThumbnailTask>{
return super.executeTask({
type: WorkerTaskTypes.thumbnail,
input: input,
});
input,
} as ThumbnailTask);
}
}

View File

@@ -2,8 +2,7 @@ import {Logger} from '../../Logger';
import {promises as fsp} from 'fs';
import {FfmpegCommand} from 'fluent-ffmpeg';
import {FFmpegFactory} from '../FFmpegFactory';
import {ServerConfig} from '../../../common/config/private/PrivateConfig';
import FFmpegPresets = ServerConfig.FFmpegPresets;
import {FFmpegPresets, videoCodecType, videoFormatType, videoResolutionType} from '../../../common/config/private/PrivateConfig';
export interface VideoConverterInput {
@@ -11,13 +10,13 @@ export interface VideoConverterInput {
output: {
path: string,
bitRate?: number,
resolution?: ServerConfig.resolutionType,
resolution?: videoResolutionType,
fps?: number,
crf?: number,
preset?: ServerConfig.FFmpegPresets,
preset?: FFmpegPresets,
customOptions?: string[],
codec: ServerConfig.codecType,
format: ServerConfig.formatType
codec: videoCodecType,
format: videoFormatType
};
}

View File

@@ -1,36 +1,37 @@
import {DiskMangerWorker} from './DiskMangerWorker';
import {DirectoryScanSettings, DiskMangerWorker} from './DiskMangerWorker';
import {Logger} from '../../Logger';
import {PhotoWorker, RendererInput} from './PhotoWorker';
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
import {Utils} from '../../../common/Utils';
import {ServerConfig} from '../../../common/config/private/PrivateConfig';
import {MediaDTO} from '../../../common/entities/MediaDTO';
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
declare var process: NodeJS.Process;
declare var global: NodeJS.Global;
export class Worker {
public static process(): void {
public static process<O extends (void | DirectoryDTO<MediaDTO>)>(): void {
Logger.debug('Worker is waiting for tasks');
process.on('message', async (task: WorkerTask) => {
try {
let result = null;
switch (task.type) {
case WorkerTaskTypes.diskManager:
result = await DiskMangerWorker.scanDirectory((<DiskManagerTask>task).relativeDirectoryName, (<DiskManagerTask>task).settings);
result = await DiskMangerWorker.scanDirectory((task as DiskManagerTask).relativeDirectoryName,
(task as DiskManagerTask).settings);
if (global.gc) {
global.gc();
}
break;
case WorkerTaskTypes.thumbnail:
result = await PhotoWorker.render((<ThumbnailTask>task).input);
result = await PhotoWorker.render((task as ThumbnailTask).input);
break;
default:
throw new Error('Unknown worker task type');
}
process.send(<WorkerMessage>{
process.send({
error: null,
result: result
});
result
} as WorkerMessage<O>);
} catch (err) {
process.send({error: err, result: null});
}
@@ -49,20 +50,20 @@ export interface WorkerTask {
export interface DiskManagerTask extends WorkerTask {
relativeDirectoryName: string;
settings: DiskMangerWorker.DirectoryScanSettings;
settings: DirectoryScanSettings;
}
export interface ThumbnailTask extends WorkerTask {
input: RendererInput;
}
export module WorkerTask {
export const equals = (t1: WorkerTask, t2: WorkerTask): boolean => {
export const WorkerTask = {
equals: (t1: WorkerTask, t2: WorkerTask): boolean => {
return Utils.equalsFilter(t1, t2);
};
}
}
};
export interface WorkerMessage {
export interface WorkerMessage<O> {
error: Error;
result: DirectoryDTO | void;
result: O;
}

View File

@@ -1,23 +1,23 @@
import {RenderingMWs} from '../middlewares/RenderingMWs';
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
import {Logger} from '../Logger';
import {Express, Request, Response} from 'express';
import {Express, NextFunction, Request, Response} from 'express';
export class ErrorRouter {
public static route(app: Express) {
public static route(app: Express): void {
this.addApiErrorHandler(app);
this.addGenericHandler(app);
}
private static addApiErrorHandler(app: Express) {
private static addApiErrorHandler(app: Express): void {
app.use('/api/*',
RenderingMWs.renderError
);
}
private static addGenericHandler(app: Express) {
app.use((err: any, req: Request, res: Response, next: Function) => {
private static addGenericHandler(app: Express): void {
app.use((err: any, req: Request, res: Response, next: NextFunction): any => {
if (err.name === 'UnauthorizedError') {
// jwt authentication error

View File

@@ -10,7 +10,7 @@ import {SupportedFormats} from '../../common/SupportedFormats';
import {PhotoConverterMWs} from '../middlewares/thumbnail/PhotoConverterMWs';
export class GalleryRouter {
public static route(app: Express) {
public static route(app: Express): void {
this.addGetImageIcon(app);
this.addGetVideoIcon(app);
@@ -28,7 +28,7 @@ export class GalleryRouter {
this.addAutoComplete(app);
}
protected static addDirectoryList(app: Express) {
protected static addDirectoryList(app: Express): void {
app.get(['/api/gallery/content/:directory(*)', '/api/gallery/', '/api/gallery//'],
// common part
AuthenticationMWs.authenticate,
@@ -45,7 +45,7 @@ export class GalleryRouter {
}
protected static addGetImage(app: Express) {
protected static addGetImage(app: Express): void {
app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))'],
// common part
AuthenticationMWs.authenticate,
@@ -58,7 +58,7 @@ export class GalleryRouter {
);
}
protected static addGetBestFitImage(app: Express) {
protected static addGetBestFitImage(app: Express): void {
app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/bestFit'],
// common part
AuthenticationMWs.authenticate,
@@ -72,7 +72,7 @@ export class GalleryRouter {
);
}
protected static addGetVideo(app: Express) {
protected static addGetVideo(app: Express): void {
app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))'],
// common part
AuthenticationMWs.authenticate,
@@ -85,7 +85,7 @@ export class GalleryRouter {
);
}
protected static addGetBestFitVideo(app: Express) {
protected static addGetBestFitVideo(app: Express): void {
app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))/bestFit'],
// common part
AuthenticationMWs.authenticate,
@@ -99,7 +99,7 @@ export class GalleryRouter {
);
}
protected static addGetMetaFile(app: Express) {
protected static addGetMetaFile(app: Express): void {
app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.MetaFiles.join('|') + '))'],
// common part
AuthenticationMWs.authenticate,
@@ -112,7 +112,7 @@ export class GalleryRouter {
);
}
protected static addRandom(app: Express) {
protected static addRandom(app: Express): void {
app.get(['/api/gallery/random/:searchQueryDTO'],
// common part
AuthenticationMWs.authenticate,
@@ -126,7 +126,7 @@ export class GalleryRouter {
);
}
protected static addGetPhotoThumbnail(app: Express) {
protected static addGetPhotoThumbnail(app: Express): void {
app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/thumbnail/:size?',
// common part
AuthenticationMWs.authenticate,
@@ -140,7 +140,7 @@ export class GalleryRouter {
);
}
protected static addGetVideoThumbnail(app: Express) {
protected static addGetVideoThumbnail(app: Express): void {
app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))/thumbnail/:size?',
// common part
AuthenticationMWs.authenticate,
@@ -155,7 +155,7 @@ export class GalleryRouter {
}
protected static addGetVideoIcon(app: Express) {
protected static addGetVideoIcon(app: Express): void {
app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))/icon',
// common part
AuthenticationMWs.authenticate,
@@ -169,7 +169,7 @@ export class GalleryRouter {
);
}
protected static addGetImageIcon(app: Express) {
protected static addGetImageIcon(app: Express): void {
app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/icon',
// common part
AuthenticationMWs.authenticate,
@@ -183,7 +183,7 @@ export class GalleryRouter {
);
}
protected static addSearch(app: Express) {
protected static addSearch(app: Express): void {
app.get('/api/search/:searchQueryDTO',
// common part
AuthenticationMWs.authenticate,
@@ -199,7 +199,7 @@ export class GalleryRouter {
}
protected static addAutoComplete(app: Express) {
protected static addAutoComplete(app: Express): void {
app.get('/api/autocomplete/:text',
// common part
AuthenticationMWs.authenticate,

View File

@@ -14,37 +14,37 @@ declare global {
* Adds logging to express
*/
export class LoggerRouter {
public static log(loggerFn: logFN, req: Request, res: Response) {
public static log(loggerFn: logFN, req: Request, res: Response): void {
if (req.logged === true) {
return;
}
req.logged = true;
const end = res.end;
res.end = (a?: any, b?: any, c?: any) => {
res.end = (a?: any, b?: any, c?: any): void => {
res.end = end;
res.end(a, b, c);
loggerFn(req.method, req.url, res.statusCode, (Date.now() - req._startTime) + 'ms');
};
}
public static route(app: Express) {
public static route(app: Express): void {
/* Save start time for all requests */
app.use((req: Request, res: Response, next: NextFunction) => {
app.use((req: Request, res: Response, next: NextFunction): any => {
req._startTime = Date.now();
return next();
});
app.get('/api*', (req: Request, res: Response, next: NextFunction) => {
app.get('/api*', (req: Request, res: Response, next: NextFunction): any => {
LoggerRouter.log(Logger.verbose, req, res);
return next();
});
app.get('/node_modules*', (req: Request, res: Response, next: NextFunction) => {
app.get('/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) => {
app.use((req: Request, res: Response, next: NextFunction): any => {
LoggerRouter.log(Logger.debug, req, res);
return next();
});

View File

@@ -6,12 +6,12 @@ import {Express} from 'express';
import {VersionMWs} from '../middlewares/VersionMWs';
export class NotificationRouter {
public static route(app: Express) {
public static route(app: Express): void {
this.addGetNotifications(app);
}
private static addGetNotifications(app: Express) {
private static addGetNotifications(app: Express): void {
app.get('/api/notifications',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),

View File

@@ -8,7 +8,7 @@ import {VersionMWs} from '../middlewares/VersionMWs';
import {Config} from '../../common/config/private/Config';
export class PersonRouter {
public static route(app: Express) {
public static route(app: Express): void {
this.updatePerson(app);
this.addGetPersons(app);
@@ -16,7 +16,7 @@ export class PersonRouter {
}
protected static updatePerson(app: Express) {
protected static updatePerson(app: Express): void {
app.post(['/api/person/:name'],
// common part
AuthenticationMWs.authenticate,
@@ -29,7 +29,7 @@ export class PersonRouter {
);
}
protected static addGetPersons(app: Express) {
protected static addGetPersons(app: Express): void {
app.get(['/api/person'],
// common part
AuthenticationMWs.authenticate,
@@ -38,14 +38,14 @@ export class PersonRouter {
// specific part
PersonMWs.listPersons,
// PersonMWs.addSamplePhotoForAll,
// PersonMWs.addSamplePhotoForAll,
ThumbnailGeneratorMWs.addThumbnailInfoForPersons,
PersonMWs.cleanUpPersonResults,
RenderingMWs.renderResult
);
}
protected static getPersonThumbnail(app: Express) {
protected static getPersonThumbnail(app: Express): void {
app.get(['/api/person/:name/thumbnail'],
// common part
AuthenticationMWs.authenticate,

View File

@@ -28,19 +28,19 @@ export class PublicRouter {
public static route(app: Express): void {
const setLocale = (req: Request, res: Response, next: NextFunction) => {
let selectedLocale = req['locale'];
let selectedLocale = req.locale;
if (req.cookies && req.cookies[CookieNames.lang]) {
if (Config.Client.languages.indexOf(req.cookies[CookieNames.lang]) !== -1) {
selectedLocale = req.cookies[CookieNames.lang];
}
}
res.cookie(CookieNames.lang, selectedLocale);
req['localePath'] = selectedLocale;
req.localePath = selectedLocale;
next();
};
const renderIndex = (req: Request, res: Response, next: NextFunction) => {
ejs.renderFile(path.join(ProjectPath.FrontendFolder, req['localePath'], 'index.html'),
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));
@@ -121,7 +121,7 @@ export class PublicRouter {
const renderFile = (subDir: string = '') => {
return (req: Request, res: Response) => {
const file = path.join(ProjectPath.FrontendFolder, req['localePath'], subDir, req.params.file);
const file = path.join(ProjectPath.FrontendFolder, req.localePath, subDir, req.params.file);
fs.exists(file, (exists: boolean) => {
if (!exists) {
return res.sendStatus(404);

View File

@@ -11,7 +11,7 @@ import {ErrorRouter} from './ErrorRouter';
export class Router {
public static route(app: Express) {
public static route(app: Express): void {
PublicRouter.route(app);

View File

@@ -6,7 +6,7 @@ import * as express from 'express';
import {QueryParams} from '../../common/QueryParams';
export class SharingRouter {
public static route(app: express.Express) {
public static route(app: express.Express): void {
this.addShareLogin(app);
this.addGetSharing(app);
@@ -16,7 +16,7 @@ export class SharingRouter {
this.addDeleteSharing(app);
}
private static addShareLogin(app: express.Express) {
private static addShareLogin(app: express.Express): void {
app.post('/api/share/login',
AuthenticationMWs.inverseAuthenticate,
AuthenticationMWs.shareLogin,
@@ -24,7 +24,7 @@ export class SharingRouter {
);
}
private static addGetSharing(app: express.Express) {
private static addGetSharing(app: express.Express): void {
app.get('/api/share/:' + QueryParams.gallery.sharingKey_params,
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.LimitedGuest),
@@ -33,7 +33,7 @@ export class SharingRouter {
);
}
private static addCreateSharing(app: express.Express) {
private static addCreateSharing(app: express.Express): void {
app.post(['/api/share/:directory(*)', '/api/share/', '/api/share//'],
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User),
@@ -42,7 +42,7 @@ export class SharingRouter {
);
}
private static addUpdateSharing(app: express.Express) {
private static addUpdateSharing(app: express.Express): void {
app.put(['/api/share/:directory(*)', '/api/share/', '/api/share//'],
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User),
@@ -52,7 +52,7 @@ export class SharingRouter {
}
private static addDeleteSharing(app: express.Express) {
private static addDeleteSharing(app: express.Express): void {
app.delete(['/api/share/:sharingKey'],
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
@@ -61,7 +61,7 @@ export class SharingRouter {
);
}
private static addListSharing(app: express.Express) {
private static addListSharing(app: express.Express): void {
app.get(['/api/share/list'],
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User),

View File

@@ -6,7 +6,7 @@ import {UserRequestConstrainsMWs} from '../middlewares/user/UserRequestConstrain
import {RenderingMWs} from '../middlewares/RenderingMWs';
export class UserRouter {
public static route(app: Express) {
public static route(app: Express): void {
this.addLogin(app);
this.addLogout(app);
this.addGetSessionUser(app);
@@ -19,7 +19,7 @@ export class UserRouter {
this.addChangeRole(app);
}
private static addLogin(app: Express) {
private static addLogin(app: Express): void {
app.post('/api/user/login',
AuthenticationMWs.inverseAuthenticate,
AuthenticationMWs.login,
@@ -27,7 +27,7 @@ export class UserRouter {
);
}
private static addLogout(app: Express) {
private static addLogout(app: Express): void {
app.post('/api/user/logout',
AuthenticationMWs.logout,
RenderingMWs.renderOK
@@ -35,7 +35,7 @@ export class UserRouter {
}
private static addGetSessionUser(app: Express) {
private static addGetSessionUser(app: Express): void {
app.get('/api/user/me',
AuthenticationMWs.authenticate,
RenderingMWs.renderSessionUser
@@ -43,7 +43,7 @@ export class UserRouter {
}
private static addChangePassword(app: Express) {
private static addChangePassword(app: Express): void {
app.post('/api/user/:id/password',
AuthenticationMWs.authenticate,
UserRequestConstrainsMWs.forceSelfRequest,
@@ -53,7 +53,7 @@ export class UserRouter {
}
private static addCreateUser(app: Express) {
private static addCreateUser(app: Express): void {
app.put('/api/user',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
@@ -62,7 +62,7 @@ export class UserRouter {
);
}
private static addDeleteUser(app: Express) {
private static addDeleteUser(app: Express): void {
app.delete('/api/user/:id',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
@@ -73,7 +73,7 @@ export class UserRouter {
}
private static addListUsers(app: Express) {
private static addListUsers(app: Express): void {
app.get('/api/user/list',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
@@ -82,7 +82,7 @@ export class UserRouter {
);
}
private static addChangeRole(app: Express) {
private static addChangeRole(app: Express): void {
app.post('/api/user/:id/role',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),

View File

@@ -5,14 +5,14 @@ import {AdminMWs} from '../../middlewares/admin/AdminMWs';
import {Express} from 'express';
export class AdminRouter {
public static route(app: Express) {
public static route(app: Express): void {
this.addGetStatistic(app);
this.addGetDuplicates(app);
this.addJobs(app);
}
private static addGetStatistic(app: Express) {
private static addGetStatistic(app: Express): void {
app.get('/api/admin/statistic',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
@@ -21,7 +21,7 @@ export class AdminRouter {
);
}
private static addGetDuplicates(app: Express) {
private static addGetDuplicates(app: Express): void {
app.get('/api/admin/duplicates',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
@@ -30,7 +30,7 @@ export class AdminRouter {
);
}
private static addJobs(app: Express) {
private static addJobs(app: Express): void {
app.get('/api/admin/jobs/available',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),

View File

@@ -5,12 +5,12 @@ import {Express} from 'express';
import {SettingsMWs} from '../../middlewares/admin/SettingsMWs';
export class SettingsRouter {
public static route(app: Express) {
public static route(app: Express): void {
this.addSettings(app);
}
private static addSettings(app: Express) {
private static addSettings(app: Express): void {
app.get('/api/settings',
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),

View File

@@ -20,11 +20,11 @@ import * as _csrf from 'csurf';
import * as unless from 'express-unless';
import {Event} from '../common/event/Event';
import {QueryParams} from '../common/QueryParams';
import {ServerConfig} from '../common/config/private/PrivateConfig';
import {ConfigClassBuilder} from 'typeconfig/node';
import {ConfigClassOptions} from 'typeconfig/src/decorators/class/IConfigClass';
import {DatabaseType} from '../common/config/private/PrivateConfig';
const _session = require('cookie-session');
const session = require('cookie-session');
declare var process: NodeJS.Process;
@@ -51,7 +51,7 @@ export class Server {
Logger.info(LOG_TAG, 'running diagnostics...');
await ConfigDiagnostics.runDiagnostics();
Logger.verbose(LOG_TAG, 'using config from ' +
(<ConfigClassOptions>ConfigClassBuilder.attachPrivateInterface(Config).__options).configPath + ':');
(ConfigClassBuilder.attachPrivateInterface(Config).__options as ConfigClassOptions).configPath + ':');
Logger.verbose(LOG_TAG, JSON.stringify(Config, null, '\t'));
this.app = _express();
@@ -65,7 +65,7 @@ export class Server {
* Session above all
*/
this.app.use(_session({
this.app.use(session({
name: CookieNames.session,
keys: Config.Server.sessionSecret
}));
@@ -95,7 +95,7 @@ export class Server {
Localizations.init();
this.app.use(locale(Config.Client.languages, 'en'));
if (Config.Server.Database.type !== ServerConfig.DatabaseType.memory) {
if (Config.Server.Database.type !== DatabaseType.memory) {
await ObjectManagers.InitSQLManagers();
} else {
await ObjectManagers.InitMemoryManagers();

View File

@@ -121,23 +121,23 @@ export class SearchQueryParser {
if (tokenEnd !== str.length - 1) {
if (str.startsWith(' ' + this.keywords.and, tokenEnd)) {
return <ANDSearchQuery>{
return {
type: SearchQueryTypes.AND,
list: [this.parse(str.slice(0, tokenEnd), implicitOR), // trim brackets
this.parse(str.slice(tokenEnd + (' ' + this.keywords.and).length), implicitOR)]
};
} as ANDSearchQuery;
} else if (str.startsWith(' ' + this.keywords.or, tokenEnd)) {
return <ORSearchQuery>{
return {
type: SearchQueryTypes.OR,
list: [this.parse(str.slice(0, tokenEnd), implicitOR), // trim brackets
this.parse(str.slice(tokenEnd + (' ' + this.keywords.or).length), implicitOR)]
};
} as ORSearchQuery;
} else { // Relation cannot be detected
return <SearchListQuery>{
return {
type: implicitOR === true ? SearchQueryTypes.OR : SearchQueryTypes.UNKNOWN_RELATION,
list: [this.parse(str.slice(0, tokenEnd), implicitOR), // trim brackets
this.parse(str.slice(tokenEnd), implicitOR)]
};
} as SearchListQuery;
}
}
if (str.startsWith(this.keywords.someOf + ':') ||
@@ -150,18 +150,18 @@ export class SearchQueryParser {
const unfoldList = (q: SearchListQuery): SearchQueryDTO[] => {
if (q.list) {
if (q.type === SearchQueryTypes.UNKNOWN_RELATION) {
return [].concat.apply([], q.list.map(e => unfoldList(<any>e))); // flatten array
return [].concat.apply([], q.list.map(e => unfoldList(e as any))); // flatten array
} else {
q.list.forEach(e => unfoldList(<any>e));
q.list.forEach(e => unfoldList(e as any));
}
}
return [q];
};
tmpList = unfoldList(<SearchListQuery>tmpList);
const ret = <SomeOfSearchQuery>{
tmpList = unfoldList(tmpList as SearchListQuery);
const ret = {
type: SearchQueryTypes.SOME_OF,
list: tmpList
};
} as SomeOfSearchQuery;
if (new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').test(str)) {
ret.min = parseInt(new RegExp(/^\d*/).exec(str)[0], 10);
}
@@ -169,41 +169,41 @@ export class SearchQueryParser {
}
if (str.startsWith(this.keywords.from + ':')) {
return <FromDateSearch>{
return {
type: SearchQueryTypes.from_date,
value: SearchQueryParser.parseDate(str.substring((this.keywords.from + ':').length))
};
} as FromDateSearch;
}
if (str.startsWith(this.keywords.to + ':')) {
return <ToDateSearch>{
return {
type: SearchQueryTypes.to_date,
value: SearchQueryParser.parseDate(str.substring((this.keywords.to + ':').length))
};
} as ToDateSearch;
}
if (str.startsWith(this.keywords.minRating + ':')) {
return <MinRatingSearch>{
return {
type: SearchQueryTypes.min_rating,
value: parseInt(str.slice((this.keywords.minRating + ':').length), 10)
};
} as MinRatingSearch;
}
if (str.startsWith(this.keywords.maxRating + ':')) {
return <MaxRatingSearch>{
return {
type: SearchQueryTypes.max_rating,
value: parseInt(str.slice((this.keywords.maxRating + ':').length), 10)
};
} as MaxRatingSearch;
}
if (str.startsWith(this.keywords.minResolution + ':')) {
return <MinResolutionSearch>{
return {
type: SearchQueryTypes.min_resolution,
value: parseInt(str.slice((this.keywords.minResolution + ':').length), 10)
};
} as MinResolutionSearch;
}
if (str.startsWith(this.keywords.maxResolution + ':')) {
return <MaxResolutionSearch>{
return {
type: SearchQueryTypes.max_resolution,
value: parseInt(str.slice((this.keywords.maxResolution + ':').length), 10)
};
} as MaxResolutionSearch;
}
if (new RegExp('^\\d*-' + this.keywords.kmFrom + ':').test(str)) {
let from = str.slice(new RegExp('^\\d*-' + this.keywords.kmFrom + ':').exec(str)[0].length);
@@ -211,42 +211,42 @@ export class SearchQueryParser {
(from.charAt(0) === '"' && from.charAt(from.length - 1) === '"')) {
from = from.slice(1, from.length - 1);
}
return <DistanceSearch>{
return {
type: SearchQueryTypes.distance,
distance: parseInt(new RegExp(/^\d*/).exec(str)[0], 10),
from: {text: from}
};
} as DistanceSearch;
}
if (str.startsWith(this.keywords.orientation + ':')) {
return <OrientationSearch>{
return {
type: SearchQueryTypes.orientation,
landscape: str.slice((this.keywords.orientation + ':').length) === this.keywords.landscape
};
} as OrientationSearch;
}
// parse text search
const tmp = TextSearchQueryTypes.map(type => ({
key: (<any>this.keywords)[SearchQueryTypes[type]] + ':',
queryTemplate: <TextSearch>{type: type, text: ''}
key: (this.keywords as any)[SearchQueryTypes[type]] + ':',
queryTemplate: {type, text: ''} as TextSearch
}));
for (let i = 0; i < tmp.length; ++i) {
if (str.startsWith(tmp[i].key)) {
const ret: TextSearch = Utils.clone(tmp[i].queryTemplate);
if (str.charAt(tmp[i].key.length) === '"' && str.charAt(str.length - 1) === '"') {
ret.text = str.slice(tmp[i].key.length + 1, str.length - 1);
for (const typeTmp of tmp) {
if (str.startsWith(typeTmp.key)) {
const ret: TextSearch = Utils.clone(typeTmp.queryTemplate);
if (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;
} else if (str.charAt(tmp[i].key.length) === '(' && str.charAt(str.length - 1) === ')') {
ret.text = str.slice(tmp[i].key.length + 1, str.length - 1);
} else if (str.charAt(typeTmp.key.length) === '(' && str.charAt(str.length - 1) === ')') {
ret.text = str.slice(typeTmp.key.length + 1, str.length - 1);
} else {
ret.text = str.slice(tmp[i].key.length);
ret.text = str.slice(typeTmp.key.length);
}
return ret;
}
}
return <TextSearch>{type: SearchQueryTypes.any_text, text: str};
return {type: SearchQueryTypes.any_text, text: str} as TextSearch;
}
public stringify(query: SearchQueryDTO): string {
@@ -255,51 +255,52 @@ export class SearchQueryParser {
}
switch (query.type) {
case SearchQueryTypes.AND:
return '(' + (<SearchListQuery>query).list.map(q => this.stringify(q)).join(' ' + this.keywords.and + ' ') + ')';
return '(' + (query as SearchListQuery).list.map(q => this.stringify(q)).join(' ' + this.keywords.and + ' ') + ')';
case SearchQueryTypes.OR:
return '(' + (<SearchListQuery>query).list.map(q => this.stringify(q)).join(' ' + this.keywords.or + ' ') + ')';
return '(' + (query as SearchListQuery).list.map(q => this.stringify(q)).join(' ' + this.keywords.or + ' ') + ')';
case SearchQueryTypes.SOME_OF:
if ((<SomeOfSearchQuery>query).min) {
return (<SomeOfSearchQuery>query).min + '-' + this.keywords.NSomeOf + ':(' +
(<SearchListQuery>query).list.map(q => this.stringify(q)).join(' ') + ')';
if ((query as SomeOfSearchQuery).min) {
return (query as SomeOfSearchQuery).min + '-' + this.keywords.NSomeOf + ':(' +
(query as SearchListQuery).list.map(q => this.stringify(q)).join(' ') + ')';
}
return this.keywords.someOf + ':(' +
(<SearchListQuery>query).list.map(q => this.stringify(q)).join(' ') + ')';
(query as SearchListQuery).list.map(q => this.stringify(q)).join(' ') + ')';
case SearchQueryTypes.orientation:
return this.keywords.orientation + ':' + ((<OrientationSearch>query).landscape ? this.keywords.landscape : this.keywords.portrait);
return this.keywords.orientation + ':' +
((query as OrientationSearch).landscape ? this.keywords.landscape : this.keywords.portrait);
case SearchQueryTypes.from_date:
if (!(<FromDateSearch>query).value) {
if (!(query as FromDateSearch).value) {
return '';
}
return this.keywords.from + ':' +
SearchQueryParser.stringifyDate((<FromDateSearch>query).value);
SearchQueryParser.stringifyDate((query as FromDateSearch).value);
case SearchQueryTypes.to_date:
if (!(<ToDateSearch>query).value) {
if (!(query as ToDateSearch).value) {
return '';
}
return this.keywords.to + ':' +
SearchQueryParser.stringifyDate((<ToDateSearch>query).value);
SearchQueryParser.stringifyDate((query as ToDateSearch).value);
case SearchQueryTypes.min_rating:
return this.keywords.minRating + ':' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
return this.keywords.minRating + ':' + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
case SearchQueryTypes.max_rating:
return this.keywords.maxRating + ':' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
return this.keywords.maxRating + ':' + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
case SearchQueryTypes.min_resolution:
return this.keywords.minResolution + ':' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
return this.keywords.minResolution + ':' + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
case SearchQueryTypes.max_resolution:
return this.keywords.maxResolution + ':' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
return this.keywords.maxResolution + ':' + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
case SearchQueryTypes.distance:
if ((<DistanceSearch>query).from.text.indexOf(' ') !== -1) {
return (<DistanceSearch>query).distance + '-' + this.keywords.kmFrom + ':(' + (<DistanceSearch>query).from.text + ')';
if ((query as DistanceSearch).from.text.indexOf(' ') !== -1) {
return (query as DistanceSearch).distance + '-' + this.keywords.kmFrom + ':(' + (query as DistanceSearch).from.text + ')';
}
return (<DistanceSearch>query).distance + '-' + this.keywords.kmFrom + ':' + (<DistanceSearch>query).from.text;
return (query as DistanceSearch).distance + '-' + this.keywords.kmFrom + ':' + (query as DistanceSearch).from.text;
case SearchQueryTypes.any_text:
return SearchQueryParser.stringifyText((<TextSearch>query).text, (<TextSearch>query).matchType);
return SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType);
case SearchQueryTypes.person:
case SearchQueryTypes.position:
@@ -307,11 +308,11 @@ export class SearchQueryParser {
case SearchQueryTypes.caption:
case SearchQueryTypes.file_name:
case SearchQueryTypes.directory:
if (!(<TextSearch>query).text) {
if (!(query as TextSearch).text) {
return '';
}
return (<any>this.keywords)[SearchQueryTypes[query.type]] + ':' +
SearchQueryParser.stringifyText((<TextSearch>query).text, (<TextSearch>query).matchType);
return (this.keywords as any)[SearchQueryTypes[query.type]] + ':' +
SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType);
default:
throw new Error('Unknown type: ' + query.type);

View File

@@ -21,19 +21,19 @@ export const SupportedFormats = {
TranscodeNeed: {
// based on libvips, all supported formats for sharp: https://github.com/libvips/libvips
// all supported formats for gm: http://www.graphicsmagick.org/GraphicsMagick.html
Photos: <string[]>[],
Photos: [] as string[],
Videos: [
'avi', 'mkv', 'mov', 'wmv', 'flv', 'mts', 'm2ts', 'mpg', '3gp', 'm4v', 'mpeg', 'vob',
'divx', 'xvid', 'ts'
],
},
WithDots: {
Photos: <string[]>[],
Videos: <string[]>[],
MetaFiles: <string[]>[],
Photos: [] as string[],
Videos: [] as string[],
MetaFiles: [] as string[],
TranscodeNeed: {
Photos: <string[]>[],
Videos: <string[]>[],
Photos: [] as string[],
Videos: [] as string[],
}
}
};

View File

@@ -1,10 +1,8 @@
export class Utils {
static GUID() {
const s4 = function () {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
static GUID(): string {
const s4 = (): string => Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
return s4() + s4() + '-' + s4() + s4();
}
@@ -17,8 +15,8 @@ export class Utils {
return R;
}
static wait(time: number) {
return new Promise((resolve) => {
static wait(time: number): Promise<unknown> {
return new Promise((resolve): void => {
setTimeout(resolve, time);
});
}
@@ -30,8 +28,7 @@ export class Utils {
}
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
const key: string = keys[i];
for (const key of keys) {
if (obj[key] !== null && typeof obj[key] === 'object') {
if (Utils.removeNullOrEmptyObj(obj[key])) {
if (Object.keys(obj[key]).length === 0) {
@@ -49,15 +46,13 @@ export class Utils {
return JSON.parse(JSON.stringify(object));
}
static zeroPrefix(value: string | number, length: number) {
static zeroPrefix(value: string | number, length: number): string {
const ret = '00000' + value;
return ret.substr(ret.length - length);
}
/**
* Checks if the two input (let them be objects or arrays or just primitives) are equal
* @param object
* @param filter
*/
static equalsFilter(object: any, filter: any): boolean {
if (typeof filter !== 'object' || filter == null) {
@@ -71,8 +66,7 @@ export class Utils {
return false;
}
const keys = Object.keys(filter);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
for (const key of keys) {
if (typeof filter[key] === 'object') {
if (Utils.equalsFilter(object[key], filter[key]) === false) {
return false;
@@ -86,7 +80,7 @@ export class Utils {
return true;
}
static renderDataSize(size: number) {
static renderDataSize(size: number): string {
const postFixes = ['B', 'KB', 'MB', 'GB', 'TB'];
let index = 0;
while (size > 1000 && index < postFixes.length - 1) {
@@ -97,12 +91,6 @@ export class Utils {
}
/**
*
* @param from
* @param to inclusive
* @returns {Array}
*/
static createRange(from: number, to: number): Array<number> {
const arr = new Array(to - from + 1);
let c = to - from + 1;
@@ -112,20 +100,20 @@ export class Utils {
return arr;
}
public static canonizePath(path: string) {
public static canonizePath(path: string): string {
return path
.replace(new RegExp('\\\\', 'g'), '/')
.replace(new RegExp('/+', 'g'), '/');
}
static concatUrls(...args: Array<string>) {
static concatUrls(...args: Array<string>): string {
let url = '';
for (let i = 0; i < args.length; i++) {
if (args[i] === '' || typeof args[i] === 'undefined') {
for (const item of args) {
if (item === '' || typeof item === 'undefined') {
continue;
}
const part = args[i].replace(new RegExp('\\\\', 'g'), '/');
const part = item.replace(new RegExp('\\\\', 'g'), '/');
if (part === '/' || part === './') {
continue;
}
@@ -141,8 +129,8 @@ export class Utils {
return url.substring(0, url.length - 1);
}
public static updateKeys(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
public static updateKeys(targetObject: any, sourceObject: any): void {
Object.keys(sourceObject).forEach((key): void => {
if (typeof targetObject[key] === 'undefined') {
return;
}
@@ -154,8 +142,8 @@ export class Utils {
});
}
public static setKeys(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
public static setKeys(targetObject: any, sourceObject: any): void {
Object.keys(sourceObject).forEach((key): void => {
if (typeof targetObject[key] === 'object') {
Utils.setKeys(targetObject[key], sourceObject[key]);
} else {
@@ -164,8 +152,8 @@ export class Utils {
});
}
public static setKeysForced(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
public static setKeysForced(targetObject: any, sourceObject: any): void {
Object.keys(sourceObject).forEach((key): void => {
if (typeof sourceObject[key] === 'object') {
if (typeof targetObject[key] === 'undefined') {
targetObject[key] = {};
@@ -185,21 +173,21 @@ export class Utils {
}
const key = parseInt(enumMember, 10);
if (key >= 0) {
arr.push({key: key, value: EnumType[enumMember]});
arr.push({key, value: EnumType[enumMember]});
}
}
return arr;
}
public static findClosest(number: number, arr: number[]): number {
public static findClosest(num: number, arr: number[]): number {
let curr = arr[0];
let diff = Math.abs(number - curr);
let diff = Math.abs(num - curr);
arr.forEach((value) => {
arr.forEach((value): void => {
const newDiff = Math.abs(number - value);
const newDiff = Math.abs(num - value);
if (newDiff < diff) {
diff = newDiff;
@@ -212,35 +200,35 @@ export class Utils {
}
public static findClosestinSorted(number: number, arr: number[]): number {
public static findClosestinSorted(num: number, arr: number[]): number {
let curr = arr[0];
let diff = Math.abs(number - curr);
for (let i = 0; i < arr.length; ++i) {
let diff = Math.abs(num - curr);
for (const item of arr) {
const newDiff = Math.abs(number - arr[i]);
const newDiff = Math.abs(num - item);
if (newDiff > diff) {
break;
}
diff = newDiff;
curr = arr[i];
curr = item;
}
return curr;
}
public static isUInt32(value: number, max: number = 4294967295) {
public static isUInt32(value: number, max: number = 4294967295): boolean {
value = parseInt('' + value, 10);
return !isNaN(value) && value >= 0 && value <= max;
}
public static isInt32(value: number) {
public static isInt32(value: number): boolean {
value = parseFloat('' + value);
return !isNaN(value) && value >= -2147483648 && value <= 2147483647;
}
public static isFloat32(value: number) {
public static isFloat32(value: number): boolean {
const E = Math.pow(10, 38);
const nE = Math.pow(10, -38);
return !isNaN(value) && ((value >= -3.402823466 * E && value <= -1.175494351 * nE) ||
@@ -252,14 +240,14 @@ export class Utils {
return [];
}
if (num <= 1) {
return arr.slice(start).map(e => [e]);
return arr.slice(start).map((e): any[] => [e]);
}
if (num === arr.length - start) {
return [arr.slice(start)];
}
const ret: any[][] = [];
for (let i = start; i < arr.length; ++i) {
Utils.getAnyX(num - 1, arr, i + 1).forEach(a => {
Utils.getAnyX(num - 1, arr, i + 1).forEach((a): void => {
a.push(arr[i]);
ret.push(a);
});
@@ -269,34 +257,33 @@ export class Utils {
}
export namespace Utils {
export class LRU<V> {
data: { [key: string]: { value: V, usage: number } } = {};
export class LRU<V> {
data: { [key: string]: { value: V, usage: number } } = {};
constructor(public readonly size: number) {
}
constructor(public readonly size: number) {
}
set(key: string, value: V): void {
this.data[key] = {usage: Date.now(), value: value};
if (Object.keys(this.data).length > this.size) {
let oldestK = key;
let oldest = this.data[oldestK].usage;
for (const k in this.data) {
if (this.data[k].usage < oldest) {
oldestK = k;
oldest = this.data[oldestK].usage;
}
set(key: string, value: V): void {
this.data[key] = {usage: Date.now(), value};
if (Object.keys(this.data).length > this.size) {
let oldestK = key;
let oldest = this.data[oldestK].usage;
for (const k in this.data) {
if (this.data[k].usage < oldest) {
oldestK = k;
oldest = this.data[oldestK].usage;
}
delete this.data[oldestK];
}
}
get(key: string): V {
if (!this.data[key]) {
return;
}
return this.data[key].value;
delete this.data[oldestK];
}
}
get(key: string): V {
if (!this.data[key]) {
return;
}
return this.data[key].value;
}
}

View File

@@ -33,10 +33,10 @@ const upTime = (new Date()).toISOString();
}
})
export class PrivateConfigClass implements IPrivateConfig {
@ConfigProperty({type: ServerConfig.Config})
Server: ServerConfig.Config = new ServerConfig.Config();
@ConfigProperty({type: ClientConfig.Config})
Client: IConfigClass & ClientConfig.Config = new ClientConfig.Config() as (IConfigClass & ClientConfig.Config);
@ConfigProperty({type: ServerConfig})
Server: ServerConfig = new ServerConfig();
@ConfigProperty({type: ClientConfig})
Client: IConfigClass & ClientConfig = new ClientConfig() as (IConfigClass & ClientConfig);
constructor() {
if (!this.Server.sessionSecret || this.Server.sessionSecret.length === 0) {

View File

@@ -6,360 +6,359 @@ import {SubConfigClass} from 'typeconfig/src/decorators/class/SubConfigClass';
import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty';
import {DefaultsJobs} from '../../entities/job/JobDTO';
export module ServerConfig {
export enum DatabaseType {
memory = 1, mysql = 2, sqlite = 3
}
export enum DatabaseType {
memory = 1, mysql = 2, sqlite = 3
}
export enum LogLevel {
error = 1, warn = 2, info = 3, verbose = 4, debug = 5, silly = 6
}
export enum LogLevel {
error = 1, warn = 2, info = 3, verbose = 4, debug = 5, silly = 6
}
export enum SQLLogLevel {
none = 1, error = 2, all = 3
}
export enum SQLLogLevel {
none = 1, error = 2, all = 3
}
export enum ReIndexingSensitivity {
low = 1, medium = 2, high = 3
}
export enum ReIndexingSensitivity {
low = 1, medium = 2, high = 3
}
export enum FFmpegPresets {
ultrafast = 1, superfast = 2, veryfast = 3, faster = 4, fast = 5, medium = 6,
slow = 7, slower = 8, veryslow = 9, placebo = 10
}
export enum FFmpegPresets {
ultrafast = 1, superfast = 2, veryfast = 3, faster = 4, fast = 5, medium = 6,
slow = 7, slower = 8, veryslow = 9, placebo = 10
}
export type codecType = 'libvpx-vp9' | 'libx264' | 'libvpx' | 'libx265';
export type resolutionType = 240 | 360 | 480 | 720 | 1080 | 1440 | 2160 | 4320;
export type formatType = 'mp4' | 'webm';
export type videoCodecType = 'libvpx-vp9' | 'libx264' | 'libvpx' | 'libx265';
export type videoResolutionType = 240 | 360 | 480 | 720 | 1080 | 1440 | 2160 | 4320;
export type videoFormatType = 'mp4' | 'webm';
@SubConfigClass()
export class MySQLConfig {
@ConfigProperty({envAlias: 'MYSQL_HOST'})
host: string = 'localhost';
@ConfigProperty({envAlias: 'MYSQL_PORT', min: 0, max: 65535})
port: number = 3306;
@ConfigProperty({envAlias: 'MYSQL_DATABASE'})
database: string = 'pigallery2';
@ConfigProperty({envAlias: 'MYSQL_USERNAME'})
username: string = '';
@ConfigProperty({envAlias: 'MYSQL_PASSWORD', type: 'password'})
password: string = '';
}
@SubConfigClass()
export class MySQLConfig {
@ConfigProperty({envAlias: 'MYSQL_HOST'})
host: string = 'localhost';
@ConfigProperty({envAlias: 'MYSQL_PORT', min: 0, max: 65535})
port: number = 3306;
@ConfigProperty({envAlias: 'MYSQL_DATABASE'})
database: string = 'pigallery2';
@ConfigProperty({envAlias: 'MYSQL_USERNAME'})
username: string = '';
@ConfigProperty({envAlias: 'MYSQL_PASSWORD', type: 'password'})
password: string = '';
}
@SubConfigClass()
export class SQLiteConfig {
@ConfigProperty()
DBFileName: string = 'sqlite.db';
}
@SubConfigClass()
export class SQLiteConfig {
@ConfigProperty()
DBFileName: string = 'sqlite.db';
}
@SubConfigClass()
export class DataBaseConfig {
@ConfigProperty<DatabaseType, IPrivateConfig>({
type: DatabaseType,
onNewValue: (value, config) => {
if (value === ServerConfig.DatabaseType.memory) {
config.Client.Search.enabled = false;
config.Client.Sharing.enabled = false;
}
@SubConfigClass()
export class ServerDataBaseConfig {
@ConfigProperty<DatabaseType, IPrivateConfig>({
type: DatabaseType,
onNewValue: (value, config) => {
if (value === DatabaseType.memory) {
config.Client.Search.enabled = false;
config.Client.Sharing.enabled = false;
}
})
type: DatabaseType = DatabaseType.sqlite;
@ConfigProperty()
dbFolder: string = 'db';
@ConfigProperty()
sqlite?: SQLiteConfig = new SQLiteConfig();
@ConfigProperty()
mysql?: MySQLConfig = new MySQLConfig();
}
@SubConfigClass()
export class ThumbnailConfig {
@ConfigProperty({description: 'if true, photos will have better quality.'})
qualityPriority: boolean = true;
@ConfigProperty({type: 'ratio'})
personFaceMargin: number = 0.6; // in ration [0-1]
}
@SubConfigClass()
export class SharingConfig {
@ConfigProperty()
updateTimeout: number = 1000 * 60 * 5;
}
@SubConfigClass()
export class IndexingConfig {
@ConfigProperty()
cachedFolderTimeout: number = 1000 * 60 * 60; // Do not rescans the folder if seems ok
@ConfigProperty({type: ReIndexingSensitivity})
reIndexingSensitivity: ReIndexingSensitivity = ReIndexingSensitivity.low;
@ConfigProperty({
arrayType: 'string',
description: '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', description: 'Any folder that contains a file with this name will be excluded from indexing.'})
excludeFileList: string[] = [];
}
@SubConfigClass()
export class ThreadingConfig {
@ConfigProperty({description: 'App can run on multiple thread'})
enabled: boolean = true;
@ConfigProperty({description: '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
}
@SubConfigClass()
export class DuplicatesConfig {
@ConfigProperty()
listingLimit: number = 1000; // maximum number of duplicates to list
}
@SubConfigClass()
export class LogConfig {
@ConfigProperty({type: LogLevel})
level: LogLevel = LogLevel.info;
@ConfigProperty({type: SQLLogLevel})
sqlLevel: SQLLogLevel = SQLLogLevel.error;
}
@SubConfigClass()
export class NeverJobTrigger implements JobTrigger {
@ConfigProperty({type: JobTriggerType})
readonly type = JobTriggerType.never;
}
@SubConfigClass()
export class ScheduledJobTrigger implements JobTrigger {
@ConfigProperty({type: JobTriggerType})
readonly type = JobTriggerType.scheduled;
@ConfigProperty()
time: number; // data time
}
@SubConfigClass()
export class PeriodicJobTrigger implements JobTrigger {
@ConfigProperty({type: JobTriggerType})
readonly type = JobTriggerType.periodic;
@ConfigProperty()
periodicity: number; // 0-6: week days 7 every day
@ConfigProperty()
atTime: number; // day time
}
@SubConfigClass()
export class AfterJobTrigger implements JobTrigger {
@ConfigProperty({type: JobTriggerType})
readonly type = JobTriggerType.after;
@ConfigProperty()
afterScheduleName: string; // runs after schedule
constructor(afterScheduleName?: string) {
this.afterScheduleName = afterScheduleName;
}
}
})
type: DatabaseType = DatabaseType.sqlite;
@ConfigProperty()
dbFolder: string = 'db';
@ConfigProperty()
sqlite?: SQLiteConfig = new SQLiteConfig();
@ConfigProperty()
mysql?: MySQLConfig = new MySQLConfig();
}
@SubConfigClass()
export class ServerThumbnailConfig {
@ConfigProperty({description: 'if true, photos will have better quality.'})
qualityPriority: boolean = true;
@ConfigProperty({type: 'ratio'})
personFaceMargin: number = 0.6; // in ration [0-1]
}
@SubConfigClass()
export class ServerSharingConfig {
@ConfigProperty()
updateTimeout: number = 1000 * 60 * 5;
}
@SubConfigClass()
export class JobScheduleConfig implements JobScheduleDTO {
@SubConfigClass()
export class ServerIndexingConfig {
@ConfigProperty()
cachedFolderTimeout: number = 1000 * 60 * 60; // Do not rescans the folder if seems ok
@ConfigProperty({type: ReIndexingSensitivity})
reIndexingSensitivity: ReIndexingSensitivity = ReIndexingSensitivity.low;
@ConfigProperty({
arrayType: 'string',
description: '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', description: 'Any folder that contains a file with this name will be excluded from indexing.'})
excludeFileList: string[] = [];
}
@ConfigProperty()
name: string;
@ConfigProperty()
jobName: string;
@ConfigProperty()
config: any = {};
@ConfigProperty()
allowParallelRun: boolean;
@ConfigProperty({
type: NeverJobTrigger,
typeBuilder: (v: JobTrigger) => {
const type = typeof v.type === 'number' ? v.type : JobTriggerType[v.type];
switch (type) {
case JobTriggerType.after:
return AfterJobTrigger;
case JobTriggerType.never:
return NeverJobTrigger;
case JobTriggerType.scheduled:
return ScheduledJobTrigger;
case JobTriggerType.periodic:
return PeriodicJobTrigger;
}
return null;
}
})
trigger: AfterJobTrigger | NeverJobTrigger | PeriodicJobTrigger | ScheduledJobTrigger;
@SubConfigClass()
export class ServerThreadingConfig {
@ConfigProperty({description: 'App can run on multiple thread'})
enabled: boolean = true;
@ConfigProperty({description: '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
}
constructor(name: string, jobName: string, allowParallelRun: boolean,
trigger: AfterJobTrigger | NeverJobTrigger | PeriodicJobTrigger | ScheduledJobTrigger, config: any) {
this.name = name;
this.jobName = jobName;
this.config = config;
this.allowParallelRun = allowParallelRun;
this.trigger = trigger;
}
}
@SubConfigClass()
export class ServerDuplicatesConfig {
@ConfigProperty()
listingLimit: number = 1000; // maximum number of duplicates to list
}
@SubConfigClass()
export class JobConfig {
@ConfigProperty({type: 'integer', description: 'Job history size'})
maxSavedProgress: number = 10;
@ConfigProperty({arrayType: JobScheduleConfig})
scheduled: JobScheduleConfig[] = [
new JobScheduleConfig(DefaultsJobs[DefaultsJobs.Indexing],
DefaultsJobs[DefaultsJobs.Indexing],
false,
new NeverJobTrigger(), {}
),
new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Thumbnail Generation']],
DefaultsJobs[DefaultsJobs['Thumbnail Generation']],
false,
new AfterJobTrigger(DefaultsJobs[DefaultsJobs.Indexing]), {sizes: [240], indexedOnly: true}
),
new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Photo Converting']],
DefaultsJobs[DefaultsJobs['Photo Converting']],
false,
new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Thumbnail Generation']]), {indexedOnly: true}
),
new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Video Converting']],
DefaultsJobs[DefaultsJobs['Video Converting']],
false,
new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Photo Converting']]), {indexedOnly: true}
),
new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']],
DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']],
false,
new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Video Converting']]), {indexedOnly: true}
),
];
}
@SubConfigClass()
export class ServerLogConfig {
@ConfigProperty({type: LogLevel})
level: LogLevel = LogLevel.info;
@ConfigProperty({type: SQLLogLevel})
sqlLevel: SQLLogLevel = SQLLogLevel.error;
}
@SubConfigClass()
export class VideoTranscodingConfig {
@ConfigProperty({type: 'unsignedInt'})
bitRate: number = 5 * 1024 * 1024;
@ConfigProperty({type: 'unsignedInt'})
resolution: resolutionType = 720;
@ConfigProperty({type: 'positiveFloat'})
fps: number = 25;
@ConfigProperty()
codec: codecType = 'libx264';
@ConfigProperty()
format: formatType = 'mp4';
@ConfigProperty({
type: 'unsignedInt',
description: 'Constant Rate Factor. The range of the CRF scale is 0–51, where 0 is lossless, 23 is the default, and 51 is worst quality possible.',
max: 51
})
crf: number = 23;
@ConfigProperty({
type: FFmpegPresets,
description: 'A preset is a collection of options that will provide a certain encoding speed to compression ratio'
})
preset: FFmpegPresets = FFmpegPresets.medium;
@SubConfigClass()
export class NeverJobTrigger implements JobTrigger {
@ConfigProperty({type: JobTriggerType})
readonly type = JobTriggerType.never;
}
@ConfigProperty({arrayType: 'string', description: 'It will be sent to ffmpeg as it is, as custom options.'})
customOptions: string[] = [];
}
@SubConfigClass()
export class ScheduledJobTrigger implements JobTrigger {
@ConfigProperty({type: JobTriggerType})
readonly type = JobTriggerType.scheduled;
@SubConfigClass()
export class VideoConfig {
@ConfigProperty()
transcoding: VideoTranscodingConfig = new VideoTranscodingConfig();
}
@ConfigProperty()
time: number; // data time
}
@SubConfigClass()
export class PhotoConvertingConfig {
@ConfigProperty({description: 'Converts photos on the fly, when they are requested.'})
onTheFly: boolean = true;
@ConfigProperty({type: 'unsignedInt'})
resolution: resolutionType = 1080;
}
@SubConfigClass()
export class PeriodicJobTrigger implements JobTrigger {
@ConfigProperty({type: JobTriggerType})
readonly type = JobTriggerType.periodic;
@ConfigProperty()
periodicity: number; // 0-6: week days 7 every day
@ConfigProperty()
atTime: number; // day time
}
@SubConfigClass()
export class PhotoConfig {
@ConfigProperty()
Converting: PhotoConvertingConfig = new PhotoConvertingConfig();
}
@SubConfigClass()
export class AfterJobTrigger implements JobTrigger {
@SubConfigClass()
export class MediaConfig {
@ConfigProperty({description: 'Images are loaded from this folder (read permission required)'})
folder: string = 'demo/images';
@ConfigProperty({description: 'Thumbnails, coverted photos, videos will be stored here (write permission required)'})
tempFolder: string = 'demo/tmp';
@ConfigProperty()
Video: VideoConfig = new VideoConfig();
@ConfigProperty()
Photo: PhotoConfig = new PhotoConfig();
@ConfigProperty()
Thumbnail: ThumbnailConfig = new ThumbnailConfig();
}
@ConfigProperty({type: JobTriggerType})
readonly type = JobTriggerType.after;
@ConfigProperty()
afterScheduleName: string; // runs after schedule
@SubConfigClass()
export class EnvironmentConfig {
@ConfigProperty({volatile: true})
upTime: string;
@ConfigProperty({volatile: true})
appVersion: string;
@ConfigProperty({volatile: true})
buildTime: string;
@ConfigProperty({volatile: true})
buildCommitHash: string;
@ConfigProperty({volatile: true})
isDocker: boolean;
}
@SubConfigClass()
export class Config {
@ConfigProperty({volatile: true})
Environment: EnvironmentConfig = new EnvironmentConfig();
@ConfigProperty({arrayType: 'string'})
sessionSecret: string[] = [];
@ConfigProperty({type: 'unsignedInt', envAlias: 'PORT', min: 0, max: 65535})
port: number = 80;
@ConfigProperty()
host: string = '0.0.0.0';
@ConfigProperty()
Media: MediaConfig = new MediaConfig();
@ConfigProperty()
Threading: ThreadingConfig = new ThreadingConfig();
@ConfigProperty()
Database: DataBaseConfig = new DataBaseConfig();
@ConfigProperty()
Sharing: SharingConfig = new SharingConfig();
@ConfigProperty({type: 'unsignedInt', description: 'unit: ms'})
sessionTimeout: number = 1000 * 60 * 60 * 24 * 7; // in ms
@ConfigProperty()
Indexing: IndexingConfig = new IndexingConfig();
@ConfigProperty({type: 'unsignedInt', description: 'only this many bites will be loaded when scanning photo for metadata'})
photoMetadataSize: number = 512 * 1024; // only this many bites will be loaded when scanning photo for metadata
@ConfigProperty()
Duplicates: DuplicatesConfig = new DuplicatesConfig();
@ConfigProperty()
Log: LogConfig = new LogConfig();
@ConfigProperty()
Jobs: JobConfig = new JobConfig();
constructor(afterScheduleName?: string) {
this.afterScheduleName = afterScheduleName;
}
}
@SubConfigClass()
export class JobScheduleConfig implements JobScheduleDTO {
@ConfigProperty()
name: string;
@ConfigProperty()
jobName: string;
@ConfigProperty()
config: any = {};
@ConfigProperty()
allowParallelRun: boolean;
@ConfigProperty({
type: NeverJobTrigger,
typeBuilder: (v: JobTrigger) => {
const type = typeof v.type === 'number' ? v.type : JobTriggerType[v.type];
switch (type) {
case JobTriggerType.after:
return AfterJobTrigger;
case JobTriggerType.never:
return NeverJobTrigger;
case JobTriggerType.scheduled:
return ScheduledJobTrigger;
case JobTriggerType.periodic:
return PeriodicJobTrigger;
}
return null;
}
})
trigger: AfterJobTrigger | NeverJobTrigger | PeriodicJobTrigger | ScheduledJobTrigger;
constructor(name: string, jobName: string, allowParallelRun: boolean,
trigger: AfterJobTrigger | NeverJobTrigger | PeriodicJobTrigger | ScheduledJobTrigger, config: any) {
this.name = name;
this.jobName = jobName;
this.config = config;
this.allowParallelRun = allowParallelRun;
this.trigger = trigger;
}
}
@SubConfigClass()
export class ServerJobConfig {
@ConfigProperty({type: 'integer', description: 'Job history size'})
maxSavedProgress: number = 10;
@ConfigProperty({arrayType: JobScheduleConfig})
scheduled: JobScheduleConfig[] = [
new JobScheduleConfig(DefaultsJobs[DefaultsJobs.Indexing],
DefaultsJobs[DefaultsJobs.Indexing],
false,
new NeverJobTrigger(), {}
),
new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Thumbnail Generation']],
DefaultsJobs[DefaultsJobs['Thumbnail Generation']],
false,
new AfterJobTrigger(DefaultsJobs[DefaultsJobs.Indexing]), {sizes: [240], indexedOnly: true}
),
new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Photo Converting']],
DefaultsJobs[DefaultsJobs['Photo Converting']],
false,
new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Thumbnail Generation']]), {indexedOnly: true}
),
new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Video Converting']],
DefaultsJobs[DefaultsJobs['Video Converting']],
false,
new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Photo Converting']]), {indexedOnly: true}
),
new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']],
DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']],
false,
new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Video Converting']]), {indexedOnly: true}
),
];
}
@SubConfigClass()
export class VideoTranscodingConfig {
@ConfigProperty({type: 'unsignedInt'})
bitRate: number = 5 * 1024 * 1024;
@ConfigProperty({type: 'unsignedInt'})
resolution: videoResolutionType = 720;
@ConfigProperty({type: 'positiveFloat'})
fps: number = 25;
@ConfigProperty()
codec: videoCodecType = 'libx264';
@ConfigProperty()
format: videoFormatType = 'mp4';
@ConfigProperty({
type: 'unsignedInt',
description: 'Constant Rate Factor. The range of the CRF scale is 0–51, where 0 is lossless, 23 is the default, and 51 is worst quality possible.',
max: 51
})
crf: number = 23;
@ConfigProperty({
type: FFmpegPresets,
description: 'A preset is a collection of options that will provide a certain encoding speed to compression ratio'
})
preset: FFmpegPresets = FFmpegPresets.medium;
@ConfigProperty({arrayType: 'string', description: 'It will be sent to ffmpeg as it is, as custom options.'})
customOptions: string[] = [];
}
@SubConfigClass()
export class ServerVideoConfig {
@ConfigProperty()
transcoding: VideoTranscodingConfig = new VideoTranscodingConfig();
}
@SubConfigClass()
export class PhotoConvertingConfig {
@ConfigProperty({description: 'Converts photos on the fly, when they are requested.'})
onTheFly: boolean = true;
@ConfigProperty({type: 'unsignedInt'})
resolution: videoResolutionType = 1080;
}
@SubConfigClass()
export class ServerPhotoConfig {
@ConfigProperty()
Converting: PhotoConvertingConfig = new PhotoConvertingConfig();
}
@SubConfigClass()
export class ServerMediaConfig {
@ConfigProperty({description: 'Images are loaded from this folder (read permission required)'})
folder: string = 'demo/images';
@ConfigProperty({description: 'Thumbnails, coverted photos, videos will be stored here (write permission required)'})
tempFolder: string = 'demo/tmp';
@ConfigProperty()
Video: ServerVideoConfig = new ServerVideoConfig();
@ConfigProperty()
Photo: ServerPhotoConfig = new ServerPhotoConfig();
@ConfigProperty()
Thumbnail: ServerThumbnailConfig = new ServerThumbnailConfig();
}
@SubConfigClass()
export class ServerEnvironmentConfig {
@ConfigProperty({volatile: true})
upTime: string;
@ConfigProperty({volatile: true})
appVersion: string;
@ConfigProperty({volatile: true})
buildTime: string;
@ConfigProperty({volatile: true})
buildCommitHash: string;
@ConfigProperty({volatile: true})
isDocker: boolean;
}
@SubConfigClass()
export class ServerConfig {
@ConfigProperty({volatile: true})
Environment: ServerEnvironmentConfig = new ServerEnvironmentConfig();
@ConfigProperty({arrayType: 'string'})
sessionSecret: string[] = [];
@ConfigProperty({type: 'unsignedInt', envAlias: 'PORT', min: 0, max: 65535})
port: number = 80;
@ConfigProperty()
host: string = '0.0.0.0';
@ConfigProperty()
Media: ServerMediaConfig = new ServerMediaConfig();
@ConfigProperty()
Threading: ServerThreadingConfig = new ServerThreadingConfig();
@ConfigProperty()
Database: ServerDataBaseConfig = new ServerDataBaseConfig();
@ConfigProperty()
Sharing: ServerSharingConfig = new ServerSharingConfig();
@ConfigProperty({type: 'unsignedInt', description: 'unit: ms'})
sessionTimeout: number = 1000 * 60 * 60 * 24 * 7; // in ms
@ConfigProperty()
Indexing: ServerIndexingConfig = new ServerIndexingConfig();
@ConfigProperty({type: 'unsignedInt', description: 'only this many bites will be loaded when scanning photo for metadata'})
photoMetadataSize: number = 512 * 1024; // only this many bites will be loaded when scanning photo for metadata
@ConfigProperty()
Duplicates: ServerDuplicatesConfig = new ServerDuplicatesConfig();
@ConfigProperty()
Log: ServerLogConfig = new ServerLogConfig();
@ConfigProperty()
Jobs: ServerJobConfig = new ServerJobConfig();
}
export interface IPrivateConfig {
Server: ServerConfig.Config;
Client: ClientConfig.Config;
Server: ServerConfig;
Client: ClientConfig;
}

View File

@@ -12,8 +12,8 @@ export class WebConfig {
State: any;
@ConfigProperty()
Server: ServerConfig.Config = new ServerConfig.Config();
Server: ServerConfig = new ServerConfig();
@ConfigProperty()
Client: ClientConfig.Config = new ClientConfig.Config();
Client: ClientConfig = new ClientConfig();
}

View File

@@ -5,187 +5,185 @@ import {UserRoles} from '../../entities/UserDTO';
import {ConfigProperty, SubConfigClass} from 'typeconfig/common';
export module ClientConfig {
export enum MapProviders {
OpenStreetMap = 1, Mapbox = 2, Custom = 3
}
@SubConfigClass()
export class AutoCompleteConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty({type: 'unsignedInt'})
maxItemsPerCategory: number = 5;
@ConfigProperty({type: 'unsignedInt'})
cacheTimeout: number = 1000 * 60 * 60;
}
@SubConfigClass()
export class SearchConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty({type: 'unsignedInt'})
searchCacheTimeout: number = 1000 * 60 * 60;
@ConfigProperty()
AutoComplete: AutoCompleteConfig = new AutoCompleteConfig();
@ConfigProperty({type: 'unsignedInt'})
maxMediaResult: number = 2000;
@ConfigProperty({type: 'unsignedInt'})
maxDirectoryResult: number = 200;
}
@SubConfigClass()
export class SharingConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty()
passwordProtected: boolean = true;
}
@SubConfigClass()
export class RandomPhotoConfig {
@ConfigProperty()
enabled: boolean = true;
}
@SubConfigClass()
export class MapLayers {
@ConfigProperty()
name: string = 'street';
@ConfigProperty()
url: string = '';
}
@SubConfigClass()
export class MapConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty({type: 'unsignedInt', description: 'Maximum number of markers to be shown on the map preview on the gallery page.'})
maxPreviewMarkers: number = 50;
@ConfigProperty()
useImageMarkers: boolean = true;
@ConfigProperty({type: MapProviders})
mapProvider: MapProviders = MapProviders.OpenStreetMap;
@ConfigProperty()
mapboxAccessToken: string = '';
@ConfigProperty({arrayType: MapLayers})
customLayers: MapLayers[] = [new MapLayers()];
}
@SubConfigClass()
export class ThumbnailConfig {
@ConfigProperty({type: 'unsignedInt', max: 100})
iconSize: number = 45;
@ConfigProperty({type: 'unsignedInt'})
personThumbnailSize: number = 200;
@ConfigProperty({arrayType: 'unsignedInt'})
thumbnailSizes: number[] = [240, 480];
@ConfigProperty({volatile: true})
concurrentThumbnailGenerations: number = 1;
}
@SubConfigClass()
export class NavBarConfig {
@ConfigProperty()
showItemCount: boolean = true;
}
@SubConfigClass()
export class OtherConfig {
@ConfigProperty()
enableCache: boolean = true;
@ConfigProperty()
enableOnScrollRendering: boolean = true;
@ConfigProperty({type: SortingMethods})
defaultPhotoSortingMethod: SortingMethods = SortingMethods.ascDate;
@ConfigProperty({description: 'If enabled directories will be sorted by date, like photos, otherwise by name. Directory date is the last modification time of that directory not the creation date of the oldest photo'})
enableDirectorySortingByDate: boolean = false;
@ConfigProperty()
enableOnScrollThumbnailPrioritising: boolean = true;
@ConfigProperty()
NavBar: NavBarConfig = new NavBarConfig();
@ConfigProperty()
captionFirstNaming: boolean = false; // shows the caption instead of the filename in the photo grid
}
@SubConfigClass()
export class VideoConfig {
@ConfigProperty()
enabled: boolean = true;
}
@SubConfigClass()
export class PhotoConvertingConfig {
@ConfigProperty()
enabled: boolean = true;
}
@SubConfigClass()
export class PhotoConfig {
@ConfigProperty()
Converting: PhotoConvertingConfig = new PhotoConvertingConfig();
}
@SubConfigClass()
export class MediaConfig {
@ConfigProperty()
Thumbnail: ThumbnailConfig = new ThumbnailConfig();
@ConfigProperty()
Video: VideoConfig = new VideoConfig();
@ConfigProperty()
Photo: PhotoConfig = new PhotoConfig();
}
@SubConfigClass()
export class MetaFileConfig {
@ConfigProperty()
enabled: boolean = true;
}
@SubConfigClass()
export class FacesConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty()
keywordsToPersons: boolean = true;
@ConfigProperty({type: UserRoles})
writeAccessMinRole: UserRoles = UserRoles.Admin;
@ConfigProperty({type: UserRoles})
readAccessMinRole: UserRoles = UserRoles.User;
}
@SubConfigClass()
export class Config {
@ConfigProperty()
applicationTitle: string = 'PiGallery 2';
@ConfigProperty()
publicUrl: string = '';
@ConfigProperty()
urlBase: string = '';
@ConfigProperty()
Search: SearchConfig = new SearchConfig();
@ConfigProperty()
Sharing: SharingConfig = new SharingConfig();
@ConfigProperty()
Map: MapConfig = new MapConfig();
@ConfigProperty()
RandomPhoto: RandomPhotoConfig = new RandomPhotoConfig();
@ConfigProperty()
Other: OtherConfig = new OtherConfig();
@ConfigProperty()
authenticationRequired: boolean = true;
@ConfigProperty({type: UserRoles})
unAuthenticatedUserRole: UserRoles = UserRoles.Admin;
@ConfigProperty({arrayType: 'string', volatile: true})
languages: string[];
@ConfigProperty()
Media: MediaConfig = new MediaConfig();
@ConfigProperty()
MetaFile: MetaFileConfig = new MetaFileConfig();
@ConfigProperty()
Faces: FacesConfig = new FacesConfig();
}
export enum MapProviders {
OpenStreetMap = 1, Mapbox = 2, Custom = 3
}
@SubConfigClass()
export class AutoCompleteConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty({type: 'unsignedInt'})
maxItemsPerCategory: number = 5;
@ConfigProperty({type: 'unsignedInt'})
cacheTimeout: number = 1000 * 60 * 60;
}
@SubConfigClass()
export class ClientSearchConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty({type: 'unsignedInt'})
searchCacheTimeout: number = 1000 * 60 * 60;
@ConfigProperty()
AutoComplete: AutoCompleteConfig = new AutoCompleteConfig();
@ConfigProperty({type: 'unsignedInt'})
maxMediaResult: number = 2000;
@ConfigProperty({type: 'unsignedInt'})
maxDirectoryResult: number = 200;
}
@SubConfigClass()
export class ClientSharingConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty()
passwordProtected: boolean = true;
}
@SubConfigClass()
export class ClientRandomPhotoConfig {
@ConfigProperty()
enabled: boolean = true;
}
@SubConfigClass()
export class MapLayers {
@ConfigProperty()
name: string = 'street';
@ConfigProperty()
url: string = '';
}
@SubConfigClass()
export class ClientMapConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty({type: 'unsignedInt', description: 'Maximum number of markers to be shown on the map preview on the gallery page.'})
maxPreviewMarkers: number = 50;
@ConfigProperty()
useImageMarkers: boolean = true;
@ConfigProperty({type: MapProviders})
mapProvider: MapProviders = MapProviders.OpenStreetMap;
@ConfigProperty()
mapboxAccessToken: string = '';
@ConfigProperty({arrayType: MapLayers})
customLayers: MapLayers[] = [new MapLayers()];
}
@SubConfigClass()
export class ClientThumbnailConfig {
@ConfigProperty({type: 'unsignedInt', max: 100})
iconSize: number = 45;
@ConfigProperty({type: 'unsignedInt'})
personThumbnailSize: number = 200;
@ConfigProperty({arrayType: 'unsignedInt'})
thumbnailSizes: number[] = [240, 480];
@ConfigProperty({volatile: true})
concurrentThumbnailGenerations: number = 1;
}
@SubConfigClass()
export class NavBarConfig {
@ConfigProperty()
showItemCount: boolean = true;
}
@SubConfigClass()
export class ClientOtherConfig {
@ConfigProperty()
enableCache: boolean = true;
@ConfigProperty()
enableOnScrollRendering: boolean = true;
@ConfigProperty({type: SortingMethods})
defaultPhotoSortingMethod: SortingMethods = SortingMethods.ascDate;
@ConfigProperty({description: 'If enabled directories will be sorted by date, like photos, otherwise by name. Directory date is the last modification time of that directory not the creation date of the oldest photo'})
enableDirectorySortingByDate: boolean = false;
@ConfigProperty()
enableOnScrollThumbnailPrioritising: boolean = true;
@ConfigProperty()
NavBar: NavBarConfig = new NavBarConfig();
@ConfigProperty()
captionFirstNaming: boolean = false; // shows the caption instead of the filename in the photo grid
}
@SubConfigClass()
export class ClientVideoConfig {
@ConfigProperty()
enabled: boolean = true;
}
@SubConfigClass()
export class PhotoConvertingConfig {
@ConfigProperty()
enabled: boolean = true;
}
@SubConfigClass()
export class ClientPhotoConfig {
@ConfigProperty()
Converting: PhotoConvertingConfig = new PhotoConvertingConfig();
}
@SubConfigClass()
export class ClientMediaConfig {
@ConfigProperty()
Thumbnail: ClientThumbnailConfig = new ClientThumbnailConfig();
@ConfigProperty()
Video: ClientVideoConfig = new ClientVideoConfig();
@ConfigProperty()
Photo: ClientPhotoConfig = new ClientPhotoConfig();
}
@SubConfigClass()
export class ClientMetaFileConfig {
@ConfigProperty()
enabled: boolean = true;
}
@SubConfigClass()
export class ClientFacesConfig {
@ConfigProperty()
enabled: boolean = true;
@ConfigProperty()
keywordsToPersons: boolean = true;
@ConfigProperty({type: UserRoles})
writeAccessMinRole: UserRoles = UserRoles.Admin;
@ConfigProperty({type: UserRoles})
readAccessMinRole: UserRoles = UserRoles.User;
}
@SubConfigClass()
export class ClientConfig {
@ConfigProperty()
applicationTitle: string = 'PiGallery 2';
@ConfigProperty()
publicUrl: string = '';
@ConfigProperty()
urlBase: string = '';
@ConfigProperty()
Search: ClientSearchConfig = new ClientSearchConfig();
@ConfigProperty()
Sharing: ClientSharingConfig = new ClientSharingConfig();
@ConfigProperty()
Map: ClientMapConfig = new ClientMapConfig();
@ConfigProperty()
RandomPhoto: ClientRandomPhotoConfig = new ClientRandomPhotoConfig();
@ConfigProperty()
Other: ClientOtherConfig = new ClientOtherConfig();
@ConfigProperty()
authenticationRequired: boolean = true;
@ConfigProperty({type: UserRoles})
unAuthenticatedUserRole: UserRoles = UserRoles.Admin;
@ConfigProperty({arrayType: 'string', volatile: true})
languages: string[];
@ConfigProperty()
Media: ClientMediaConfig = new ClientMediaConfig();
@ConfigProperty()
MetaFile: ClientMetaFileConfig = new ClientMetaFileConfig();
@ConfigProperty()
Faces: ClientFacesConfig = new ClientFacesConfig();
}

View File

@@ -11,10 +11,12 @@ import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty
export class ClientClass {
@ConfigProperty()
public Client: ClientConfig.Config = new ClientConfig.Config();
public Client: ClientConfig = new ClientConfig();
}
declare module ServerInject {
// ConfigInject is getting injected form the server side to the global scope
// tslint:disable-next-line:no-namespace
declare namespace ServerInject {
export const ConfigInject: ClientClass;
}

View File

@@ -9,7 +9,7 @@ export class AutoCompleteItem implements IAutoCompleteItem {
constructor(public text: string, public type: SearchQueryTypes = null) {
}
equals(other: AutoCompleteItem) {
equals(other: AutoCompleteItem): boolean {
return this.text === other.text && this.type === other.type;
}
}

View File

@@ -1,4 +1,4 @@
import {MediaDTO} from './MediaDTO';
import {MediaDTO, MediaDTOUtils} from './MediaDTO';
import {FileDTO} from './FileDTO';
import {PhotoDTO, PreviewPhotoDTO} from './PhotoDTO';
import {Utils} from '../Utils';
@@ -23,8 +23,8 @@ export interface DirectoryDTO<S extends FileDTO = MediaDTO> extends DirectoryBas
metaFile: FileDTO[];
}
export module DirectoryDTO {
export const unpackDirectory = (dir: DirectoryDTO): void => {
export const DirectoryDTOUtils = {
unpackDirectory: (dir: DirectoryDTO): void => {
dir.media.forEach((media: MediaDTO) => {
media.directory = dir;
});
@@ -37,13 +37,13 @@ export module DirectoryDTO {
if (dir.directories) {
dir.directories.forEach((directory: DirectoryDTO) => {
unpackDirectory(directory);
DirectoryDTOUtils.unpackDirectory(directory);
directory.parent = dir;
});
}
};
},
export const packDirectory = (dir: DirectoryDTO): DirectoryDTO => {
packDirectory: (dir: DirectoryDTO): DirectoryDTO => {
if (dir.preview) {
dir.preview.directory = {
path: dir.preview.directory.path,
@@ -68,18 +68,18 @@ export module DirectoryDTO {
}
if (dir.directories) {
dir.directories.forEach((directory: DirectoryDTO) => {
packDirectory(directory);
DirectoryDTOUtils.packDirectory(directory);
directory.parent = null;
});
}
return dir;
};
export const filterPhotos = (dir: DirectoryDTO): PhotoDTO[] => {
return <PhotoDTO[]>dir.media.filter(m => MediaDTO.isPhoto(m));
};
export const filterVideos = (dir: DirectoryDTO): PhotoDTO[] => {
return <PhotoDTO[]>dir.media.filter(m => MediaDTO.isPhoto(m));
};
}
},
filterPhotos: (dir: DirectoryDTO): PhotoDTO[] => {
return dir.media.filter(m => MediaDTOUtils.isPhoto(m)) as PhotoDTO[];
},
filterVideos: (dir: DirectoryDTO): PhotoDTO[] => {
return dir.media.filter(m => MediaDTOUtils.isPhoto(m)) as PhotoDTO[];
}
};

View File

@@ -34,23 +34,22 @@ export interface MediaDimension {
height: number;
}
export module MediaDTO {
export const hasPositionData = (media: MediaBaseDTO): boolean => {
return !!(<PhotoDTO>media).metadata.positionData &&
!!((<PhotoDTO>media).metadata.positionData.city ||
(<PhotoDTO>media).metadata.positionData.state ||
(<PhotoDTO>media).metadata.positionData.country ||
((<PhotoDTO>media).metadata.positionData.GPSData &&
(<PhotoDTO>media).metadata.positionData.GPSData.altitude &&
(<PhotoDTO>media).metadata.positionData.GPSData.latitude &&
(<PhotoDTO>media).metadata.positionData.GPSData.longitude));
};
export const MediaDTOUtils = {
hasPositionData: (media: MediaBaseDTO): 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.altitude &&
(media as PhotoDTO).metadata.positionData.GPSData.latitude &&
(media as PhotoDTO).metadata.positionData.GPSData.longitude));
},
isPhoto: (media: FileBaseDTO): boolean => {
return !MediaDTOUtils.isVideo(media);
},
export const isPhoto = (media: FileBaseDTO): boolean => {
return !MediaDTO.isVideo(media);
};
export const isVideo = (media: FileBaseDTO): boolean => {
isVideo: (media: FileBaseDTO): boolean => {
const lower = media.name.toLowerCase();
for (const ext of SupportedFormats.WithDots.Videos) {
if (lower.endsWith(ext)) {
@@ -58,9 +57,9 @@ export module MediaDTO {
}
}
return false;
};
},
export const isVideoPath = (path: string): boolean => {
isVideoPath: (path: string): boolean => {
const lower = path.toLowerCase();
for (const ext of SupportedFormats.WithDots.Videos) {
if (lower.endsWith(ext)) {
@@ -68,9 +67,9 @@ export module MediaDTO {
}
}
return false;
};
},
export const isVideoTranscodingNeeded = (media: FileBaseDTO): boolean => {
isVideoTranscodingNeeded: (media: FileBaseDTO): boolean => {
const lower = media.name.toLowerCase();
for (const ext of SupportedFormats.WithDots.TranscodeNeed.Videos) {
if (lower.endsWith(ext)) {
@@ -78,10 +77,10 @@ export module MediaDTO {
}
}
return false;
};
},
export const calcAspectRatio = (photo: MediaBaseDTO): number => {
calcAspectRatio: (photo: MediaBaseDTO): number => {
return photo.metadata.size.width / photo.metadata.size.height;
};
}
}
};

View File

@@ -72,26 +72,26 @@ export enum TextSearchQueryMatchTypes {
}
export namespace SearchQueryDTO {
export const getRangedQueryPair = (type: SearchQueryTypes): SearchQueryTypes => {
export const SearchQueryDTOUtils = {
getRangedQueryPair: (type: SearchQueryTypes): SearchQueryTypes => {
if (rangedTypePairs[type]) {
return rangedTypePairs[type];
}
throw new Error('Unknown ranged type');
};
export const negate = (query: SearchQueryDTO): SearchQueryDTO => {
},
negate: (query: SearchQueryDTO): SearchQueryDTO => {
switch (query.type) {
case SearchQueryTypes.AND:
query.type = SearchQueryTypes.OR;
(<SearchListQuery>query).list = (<SearchListQuery>query).list.map(q => SearchQueryDTO.negate(q));
(query as SearchListQuery).list = (query as SearchListQuery).list.map(q => SearchQueryDTOUtils.negate(q));
return query;
case SearchQueryTypes.OR:
query.type = SearchQueryTypes.AND;
(<SearchListQuery>query).list = (<SearchListQuery>query).list.map(q => SearchQueryDTO.negate(q));
(query as SearchListQuery).list = (query as SearchListQuery).list.map(q => SearchQueryDTOUtils.negate(q));
return query;
case SearchQueryTypes.orientation:
(<OrientationSearch>query).landscape = !(<OrientationSearch>query).landscape;
(query as OrientationSearch).landscape = !(query as OrientationSearch).landscape;
return query;
case SearchQueryTypes.from_date:
@@ -108,7 +108,7 @@ export namespace SearchQueryDTO {
case SearchQueryTypes.caption:
case SearchQueryTypes.file_name:
case SearchQueryTypes.directory:
(<NegatableSearchQuery>query).negate = !(<NegatableSearchQuery>query).negate;
(query as NegatableSearchQuery).negate = !(query as NegatableSearchQuery).negate;
return query;
case SearchQueryTypes.SOME_OF:
@@ -117,8 +117,8 @@ export namespace SearchQueryDTO {
default:
throw new Error('Unknown type' + query.type);
}
};
}
}
};
export interface SearchQueryDTO {
type: SearchQueryTypes;

View File

@@ -19,9 +19,9 @@ export interface UserDTO {
permissions: string[]; // user can only see these permissions. if ends with *, its recursive
}
export module UserDTO {
export const UserDTOUtils = {
export const isDirectoryPathAvailable = (path: string, permissions: string[]): boolean => {
isDirectoryPathAvailable: (path: string, permissions: string[]): boolean => {
if (permissions == null) {
return true;
}
@@ -30,9 +30,8 @@ export module UserDTO {
if (permissions.length === 0 || permissions[0] === '/*') {
return true;
}
for (let i = 0; i < permissions.length; i++) {
let permission = permissions[i];
if (permissions[i] === '/*') {
for (let permission of permissions) {
if (permission === '/*') {
return true;
}
if (permission[permission.length - 1] === '*') {
@@ -48,10 +47,11 @@ export module UserDTO {
}
return false;
};
},
export const isDirectoryAvailable = (directory: DirectoryDTO, permissions: string[]): boolean => {
return isDirectoryPathAvailable(
isDirectoryAvailable: (directory: DirectoryDTO, permissions: string[]): boolean => {
return UserDTOUtils.isDirectoryPathAvailable(
Utils.concatUrls(directory.path, directory.name), permissions);
};
}
}
};

View File

@@ -25,8 +25,8 @@ export interface JobDTO {
ConfigTemplate: ConfigTemplateEntry[];
}
export module JobDTO {
export const getHashName = (jobName: string, config: any = {}) => {
export const JobDTOUtils = {
getHashName: (jobName: string, config: any = {}) => {
return jobName + '-' + JSON.stringify(config);
};
}
}
};

View File

@@ -35,9 +35,9 @@ export interface JobScheduleDTO {
}
export module JobScheduleDTO {
export const JobScheduleDTOUtils = {
const getNextDayOfTheWeek = (refDate: Date, dayOfWeek: number) => {
getNextDayOfTheWeek: (refDate: Date, dayOfWeek: number) => {
const date = new Date(refDate);
date.setDate(refDate.getDate() + (dayOfWeek + 1 + 7 - refDate.getDay()) % 7);
if (date.getDay() === refDate.getDay()) {
@@ -45,9 +45,9 @@ export module JobScheduleDTO {
}
date.setHours(0, 0, 0, 0);
return date;
};
},
const nextValidDate = (date: Date, h: number, m: number, dayDiff: number): Date => {
nextValidDate: (date: Date, h: number, m: number, dayDiff: number): Date => {
date.setSeconds(0);
if (date.getHours() < h || (date.getHours() === h && date.getMinutes() < m)) {
@@ -59,9 +59,9 @@ export module JobScheduleDTO {
date.setMinutes(m);
}
return date;
};
},
export const getNextRunningDate = (refDate: Date, schedule: JobScheduleDTO): Date => {
getNextRunningDate: (refDate: Date, schedule: JobScheduleDTO): Date => {
switch (schedule.trigger.type) {
case JobTriggerType.scheduled:
return new Date(schedule.trigger.time);
@@ -73,13 +73,13 @@ export module JobScheduleDTO {
const minute = (schedule.trigger.atTime / 1000 / 60) % 60;
if (schedule.trigger.periodicity <= 6) { // Between Monday and Sunday
const nextRunDate = getNextDayOfTheWeek(refDate, schedule.trigger.periodicity);
return nextValidDate(nextRunDate, hour, minute, 7 * 24 * 60 * 60 * 1000);
const nextRunDate = JobScheduleDTOUtils.getNextDayOfTheWeek(refDate, schedule.trigger.periodicity);
return JobScheduleDTOUtils.nextValidDate(nextRunDate, hour, minute, 7 * 24 * 60 * 60 * 1000);
}
// every day
return nextValidDate(new Date(refDate), hour, minute, 24 * 60 * 60 * 1000);
return JobScheduleDTOUtils.nextValidDate(new Date(refDate), hour, minute, 24 * 60 * 60 * 1000);
}
return null;
};
}
}
};

View File

@@ -1,8 +1,8 @@
import {ServerConfig} from '../../config/private/PrivateConfig';
import {ClientConfig} from '../../config/public/ClientConfig';
import {ServerConfig, ServerThreadingConfig} from '../../config/private/PrivateConfig';
import {ClientConfig, ClientOtherConfig} from '../../config/public/ClientConfig';
export interface OtherConfigDTO {
Server: ServerConfig.ThreadingConfig;
Client: ClientConfig.OtherConfig;
Server: ServerThreadingConfig;
Client: ClientOtherConfig;
}

View File

@@ -1,21 +1,21 @@
export class Event2Args<T, M> {
private handlers: ((data?: T, data2?: M) => void)[] = [];
public on(handler: (data?: T, data2?: M) => void) {
public on(handler: (data?: T, data2?: M) => void): void {
this.handlers.push(handler);
}
public off(handler: (data?: T, data2?: M) => void) {
this.handlers = this.handlers.filter(h => h !== handler);
public off(handler: (data?: T, data2?: M) => void): void {
this.handlers = this.handlers.filter((h): boolean => h !== handler);
}
public allOff() {
public allOff(): void {
this.handlers = [];
}
public trigger(data?: T, data2?: M) {
public trigger(data?: T, data2?: M): void {
if (this.handlers) {
this.handlers.slice(0).forEach(h => h(data, data2));
this.handlers.slice(0).forEach((h): void => h(data, data2));
}
}
}

View File

@@ -4,25 +4,25 @@ export class EventLimit<T> {
private handlers: Array<EventLimitHandler<T>> = [];
public on(limit: T, handler: (data?: T) => void) {
public on(limit: T, handler: (data?: T) => void): void {
this.handlers.push(new EventLimitHandler(limit, handler));
if (this.lastTriggerValue != null) {
this.trigger(this.lastTriggerValue);
}
}
public onSingle(limit: T, handler: (data?: T) => void) {
public onSingle(limit: T, handler: (data?: T) => void): void {
this.handlers.push(new SingleFireEventLimitHandler(limit, handler));
if (this.lastTriggerValue != null) {
this.trigger(this.lastTriggerValue);
}
}
public off(limit: T, handler: (data?: T) => void) {
public off(limit: T, handler: (data?: T) => void): void {
this.handlers = this.handlers.filter(h => h.handler !== handler && h.limit !== limit);
}
public allOff() {
public allOff(): void {
this.handlers = [];
}
@@ -46,7 +46,7 @@ class EventLimitHandler<T> {
constructor(public limit: T, public handler: (data?: T) => void) {
}
public fire(data?: T) {
public fire(data?: T): void {
this.handler(data);
}
@@ -62,7 +62,7 @@ class SingleFireEventLimitHandler<T> extends EventLimitHandler<T> {
super(limit, handler);
}
public fire(data?: T) {
public fire(data?: T): void {
if (this.fired === false) {
this.handler(data);
}

View File

@@ -9,8 +9,8 @@ export interface Dimension {
height: number;
}
export module Dimension {
export const toString = (dim: Dimension) => {
export const DimensionUtils = {
toString: (dim: Dimension) => {
return {top: dim.top + 'px', left: dim.left + 'px', width: dim.width + 'px', height: dim.height + 'px'};
};
}
}
};

View File

@@ -7,34 +7,34 @@ import {ShareService} from '../ui/gallery/share.service';
export class NavigationService {
constructor(private _router: Router,
private _shareService: ShareService) {
constructor(private router: Router,
private shareService: ShareService) {
}
public isLoginPage() {
return this._router.isActive('login', true) || this._router.isActive('shareLogin', true);
public isLoginPage(): boolean {
return this.router.isActive('login', true) || this.router.isActive('shareLogin', true);
}
public async toLogin() {
await this._shareService.wait();
if (this._shareService.isSharing()) {
return this._router.navigate(['shareLogin'], {queryParams: {sk: this._shareService.getSharingKey()}});
public async toLogin(): Promise<boolean> {
await this.shareService.wait();
if (this.shareService.isSharing()) {
return this.router.navigate(['shareLogin'], {queryParams: {sk: this.shareService.getSharingKey()}});
} else {
return this._router.navigate(['login']);
return this.router.navigate(['login']);
}
}
public async toGallery() {
await this._shareService.wait();
if (this._shareService.isSharing()) {
return this._router.navigate(['share', this._shareService.getSharingKey()]);
public async toGallery(): Promise<boolean> {
await this.shareService.wait();
if (this.shareService.isSharing()) {
return this.router.navigate(['share', this.shareService.getSharingKey()]);
} else {
return this._router.navigate(['gallery', '']);
return this.router.navigate(['gallery', '']);
}
}
public async search(searchText: string) {
return this._router.navigate(['search', searchText]);
public async search(searchText: string): Promise<boolean> {
return this.router.navigate(['search', searchText]);
}
}

View File

@@ -10,7 +10,7 @@ import {ShareService} from '../../ui/gallery/share.service';
class MockUserService {
public login(credential: LoginCredential): Promise<UserDTO> {
return Promise.resolve(<UserDTO>{name: 'testUserName'});
return Promise.resolve({name: 'testUserName'} as UserDTO);
}
public async getSessionUser(): Promise<UserDTO> {
@@ -19,12 +19,12 @@ class MockUserService {
}
class MockNetworkService {
addGlobalErrorHandler(fn: (error: ErrorDTO) => boolean) {
addGlobalErrorHandler(fn: (error: ErrorDTO) => boolean): void {
}
}
class MockShareService {
onNewUser(user: any) {
onNewUser(user: any): void {
}
}
@@ -71,7 +71,7 @@ describe('AuthenticationService', () => {
expect(authService.isAuthenticated()).toBe(true);
done();
});
authService.login(<any>{});
authService.login({} as any);
})());
});

View File

@@ -8,19 +8,19 @@ import {ShareService} from '../../ui/gallery/share.service';
import {VersionService} from '../version.service';
class MockShareService {
wait() {
wait(): Promise<boolean> {
return Promise.resolve(true);
}
isSharing() {
isSharing(): boolean {
return false;
}
}
describe('UserService', () => {
describe('UserService', (): void => {
beforeEach(() => {
beforeEach((): void => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
@@ -34,20 +34,20 @@ describe('UserService', () => {
});
it('should call postJson at login', inject([UserService, NetworkService],
async (userService: UserService, networkService: NetworkService) => {
async (userService: UserService, networkService: NetworkService): Promise<void> => {
spyOn(networkService, 'postJson');
const credential = new LoginCredential('name', 'pass');
await userService.login(credential);
expect(networkService.postJson).toHaveBeenCalled();
expect((<any>networkService.postJson).calls.argsFor(0)).toEqual(['/user/login', {'loginCredential': credential}]);
expect((networkService.postJson as any).calls.argsFor(0)).toEqual(['/user/login', {loginCredential: credential}]);
}));
it('should call getJson at getSessionUser', inject([UserService, NetworkService],
async (userService: UserService, networkService: NetworkService) => {
async (userService: UserService, networkService: NetworkService): Promise<void> => {
spyOn(networkService, 'getJson');
await userService.getSessionUser();
expect(networkService.getJson).toHaveBeenCalled();
expect((<any>networkService.getJson).calls.argsFor(0)).toEqual(['/user/me']);
expect((networkService.getJson as any).calls.argsFor(0)).toEqual(['/user/me']);
}));

View File

@@ -9,33 +9,33 @@ import {QueryParams} from '../../../../common/QueryParams';
@Injectable()
export class UserService {
constructor(private _networkService: NetworkService,
private _shareService: ShareService) {
constructor(private networkService: NetworkService,
private shareService: ShareService) {
}
public async logout(): Promise<string> {
return this._networkService.postJson('/user/logout');
return this.networkService.postJson('/user/logout');
}
public async login(credential: LoginCredential): Promise<UserDTO> {
return this._networkService.postJson<UserDTO>('/user/login', {loginCredential: credential});
return this.networkService.postJson<UserDTO>('/user/login', {loginCredential: credential});
}
public async shareLogin(password: string): Promise<UserDTO> {
return this._networkService.postJson<UserDTO>('/share/login?' + QueryParams.gallery.sharingKey_query
+ '=' + this._shareService.getSharingKey(), {'password': password});
return this.networkService.postJson<UserDTO>('/share/login?' + QueryParams.gallery.sharingKey_query
+ '=' + this.shareService.getSharingKey(), {password});
}
public async getSessionUser(): Promise<UserDTO> {
await this._shareService.wait();
await this.shareService.wait();
if (Config.Client.Sharing.enabled === true) {
if (this._shareService.isSharing()) {
if (this.shareService.isSharing()) {
const query: any = {};
query[QueryParams.gallery.sharingKey_query] = this._shareService.getSharingKey();
return this._networkService.getJson<UserDTO>('/user/me', query);
query[QueryParams.gallery.sharingKey_query] = this.shareService.getSharingKey();
return this.networkService.getJson<UserDTO>('/user/me', query);
}
}
return this._networkService.getJson<UserDTO>('/user/me');
return this.networkService.getJson<UserDTO>('/user/me');
}
}

View File

@@ -25,7 +25,7 @@ export class PageHelper {
return this.supportPageOffset ? window.pageXOffset : this.isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft;
}
public static showScrollY() {
public static showScrollY(): void {
PageHelper.body.style.overflowY = 'scroll';
}
@@ -33,7 +33,7 @@ export class PageHelper {
return PageHelper.body.style.overflowY === 'scroll';
}
public static hideScrollY() {
public static hideScrollY(): void {
PageHelper.body.style.overflowY = 'hidden';
}
}

View File

@@ -14,11 +14,11 @@ export class SeededRandomService {
}
}
setSeed(seed: number) {
setSeed(seed: number): void {
this.seed = (SeededRandomService.baseSeed + seed) % 2147483647; // shifting with 16 to the left
}
get() {
get(): number {
this.seed = (this.seed * 16807 % 2147483647);
return this.seed / 2147483647;
}

View File

@@ -10,7 +10,7 @@ export class VersionService {
this.version = new BehaviorSubject<string>(null);
}
public onNewVersion(version: string) {
public onNewVersion(version: string): void {
if (this.version.value === version) {
return;
}

View File

@@ -4,10 +4,10 @@ import {FileDTO} from '../../../common/entities/FileDTO';
@Pipe({name: 'gpxFiles'})
export class GPXFilesFilterPipe implements PipeTransform {
transform(metaFiles: FileDTO[]) {
transform(metaFiles: FileDTO[]): FileDTO[] | null {
if (!metaFiles) {
return null;
}
return metaFiles.filter((f: FileDTO) => f.name.toLocaleLowerCase().endsWith('.gpx'));
return metaFiles.filter((f: FileDTO): boolean => f.name.toLocaleLowerCase().endsWith('.gpx'));
}
}

View File

@@ -6,11 +6,11 @@ import {SearchQueryParserService} from '../ui/gallery/search/search-query-parser
@Pipe({name: 'searchQuery'})
export class StringifySearchQuery implements PipeTransform {
constructor(
private _searchQueryParserService: SearchQueryParserService) {
private searchQueryParserService: SearchQueryParserService) {
}
transform(query: SearchQueryDTO): string {
return this._searchQueryParserService.stringify(query);
return this.searchQueryParserService.stringify(query);
}
}

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