1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-12 04:23:09 +02:00

refactoring random photo. Fixing tests

This commit is contained in:
Patrik J. Braun 2021-01-17 15:56:15 +01:00
parent a8bbefe0f8
commit de9c58fd90
11 changed files with 171 additions and 428 deletions

View File

@ -4,19 +4,17 @@ import {NextFunction, Request, Response} from 'express';
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
import {DirectoryDTO} from '../../common/entities/DirectoryDTO'; import {DirectoryDTO} from '../../common/entities/DirectoryDTO';
import {ObjectManagers} from '../model/ObjectManagers'; import {ObjectManagers} from '../model/ObjectManagers';
import {SearchTypes} from '../../common/entities/AutoCompleteItem';
import {ContentWrapper} from '../../common/entities/ConentWrapper'; import {ContentWrapper} from '../../common/entities/ConentWrapper';
import {PhotoDTO} from '../../common/entities/PhotoDTO'; import {PhotoDTO} from '../../common/entities/PhotoDTO';
import {ProjectPath} from '../ProjectPath'; import {ProjectPath} from '../ProjectPath';
import {Config} from '../../common/config/private/Config'; import {Config} from '../../common/config/private/Config';
import {UserDTO} from '../../common/entities/UserDTO'; import {UserDTO} from '../../common/entities/UserDTO';
import {RandomQuery} from '../model/database/interfaces/IGalleryManager';
import {MediaDTO} from '../../common/entities/MediaDTO'; import {MediaDTO} from '../../common/entities/MediaDTO';
import {VideoDTO} from '../../common/entities/VideoDTO'; import {VideoDTO} from '../../common/entities/VideoDTO';
import {Utils} from '../../common/Utils'; import {Utils} from '../../common/Utils';
import {QueryParams} from '../../common/QueryParams'; import {QueryParams} from '../../common/QueryParams';
import {VideoProcessing} from '../model/fileprocessing/VideoProcessing'; import {VideoProcessing} from '../model/fileprocessing/VideoProcessing';
import {DiskMangerWorker} from '../model/threading/DiskMangerWorker'; import {SearchQueryDTO, SearchQueryTypes} from '../../common/entities/SearchQueryDTO';
export class GalleryMWs { export class GalleryMWs {
@ -115,53 +113,6 @@ export class GalleryMWs {
} }
public static async getRandomImage(req: Request, res: Response, next: NextFunction) {
if (Config.Client.RandomPhoto.enabled === false) {
return next();
}
try {
const query: RandomQuery = {};
if (req.query.directory) {
query.directory = DiskMangerWorker.normalizeDirPath(<string>req.query.directory);
}
if (req.query.recursive === 'true') {
query.recursive = true;
}
if (req.query.orientation) {
query.orientation = parseInt(req.query.orientation.toString(), 10);
}
if (req.query.maxResolution) {
query.maxResolution = parseFloat(req.query.maxResolution.toString());
}
if (req.query.minResolution) {
query.minResolution = parseFloat(req.query.minResolution.toString());
}
if (req.query.fromDate) {
query.fromDate = new Date(<string>req.query.fromDate);
}
if (req.query.toDate) {
query.toDate = new Date(<string>req.query.toDate);
}
if (query.minResolution && query.maxResolution && query.maxResolution < query.minResolution) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'Input error: min resolution is greater than the max resolution'));
}
if (query.toDate && query.fromDate && query.toDate.getTime() < query.fromDate.getTime()) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'Input error: to date is earlier than from date'));
}
const photo = await ObjectManagers.getInstance()
.GalleryManager.getRandomPhoto(query);
if (!photo) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'No photo found'));
}
req.params.mediaPath = path.join(photo.directory.path, photo.directory.name, photo.name);
return next();
} catch (e) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Can\'t get random photo: ' + e.toString()));
}
}
public static async loadFile(req: Request, res: Response, next: NextFunction) { public static async loadFile(req: Request, res: Response, next: NextFunction) {
if (!(req.params.mediaPath)) { if (!(req.params.mediaPath)) {
return next(); return next();
@ -203,7 +154,7 @@ export class GalleryMWs {
public static async search(req: Request, res: Response, next: NextFunction) { public static async search(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.enabled === false) { if (Config.Client.Search.enabled === false || !req.query[QueryParams.gallery.search.query]) {
return next(); return next();
} }
@ -211,12 +162,10 @@ export class GalleryMWs {
return next(); return next();
} }
let type: SearchTypes; const query: SearchQueryDTO = <any>req.query[QueryParams.gallery.search.type];
if (req.query[QueryParams.gallery.search.type]) {
type = parseInt(<string>req.query[QueryParams.gallery.search.type], 10);
}
try { try {
const result = await ObjectManagers.getInstance().SearchManager.search(req.params.text, type); const result = await ObjectManagers.getInstance().SearchManager.search(query);
result.directories.forEach(dir => dir.media = dir.media || []); result.directories.forEach(dir => dir.media = dir.media || []);
req.resultPipe = new ContentWrapper(null, result); req.resultPipe = new ContentWrapper(null, result);
@ -226,25 +175,6 @@ export class GalleryMWs {
} }
} }
public static async instantSearch(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.instantSearchEnabled === false) {
return next();
}
if (!(req.params.text)) {
return next();
}
try {
const result = await ObjectManagers.getInstance().SearchManager.instantSearch(req.params.text);
result.directories.forEach(dir => dir.media = dir.media || []);
req.resultPipe = new ContentWrapper(null, result);
return next();
} catch (err) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err));
}
}
public static async autocomplete(req: Request, res: Response, next: NextFunction) { public static async autocomplete(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.AutoComplete.enabled === false) { if (Config.Client.Search.AutoComplete.enabled === false) {
@ -254,8 +184,12 @@ export class GalleryMWs {
return next(); return next();
} }
let type: SearchQueryTypes = SearchQueryTypes.any_text;
if (req.query[QueryParams.gallery.search.type]) {
type = parseInt(<string>req.query[QueryParams.gallery.search.type], 10);
}
try { try {
req.resultPipe = await ObjectManagers.getInstance().SearchManager.autocomplete(req.params.text); req.resultPipe = await ObjectManagers.getInstance().SearchManager.autocomplete(req.params.text, type);
return next(); return next();
} catch (err) { } catch (err) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err)); return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err));
@ -264,4 +198,25 @@ export class GalleryMWs {
} }
public static async getRandomImage(req: Request, res: Response, next: NextFunction) {
if (Config.Client.RandomPhoto.enabled === false) {
return next();
}
try {
const query: SearchQueryDTO = <any>req.query[QueryParams.gallery.search.type];
const photo = await ObjectManagers.getInstance()
.SearchManager.getRandomPhoto(query);
if (!photo) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'No photo found'));
}
req.params.mediaPath = path.join(photo.directory.path, photo.directory.name, photo.name);
return next();
} catch (e) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Can\'t get random photo: ' + e.toString()));
}
}
} }

