diff --git a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts index af897e77..49490c80 100644 --- a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts +++ b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts @@ -14,6 +14,7 @@ import {ServerTime} from '../ServerTimingMWs'; export class ThumbnailGeneratorMWs { + private static ThumbnailMap: { [key: number]: number }; @ServerTime('2.th', 'Thumbnail decoration') public static async addThumbnailInformation(req: Request, res: Response, next: NextFunction): Promise { @@ -67,7 +68,7 @@ export class ThumbnailGeneratorMWs { // generate thumbnail path const thPath = PhotoProcessing.generatePersonThumbnailPath(mediaPath, item.sampleRegion, size); - item.readyThumbnail = fs.existsSync(thPath); + item.missingThumbnail = !fs.existsSync(thPath); } } catch (error) { @@ -148,6 +149,7 @@ export class ThumbnailGeneratorMWs { private static addThInfoTODir(directory: ParentDirectoryDTO | SubDirectoryDTO): void { + ThumbnailGeneratorMWs.ThumbnailMap = Config.Client.Media.Thumbnail.generateThumbnailMap(); if (typeof directory.media !== 'undefined') { ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media); } @@ -164,20 +166,16 @@ export class ThumbnailGeneratorMWs { private static addThInfoToAPhoto(photo: MediaDTO): void { const fullMediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name); - for (const size of Config.Client.Media.Thumbnail.thumbnailSizes) { - const thPath = PhotoProcessing.generateConvertedPath(fullMediaPath, size); - if (fs.existsSync(thPath) === true) { - if (typeof photo.readyThumbnails === 'undefined') { - photo.readyThumbnails = []; + for (const size of Object.keys(ThumbnailGeneratorMWs.ThumbnailMap)) { + const thPath = PhotoProcessing.generateConvertedPath(fullMediaPath, size as any); + if (fs.existsSync(thPath) !== true) { + if (typeof photo.missingThumbnails === 'undefined') { + photo.missingThumbnails = 0; } - photo.readyThumbnails.push(size); + // this is a bitwise operation + photo.missingThumbnails += ThumbnailGeneratorMWs.ThumbnailMap[size as any]; } } - const iconPath = PhotoProcessing.generateConvertedPath(fullMediaPath, Config.Client.Media.Thumbnail.iconSize); - if (fs.existsSync(iconPath) === true) { - photo.readyIcon = true; - } - } } diff --git a/src/backend/model/database/sql/GalleryManager.ts b/src/backend/model/database/sql/GalleryManager.ts index 561d410b..a436ef65 100644 --- a/src/backend/model/database/sql/GalleryManager.ts +++ b/src/backend/model/database/sql/GalleryManager.ts @@ -229,10 +229,6 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { dir.media = []; dir.isPartial = true; - if (dir.preview) { - dir.preview.readyThumbnails = []; - dir.preview.readyIcon = false; - } } @@ -282,8 +278,6 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { .getMany(); for (const item of dir.media) { item.directory = dir; - item.readyThumbnails = []; - item.readyIcon = false; (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})); diff --git a/src/backend/model/database/sql/IndexingManager.ts b/src/backend/model/database/sql/IndexingManager.ts index be6a92a3..fa4ef181 100644 --- a/src/backend/model/database/sql/IndexingManager.ts +++ b/src/backend/model/database/sql/IndexingManager.ts @@ -61,13 +61,6 @@ export class IndexingManager implements IIndexingManager { try { const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName); - // returning with the result - if (scannedDirectory.preview) { - scannedDirectory.preview.readyThumbnails = []; - } - scannedDirectory.media.forEach((p): any[] => p.readyThumbnails = []); - - const dirClone = Utils.shallowClone(scannedDirectory); // filter server side only config from returning dirClone.metaFile = dirClone.metaFile.filter(m => !ServerPG2ConfMap[m.name]); diff --git a/src/backend/model/database/sql/enitites/MediaEntity.ts b/src/backend/model/database/sql/enitites/MediaEntity.ts index b7a3aff4..fd8b2868 100644 --- a/src/backend/model/database/sql/enitites/MediaEntity.ts +++ b/src/backend/model/database/sql/enitites/MediaEntity.ts @@ -167,8 +167,5 @@ export abstract class MediaEntity implements MediaDTO { @Column(type => MediaMetadataEntity) metadata: MediaMetadataEntity; - readyThumbnails: number[] = []; - - readyIcon = false; - + missingThumbnails: number; } diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts index 700a5596..dfbb3a17 100644 --- a/src/common/config/public/ClientConfig.ts +++ b/src/common/config/public/ClientConfig.ts @@ -103,6 +103,17 @@ export class ClientThumbnailConfig { thumbnailSizes: number[] = [240, 480]; @ConfigProperty({volatile: true}) concurrentThumbnailGenerations: number = 1; + + /** + * Generates a map for bitwise operation from icon and normal thumbnails + */ + generateThumbnailMap(): { [key: number]: number } { + const m: { [key: number]: number } = {}; + [this.iconSize, ...this.thumbnailSizes.sort()].forEach((v, i) => { + m[v] = Math.pow(2, i + 1); + }); + return m; + } } @SubConfigClass() diff --git a/src/common/entities/MediaDTO.ts b/src/common/entities/MediaDTO.ts index 9ba84f37..101f19d5 100644 --- a/src/common/entities/MediaDTO.ts +++ b/src/common/entities/MediaDTO.ts @@ -9,9 +9,7 @@ export interface MediaDTO extends FileDTO { name: string; directory: DirectoryPathDTO; metadata: MediaMetadata; - readyThumbnails: number[]; - readyIcon: boolean; - + missingThumbnails?: number; } diff --git a/src/common/entities/PersonDTO.ts b/src/common/entities/PersonDTO.ts index 7110a59b..8cc3f599 100644 --- a/src/common/entities/PersonDTO.ts +++ b/src/common/entities/PersonDTO.ts @@ -8,7 +8,7 @@ export interface PersonDTO { id: number; name: string; count: number; - readyThumbnail?: boolean; + missingThumbnail?: boolean; isFavourite: boolean; } diff --git a/src/common/entities/PhotoDTO.ts b/src/common/entities/PhotoDTO.ts index 62b544c2..3b801cd0 100644 --- a/src/common/entities/PhotoDTO.ts +++ b/src/common/entities/PhotoDTO.ts @@ -5,8 +5,6 @@ import {MediaDimension, MediaDTO, MediaMetadata} from './MediaDTO'; export interface PreviewPhotoDTO extends MediaDTO { name: string; directory: DirectoryPathDTO; - readyThumbnails: number[]; - readyIcon: boolean; } export interface PhotoDTO extends PreviewPhotoDTO, MediaDTO { @@ -14,8 +12,7 @@ export interface PhotoDTO extends PreviewPhotoDTO, MediaDTO { name: string; directory: DirectoryPathDTO; metadata: PhotoMetadata; - readyThumbnails: number[]; - readyIcon: boolean; + missingThumbnails?: number; } export interface FaceRegionBox { diff --git a/src/frontend/app/ui/gallery/Media.ts b/src/frontend/app/ui/gallery/Media.ts index b4765862..314325c8 100644 --- a/src/frontend/app/ui/gallery/Media.ts +++ b/src/frontend/app/ui/gallery/Media.ts @@ -14,9 +14,13 @@ export class Media extends MediaIcon { thumbnailLoaded(): void { + console.log(this.media.name, this.media.missingThumbnails); if (!this.isThumbnailAvailable()) { - this.media.readyThumbnails = this.media.readyThumbnails || []; - this.media.readyThumbnails.push(this.getThumbnailSize()); + this.media.missingThumbnails = this.media.missingThumbnails || 0; + this.media.missingThumbnails -= MediaIcon.ThumbnailMap[this.getThumbnailSize()]; + if (this.media.missingThumbnails < 0) { + throw new Error('missingThumbnails got below 0'); + } } } @@ -31,10 +35,12 @@ export class Media extends MediaIcon { this.replacementSizeCache = null; const size = this.getThumbnailSize(); - if (!!this.media.readyThumbnails) { - for (const item of this.media.readyThumbnails) { - if (item < size) { - this.replacementSizeCache = item; + if (!!this.media.missingThumbnails) { + for (const thSize of Config.Client.Media.Thumbnail.thumbnailSizes) { + // tslint:disable-next-line:no-bitwise + if ((this.media.missingThumbnails & MediaIcon.ThumbnailMap[thSize]) === 0 && + thSize < size) { + this.replacementSizeCache = thSize; break; } } @@ -48,7 +54,8 @@ export class Media extends MediaIcon { } isThumbnailAvailable(): boolean { - return this.media.readyThumbnails && this.media.readyThumbnails.indexOf(this.getThumbnailSize()) !== -1; + // tslint:disable-next-line:no-bitwise + return (this.media.missingThumbnails & MediaIcon.ThumbnailMap[this.getThumbnailSize()]) === 0; } getReplacementThumbnailPath(): string { diff --git a/src/frontend/app/ui/gallery/MediaIcon.ts b/src/frontend/app/ui/gallery/MediaIcon.ts index 8c292f6d..1dc80c50 100644 --- a/src/frontend/app/ui/gallery/MediaIcon.ts +++ b/src/frontend/app/ui/gallery/MediaIcon.ts @@ -3,7 +3,7 @@ import {Config} from '../../../../common/config/public/Config'; import {MediaDTO} from '../../../../common/entities/MediaDTO'; export class MediaIcon { - + protected static readonly ThumbnailMap = Config.Client.Media.Thumbnail.generateThumbnailMap(); protected replacementSizeCache: number | boolean = false; @@ -16,11 +16,12 @@ export class MediaIcon { } iconLoaded(): void { - this.media.readyIcon = true; + this.media.missingThumbnails -= MediaIcon.ThumbnailMap[Config.Client.Media.Thumbnail.iconSize]; } isIconAvailable(): boolean { - return this.media.readyIcon; + // tslint:disable-next-line:no-bitwise + return (this.media.missingThumbnails & MediaIcon.ThumbnailMap[Config.Client.Media.Thumbnail.iconSize]) === 0; } getRelativePath(): string { diff --git a/src/frontend/app/ui/gallery/cache.gallery.service.ts b/src/frontend/app/ui/gallery/cache.gallery.service.ts index 0f3af091..ddbd77e6 100644 --- a/src/frontend/app/ui/gallery/cache.gallery.service.ts +++ b/src/frontend/app/ui/gallery/cache.gallery.service.ts @@ -244,24 +244,27 @@ export class GalleryCacheService { * @param media: MediaBaseDTO */ public mediaUpdated(media: MediaDTO): void { - if (Config.Client.Other.enableCache === false) { return; } try { - const directoryName = Utils.concatUrls(media.directory.path, media.directory.name); - const value = localStorage.getItem(directoryName); + const directoryKey = GalleryCacheService.CONTENT_PREFIX + Utils.concatUrls(media.directory.path, media.directory.name); + const value = localStorage.getItem(directoryKey); if (value != null) { const directory: ParentDirectoryDTO = JSON.parse(value); directory.media.forEach((p) => { if (p.name === media.name) { // update data p.metadata = media.metadata; - p.readyThumbnails = media.readyThumbnails; + if (media.missingThumbnails) { + p.missingThumbnails = media.missingThumbnails; + } else { + delete p.missingThumbnails; + } // save changes - localStorage.setItem(directoryName, JSON.stringify(directory)); + localStorage.setItem(directoryKey, JSON.stringify(directory)); return; } }); diff --git a/src/frontend/app/ui/gallery/thumbnailManager.service.ts b/src/frontend/app/ui/gallery/thumbnailManager.service.ts index d8e50b20..0a03e05f 100644 --- a/src/frontend/app/ui/gallery/thumbnailManager.service.ts +++ b/src/frontend/app/ui/gallery/thumbnailManager.service.ts @@ -83,7 +83,7 @@ export class PersonThumbnail extends ThumbnailBase { super(thumbnailService); this.src = ''; this.error = false; - if (this.person.readyThumbnail) { + if (!this.person.missingThumbnail) { this.src = Person.getThumbnailUrl(person); this.available = true; if (this.onLoad) { diff --git a/test/backend/unit/model/sql/SearchManager.spec.ts b/test/backend/unit/model/sql/SearchManager.spec.ts index abb6b29e..a3c51e29 100644 --- a/test/backend/unit/model/sql/SearchManager.spec.ts +++ b/test/backend/unit/model/sql/SearchManager.spec.ts @@ -1240,9 +1240,7 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => { name: subDir.name, path: subDir.path }, - name: pFaceLess.name, - readyIcon: false, - readyThumbnails: [] + name: pFaceLess.name } as any; const query = { text: subDir.name, diff --git a/test/backend/unit/model/sql/TestHelper.ts b/test/backend/unit/model/sql/TestHelper.ts index c16bb1a9..eda23fbd 100644 --- a/test/backend/unit/model/sql/TestHelper.ts +++ b/test/backend/unit/model/sql/TestHelper.ts @@ -354,9 +354,7 @@ export class TestHelper { id: null, name: rndStr() + '.jpg', directory: dir, - metadata: m, - readyThumbnails: [], - readyIcon: false + metadata: m }; for (let i = 0; i < faces; i++) {