mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
fixing searching
This commit is contained in:
parent
1f58bffa2c
commit
0445c499e8
@ -67,7 +67,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
||||
// on the fly reindexing
|
||||
|
||||
Logger.silly(LOG_TAG, 'lazy reindexing reason: cache timeout: lastScanned: '
|
||||
+ (Date.now() - dir.lastScanned) + ', cachedFolderTimeout:' + Config.Server.indexing.cachedFolderTimeout);
|
||||
+ (Date.now() - dir.lastScanned) + ' ms ago, cachedFolderTimeout:' + Config.Server.indexing.cachedFolderTimeout);
|
||||
ObjectManagerRepository.getInstance().IndexingManager.indexDirectory(relativeDirectoryName).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
@ -44,7 +44,7 @@ export class SQLConnection {
|
||||
VersionEntity
|
||||
];
|
||||
options.synchronize = false;
|
||||
// options.logging = 'all';
|
||||
//options.logging = 'all';
|
||||
this.connection = await createConnection(options);
|
||||
await SQLConnection.schemeSync(this.connection);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import {MediaEntity} from './enitites/MediaEntity';
|
||||
import {VideoEntity} from './enitites/VideoEntity';
|
||||
import {PersonEntry} from './enitites/PersonEntry';
|
||||
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
|
||||
import {SelectQueryBuilder} from 'typeorm';
|
||||
|
||||
export class SearchManager implements ISearchManager {
|
||||
|
||||
@ -24,7 +25,7 @@ export class SearchManager implements ISearchManager {
|
||||
return a;
|
||||
}
|
||||
|
||||
async autocomplete(text: string): Promise<Array<AutoCompleteItem>> {
|
||||
async autocomplete(text: string): Promise<AutoCompleteItem[]> {
|
||||
|
||||
const connection = await SQLConnection.getConnection();
|
||||
|
||||
@ -122,61 +123,60 @@ export class SearchManager implements ISearchManager {
|
||||
resultOverflow: false
|
||||
};
|
||||
|
||||
let repository = connection.getRepository(MediaEntity);
|
||||
const faceRepository = connection.getRepository(FaceRegionEntry);
|
||||
let usedEntity = MediaEntity;
|
||||
|
||||
if (searchType === SearchTypes.photo) {
|
||||
repository = connection.getRepository(PhotoEntity);
|
||||
usedEntity = PhotoEntity;
|
||||
} else if (searchType === SearchTypes.video) {
|
||||
repository = connection.getRepository(VideoEntity);
|
||||
usedEntity = VideoEntity;
|
||||
}
|
||||
|
||||
const query = repository.createQueryBuilder('media')
|
||||
const query = await connection.getRepository(usedEntity).createQueryBuilder('media')
|
||||
.innerJoin(q => {
|
||||
const subQuery = q.from(usedEntity, 'media')
|
||||
.select('distinct media.id')
|
||||
.limit(2000);
|
||||
|
||||
|
||||
if (!searchType || searchType === SearchTypes.directory) {
|
||||
subQuery.leftJoin('media.directory', 'directory')
|
||||
.orWhere('directory.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
|
||||
if (!searchType || searchType === SearchTypes.photo || searchType === SearchTypes.video) {
|
||||
subQuery.orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
|
||||
if (!searchType || searchType === SearchTypes.photo) {
|
||||
subQuery.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
if (!searchType || searchType === SearchTypes.person) {
|
||||
subQuery
|
||||
.leftJoin('media.metadata.faces', 'faces')
|
||||
.leftJoin('faces.person', 'person')
|
||||
.orWhere('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
|
||||
if (!searchType || searchType === SearchTypes.position) {
|
||||
subQuery.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
|
||||
}
|
||||
if (!searchType || searchType === SearchTypes.keyword) {
|
||||
subQuery.orWhere('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
|
||||
return subQuery;
|
||||
},
|
||||
'innerMedia',
|
||||
'media.id=innerMedia.id')
|
||||
.leftJoinAndSelect('media.directory', 'directory')
|
||||
.leftJoin('media.metadata.faces', 'faces')
|
||||
.leftJoin('faces.person', 'person')
|
||||
.orderBy('media.metadata.creationDate', 'ASC');
|
||||
.leftJoinAndSelect('media.metadata.faces', 'faces')
|
||||
.leftJoinAndSelect('faces.person', 'person');
|
||||
|
||||
|
||||
if (!searchType || searchType === SearchTypes.directory) {
|
||||
query.orWhere('directory.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
|
||||
if (!searchType || searchType === SearchTypes.photo || searchType === SearchTypes.video) {
|
||||
query.orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
|
||||
if (!searchType || searchType === SearchTypes.photo) {
|
||||
query.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
if (!searchType || searchType === SearchTypes.person) {
|
||||
query.orWhere('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
|
||||
if (!searchType || searchType === SearchTypes.position) {
|
||||
query.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
|
||||
}
|
||||
if (!searchType || searchType === SearchTypes.keyword) {
|
||||
query.orWhere('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||
}
|
||||
|
||||
|
||||
result.media = (await query
|
||||
.limit(5000).getMany()).slice(0, 2001);
|
||||
|
||||
for (let i = 0; i < result.media.length; i++) {
|
||||
const faces = (await faceRepository
|
||||
.createQueryBuilder('faces')
|
||||
.leftJoinAndSelect('faces.person', 'person')
|
||||
.where('faces.media = :media', {media: result.media[i].id})
|
||||
.getMany()).map(fE => ({name: fE.person.name, box: fE.box}));
|
||||
if (faces.length > 0) {
|
||||
result.media[i].metadata.faces = faces;
|
||||
}
|
||||
}
|
||||
result.media = await this.loadMediaWithFaces(query);
|
||||
|
||||
if (result.media.length > 2000) {
|
||||
result.resultOverflow = true;
|
||||
@ -208,35 +208,30 @@ export class SearchManager implements ISearchManager {
|
||||
resultOverflow: false
|
||||
};
|
||||
|
||||
const faceRepository = connection.getRepository(FaceRegionEntry);
|
||||
const query = await connection.getRepository(MediaEntity).createQueryBuilder('media')
|
||||
.innerJoin(q => q.from(MediaEntity, 'media')
|
||||
.select('distinct media.id')
|
||||
.limit(10)
|
||||
.leftJoin('media.directory', 'directory')
|
||||
.leftJoin('media.metadata.faces', 'faces')
|
||||
.leftJoin('faces.person', 'person')
|
||||
.where('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
,
|
||||
'innerMedia',
|
||||
'media.id=innerMedia.id')
|
||||
.leftJoinAndSelect('media.directory', 'directory')
|
||||
.leftJoinAndSelect('media.metadata.faces', 'faces')
|
||||
.leftJoinAndSelect('faces.person', 'person');
|
||||
|
||||
result.media = await connection
|
||||
.getRepository(MediaEntity)
|
||||
.createQueryBuilder('media')
|
||||
.orderBy('media.metadata.creationDate', 'ASC')
|
||||
.where('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.innerJoinAndSelect('media.directory', 'directory')
|
||||
.leftJoin('media.metadata.faces', 'faces')
|
||||
.leftJoin('faces.person', 'person')
|
||||
.limit(10)
|
||||
.getMany();
|
||||
|
||||
for (let i = 0; i < result.media.length; i++) {
|
||||
const faces = (await faceRepository
|
||||
.createQueryBuilder('faces')
|
||||
.leftJoinAndSelect('faces.person', 'person')
|
||||
.where('faces.media = :media', {media: result.media[i].id})
|
||||
.getMany()).map(fE => ({name: fE.person.name, box: fE.box}));
|
||||
if (faces.length > 0) {
|
||||
result.media[i].metadata.faces = faces;
|
||||
}
|
||||
}
|
||||
result.media = await this.loadMediaWithFaces(query);
|
||||
|
||||
|
||||
result.directories = await connection
|
||||
.getRepository(DirectoryEntity)
|
||||
@ -255,4 +250,29 @@ export class SearchManager implements ISearchManager {
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
private async loadMediaWithFaces(query: SelectQueryBuilder<MediaEntity>) {
|
||||
const rawAndEntities = await query.orderBy('media.id').getRawAndEntities();
|
||||
const media: MediaEntity[] = rawAndEntities.entities;
|
||||
|
||||
// console.log(rawAndEntities.raw);
|
||||
let rawIndex = 0;
|
||||
for (let i = 0; i < media.length; i++) {
|
||||
if (rawAndEntities.raw[rawIndex].faces_id === null ||
|
||||
rawAndEntities.raw[rawIndex].media_id !== media[i].id) {
|
||||
delete media[i].metadata.faces;
|
||||
continue;
|
||||
}
|
||||
media[i].metadata.faces = [];
|
||||
|
||||
while (rawAndEntities.raw[rawIndex].media_id === media[i].id) {
|
||||
media[i].metadata.faces.push(<any>FaceRegionEntry.fromRawToDTO(rawAndEntities.raw[rawIndex]));
|
||||
rawIndex++;
|
||||
if (rawIndex >= rawAndEntities.raw.length) {
|
||||
return media;
|
||||
}
|
||||
}
|
||||
}
|
||||
return media;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {FaceRegionBox} from '../../../../common/entities/PhotoDTO';
|
||||
import {Column, ManyToOne, Entity, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import {FaceRegion, FaceRegionBox} from '../../../../common/entities/PhotoDTO';
|
||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import {PersonEntry} from './PersonEntry';
|
||||
import {MediaEntity, MediaMetadataEntity} from './MediaEntity';
|
||||
import {MediaEntity} from './MediaEntity';
|
||||
|
||||
export class FaceRegionBoxEntry implements FaceRegionBox {
|
||||
@Column('int')
|
||||
@ -35,4 +35,21 @@ export class FaceRegionEntry {
|
||||
person: PersonEntry;
|
||||
|
||||
name: string;
|
||||
|
||||
public static fromRawToDTO(raw: {
|
||||
faces_id: number,
|
||||
faces_mediaId: number,
|
||||
faces_personId: number,
|
||||
faces_boxHeight: number,
|
||||
faces_boxWidth: number,
|
||||
faces_boxX: number,
|
||||
faces_boxY: number,
|
||||
person_id: number,
|
||||
person_name: string
|
||||
}): FaceRegion {
|
||||
return {
|
||||
box: {width: raw.faces_boxWidth, height: raw.faces_boxHeight, x: raw.faces_boxX, y: raw.faces_boxY},
|
||||
name: raw.person_name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
137
benchmark/Benchmarks.ts
Normal file
137
benchmark/Benchmarks.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import {SQLConnection} from '../backend/model/sql/SQLConnection';
|
||||
import {Config} from '../common/config/private/Config';
|
||||
import {DatabaseType, ReIndexingSensitivity} from '../common/config/private/IPrivateConfig';
|
||||
import {ObjectManagerRepository} from '../backend/model/ObjectManagerRepository';
|
||||
import {DiskMangerWorker} from '../backend/model/threading/DiskMangerWorker';
|
||||
import {IndexingManager} from '../backend/model/sql/IndexingManager';
|
||||
import {SearchManager} from '../backend/model/sql/SearchManager';
|
||||
import * as fs from 'fs';
|
||||
import {SearchTypes} from '../common/entities/AutoCompleteItem';
|
||||
import {Utils} from '../common/Utils';
|
||||
import {GalleryManager} from '../backend/model/sql/GalleryManager';
|
||||
import {DirectoryDTO} from '../common/entities/DirectoryDTO';
|
||||
|
||||
export interface BenchmarkResult {
|
||||
duration: number;
|
||||
directories?: number;
|
||||
media?: number;
|
||||
items?: number;
|
||||
}
|
||||
|
||||
export class BMIndexingManager extends IndexingManager {
|
||||
|
||||
public async saveToDB(scannedDirectory: DirectoryDTO): Promise<void> {
|
||||
return super.saveToDB(scannedDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
export class Benchmarks {
|
||||
|
||||
constructor(public RUNS: number, public dbPath: string) {
|
||||
|
||||
}
|
||||
|
||||
async bmSaveDirectory(): Promise<BenchmarkResult> {
|
||||
await this.resetDB();
|
||||
const dir = await DiskMangerWorker.scanDirectory('./');
|
||||
const im = new BMIndexingManager();
|
||||
return await this.benchmark(() => im.saveToDB(dir), () => this.resetDB());
|
||||
}
|
||||
|
||||
async bmScanDirectory(): Promise<BenchmarkResult> {
|
||||
return await this.benchmark(() => DiskMangerWorker.scanDirectory('./'));
|
||||
}
|
||||
|
||||
async bmListDirectory(): Promise<BenchmarkResult> {
|
||||
const gm = new GalleryManager();
|
||||
await this.setupDB();
|
||||
Config.Server.indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
||||
return await this.benchmark(() => gm.listDirectory('./'));
|
||||
}
|
||||
|
||||
async bmAllSearch(text: string): Promise<{ result: BenchmarkResult, searchType: SearchTypes }[]> {
|
||||
await this.setupDB();
|
||||
const types = Utils.enumToArray(SearchTypes).map(a => a.key).concat([null]);
|
||||
const results: { result: BenchmarkResult, searchType: SearchTypes }[] = [];
|
||||
const sm = new SearchManager();
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
results.push({result: await this.benchmark(() => sm.search(text, types[i])), searchType: types[i]});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async bmInstantSearch(text: string): Promise<BenchmarkResult> {
|
||||
await this.setupDB();
|
||||
const sm = new SearchManager();
|
||||
return await this.benchmark(() => sm.instantSearch(text));
|
||||
}
|
||||
|
||||
async bmAutocomplete(text: string): Promise<BenchmarkResult> {
|
||||
await this.setupDB();
|
||||
const sm = new SearchManager();
|
||||
return await this.benchmark(() => sm.autocomplete(text));
|
||||
}
|
||||
|
||||
|
||||
private async benchmark(fn: () => Promise<{ media: any[], directories: any[] } | any[] | void>,
|
||||
beforeEach: () => Promise<any> = null,
|
||||
afterEach: () => Promise<any> = null) {
|
||||
const scanned = await fn();
|
||||
const start = process.hrtime();
|
||||
let skip = 0;
|
||||
for (let i = 0; i < this.RUNS; i++) {
|
||||
if (beforeEach) {
|
||||
const startSkip = process.hrtime();
|
||||
await beforeEach();
|
||||
const endSkip = process.hrtime(startSkip);
|
||||
skip += (endSkip[0] * 1000 + endSkip[1] / 1000);
|
||||
}
|
||||
await fn();
|
||||
if (afterEach) {
|
||||
const startSkip = process.hrtime();
|
||||
await afterEach();
|
||||
const endSkip = process.hrtime(startSkip);
|
||||
skip += (endSkip[0] * 1000 + endSkip[1] / 1000);
|
||||
}
|
||||
}
|
||||
const end = process.hrtime(start);
|
||||
const duration = (end[0] * 1000 + end[1] / 1000) / this.RUNS;
|
||||
|
||||
if (!scanned) {
|
||||
return {
|
||||
duration: duration
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(scanned)) {
|
||||
return {
|
||||
duration: duration,
|
||||
items: scanned.length
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
duration: duration,
|
||||
media: scanned.media.length,
|
||||
directories: scanned.directories.length
|
||||
};
|
||||
}
|
||||
|
||||
private resetDB = async () => {
|
||||
await SQLConnection.close();
|
||||
if (fs.existsSync(this.dbPath)) {
|
||||
fs.unlinkSync(this.dbPath);
|
||||
}
|
||||
Config.Server.database.type = DatabaseType.sqlite;
|
||||
Config.Server.database.sqlite.storage = this.dbPath;
|
||||
await ObjectManagerRepository.InitSQLManagers();
|
||||
};
|
||||
|
||||
private async setupDB() {
|
||||
const im = new BMIndexingManager();
|
||||
await this.resetDB();
|
||||
const dir = await DiskMangerWorker.scanDirectory('./');
|
||||
await im.saveToDB(dir);
|
||||
}
|
||||
|
||||
}
|
5
benchmark/README.md
Normal file
5
benchmark/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# PiGallery2 performance benchmark results
|
||||
|
||||
These results are created mostly for development, but I'm making them public for curious users.
|
||||
|
||||
|
70
benchmark/index.ts
Normal file
70
benchmark/index.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {Config} from '../common/config/private/Config';
|
||||
import * as path from 'path';
|
||||
import {ProjectPath} from '../backend/ProjectPath';
|
||||
import {BenchmarkResult, Benchmarks} from './Benchmarks';
|
||||
import {SearchTypes} from '../common/entities/AutoCompleteItem';
|
||||
import {Utils} from '../common/Utils';
|
||||
import {DiskMangerWorker} from '../backend/model/threading/DiskMangerWorker';
|
||||
|
||||
const config: { path: string, system: string } = require(path.join(__dirname, 'config.json'));
|
||||
Config.Server.imagesFolder = config.path;
|
||||
const dbPath = path.join(__dirname, 'test.db');
|
||||
ProjectPath.reset();
|
||||
const RUNS = 1;
|
||||
|
||||
let resultsText = '';
|
||||
const printLine = (text: string) => {
|
||||
resultsText += text + '\n';
|
||||
};
|
||||
|
||||
const printHeader = async () => {
|
||||
const dt = new Date();
|
||||
printLine('## PiGallery2 v' + require('./../package.json').version +
|
||||
', ' + Utils.zeroPrefix(dt.getDay(), 2) +
|
||||
'.' + Utils.zeroPrefix(dt.getMonth() + 1, 2) +
|
||||
'.' + dt.getFullYear());
|
||||
printLine('**System**: ' + config.system);
|
||||
const dir = await DiskMangerWorker.scanDirectory('./');
|
||||
printLine('**Gallery**: directories:' + dir.directories.length + ' media:' + dir.media.length);
|
||||
};
|
||||
|
||||
|
||||
const printTableHeader = () => {
|
||||
printLine('| action | action details | average time | details |');
|
||||
printLine('|:------:|:--------------:|:------------:|:-------:|');
|
||||
};
|
||||
const printResult = (result: BenchmarkResult, action: string, actionDetails: string = '') => {
|
||||
let details = '-';
|
||||
if (result.items) {
|
||||
details = 'items: ' + result.items;
|
||||
}
|
||||
if (result.media) {
|
||||
details = 'media: ' + result.media + ', directories:' + result.directories;
|
||||
}
|
||||
printLine('| ' + action + ' | ' + actionDetails +
|
||||
' | ' + (result.duration / 1000).toFixed(2) + 's | ' + details + ' |');
|
||||
};
|
||||
|
||||
const run = async () => {
|
||||
const bm = new Benchmarks(RUNS, dbPath);
|
||||
|
||||
// header
|
||||
await printHeader();
|
||||
printTableHeader();
|
||||
printResult(await bm.bmScanDirectory(), 'Scanning directory');
|
||||
printResult(await bm.bmSaveDirectory(), 'Saving directory');
|
||||
printResult(await bm.bmListDirectory(), 'Listing Directory');
|
||||
(await bm.bmAllSearch('a')).forEach(res => {
|
||||
if (res.searchType !== null) {
|
||||
printResult(res.result, 'searching', '`a` as `' + SearchTypes[res.searchType] + '`');
|
||||
} else {
|
||||
printResult(res.result, 'searching', '`a` as `any`');
|
||||
}
|
||||
});
|
||||
printResult(await bm.bmInstantSearch('a'), 'instant search', '`a`');
|
||||
printResult(await bm.bmAutocomplete('a'), 'auto complete', '`a`');
|
||||
console.log(resultsText);
|
||||
};
|
||||
|
||||
run();
|
||||
|
@ -23,4 +23,8 @@ export enum ErrorCodes {
|
||||
export class ErrorDTO {
|
||||
constructor(public code: ErrorCodes, public message?: string, public details?: any) {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return '[' + ErrorCodes[this.code] + '] ' + this.message + (this.details ? this.details.toString() : '');
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 38 KiB |
@ -13,15 +13,24 @@
|
||||
"faces": [
|
||||
{
|
||||
"box": {
|
||||
"height": 19,
|
||||
"width": 20,
|
||||
"x": 82,
|
||||
"y": 38
|
||||
"height": 2,
|
||||
"width": 2,
|
||||
"x": 8,
|
||||
"y": 4
|
||||
},
|
||||
"name": "squirrel"
|
||||
},
|
||||
{
|
||||
"box": {
|
||||
"height": 3,
|
||||
"width": 2,
|
||||
"x": 5,
|
||||
"y": 5
|
||||
},
|
||||
"name": "special_chars űáéúőóüío?._:"
|
||||
}
|
||||
],
|
||||
"fileSize": 59187,
|
||||
"fileSize": 39424,
|
||||
"keywords": [
|
||||
"Berkley",
|
||||
"USA",
|
||||
@ -39,7 +48,7 @@
|
||||
"state": "test state őúéáűóöí-.,)("
|
||||
},
|
||||
"size": {
|
||||
"height": 93,
|
||||
"width": 140
|
||||
"height": 10,
|
||||
"width": 14
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,7 @@ import * as path from 'path';
|
||||
import {Config} from '../../../../../common/config/private/Config';
|
||||
import {DatabaseType} from '../../../../../common/config/private/IPrivateConfig';
|
||||
import {SQLConnection} from '../../../../../backend/model/sql/SQLConnection';
|
||||
import {
|
||||
CameraMetadataEntity,
|
||||
GPSMetadataEntity,
|
||||
PhotoEntity,
|
||||
PhotoMetadataEntity,
|
||||
PositionMetaDataEntity
|
||||
} from '../../../../../backend/model/sql/enitites/PhotoEntity';
|
||||
import {PhotoEntity} from '../../../../../backend/model/sql/enitites/PhotoEntity';
|
||||
import {SearchManager} from '../../../../../backend/model/sql/SearchManager';
|
||||
import {AutoCompleteItem, SearchTypes} from '../../../../../common/entities/AutoCompleteItem';
|
||||
import {SearchResultDTO} from '../../../../../common/entities/SearchResultDTO';
|
||||
@ -18,6 +12,9 @@ import {DirectoryEntity} from '../../../../../backend/model/sql/enitites/Directo
|
||||
import {Utils} from '../../../../../common/Utils';
|
||||
import {TestHelper} from './TestHelper';
|
||||
import {VideoEntity} from '../../../../../backend/model/sql/enitites/VideoEntity';
|
||||
import {PersonEntry} from '../../../../../backend/model/sql/enitites/PersonEntry';
|
||||
import {FaceRegionEntry} from '../../../../../backend/model/sql/enitites/FaceRegionEntry';
|
||||
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
||||
|
||||
describe('SearchManager', () => {
|
||||
|
||||
@ -28,6 +25,9 @@ describe('SearchManager', () => {
|
||||
const dir = TestHelper.getDirectoryEntry();
|
||||
const p = TestHelper.getPhotoEntry1(dir);
|
||||
const p2 = TestHelper.getPhotoEntry2(dir);
|
||||
const p_faceLess = TestHelper.getPhotoEntry2(dir);
|
||||
delete p_faceLess.metadata.faces;
|
||||
p_faceLess.name = 'fl';
|
||||
const v = TestHelper.getVideoEntry1(dir);
|
||||
|
||||
const setUpSqlDB = async () => {
|
||||
@ -41,13 +41,26 @@ describe('SearchManager', () => {
|
||||
Config.Server.database.type = DatabaseType.sqlite;
|
||||
Config.Server.database.sqlite.storage = dbPath;
|
||||
|
||||
const savePhoto = async (photo: PhotoDTO) => {
|
||||
const savedPhoto = await pr.save(photo);
|
||||
if (!photo.metadata.faces) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < photo.metadata.faces.length; i++) {
|
||||
const face = photo.metadata.faces[i];
|
||||
const person = await conn.getRepository(PersonEntry).save({name: face.name});
|
||||
await conn.getRepository(FaceRegionEntry).save({box: face.box, person: person, media: savedPhoto});
|
||||
}
|
||||
};
|
||||
const conn = await SQLConnection.getConnection();
|
||||
|
||||
const pr = conn.getRepository(PhotoEntity);
|
||||
|
||||
await conn.getRepository(DirectoryEntity).save(p.directory);
|
||||
await pr.save(p);
|
||||
await pr.save(p2);
|
||||
await savePhoto(p);
|
||||
await savePhoto(p2);
|
||||
await savePhoto(p_faceLess);
|
||||
|
||||
await conn.getRepository(VideoEntity).save(v);
|
||||
|
||||
await SQLConnection.close();
|
||||
@ -76,6 +89,9 @@ describe('SearchManager', () => {
|
||||
const sm = new SearchManager();
|
||||
|
||||
const cmp = (a: AutoCompleteItem, b: AutoCompleteItem) => {
|
||||
if (a.text === b.text) {
|
||||
return a.type - b.type;
|
||||
}
|
||||
return a.text.localeCompare(b.text);
|
||||
};
|
||||
|
||||
@ -89,9 +105,14 @@ describe('SearchManager', () => {
|
||||
expect((await sm.autocomplete('arch'))).eql([new AutoCompleteItem('Research City', SearchTypes.position)]);
|
||||
expect((await sm.autocomplete('a')).sort(cmp)).eql([
|
||||
new AutoCompleteItem('Boba Fett', SearchTypes.keyword),
|
||||
new AutoCompleteItem('Boba Fett', SearchTypes.person),
|
||||
new AutoCompleteItem('star wars', SearchTypes.keyword),
|
||||
new AutoCompleteItem('Anakin', SearchTypes.keyword),
|
||||
new AutoCompleteItem('Anakin Skywalker', SearchTypes.person),
|
||||
new AutoCompleteItem('Luke Skywalker', SearchTypes.person),
|
||||
new AutoCompleteItem('Han Solo', SearchTypes.person),
|
||||
new AutoCompleteItem('death star', SearchTypes.keyword),
|
||||
new AutoCompleteItem('Padmé Amidala', SearchTypes.person),
|
||||
new AutoCompleteItem('Padmé Amidala', SearchTypes.keyword),
|
||||
new AutoCompleteItem('Natalie Portman', SearchTypes.keyword),
|
||||
new AutoCompleteItem('Han Solo\'s dice', SearchTypes.photo),
|
||||
@ -120,6 +141,15 @@ describe('SearchManager', () => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.search('Boba', null))).to.deep.equal(Utils.clone(<SearchResultDTO>{
|
||||
searchText: 'Boba',
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.search('Tatooine', SearchTypes.position))).to.deep.equal(Utils.clone(<SearchResultDTO>{
|
||||
searchText: 'Tatooine',
|
||||
searchType: SearchTypes.position,
|
||||
@ -133,7 +163,7 @@ describe('SearchManager', () => {
|
||||
searchText: 'ortm',
|
||||
searchType: SearchTypes.keyword,
|
||||
directories: [],
|
||||
media: [p2],
|
||||
media: [p2, p_faceLess],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
@ -142,7 +172,7 @@ describe('SearchManager', () => {
|
||||
searchText: 'ortm',
|
||||
searchType: SearchTypes.keyword,
|
||||
directories: [],
|
||||
media: [p2],
|
||||
media: [p2, p_faceLess],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
@ -151,7 +181,7 @@ describe('SearchManager', () => {
|
||||
searchText: 'wa',
|
||||
searchType: SearchTypes.keyword,
|
||||
directories: [dir],
|
||||
media: [p, p2],
|
||||
media: [p, p2, p_faceLess],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
@ -165,6 +195,15 @@ describe('SearchManager', () => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.search('sw', SearchTypes.video))).to.deep.equal(Utils.clone(<SearchResultDTO>{
|
||||
searchText: 'sw',
|
||||
searchType: SearchTypes.video,
|
||||
directories: [],
|
||||
media: [v],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.search('han', SearchTypes.keyword))).to.deep.equal(Utils.clone(<SearchResultDTO>{
|
||||
searchText: 'han',
|
||||
searchType: SearchTypes.keyword,
|
||||
@ -173,6 +212,15 @@ describe('SearchManager', () => {
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.search('Boba', SearchTypes.person))).to.deep.equal(Utils.clone(<SearchResultDTO>{
|
||||
searchText: 'Boba',
|
||||
searchType: SearchTypes.person,
|
||||
directories: [],
|
||||
media: [p],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@ -198,23 +246,16 @@ describe('SearchManager', () => {
|
||||
expect(Utils.clone(await sm.instantSearch('ortm'))).to.deep.equal(Utils.clone({
|
||||
searchText: 'ortm',
|
||||
directories: [],
|
||||
media: [p2],
|
||||
media: [p2, p_faceLess],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.instantSearch('ortm'))).to.deep.equal(Utils.clone({
|
||||
searchText: 'ortm',
|
||||
directories: [],
|
||||
media: [p2],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.instantSearch('wa'))).to.deep.equal(Utils.clone({
|
||||
searchText: 'wa',
|
||||
directories: [dir],
|
||||
media: [p, p2],
|
||||
media: [p, p2, p_faceLess],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
@ -226,6 +267,13 @@ describe('SearchManager', () => {
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
expect(Utils.clone(await sm.instantSearch('Boba'))).to.deep.equal(Utils.clone({
|
||||
searchText: 'Boba',
|
||||
directories: [],
|
||||
media: [p],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
@ -104,6 +104,20 @@ export class TestHelper {
|
||||
p.metadata.positionData.city = 'Mos Eisley';
|
||||
p.metadata.positionData.country = 'Tatooine';
|
||||
p.name = 'sw1';
|
||||
|
||||
p.metadata.faces = [<any>{
|
||||
box: {height: 10, width: 10, x: 10, y: 10},
|
||||
name: 'Boba Fett'
|
||||
}, <any>{
|
||||
box: {height: 10, width: 10, x: 101, y: 101},
|
||||
name: 'Luke Skywalker'
|
||||
}, <any>{
|
||||
box: {height: 10, width: 10, x: 101, y: 101},
|
||||
name: 'Han Solo'
|
||||
}, <any>{
|
||||
box: {height: 10, width: 10, x: 101, y: 101},
|
||||
name: 'Unkle Ben'
|
||||
}];
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -121,6 +135,16 @@ export class TestHelper {
|
||||
p.metadata.positionData.state = 'Research City';
|
||||
p.metadata.positionData.country = 'Kamino';
|
||||
p.name = 'sw2';
|
||||
p.metadata.faces = [<any>{
|
||||
box: {height: 10, width: 10, x: 10, y: 10},
|
||||
name: 'Padmé Amidala'
|
||||
}, <any>{
|
||||
box: {height: 10, width: 10, x: 101, y: 101},
|
||||
name: 'Anakin Skywalker'
|
||||
}, <any>{
|
||||
box: {height: 10, width: 10, x: 101, y: 101},
|
||||
name: 'Obivan Kenobi'
|
||||
}];
|
||||
return p;
|
||||
}
|
||||
|
||||
|
@ -11,10 +11,10 @@ describe('DiskMangerWorker', () => {
|
||||
Config.Server.imagesFolder = path.join(__dirname, '/../../assets');
|
||||
ProjectPath.ImageFolder = path.join(__dirname, '/../../assets');
|
||||
const dir = await DiskMangerWorker.scanDirectory('/');
|
||||
expect(dir.media.length).to.be.equals(2);
|
||||
expect(dir.media.length).to.be.equals(3);
|
||||
const expected = require(path.join(__dirname, '/../../assets/test image öüóőúéáű-.,.json'));
|
||||
expect(Utils.clone(dir.media[0].name)).to.be.deep.equal('test image öüóőúéáű-.,.jpg');
|
||||
expect(Utils.clone(dir.media[0].metadata)).to.be.deep.equal(expected);
|
||||
expect(Utils.clone(dir.media[1].name)).to.be.deep.equal('test image öüóőúéáű-.,.jpg');
|
||||
expect(Utils.clone(dir.media[1].metadata)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user