mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
improving advanced search
This commit is contained in:
parent
9a923aa8ab
commit
e643ee2ed1
@ -15,17 +15,19 @@ import {
|
||||
DateSearch,
|
||||
DistanceSearch,
|
||||
OrientationSearch,
|
||||
OrientationSearchTypes,
|
||||
ORSearchQuery,
|
||||
RatingSearch,
|
||||
ResolutionSearch,
|
||||
SearchListQuery,
|
||||
SearchQueryDTO,
|
||||
SearchQueryTypes,
|
||||
SomeOfSearchQuery,
|
||||
TextSearch,
|
||||
TextSearchQueryTypes
|
||||
} from '../../../../common/entities/SearchQueryDTO';
|
||||
import {GalleryManager} from './GalleryManager';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
import {Utils} from '../../../../common/Utils';
|
||||
|
||||
export class SearchManager implements ISearchManager {
|
||||
|
||||
@ -145,6 +147,7 @@ export class SearchManager implements ISearchManager {
|
||||
|
||||
async aSearch(query: SearchQueryDTO) {
|
||||
query = this.flattenSameOfQueries(query);
|
||||
console.log(JSON.stringify(query, null, 4));
|
||||
query = await this.getGPSData(query);
|
||||
const connection = await SQLConnection.getConnection();
|
||||
|
||||
@ -367,10 +370,17 @@ export class SearchManager implements ISearchManager {
|
||||
textParam['minLat' + paramCounter.value] = minLat;
|
||||
textParam['maxLon' + paramCounter.value] = maxLon;
|
||||
textParam['minLon' + paramCounter.value] = minLon;
|
||||
if (!(<DistanceSearch>query).negate) {
|
||||
q.where(`media.metadata.positionData.GPSData.latitude < :maxLat${paramCounter.value}`, textParam);
|
||||
q.andWhere(`media.metadata.positionData.GPSData.latitude > :minLat${paramCounter.value}`, textParam);
|
||||
q.andWhere(`media.metadata.positionData.GPSData.longitude < :maxLon${paramCounter.value}`, textParam);
|
||||
q.andWhere(`media.metadata.positionData.GPSData.longitude > :minLon${paramCounter.value}`, textParam);
|
||||
} else {
|
||||
q.where(`media.metadata.positionData.GPSData.latitude > :maxLat${paramCounter.value}`, textParam);
|
||||
q.orWhere(`media.metadata.positionData.GPSData.latitude < :minLat${paramCounter.value}`, textParam);
|
||||
q.orWhere(`media.metadata.positionData.GPSData.longitude > :maxLon${paramCounter.value}`, textParam);
|
||||
q.orWhere(`media.metadata.positionData.GPSData.longitude < :minLon${paramCounter.value}`, textParam);
|
||||
}
|
||||
return q;
|
||||
});
|
||||
|
||||
@ -379,17 +389,22 @@ export class SearchManager implements ISearchManager {
|
||||
if (typeof (<DateSearch>query).before === 'undefined' && typeof (<DateSearch>query).after === 'undefined') {
|
||||
throw new Error('Invalid search query: Date Query should contain before or after value');
|
||||
}
|
||||
if (typeof (<DateSearch>query).before !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['before' + paramCounter.value] = (<DateSearch>query).before;
|
||||
q.where(`media.metadata.creationDate <= :before${paramCounter.value}`, textParam);
|
||||
}
|
||||
const whereFN = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||
const relationRev = (<TextSearch>query).negate ? '>' : '<=';
|
||||
|
||||
if (typeof (<DateSearch>query).after !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['after' + paramCounter.value] = (<DateSearch>query).after;
|
||||
q.andWhere(`media.metadata.creationDate >= :after${paramCounter.value}`, textParam);
|
||||
q.where(`media.metadata.creationDate ${relation} :after${paramCounter.value}`, textParam);
|
||||
}
|
||||
|
||||
if (typeof (<DateSearch>query).before !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['before' + paramCounter.value] = (<DateSearch>query).before;
|
||||
q[whereFN](`media.metadata.creationDate ${relationRev} :before${paramCounter.value}`, textParam);
|
||||
}
|
||||
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
});
|
||||
@ -399,16 +414,20 @@ export class SearchManager implements ISearchManager {
|
||||
if (typeof (<RatingSearch>query).min === 'undefined' && typeof (<RatingSearch>query).max === 'undefined') {
|
||||
throw new Error('Invalid search query: Rating Query should contain min or max value');
|
||||
}
|
||||
|
||||
const whereFN = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||
const relationRev = (<TextSearch>query).negate ? '>' : '<=';
|
||||
if (typeof (<RatingSearch>query).min !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['min' + paramCounter.value] = (<RatingSearch>query).min;
|
||||
q.where(`media.metadata.rating >= :min${paramCounter.value}`, textParam);
|
||||
q.where(`media.metadata.rating ${relation} :min${paramCounter.value}`, textParam);
|
||||
}
|
||||
|
||||
if (typeof (<RatingSearch>query).max !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['max' + paramCounter.value] = (<RatingSearch>query).max;
|
||||
q.andWhere(`media.metadata.rating <= :max${paramCounter.value}`, textParam);
|
||||
q[whereFN](`media.metadata.rating ${relationRev} :max${paramCounter.value}`, textParam);
|
||||
}
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
@ -419,16 +438,20 @@ export class SearchManager implements ISearchManager {
|
||||
if (typeof (<ResolutionSearch>query).min === 'undefined' && typeof (<ResolutionSearch>query).max === 'undefined') {
|
||||
throw new Error('Invalid search query: Rating Query should contain min or max value');
|
||||
}
|
||||
|
||||
const whereFN = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||
const relationRev = (<TextSearch>query).negate ? '>' : '<=';
|
||||
if (typeof (<ResolutionSearch>query).min !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['min' + paramCounter.value] = (<RatingSearch>query).min * 1000 * 1000;
|
||||
q.where(`media.metadata.size.width * media.metadata.size.height >= :min${paramCounter.value}`, textParam);
|
||||
q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :min${paramCounter.value}`, textParam);
|
||||
}
|
||||
|
||||
if (typeof (<ResolutionSearch>query).max !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['max' + paramCounter.value] = (<RatingSearch>query).max * 1000 * 1000;
|
||||
q.andWhere(`media.metadata.size.width * media.metadata.size.height <= :max${paramCounter.value}`, textParam);
|
||||
q[whereFN](`media.metadata.size.width * media.metadata.size.height ${relationRev} :max${paramCounter.value}`, textParam);
|
||||
}
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
@ -436,11 +459,10 @@ export class SearchManager implements ISearchManager {
|
||||
|
||||
case SearchQueryTypes.orientation:
|
||||
return new Brackets(q => {
|
||||
if ((<OrientationSearch>query).orientation === OrientationSearchTypes.landscape) {
|
||||
if ((<OrientationSearch>query).landscape) {
|
||||
q.where('media.metadata.size.width >= media.metadata.size.height');
|
||||
}
|
||||
if ((<OrientationSearch>query).orientation === OrientationSearchTypes.portrait) {
|
||||
q.andWhere('media.metadata.size.width <= media.metadata.size.height');
|
||||
} else {
|
||||
q.where('media.metadata.size.width <= media.metadata.size.height');
|
||||
}
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
@ -458,6 +480,11 @@ export class SearchManager implements ISearchManager {
|
||||
return (<TextSearch>query).matchType === TextSearchQueryTypes.exact_match ? str : `%${str}%`;
|
||||
};
|
||||
|
||||
const LIKE = (<TextSearch>query).negate ? 'NOT LIKE' : 'LIKE';
|
||||
// if the expression is negated, we use AND instead of OR as nowhere should that match
|
||||
const whereFN = (<TextSearch>query).negate ? 'andWhere' : 'orWhere';
|
||||
const whereFNRev = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||
|
||||
const textParam: any = {};
|
||||
paramCounter.value++;
|
||||
textParam['text' + paramCounter.value] = createMatchString((<TextSearch>query).text);
|
||||
@ -468,17 +495,17 @@ export class SearchManager implements ISearchManager {
|
||||
|
||||
|
||||
textParam['fullPath' + paramCounter.value] = createMatchString(dirPathStr);
|
||||
q.orWhere(`directory.path LIKE :fullPath${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
q[whereFN](`directory.path ${LIKE} :fullPath${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
|
||||
const directoryPath = GalleryManager.parseRelativeDirePath(dirPathStr);
|
||||
q.orWhere(new Brackets(dq => {
|
||||
q[whereFN](new Brackets(dq => {
|
||||
textParam['dirName' + paramCounter.value] = createMatchString(directoryPath.name);
|
||||
dq.where(`directory.name LIKE :dirName${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
dq[whereFNRev](`directory.name ${LIKE} :dirName${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
if (dirPathStr.includes('/')) {
|
||||
textParam['parentName' + paramCounter.value] = createMatchString(directoryPath.parent);
|
||||
dq.andWhere(`directory.path LIKE :parentName${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
dq[whereFNRev](`directory.path ${LIKE} :parentName${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
}
|
||||
return dq;
|
||||
@ -486,29 +513,39 @@ export class SearchManager implements ISearchManager {
|
||||
}
|
||||
|
||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.file_name) {
|
||||
q.orWhere(`media.name LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
q[whereFN](`media.name ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
}
|
||||
|
||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.caption) {
|
||||
q.orWhere(`media.metadata.caption LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
q[whereFN](`media.metadata.caption ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
}
|
||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.person) {
|
||||
q.orWhere(`person.name LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
if (!(<TextSearch>query).negate) {
|
||||
q[whereFN](`person.name ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
} else {
|
||||
// because of the Left JOIN on the faces, we also need to check for NULL
|
||||
q[whereFN](new Brackets(dq => {
|
||||
dq.where(`person.name ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
dq.orWhere(`person.name is NULL`);
|
||||
return dq;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.position) {
|
||||
q.orWhere(`media.metadata.positionData.country LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
q[whereFN](`media.metadata.positionData.country ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam)
|
||||
.orWhere(`media.metadata.positionData.state LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
[whereFN](`media.metadata.positionData.state ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam)
|
||||
.orWhere(`media.metadata.positionData.city LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
[whereFN](`media.metadata.positionData.city ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
}
|
||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.keyword) {
|
||||
q.orWhere(`media.metadata.keywords LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
q[whereFN](`media.metadata.keywords ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||
textParam);
|
||||
}
|
||||
return q;
|
||||
@ -516,6 +553,41 @@ export class SearchManager implements ISearchManager {
|
||||
}
|
||||
|
||||
private flattenSameOfQueries(query: SearchQueryDTO): SearchQueryDTO {
|
||||
switch (query.type) {
|
||||
case SearchQueryTypes.AND:
|
||||
case SearchQueryTypes.OR:
|
||||
return <SearchListQuery>{
|
||||
type: query.type,
|
||||
list: (<SearchListQuery>query).list.map(q => this.flattenSameOfQueries(q))
|
||||
};
|
||||
case SearchQueryTypes.SOME_OF:
|
||||
const someOfQ = <SomeOfSearchQuery>query;
|
||||
someOfQ.min = someOfQ.min || 1;
|
||||
|
||||
if (someOfQ.min === 1) {
|
||||
return this.flattenSameOfQueries(<ORSearchQuery>{
|
||||
type: SearchQueryTypes.OR,
|
||||
list: (<SearchListQuery>someOfQ).list
|
||||
});
|
||||
}
|
||||
|
||||
if (someOfQ.min === (<SearchListQuery>query).list.length) {
|
||||
return this.flattenSameOfQueries(<ANDSearchQuery>{
|
||||
type: SearchQueryTypes.AND,
|
||||
list: (<SearchListQuery>someOfQ).list
|
||||
});
|
||||
}
|
||||
|
||||
const combinations: SearchQueryDTO[][] = Utils.getAnyX(someOfQ.min, (<SearchListQuery>query).list);
|
||||
|
||||
|
||||
return this.flattenSameOfQueries(<ORSearchQuery>{
|
||||
type: SearchQueryTypes.OR,
|
||||
list: combinations.map(c => <ANDSearchQuery>{
|
||||
type: SearchQueryTypes.AND, list: c
|
||||
})
|
||||
});
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
|
@ -240,4 +240,24 @@ export class Utils {
|
||||
(value <= 3.402823466 * E && value >= 1.175494351 * nE));
|
||||
}
|
||||
|
||||
public static getAnyX(num: number, arr: any[], start = 0): any[][] {
|
||||
if (num <= 0 || num > arr.length || start >= arr.length) {
|
||||
return [];
|
||||
}
|
||||
if (num <= 1) {
|
||||
return arr.slice(start).map(e => [e]);
|
||||
}
|
||||
if (num === arr.length - start) {
|
||||
return [arr.slice(start)];
|
||||
}
|
||||
const ret: any[][] = [];
|
||||
for (let i = start; i < arr.length; ++i) {
|
||||
Utils.getAnyX(num - 1, arr, i + 1).forEach(a => {
|
||||
a.push(arr[i]);
|
||||
ret.push(a);
|
||||
});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,30 +24,47 @@ export enum TextSearchQueryTypes {
|
||||
exact_match = 1, like = 2
|
||||
}
|
||||
|
||||
export enum OrientationSearchTypes {
|
||||
portrait = 1, landscape = 2
|
||||
|
||||
export namespace SearchQueryDTO {
|
||||
export const negate = (query: SearchQueryDTO): SearchQueryDTO => {
|
||||
switch (query.type) {
|
||||
case SearchQueryTypes.AND:
|
||||
query.type = SearchQueryTypes.OR;
|
||||
(<SearchListQuery>query).list = (<SearchListQuery>query).list.map(q => SearchQueryDTO.negate(q));
|
||||
return query;
|
||||
case SearchQueryTypes.OR:
|
||||
query.type = SearchQueryTypes.AND;
|
||||
(<SearchListQuery>query).list = (<SearchListQuery>query).list.map(q => SearchQueryDTO.negate(q));
|
||||
return query;
|
||||
|
||||
case SearchQueryTypes.orientation:
|
||||
(<OrientationSearch>query).landscape = !(<OrientationSearch>query).landscape;
|
||||
return query;
|
||||
|
||||
case SearchQueryTypes.date:
|
||||
case SearchQueryTypes.rating:
|
||||
case SearchQueryTypes.resolution:
|
||||
case SearchQueryTypes.distance:
|
||||
case SearchQueryTypes.any_text:
|
||||
case SearchQueryTypes.person:
|
||||
case SearchQueryTypes.position:
|
||||
case SearchQueryTypes.keyword:
|
||||
case SearchQueryTypes.caption:
|
||||
case SearchQueryTypes.file_name:
|
||||
case SearchQueryTypes.directory:
|
||||
(<NegatableSearchQuery>query).negate = !(<NegatableSearchQuery>query).negate;
|
||||
return query;
|
||||
|
||||
case SearchQueryTypes.SOME_OF:
|
||||
throw new Error('Some of not supported');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface SearchQueryDTO {
|
||||
type: SearchQueryTypes;
|
||||
}
|
||||
|
||||
export interface ANDSearchQuery extends SearchQueryDTO {
|
||||
type: SearchQueryTypes.AND;
|
||||
list: SearchQueryDTO[];
|
||||
}
|
||||
|
||||
export interface ORSearchQuery extends SearchQueryDTO {
|
||||
type: SearchQueryTypes.OR;
|
||||
list: SearchQueryDTO[];
|
||||
}
|
||||
|
||||
export interface SomeOfSearchQuery extends SearchQueryDTO, RangeSearchQuery {
|
||||
type: SearchQueryTypes.SOME_OF;
|
||||
list: NegatableSearchQuery[];
|
||||
min?: number; // at least this amount of items
|
||||
max?: number; // maximum this amount of items
|
||||
}
|
||||
|
||||
export interface NegatableSearchQuery extends SearchQueryDTO {
|
||||
negate?: boolean; // if true negates the expression
|
||||
@ -58,6 +75,26 @@ export interface RangeSearchQuery extends SearchQueryDTO {
|
||||
max?: number;
|
||||
}
|
||||
|
||||
export interface SearchListQuery extends SearchQueryDTO {
|
||||
list: SearchQueryDTO[];
|
||||
}
|
||||
|
||||
|
||||
export interface ANDSearchQuery extends SearchQueryDTO, SearchListQuery {
|
||||
type: SearchQueryTypes.AND;
|
||||
list: SearchQueryDTO[];
|
||||
}
|
||||
|
||||
export interface ORSearchQuery extends SearchQueryDTO, SearchListQuery {
|
||||
type: SearchQueryTypes.OR;
|
||||
list: SearchQueryDTO[];
|
||||
}
|
||||
|
||||
export interface SomeOfSearchQuery extends SearchQueryDTO, SearchListQuery {
|
||||
type: SearchQueryTypes.SOME_OF;
|
||||
list: NegatableSearchQuery[];
|
||||
min?: number; // at least this amount of items
|
||||
}
|
||||
|
||||
export interface TextSearch extends NegatableSearchQuery {
|
||||
type: SearchQueryTypes.any_text |
|
||||
@ -87,20 +124,20 @@ export interface DateSearch extends NegatableSearchQuery {
|
||||
before?: number;
|
||||
}
|
||||
|
||||
export interface RatingSearch extends NegatableSearchQuery, RangeSearchQuery {
|
||||
export interface RatingSearch extends RangeSearchQuery, NegatableSearchQuery {
|
||||
type: SearchQueryTypes.rating;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
export interface ResolutionSearch extends NegatableSearchQuery, RangeSearchQuery {
|
||||
export interface ResolutionSearch extends RangeSearchQuery, NegatableSearchQuery {
|
||||
type: SearchQueryTypes.resolution;
|
||||
min?: number; // in megapixels
|
||||
max?: number; // in megapixels
|
||||
}
|
||||
|
||||
export interface OrientationSearch extends NegatableSearchQuery {
|
||||
export interface OrientationSearch {
|
||||
type: SearchQueryTypes.orientation;
|
||||
orientation: OrientationSearchTypes;
|
||||
landscape: boolean;
|
||||
}
|
||||
|
||||
|
@ -9,12 +9,12 @@ import {
|
||||
DateSearch,
|
||||
DistanceSearch,
|
||||
OrientationSearch,
|
||||
OrientationSearchTypes,
|
||||
ORSearchQuery,
|
||||
RatingSearch,
|
||||
ResolutionSearch,
|
||||
SearchQueryDTO,
|
||||
SearchQueryTypes,
|
||||
SomeOfSearchQuery,
|
||||
TextSearch,
|
||||
TextSearchQueryTypes
|
||||
} from '../../../../../src/common/entities/SearchQueryDTO';
|
||||
@ -98,10 +98,11 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
ObjectManagers.getInstance().IndexingManager.indexDirectory = () => Promise.resolve(null);
|
||||
|
||||
|
||||
const im = new IndexingManagerTest();
|
||||
await im.saveToDB(directory);
|
||||
await im.saveToDB(subDir);
|
||||
await im.saveToDB(subDir2);
|
||||
// await im.saveToDB(subDir);
|
||||
// await im.saveToDB(subDir2);
|
||||
|
||||
if (ObjectManagers.getInstance().IndexingManager &&
|
||||
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) {
|
||||
@ -389,7 +390,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p, p2],
|
||||
media: [p, p2, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
@ -467,7 +468,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p, p2],
|
||||
media: [p, p2, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
@ -487,7 +488,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p, p2],
|
||||
media: [p, p2, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
@ -515,6 +516,84 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should minimum of', async () => {
|
||||
const sm = new SearchManager();
|
||||
|
||||
let query: SomeOfSearchQuery = <SomeOfSearchQuery>{
|
||||
type: SearchQueryTypes.SOME_OF,
|
||||
list: [<TextSearch>{text: 'jpg', type: SearchQueryTypes.file_name},
|
||||
<TextSearch>{text: 'mp4', type: SearchQueryTypes.file_name}]
|
||||
};
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p, p2, p_faceLess, p4, v],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <SomeOfSearchQuery>{
|
||||
type: SearchQueryTypes.SOME_OF,
|
||||
list: [<TextSearch>{text: 'R2', type: SearchQueryTypes.person},
|
||||
<TextSearch>{text: 'Anakin', type: SearchQueryTypes.person},
|
||||
<TextSearch>{text: 'Luke', type: SearchQueryTypes.person}]
|
||||
};
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p, p2, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
|
||||
query.min = 2;
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p, p2, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query.min = 3;
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <SomeOfSearchQuery>{
|
||||
type: SearchQueryTypes.SOME_OF,
|
||||
min: 3,
|
||||
list: [<TextSearch>{text: 'sw', type: SearchQueryTypes.file_name},
|
||||
<TextSearch>{text: 'R2', type: SearchQueryTypes.person},
|
||||
<TextSearch>{text: 'Kamino', type: SearchQueryTypes.position},
|
||||
<TextSearch>{text: 'Han', type: SearchQueryTypes.person}]
|
||||
};
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p2],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('should search text', async () => {
|
||||
it('as any', async () => {
|
||||
const sm = new SearchManager();
|
||||
@ -529,6 +608,17 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<TextSearch>{text: 'sw', negate: true, type: SearchQueryTypes.any_text})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<TextSearch>{text: 'Boba', type: SearchQueryTypes.any_text})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
@ -539,6 +629,28 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<TextSearch>{text: 'Boba', negate: true, type: SearchQueryTypes.any_text})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p2, p_faceLess, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
// all should have faces
|
||||
const sRet = await sm.aSearch(<TextSearch>{text: 'Boba', negate: true, type: SearchQueryTypes.any_text});
|
||||
for (let i = 0; i < sRet.media.length; ++i) {
|
||||
if (sRet.media[i].id === p_faceLess.id) {
|
||||
continue;
|
||||
}
|
||||
console.log(sRet.media[i]);
|
||||
expect((<PhotoDTO>sRet.media[i]).metadata.faces).to.be.not.an('undefined');
|
||||
expect((<PhotoDTO>sRet.media[i]).metadata.faces).to.be.lengthOf.above(1);
|
||||
}
|
||||
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<TextSearch>{
|
||||
text: 'Boba',
|
||||
type: SearchQueryTypes.any_text,
|
||||
@ -815,6 +927,21 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<DateSearch>{
|
||||
before: p.metadata.creationDate,
|
||||
after: p.metadata.creationDate,
|
||||
negate: true,
|
||||
type: SearchQueryTypes.date
|
||||
})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p2, p_faceLess, p4, v],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<DateSearch>{
|
||||
before: p.metadata.creationDate + 1000000000,
|
||||
after: 0, type: SearchQueryTypes.date
|
||||
@ -854,6 +981,16 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<RatingSearch>{min: 0, max: 5, negate: true, type: SearchQueryTypes.rating})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<RatingSearch>{min: 2, max: 2, type: SearchQueryTypes.rating})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
@ -864,6 +1001,15 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<RatingSearch>{min: 2, max: 2, negate: true, type: SearchQueryTypes.rating})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p, p_faceLess],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@ -900,6 +1046,16 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<ResolutionSearch>{min: 2, max: 3, negate: true, type: SearchQueryTypes.resolution})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p, v, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<ResolutionSearch>{min: 3, type: SearchQueryTypes.resolution})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
@ -917,7 +1073,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
const sm = new SearchManager();
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<OrientationSearch>{
|
||||
orientation: OrientationSearchTypes.portrait,
|
||||
landscape: false,
|
||||
type: SearchQueryTypes.orientation
|
||||
})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
@ -930,7 +1086,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<OrientationSearch>{
|
||||
orientation: OrientationSearchTypes.landscape,
|
||||
landscape: true,
|
||||
type: SearchQueryTypes.orientation
|
||||
})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
@ -984,6 +1140,22 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<DistanceSearch>{
|
||||
from: {GPSData: {latitude: 0, longitude: 0}},
|
||||
distance: 112 * 10, // number of km per degree = ~111km
|
||||
negate: true,
|
||||
type: SearchQueryTypes.distance
|
||||
})))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchText: null,
|
||||
searchType: null,
|
||||
directories: [],
|
||||
media: [p_faceLess, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
expect(Utils.clone(await sm.aSearch(<DistanceSearch>{
|
||||
from: {GPSData: {latitude: 10, longitude: 10}},
|
||||
distance: 1,
|
||||
|
@ -148,6 +148,9 @@ export class TestHelper {
|
||||
}, <FaceRegion>{
|
||||
box: {height: 10, width: 10, left: 105, top: 105},
|
||||
name: 'Arvíztűrő Tükörfúrógép'
|
||||
}, <FaceRegion>{
|
||||
box: {height: 10, width: 10, left: 201, top: 201},
|
||||
name: 'R2-D2'
|
||||
}] as any[];
|
||||
return p;
|
||||
}
|
||||
@ -178,6 +181,9 @@ export class TestHelper {
|
||||
}, <FaceRegion>{
|
||||
box: {height: 10, width: 10, left: 101, top: 101},
|
||||
name: 'Obivan Kenobi'
|
||||
}, <FaceRegion>{
|
||||
box: {height: 10, width: 10, left: 201, top: 201},
|
||||
name: 'R2-D2'
|
||||
}] as any[];
|
||||
return p;
|
||||
}
|
||||
@ -224,6 +230,7 @@ export class TestHelper {
|
||||
p.metadata.creationDate = Date.now() - 4000;
|
||||
p.metadata.size.height = 3000;
|
||||
p.metadata.size.width = 2000;
|
||||
|
||||
p.metadata.faces = [<FaceRegion>{
|
||||
box: {height: 10, width: 10, left: 10, top: 10},
|
||||
name: 'Kylo Ren'
|
||||
@ -233,7 +240,11 @@ export class TestHelper {
|
||||
}, <FaceRegion>{
|
||||
box: {height: 10, width: 10, left: 101, top: 101},
|
||||
name: 'Obivan Kenobi'
|
||||
}, <FaceRegion>{
|
||||
box: {height: 10, width: 10, left: 201, top: 201},
|
||||
name: 'R2-D2'
|
||||
}] as any[];
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user