diff --git a/src/backend/model/database/interfaces/IAlbumManager.ts b/src/backend/model/database/interfaces/IAlbumManager.ts index 060300b0..a4d940b5 100644 --- a/src/backend/model/database/interfaces/IAlbumManager.ts +++ b/src/backend/model/database/interfaces/IAlbumManager.ts @@ -5,7 +5,14 @@ export interface IAlbumManager { /** * Creates a saved search type of album */ - addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise; + addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise; + + + /** + * Creates a saved search type of album if the album is not yet exists + * lockAlbum: Album cannot be removed from the UI + */ + addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise; /** * Deletes an album diff --git a/src/backend/model/database/memory/AlbumManager.ts b/src/backend/model/database/memory/AlbumManager.ts index 2e58e8a0..1a934461 100644 --- a/src/backend/model/database/memory/AlbumManager.ts +++ b/src/backend/model/database/memory/AlbumManager.ts @@ -3,10 +3,12 @@ import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; import {IAlbumManager} from '../interfaces/IAlbumManager'; export class AlbumManager implements IAlbumManager { - - public async addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise { + addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise { throw new Error('not supported by memory DB'); + } + public async addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise { + throw new Error('not supported by memory DB'); } public async deleteAlbum(id: number): Promise { diff --git a/src/backend/model/database/memory/GalleryManager.ts b/src/backend/model/database/memory/GalleryManager.ts index 47bdb518..fe85e577 100644 --- a/src/backend/model/database/memory/GalleryManager.ts +++ b/src/backend/model/database/memory/GalleryManager.ts @@ -7,10 +7,11 @@ import {ProjectPath} from '../../../ProjectPath'; import {Config} from '../../../../common/config/private/Config'; import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; import {ReIndexingSensitivity} from '../../../../common/config/private/PrivateConfig'; +import {ServerPG2ConfMap} from '../../../../common/PG2ConfMap'; export class GalleryManager implements IGalleryManager { - public listDirectory(relativeDirectoryName: string, knownLastModified?: number, knownLastScanned?: number): Promise { + public async listDirectory(relativeDirectoryName: string, knownLastModified?: number, knownLastScanned?: number): Promise { // If it seems that the content did not changed, do not work on it if (knownLastModified && knownLastScanned) { const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName)); @@ -21,7 +22,9 @@ export class GalleryManager implements IGalleryManager { return Promise.resolve(null); } } - return DiskManager.scanDirectory(relativeDirectoryName); + const dir = await DiskManager.scanDirectory(relativeDirectoryName); + dir.metaFile = dir.metaFile.filter(m => !ServerPG2ConfMap[m.name]); + return dir; } } diff --git a/src/backend/model/database/sql/AlbumManager.ts b/src/backend/model/database/sql/AlbumManager.ts index dab0ec67..ec9108ef 100644 --- a/src/backend/model/database/sql/AlbumManager.ts +++ b/src/backend/model/database/sql/AlbumManager.ts @@ -6,9 +6,9 @@ import {ObjectManagers} from '../../ObjectManagers'; import {ISQLSearchManager} from './ISearchManager'; import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; import {SavedSearchEntity} from './enitites/album/SavedSearchEntity'; -import { IAlbumManager } from '../interfaces/IAlbumManager'; +import {IAlbumManager} from '../interfaces/IAlbumManager'; -export class AlbumManager implements 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'); @@ -17,15 +17,31 @@ export class AlbumManager implements IAlbumManager{ .getPreview((album as SavedSearchDTO).searchQuery); } - public async addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise { + public async addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum: boolean): Promise { const connection = await SQLConnection.getConnection(); - await connection.getRepository(SavedSearchEntity).insert({name, searchQuery}); + const album = await connection.getRepository(SavedSearchEntity) + .findOne({name, searchQuery}); + if (album) { + return; + } + 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}); } public async deleteAlbum(id: number): Promise { const connection = await SQLConnection.getConnection(); - await connection.getRepository(AlbumBaseEntity).delete({id}); + + if (await connection.getRepository(AlbumBaseEntity) + .count({id, locked: false}) !== 1) { + throw new Error('Could not delete album, id:' + id); + } + + await connection.getRepository(AlbumBaseEntity).delete({id, locked: false}); + } public async getAlbums(): Promise { diff --git a/src/backend/model/database/sql/IndexingManager.ts b/src/backend/model/database/sql/IndexingManager.ts index c7eb83c6..fd020651 100644 --- a/src/backend/model/database/sql/IndexingManager.ts +++ b/src/backend/model/database/sql/IndexingManager.ts @@ -17,6 +17,11 @@ import {ObjectManagers} from '../../ObjectManagers'; import {IIndexingManager} from '../interfaces/IIndexingManager'; import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; import {Logger} from '../../../Logger'; +import {ServerPG2ConfMap, ServerSidePG2ConfAction} from '../../../../common/PG2ConfMap'; +import {ProjectPath} from '../../../ProjectPath'; +import * as path from 'path'; +import * as fs from 'fs'; +import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; const LOG_TAG = '[IndexingManager]'; @@ -31,6 +36,21 @@ export class IndexingManager implements IIndexingManager { return this.SavingReady !== null; } + private static async processServerSidePG2Conf(files: FileDTO[]): Promise { + for (const f of files) { + if (ServerPG2ConfMap[f.name] === ServerSidePG2ConfAction.SAVED_SEARCH) { + const fullMediaPath = path.join(ProjectPath.ImageFolder, f.directory.path, f.directory.name, f.name); + + Logger.silly(LOG_TAG, 'Saving saved searches to DB from:', fullMediaPath); + const savedSearches: { name: string, searchQuery: SearchQueryDTO }[] = + JSON.parse(await fs.promises.readFile(fullMediaPath, 'utf8')); + for (const s of savedSearches) { + await ObjectManagers.getInstance().AlbumManager.addIfNotExistSavedSearch(s.name, s.searchQuery, true); + } + } + } + } + public indexDirectory(relativeDirectoryName: string): Promise { return new Promise(async (resolve, reject): Promise => { try { @@ -41,8 +61,17 @@ export class IndexingManager implements IIndexingManager { scannedDirectory.preview.readyThumbnails = []; } scannedDirectory.media.forEach((p): any[] => p.readyThumbnails = []); + + // filter server side pg2conf + const serverSideConfs = scannedDirectory.metaFile.filter(m => ServerPG2ConfMap[m.name]); + scannedDirectory.metaFile = scannedDirectory.metaFile.filter(m => !ServerPG2ConfMap[m.name]); + resolve(scannedDirectory); + // process server side pg2conf + await IndexingManager.processServerSidePG2Conf(serverSideConfs); + + // save directory to DB this.queueForSave(scannedDirectory).catch(console.error); } catch (error) { diff --git a/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts b/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts index 3d1164aa..6b482a9f 100644 --- a/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts +++ b/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts @@ -15,6 +15,12 @@ export class AlbumBaseEntity implements AlbumBaseDTO { @Column(columnCharsetCS) name: string; + /** + * Locked albums are not possible to remove + */ + @Column({default: false}) + locked: boolean; + // not saving to database, it is only assigned when querying the DB public preview: MediaEntity; diff --git a/src/common/DataStructureVersion.ts b/src/common/DataStructureVersion.ts index 1df199c3..b4e2b4cd 100644 --- a/src/common/DataStructureVersion.ts +++ b/src/common/DataStructureVersion.ts @@ -1 +1 @@ -export const DataStructureVersion = 22; +export const DataStructureVersion = 23; diff --git a/src/common/PG2ConfMap.ts b/src/common/PG2ConfMap.ts index a1dde1da..d8605988 100644 --- a/src/common/PG2ConfMap.ts +++ b/src/common/PG2ConfMap.ts @@ -14,3 +14,18 @@ export const PG2ConfMap = { '.order_random.pg2conf': SortingMethods.random } }; + +/** + * These files are processed on the server side, + * do not get passed down to the client or saved to the DB + */ + + +export enum ServerSidePG2ConfAction { + SAVED_SEARCH = 1 +} + +export const ServerPG2ConfMap: { [key: string]: ServerSidePG2ConfAction } = { + '.saved_searches.pg2conf': ServerSidePG2ConfAction.SAVED_SEARCH +}; + diff --git a/src/common/SupportedFormats.ts b/src/common/SupportedFormats.ts index 3dfaf7a5..1380131e 100644 --- a/src/common/SupportedFormats.ts +++ b/src/common/SupportedFormats.ts @@ -20,13 +20,14 @@ export const SupportedFormats = { // These formats need to be transcoded (with the build-in ffmpeg support) TranscodeNeed: { // based on libvips, all supported formats for sharp: https://github.com/libvips/libvips - // all supported formats for gm: http://www.graphicsmagick.org/GraphicsMagick.html Photos: [] as string[], Videos: [ 'avi', 'mkv', 'mov', 'wmv', 'flv', 'mts', 'm2ts', 'mpg', '3gp', 'm4v', 'mpeg', 'vob', 'divx', 'xvid', 'ts' ], }, + // -------------------------------------------- + // Below this, it is autogenerated, DO NOT EDIT WithDots: { Photos: [] as string[], Videos: [] as string[], diff --git a/src/common/entities/album/AlbumBaseDTO.ts b/src/common/entities/album/AlbumBaseDTO.ts index e305d4ca..57ee5c11 100644 --- a/src/common/entities/album/AlbumBaseDTO.ts +++ b/src/common/entities/album/AlbumBaseDTO.ts @@ -3,5 +3,6 @@ import {PreviewPhotoDTO} from '../PhotoDTO'; export interface AlbumBaseDTO { id: number; name: string; - preview: PreviewPhotoDTO; + preview?: PreviewPhotoDTO; + locked: boolean; } diff --git a/src/common/entities/album/SavedSearchDTO.ts b/src/common/entities/album/SavedSearchDTO.ts index 12394a0f..2300130f 100644 --- a/src/common/entities/album/SavedSearchDTO.ts +++ b/src/common/entities/album/SavedSearchDTO.ts @@ -5,7 +5,8 @@ import {SearchQueryDTO} from '../SearchQueryDTO'; export interface SavedSearchDTO extends AlbumBaseDTO { id: number; name: string; - preview: PreviewPhotoDTO; + preview?: PreviewPhotoDTO; + locked: boolean; searchQuery: SearchQueryDTO; } diff --git a/src/frontend/app/ui/albums/album/album.component.css b/src/frontend/app/ui/albums/album/album.component.css index 56d40552..99c513ef 100644 --- a/src/frontend/app/ui/albums/album/album.component.css +++ b/src/frontend/app/ui/albums/album/album.component.css @@ -1,8 +1,9 @@ -.delete { +.info-button{ margin: 2px; cursor: default; } + .delete { cursor: pointer; transition: all .05s ease-in-out; diff --git a/src/frontend/app/ui/albums/album/album.component.html b/src/frontend/app/ui/albums/album/album.component.html index c0692e80..73fbc185 100644 --- a/src/frontend/app/ui/albums/album/album.component.html +++ b/src/frontend/app/ui/albums/album/album.component.html @@ -19,10 +19,14 @@
{{album.name}} - + +
diff --git a/src/frontend/app/ui/albums/albums.component.html b/src/frontend/app/ui/albums/albums.component.html index ac6efa9f..b5b0a6be 100644 --- a/src/frontend/app/ui/albums/albums.component.html +++ b/src/frontend/app/ui/albums/albums.component.html @@ -6,6 +6,7 @@ [size]="size">