2020-12-31 04:13:19 +08:00
|
|
|
import {Config} from '../src/common/config/private/Config';
|
|
|
|
import {ObjectManagers} from '../src/backend/model/ObjectManagers';
|
|
|
|
import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker';
|
|
|
|
import {IndexingManager} from '../src/backend/model/database/sql/IndexingManager';
|
2020-12-31 04:54:07 +08:00
|
|
|
import * as path from 'path';
|
2021-01-04 17:32:19 +08:00
|
|
|
import * as fs from 'fs';
|
2020-12-31 04:13:19 +08:00
|
|
|
import {Utils} from '../src/common/Utils';
|
2021-04-18 21:48:35 +08:00
|
|
|
import {DatabaseType, ReIndexingSensitivity} from '../src/common/config/private/PrivateConfig';
|
2020-12-31 04:13:19 +08:00
|
|
|
import {ProjectPath} from '../src/backend/ProjectPath';
|
|
|
|
import {Benchmark} from './Benchmark';
|
|
|
|
import {IndexingJob} from '../src/backend/model/jobs/jobs/IndexingJob';
|
|
|
|
import {IJob} from '../src/backend/model/jobs/jobs/IJob';
|
|
|
|
import {JobProgressStates} from '../src/common/entities/job/JobProgressDTO';
|
|
|
|
import {JobProgress} from '../src/backend/model/jobs/jobs/JobProgress';
|
|
|
|
import {ContentWrapper} from '../src/common/entities/ConentWrapper';
|
2020-12-31 04:54:07 +08:00
|
|
|
import {GalleryManager} from '../src/backend/model/database/sql/GalleryManager';
|
|
|
|
import {PersonManager} from '../src/backend/model/database/sql/PersonManager';
|
2020-12-31 19:35:28 +08:00
|
|
|
import {GalleryRouter} from '../src/backend/routes/GalleryRouter';
|
|
|
|
import {Express} from 'express';
|
|
|
|
import {PersonRouter} from '../src/backend/routes/PersonRouter';
|
2021-05-10 19:03:22 +08:00
|
|
|
import {
|
|
|
|
ANDSearchQuery,
|
|
|
|
ORSearchQuery,
|
|
|
|
SearchQueryDTO,
|
|
|
|
SearchQueryTypes,
|
|
|
|
SomeOfSearchQuery,
|
|
|
|
TextSearch,
|
|
|
|
TextSearchQueryMatchTypes,
|
|
|
|
TextSearchQueryTypes
|
|
|
|
} from '../src/common/entities/SearchQueryDTO';
|
|
|
|
import {QueryKeywords, SearchQueryParser} from '../src/common/SearchQueryParser';
|
2021-06-28 01:33:37 +08:00
|
|
|
import {DirectoryBaseDTO, ParentDirectoryDTO} from '../src/common/entities/DirectoryDTO';
|
2020-12-31 04:13:19 +08:00
|
|
|
|
|
|
|
|
|
|
|
export interface BenchmarkResult {
|
|
|
|
name: string;
|
2021-05-13 15:56:36 +08:00
|
|
|
experiment?: string;
|
2020-12-31 04:13:19 +08:00
|
|
|
duration: number;
|
|
|
|
contentWrapper?: ContentWrapper;
|
|
|
|
items?: number;
|
|
|
|
subBenchmarks?: BenchmarkResult[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export class BMIndexingManager extends IndexingManager {
|
|
|
|
|
2021-06-28 01:33:37 +08:00
|
|
|
public async saveToDB(scannedDirectory: ParentDirectoryDTO): Promise<void> {
|
2020-12-31 04:13:19 +08:00
|
|
|
return super.saveToDB(scannedDirectory);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-31 19:35:28 +08:00
|
|
|
|
|
|
|
class BMGalleryRouter extends GalleryRouter {
|
2021-04-18 21:48:35 +08:00
|
|
|
public static addDirectoryList(app: Express): void {
|
2020-12-31 19:35:28 +08:00
|
|
|
GalleryRouter.addDirectoryList(app);
|
|
|
|
}
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
public static addSearch(app: Express): void {
|
2020-12-31 19:35:28 +08:00
|
|
|
GalleryRouter.addSearch(app);
|
|
|
|
}
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
public static addAutoComplete(app: Express): void {
|
2020-12-31 19:35:28 +08:00
|
|
|
GalleryRouter.addAutoComplete(app);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class BMPersonRouter extends PersonRouter {
|
2021-04-18 21:48:35 +08:00
|
|
|
public static addGetPersons(app: Express): void {
|
2020-12-31 19:35:28 +08:00
|
|
|
PersonRouter.addGetPersons(app);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-31 04:13:19 +08:00
|
|
|
export class BenchmarkRunner {
|
2020-12-31 04:54:07 +08:00
|
|
|
inited = false;
|
2020-12-31 19:35:28 +08:00
|
|
|
private biggestDirPath: string = null;
|
|
|
|
private readonly requestTemplate: any = {
|
|
|
|
requestPipe: null,
|
|
|
|
params: {},
|
|
|
|
query: {},
|
|
|
|
session: {}
|
|
|
|
};
|
2020-12-31 04:13:19 +08:00
|
|
|
|
|
|
|
constructor(public RUNS: number) {
|
2020-12-31 19:35:28 +08:00
|
|
|
Config.Client.authenticationRequired = false;
|
2020-12-31 04:13:19 +08:00
|
|
|
}
|
|
|
|
|
2021-05-13 15:56:36 +08:00
|
|
|
async bmSaveDirectory(): Promise<BenchmarkResult[]> {
|
2020-12-31 04:54:07 +08:00
|
|
|
await this.init();
|
2020-12-31 04:13:19 +08:00
|
|
|
await this.resetDB();
|
2020-12-31 19:35:28 +08:00
|
|
|
const dir = await DiskMangerWorker.scanDirectory(this.biggestDirPath);
|
2021-04-18 21:48:35 +08:00
|
|
|
const bm = new Benchmark('Saving directory to DB', null, (): Promise<void> => this.resetDB());
|
2020-12-31 04:13:19 +08:00
|
|
|
bm.addAStep({
|
|
|
|
name: 'Saving directory to DB',
|
2021-04-18 21:48:35 +08:00
|
|
|
fn: (): Promise<void> => {
|
2020-12-31 04:13:19 +08:00
|
|
|
const im = new BMIndexingManager();
|
2021-06-28 01:33:37 +08:00
|
|
|
return im.saveToDB(dir as ParentDirectoryDTO);
|
2020-12-31 04:13:19 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return await bm.run(this.RUNS);
|
|
|
|
}
|
|
|
|
|
2021-05-13 15:56:36 +08:00
|
|
|
async bmScanDirectory(): Promise<BenchmarkResult[]> {
|
2020-12-31 04:54:07 +08:00
|
|
|
await this.init();
|
2020-12-31 04:13:19 +08:00
|
|
|
const bm = new Benchmark('Scanning directory');
|
|
|
|
bm.addAStep({
|
|
|
|
name: 'Scanning directory',
|
2021-04-18 21:48:35 +08:00
|
|
|
fn: async (): Promise<ContentWrapper> => new ContentWrapper(await DiskMangerWorker.scanDirectory(this.biggestDirPath))
|
2020-12-31 04:13:19 +08:00
|
|
|
});
|
|
|
|
return await bm.run(this.RUNS);
|
|
|
|
}
|
|
|
|
|
2021-05-13 15:56:36 +08:00
|
|
|
async bmListDirectory(): Promise<BenchmarkResult[]> {
|
2021-04-18 21:48:35 +08:00
|
|
|
Config.Server.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
2020-12-31 04:54:07 +08:00
|
|
|
await this.init();
|
2020-12-31 04:13:19 +08:00
|
|
|
await this.setupDB();
|
2020-12-31 19:35:28 +08:00
|
|
|
const req = Utils.clone(this.requestTemplate);
|
|
|
|
req.params.directory = this.biggestDirPath;
|
|
|
|
const bm = new Benchmark('List directory', req,
|
2021-04-18 21:48:35 +08:00
|
|
|
async (): Promise<void> => {
|
2020-12-31 04:13:19 +08:00
|
|
|
await ObjectManagers.reset();
|
|
|
|
await ObjectManagers.InitSQLManagers();
|
|
|
|
});
|
2020-12-31 19:35:28 +08:00
|
|
|
BMGalleryRouter.addDirectoryList(bm.BmExpressApp);
|
2020-12-31 04:13:19 +08:00
|
|
|
return await bm.run(this.RUNS);
|
|
|
|
}
|
|
|
|
|
2021-05-13 15:56:36 +08:00
|
|
|
async bmListPersons(): Promise<BenchmarkResult[]> {
|
2020-12-31 04:13:19 +08:00
|
|
|
await this.setupDB();
|
2021-04-18 21:48:35 +08:00
|
|
|
Config.Server.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
|
|
|
const bm = new Benchmark('Listing Faces', Utils.clone(this.requestTemplate), async (): Promise<void> => {
|
2020-12-31 04:13:19 +08:00
|
|
|
await ObjectManagers.reset();
|
|
|
|
await ObjectManagers.InitSQLManagers();
|
|
|
|
});
|
2020-12-31 19:35:28 +08:00
|
|
|
BMPersonRouter.addGetPersons(bm.BmExpressApp);
|
2020-12-31 04:13:19 +08:00
|
|
|
return await bm.run(this.RUNS);
|
|
|
|
}
|
|
|
|
|
2021-05-13 15:56:36 +08:00
|
|
|
async bmAllSearch(): Promise<{ result: BenchmarkResult[], searchQuery: SearchQueryDTO }[]> {
|
2020-12-31 04:13:19 +08:00
|
|
|
await this.setupDB();
|
|
|
|
|
2021-05-10 19:03:22 +08:00
|
|
|
const queryKeywords: QueryKeywords = {
|
2021-05-23 23:58:58 +08:00
|
|
|
NSomeOf: 'of',
|
2021-05-10 19:03:22 +08:00
|
|
|
and: 'and',
|
2021-05-23 23:58:58 +08:00
|
|
|
or: 'or',
|
|
|
|
|
|
|
|
from: 'after',
|
|
|
|
to: 'before',
|
2021-05-10 19:03:22 +08:00
|
|
|
landscape: 'landscape',
|
|
|
|
maxRating: 'max-rating',
|
|
|
|
maxResolution: 'max-resolution',
|
|
|
|
minRating: 'min-rating',
|
|
|
|
minResolution: 'min-resolution',
|
|
|
|
orientation: 'orientation',
|
2021-05-23 23:58:58 +08:00
|
|
|
|
2021-05-23 23:56:58 +08:00
|
|
|
any_text: 'any-text',
|
2021-05-23 23:58:58 +08:00
|
|
|
keyword: 'keyword',
|
|
|
|
caption: 'caption',
|
|
|
|
directory: 'directory',
|
|
|
|
file_name: 'file-name',
|
2021-05-10 19:03:22 +08:00
|
|
|
person: 'person',
|
|
|
|
portrait: 'portrait',
|
|
|
|
position: 'position',
|
|
|
|
someOf: 'some-of',
|
|
|
|
kmFrom: 'km-from'
|
|
|
|
};
|
|
|
|
const queryParser = new SearchQueryParser(queryKeywords);
|
|
|
|
const names = (await ObjectManagers.getInstance().PersonManager.getAll()).sort((a, b) => b.count - a.count);
|
|
|
|
const queries: { query: SearchQueryDTO, description: string }[] = TextSearchQueryTypes.map(t => {
|
|
|
|
const q = {
|
|
|
|
type: t, text: 'a'
|
|
|
|
};
|
|
|
|
return {
|
|
|
|
query: q, description: queryParser.stringify(q)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
// searching for everything
|
|
|
|
queries.push({
|
|
|
|
query: {
|
|
|
|
type: SearchQueryTypes.any_text, text: '.'
|
|
|
|
} as TextSearch, description: queryParser.stringify({
|
|
|
|
type: SearchQueryTypes.any_text, text: '.'
|
|
|
|
} as TextSearch)
|
|
|
|
});
|
|
|
|
if (names.length > 0) {
|
|
|
|
queries.push({
|
|
|
|
query: {
|
|
|
|
type: SearchQueryTypes.person, text: names[0].name,
|
|
|
|
matchType: TextSearchQueryMatchTypes.exact_match
|
|
|
|
} as TextSearch, description: '<Most common name>'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (names.length > 1) {
|
|
|
|
queries.push({
|
|
|
|
query: {
|
|
|
|
type: SearchQueryTypes.AND, list: [
|
|
|
|
{
|
|
|
|
type: SearchQueryTypes.person, text: names[0].name,
|
|
|
|
matchType: TextSearchQueryMatchTypes.exact_match
|
|
|
|
} as TextSearch,
|
|
|
|
{
|
|
|
|
type: SearchQueryTypes.person, text: names[1].name,
|
|
|
|
matchType: TextSearchQueryMatchTypes.exact_match
|
|
|
|
} as TextSearch
|
|
|
|
]
|
|
|
|
} as ANDSearchQuery, description: '<Most AND second common names>'
|
|
|
|
});
|
|
|
|
queries.push({
|
|
|
|
query: {
|
|
|
|
type: SearchQueryTypes.OR, list: [
|
|
|
|
{
|
|
|
|
type: SearchQueryTypes.person, text: names[0].name,
|
|
|
|
matchType: TextSearchQueryMatchTypes.exact_match
|
|
|
|
} as TextSearch,
|
|
|
|
{
|
|
|
|
type: SearchQueryTypes.person, text: names[1].name,
|
|
|
|
matchType: TextSearchQueryMatchTypes.exact_match
|
|
|
|
} as TextSearch
|
|
|
|
]
|
|
|
|
} as ORSearchQuery, description: '<Most OR second common names>'
|
|
|
|
});
|
|
|
|
queries.push({
|
|
|
|
query: {
|
|
|
|
type: SearchQueryTypes.SOME_OF,
|
|
|
|
min: 2,
|
|
|
|
list: names.map(n => ({
|
|
|
|
type: SearchQueryTypes.person, text: n.name,
|
|
|
|
matchType: TextSearchQueryMatchTypes.exact_match
|
|
|
|
} as TextSearch))
|
|
|
|
} as SomeOfSearchQuery, description: '<Contain at least 2 out of all names>'
|
|
|
|
});
|
|
|
|
}
|
2021-05-13 15:56:36 +08:00
|
|
|
const results: { result: BenchmarkResult[], searchQuery: SearchQueryDTO }[] = [];
|
2021-05-10 19:03:22 +08:00
|
|
|
|
|
|
|
for (const entry of queries) {
|
2020-12-31 19:35:28 +08:00
|
|
|
const req = Utils.clone(this.requestTemplate);
|
2021-05-10 19:03:22 +08:00
|
|
|
req.params.searchQueryDTO = JSON.stringify(entry.query);
|
|
|
|
|
|
|
|
const bm = new Benchmark('Searching for `' + entry.description + '`', req);
|
2020-12-31 19:35:28 +08:00
|
|
|
BMGalleryRouter.addSearch(bm.BmExpressApp);
|
|
|
|
|
2021-05-10 19:03:22 +08:00
|
|
|
results.push({result: await bm.run(this.RUNS), searchQuery: entry.query});
|
2020-12-31 04:13:19 +08:00
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-05-13 15:56:36 +08:00
|
|
|
async bmAutocomplete(text: string): Promise<BenchmarkResult[]> {
|
2020-12-31 04:13:19 +08:00
|
|
|
await this.setupDB();
|
2020-12-31 19:35:28 +08:00
|
|
|
const req = Utils.clone(this.requestTemplate);
|
|
|
|
req.params.text = text;
|
|
|
|
const bm = new Benchmark('Auto complete for `' + text + '`', req);
|
|
|
|
BMGalleryRouter.addAutoComplete(bm.BmExpressApp);
|
2020-12-31 04:13:19 +08:00
|
|
|
return await bm.run(this.RUNS);
|
|
|
|
}
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
async getStatistic(): Promise<string> {
|
2020-12-31 04:54:07 +08:00
|
|
|
await this.setupDB();
|
|
|
|
const gm = new GalleryManager();
|
|
|
|
const pm = new PersonManager();
|
|
|
|
|
2020-12-31 19:35:28 +08:00
|
|
|
|
2020-12-31 04:54:07 +08:00
|
|
|
return 'directories: ' + await gm.countDirectories() +
|
|
|
|
', photos: ' + await gm.countPhotos() +
|
|
|
|
', videos: ' + await gm.countVideos() +
|
2020-12-31 19:35:28 +08:00
|
|
|
', diskUsage : ' + Utils.renderDataSize(await gm.countMediaSize()) +
|
2020-12-31 04:54:07 +08:00
|
|
|
', persons : ' + await pm.countFaces() +
|
|
|
|
', unique persons (faces): ' + (await pm.getAll()).length;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
private async init(): Promise<string> {
|
2020-12-31 04:54:07 +08:00
|
|
|
if (this.inited === false) {
|
|
|
|
await this.setupDB();
|
|
|
|
|
|
|
|
const gm = new GalleryManager();
|
|
|
|
let biggest = 0;
|
|
|
|
let biggestPath = '/';
|
|
|
|
const queue = ['/'];
|
|
|
|
while (queue.length > 0) {
|
|
|
|
const dirPath = queue.shift();
|
|
|
|
const dir = await gm.listDirectory(dirPath);
|
2021-04-18 21:48:35 +08:00
|
|
|
dir.directories.forEach((d): number => queue.push(path.join(d.path + d.name)));
|
2020-12-31 04:54:07 +08:00
|
|
|
if (biggest < dir.media.length) {
|
|
|
|
biggestPath = path.join(dir.path + dir.name);
|
|
|
|
biggest = dir.media.length;
|
|
|
|
}
|
|
|
|
}
|
2020-12-31 19:35:28 +08:00
|
|
|
this.biggestDirPath = biggestPath;
|
|
|
|
console.log('updating path of biggest dir to: ' + this.biggestDirPath);
|
2020-12-31 04:54:07 +08:00
|
|
|
this.inited = true;
|
|
|
|
}
|
2020-12-31 19:35:28 +08:00
|
|
|
return this.biggestDirPath;
|
2020-12-31 04:54:07 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-12-31 04:13:19 +08:00
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
private resetDB = async (): Promise<void> => {
|
2020-12-31 04:13:19 +08:00
|
|
|
Config.Server.Threading.enabled = false;
|
|
|
|
await ObjectManagers.reset();
|
2021-01-04 17:32:19 +08:00
|
|
|
await fs.promises.rmdir(ProjectPath.DBFolder, {recursive: true});
|
2021-04-18 21:48:35 +08:00
|
|
|
Config.Server.Database.type = DatabaseType.sqlite;
|
2020-12-31 04:13:19 +08:00
|
|
|
Config.Server.Jobs.scheduled = [];
|
|
|
|
await ObjectManagers.InitSQLManagers();
|
|
|
|
};
|
|
|
|
|
2021-01-04 17:32:19 +08:00
|
|
|
private async setupDB(): Promise<void> {
|
2020-12-31 04:13:19 +08:00
|
|
|
Config.Server.Threading.enabled = false;
|
2021-01-04 17:32:19 +08:00
|
|
|
await this.resetDB();
|
2021-04-18 21:48:35 +08:00
|
|
|
await new Promise<void>((resolve, reject): void => {
|
2020-12-31 04:13:19 +08:00
|
|
|
try {
|
|
|
|
const indexingJob = new IndexingJob();
|
|
|
|
|
|
|
|
indexingJob.JobListener = {
|
2021-04-18 21:48:35 +08:00
|
|
|
onJobFinished: (job: IJob<any>, state: JobProgressStates, soloRun: boolean): void => {
|
2020-12-31 04:13:19 +08:00
|
|
|
resolve();
|
|
|
|
},
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
onProgressUpdate: (progress: JobProgress): void => {
|
2020-12-31 04:13:19 +08:00
|
|
|
}
|
|
|
|
};
|
2021-05-12 19:56:10 +08:00
|
|
|
indexingJob.start({indexChangesOnly: false}).catch(console.error);
|
2020-12-31 04:13:19 +08:00
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
reject(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|