1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2024-11-03 21:04:03 +08:00
pigallery2/backend/model/sql/SearchManager.ts

280 lines
11 KiB
TypeScript
Raw Normal View History

2018-03-31 03:30:30 +08:00
import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem';
import {ISearchManager} from '../interfaces/ISearchManager';
import {SearchResultDTO} from '../../../common/entities/SearchResultDTO';
import {SQLConnection} from './SQLConnection';
import {PhotoEntity} from './enitites/PhotoEntity';
import {DirectoryEntity} from './enitites/DirectoryEntity';
2018-11-18 05:46:34 +08:00
import {MediaEntity} from './enitites/MediaEntity';
import {VideoEntity} from './enitites/VideoEntity';
2019-01-14 03:18:18 +08:00
import {PersonEntry} from './enitites/PersonEntry';
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
2019-01-27 07:03:40 +08:00
import {SelectQueryBuilder} from 'typeorm';
import {Config} from '../../../common/config/private/Config';
2016-12-28 18:50:05 +08:00
export class SearchManager implements ISearchManager {
2018-01-31 09:01:16 +08:00
private static autoCompleteItemsUnique(array: Array<AutoCompleteItem>): Array<AutoCompleteItem> {
const a = array.concat();
2018-01-31 09:01:16 +08:00
for (let i = 0; i < a.length; ++i) {
for (let j = i + 1; j < a.length; ++j) {
if (a[i].equals(a[j])) {
2018-01-31 09:01:16 +08:00
a.splice(j--, 1);
}
2018-01-31 09:01:16 +08:00
}
}
return a;
}
2019-01-27 07:03:40 +08:00
async autocomplete(text: string): Promise<AutoCompleteItem[]> {
2017-07-08 06:18:24 +08:00
const connection = await SQLConnection.getConnection();
2017-07-08 06:18:24 +08:00
2018-11-18 05:46:34 +08:00
let result: AutoCompleteItem[] = [];
const photoRepository = connection.getRepository(PhotoEntity);
const videoRepository = connection.getRepository(VideoEntity);
2019-01-14 03:18:18 +08:00
const personRepository = connection.getRepository(PersonEntry);
const directoryRepository = connection.getRepository(DirectoryEntity);
2017-07-08 06:18:24 +08:00
(await photoRepository
2018-11-18 05:46:34 +08:00
.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)
2017-07-08 06:18:24 +08:00
.getRawMany())
2018-11-18 05:46:34 +08:00
.map(r => <Array<string>>(<string>r.metadataKeywords).split(','))
2017-07-08 06:18:24 +08:00
.forEach(keywords => {
result = result.concat(this.encapsulateAutoComplete(keywords
.filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchTypes.keyword));
2017-07-08 06:18:24 +08:00
});
2019-01-14 03:18:18 +08:00
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')
2019-01-14 03:18:18 +08:00
.getRawMany())
.map(r => r.name), SearchTypes.person));
2017-07-08 06:18:24 +08:00
(await photoRepository
2018-11-18 05:46:34 +08:00
.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 + '%'})
2018-11-18 05:46:34 +08:00
.groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city')
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
2017-07-08 06:18:24 +08:00
.getRawMany())
.filter(pm => !!pm)
2018-03-31 03:30:30 +08:00
.map(pm => <Array<string>>[pm.city || '', pm.country || '', pm.state || ''])
2017-07-08 06:18:24 +08:00
.forEach(positions => {
result = result.concat(this.encapsulateAutoComplete(positions
.filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchTypes.position));
2017-07-08 06:18:24 +08:00
});
result = result.concat(this.encapsulateAutoComplete((await photoRepository
2018-11-05 02:28:32 +08:00
.createQueryBuilder('media')
.select('DISTINCT(media.name)')
.where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
2017-07-08 06:18:24 +08:00
.getRawMany())
.map(r => r.name), SearchTypes.photo));
2018-12-06 02:15:24 +08:00
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)
2018-12-06 02:15:24 +08:00
.getRawMany())
.map(r => r.caption), SearchTypes.photo));
result = result.concat(this.encapsulateAutoComplete((await videoRepository
.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), SearchTypes.video));
2017-07-08 06:18:24 +08:00
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)
2017-07-08 06:18:24 +08:00
.getRawMany())
.map(r => r.name), SearchTypes.directory));
2018-01-31 09:01:16 +08:00
return SearchManager.autoCompleteItemsUnique(result);
2017-07-08 04:54:18 +08:00
}
2018-01-31 09:01:16 +08:00
async search(text: string, searchType: SearchTypes): Promise<SearchResultDTO> {
const connection = await SQLConnection.getConnection();
2017-07-08 04:54:18 +08:00
2018-11-26 07:26:29 +08:00
const result: SearchResultDTO = {
2017-07-08 06:18:24 +08:00
searchText: text,
searchType: searchType,
directories: [],
2018-11-05 02:28:32 +08:00
media: [],
2018-11-26 07:26:29 +08:00
metaFile: [],
2017-07-26 03:36:28 +08:00
resultOverflow: false
2017-07-08 06:18:24 +08:00
};
2017-07-08 04:54:18 +08:00
2019-01-27 07:03:40 +08:00
let usedEntity = MediaEntity;
if (searchType === SearchTypes.photo) {
2019-01-27 07:03:40 +08:00
usedEntity = PhotoEntity;
} else if (searchType === SearchTypes.video) {
2019-01-27 07:03:40 +08:00
usedEntity = VideoEntity;
}
2019-01-27 07:03:40 +08:00
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 + '%'});
2019-01-27 07:03:40 +08:00
}
if (!searchType || searchType === SearchTypes.photo || searchType === SearchTypes.video) {
subQuery.orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
2019-01-27 07:03:40 +08:00
}
if (!searchType || searchType === SearchTypes.photo) {
subQuery.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
2019-01-27 07:03:40 +08:00
}
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 + '%'});
2019-01-27 07:03:40 +08:00
}
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 + '%'});
2019-01-27 07:03:40 +08:00
}
if (!searchType || searchType === SearchTypes.keyword) {
subQuery.orWhere('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
2019-01-27 07:03:40 +08:00
}
return subQuery;
},
'innerMedia',
'media.id=innerMedia.id')
2019-01-14 03:18:18 +08:00
.leftJoinAndSelect('media.directory', 'directory')
2019-01-27 07:03:40 +08:00
.leftJoinAndSelect('media.metadata.faces', 'faces')
.leftJoinAndSelect('faces.person', 'person');
2017-07-08 04:54:18 +08:00
2019-01-27 07:03:40 +08:00
result.media = await this.loadMediaWithFaces(query);
2017-07-08 06:18:24 +08:00
2018-11-05 02:28:32 +08:00
if (result.media.length > 2000) {
2018-01-31 09:01:16 +08:00
result.resultOverflow = true;
2017-07-08 06:18:24 +08:00
}
2017-07-08 04:54:18 +08:00
2017-07-08 06:18:24 +08:00
result.directories = await connection
.getRepository(DirectoryEntity)
2018-03-31 03:30:30 +08:00
.createQueryBuilder('dir')
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
2017-10-20 00:08:07 +08:00
.limit(201)
2017-07-08 06:18:24 +08:00
.getMany();
2017-07-08 04:54:18 +08:00
2017-07-26 03:36:28 +08:00
if (result.directories.length > 200) {
result.resultOverflow = true;
}
2017-07-08 04:54:18 +08:00
2017-07-08 06:18:24 +08:00
return result;
2017-07-08 04:54:18 +08:00
}
2018-01-31 09:01:16 +08:00
async instantSearch(text: string): Promise<SearchResultDTO> {
const connection = await SQLConnection.getConnection();
2017-07-08 06:18:24 +08:00
2018-11-26 07:26:29 +08:00
const result: SearchResultDTO = {
2017-07-08 06:18:24 +08:00
searchText: text,
// searchType:undefined, not adding this
2017-07-08 06:18:24 +08:00
directories: [],
2018-11-05 02:28:32 +08:00
media: [],
2018-11-26 07:26:29 +08:00
metaFile: [],
2017-07-26 03:36:28 +08:00
resultOverflow: false
2017-07-08 06:18:24 +08:00
};
2019-01-27 07:03:40 +08:00
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 + '%'})
2019-01-27 07:03:40 +08:00
,
'innerMedia',
'media.id=innerMedia.id')
.leftJoinAndSelect('media.directory', 'directory')
.leftJoinAndSelect('media.metadata.faces', 'faces')
.leftJoinAndSelect('faces.person', 'person');
2019-01-14 03:18:18 +08:00
2017-07-08 06:18:24 +08:00
2019-01-27 07:03:40 +08:00
result.media = await this.loadMediaWithFaces(query);
2017-07-08 06:18:24 +08:00
2018-01-31 09:01:16 +08:00
result.directories = await connection
2017-07-08 06:18:24 +08:00
.getRepository(DirectoryEntity)
2018-03-31 03:30:30 +08:00
.createQueryBuilder('dir')
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
2017-10-20 00:08:07 +08:00
.limit(10)
2017-07-08 06:18:24 +08:00
.getMany();
2017-07-08 04:54:18 +08:00
2017-07-08 06:18:24 +08:00
return result;
2017-07-08 04:54:18 +08:00
}
private encapsulateAutoComplete(values: string[], type: SearchTypes): Array<AutoCompleteItem> {
2018-11-29 06:49:33 +08:00
const res: AutoCompleteItem[] = [];
2017-07-08 04:54:18 +08:00
values.forEach((value) => {
res.push(new AutoCompleteItem(value, type));
});
return res;
}
2019-01-27 07:03:40 +08:00
private async loadMediaWithFaces(query: SelectQueryBuilder<MediaEntity>) {
const rawAndEntities = await query.orderBy('media.id').getRawAndEntities();
const media: MediaEntity[] = rawAndEntities.entities;
let rawIndex = 0;
for (let i = 0; i < media.length; i++) {
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;
}
2017-07-08 04:54:18 +08:00
}