1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-02-01 13:17:55 +02:00

Improving preiview handling (fixing DB case insensitive issue when selecting preview that is on the same path, adding tests) #31

This commit is contained in:
Patrik J. Braun 2021-03-28 12:43:13 +02:00
parent 09e2a07c5e
commit 5020949a88
9 changed files with 148 additions and 39 deletions

View File

@ -18,6 +18,7 @@ import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {ObjectManagers} from '../../ObjectManagers';
import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import DatabaseType = ServerConfig.DatabaseType;
const LOG_TAG = '[GalleryManager]';
@ -202,7 +203,8 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
path: directoryParent
})
.leftJoinAndSelect('directory.directories', 'directories')
.leftJoinAndSelect('directory.media', 'media');
.leftJoinAndSelect('directory.media', 'media')
.orderBy('media.metadata.creationDate', 'DESC');
if (Config.Client.MetaFile.enabled === true) {
query.leftJoinAndSelect('directory.metaFile', 'metaFile');
@ -211,6 +213,33 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
return await query.getOne();
}
protected async fillPreviewFromSubDir(connection: Connection, dir: DirectoryEntity): Promise<void> {
dir.media = [];
const query = connection
.getRepository(MediaEntity)
.createQueryBuilder('media')
.innerJoinAndSelect('media.directory', 'directory');
if (Config.Server.Database.type === DatabaseType.mysql) {
query.where('directory.path like :path || \'%\'', {
path: (DiskMangerWorker.pathFromParent(dir))
});
} else {
query.where('directory.path GLOB :path', {
path: DiskMangerWorker.pathFromParent(dir) + '*'
});
}
dir.preview = await query.orderBy('media.metadata.creationDate', 'DESC')
.limit(1)
.getOne();
if (dir.preview) {
dir.preview.directory = dir;
dir.preview.readyThumbnails = [];
dir.preview.readyIcon = false;
}
}
protected async fillParentDir(connection: Connection, dir: DirectoryEntity): Promise<void> {
if (dir.media) {
const indexedFaces = await connection.getRepository(FaceRegionEntry)
@ -232,14 +261,15 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
.filter(fe => fe.media.id === dir.media[i].id)
.map(f => ({box: f.box, name: f.person.name}));
}
if (dir.media.length > 0) {
dir.preview = dir.media[0];
} else {
await this.fillPreviewFromSubDir(connection, dir);
}
}
if (dir.directories) {
for (let i = 0; i < dir.directories.length; i++) {
const _path = dir.path;
const currentRoot = (_path === './' ? '' : _path);
const dirName = currentRoot + dir.name + path.sep;
dir.directories[i].media = [];
dir.directories[i].preview = await connection
.getRepository(MediaEntity)
@ -248,9 +278,6 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
.where('media.directory = :dir', {
dir: dir.directories[i].id
})
.orWhere('directory.path like :parentPath||\'%\'', {
parentPath: dirName
})
.orderBy('media.metadata.creationDate', 'DESC')
.limit(1)
.getOne();
@ -260,6 +287,8 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
dir.directories[i].preview.directory = dir.directories[i];
dir.directories[i].preview.readyThumbnails = [];
dir.directories[i].preview.readyIcon = false;
} else {
await this.fillPreviewFromSubDir(connection, dir.directories[i]);
}
}
}

View File

