From c670a17e27e0e436e7c656664a6ade9bd54f76ea Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sat, 27 Mar 2021 21:31:19 +0100 Subject: [PATCH] Adding preview field to Directory and populating it with any photo from subdirectory (based on #165), fixes #31 --- src/backend/middlewares/GalleryMWs.ts | 2 +- .../thumbnail/ThumbnailGeneratorMWs.ts | 38 ++++++++++++------- src/backend/model/DiskManger.ts | 10 +---- .../model/database/sql/GalleryManager.ts | 36 +++++++----------- .../model/database/sql/IndexingManager.ts | 3 ++ .../database/sql/enitites/DirectoryEntity.ts | 2 + .../model/threading/DiskMangerWorker.ts | 28 +++++++------- src/common/config/private/PrivateConfig.ts | 2 - src/common/entities/DirectoryDTO.ts | 7 ++++ .../directory/directory.gallery.component.ts | 7 +--- .../indexing/indexing.settings.component.html | 8 ---- .../backend/unit/model/sql/IndexingManager.ts | 19 ++++++++++ test/backend/unit/model/sql/TestHelper.ts | 1 + 13 files changed, 89 insertions(+), 74 deletions(-) diff --git a/src/backend/middlewares/GalleryMWs.ts b/src/backend/middlewares/GalleryMWs.ts index ddcb70f7..13949703 100644 --- a/src/backend/middlewares/GalleryMWs.ts +++ b/src/backend/middlewares/GalleryMWs.ts @@ -86,7 +86,7 @@ export class GalleryMWs { if (cw.directory) { DirectoryDTO.removeReferences(cw.directory); - // TODO: remove when typeorm inheritance is fixed + // TODO: remove when typeorm inheritance is fixed (and handles proper inheritance) cleanUpMedia(cw.directory.media); } if (cw.searchResult) { diff --git a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts index d3047c10..f8e004df 100644 --- a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts +++ b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts @@ -31,6 +31,7 @@ export class ThumbnailGeneratorMWs { } } catch (error) { + console.error(error); return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'error during postprocessing result (adding thumbnail info)', error.toString())); } @@ -62,7 +63,8 @@ export class ThumbnailGeneratorMWs { } } catch (error) { - return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'error during postprocessing result (adding thumbnail info)', error.toString())); + return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'error during postprocessing result (adding thumbnail info for persons)', + error.toString())); } @@ -139,6 +141,9 @@ export class ThumbnailGeneratorMWs { if (typeof directory.media !== 'undefined') { ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media); } + if (directory.preview) { + ThumbnailGeneratorMWs.addThInfoToAPhoto(directory.preview); + } if (typeof directory.directories !== 'undefined') { for (let i = 0; i < directory.directories.length; i++) { ThumbnailGeneratorMWs.addThInfoTODir(directory.directories[i]); @@ -148,22 +153,27 @@ export class ThumbnailGeneratorMWs { private static addThInfoToPhotos(photos: MediaDTO[]) { for (let i = 0; i < photos.length; i++) { - const fullMediaPath = path.join(ProjectPath.ImageFolder, photos[i].directory.path, photos[i].directory.name, photos[i].name); - for (let j = 0; j < Config.Client.Media.Thumbnail.thumbnailSizes.length; j++) { - const size = Config.Client.Media.Thumbnail.thumbnailSizes[j]; - const thPath = PhotoProcessing.generateConvertedPath(fullMediaPath, size); - if (fs.existsSync(thPath) === true) { - if (typeof photos[i].readyThumbnails === 'undefined') { - photos[i].readyThumbnails = []; - } - photos[i].readyThumbnails.push(size); + this.addThInfoToAPhoto(photos[i]); + } + } + + private static addThInfoToAPhoto(photo: MediaDTO) { + const fullMediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name); + for (let j = 0; j < Config.Client.Media.Thumbnail.thumbnailSizes.length; j++) { + const size = Config.Client.Media.Thumbnail.thumbnailSizes[j]; + const thPath = PhotoProcessing.generateConvertedPath(fullMediaPath, size); + if (fs.existsSync(thPath) === true) { + if (typeof photo.readyThumbnails === 'undefined') { + photo.readyThumbnails = []; } - } - const iconPath = PhotoProcessing.generateConvertedPath(fullMediaPath, Config.Client.Media.Thumbnail.iconSize); - if (fs.existsSync(iconPath) === true) { - photos[i].readyIcon = true; + photo.readyThumbnails.push(size); } } + const iconPath = PhotoProcessing.generateConvertedPath(fullMediaPath, Config.Client.Media.Thumbnail.iconSize); + if (fs.existsSync(iconPath) === true) { + photo.readyIcon = true; + } + } } diff --git a/src/backend/model/DiskManger.ts b/src/backend/model/DiskManger.ts index 06433f35..8e29dec4 100644 --- a/src/backend/model/DiskManger.ts +++ b/src/backend/model/DiskManger.ts @@ -36,15 +36,7 @@ export class DiskManager { } else { directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings); } - const addDirs = (dir: DirectoryDTO) => { - dir.media.forEach((ph) => { - ph.directory = dir; - }); - dir.directories.forEach((d) => { - addDirs(d); - }); - }; - addDirs(directory); + DirectoryDTO.addReferences(directory); return directory; } diff --git a/src/backend/model/database/sql/GalleryManager.ts b/src/backend/model/database/sql/GalleryManager.ts index 5bccd17b..8dae59bc 100644 --- a/src/backend/model/database/sql/GalleryManager.ts +++ b/src/backend/model/database/sql/GalleryManager.ts @@ -86,7 +86,6 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { } - async countDirectories(): Promise { const connection = await SQLConnection.getConnection(); return await connection.getRepository(DirectoryEntity) @@ -103,7 +102,6 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { return sum || 0; } - async countPhotos(): Promise { const connection = await SQLConnection.getConnection(); return await connection.getRepository(PhotoEntity) @@ -238,38 +236,32 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { } if (dir.directories) { for (let i = 0; i < dir.directories.length; i++) { - const dirName = GalleryManager.getAbsoluteDirName(dir.directories[i]); - dir.directories[i].media = await connection + + 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) .createQueryBuilder('media') .innerJoinAndSelect('media.directory', 'directory') .where('media.directory = :dir', { dir: dir.directories[i].id }) - .orWhere("directory.path like :parentPath||'%'", { - parentPath: dirName + .orWhere('directory.path like :parentPath||\'%\'', { + parentPath: dirName }) .orderBy('media.metadata.creationDate', 'DESC') - .limit(Config.Server.Indexing.folderPreviewSize) - .getMany(); + .limit(1) + .getOne(); dir.directories[i].isPartial = true; - const dirs = dir.directories[i] - for (let j = 0; j < dirs.media.length; j++) { - const mediaDirName = GalleryManager.getAbsoluteDirName(dirs.media[j].directory); - const name = mediaDirName.substring(dirName.length); - dir.directories[i].media[j].name = name + dir.directories[i].media[j].name - dir.directories[i].media[j].directory = dir.directories[i]; - dir.directories[i].media[j].readyThumbnails = []; - dir.directories[i].media[j].readyIcon = false; + if (dir.directories[i].preview) { + dir.directories[i].preview.directory = dir.directories[i]; + dir.directories[i].preview.readyThumbnails = []; + dir.directories[i].preview.readyIcon = false; } } } } - - public static getAbsoluteDirName(dir: DirectoryEntity) { - const path = dir.path; - const currentRoot = (path === "./" ? "" : path) ; - return currentRoot + dir.name + "/"; - } } diff --git a/src/backend/model/database/sql/IndexingManager.ts b/src/backend/model/database/sql/IndexingManager.ts index f54fd4b1..da3210d7 100644 --- a/src/backend/model/database/sql/IndexingManager.ts +++ b/src/backend/model/database/sql/IndexingManager.ts @@ -37,6 +37,9 @@ export class IndexingManager implements IIndexingManager { const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName); // returning with the result + if (scannedDirectory.preview) { + scannedDirectory.preview.readyThumbnails = []; + } scannedDirectory.media.forEach(p => p.readyThumbnails = []); resolve(scannedDirectory); diff --git a/src/backend/model/database/sql/enitites/DirectoryEntity.ts b/src/backend/model/database/sql/enitites/DirectoryEntity.ts index 6fcb0b1b..a4dcada5 100644 --- a/src/backend/model/database/sql/enitites/DirectoryEntity.ts +++ b/src/backend/model/database/sql/enitites/DirectoryEntity.ts @@ -54,6 +54,8 @@ export class DirectoryEntity implements DirectoryDTO { @OneToMany(type => DirectoryEntity, dir => dir.parent) public directories: DirectoryEntity[]; + public preview: MediaEntity; + @OneToMany(type => MediaEntity, media => media.directory) public media: MediaEntity[]; diff --git a/src/backend/model/threading/DiskMangerWorker.ts b/src/backend/model/threading/DiskMangerWorker.ts index fb9359eb..56c3f5d2 100644 --- a/src/backend/model/threading/DiskMangerWorker.ts +++ b/src/backend/model/threading/DiskMangerWorker.ts @@ -104,6 +104,7 @@ export class DiskMangerWorker { directories: [], isPartial: false, mediaCount: 0, + preview: null, media: [], metaFile: [] }; @@ -117,7 +118,7 @@ export class DiskMangerWorker { const file = list[i]; const fullFilePath = path.normalize(path.join(absoluteDirectoryName, file)); if ((await fsp.stat(fullFilePath)).isDirectory()) { - if (settings.noDirectory === true || + if (settings.noDirectory === true || settings.previewOnly === true || await DiskMangerWorker.excludeDir(file, relativeDirectoryName, absoluteDirectoryName)) { continue; } @@ -125,11 +126,7 @@ export class DiskMangerWorker { // create preview directory const d = await DiskMangerWorker.scanDirectory(path.join(relativeDirectoryName, file), { - maxPhotos: Config.Server.Indexing.folderPreviewSize, - noMetaFile: true, - noVideo: true, - noDirectory: true, - noPhoto: settings.noChildDirPhotos || settings.noPhoto + previewOnly: true } ); @@ -141,18 +138,23 @@ export class DiskMangerWorker { if (settings.noPhoto === true) { continue; } - directory.media.push({ + + const photo = { name: file, directory: null, metadata: settings.noMetadata === true ? null : await MetadataLoader.loadPhotoMetadata(fullFilePath) - }); + }; - - if (settings.maxPhotos && directory.media.length > settings.maxPhotos) { + if (!directory.preview) { + directory.preview = 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) { + if (Config.Client.Media.Video.enabled === false || settings.noVideo === true || settings.previewOnly === true) { continue; } try { @@ -166,7 +168,7 @@ export class DiskMangerWorker { } } else if (DiskMangerWorker.isMetaFile(fullFilePath)) { - if (Config.Client.MetaFile.enabled === false || settings.noMetaFile === true) { + if (Config.Client.MetaFile.enabled === false || settings.noMetaFile === true || settings.previewOnly === true) { continue; } @@ -193,7 +195,7 @@ export class DiskMangerWorker { export namespace DiskMangerWorker { export interface DirectoryScanSettings { - maxPhotos?: number; + previewOnly?: boolean; noMetaFile?: boolean; noVideo?: boolean; noPhoto?: boolean; diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index fb32b679..aa214898 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -96,8 +96,6 @@ export module ServerConfig { @SubConfigClass() export class IndexingConfig { - @ConfigProperty() - folderPreviewSize: number = 2; @ConfigProperty() cachedFolderTimeout: number = 1000 * 60 * 60; // Do not rescans the folder if seems ok @ConfigProperty({type: ReIndexingSensitivity}) diff --git a/src/common/entities/DirectoryDTO.ts b/src/common/entities/DirectoryDTO.ts index 483bae8d..58a89e3e 100644 --- a/src/common/entities/DirectoryDTO.ts +++ b/src/common/entities/DirectoryDTO.ts @@ -12,6 +12,7 @@ export interface DirectoryDTO { parent: DirectoryDTO; mediaCount: number; directories: DirectoryDTO[]; + preview: S; media: S[]; metaFile: FileDTO[]; } @@ -22,6 +23,9 @@ export module DirectoryDTO { media.directory = dir; }); + if (dir.preview) { + dir.preview.directory = dir; + } if (dir.metaFile) { dir.metaFile.forEach((file: FileDTO) => { file.directory = dir; @@ -42,6 +46,9 @@ export module DirectoryDTO { media.directory = null; }); } + if (dir.preview) { + dir.preview.directory = null; + } if (dir.metaFile) { dir.metaFile.forEach((file: FileDTO) => { file.directory = null; diff --git a/src/frontend/app/ui/gallery/directories/directory/directory.gallery.component.ts b/src/frontend/app/ui/gallery/directories/directory/directory.gallery.component.ts index 5f9dcb44..14cc0410 100644 --- a/src/frontend/app/ui/gallery/directories/directory/directory.gallery.component.ts +++ b/src/frontend/app/ui/gallery/directories/directory/directory.gallery.component.ts @@ -28,10 +28,7 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy { } public get SamplePhoto(): MediaDTO { - if (this.directory.media.length > 0) { - return this.directory.media[0]; - } - return null; + return this.directory.preview; } getSanitizedThUrl() { @@ -59,7 +56,7 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy { } ngOnInit() { - if (this.directory.media.length > 0) { + if (this.directory.preview) { this.thumbnail = this.thumbnailService.getThumbnail(new Media(this.SamplePhoto, this.size, this.size)); } } diff --git a/src/frontend/app/ui/settings/indexing/indexing.settings.component.html b/src/frontend/app/ui/settings/indexing/indexing.settings.component.html index af6390ff..e8417727 100644 --- a/src/frontend/app/ui/settings/indexing/indexing.settings.component.html +++ b/src/frontend/app/ui/settings/indexing/indexing.settings.component.html @@ -16,14 +16,6 @@ required="true"> - - - { dir.media.forEach((media: MediaDTO) => { delete media.id; }); + if (dir.preview) { + delete dir.preview.id; + } if (dir.metaFile) { if (dir.metaFile.length === 0) { delete dir.metaFile; @@ -128,12 +131,16 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => { DirectoryDTO.removeReferences(selected); removeIds(selected); + subDir1.preview = subDir1.media[0]; subDir1.isPartial = true; delete subDir1.directories; delete subDir1.metaFile; + delete subDir1.media; + subDir2.preview = subDir2.media[0]; subDir2.isPartial = true; delete subDir2.directories; delete subDir2.metaFile; + delete subDir2.media; expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent))); }); @@ -163,8 +170,10 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => { DirectoryDTO.removeReferences(selected); removeIds(selected); subDir1.isPartial = true; + subDir1.preview = subDir1.media[0]; delete subDir1.directories; delete subDir1.metaFile; + delete subDir1.media; expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent1))); } @@ -175,8 +184,10 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => { DirectoryDTO.removeReferences(selected); removeIds(selected); subDir2.isPartial = true; + subDir2.preview = subDir2.media[0]; delete subDir2.directories; delete subDir2.metaFile; + delete subDir2.media; expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent2))); } @@ -212,8 +223,10 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => { DirectoryDTO.removeReferences(selected); removeIds(selected); subDir.isPartial = true; + subDir.preview = subDir.media[0]; delete subDir.directories; delete subDir.metaFile; + delete subDir.media; expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent))); }); @@ -248,8 +261,10 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => { DirectoryDTO.removeReferences(selected); removeIds(selected); subDir.isPartial = true; + subDir.preview = subDir.media[0]; delete subDir.directories; delete subDir.metaFile; + delete subDir.media; expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent))); }); @@ -277,8 +292,10 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => { DirectoryDTO.removeReferences(selected); removeIds(selected); subDir.isPartial = true; + subDir.preview = subDir.media[0]; delete subDir.directories; delete subDir.metaFile; + delete subDir.media; expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) .to.deep.equal(Utils.clone(Utils.removeNullOrEmptyObj(parent))); }); @@ -405,8 +422,10 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => { DirectoryDTO.removeReferences(selected); removeIds(selected); subDir.isPartial = true; + subDir.preview = subDir.media[0]; delete subDir.directories; delete subDir.metaFile; + delete subDir.media; delete sp1.metadata.faces; delete sp2.metadata.faces; expect(Utils.clone(Utils.removeNullOrEmptyObj(selected))) diff --git a/test/backend/unit/model/sql/TestHelper.ts b/test/backend/unit/model/sql/TestHelper.ts index 2eb5addf..47129ead 100644 --- a/test/backend/unit/model/sql/TestHelper.ts +++ b/test/backend/unit/model/sql/TestHelper.ts @@ -259,6 +259,7 @@ export class TestHelper { mediaCount: 0, directories: [], metaFile: [], + preview: null, media: [], lastModified: Date.now(), lastScanned: null,