1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

Fixing date boundary issue. fixes #660

This commit is contained in:
Patrik J. Braun 2023-10-11 00:04:53 +02:00
parent e1ffeb0173
commit 41796e8d72
4 changed files with 771 additions and 497 deletions

View File

@ -807,43 +807,70 @@ export class SearchManager {
const textParam: { [key: string]: unknown } = {};
textParam['diff' + queryId] = tq.daysLength;
const addWhere = (duration: string, crossesDateBoundary: boolean) => {
const relationEql = tq.negate ? '!=' : '=';
// reminder: !(a&&b) = (!a || !b), that is what happening here if negate is true
const relationTop = tq.negate ? '>' : '<=';
const relationBottom = tq.negate ? '<=' : '>';
// this is an XoR. during date boundary crossing we swap boolean logic again
const whereFN = !!tq.negate !== crossesDateBoundary ? 'orWhere' : 'andWhere';
const addWhere = (duration: string) => {
if (Config.Database.type === DatabaseType.sqlite) {
if (tq.daysLength == 0) {
q.where(
`CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationEql} CAST(strftime('${duration}','now') AS INTEGER)`
);
} else {
q.where(
`CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('${duration}','now') AS INTEGER)`
).andWhere(`CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('${duration}','now','-:diff${queryId} day') AS INTEGER)`,
)[whereFN](`CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('${duration}','now','-:diff${queryId} day') AS INTEGER)`,
textParam);
}
} else {
if (tq.daysLength == 0) {
q.where(
`CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationEql} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)`
);
} else {
q.where(
`CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationTop} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)`
).andWhere(`CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationBottom} CAST(DATE_FORMAT((DATE_ADD(curdate(), INTERVAL -:diff${queryId} DAY)),'${duration}') AS SIGNED)`,
)[whereFN](`CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationBottom} CAST(DATE_FORMAT((DATE_ADD(curdate(), INTERVAL -:diff${queryId} DAY)),'${duration}') AS SIGNED)`,
textParam);
}
}
};
switch (tq.frequency) {
case DatePatternFrequency.every_year:
if (tq.daysLength >= 365) {
if (tq.daysLength >= 365) { // trivial result includes all photos
if (tq.negate) {
q.andWhere('FALSE');
}
return q;
}
addWhere('%j');
const d = new Date();
const dayOfYear = Math.floor((d.getTime() - new Date(d.getFullYear(), 0, 0).getTime()) / 1000 / 60 / 60 / 24);
addWhere('%j', dayOfYear - tq.daysLength < 0);
break;
case DatePatternFrequency.every_month:
if (tq.daysLength >= 31) {
if (tq.daysLength >= 31) { // trivial result includes all photos
if (tq.negate) {
q.andWhere('FALSE');
}
return q;
}
addWhere('%d');
addWhere('%d', (new Date()).getUTCDate() - tq.daysLength < 0);
break;
case DatePatternFrequency.every_week:
if (tq.daysLength >= 7) {
if (tq.daysLength >= 7) { // trivial result includes all photos
if (tq.negate) {
q.andWhere('FALSE');
}
return q;
}
addWhere('%w');
addWhere('%w', (new Date()).getUTCDay() - tq.daysLength < 0);
break;
}

View File

@ -46,6 +46,34 @@ export class TestHelper {
return dir;
}
public static getBasePhotoEntry(dir: DirectoryPathDTO, name = 'base media.jpg'): PhotoEntity {
const sd = new MediaDimensionEntity();
sd.height = 400;
sd.width = 200;
const m = new PhotoMetadataEntity();
m.caption = null;
m.size = sd;
m.creationDate = 1656069387772;
m.fileSize = 123456789;
// m.rating = 0; no rating by default
// TODO: remove when typeorm is fixed
m.duration = null;
m.bitRate = null;
const d = new PhotoEntity();
d.name = name;
d.directory = (dir as any);
if ((dir as DirectoryBaseDTO).media) {
(dir as DirectoryBaseDTO).media.push(d);
(dir as DirectoryBaseDTO).mediaCount++;
}
d.metadata = m;
return d;
}
public static getPhotoEntry(dir: DirectoryPathDTO): PhotoEntity {
const sd = new MediaDimensionEntity();
sd.height = 400;

View File

@ -2,7 +2,7 @@ import {Config} from '../../src/common/config/private/Config';
import * as path from 'path';
import * as fs from 'fs';
import {SQLConnection} from '../../src/backend/model/database/SQLConnection';
import {DatabaseType} from '../../src/common/config/private/PrivateConfig';
import {DatabaseType, LogLevel} from '../../src/common/config/private/PrivateConfig';
import {ProjectPath} from '../../src/backend/ProjectPath';
import {DirectoryBaseDTO, ParentDirectoryDTO, SubDirectoryDTO} from '../../src/common/entities/DirectoryDTO';
import {ObjectManagers} from '../../src/backend/model/ObjectManagers';
@ -129,7 +129,7 @@ export class DBTestHelper {
const gm = new GalleryManagerTest();
const dir = await gm.getParentDirFromId(connection,
(await gm.getDirIdAndTime(connection, directory.name, path.join(path.dirname('.'), path.sep))).id);
(await gm.getDirIdAndTime(connection, directory.name, path.join(directory.path, path.sep))).id);
const populateDir = async (d: DirectoryBaseDTO) => {
for (let i = 0; i < d.directories.length; i++) {

View File

@ -38,6 +38,7 @@ import {Config} from '../../../../../src/common/config/private/Config';
import {SearchQueryParser} from '../../../../../src/common/SearchQueryParser';
import {FileDTO} from '../../../../../src/common/entities/FileDTO';
import {SortByTypes} from '../../../../../src/common/entities/SortingMethods';
import {LogLevel} from '../../../../../src/common/config/private/PrivateConfig';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
@ -103,6 +104,8 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
let p2: PhotoDTO;
let pFaceLess: PhotoDTO;
let p4: PhotoDTO;
// let p5: PhotoDTO;
// let p6: PhotoDTO;
let gpx: FileDTO;
@ -124,7 +127,6 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
pFaceLessTmp.metadata.creationDate = Date.now() - 60 * 60 * 24 * 32 * 1000;
dir = await DBTestHelper.persistTestDir(directory);
subDir = dir.directories[0];
subDir2 = dir.directories[1];
p = (dir.media.filter(m => m.name === p.name)[0] as any);
@ -429,7 +431,6 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
});
it('should minimum of', async () => {
const sm = new SearchManager();
@ -925,9 +926,43 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
} as SearchResultDTO));
});
describe('search date pattern', async () => {
let p5: PhotoDTO;
let p6: PhotoDTO;
let p7: PhotoDTO;
let sm: SearchManager;
before(async () => {
await sqlHelper.clearDB();
await setUpSqlDB();
p5 = TestHelper.getBasePhotoEntry(subDir2, 'p5-23h-ago.jpg');
p5.metadata.creationDate = Date.now() - 60 * 60 * 23 * 1000;
p6 = TestHelper.getBasePhotoEntry(subDir2, 'p6-300d-ago.jpg');
p6.metadata.creationDate = Date.now() - 60 * 60 * 24 * 300 * 1000;
p7 = TestHelper.getBasePhotoEntry(subDir2, 'p7-1y-1min-ago.jpg');
const d = new Date();
d.setUTCFullYear(d.getUTCFullYear() - 1);
d.setUTCMinutes(d.getUTCMinutes() - 1);
p7.metadata.creationDate = d.getTime();
subDir2 = await DBTestHelper.persistTestDir(subDir2) as any;
p4 = subDir2.media[0];
p4.directory = subDir2;
p5 = subDir2.media[1];
p5.directory = subDir2;
p6 = subDir2.media[2];
p6.directory = subDir2;
p7 = subDir2.media[3];
p7.directory = subDir2;
Config.Search.listDirectories = false;
Config.Search.listMetafiles = false;
sm = new SearchManager();
});
//TODO: this is flaky test for mysql
it('last-0-days:every-year', async () => {
it('should search date pattern', async () => {
const sm = new SearchManager();
let query: DatePatternSearch = {
daysLength: 0,
@ -939,13 +974,32 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [],
media: [p, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = {
daysLength: 0,
negate: true,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
};
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p2, p4, pFaceLess, v, p5, p6],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
//TODO: this is flaky test for mysql
it('last-1-days:every-year', async () => {
let query: DatePatternSearch = {
daysLength: 1,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
@ -955,12 +1009,31 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p],
media: [p, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = {
daysLength: 1,
negate: true,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p2, p4, pFaceLess, v, p5, p6],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
it('last-2-days:every-year', async () => {
let query = {
daysLength: 2,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
@ -970,13 +1043,31 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, p4],
media: [p, p2, p4, p5, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = {
daysLength: 2,
negate: true,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [v, pFaceLess, p6],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
it('last-1-days:10-days-ago', async () => {
let query = {
daysLength: 1,
agoNumber: 10,
frequency: DatePatternFrequency.days_ago,
@ -992,7 +1083,27 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
resultOverflow: false
} as SearchResultDTO));
query = {
daysLength: 1,
agoNumber: 10,
negate: true,
frequency: DatePatternFrequency.days_ago,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, p4, pFaceLess, v, p5, p6, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
it('last-3-days:1-month-ago', async () => {
let query = {
daysLength: 3,
agoNumber: 1,
frequency: DatePatternFrequency.months_ago,
@ -1009,6 +1120,61 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
} as SearchResultDTO));
query = {
daysLength: 3,
agoNumber: 1,
negate: true,
frequency: DatePatternFrequency.months_ago,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, p4, v, p5, p6, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
it('last-3-days:12-month-ago', async () => {
let query = {
daysLength: 3,
agoNumber: 12,
frequency: DatePatternFrequency.months_ago,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p4, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = {
daysLength: 3,
agoNumber: 12,
negate: true,
frequency: DatePatternFrequency.months_ago,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, v, p5, p6, pFaceLess],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
it('last-366-days:every-year', async () => {
let query = {
daysLength: 366,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
@ -1018,12 +1184,30 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, p4, pFaceLess, v],
media: [p, p2, p4, pFaceLess, v, p5, p6, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = {
daysLength: 366,
negate: true,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
it('last-32-days:every-month', async () => {
const query = {
daysLength: 32,
frequency: DatePatternFrequency.every_month,
type: SearchQueryTypes.date_pattern
@ -1033,13 +1217,48 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, p4, pFaceLess, v],
media: [p, p2, p4, pFaceLess, v, p5, p6, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
});
it('last-364-days:every-year', async () => {
let query = {
daysLength: 364,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, p4, pFaceLess, v, p5, p6, p7],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = {
daysLength: 364,
negate: true,
frequency: DatePatternFrequency.every_year,
type: SearchQueryTypes.date_pattern
} as DatePatternSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
});
it('should search rating', async () => {
const sm = new SearchManager();