From 597d179f7d7c1c18ffc2a1a5092606e7e67bbe58 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Mon, 31 May 2021 19:55:27 +0200 Subject: [PATCH] Adding count and precalculated preview to saved search #45 --- .../database/interfaces/IAlbumManager.ts | 5 +++ .../model/database/sql/AlbumManager.ts | 45 +++++++++++++------ .../model/database/sql/ISearchManager.ts | 1 + .../model/database/sql/IndexingManager.ts | 1 + .../model/database/sql/SearchManager.ts | 12 +++++ .../sql/enitites/album/AlbumBaseEntity.ts | 7 ++- src/common/DataStructureVersion.ts | 2 +- src/common/entities/album/AlbumBaseDTO.ts | 1 + src/common/entities/album/SavedSearchDTO.ts | 1 + .../app/ui/albums/album/album.component.html | 2 +- .../unit/model/sql/AlbumManager.spec.ts | 6 +++ .../unit/model/sql/IndexingManager.spec.ts | 8 ++-- 12 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/backend/model/database/interfaces/IAlbumManager.ts b/src/backend/model/database/interfaces/IAlbumManager.ts index a4d940b5..f031c10f 100644 --- a/src/backend/model/database/interfaces/IAlbumManager.ts +++ b/src/backend/model/database/interfaces/IAlbumManager.ts @@ -23,4 +23,9 @@ export interface IAlbumManager { * Returns with all albums */ getAlbums(): Promise; + + /** + * Updates previews and album counts + */ + onGalleryIndexUpdate(): Promise; } diff --git a/src/backend/model/database/sql/AlbumManager.ts b/src/backend/model/database/sql/AlbumManager.ts index aa2f28a5..e1dc5280 100644 --- a/src/backend/model/database/sql/AlbumManager.ts +++ b/src/backend/model/database/sql/AlbumManager.ts @@ -9,14 +9,6 @@ import {SavedSearchEntity} from './enitites/album/SavedSearchEntity'; import {IAlbumManager} from '../interfaces/IAlbumManager'; export class AlbumManager implements IAlbumManager { - private static async fillPreviewToAlbum(album: AlbumBaseDTO): Promise { - if (!(album as SavedSearchDTO).searchQuery) { - throw new Error('no search query present'); - } - album.preview = await (ObjectManagers.getInstance().SearchManager as ISQLSearchManager) - .getPreview((album as SavedSearchDTO).searchQuery); - } - public async addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum: boolean): Promise { const connection = await SQLConnection.getConnection(); const album = await connection.getRepository(SavedSearchEntity) @@ -24,12 +16,13 @@ export class AlbumManager implements IAlbumManager { if (album) { return; } - this.addSavedSearch(name, searchQuery, lockedAlbum); + await this.addSavedSearch(name, searchQuery, lockedAlbum); } public async addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise { const connection = await SQLConnection.getConnection(); - await connection.getRepository(SavedSearchEntity).insert({name, searchQuery, locked: lockedAlbum}); + const a = await connection.getRepository(SavedSearchEntity).save({name, searchQuery, locked: lockedAlbum}); + await this.updateAlbum(a); } public async deleteAlbum(id: number): Promise { @@ -46,12 +39,36 @@ export class AlbumManager implements IAlbumManager { public async getAlbums(): Promise { const connection = await SQLConnection.getConnection(); - const albums = await connection.getRepository(AlbumBaseEntity).find(); + return await connection.getRepository(AlbumBaseEntity).find({ + relations: ['preview', 'preview.directory'] + }); + } + + public async onGalleryIndexUpdate(): Promise { + await this.updateAlbums(); + } + + + private async updateAlbums(): Promise { + const albums = await this.getAlbums(); for (const a of albums) { - await AlbumManager.fillPreviewToAlbum(a); + await this.updateAlbum(a as SavedSearchEntity); } - - return albums; } + + private async updateAlbum(album: SavedSearchEntity): Promise { + const connection = await SQLConnection.getConnection(); + const preview = await (ObjectManagers.getInstance().SearchManager as ISQLSearchManager) + .getPreview((album as SavedSearchDTO).searchQuery); + const count = await (ObjectManagers.getInstance().SearchManager as ISQLSearchManager) + .getCount((album as SavedSearchDTO).searchQuery); + await connection + .createQueryBuilder() + .update(AlbumBaseEntity) + .set({preview, count}) + .where('id = :id', {id: album.id}) + .execute(); + } + } diff --git a/src/backend/model/database/sql/ISearchManager.ts b/src/backend/model/database/sql/ISearchManager.ts index 0bb1ee62..edac9a42 100644 --- a/src/backend/model/database/sql/ISearchManager.ts +++ b/src/backend/model/database/sql/ISearchManager.ts @@ -14,4 +14,5 @@ export interface ISQLSearchManager extends ISearchManager { // "Protected" functions. only called from other Managers, not from middlewares getPreview(query: SearchQueryDTO): Promise; + getCount(query: SearchQueryDTO): Promise; } diff --git a/src/backend/model/database/sql/IndexingManager.ts b/src/backend/model/database/sql/IndexingManager.ts index 779e33af..66672deb 100644 --- a/src/backend/model/database/sql/IndexingManager.ts +++ b/src/backend/model/database/sql/IndexingManager.ts @@ -370,6 +370,7 @@ export class IndexingManager implements IIndexingManager { await this.saveMedia(connection, currentDirId, scannedDirectory.media); await this.saveMetaFiles(connection, currentDirId, scannedDirectory); await ObjectManagers.getInstance().PersonManager.onGalleryIndexUpdate(); + await ObjectManagers.getInstance().AlbumManager.onGalleryIndexUpdate(); await ObjectManagers.getInstance().VersionManager.updateDataVersion(); await IndexingManager.processServerSidePG2Conf(serverSideConfigs); } finally { diff --git a/src/backend/model/database/sql/SearchManager.ts b/src/backend/model/database/sql/SearchManager.ts index d2dea7f2..8af49aa5 100644 --- a/src/backend/model/database/sql/SearchManager.ts +++ b/src/backend/model/database/sql/SearchManager.ts @@ -237,6 +237,18 @@ export class SearchManager implements ISQLSearchManager { .getOne(); } + public async getCount(queryIN: SearchQueryDTO): Promise { + const query = await this.prepareQuery(queryIN); + const connection = await SQLConnection.getConnection(); + + return await connection + .getRepository(MediaEntity) + .createQueryBuilder('media') + .innerJoin('media.directory', 'directory') + .where(this.buildWhereQuery(query)) + .getCount(); + } + protected flattenSameOfQueries(query: SearchQueryDTO): SearchQueryDTO { switch (query.type) { case SearchQueryTypes.AND: diff --git a/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts b/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts index 6b482a9f..34407c05 100644 --- a/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts +++ b/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts @@ -1,4 +1,4 @@ -import {Column, Entity, Index, PrimaryGeneratedColumn, TableInheritance} from 'typeorm'; +import {Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, TableInheritance} from 'typeorm'; import {MediaEntity} from '../MediaEntity'; import {columnCharsetCS} from '../EntityUtils'; import {AlbumBaseDTO} from '../../../../../../common/entities/album/AlbumBaseDTO'; @@ -21,7 +21,10 @@ export class AlbumBaseEntity implements AlbumBaseDTO { @Column({default: false}) locked: boolean; - // not saving to database, it is only assigned when querying the DB + @Column('int', {unsigned: true, default: 0}) + count: number; + + @ManyToOne(type => MediaEntity, {onDelete: 'SET NULL', nullable: true}) public preview: MediaEntity; } diff --git a/src/common/DataStructureVersion.ts b/src/common/DataStructureVersion.ts index 8d287564..f6fd23c7 100644 --- a/src/common/DataStructureVersion.ts +++ b/src/common/DataStructureVersion.ts @@ -1,4 +1,4 @@ /** * This version indicates that the SQL sql/entities/*Entity.ts files got changed and the db needs to be recreated */ -export const DataStructureVersion = 24; +export const DataStructureVersion = 25; diff --git a/src/common/entities/album/AlbumBaseDTO.ts b/src/common/entities/album/AlbumBaseDTO.ts index 57ee5c11..94c331c0 100644 --- a/src/common/entities/album/AlbumBaseDTO.ts +++ b/src/common/entities/album/AlbumBaseDTO.ts @@ -4,5 +4,6 @@ export interface AlbumBaseDTO { id: number; name: string; preview?: PreviewPhotoDTO; + count: number; locked: boolean; } diff --git a/src/common/entities/album/SavedSearchDTO.ts b/src/common/entities/album/SavedSearchDTO.ts index 2300130f..78d12418 100644 --- a/src/common/entities/album/SavedSearchDTO.ts +++ b/src/common/entities/album/SavedSearchDTO.ts @@ -6,6 +6,7 @@ export interface SavedSearchDTO extends AlbumBaseDTO { id: number; name: string; preview?: PreviewPhotoDTO; + count: number; locked: boolean; searchQuery: SearchQueryDTO; diff --git a/src/frontend/app/ui/albums/album/album.component.html b/src/frontend/app/ui/albums/album/album.component.html index 73fbc185..7f6b08c3 100644 --- a/src/frontend/app/ui/albums/album/album.component.html +++ b/src/frontend/app/ui/albums/album/album.component.html @@ -18,7 +18,7 @@
- {{album.name}} + {{album.name}} ({{album.count}}) diff --git a/test/backend/unit/model/sql/AlbumManager.spec.ts b/test/backend/unit/model/sql/AlbumManager.spec.ts index 39da8889..2251fce7 100644 --- a/test/backend/unit/model/sql/AlbumManager.spec.ts +++ b/test/backend/unit/model/sql/AlbumManager.spec.ts @@ -119,6 +119,7 @@ describe('AlbumManager', (sqlHelper: DBTestHelper) => { id: 1, name: 'Test Album', locked: false, + count: 0, searchQuery: query } as SavedSearchDTO]); }); @@ -138,12 +139,14 @@ describe('AlbumManager', (sqlHelper: DBTestHelper) => { id: 1, name: 'Test Album', locked: false, + count: 0, searchQuery: query } as SavedSearchDTO, { id: 2, name: 'Test Album2', locked: true, + count: 0, searchQuery: query } as SavedSearchDTO]); @@ -152,6 +155,7 @@ describe('AlbumManager', (sqlHelper: DBTestHelper) => { id: 2, name: 'Test Album2', locked: true, + count: 0, searchQuery: query } as SavedSearchDTO]); @@ -165,6 +169,7 @@ describe('AlbumManager', (sqlHelper: DBTestHelper) => { id: 2, name: 'Test Album2', locked: true, + count: 0, searchQuery: query } as SavedSearchDTO]); }); @@ -182,6 +187,7 @@ describe('AlbumManager', (sqlHelper: DBTestHelper) => { name: 'Test Album', searchQuery: query, locked: false, + count: 1, preview: toAlbumPreview(p) } as SavedSearchDTO])); diff --git a/test/backend/unit/model/sql/IndexingManager.spec.ts b/test/backend/unit/model/sql/IndexingManager.spec.ts index 806859b7..b3378390 100644 --- a/test/backend/unit/model/sql/IndexingManager.spec.ts +++ b/test/backend/unit/model/sql/IndexingManager.spec.ts @@ -14,11 +14,11 @@ 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 {AlbumManager} from '../../../../../src/backend/model/database/sql/AlbumManager'; import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../../../src/common/entities/SearchQueryDTO'; import {ProjectPath} from '../../../../../src/backend/ProjectPath'; import * as path from 'path'; import {DiskManager} from '../../../../../src/backend/model/DiskManger'; +import {AlbumManager} from '../../../../../src/backend/model/database/sql/AlbumManager'; const deepEqualInAnyOrder = require('deep-equal-in-any-order'); const chai = require('chai'); @@ -47,7 +47,7 @@ class IndexingManagerTest extends IndexingManager { } public async saveToDB(scannedDirectory: DirectoryDTO): Promise { - return super.saveToDB(scannedDirectory); + return await super.saveToDB(scannedDirectory); } } @@ -591,16 +591,18 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => { const am = new AlbumManager(); const dir = await DiskManager.scanDirectory('/'); + await im.saveToDB(dir); const albums = await am.getAlbums(); - // expect(albums[0].preview).to.be.an('object'); + expect(albums[0].preview).to.be.an('object'); delete albums[0].preview; expect(albums).to.be.equalInAnyOrder([ { id: 1, name: 'Alvin', locked: true, + count: 1, searchQuery: { type: SearchQueryTypes.person, text: 'Alvin',