1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-12-23 01:27:14 +02:00

Adding more indices to fields and improving SQL queries #437

This commit is contained in:
Patrik J. Braun 2022-12-04 22:23:51 +01:00
parent eea77a5bea
commit d75a307be6
21 changed files with 306 additions and 345 deletions

View File

@ -3,9 +3,9 @@ import { ErrorCodes, ErrorDTO } from '../../common/entities/Error';
import { ObjectManagers } from '../model/ObjectManagers';
import {
PersonDTO,
PersonWithSampleRegion,
} from '../../common/entities/PersonDTO';
import { Utils } from '../../common/Utils';
import {PersonEntry} from '../model/database/sql/enitites/PersonEntry';
export class PersonMWs {
public static async updatePerson(
@ -90,7 +90,7 @@ export class PersonMWs {
return next();
}
try {
const persons = Utils.clone(req.resultPipe as PersonWithSampleRegion[]);
const persons = Utils.clone(req.resultPipe as PersonEntry[]);
for (const item of persons) {
delete item.sampleRegion;
}

View File

@ -4,6 +4,7 @@ import {NextFunction, Request, Response} from 'express';
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
import {ContentWrapper} from '../../../common/entities/ConentWrapper';
import {
DirectoryPathDTO,
ParentDirectoryDTO,
SubDirectoryDTO,
} from '../../../common/entities/DirectoryDTO';
@ -12,8 +13,8 @@ import {Config} from '../../../common/config/private/Config';
import {ThumbnailSourceType} from '../../model/threading/PhotoWorker';
import {MediaDTO} from '../../../common/entities/MediaDTO';
import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing';
import {PersonWithSampleRegion} from '../../../common/entities/PersonDTO';
import {ServerTime} from '../ServerTimingMWs';
import {PersonEntry} from '../../model/database/sql/enitites/PersonEntry';
export class ThumbnailGeneratorMWs {
private static ThumbnailMapEntries =
@ -71,7 +72,7 @@ export class ThumbnailGeneratorMWs {
try {
const size: number = Config.Client.Media.Thumbnail.personThumbnailSize;
const persons: PersonWithSampleRegion[] = req.resultPipe as PersonWithSampleRegion[];
const persons: PersonEntry[] = req.resultPipe as PersonEntry[];
for (const item of persons) {
if (!item.sampleRegion) {
@ -88,7 +89,7 @@ export class ThumbnailGeneratorMWs {
// generate thumbnail path
const thPath = PhotoProcessing.generatePersonThumbnailPath(
mediaPath,
item.sampleRegion,
item.sampleRegion.media.metadata.faces.find(f => f.name === item.name),
size
);
@ -115,7 +116,7 @@ export class ThumbnailGeneratorMWs {
if (!req.resultPipe) {
return next();
}
const person: PersonWithSampleRegion = req.resultPipe as PersonWithSampleRegion;
const person: PersonEntry = req.resultPipe as PersonEntry;
try {
req.resultPipe = await PhotoProcessing.generatePersonThumbnail(person);
return next();
@ -214,24 +215,24 @@ export class ThumbnailGeneratorMWs {
directory: ParentDirectoryDTO | SubDirectoryDTO
): void {
if (typeof directory.media !== 'undefined') {
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media);
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media, directory);
}
if (directory.preview) {
ThumbnailGeneratorMWs.addThInfoToAPhoto(directory.preview);
ThumbnailGeneratorMWs.addThInfoToAPhoto(directory.preview, directory);
}
}
private static addThInfoToPhotos(photos: MediaDTO[]): void {
private static addThInfoToPhotos(photos: MediaDTO[], directory?: DirectoryPathDTO): void {
for (let i = 0; i < photos.length; ++i) {
this.addThInfoToAPhoto(photos[i]);
this.addThInfoToAPhoto(photos[i], directory ? directory : photos[i].directory);
}
}
private static addThInfoToAPhoto(photo: MediaDTO): void {
private static addThInfoToAPhoto(photo: MediaDTO, directory: DirectoryPathDTO): void {
const fullMediaPath = path.join(
ProjectPath.ImageFolder,
photo.directory.path,
photo.directory.name,
directory.path,
directory.name,
photo.name
);
for (let i = 0; i < ThumbnailGeneratorMWs.ThumbnailMapEntries.length; ++i) {

View File

@ -1,7 +1,6 @@
import { PersonEntry } from '../sql/enitites/PersonEntry';
import { PersonDTO } from '../../../../common/entities/PersonDTO';
import { IObjectManager } from './IObjectManager';
import { FaceRegion } from '../../../../common/entities/PhotoDTO';
import {PersonEntry} from '../sql/enitites/PersonEntry';
import {PersonDTO} from '../../../../common/entities/PersonDTO';
import {IObjectManager} from './IObjectManager';
export interface IPersonManager extends IObjectManager {
getAll(): Promise<PersonEntry[]>;
@ -9,7 +8,7 @@ export interface IPersonManager extends IObjectManager {
get(name: string): Promise<PersonEntry>;
// saving a Person with a sample region. Person entry cannot exist without a face region
saveAll(person: { name: string; faceRegion: FaceRegion }[]): Promise<void>;
saveAll(person: { name: string; mediaId: number }[]): Promise<void>;
updatePerson(name: string, partialPerson: PersonDTO): Promise<PersonEntry>;

View File

@ -7,7 +7,7 @@ export class PersonManager implements IPersonManager {
throw new Error('not supported by memory DB');
}
saveAll(person: { name: string; faceRegion: FaceRegion }[]): Promise<void> {
saveAll(person: { name: string; mediaId: number }[]): Promise<void> {
throw new Error('not supported by memory DB');
}

View File

@ -1,26 +1,24 @@
import { IGalleryManager } from '../interfaces/IGalleryManager';
import {IGalleryManager} from '../interfaces/IGalleryManager';
import {
ParentDirectoryDTO,
SubDirectoryDTO,
} from '../../../../common/entities/DirectoryDTO';
import * as path from 'path';
import * as fs from 'fs';
import { DirectoryEntity } from './enitites/DirectoryEntity';
import { SQLConnection } from './SQLConnection';
import { PhotoEntity } from './enitites/PhotoEntity';
import { ProjectPath } from '../../../ProjectPath';
import { Config } from '../../../../common/config/private/Config';
import { ISQLGalleryManager } from './IGalleryManager';
import { PhotoDTO } from '../../../../common/entities/PhotoDTO';
import { Connection } from 'typeorm';
import { MediaEntity } from './enitites/MediaEntity';
import { VideoEntity } from './enitites/VideoEntity';
import { DiskMangerWorker } from '../../threading/DiskMangerWorker';
import { Logger } from '../../../Logger';
import { FaceRegionEntry } from './enitites/FaceRegionEntry';
import { ObjectManagers } from '../../ObjectManagers';
import { DuplicatesDTO } from '../../../../common/entities/DuplicatesDTO';
import { ReIndexingSensitivity } from '../../../../common/config/private/PrivateConfig';
import {DirectoryEntity} from './enitites/DirectoryEntity';
import {SQLConnection} from './SQLConnection';
import {PhotoEntity} from './enitites/PhotoEntity';
import {ProjectPath} from '../../../ProjectPath';
import {Config} from '../../../../common/config/private/Config';
import {ISQLGalleryManager} from './IGalleryManager';
import {Connection} from 'typeorm';
import {MediaEntity} from './enitites/MediaEntity';
import {VideoEntity} from './enitites/VideoEntity';
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
import {Logger} from '../../../Logger';
import {ObjectManagers} from '../../ObjectManagers';
import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO';
import {ReIndexingSensitivity} from '../../../../common/config/private/PrivateConfig';
const LOG_TAG = '[GalleryManager]';
@ -52,13 +50,11 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
);
const lastModified = DiskMangerWorker.calcLastModified(stat);
const dir = await this.selectParentDir(
connection,
directoryPath.name,
directoryPath.parent
);
const dir = await this.getDirIdAndTime(connection, directoryPath.name, directoryPath.parent);
if (dir && dir.lastScanned != null) {
// If it seems that the content did not changed, do not work on it
// If it seems that the content did not change, do not work on it
if (
knownLastModified &&
knownLastScanned &&
@ -73,9 +69,9 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
}
if (
Date.now() - dir.lastScanned <=
Config.Server.Indexing.cachedFolderTimeout &&
Config.Server.Indexing.cachedFolderTimeout &&
Config.Server.Indexing.reIndexingSensitivity ===
ReIndexingSensitivity.medium
ReIndexingSensitivity.medium
) {
return null;
}
@ -85,9 +81,9 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
Logger.silly(
LOG_TAG,
'Reindexing reason: lastModified mismatch: known: ' +
dir.lastModified +
', current:' +
lastModified
dir.lastModified +
', current:' +
lastModified
);
const ret =
await ObjectManagers.getInstance().IndexingManager.indexDirectory(
@ -95,7 +91,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
);
for (const subDir of ret.directories) {
if (!subDir.preview) {
// if sub directories does not have photos, so cannot show a preview, try get one from DB
// if subdirectories do not have photos, so cannot show a preview, try getting one from DB
await this.fillPreviewForSubDir(connection, subDir);
}
}
@ -107,25 +103,24 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
(Date.now() - dir.lastScanned >
Config.Server.Indexing.cachedFolderTimeout &&
Config.Server.Indexing.reIndexingSensitivity >=
ReIndexingSensitivity.medium) ||
ReIndexingSensitivity.medium) ||
Config.Server.Indexing.reIndexingSensitivity >=
ReIndexingSensitivity.high
ReIndexingSensitivity.high
) {
// on the fly reindexing
Logger.silly(
LOG_TAG,
'lazy reindexing reason: cache timeout: lastScanned: ' +
(Date.now() - dir.lastScanned) +
'ms ago, cachedFolderTimeout:' +
Config.Server.Indexing.cachedFolderTimeout
(Date.now() - dir.lastScanned) +
'ms ago, cachedFolderTimeout:' +
Config.Server.Indexing.cachedFolderTimeout
);
ObjectManagers.getInstance()
.IndexingManager.indexDirectory(relativeDirectoryName)
.catch(console.error);
}
await this.fillParentDir(connection, dir);
return dir;
return await this.getParentDirFromId(connection, dir.id);
}
// never scanned (deep indexed), do it and return with it
@ -145,7 +140,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
async countMediaSize(): Promise<number> {
const connection = await SQLConnection.getConnection();
const { sum } = await connection
const {sum} = await connection
.getRepository(MediaEntity)
.createQueryBuilder('media')
.select('SUM(media.metadata.fileSize)', 'sum')
@ -234,7 +229,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
}
}
duplicateParis.push({ media: list });
duplicateParis.push({media: list});
}
};
@ -318,17 +313,30 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
dir.isPartial = true;
}
protected async selectParentDir(
protected async getDirIdAndTime(connection: Connection, name: string, path: string): Promise<{ id: number, lastScanned: number, lastModified: number }> {
return await connection
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.where('directory.name = :name AND directory.path = :path', {
name: name,
path: path,
})
.select([
'directory.id',
'directory.lastScanned',
'directory.lastModified',
]).getOne();
}
protected async getParentDirFromId(
connection: Connection,
directoryName: string,
directoryParent: string
partialDirId: number
): Promise<ParentDirectoryDTO> {
const query = connection
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.where('directory.name = :name AND directory.path = :path', {
name: directoryName,
path: directoryParent,
.where('directory.id = :id', {
id: partialDirId
})
.leftJoinAndSelect('directory.directories', 'directories')
.leftJoinAndSelect('directory.media', 'media')
@ -344,7 +352,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
]);
// TODO: do better filtering
// NOTE: it should not cause an issue as it also do not shave to the DB
// NOTE: it should not cause an issue as it also do not save to the DB
if (
Config.Client.MetaFile.gpx === true ||
Config.Client.MetaFile.pg2conf === true ||
@ -353,52 +361,13 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
query.leftJoinAndSelect('directory.metaFile', 'metaFile');
}
return await query.getOne();
}
protected async fillParentDir(
connection: Connection,
dir: ParentDirectoryDTO
): Promise<void> {
if (dir.media) {
const indexedFaces = await connection
.getRepository(FaceRegionEntry)
.createQueryBuilder('face')
.leftJoinAndSelect('face.media', 'media')
.where('media.directory = :directory', {
directory: dir.id,
})
.leftJoinAndSelect('face.person', 'person')
.select([
'face.id',
'face.box.left',
'face.box.top',
'face.box.width',
'face.box.height',
'media.id',
'person.name',
'person.id',
])
.getMany();
for (const item of dir.media) {
item.directory = dir;
(item as PhotoDTO).metadata.faces = indexedFaces
.filter((fe): boolean => fe.media.id === item.id)
.map((f): { name: any; box: any } => ({
box: f.box,
name: f.person.name,
}));
}
}
if (dir.metaFile) {
for (const item of dir.metaFile) {
item.directory = dir;
}
}
const dir = await query.getOne();
if (dir.directories) {
for (const item of dir.directories) {
await this.fillPreviewForSubDir(connection, item);
}
}
return dir;
}
}

View File

@ -15,7 +15,6 @@ import {VideoEntity} from './enitites/VideoEntity';
import {FileEntity} from './enitites/FileEntity';
import {FileDTO} from '../../../../common/entities/FileDTO';
import {NotificationManager} from '../../NotifocationManager';
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {ObjectManagers} from '../../ObjectManagers';
import {IIndexingManager} from '../interfaces/IIndexingManager';
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
@ -29,6 +28,7 @@ import * as path from 'path';
import * as fs from 'fs';
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
import {PersonEntry} from './enitites/PersonEntry';
import {PersonJunctionTable} from './enitites/PersonJunctionTable';
const LOG_TAG = '[IndexingManager]';
@ -346,16 +346,16 @@ export class IndexingManager implements IIndexingManager {
})
.getMany();
const mediaChange: any = {
saveP: [], // save/update photo
saveV: [], // save/update video
insertP: [], // insert photo
insertV: [], // insert video
const mediaChange = {
saveP: [] as MediaDTO[], // save/update photo
saveV: [] as MediaDTO[], // save/update video
insertP: [] as MediaDTO[], // insert photo
insertV: [] as MediaDTO[], // insert video
};
const facesPerPhoto: { faces: FaceRegionEntry[]; mediaName: string }[] = [];
const personsPerPhoto: { faces: { name: string, mediaId?: number }[]; mediaName: string }[] = [];
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < media.length; i++) {
let mediaItem: MediaEntity = null;
let mediaItem: MediaDTO = null;
for (let j = 0; j < indexedMedia.length; j++) {
if (indexedMedia[j].name === media[i].name) {
mediaItem = indexedMedia[j];
@ -364,7 +364,7 @@ export class IndexingManager implements IIndexingManager {
}
}
const scannedFaces = (media[i].metadata as PhotoMetadata).faces || [];
const scannedFaces: { name: string }[] = (media[i].metadata as PhotoMetadata).faces || [];
if ((media[i].metadata as PhotoMetadata).faces) {
// if it has faces, cache them
// make the list distinct (some photos may contain the same person multiple times)
@ -374,22 +374,22 @@ export class IndexingManager implements IIndexingManager {
),
];
}
delete (media[i].metadata as PhotoMetadata).faces; // this is a separated DB, lets save separately
if (mediaItem == null) {
// not in DB yet
// Media not in DB yet
media[i].directory = null;
mediaItem = Utils.clone(media[i]) as any;
mediaItem = Utils.clone(media[i]);
mediaItem.directory = {id: parentDirId} as any;
(MediaDTOUtils.isPhoto(mediaItem)
? mediaChange.insertP
: mediaChange.insertV
).push(mediaItem);
} else {
// already in the DB, only needs to be updated
// Media already in the DB, only needs to be updated
delete (mediaItem.metadata as PhotoMetadata).faces;
if (!Utils.equalsFilter(mediaItem.metadata, media[i].metadata)) {
mediaItem.metadata = media[i].metadata as any;
mediaItem.metadata = media[i].metadata;
(MediaDTOUtils.isPhoto(mediaItem)
? mediaChange.saveP
: mediaChange.saveV
@ -397,9 +397,9 @@ export class IndexingManager implements IIndexingManager {
}
}
facesPerPhoto.push({
faces: scannedFaces as FaceRegionEntry[],
mediaName: mediaItem.name,
personsPerPhoto.push({
faces: scannedFaces,
mediaName: mediaItem.name
});
}
@ -416,44 +416,44 @@ export class IndexingManager implements IIndexingManager {
.select(['media.name', 'media.id'])
.getMany();
const faces: FaceRegionEntry[] = [];
facesPerPhoto.forEach((group): void => {
const persons: { name: string; mediaId: number }[] = [];
personsPerPhoto.forEach((group): void => {
const mIndex = indexedMedia.findIndex(
(m): boolean => m.name === group.mediaName
);
group.faces.forEach(
(sf: FaceRegionEntry): any =>
(sf.media = {id: indexedMedia[mIndex].id} as any)
group.faces.forEach((sf) =>
(sf.mediaId = indexedMedia[mIndex].id)
);
faces.push(...group.faces);
persons.push(...group.faces as { name: string; mediaId: number }[]);
indexedMedia.splice(mIndex, 1);
});
await this.saveFaces(connection, parentDirId, faces);
await this.savePersonsToMedia(connection, parentDirId, persons);
await mediaRepository.remove(indexedMedia);
}
protected async saveFaces(
protected async savePersonsToMedia(
connection: Connection,
parentDirId: number,
scannedFaces: FaceRegion[]
scannedFaces: { name: string; mediaId: number }[]
): Promise<void> {
const faceRepository = connection.getRepository(FaceRegionEntry);
const personJunctionTable = connection.getRepository(PersonJunctionTable);
const personRepository = connection.getRepository(PersonEntry);
const persons: { name: string; faceRegion: FaceRegion }[] = [];
const persons: { name: string; mediaId: number }[] = [];
// Make a set
for (const face of scannedFaces) {
if (persons.findIndex((f) => f.name === face.name) === -1) {
persons.push({name: face.name, faceRegion: face});
persons.push(face);
}
}
await ObjectManagers.getInstance().PersonManager.saveAll(persons);
// get saved persons without triggering denormalized data update (i.e.: do not use PersonManager.get).
const savedPersons = await personRepository.find();
const indexedFaces = await faceRepository
const indexedFaces = await personJunctionTable
.createQueryBuilder('face')
.leftJoin('face.media', 'media')
.where('media.directory = :directory', {
@ -462,19 +462,13 @@ export class IndexingManager implements IIndexingManager {
.leftJoinAndSelect('face.person', 'person')
.getMany();
const faceToInsert = [];
const faceToInsert: { person: { id: number }, media: { id: number } }[] = [];
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < scannedFaces.length; i++) {
// was the face region already indexed
let face: FaceRegionEntry = null;
// was the Person - media connection already indexed
let face: PersonJunctionTable = null;
for (let j = 0; j < indexedFaces.length; j++) {
if (
indexedFaces[j].box.height === scannedFaces[i].box.height &&
indexedFaces[j].box.width === scannedFaces[i].box.width &&
indexedFaces[j].box.left === scannedFaces[i].box.left &&
indexedFaces[j].box.top === scannedFaces[i].box.top &&
indexedFaces[j].person.name === scannedFaces[i].name
) {
if (indexedFaces[j].person.name === scannedFaces[i].name) {
face = indexedFaces[j];
indexedFaces.splice(j, 1);
break; // region found, stop processing
@ -482,16 +476,18 @@ export class IndexingManager implements IIndexingManager {
}
if (face == null) {
(scannedFaces[i] as FaceRegionEntry).person = savedPersons.find(
(p) => p.name === scannedFaces[i].name
);
faceToInsert.push(scannedFaces[i]);
faceToInsert.push({
person: savedPersons.find(
(p) => p.name === scannedFaces[i].name
),
media: {id: scannedFaces[i].mediaId}
});
}
}
if (faceToInsert.length > 0) {
await this.insertChunk(faceRepository, faceToInsert, 100);
await this.insertChunk(personJunctionTable, faceToInsert, 100);
}
await faceRepository.remove(indexedFaces, {
await personJunctionTable.remove(indexedFaces, {
chunk: Math.max(Math.ceil(indexedFaces.length / 500), 1),
});
}

View File

@ -1,11 +1,11 @@
import { SQLConnection } from './SQLConnection';
import { PersonEntry } from './enitites/PersonEntry';
import { FaceRegionEntry } from './enitites/FaceRegionEntry';
import { PersonDTO } from '../../../../common/entities/PersonDTO';
import { ISQLPersonManager } from './IPersonManager';
import { Logger } from '../../../Logger';
import { FaceRegion } from '../../../../common/entities/PhotoDTO';
import { SQL_COLLATE } from './enitites/EntityUtils';
import {SQLConnection} from './SQLConnection';
import {PersonEntry} from './enitites/PersonEntry';
import {PersonDTO} from '../../../../common/entities/PersonDTO';
import {ISQLPersonManager} from './IPersonManager';
import {Logger} from '../../../Logger';
import {FaceRegion} from '../../../../common/entities/PhotoDTO';
import {SQL_COLLATE} from './enitites/EntityUtils';
import {PersonJunctionTable} from './enitites/PersonJunctionTable';
const LOG_TAG = '[PersonManager]';
@ -20,7 +20,7 @@ export class PersonManager implements ISQLPersonManager {
const connection = await SQLConnection.getConnection();
await connection.query(
'UPDATE person_entry SET count = ' +
' (SELECT COUNT(1) FROM face_region_entry WHERE face_region_entry.personId = person_entry.id)'
' (SELECT COUNT(1) FROM person_junction_table WHERE person_junction_table.personId = person_entry.id)'
);
// remove persons without photo
@ -36,11 +36,11 @@ export class PersonManager implements ISQLPersonManager {
const connection = await SQLConnection.getConnection();
await connection.query(
'update person_entry set sampleRegionId = ' +
'(Select face_region_entry.id from media_entity ' +
'left join face_region_entry on media_entity.id = face_region_entry.mediaId ' +
'where face_region_entry.personId=person_entry.id ' +
'order by media_entity.metadataCreationdate desc ' +
'limit 1)'
'(Select person_junction_table.id from media_entity ' +
'left join person_junction_table on media_entity.id = person_junction_table.mediaId ' +
'where person_junction_table.personId=person_entry.id ' +
'order by media_entity.metadataCreationdate desc ' +
'limit 1)'
);
}
@ -54,7 +54,7 @@ export class PersonManager implements ISQLPersonManager {
const person = await repository
.createQueryBuilder('person')
.limit(1)
.where('person.name LIKE :name COLLATE ' + SQL_COLLATE, { name })
.where('person.name LIKE :name COLLATE ' + SQL_COLLATE, {name})
.getOne();
if (typeof partialPerson.name !== 'undefined') {
@ -83,8 +83,8 @@ export class PersonManager implements ISQLPersonManager {
public async countFaces(): Promise<number> {
const connection = await SQLConnection.getConnection();
return await connection
.getRepository(FaceRegionEntry)
.createQueryBuilder('faceRegion')
.getRepository(PersonJunctionTable)
.createQueryBuilder('personJunction')
.getCount();
}
@ -96,12 +96,12 @@ export class PersonManager implements ISQLPersonManager {
}
public async saveAll(
persons: { name: string; faceRegion: FaceRegion }[]
persons: { name: string; mediaId: number }[]
): Promise<void> {
const toSave: { name: string; faceRegion: FaceRegion }[] = [];
const toSave: { name: string; mediaId: number }[] = [];
const connection = await SQLConnection.getConnection();
const personRepository = connection.getRepository(PersonEntry);
const faceRegionRepository = connection.getRepository(FaceRegionEntry);
const personJunction = connection.getRepository(PersonJunctionTable);
const savedPersons = await personRepository.find();
// filter already existing persons
@ -117,14 +117,13 @@ export class PersonManager implements ISQLPersonManager {
if (toSave.length > 0) {
for (let i = 0; i < toSave.length / 200; i++) {
const saving = toSave.slice(i * 200, (i + 1) * 200);
// saving person
const inserted = await personRepository.insert(
saving.map((p) => ({ name: p.name }))
saving.map((p) => ({name: p.name}))
);
// setting Person id
inserted.identifiers.forEach((idObj: { id: number }, j: number) => {
(saving[j].faceRegion as FaceRegionEntry).person = idObj as any;
});
await faceRegionRepository.insert(saving.map((p) => p.faceRegion));
// saving junction table
const junctionTable = inserted.identifiers.map((idObj, j) => ({person: idObj, media: {id: saving[j].mediaId}}));
await personJunction.insert(junctionTable);
}
}
this.isDBValid = false;
@ -148,6 +147,7 @@ export class PersonManager implements ISQLPersonManager {
'sampleRegion',
'sampleRegion.media',
'sampleRegion.media.directory',
'sampleRegion.media.metadata',
],
});
}

View File

@ -19,7 +19,6 @@ import { MediaEntity } from './enitites/MediaEntity';
import { VideoEntity } from './enitites/VideoEntity';
import { DataStructureVersion } from '../../../../common/DataStructureVersion';
import { FileEntity } from './enitites/FileEntity';
import { FaceRegionEntry } from './enitites/FaceRegionEntry';
import { PersonEntry } from './enitites/PersonEntry';
import { Utils } from '../../../../common/Utils';
import * as path from 'path';
@ -31,6 +30,7 @@ import {
import { AlbumBaseEntity } from './enitites/album/AlbumBaseEntity';
import { SavedSearchEntity } from './enitites/album/SavedSearchEntity';
import { NotificationManager } from '../../NotifocationManager';
import {PersonJunctionTable} from './enitites/PersonJunctionTable';
const LOG_TAG = '[SQLConnection]';
@ -45,7 +45,7 @@ export class SQLConnection {
options.entities = [
UserEntity,
FileEntity,
FaceRegionEntry,
PersonJunctionTable,
PersonEntry,
MediaEntity,
PhotoEntity,
@ -84,7 +84,7 @@ export class SQLConnection {
options.entities = [
UserEntity,
FileEntity,
FaceRegionEntry,
PersonJunctionTable,
PersonEntry,
MediaEntity,
PhotoEntity,

View File

@ -37,19 +37,12 @@ import {FileEntity} from './enitites/FileEntity';
import {SQL_COLLATE} from './enitites/EntityUtils';
export class SearchManager implements ISQLSearchManager {
// This trick enables us to list less rows as faces will be concatenated into one row
// Also typeorm does not support automatic mapping of nested foreign keys
// (i.e: leftJoinAndSelect('media.metadata.faces', 'faces') does not work)
private FACE_SELECT =
Config.Server.Database.type === DatabaseType.mysql
? 'CONCAT(\'[\' , GROUP_CONCAT( \'{"name": "\' , person.name , \'", "box": {"top":\' , faces.box.top , \', "left":\' , faces.box.left , \', "height":\' , faces.box.height ,\', "width":\' , faces.box.width , \'}}\' ) ,\']\') as media_metadataFaces'
: '\'[\' || GROUP_CONCAT( \'{"name": "\' || person.name || \'", "box": {"top":\' || faces.box.top || \', "left":\' || faces.box.left || \', "height":\' || faces.box.height ||\', "width":\' || faces.box.width || \'}}\' ) ||\']\' as media_metadataFaces';
private DIRECTORY_SELECT = [
'directory.id',
'directory.name',
'directory.path',
];
// makes all search query params unique, so typeorm wont mix them
// makes all search query params unique, so typeorm won't mix them
private queryIdBase = 0;
private static autoCompleteItemsUnique(
@ -293,47 +286,28 @@ export class SearchManager implements ISQLSearchManager {
resultOverflow: false,
};
const rawAndEntries = await connection
result.media = await connection
.getRepository(MediaEntity)
.createQueryBuilder('media')
.select(['media', ...this.DIRECTORY_SELECT, this.FACE_SELECT])
.select(['media', ...this.DIRECTORY_SELECT])
.where(this.buildWhereQuery(query))
.leftJoin('media.directory', 'directory')
.leftJoin('media.metadata.faces', 'faces')
.leftJoin('faces.person', 'person')
.limit(Config.Client.Search.maxMediaResult + 1)
.groupBy('media.id')
.getRawAndEntities();
.getMany();
for (let i = 0; i < rawAndEntries.entities.length; ++i) {
if (rawAndEntries.raw[i].media_metadataFaces) {
rawAndEntries.entities[i].metadata.faces = JSON.parse(
rawAndEntries.raw[i].media_metadataFaces
);
}
}
result.media = rawAndEntries.entities;
if (result.media.length > Config.Client.Search.maxMediaResult) {
result.resultOverflow = true;
}
if (Config.Client.Search.listMetafiles === true) {
const dIds = Array.from(new Set(result.media.map(m => (m.directory as unknown as { id: number }).id)));
result.metaFile = await connection
.getRepository(FileEntity)
.createQueryBuilder('file')
.select(['file', ...this.DIRECTORY_SELECT])
.innerJoin(
(q) =>
q
.from(MediaEntity, 'media')
.select('distinct directory.id')
.where(this.buildWhereQuery(query))
.leftJoin('media.directory', 'directory'),
'dir',
'file.directory=dir.id'
)
.where(`file.directoryId IN(${dIds})`)
.leftJoin('file.directory', 'directory')
.getMany();
}

View File

@ -14,10 +14,10 @@ import {
MediaDTO,
MediaMetadata,
} from '../../../../../common/entities/MediaDTO';
import { FaceRegionEntry } from './FaceRegionEntry';
import { PersonJunctionTable} from './PersonJunctionTable';
import { columnCharsetCS } from './EntityUtils';
import {
CameraMetadata,
CameraMetadata, FaceRegion,
GPSMetadata,
PositionMetaData,
} from '../../../../../common/entities/PhotoDTO';
@ -117,6 +117,7 @@ export class MediaMetadataEntity implements MediaMetadata {
to: (v) => v,
},
})
@Index()
creationDate: number;
@Column('int', { unsigned: true })
@ -136,10 +137,18 @@ export class MediaMetadataEntity implements MediaMetadata {
positionData: PositionMetaDataEntity;
@Column('tinyint', { unsigned: true })
@Index()
rating: 0 | 1 | 2 | 3 | 4 | 5;
@OneToMany((type) => FaceRegionEntry, (faceRegion) => faceRegion.media)
faces: FaceRegionEntry[];
@OneToMany((type) => PersonJunctionTable, (junctionTable) => junctionTable.media)
personJunction: PersonJunctionTable[];
@Column({
type:'simple-json',
nullable: true,
charset: columnCharsetCS.charset,
collation: columnCharsetCS.collation})
faces: FaceRegion[];
/**
* Caches the list of persons. Only used for searching

View File

@ -7,32 +7,35 @@ import {
PrimaryGeneratedColumn,
Unique,
} from 'typeorm';
import { FaceRegionEntry } from './FaceRegionEntry';
import { columnCharsetCS } from './EntityUtils';
import { PersonWithSampleRegion } from '../../../../../common/entities/PersonDTO';
import {PersonJunctionTable} from './PersonJunctionTable';
import {columnCharsetCS} from './EntityUtils';
import { PersonDTO } from '../../../../../common/entities/PersonDTO';
@Entity()
@Unique(['name'])
export class PersonEntry implements PersonWithSampleRegion {
export class PersonEntry implements PersonDTO {
@Index()
@PrimaryGeneratedColumn({ unsigned: true })
@PrimaryGeneratedColumn({unsigned: true})
id: number;
@Column(columnCharsetCS)
name: string;
@Column('int', { unsigned: true, default: 0 })
@Column('int', {unsigned: true, default: 0})
count: number;
@Column({ default: false })
@Column({default: false})
isFavourite: boolean;
@OneToMany((type) => FaceRegionEntry, (faceRegion) => faceRegion.person)
public faces: FaceRegionEntry[];
@OneToMany((type) => PersonJunctionTable, (junctionTable) => junctionTable.person)
public faces: PersonJunctionTable[];
@ManyToOne((type) => FaceRegionEntry, {
@ManyToOne((type) => PersonJunctionTable, {
onDelete: 'SET NULL',
nullable: true,
})
sampleRegion: FaceRegionEntry;
sampleRegion: PersonJunctionTable;
// does not store in the DB, temporal field
missingThumbnail?: boolean;
}

View File

@ -12,7 +12,7 @@ import {
import {ITaskExecuter, TaskExecuter} from '../threading/TaskExecuter';
import {FaceRegion, PhotoDTO} from '../../../common/entities/PhotoDTO';
import {SupportedFormats} from '../../../common/SupportedFormats';
import {PersonWithSampleRegion} from '../../../common/entities/PersonDTO';
import {PersonEntry} from '../database/sql/enitites/PersonEntry';
export class PhotoProcessing {
private static initDone = false;
@ -47,7 +47,7 @@ export class PhotoProcessing {
}
public static async generatePersonThumbnail(
person: PersonWithSampleRegion
person: PersonEntry
): Promise<string> {
// load parameters
const photo: PhotoDTO = person.sampleRegion.media;
@ -58,10 +58,11 @@ export class PhotoProcessing {
photo.name
);
const size: number = Config.Client.Media.Thumbnail.personThumbnailSize;
const faceRegion = person.sampleRegion.media.metadata.faces.find(f => f.name === person.name);
// generate thumbnail path
const thPath = PhotoProcessing.generatePersonThumbnailPath(
mediaPath,
person.sampleRegion,
faceRegion,
size
);
@ -75,11 +76,11 @@ export class PhotoProcessing {
const margin = {
x: Math.round(
person.sampleRegion.box.width *
faceRegion.box.width *
Config.Server.Media.Thumbnail.personFaceMargin
),
y: Math.round(
person.sampleRegion.box.height *
faceRegion.box.height *
Config.Server.Media.Thumbnail.personFaceMargin
),
};
@ -93,13 +94,13 @@ export class PhotoProcessing {
makeSquare: false,
cut: {
left: Math.round(
Math.max(0, person.sampleRegion.box.left - margin.x / 2)
Math.max(0, faceRegion.box.left - margin.x / 2)
),
top: Math.round(
Math.max(0, person.sampleRegion.box.top - margin.y / 2)
Math.max(0, faceRegion.box.top - margin.y / 2)
),
width: person.sampleRegion.box.width + margin.x,
height: person.sampleRegion.box.height + margin.y,
width: faceRegion.box.width + margin.x,
height: faceRegion.box.height + margin.y,
},
useLanczos3: Config.Server.Media.Thumbnail.useLanczos3,
quality: Config.Server.Media.Thumbnail.quality,

View File

@ -1,4 +1,4 @@
/**
* This version indicates that the SQL sql/entities/*Entity.ts files got changed and the db needs to be recreated
* This version indicates that the sql/entities/*Entity.ts files got changed and the db needs to be recreated
*/
export const DataStructureVersion = 28;
export const DataStructureVersion = 30;

View File

@ -1,9 +1,3 @@
import { FaceRegionEntry } from '../../backend/model/database/sql/enitites/FaceRegionEntry';
export interface PersonWithSampleRegion extends PersonDTO {
sampleRegion: FaceRegionEntry;
}
export interface PersonDTO {
id: number;
name: string;

View File

@ -14,6 +14,7 @@ import {Utils} from '../../src/common/Utils';
import {TestHelper} from '../TestHelper';
import {VideoDTO} from '../../src/common/entities/VideoDTO';
import {PhotoDTO} from '../../src/common/entities/PhotoDTO';
import {Logger} from '../../src/backend/Logger';
declare let describe: any;
const savedDescribe = describe;
@ -27,15 +28,18 @@ class IndexingManagerTest extends IndexingManager {
class GalleryManagerTest extends GalleryManager {
public async selectParentDir(connection: Connection, directoryName: string, directoryParent: string): Promise<ParentDirectoryDTO> {
return super.selectParentDir(connection, directoryName, directoryParent);
public async getDirIdAndTime(connection: Connection, directoryName: string, directoryParent: string) {
return super.getDirIdAndTime(connection, directoryName, directoryParent);
}
public async fillParentDir(connection: Connection, dir: ParentDirectoryDTO): Promise<void> {
return super.fillParentDir(connection, dir);
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
return super.getParentDirFromId(connection, dir);
}
}
const LOG_TAG = 'DBTestHelper';
export class DBTestHelper {
static enable = {
@ -44,7 +48,7 @@ export class DBTestHelper {
mysql: process.env.TEST_MYSQL !== 'false'
};
public static readonly savedDescribe = savedDescribe;
tempDir: string;
public tempDir: string;
public readonly testGalleyEntities: {
dir: ParentDirectoryDTO,
subDir: SubDirectoryDTO,
@ -101,7 +105,6 @@ export class DBTestHelper {
const helper = new DBTestHelper(DatabaseType.mysql);
savedDescribe('mysql', function(): void {
this.timeout(99999999); // hint for the test environment
// @ts-ignore
return tests(helper);
});
}
@ -132,14 +135,15 @@ export class DBTestHelper {
}
const gm = new GalleryManagerTest();
const dir = await gm.selectParentDir(connection, directory.name, path.join(path.dirname('.'), path.sep));
await gm.fillParentDir(connection, dir);
const dir = await gm.getParentDirFromId(connection,
(await gm.getDirIdAndTime(connection, directory.name, path.join(path.dirname('.'), path.sep))).id);
const populateDir = async (d: DirectoryBaseDTO) => {
for (let i = 0; i < d.directories.length; i++) {
d.directories[i] = await gm.selectParentDir(connection, d.directories[i].name,
path.join(DiskMangerWorker.pathFromParent(d), path.sep));
await gm.fillParentDir(connection, d.directories[i] as any);
d.directories[i] = await gm.getParentDirFromId(connection,
(await gm.getDirIdAndTime(connection, d.directories[i].name,
path.join(DiskMangerWorker.pathFromParent(d), path.sep))).id);
await populateDir(d.directories[i]);
}
};
@ -184,10 +188,15 @@ export class DBTestHelper {
this.testGalleyEntities.subDir = this.testGalleyEntities.dir.directories[0];
this.testGalleyEntities.subDir2 = this.testGalleyEntities.dir.directories[1];
this.testGalleyEntities.p = (this.testGalleyEntities.dir.media.filter(m => m.name === this.testGalleyEntities.p.name)[0] as any);
this.testGalleyEntities.p.directory = this.testGalleyEntities.dir;
this.testGalleyEntities.p2 = (this.testGalleyEntities.dir.media.filter(m => m.name === this.testGalleyEntities.p2.name)[0] as any);
this.testGalleyEntities.p2.directory = this.testGalleyEntities.dir;
this.testGalleyEntities.v = (this.testGalleyEntities.dir.media.filter(m => m.name === this.testGalleyEntities.v.name)[0] as any);
this.testGalleyEntities.v.directory = this.testGalleyEntities.dir;
this.testGalleyEntities.p3 = (this.testGalleyEntities.dir.directories[0].media[0] as any);
this.testGalleyEntities.p3.directory = this.testGalleyEntities.dir.directories[0];
this.testGalleyEntities.p4 = (this.testGalleyEntities.dir.directories[1].media[0] as any);
this.testGalleyEntities.p2.directory = this.testGalleyEntities.dir.directories[1];
}
private async initMySQL(): Promise<void> {
@ -195,20 +204,20 @@ export class DBTestHelper {
}
private async resetMySQL(): Promise<void> {
await ObjectManagers.reset();
Config.Server.Database.type = DatabaseType.mysql;
Config.Server.Database.mysql.database = 'pigallery2_test';
Logger.debug(LOG_TAG, 'resetting up mysql');
await this.clearUpMysql();
const conn = await SQLConnection.getConnection();
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
await conn.query('CREATE DATABASE IF NOT EXISTS ' + conn.options.database);
await SQLConnection.close();
await ObjectManagers.InitSQLManagers();
}
private async clearUpMysql(): Promise<void> {
Logger.debug(LOG_TAG, 'clearing up mysql');
await ObjectManagers.reset();
Config.Server.Database.type = DatabaseType.mysql;
Config.Server.Database.mysql.database = 'pigallery2_test';
await fs.promises.rm(this.tempDir, {recursive: true, force: true});
const conn = await SQLConnection.getConnection();
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
await SQLConnection.close();
@ -219,15 +228,13 @@ export class DBTestHelper {
}
private async resetSQLite(): Promise<void> {
Config.Server.Database.type = DatabaseType.sqlite;
Config.Server.Database.dbFolder = this.tempDir;
ProjectPath.reset();
await ObjectManagers.reset();
await fs.promises.rm(this.tempDir, {recursive: true, force: true});
Logger.debug(LOG_TAG, 'resetting sqlite');
await this.clearUpSQLite();
await ObjectManagers.InitSQLManagers();
}
private async clearUpSQLite(): Promise<void> {
Logger.debug(LOG_TAG, 'clearing up sqlite');
Config.Server.Database.type = DatabaseType.sqlite;
Config.Server.Database.dbFolder = this.tempDir;
ProjectPath.reset();

View File

@ -1,9 +1,6 @@
import {DBTestHelper} from '../../../DBTestHelper';
import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
import {TestHelper} from '../../../../TestHelper';
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
import {PhotoDTO} from '../../../../../src/common/entities/PhotoDTO';
import {VideoDTO} from '../../../../../src/common/entities/VideoDTO';
import {AlbumManager} from '../../../../../src/backend/model/database/sql/AlbumManager';
import {SearchQueryTypes, TextSearch} from '../../../../../src/common/entities/SearchQueryDTO';
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
@ -13,7 +10,9 @@ import {MediaDTO} from '../../../../../src/common/entities/MediaDTO';
import {SavedSearchDTO} from '../../../../../src/common/entities/album/SavedSearchDTO';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const chai = require('chai');
chai.use(deepEqualInAnyOrder);

View File

@ -6,7 +6,9 @@ import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/eni
import {ParentDirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
import {Connection} from 'typeorm';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const chai = require('chai');
chai.use(deepEqualInAnyOrder);
@ -22,15 +24,13 @@ describe = DBTestHelper.describe();
class GalleryManagerTest extends GalleryManager {
public async selectParentDir(connection: Connection, directoryName: string, directoryParent: string): Promise<ParentDirectoryDTO> {
return super.selectParentDir(connection, directoryName, directoryParent);
public async getDirIdAndTime(connection: Connection, directoryName: string, directoryParent: string) {
return super.getDirIdAndTime(connection, directoryName, directoryParent);
}
public async fillParentDir(connection: Connection, dir: ParentDirectoryDTO): Promise<void> {
return super.fillParentDir(connection, dir);
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
return super.getParentDirFromId(connection, dir);
}
}
describe('GalleryManager', (sqlHelper: DBTestHelper) => {

View File

@ -5,7 +5,6 @@ import {GalleryManager} from '../../../../../src/backend/model/database/sql/Gall
import {DirectoryBaseDTO, DirectoryDTOUtils, ParentDirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
import {TestHelper} from '../../../../TestHelper';
import {Connection} from 'typeorm';
import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity';
import {Utils} from '../../../../../src/common/Utils';
import {MediaDTO} from '../../../../../src/common/entities/MediaDTO';
import {FileDTO} from '../../../../../src/common/entities/FileDTO';
@ -13,7 +12,7 @@ import {IndexingManager} from '../../../../../src/backend/model/database/sql/Ind
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
import {DBTestHelper} from '../../../DBTestHelper';
import {DiskMangerWorker} from '../../../../../src/backend/model/threading/DiskMangerWorker';
import {ReIndexingSensitivity} from '../../../../../src/common/config/private/PrivateConfig';
import {ReIndexingSensitivity, SQLLogLevel} from '../../../../../src/common/config/private/PrivateConfig';
import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../../../src/common/entities/SearchQueryDTO';
import {ProjectPath} from '../../../../../src/backend/ProjectPath';
import * as path from 'path';
@ -31,15 +30,13 @@ const {expect} = chai;
class GalleryManagerTest extends GalleryManager {
public async selectParentDir(connection: Connection, directoryName: string, directoryParent: string): Promise<ParentDirectoryDTO> {
return super.selectParentDir(connection, directoryName, directoryParent);
public async getDirIdAndTime(connection: Connection, directoryName: string, directoryParent: string) {
return super.getDirIdAndTime(connection, directoryName, directoryParent);
}
public async fillParentDir(connection: Connection, dir: ParentDirectoryDTO): Promise<void> {
return super.fillParentDir(connection, dir);
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
return super.getParentDirFromId(connection, dir);
}
}
class IndexingManagerTest extends IndexingManager {
@ -149,8 +146,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
@ -175,14 +172,16 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
'.'
);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, directoryPath.name,
directoryPath.parent);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, directoryPath.name, directoryPath.parent)).id);
expect(selected?.media?.length)
.to.be.greaterThan(0);
const tmpDir = path.join(__dirname, '/../../../tmp/rnd5sdf_emptyDir');
if (!fs.existsSync(sqlHelper.tempDir)) {
fs.mkdirSync(sqlHelper.tempDir);
}
const tmpDir = path.join(sqlHelper.tempDir, '/rnd5sdf_emptyDir');
fs.mkdirSync(tmpDir);
ProjectPath.ImageFolder = tmpDir;
let notFailed = false;
@ -214,8 +213,9 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -244,8 +244,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
const conn = await SQLConnection.getConnection();
{
const selected = await gm.selectParentDir(conn, parent1.name, parent1.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent1.name, parent1.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -254,8 +254,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
.to.deep.equalInAnyOrder(Utils.removeNullOrEmptyObj(indexifyReturn(parent1)));
}
{
const selected = await gm.selectParentDir(conn, parent2.name, parent2.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent2.name, parent2.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -277,8 +277,9 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
@ -290,8 +291,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
it('should select preview', async () => {
const selectDirectory = async (gmTest: GalleryManagerTest, dir: DirectoryBaseDTO): Promise<ParentDirectoryDTO> => {
const conn = await SQLConnection.getConnection();
const selected = await gmTest.selectParentDir(conn, dir.name, dir.path);
await gmTest.fillParentDir(conn, selected);
const selected = await gmTest.getParentDirFromId(conn,
(await gmTest.getDirIdAndTime(conn, dir.name, dir.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -369,8 +370,9 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -407,8 +409,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -437,8 +439,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -473,8 +475,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -499,8 +501,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
Config.Client.MetaFile.markdown = false;
Config.Client.MetaFile.pg2conf = false;
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
delete parent.metaFile;
DirectoryDTOUtils.removeReferences(selected);
@ -530,8 +532,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(subDir) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, subDir.name, subDir.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, subDir.name, subDir.path)).id);
// subDir.isPartial = true;
// delete subDir.directories;
@ -570,8 +572,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await Promise.all([s1, s2, s3]);
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -595,8 +597,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
const conn = await SQLConnection.getConnection();
const selected = await gm.selectParentDir(conn, parent.name, parent.path);
await gm.fillParentDir(conn, selected);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
DirectoryDTOUtils.removeReferences(selected);
removeIds(selected);
@ -604,7 +606,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
.to.deep.equal(Utils.removeNullOrEmptyObj(indexifyReturn(parent)));
await im.resetDB();
const selectReset = await gm.selectParentDir(conn, parent.name, parent.path);
const selectReset = await gm.getDirIdAndTime(conn, parent.name, parent.path);
expect(selectReset).to.deep.equal(null);
});
@ -627,8 +629,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
DirectoryDTOUtils.removeReferences(parent);
await im.saveToDB(subDir as ParentDirectoryDTO);
const selected = await gm.selectParentDir(conn, subDir.name, subDir.path);
const selected = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, subDir.name, subDir.path)).id);
expect(selected.media.length).to.equal(subDir.media.length);
}) as any).timeout(40000);
@ -661,11 +663,11 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
// @ts-ignore
fs.statSync = () => ({ctime: new Date(dirTime), mtime: new Date(dirTime)});
const gm = new GalleryManagerTest();
gm.selectParentDir = (connection: Connection, directoryName: string, directoryParent: string) => {
gm.getDirIdAndTime = () => {
return Promise.resolve(indexedTime as any);
};
gm.fillParentDir = (connection: Connection, dir: DirectoryEntity) => {
return Promise.resolve();
gm.getParentDirFromId = ():Promise<ParentDirectoryDTO> => {
return Promise.resolve(indexedTime) as unknown as Promise<ParentDirectoryDTO>;
};
ObjectManagers.getInstance().IndexingManager.indexDirectory = (...args) => {

View File

@ -4,7 +4,6 @@ import {DBTestHelper} from '../../../DBTestHelper';
import {TestHelper} from '../../../../TestHelper';
import {PhotoDTO} from '../../../../../src/common/entities/PhotoDTO';
import {Utils} from '../../../../../src/common/Utils';
import {PersonWithSampleRegion} from '../../../../../src/common/entities/PersonDTO';
import {ParentDirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
import {VideoDTO} from '../../../../../src/common/entities/VideoDTO';
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
@ -18,6 +17,7 @@ declare const before: any;
declare const it: any;
// eslint-disable-next-line prefer-const
describe = DBTestHelper.describe();
describe('PersonManager', (sqlHelper: DBTestHelper) => {
@ -30,7 +30,7 @@ describe('PersonManager', (sqlHelper: DBTestHelper) => {
let p2: PhotoDTO;
let pFaceLess: PhotoDTO;
let savedPerson: PersonWithSampleRegion[] = [];
let savedPerson: PersonEntry[] = [];
const setUpSqlDB = async () => {
await sqlHelper.initDB();
@ -73,7 +73,7 @@ describe('PersonManager', (sqlHelper: DBTestHelper) => {
person.count = 1;
expect(selected).to.deep.equal(person);
expect((await pm.get('Boba Fett') as PersonWithSampleRegion).sampleRegion.media.name).to.deep.equal(p.name);
expect((await pm.get('Boba Fett') as PersonEntry).sampleRegion.media.name).to.deep.equal(p.name);
});

View File

@ -17,7 +17,9 @@ import {Utils} from '../../../../../src/common/Utils';
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const chai = require('chai');
chai.use(deepEqualInAnyOrder);
@ -48,12 +50,12 @@ class SearchManagerTest extends SearchManager {
class GalleryManagerTest extends GalleryManager {
public async selectParentDir(connection: Connection, directoryName: string, directoryParent: string): Promise<ParentDirectoryDTO> {
return super.selectParentDir(connection, directoryName, directoryParent);
public async getDirIdAndTime(connection: Connection, directoryName: string, directoryParent: string) {
return super.getDirIdAndTime(connection, directoryName, directoryParent);
}
public async fillParentDir(connection: Connection, dir: ParentDirectoryDTO): Promise<void> {
return super.fillParentDir(connection, dir);
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
return super.getParentDirFromId(connection, dir);
}
}
@ -66,7 +68,6 @@ describe('PreviewManager', (sqlHelper: DBTestHelper) => {
* |- v
* |- p
* |- p2
* |- gpx
* |-> subDir2
* |- p4
*/
@ -79,7 +80,6 @@ describe('PreviewManager', (sqlHelper: DBTestHelper) => {
let p2: PhotoDTO;
let pFaceLess: PhotoDTO;
let p4: PhotoDTO;
let gpx: FileDTO;
const setUpTestGallery = async (): Promise<void> => {
@ -94,7 +94,6 @@ describe('PreviewManager', (sqlHelper: DBTestHelper) => {
p2.metadata.creationDate = 20000;
v = TestHelper.getVideoEntry1(subDir);
v.metadata.creationDate = 500;
gpx = TestHelper.getRandomizedGPXEntry(subDir);
const pFaceLessTmp = TestHelper.getPhotoEntry3(subDir);
pFaceLessTmp.metadata.rating = 0;
pFaceLessTmp.metadata.creationDate = 400000;
@ -108,11 +107,15 @@ describe('PreviewManager', (sqlHelper: DBTestHelper) => {
subDir = dir.directories[0];
subDir2 = dir.directories[1];
p = (subDir.media.filter(m => m.name === p.name)[0] as any);
p.directory = subDir;
p2 = (subDir.media.filter(m => m.name === p2.name)[0] as any);
gpx = (subDir.metaFile[0] as any);
p2.directory = subDir;
v = (subDir.media.filter(m => m.name === v.name)[0] as any);
v.directory = subDir;
pFaceLess = (subDir.media.filter(m => m.name === pFaceLessTmp.name)[0] as any);
pFaceLess.directory = subDir;
p4 = (subDir2.media[0] as any);
p4.directory = subDir2;
};
const setUpSqlDB = async () => {
@ -274,8 +277,9 @@ describe('PreviewManager', (sqlHelper: DBTestHelper) => {
.set({validPreview: false, preview: null}).execute();
expect((await selectDir()).preview).to.equal(null);
const res = await gm.selectParentDir(conn, dir.name, dir.path);
await gm.fillParentDir(conn, res);
const res = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, dir.name, dir.path)).id);
subdir = await selectDir();
expect(subdir.validPreview).to.equal(true);
expect(subdir.preview.id).to.equal(p2.id);

View File

@ -34,7 +34,9 @@ import {Config} from '../../../../../src/common/config/private/Config';
import {SearchQueryParser} from '../../../../../src/common/SearchQueryParser';
import {FileDTO} from '../../../../../src/common/entities/FileDTO';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const chai = require('chai');
chai.use(deepEqualInAnyOrder);
@ -65,12 +67,12 @@ class SearchManagerTest extends SearchManager {
class GalleryManagerTest extends GalleryManager {
public async selectParentDir(connection: Connection, directoryName: string, directoryParent: string): Promise<ParentDirectoryDTO> {
return super.selectParentDir(connection, directoryName, directoryParent);
public async getDirIdAndTime(connection: Connection, directoryName: string, directoryParent: string) {
return super.getDirIdAndTime(connection, directoryName, directoryParent);
}
public async fillParentDir(connection: Connection, dir: ParentDirectoryDTO): Promise<void> {
return super.fillParentDir(connection, dir);
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
return super.getParentDirFromId(connection, dir);
}
}
@ -116,11 +118,17 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
subDir = dir.directories[0];
subDir2 = dir.directories[1];
p = (dir.media.filter(m => m.name === p.name)[0] as any);
p.directory = dir;
p2 = (dir.media.filter(m => m.name === p2.name)[0] as any);
p2.directory = dir;
gpx = (dir.metaFile[0] as any);
gpx.directory = dir;
v = (dir.media.filter(m => m.name === v.name)[0] as any);
v.directory = dir;
p4 = (dir.directories[1].media[0] as any);
p4.directory = dir.directories[1];
pFaceLess = (dir.directories[0].media[0] as any);
pFaceLess.directory = dir.directories[0];
};
const setUpSqlDB = async () => {
@ -1149,7 +1157,7 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
});
/**
* flattenSameOfQueries converts converts some-of querries to AND and OR queries
* flattenSameOfQueries converts some-of queries to AND and OR queries
* E.g.:
* 2-of:(A B C) to (A and (B or C)) or (B and C)
* this tests makes sure that all queries has at least 2 constraints
@ -1169,13 +1177,8 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
}
return depth;
}
// its an or
// it's an OR
const lengths = (q as SearchListQuery).list.map(l => shortestDepth(l)).sort();
if (lengths[0] !== lengths[lengths.length - 1]) {
for (const l of (q as SearchListQuery).list) {
}
}
return lengths[0];
}
return 1;