View File

@ -17,6 +17,5 @@ export interface IGalleryManager {
knownLastModified?: number, knownLastModified?: number,
knownLastScanned?: number): Promise<DirectoryDTO>; knownLastScanned?: number): Promise<DirectoryDTO>;
getRandomPhoto(queryFilter: RandomQuery): Promise<PhotoDTO>;
} }

View File

@ -1,11 +1,13 @@
import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem'; import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem';
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO'; import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO'; import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {RandomQuery} from './IGalleryManager';
export interface ISearchManager { export interface ISearchManager {
autocomplete(text: string): Promise<AutoCompleteItem[]>; autocomplete(text: string, type: SearchQueryTypes): Promise<AutoCompleteItem[]>;
search(query: SearchQueryDTO): Promise<SearchResultDTO>; search(query: SearchQueryDTO): Promise<SearchResultDTO>;
instantSearch(text: string): Promise<SearchResultDTO>; getRandomPhoto(queryFilter: SearchQueryDTO): Promise<PhotoDTO>;
} }

View File

@ -4,16 +4,11 @@ import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO'; import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
export class SearchManager implements ISearchManager { export class SearchManager implements ISearchManager {
autocomplete(text: string): Promise<AutoCompleteItem[]> { autocomplete(text: string, type: SearchQueryTypes): Promise<AutoCompleteItem[]> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
search(query: SearchQueryDTO): Promise<SearchResultDTO> { search(query: SearchQueryDTO): Promise<SearchResultDTO> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
instantSearch(text: string): Promise<SearchResultDTO> {
throw new Error('Method not implemented.');
}
} }

View File

@ -19,6 +19,7 @@ import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {ObjectManagers} from '../../ObjectManagers'; import {ObjectManagers} from '../../ObjectManagers';
import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO'; import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig'; import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
const LOG_TAG = '[GalleryManager]'; const LOG_TAG = '[GalleryManager]';
@ -87,62 +88,6 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
} }
public async getRandomPhoto(queryFilter: RandomQuery): Promise<PhotoDTO> {
const connection = await SQLConnection.getConnection();
const photosRepository = connection.getRepository(PhotoEntity);
const query: SelectQueryBuilder<PhotoEntity> = photosRepository.createQueryBuilder('photo');
query.innerJoinAndSelect('photo.directory', 'directory');
if (queryFilter.directory) {
const directoryName = path.basename(queryFilter.directory);
const directoryParent = path.join(path.dirname(queryFilter.directory), path.sep);
query.where(new Brackets(qb => {
qb.where('directory.name = :name AND directory.path = :path', {
name: directoryName,
path: directoryParent
});
if (queryFilter.recursive) {
qb.orWhere('directory.path LIKE :text COLLATE utf8_general_ci', {text: queryFilter.directory + '%'});
}
}));
}
if (queryFilter.fromDate) {
query.andWhere('photo.metadata.creationDate >= :fromDate', {
fromDate: queryFilter.fromDate.getTime()
});
}
if (queryFilter.toDate) {
query.andWhere('photo.metadata.creationDate <= :toDate', {
toDate: queryFilter.toDate.getTime()
});
}
if (queryFilter.minResolution) {
query.andWhere('photo.metadata.size.width * photo.metadata.size.height >= :minRes', {
minRes: queryFilter.minResolution * 1000 * 1000
});
}
if (queryFilter.maxResolution) {
query.andWhere('photo.metadata.size.width * photo.metadata.size.height <= :maxRes', {
maxRes: queryFilter.maxResolution * 1000 * 1000
});
}
if (queryFilter.orientation === OrientationType.landscape) {
query.andWhere('photo.metadata.size.width >= photo.metadata.size.height');
}
if (queryFilter.orientation === OrientationType.portrait) {
query.andWhere('photo.metadata.size.width <= photo.metadata.size.height');
}
if (Config.Server.Database.type === ServerConfig.DatabaseType.mysql) {
return await query.groupBy('RAND(), photo.id').limit(1).getOne();
}
return await query.groupBy('RANDOM()').limit(1).getOne();
}
async countDirectories(): Promise<number> { async countDirectories(): Promise<number> {
const connection = await SQLConnection.getConnection(); const connection = await SQLConnection.getConnection();

View File

@ -27,6 +27,8 @@ import {
import {GalleryManager} from './GalleryManager'; import {GalleryManager} from './GalleryManager';
import {ObjectManagers} from '../../ObjectManagers'; import {ObjectManagers} from '../../ObjectManagers';
import {Utils} from '../../../../common/Utils'; import {Utils} from '../../../../common/Utils';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
export class SearchManager implements ISearchManager { export class SearchManager implements ISearchManager {
@ -43,7 +45,7 @@ export class SearchManager implements ISearchManager {
return a; return a;
} }
async autocomplete(text: string): Promise<AutoCompleteItem[]> { async autocomplete(text: string, type: SearchQueryTypes): Promise<AutoCompleteItem[]> {
const connection = await SQLConnection.getConnection(); const connection = await SQLConnection.getConnection();
@ -54,88 +56,83 @@ export class SearchManager implements ISearchManager {
const directoryRepository = connection.getRepository(DirectoryEntity); const directoryRepository = connection.getRepository(DirectoryEntity);
(await photoRepository if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.keyword) {
.createQueryBuilder('photo') (await photoRepository
.select('DISTINCT(photo.metadata.keywords)') .createQueryBuilder('photo')
.where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .select('DISTINCT(photo.metadata.keywords)')
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.getRawMany()) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.map(r => <Array<string>>(<string>r.metadataKeywords).split(',')) .getRawMany())
.forEach(keywords => { .map(r => <Array<string>>(<string>r.metadataKeywords).split(','))
result = result.concat(this.encapsulateAutoComplete(keywords .forEach(keywords => {
.filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.keyword)); result = result.concat(this.encapsulateAutoComplete(keywords
}); .filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.keyword));
});
}
result = result.concat(this.encapsulateAutoComplete((await personRepository if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.person) {
.createQueryBuilder('person') result = result.concat(this.encapsulateAutoComplete((await personRepository
.select('DISTINCT(person.name)') .createQueryBuilder('person')
.where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .select('DISTINCT(person.name)')
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orderBy('person.name') .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany()) .orderBy('person.name')
.map(r => r.name), SearchQueryTypes.person)); .getRawMany())
.map(r => r.name), SearchQueryTypes.person));
}
(await photoRepository if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.position) {
.createQueryBuilder('photo') (await photoRepository
.select('photo.metadata.positionData.country as country, ' + .createQueryBuilder('photo')
'photo.metadata.positionData.state as state, photo.metadata.positionData.city as city') .select('photo.metadata.positionData.country as country, ' +
.where('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) 'photo.metadata.positionData.state as state, photo.metadata.positionData.city as city')
.orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .where('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city') .orWhere('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city')
.getRawMany()) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.filter(pm => !!pm) .getRawMany())
.map(pm => <Array<string>>[pm.city || '', pm.country || '', pm.state || '']) .filter(pm => !!pm)
.forEach(positions => { .map(pm => <Array<string>>[pm.city || '', pm.country || '', pm.state || ''])
result = result.concat(this.encapsulateAutoComplete(positions .forEach(positions => {
.filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.position)); result = result.concat(this.encapsulateAutoComplete(positions
}); .filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.position));
});
}
result = result.concat(this.encapsulateAutoComplete((await mediaRepository if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.file_name) {
.createQueryBuilder('media') result = result.concat(this.encapsulateAutoComplete((await mediaRepository
.select('DISTINCT(media.name)') .createQueryBuilder('media')
.where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .select('DISTINCT(media.name)')
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.getRawMany()) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.map(r => r.name), SearchQueryTypes.file_name)); .getRawMany())
.map(r => r.name), SearchQueryTypes.file_name));
}
if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.caption) {
result = result.concat(this.encapsulateAutoComplete((await photoRepository
.createQueryBuilder('media')
.select('DISTINCT(media.metadata.caption) as caption')
.where('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany())
.map(r => r.caption), SearchQueryTypes.caption));
}
result = result.concat(this.encapsulateAutoComplete((await photoRepository if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.directory) {
.createQueryBuilder('media') result = result.concat(this.encapsulateAutoComplete((await directoryRepository
.select('DISTINCT(media.metadata.caption) as caption') .createQueryBuilder('dir')
.where('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .select('DISTINCT(dir.name)')
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.getRawMany()) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.map(r => r.caption), SearchQueryTypes.caption)); .getRawMany())
.map(r => r.name), SearchQueryTypes.directory));
}
result = result.concat(this.encapsulateAutoComplete((await directoryRepository
.createQueryBuilder('dir')
.select('DISTINCT(dir.name)')
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany())
.map(r => r.name), SearchQueryTypes.directory));
return SearchManager.autoCompleteItemsUnique(result); return SearchManager.autoCompleteItemsUnique(result);
} }
async getGPSData(query: 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);
}
return query;
}
async search(queryIN: SearchQueryDTO) { async search(queryIN: SearchQueryDTO) {
let query = this.flattenSameOfQueries(queryIN); let query = this.flattenSameOfQueries(queryIN);
query = await this.getGPSData(query); query = await this.getGPSData(query);
@ -188,51 +185,34 @@ export class SearchManager implements ISearchManager {
return result; return result;
} }
async instantSearch(text: string): Promise<SearchResultDTO> { public async getRandomPhoto(query: SearchQueryDTO): Promise<PhotoDTO> {
const connection = await SQLConnection.getConnection(); const connection = await SQLConnection.getConnection();
const sqlQuery: SelectQueryBuilder<PhotoEntity> = connection
const result: SearchResultDTO = { .getRepository(PhotoEntity)
searchQuery: <TextSearch>{type: SearchQueryTypes.any_text, text: text}, .createQueryBuilder('media')
// searchType:undefined, not adding this .innerJoinAndSelect('media.directory', 'directory')
directories: [], .where(this.buildWhereQuery(query));
media: [],
metaFile: [],
resultOverflow: false
};
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 this.loadMediaWithFaces(query); if (Config.Server.Database.type === ServerConfig.DatabaseType.mysql) {
return await sqlQuery.groupBy('RAND(), media.id').limit(1).getOne();
}
return await sqlQuery.groupBy('RANDOM()').limit(1).getOne();
}
result.directories = await connection private async getGPSData(query: SearchQueryDTO) {
.getRepository(DirectoryEntity) if ((query as ANDSearchQuery | ORSearchQuery).list) {
.createQueryBuilder('dir') for (let i = 0; i < (query as ANDSearchQuery | ORSearchQuery).list.length; ++i) {
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) (query as ANDSearchQuery | ORSearchQuery).list[i] =
.limit(10) await this.getGPSData((query as ANDSearchQuery | ORSearchQuery).list[i]);
.getMany(); }
}
return result; if (query.type === SearchQueryTypes.distance && (<DistanceSearch>query).from.text) {
(<DistanceSearch>query).from.GPSData =
await ObjectManagers.getInstance().LocationManager.getGPSData((<DistanceSearch>query).from.text);
}
return query;
} }
private buildWhereQuery(query: SearchQueryDTO, paramCounter = {value: 0}): Brackets { private buildWhereQuery(query: SearchQueryDTO, paramCounter = {value: 0}): Brackets {

View File

@ -25,7 +25,6 @@ export class GalleryRouter {
this.addDirectoryList(app); this.addDirectoryList(app);
this.addSearch(app); this.addSearch(app);
this.addInstantSearch(app);
this.addAutoComplete(app); this.addAutoComplete(app);
} }
@ -199,20 +198,6 @@ export class GalleryRouter {
); );
} }
protected static addInstantSearch(app: Express) {
app.get('/api/instant-search/:text',
// common part
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),
VersionMWs.injectGalleryVersion,
// specific part
GalleryMWs.instantSearch,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.cleanUpGalleryResults,
RenderingMWs.renderResult
);
}
protected static addAutoComplete(app: Express) { protected static addAutoComplete(app: Express) {
app.get('/api/autocomplete/:text', app.get('/api/autocomplete/:text',

View File

@ -10,7 +10,8 @@ export const QueryParams = {
maxResolution: 'toRes' maxResolution: 'toRes'
}, },
search: { search: {
type: 'type' type: 'type',
query: 'qs'
}, },
photo: 'p', photo: 'p',
sharingKey_query: 'sk', sharingKey_query: 'sk',

View File

@ -25,12 +25,6 @@ export module ClientConfig {
export class SearchConfig { export class SearchConfig {
@ConfigProperty() @ConfigProperty()
enabled: boolean = true; enabled: boolean = true;
@ConfigProperty()
instantSearchEnabled: boolean = true;
@ConfigProperty({type: 'unsignedInt'})
InstantSearchTimeout: number = 3000;
@ConfigProperty({type: 'unsignedInt'})
instantSearchCacheTimeout: number = 1000 * 60 * 60;
@ConfigProperty({type: 'unsignedInt'}) @ConfigProperty({type: 'unsignedInt'})
searchCacheTimeout: number = 1000 * 60 * 60; searchCacheTimeout: number = 1000 * 60 * 60;
@ConfigProperty() @ConfigProperty()

View File

@ -1,21 +1,6 @@
import {expect} from 'chai';
import {TestHelper} from './TestHelper';
import {SQLTestHelper} from '../../../SQLTestHelper'; import {SQLTestHelper} from '../../../SQLTestHelper';
import {GalleryManager} from '../../../../../src/backend/model/database/sql/GalleryManager'; import {GalleryManager} from '../../../../../src/backend/model/database/sql/GalleryManager';
import {IndexingManager} from '../../../../../src/backend/model/database/sql/IndexingManager';
import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
import {Utils} from '../../../../../src/common/Utils';
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager';
import {MediaEntity} from '../../../../../src/backend/model/database/sql/enitites/MediaEntity';
import {VersionManager} from '../../../../../src/backend/model/database/sql/VersionManager';
class IndexingManagerTest extends IndexingManager {
public async saveToDB(scannedDirectory: DirectoryDTO): Promise<void> {
return super.saveToDB(scannedDirectory);
}
}
// to help WebStorm to handle the test cases // to help WebStorm to handle the test cases
declare let describe: any; declare let describe: any;
@ -25,36 +10,4 @@ describe = SQLTestHelper.describe;
describe('GalleryManager', (sqlHelper: SQLTestHelper) => { describe('GalleryManager', (sqlHelper: SQLTestHelper) => {
beforeEach(async () => {
await sqlHelper.initDB();
ObjectManagers.getInstance().PersonManager = new PersonManager();
ObjectManagers.getInstance().VersionManager = new VersionManager();
});
after(async () => {
await sqlHelper.clearDB();
});
it('should get random photo', async () => {
const gm = new GalleryManager();
const im = new IndexingManagerTest();
const parent = TestHelper.getRandomizedDirectoryEntry();
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
expect(await gm.getRandomPhoto({})).to.not.exist;
DirectoryDTO.removeReferences(parent);
await im.saveToDB(Utils.clone(parent));
delete p1.metadata.faces;
delete p1.directory;
delete p1.id;
const found: MediaEntity = <any>await gm.getRandomPhoto({});
delete found.metadata.bitRate;
delete found.metadata.duration;
delete found.directory;
delete found.id;
expect(Utils.clone(found)).to.be.deep.equal(Utils.clone(p1));
});
}); });

View File

@ -105,30 +105,6 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
const setUpSqlDB = async () => { const setUpSqlDB = async () => {
await sqlHelper.initDB(); await sqlHelper.initDB();
await setUpTestGallery(); await setUpTestGallery();
/*
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 savePhoto(p);
await savePhoto(p2);
await savePhoto(p_faceLess);
await conn.getRepository(VideoEntity).save(v);*/
// await SQLConnection.close();
}; };
@ -151,53 +127,46 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
return a.text.localeCompare(b.text); return a.text.localeCompare(b.text);
}; };
expect((await sm.autocomplete('tat'))).to.deep.equalInAnyOrder([new AutoCompleteItem('Tatooine', SearchQueryTypes.position)]); expect((await sm.autocomplete('tat', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
expect((await sm.autocomplete('star'))).to.deep.equalInAnyOrder([new AutoCompleteItem('star wars', SearchQueryTypes.keyword), new AutoCompleteItem('Tatooine', SearchQueryTypes.position)]);
expect((await sm.autocomplete('star', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
new AutoCompleteItem('star wars', SearchQueryTypes.keyword),
new AutoCompleteItem('death star', SearchQueryTypes.keyword)]); new AutoCompleteItem('death star', SearchQueryTypes.keyword)]);
expect((await sm.autocomplete('wars'))).to.deep.equalInAnyOrder([new AutoCompleteItem('star wars', SearchQueryTypes.keyword), expect((await sm.autocomplete('wars', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
new AutoCompleteItem('star wars', SearchQueryTypes.keyword),
new AutoCompleteItem('wars dir', SearchQueryTypes.directory)]); new AutoCompleteItem('wars dir', SearchQueryTypes.directory)]);
expect((await sm.autocomplete('arch'))).eql([new AutoCompleteItem('Research City', SearchQueryTypes.position)]); expect((await sm.autocomplete('arch', SearchQueryTypes.any_text))).eql([
new AutoCompleteItem('Research City', SearchQueryTypes.position)]);
Config.Client.Search.AutoComplete.maxItemsPerCategory = 99999; Config.Client.Search.AutoComplete.maxItemsPerCategory = 99999;
expect((await sm.autocomplete('a')).sort(cmp)).eql([ expect((await sm.autocomplete('wa', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
new AutoCompleteItem('Boba Fett', SearchQueryTypes.keyword),
new AutoCompleteItem('Boba Fett', SearchQueryTypes.person),
new AutoCompleteItem('star wars', SearchQueryTypes.keyword), new AutoCompleteItem('star wars', SearchQueryTypes.keyword),
new AutoCompleteItem('Anakin', SearchQueryTypes.keyword),
new AutoCompleteItem('Anakin Skywalker', SearchQueryTypes.person), new AutoCompleteItem('Anakin Skywalker', SearchQueryTypes.person),
new AutoCompleteItem('Luke Skywalker', SearchQueryTypes.person), new AutoCompleteItem('Luke Skywalker', SearchQueryTypes.person),
new AutoCompleteItem('Han Solo', SearchQueryTypes.person), new AutoCompleteItem('wars dir', SearchQueryTypes.directory)]);
new AutoCompleteItem('death star', SearchQueryTypes.keyword),
new AutoCompleteItem('Padmé Amidala', SearchQueryTypes.person),
new AutoCompleteItem('Obivan Kenobi', SearchQueryTypes.person),
new AutoCompleteItem('Arvíztűrő Tükörfúrógép', SearchQueryTypes.person),
new AutoCompleteItem('Padmé Amidala', SearchQueryTypes.keyword),
new AutoCompleteItem('Natalie Portman', SearchQueryTypes.keyword),
new AutoCompleteItem('Han Solo\'s dice', SearchQueryTypes.caption),
new AutoCompleteItem('Kamino', SearchQueryTypes.position),
new AutoCompleteItem('Tatooine', SearchQueryTypes.position),
new AutoCompleteItem('wars dir', SearchQueryTypes.directory),
new AutoCompleteItem('Research City', SearchQueryTypes.position)].sort(cmp));
Config.Client.Search.AutoComplete.maxItemsPerCategory = 1; Config.Client.Search.AutoComplete.maxItemsPerCategory = 1;
expect((await sm.autocomplete('a')).sort(cmp)).eql([ expect((await sm.autocomplete('a', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
new AutoCompleteItem('Anakin', SearchQueryTypes.keyword), new AutoCompleteItem('Ajan Kloss', SearchQueryTypes.position),
new AutoCompleteItem('Amber stone', SearchQueryTypes.caption),
new AutoCompleteItem('star wars', SearchQueryTypes.keyword), new AutoCompleteItem('star wars', SearchQueryTypes.keyword),
new AutoCompleteItem('death star', SearchQueryTypes.keyword),
new AutoCompleteItem('Anakin Skywalker', SearchQueryTypes.person), new AutoCompleteItem('Anakin Skywalker', SearchQueryTypes.person),
new AutoCompleteItem('Han Solo\'s dice', SearchQueryTypes.caption), new AutoCompleteItem('Castilon', SearchQueryTypes.position),
new AutoCompleteItem('Kamino', SearchQueryTypes.position), new AutoCompleteItem('Devaron', SearchQueryTypes.position),
new AutoCompleteItem('Research City', SearchQueryTypes.position), new AutoCompleteItem('The Phantom Menace', SearchQueryTypes.directory)]);
new AutoCompleteItem('wars dir', SearchQueryTypes.directory),
new AutoCompleteItem('Boba Fett', SearchQueryTypes.keyword)].sort(cmp));
Config.Client.Search.AutoComplete.maxItemsPerCategory = 5; Config.Client.Search.AutoComplete.maxItemsPerCategory = 5;
expect((await sm.autocomplete('sw')).sort(cmp)).to.deep.equalInAnyOrder([new AutoCompleteItem('sw1', SearchQueryTypes.file_name), expect((await sm.autocomplete('sw', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
new AutoCompleteItem('sw2', SearchQueryTypes.file_name), new AutoCompleteItem(v.name, SearchQueryTypes.file_name)].sort(cmp)); new AutoCompleteItem('sw1.jpg', SearchQueryTypes.file_name),
new AutoCompleteItem('sw2.jpg', SearchQueryTypes.file_name),
new AutoCompleteItem('sw3.jpg', SearchQueryTypes.file_name),
new AutoCompleteItem('sw4.jpg', SearchQueryTypes.file_name),
new AutoCompleteItem(v.name, SearchQueryTypes.file_name)]);
expect((await sm.autocomplete(v.name)).sort(cmp)).to.deep.equalInAnyOrder([new AutoCompleteItem(v.name, SearchQueryTypes.file_name)]); expect((await sm.autocomplete(v.name, SearchQueryTypes.any_text))).to.deep.equalInAnyOrder(
[new AutoCompleteItem(v.name, SearchQueryTypes.file_name)]);
}); });
@ -896,7 +865,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
resultOverflow: false resultOverflow: false
})); }));
query = <RatingSearch>{min: 0, max: 5, type: SearchQueryTypes.rating}; query = <RatingSearch>{min: 0, max: 5, type: SearchQueryTypes.rating};
expect(Utils.clone(await sm.search(query))) expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{ .to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
searchQuery: query, searchQuery: query,
@ -1117,57 +1086,22 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
}); });
it('should instant search', async () => {
it('should get random photo', async () => {
const sm = new SearchManager(); const sm = new SearchManager();
expect(Utils.clone(await sm.instantSearch('sw'))).to.deep.equalInAnyOrder(Utils.clone({ let query = <TextSearch>{
searchText: 'sw', text: 'xyz',
directories: [], type: SearchQueryTypes.keyword
media: [p, p2, v], };
metaFile: [], expect(await sm.getRandomPhoto(query)).to.not.exist;
resultOverflow: false
}));
expect(Utils.clone(await sm.instantSearch('Tatooine'))).to.deep.equalInAnyOrder(Utils.clone({ query = <TextSearch>{
searchText: 'Tatooine', text: 'wookiees',
directories: [], matchType: TextSearchQueryTypes.exact_match,
media: [p], type: SearchQueryTypes.keyword
metaFile: [], };
resultOverflow: false expect(Utils.clone(await sm.getRandomPhoto(query))).to.deep.equalInAnyOrder(searchifyMedia(p_faceLess));
}));
expect(Utils.clone(await sm.instantSearch('ortm'))).to.deep.equalInAnyOrder(Utils.clone({
searchText: 'ortm',
directories: [],
media: [p2, p_faceLess],
metaFile: [],
resultOverflow: false
}));
expect(Utils.clone(await sm.instantSearch('wa'))).to.deep.equalInAnyOrder(Utils.clone({
searchText: 'wa',
directories: [dir],
media: [p, p2, p_faceLess],
metaFile: [],
resultOverflow: false
}));
expect(Utils.clone(await sm.instantSearch('han'))).to.deep.equalInAnyOrder(Utils.clone({
searchText: 'han',
directories: [],
media: [p],
metaFile: [],
resultOverflow: false
}));
expect(Utils.clone(await sm.instantSearch('Boba'))).to.deep.equalInAnyOrder(Utils.clone({
searchText: 'Boba',
directories: [],
media: [p],
metaFile: [],
resultOverflow: false
}));
}); });
}); });