@ -19,6 +19,7 @@ import {PersonEntry} from './enitites/PersonEntry';
import {Utils} from '../../../../common/Utils';
import * as path from 'path';
import {ServerConfig} from '../../../../common/config/private/PrivateConfig';
import DatabaseType = ServerConfig.DatabaseType;
export class SQLConnection {

View File

@ -148,10 +148,14 @@ export class DiskMangerWorker {
if (!directory.preview) {
directory.preview = photo;
}
// add the preview photo to the list of media, so it will be saved to the DB
// and can be queried to populate previews,
// otherwise we do not return media list that is only partial
directory.media.push(photo);
if (settings.previewOnly === true) {
break;
}
directory.media.push(photo);
} else if (VideoProcessing.isVideo(fullFilePath)) {
if (Config.Client.Media.Video.enabled === false || settings.noVideo === true || settings.previewOnly === true) {

View File

@ -24,7 +24,7 @@ export class Utils {
}
static removeNullOrEmptyObj(obj: any) {
static removeNullOrEmptyObj<T extends any>(obj: T): T {
if (typeof obj !== 'object' || obj == null) {
return obj;
}

View File

@ -40,7 +40,7 @@ export module DirectoryDTO {
}
};
export const removeReferences = (dir: DirectoryDTO): void => {
export const removeReferences = (dir: DirectoryDTO): DirectoryDTO => {
if (dir.media) {
dir.media.forEach((media: MediaDTO) => {
media.directory = null;
@ -61,6 +61,8 @@ export module DirectoryDTO {
});
}
return dir;
};
export const filterPhotos = (dir: DirectoryDTO): PhotoDTO[] => {
return <PhotoDTO[]>dir.media.filter(m => MediaDTO.isPhoto(m));

View File

@ -69,7 +69,9 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => {
});
const setPartial = (dir: DirectoryDTO) => {
dir.preview = dir.media[0];
if (!dir.preview && dir.media && dir.media.length > 0) {
dir.preview = dir.media[0];
}
dir.isPartial = true;
delete dir.directories;
delete dir.metaFile;
@ -191,6 +193,62 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => {
});
it('should select preview', async () => {
const selectDirectory = async (_gm: GalleryManagerTest, dir: DirectoryDTO) => {
const conn = await SQLConnection.getConnection();
const selected = await _gm.selectParentDir(conn, dir.name, dir.path);
await _gm.fillParentDir(conn, selected);
DirectoryDTO.removeReferences(selected);
removeIds(selected);
return selected;
};
const gm = new GalleryManagerTest();
const im = new IndexingManagerTest();
const parent = TestHelper.getRandomizedDirectoryEntry(null, 'parent');
const checkParent = async () => {
const selected = await selectDirectory(gm, parent);
const cloned = Utils.removeNullOrEmptyObj(Utils.clone(parent));
if (cloned.directories) {
cloned.directories.forEach(d => setPartial(d));
}
expect(Utils.clone(Utils.removeNullOrEmptyObj(selected)))
.to.deep.equalInAnyOrder(cloned);
};
const saveToDBAndCheck = async (dir: DirectoryDTO) => {
DirectoryDTO.removeReferences(parent);
await im.saveToDB(Utils.clone(dir));
await checkParent();
DirectoryDTO.addReferences(parent);
};
await saveToDBAndCheck(parent);
const subDir1 = TestHelper.getRandomizedDirectoryEntry(parent, 'subDir');
await saveToDBAndCheck(parent);
const p1 = TestHelper.getRandomizedPhotoEntry(subDir1, 'subPhoto1', 0);
await saveToDBAndCheck(subDir1);
const subDir2 = TestHelper.getRandomizedDirectoryEntry(parent, 'subDir2');
await saveToDBAndCheck(parent);
const p2 = TestHelper.getRandomizedPhotoEntry(subDir2, 'subPhoto2', 0);
await saveToDBAndCheck(subDir2);
const p = TestHelper.getRandomizedPhotoEntry(parent, 'photo', 0);
await saveToDBAndCheck(parent);
});
it('should save parent after child', async () => {
const gm = new GalleryManagerTest();
const im = new IndexingManagerTest();

View File

@ -2,14 +2,13 @@ import {expect} from 'chai';
import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager';
import {SQLTestHelper} from '../../../SQLTestHelper';
import {TestHelper} from './TestHelper';
import {PhotoDTO, PhotoMetadata} from '../../../../../src/common/entities/PhotoDTO';
import {PhotoDTO} from '../../../../../src/common/entities/PhotoDTO';
import {Utils} from '../../../../../src/common/Utils';
import {PersonWithSampleRegion} from '../../../../../src/common/entities/PersonDTO';
import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
import {VideoDTO} from '../../../../../src/common/entities/VideoDTO';
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
import {PersonEntry} from '../../../../../src/backend/model/database/sql/enitites/PersonEntry';
import {MediaDTO} from '../../../../../src/common/entities/MediaDTO';
// to help WebStorm to handle the test cases
@ -36,17 +35,17 @@ describe('PersonManager', (sqlHelper: SQLTestHelper) => {
const setUpSqlDB = async () => {
await sqlHelper.initDB();
const directory: DirectoryDTO = TestHelper.getDirectoryEntry();
TestHelper.getPhotoEntry1(directory);
TestHelper.getPhotoEntry2(directory);
p = TestHelper.getPhotoEntry1(directory);
p2 = TestHelper.getPhotoEntry2(directory);
const pFaceLess = TestHelper.getPhotoEntry3(directory);
delete pFaceLess.metadata.faces;
TestHelper.getVideoEntry1(directory);
v = TestHelper.getVideoEntry1(directory);
dir = await SQLTestHelper.persistTestDir(directory);
p = <any>dir.media[0];
p2 = <any>dir.media[1];
p = <any>dir.media.filter(m => m.name === p.name)[0];
p2 = <any>dir.media.filter(m => m.name === p2.name)[0];
p_faceLess = <any>dir.media[2];
v = <any>dir.media[3];
v = <any>dir.media.filter(m => m.name === v.name)[0];
savedPerson = await (await SQLConnection.getConnection()).getRepository(PersonEntry).find({
relations: ['sampleRegion',
'sampleRegion.media',
@ -64,7 +63,6 @@ describe('PersonManager', (sqlHelper: SQLTestHelper) => {
});
it('should get person', async () => {
const pm = new PersonManager();
const person = Utils.clone(savedPerson[0]);

View File

@ -90,17 +90,17 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
const directory: DirectoryDTO = TestHelper.getDirectoryEntry();
const subDir = TestHelper.getDirectoryEntry(directory, 'The Phantom Menace');
const subDir2 = TestHelper.getDirectoryEntry(directory, 'Return of the Jedi');
TestHelper.getPhotoEntry1(directory);
TestHelper.getPhotoEntry2(directory);
TestHelper.getPhotoEntry4(subDir2);
p = TestHelper.getPhotoEntry1(directory);
p2 = TestHelper.getPhotoEntry2(directory);
p4 = TestHelper.getPhotoEntry4(subDir2);
const pFaceLess = TestHelper.getPhotoEntry3(subDir);
delete pFaceLess.metadata.faces;
TestHelper.getVideoEntry1(directory);
v = TestHelper.getVideoEntry1(directory);
dir = await SQLTestHelper.persistTestDir(directory);
p = <any>dir.media[0];
p2 = <any>dir.media[1];
v = <any>dir.media[2];
p = <any>dir.media.filter(m => m.name === p.name)[0];
p2 = <any>dir.media.filter(m => m.name === p2.name)[0];
v = <any>dir.media.filter(m => m.name === v.name)[0];
p4 = <any>dir.directories[1].media[0];
p_faceLess = <any>dir.directories[0].media[0];
};
@ -176,9 +176,11 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
const searchifyMedia = (m: MediaDTO): MediaDTO => {
const tmpM = m.directory.media;
const tmpD = m.directory.directories;
const tmpP = m.directory.preview;
const tmpMT = m.directory.metaFile;
delete m.directory.directories;
delete m.directory.media;
delete m.directory.preview;
delete m.directory.metaFile;
const ret = Utils.clone(m);
if ((ret.metadata as PhotoMetadata).faces && !(ret.metadata as PhotoMetadata).faces.length) {
@ -186,6 +188,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
}
m.directory.directories = tmpD;
m.directory.media = tmpM;
m.directory.preview = tmpP;
m.directory.metaFile = tmpMT;
return ret;
};
@ -946,7 +949,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
searchQuery: query,
directories: [],
media: [p, p2, p_faceLess, v],
media: [p, p2, p_faceLess, v],
metaFile: [],
resultOverflow: false
}));

View File

@ -24,6 +24,8 @@ import {DiskMangerWorker} from '../../../../../src/backend/model/threading/DiskM
export class TestHelper {
static creationCounter = 0;
public static getDirectoryEntry(parent: DirectoryDTO = null, name = 'wars dir') {
const dir = new DirectoryEntity();
@ -156,7 +158,6 @@ export class TestHelper {
return p;
}
public static getPhotoEntry2(dir: DirectoryDTO) {
const p = TestHelper.getPhotoEntry(dir);
@ -249,7 +250,6 @@ export class TestHelper {
return p;
}
public static getRandomizedDirectoryEntry(parent: DirectoryDTO = null, forceStr: string = null) {
const dir: DirectoryDTO = {
@ -263,7 +263,7 @@ export class TestHelper {
media: [],
lastModified: Date.now(),
lastScanned: null,
parent: null
parent: parent
};
if (parent !== null) {
dir.path = DiskMangerWorker.pathFromParent(parent);
@ -272,7 +272,6 @@ export class TestHelper {
return dir;
}
public static getRandomizedGPXEntry(dir: DirectoryDTO, forceStr: string = null): FileDTO {
const d: FileDTO = {
id: null,
@ -283,7 +282,6 @@ export class TestHelper {
return d;
}
public static getRandomizedFace(media: PhotoDTO, forceStr: string = null) {
const rndStr = () => {
return forceStr + '_' + Math.random().toString(36).substring(7);
@ -348,7 +346,7 @@ export class TestHelper {
cameraData: cd,
positionData: pd,
size: sd,
creationDate: Date.now(),
creationDate: Date.now() + ++TestHelper.creationCounter,
fileSize: rndInt(10000),
orientation: OrientationTypes.TOP_LEFT,
caption: rndStr(),
@ -356,7 +354,7 @@ export class TestHelper {
};
const d: PhotoDTO = {
const p: PhotoDTO = {
id: null,
name: rndStr() + '.jpg',
directory: dir,
@ -366,11 +364,27 @@ export class TestHelper {
};
for (let i = 0; i < faces; i++) {
this.getRandomizedFace(d, 'Person ' + i);
this.getRandomizedFace(p, 'Person ' + i);
}
dir.media.push(p);
TestHelper.updatePreview(dir);
return p;
}
static updatePreview(dir: DirectoryDTO) {
if (dir.media.length > 0) {
dir.preview = dir.media.sort((a, b) => b.metadata.creationDate - a.metadata.creationDate)[0];
} else {
const filtered = dir.directories.filter(d => d.preview).map(d => d.preview);
if (filtered.length > 0) {
dir.preview = filtered.sort((a, b) => b.metadata.creationDate - a.metadata.creationDate)[0];
}
}
if (dir.parent) {
TestHelper.updatePreview(dir.parent);
}
dir.media.push(d);
return d;
}