You've already forked pigallery2
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:
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
56
gulpfile.ts
56
gulpfile.ts
@@ -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
370
package-lock.json
generated
@@ -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",
|
||||
|
@@ -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": {
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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') {
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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');
|
||||
}
|
||||
|
||||
|
@@ -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 = {
|
||||
|
@@ -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;
|
||||
|
@@ -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'});
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
@@ -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 ' +
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
});
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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')
|
||||
|
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -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})
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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> {
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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'
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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();
|
||||
});
|
||||
|
@@ -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),
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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),
|
||||
|
@@ -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),
|
||||
|
@@ -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),
|
||||
|
@@ -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),
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
|
@@ -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[],
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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;
|
||||
|
||||
}
|
||||
|
@@ -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();
|
||||
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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[];
|
||||
}
|
||||
};
|
||||
|
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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'};
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
})());
|
||||
|
||||
});
|
||||
|
@@ -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']);
|
||||
}));
|
||||
|
||||
|
||||
|
@@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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';
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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'));
|
||||
}
|
||||
}
|
||||
|
@@ -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
Reference in New Issue
Block a user