mirror of
https://github.com/xuthus83/pigallery2.git
synced 2024-11-03 21:04:03 +08:00
refactoring random photo. Fixing tests
This commit is contained in:
parent
a8bbefe0f8
commit
de9c58fd90
@ -4,19 +4,17 @@ import {NextFunction, Request, Response} from 'express';
|
||||
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
|
||||
import {DirectoryDTO} from '../../common/entities/DirectoryDTO';
|
||||
import {ObjectManagers} from '../model/ObjectManagers';
|
||||
import {SearchTypes} from '../../common/entities/AutoCompleteItem';
|
||||
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 {RandomQuery} from '../model/database/interfaces/IGalleryManager';
|
||||
import {MediaDTO} from '../../common/entities/MediaDTO';
|
||||
import {VideoDTO} from '../../common/entities/VideoDTO';
|
||||
import {Utils} from '../../common/Utils';
|
||||
import {QueryParams} from '../../common/QueryParams';
|
||||
import {VideoProcessing} from '../model/fileprocessing/VideoProcessing';
|
||||
import {DiskMangerWorker} from '../model/threading/DiskMangerWorker';
|
||||
import {SearchQueryDTO, SearchQueryTypes} from '../../common/entities/SearchQueryDTO';
|
||||
|
||||
|
||||
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) {
|
||||
if (!(req.params.mediaPath)) {
|
||||
return next();
|
||||
@ -203,7 +154,7 @@ export class GalleryMWs {
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -211,12 +162,10 @@ export class GalleryMWs {
|
||||
return next();
|
||||
}
|
||||
|
||||
let type: SearchTypes;
|
||||
if (req.query[QueryParams.gallery.search.type]) {
|
||||
type = parseInt(<string>req.query[QueryParams.gallery.search.type], 10);
|
||||
}
|
||||
const query: SearchQueryDTO = <any>req.query[QueryParams.gallery.search.type];
|
||||
|
||||
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 || []);
|
||||
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) {
|
||||
if (Config.Client.Search.AutoComplete.enabled === false) {
|
||||
@ -254,8 +184,12 @@ export class GalleryMWs {
|
||||
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 {
|
||||
req.resultPipe = await ObjectManagers.getInstance().SearchManager.autocomplete(req.params.text);
|
||||
req.resultPipe = await ObjectManagers.getInstance().SearchManager.autocomplete(req.params.text, type);
|
||||
return next();
|
||||
} catch (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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -17,6 +17,5 @@ export interface IGalleryManager {
|
||||
knownLastModified?: number,
|
||||
knownLastScanned?: number): Promise<DirectoryDTO>;
|
||||
|
||||
getRandomPhoto(queryFilter: RandomQuery): Promise<PhotoDTO>;
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem';
|
||||
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
|
||||
import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
|
||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
import {RandomQuery} from './IGalleryManager';
|
||||
|
||||
export interface ISearchManager {
|
||||
autocomplete(text: string): Promise<AutoCompleteItem[]>;
|
||||
autocomplete(text: string, type: SearchQueryTypes): Promise<AutoCompleteItem[]>;
|
||||
|
||||
search(query: SearchQueryDTO): Promise<SearchResultDTO>;
|
||||
|
||||
instantSearch(text: string): Promise<SearchResultDTO>;
|
||||
getRandomPhoto(queryFilter: SearchQueryDTO): Promise<PhotoDTO>;
|
||||
}
|
||||
|
@ -4,16 +4,11 @@ import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
|
||||
import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
|
||||
|
||||
export class SearchManager implements ISearchManager {
|
||||
autocomplete(text: string): Promise<AutoCompleteItem[]> {
|
||||
autocomplete(text: string, type: SearchQueryTypes): Promise<AutoCompleteItem[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
search(query: SearchQueryDTO): Promise<SearchResultDTO> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
instantSearch(text: string): Promise<SearchResultDTO> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import {FaceRegionEntry} from './enitites/FaceRegionEntry';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO';
|
||||
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
|
||||
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
|
||||
|
||||
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> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
|
@ -27,6 +27,8 @@ import {
|
||||
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';
|
||||
|
||||
export class SearchManager implements ISearchManager {
|
||||
|
||||
@ -43,7 +45,7 @@ export class SearchManager implements ISearchManager {
|
||||
return a;
|
||||
}
|
||||
|
||||
async autocomplete(text: string): Promise<AutoCompleteItem[]> {
|
||||
async autocomplete(text: string, type: SearchQueryTypes): Promise<AutoCompleteItem[]> {
|
||||
|
||||
const connection = await SQLConnection.getConnection();
|
||||
|
||||
@ -54,88 +56,83 @@ export class SearchManager implements ISearchManager {
|
||||
const directoryRepository = connection.getRepository(DirectoryEntity);
|
||||
|
||||
|
||||
(await photoRepository
|
||||
.createQueryBuilder('photo')
|
||||
.select('DISTINCT(photo.metadata.keywords)')
|
||||
.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 => {
|
||||
result = result.concat(this.encapsulateAutoComplete(keywords
|
||||
.filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.keyword));
|
||||
});
|
||||
if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.keyword) {
|
||||
(await photoRepository
|
||||
.createQueryBuilder('photo')
|
||||
.select('DISTINCT(photo.metadata.keywords)')
|
||||
.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 => {
|
||||
result = result.concat(this.encapsulateAutoComplete(keywords
|
||||
.filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.keyword));
|
||||
});
|
||||
}
|
||||
|
||||
result = result.concat(this.encapsulateAutoComplete((await personRepository
|
||||
.createQueryBuilder('person')
|
||||
.select('DISTINCT(person.name)')
|
||||
.where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
|
||||
.orderBy('person.name')
|
||||
.getRawMany())
|
||||
.map(r => r.name), SearchQueryTypes.person));
|
||||
if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.person) {
|
||||
result = result.concat(this.encapsulateAutoComplete((await personRepository
|
||||
.createQueryBuilder('person')
|
||||
.select('DISTINCT(person.name)')
|
||||
.where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
|
||||
.orderBy('person.name')
|
||||
.getRawMany())
|
||||
.map(r => r.name), SearchQueryTypes.person));
|
||||
}
|
||||
|
||||
(await photoRepository
|
||||
.createQueryBuilder('photo')
|
||||
.select('photo.metadata.positionData.country as country, ' +
|
||||
'photo.metadata.positionData.state as state, photo.metadata.positionData.city as city')
|
||||
.where('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.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 => {
|
||||
result = result.concat(this.encapsulateAutoComplete(positions
|
||||
.filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.position));
|
||||
});
|
||||
if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.position) {
|
||||
(await photoRepository
|
||||
.createQueryBuilder('photo')
|
||||
.select('photo.metadata.positionData.country as country, ' +
|
||||
'photo.metadata.positionData.state as state, photo.metadata.positionData.city as city')
|
||||
.where('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.orWhere('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.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 => {
|
||||
result = result.concat(this.encapsulateAutoComplete(positions
|
||||
.filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.position));
|
||||
});
|
||||
}
|
||||
|
||||
result = result.concat(this.encapsulateAutoComplete((await mediaRepository
|
||||
.createQueryBuilder('media')
|
||||
.select('DISTINCT(media.name)')
|
||||
.where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
|
||||
.getRawMany())
|
||||
.map(r => r.name), SearchQueryTypes.file_name));
|
||||
if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.file_name) {
|
||||
result = result.concat(this.encapsulateAutoComplete((await mediaRepository
|
||||
.createQueryBuilder('media')
|
||||
.select('DISTINCT(media.name)')
|
||||
.where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
|
||||
.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
|
||||
.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 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));
|
||||
|
||||
if (type === SearchQueryTypes.any_text || type === 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
let query = this.flattenSameOfQueries(queryIN);
|
||||
query = await this.getGPSData(query);
|
||||
@ -188,51 +185,34 @@ export class SearchManager implements ISearchManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
async instantSearch(text: string): Promise<SearchResultDTO> {
|
||||
public async getRandomPhoto(query: SearchQueryDTO): Promise<PhotoDTO> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
|
||||
const result: SearchResultDTO = {
|
||||
searchQuery: <TextSearch>{type: SearchQueryTypes.any_text, text: text},
|
||||
// searchType:undefined, not adding this
|
||||
directories: [],
|
||||
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');
|
||||
const sqlQuery: SelectQueryBuilder<PhotoEntity> = connection
|
||||
.getRepository(PhotoEntity)
|
||||
.createQueryBuilder('media')
|
||||
.innerJoinAndSelect('media.directory', 'directory')
|
||||
.where(this.buildWhereQuery(query));
|
||||
|
||||
|
||||
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
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('dir')
|
||||
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||
.limit(10)
|
||||
.getMany();
|
||||
|
||||
return result;
|
||||
private 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;
|
||||
}
|
||||
|
||||
private buildWhereQuery(query: SearchQueryDTO, paramCounter = {value: 0}): Brackets {
|
||||
|
@ -25,7 +25,6 @@ export class GalleryRouter {
|
||||
this.addDirectoryList(app);
|
||||
|
||||
this.addSearch(app);
|
||||
this.addInstantSearch(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) {
|
||||
app.get('/api/autocomplete/:text',
|
||||
|
@ -10,7 +10,8 @@ export const QueryParams = {
|
||||
maxResolution: 'toRes'
|
||||
},
|
||||
search: {
|
||||
type: 'type'
|
||||
type: 'type',
|
||||
query: 'qs'
|
||||
},
|
||||
photo: 'p',
|
||||
sharingKey_query: 'sk',
|
||||
|
@ -25,12 +25,6 @@ export module ClientConfig {
|
||||
export class SearchConfig {
|
||||
@ConfigProperty()
|
||||
enabled: boolean = true;
|
||||
@ConfigProperty()
|
||||
instantSearchEnabled: boolean = true;
|
||||
@ConfigProperty({type: 'unsignedInt'})
|
||||
InstantSearchTimeout: number = 3000;
|
||||
@ConfigProperty({type: 'unsignedInt'})
|
||||
instantSearchCacheTimeout: number = 1000 * 60 * 60;
|
||||
@ConfigProperty({type: 'unsignedInt'})
|
||||
searchCacheTimeout: number = 1000 * 60 * 60;
|
||||
@ConfigProperty()
|
||||
|
@ -1,21 +1,6 @@
|
||||
import {expect} from 'chai';
|
||||
import {TestHelper} from './TestHelper';
|
||||
import {SQLTestHelper} from '../../../SQLTestHelper';
|
||||
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
|
||||
declare let describe: any;
|
||||
@ -25,36 +10,4 @@ describe = SQLTestHelper.describe;
|
||||
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));
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -105,30 +105,6 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
const setUpSqlDB = async () => {
|
||||
await sqlHelper.initDB();
|
||||
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);
|
||||
};
|
||||
|
||||
expect((await sm.autocomplete('tat'))).to.deep.equalInAnyOrder([new AutoCompleteItem('Tatooine', SearchQueryTypes.position)]);
|
||||
expect((await sm.autocomplete('star'))).to.deep.equalInAnyOrder([new AutoCompleteItem('star wars', SearchQueryTypes.keyword),
|
||||
expect((await sm.autocomplete('tat', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
|
||||
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)]);
|
||||
|
||||
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)]);
|
||||
|
||||
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;
|
||||
expect((await sm.autocomplete('a')).sort(cmp)).eql([
|
||||
new AutoCompleteItem('Boba Fett', SearchQueryTypes.keyword),
|
||||
new AutoCompleteItem('Boba Fett', SearchQueryTypes.person),
|
||||
expect((await sm.autocomplete('wa', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
|
||||
new AutoCompleteItem('star wars', SearchQueryTypes.keyword),
|
||||
new AutoCompleteItem('Anakin', SearchQueryTypes.keyword),
|
||||
new AutoCompleteItem('Anakin Skywalker', SearchQueryTypes.person),
|
||||
new AutoCompleteItem('Luke Skywalker', SearchQueryTypes.person),
|
||||
new AutoCompleteItem('Han Solo', SearchQueryTypes.person),
|
||||
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));
|
||||
new AutoCompleteItem('wars dir', SearchQueryTypes.directory)]);
|
||||
|
||||
Config.Client.Search.AutoComplete.maxItemsPerCategory = 1;
|
||||
expect((await sm.autocomplete('a')).sort(cmp)).eql([
|
||||
new AutoCompleteItem('Anakin', SearchQueryTypes.keyword),
|
||||
expect((await sm.autocomplete('a', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
|
||||
new AutoCompleteItem('Ajan Kloss', SearchQueryTypes.position),
|
||||
new AutoCompleteItem('Amber stone', SearchQueryTypes.caption),
|
||||
new AutoCompleteItem('star wars', SearchQueryTypes.keyword),
|
||||
new AutoCompleteItem('death star', SearchQueryTypes.keyword),
|
||||
new AutoCompleteItem('Anakin Skywalker', SearchQueryTypes.person),
|
||||
new AutoCompleteItem('Han Solo\'s dice', SearchQueryTypes.caption),
|
||||
new AutoCompleteItem('Kamino', SearchQueryTypes.position),
|
||||
new AutoCompleteItem('Research City', SearchQueryTypes.position),
|
||||
new AutoCompleteItem('wars dir', SearchQueryTypes.directory),
|
||||
new AutoCompleteItem('Boba Fett', SearchQueryTypes.keyword)].sort(cmp));
|
||||
new AutoCompleteItem('Castilon', SearchQueryTypes.position),
|
||||
new AutoCompleteItem('Devaron', SearchQueryTypes.position),
|
||||
new AutoCompleteItem('The Phantom Menace', SearchQueryTypes.directory)]);
|
||||
Config.Client.Search.AutoComplete.maxItemsPerCategory = 5;
|
||||
|
||||
expect((await sm.autocomplete('sw')).sort(cmp)).to.deep.equalInAnyOrder([new AutoCompleteItem('sw1', SearchQueryTypes.file_name),
|
||||
new AutoCompleteItem('sw2', SearchQueryTypes.file_name), new AutoCompleteItem(v.name, SearchQueryTypes.file_name)].sort(cmp));
|
||||
expect((await sm.autocomplete('sw', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([
|
||||
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
|
||||
}));
|
||||
|
||||
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)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
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();
|
||||
|
||||
expect(Utils.clone(await sm.instantSearch('sw'))).to.deep.equalInAnyOrder(Utils.clone({
|
||||
searchText: 'sw',
|
||||
directories: [],
|
||||
media: [p, p2, v],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
let query = <TextSearch>{
|
||||
text: 'xyz',
|
||||
type: SearchQueryTypes.keyword
|
||||
};
|
||||
expect(await sm.getRandomPhoto(query)).to.not.exist;
|
||||
|
||||
expect(Utils.clone(await sm.instantSearch('Tatooine'))).to.deep.equalInAnyOrder(Utils.clone({
|
||||
searchText: 'Tatooine',
|
||||
directories: [],
|
||||
media: [p],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
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
|
||||
}));
|
||||
query = <TextSearch>{
|
||||
text: 'wookiees',
|
||||
matchType: TextSearchQueryTypes.exact_match,
|
||||
type: SearchQueryTypes.keyword
|
||||
};
|
||||
expect(Utils.clone(await sm.getRandomPhoto(query))).to.deep.equalInAnyOrder(searchifyMedia(p_faceLess));
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user