diff --git a/benchmark/Benchmarks.ts b/benchmark/Benchmarks.ts index 53509cfe..9b531fd0 100644 --- a/benchmark/Benchmarks.ts +++ b/benchmark/Benchmarks.ts @@ -1,13 +1,13 @@ -import {SQLConnection} from '../src/backend/model/sql/SQLConnection'; +import {SQLConnection} from '../src/backend/model/database/sql/SQLConnection'; import {Config} from '../src/common/config/private/Config'; import {ObjectManagers} from '../src/backend/model/ObjectManagers'; import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker'; -import {IndexingManager} from '../src/backend/model/sql/IndexingManager'; -import {SearchManager} from '../src/backend/model/sql/SearchManager'; +import {IndexingManager} from '../src/backend/model/database/sql/IndexingManager'; +import {SearchManager} from '../src/backend/model/database/sql/SearchManager'; import * as fs from 'fs'; import {SearchTypes} from '../src/common/entities/AutoCompleteItem'; import {Utils} from '../src/common/Utils'; -import {GalleryManager} from '../src/backend/model/sql/GalleryManager'; +import {GalleryManager} from '../src/backend/model/database/sql/GalleryManager'; import {DirectoryDTO} from '../src/common/entities/DirectoryDTO'; import {ServerConfig} from '../src/common/config/private/IPrivateConfig'; diff --git a/benchmark/index.ts b/benchmark/index.ts index 85ad957f..fcaafa1c 100644 --- a/benchmark/index.ts +++ b/benchmark/index.ts @@ -7,7 +7,7 @@ import {Utils} from '../src/common/Utils'; import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker'; const config: { path: string, system: string } = require(path.join(__dirname, 'config.json')); -Config.Server.imagesFolder = config.path; +Config.Server.Media.folder = config.path; const dbPath = path.join(__dirname, 'test.db'); ProjectPath.reset(); const RUNS = 50; diff --git a/package-lock.json b/package-lock.json index 1e843c5c..d4065811 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17675,9 +17675,9 @@ } }, "typeconfig": { - "version": "1.0.8-c", - "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-1.0.8-c.tgz", - "integrity": "sha512-4RrdrX0pJlC8zjGTkkZ95p6K3IMmd4Hk/TRTiTOmWhJU7ubu7diVEtIEh+sGk487E5LA6XSgNYP2fsQqp/+ELg==", + "version": "1.0.8-d", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-1.0.8-d.tgz", + "integrity": "sha512-WmuJEs/ZcSELWjRUZCN26wYZ5hZiT2jQ3CYrWjMyv+J7o245DEM/WJ/52ThNIdLaWpdvPW6uHMZMGe3AiHkZaA==", "requires": { "optimist": "0.6.1" } diff --git a/package.json b/package.json index 9b9f1706..ac057311 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,6 @@ "sharp": "0.23.4" }, "engines": { - "node": ">= 6.9 <11.0" + "node": ">=10 <13.0" } } diff --git a/src/backend/ProjectPath.ts b/src/backend/ProjectPath.ts index 2c7ee3b6..6d22d796 100644 --- a/src/backend/ProjectPath.ts +++ b/src/backend/ProjectPath.ts @@ -6,7 +6,7 @@ class ProjectPathClass { public Root: string; public ImageFolder: string; public ThumbnailFolder: string; - public TranscendedFolder: string; + public TranscodedFolder: string; public FrontendFolder: string; constructor() { @@ -27,10 +27,10 @@ class ProjectPathClass { reset() { this.Root = path.join(__dirname, '/../../'); - this.ImageFolder = this.getAbsolutePath(Config.Server.imagesFolder); - this.ThumbnailFolder = this.getAbsolutePath(Config.Server.Thumbnail.folder); - this.TranscendedFolder = path.join(this.ThumbnailFolder, 'tc'); this.FrontendFolder = path.join(this.Root, 'dist'); + this.ImageFolder = this.getAbsolutePath(Config.Server.Media.folder); + this.ThumbnailFolder = this.getAbsolutePath(Config.Server.Media.tempFolder); + this.TranscodedFolder = path.join(this.ThumbnailFolder, 'tc'); // create thumbnail folder if not exist if (!fs.existsSync(this.ThumbnailFolder)) { diff --git a/src/backend/middlewares/GalleryMWs.ts b/src/backend/middlewares/GalleryMWs.ts index c9b86d41..427cf522 100644 --- a/src/backend/middlewares/GalleryMWs.ts +++ b/src/backend/middlewares/GalleryMWs.ts @@ -10,12 +10,12 @@ import {PhotoDTO} from '../../common/entities/PhotoDTO'; import {ProjectPath} from '../ProjectPath'; import {Config} from '../../common/config/private/Config'; import {UserDTO} from '../../common/entities/UserDTO'; -import {RandomQuery} from '../model/interfaces/IGalleryManager'; +import {RandomQuery} from '../model/database/interfaces/IGalleryManager'; import {MediaDTO} from '../../common/entities/MediaDTO'; import {VideoDTO} from '../../common/entities/VideoDTO'; import {Utils} from '../../common/Utils'; import {QueryParams} from '../../common/QueryParams'; -import {VideoConverterMWs} from './VideoConverterMWs'; +import {VideoProcessing} from '../model/fileprocessing/VideoProcessing'; const LOG_TAG = '[GalleryMWs]'; @@ -94,7 +94,7 @@ export class GalleryMWs { } - if (Config.Client.Video.enabled === false) { + if (Config.Client.Media.Video.enabled === false) { if (cw.directory) { const removeVideos = (dir: DirectoryDTO) => { dir.media = dir.media.filter(m => !MediaDTO.isVideo(m)); @@ -180,22 +180,22 @@ export class GalleryMWs { } public static loadBestFitVideo(req: Request, res: Response, next: NextFunction) { - if (!(req.params.mediaPath)) { + if (!(req.resultPipe)) { return next(); } - const fullMediaPath = path.join(ProjectPath.ImageFolder, req.params.mediaPath); + const fullMediaPath = path.join(ProjectPath.ImageFolder, req.resultPipe); if (fs.statSync(fullMediaPath).isDirectory()) { return next(); } - // check if video exist + // check if video does not exist if (fs.existsSync(fullMediaPath) === false) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + req.params.mediaPath, 'can\'t find file: ' + fullMediaPath)); + return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + req.resultPipe, 'can\'t find file: ' + fullMediaPath)); } req.resultPipe = fullMediaPath; - const convertedVideo = VideoConverterMWs.generateConvertedFileName(fullMediaPath); + const convertedVideo = VideoProcessing.generateConvertedFileName(fullMediaPath); // check if transcoded video exist if (fs.existsSync(convertedVideo) === true) { diff --git a/src/backend/middlewares/VideoConverterMWs.ts b/src/backend/middlewares/VideoConverterMWs.ts deleted file mode 100644 index ae10b542..00000000 --- a/src/backend/middlewares/VideoConverterMWs.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; -import * as util from 'util'; -import {ITaskExecuter, TaskExecuter} from '../model/threading/TaskExecuter'; -import {VideoConverterInput, VideoConverterWorker} from '../model/threading/VideoConverterWorker'; -import {MetadataLoader} from '../model/threading/MetadataLoader'; -import {Config} from '../../common/config/private/Config'; -import {ProjectPath} from '../ProjectPath'; - -const existPr = util.promisify(fs.exists); - -export class VideoConverterMWs { - private static taskQue: ITaskExecuter = - new TaskExecuter(1, (input => VideoConverterWorker.convert(input))); - - - public static generateConvertedFileName(videoPath: string): string { - const extension = path.extname(videoPath); - const file = path.basename(videoPath, extension); - const postfix = Math.round(Config.Server.Video.transcoding.bitRate / 1024) + 'k' + - Config.Server.Video.transcoding.codec.toString().toLowerCase() + - Config.Server.Video.transcoding.resolution; - return path.join(ProjectPath.TranscendedFolder, - ProjectPath.getRelativePathToImages(path.dirname(videoPath)), file + - '_' + postfix + '.' + Config.Server.Video.transcoding.format); - } - - public static async convertVideo(videoPath: string): Promise { - - - const outPath = this.generateConvertedFileName(videoPath); - - if (await existPr(outPath)) { - return; - } - const metaData = await MetadataLoader.loadVideoMetadata(videoPath); - - const renderInput: VideoConverterInput = { - videoPath: videoPath, - output: { - path: outPath, - codec: Config.Server.Video.transcoding.codec, - format: Config.Server.Video.transcoding.format - } - }; - - if (metaData.bitRate > Config.Server.Video.transcoding.bitRate) { - renderInput.output.bitRate = Config.Server.Video.transcoding.bitRate; - } - if (metaData.fps > Config.Server.Video.transcoding.fps) { - renderInput.output.fps = Config.Server.Video.transcoding.fps; - } - - if (Config.Server.Video.transcoding.resolution < metaData.size.height) { - renderInput.output.resolution = Config.Server.Video.transcoding.resolution; - } - - const outDir = path.dirname(renderInput.output.path); - if (!fs.existsSync(outDir)) { - fs.mkdirSync(outDir, {recursive: true}); - } - - await VideoConverterMWs.taskQue.execute(renderInput); - - } -} - diff --git a/src/backend/middlewares/admin/AdminMWs.ts b/src/backend/middlewares/admin/AdminMWs.ts index 987016dc..700987d5 100644 --- a/src/backend/middlewares/admin/AdminMWs.ts +++ b/src/backend/middlewares/admin/AdminMWs.ts @@ -2,7 +2,7 @@ import {NextFunction, Request, Response} from 'express'; import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; import {ObjectManagers} from '../../model/ObjectManagers'; import {Config} from '../../../common/config/private/Config'; -import {ISQLGalleryManager} from '../../model/sql/IGalleryManager'; +import {ISQLGalleryManager} from '../../model/database/sql/IGalleryManager'; import {ServerConfig} from '../../../common/config/private/IPrivateConfig'; const LOG_TAG = '[AdminMWs]'; diff --git a/src/backend/middlewares/admin/SettingsMWs.ts b/src/backend/middlewares/admin/SettingsMWs.ts index 27f4a1ce..7bd9daf1 100644 --- a/src/backend/middlewares/admin/SettingsMWs.ts +++ b/src/backend/middlewares/admin/SettingsMWs.ts @@ -3,7 +3,7 @@ import {NextFunction, Request, Response} from 'express'; import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; import {ObjectManagers} from '../../model/ObjectManagers'; import {Logger} from '../../Logger'; -import {SQLConnection} from '../../model/sql/SQLConnection'; +import {SQLConnection} from '../../model/database/sql/SQLConnection'; import {Config} from '../../../common/config/private/Config'; import {ConfigDiagnostics} from '../../model/diagnostics/ConfigDiagnostics'; import {ClientConfig} from '../../../common/config/public/ConfigClass'; @@ -98,11 +98,11 @@ export class SettingsMWs { const original = Config.original(); await ConfigDiagnostics.testClientVideoConfig(settings.client); await ConfigDiagnostics.testServerVideoConfig(settings.server, original); - Config.Server.Video = settings.server; - Config.Client.Video = settings.client; + Config.Server.Media.Video = settings.server; + Config.Client.Media.Video = settings.client; // only updating explicitly set config (not saving config set by the diagnostics) - original.Server.Video = settings.server; - original.Client.Video = settings.client; + original.Server.Media.Video = settings.server; + original.Client.Media.Video = settings.client; original.save(); await ConfigDiagnostics.runDiagnostics(); Logger.info(LOG_TAG, 'new config:'); @@ -281,12 +281,12 @@ export class SettingsMWs { await ConfigDiagnostics.testServerThumbnailConfig(settings.server); await ConfigDiagnostics.testClientThumbnailConfig(settings.client); - Config.Server.Thumbnail = settings.server; - Config.Client.Thumbnail = settings.client; + Config.Server.Media.Thumbnail = settings.server; + Config.Client.Media.Thumbnail = settings.client; // only updating explicitly set config (not saving config set by the diagnostics) const original = Config.original(); - original.Server.Thumbnail = settings.server; - original.Client.Thumbnail = settings.client; + original.Server.Media.Thumbnail = settings.server; + original.Client.Media.Thumbnail = settings.client; original.save(); ProjectPath.reset(); await ConfigDiagnostics.runDiagnostics(); @@ -311,7 +311,7 @@ export class SettingsMWs { await ConfigDiagnostics.testImageFolder(settings.imagesFolder); Config.Server.port = settings.port; Config.Server.host = settings.host; - Config.Server.imagesFolder = settings.imagesFolder; + Config.Server.Media.folder = settings.imagesFolder; Config.Client.publicUrl = settings.publicUrl; Config.Client.urlBase = settings.urlBase; Config.Client.applicationTitle = settings.applicationTitle; @@ -319,7 +319,7 @@ export class SettingsMWs { const original = Config.original(); original.Server.port = settings.port; original.Server.host = settings.host; - original.Server.imagesFolder = settings.imagesFolder; + original.Server.Media.folder = settings.imagesFolder; original.Client.publicUrl = settings.publicUrl; original.Client.urlBase = settings.urlBase; original.Client.applicationTitle = settings.applicationTitle; diff --git a/src/backend/middlewares/customtypings/ExtendedRequest.d.ts b/src/backend/middlewares/customtypings/ExtendedRequest.d.ts index feec771f..2f171a92 100644 --- a/src/backend/middlewares/customtypings/ExtendedRequest.d.ts +++ b/src/backend/middlewares/customtypings/ExtendedRequest.d.ts @@ -1,5 +1,5 @@ import {LoginCredential} from '../../../common/entities/LoginCredential'; -import {UserEntity} from '../../model/sql/enitites/UserEntity'; +import {UserEntity} from '../../model/database/sql/enitites/UserEntity'; diff --git a/src/backend/middlewares/thumbnail/PhotoConverterMWs.ts b/src/backend/middlewares/thumbnail/PhotoConverterMWs.ts new file mode 100644 index 00000000..892858fe --- /dev/null +++ b/src/backend/middlewares/thumbnail/PhotoConverterMWs.ts @@ -0,0 +1,30 @@ +import {NextFunction, Request, Response} from 'express'; +import * as fs from 'fs'; +import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing'; +import {Config} from '../../../common/config/private/Config'; + +export class PhotoConverterMWs { + + public static async convertPhoto(req: Request, res: Response, next: NextFunction) { + if (!(req.resultPipe || Config.Server.Media.Photo.converting.enabled === false)) { + return next(); + } + const fullMediaPath = req.resultPipe; + + const convertedVideo = PhotoProcessing.generateConvertedFileName(fullMediaPath); + + // check if transcoded video exist + if (fs.existsSync(convertedVideo) === true) { + req.resultPipe = convertedVideo; + return next(); + } + + if (Config.Server.Media.Photo.converting.onTheFly) { + req.resultPipe = await PhotoProcessing.convertPhoto(fullMediaPath, + Config.Server.Media.Photo.converting.resolution); + } + + return next(); + } +} + diff --git a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts index bfc8ce99..5d5467e5 100644 --- a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts +++ b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts @@ -1,53 +1,18 @@ import * as path from 'path'; -import * as crypto from 'crypto'; import * as fs from 'fs'; -import * as os from 'os'; import {NextFunction, Request, Response} from 'express'; import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; import {ContentWrapper} from '../../../common/entities/ConentWrapper'; import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; import {ProjectPath} from '../../ProjectPath'; import {Config} from '../../../common/config/private/Config'; -import {ThumbnailTH} from '../../model/threading/ThreadPool'; -import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../../model/threading/ThumbnailWorker'; +import {ThumbnailSourceType} from '../../model/threading/ThumbnailWorker'; import {MediaDTO} from '../../../common/entities/MediaDTO'; -import {ITaskExecuter, TaskExecuter} from '../../model/threading/TaskExecuter'; -import {FaceRegion, PhotoDTO} from '../../../common/entities/PhotoDTO'; import {PersonWithPhoto} from '../PersonMWs'; -import {ServerConfig} from '../../../common/config/private/IPrivateConfig'; +import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing'; export class ThumbnailGeneratorMWs { - private static initDone = false; - private static taskQue: ITaskExecuter = null; - - public static init() { - if (this.initDone === true) { - return; - } - - - if (Config.Server.Threading.enable === true) { - if (Config.Server.Threading.thumbnailThreads > 0) { - Config.Client.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads; - } else { - Config.Client.Thumbnail.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1); - } - } else { - Config.Client.Thumbnail.concurrentThumbnailGenerations = 1; - } - - if (Config.Server.Threading.enable === true && - Config.Server.Thumbnail.processingLibrary === ServerConfig.ThumbnailProcessingLib.Jimp) { - this.taskQue = new ThumbnailTH(Config.Client.Thumbnail.concurrentThumbnailGenerations); - } else { - this.taskQue = new TaskExecuter(Config.Client.Thumbnail.concurrentThumbnailGenerations, - (input => ThumbnailWorker.render(input, Config.Server.Thumbnail.processingLibrary))); - } - - this.initDone = true; - } - public static addThumbnailInformation(req: Request, res: Response, next: NextFunction) { if (!req.resultPipe) { return next(); @@ -81,7 +46,7 @@ export class ThumbnailGeneratorMWs { } try { - const size: number = Config.Client.Thumbnail.personThumbnailSize; + const size: number = Config.Client.Media.Thumbnail.personThumbnailSize; const persons: PersonWithPhoto[] = req.resultPipe; for (let i = 0; i < persons.length; i++) { @@ -92,7 +57,7 @@ export class ThumbnailGeneratorMWs { // generate thumbnail path const thPath = path.join(ProjectPath.ThumbnailFolder, - ThumbnailGeneratorMWs.generatePersonThumbnailName(mediaPath, persons[i].samplePhoto.metadata.faces[0], size)); + PhotoProcessing.generatePersonThumbnailName(mediaPath, persons[i].samplePhoto.metadata.faces[0], size)); persons[i].readyThumbnail = fs.existsSync(thPath); } @@ -106,106 +71,68 @@ export class ThumbnailGeneratorMWs { } + public static async generatePersonThumbnail(req: Request, res: Response, next: NextFunction) { if (!req.resultPipe) { return next(); } - // load parameters - const photo: PhotoDTO = req.resultPipe; - if (!photo.metadata.faces || photo.metadata.faces.length !== 1) { - return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, 'Photo does not contain a face')); - } - - // load parameters - const mediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name); - const size: number = Config.Client.Thumbnail.personThumbnailSize; - // generate thumbnail path - const thPath = path.join(ProjectPath.ThumbnailFolder, - ThumbnailGeneratorMWs.generatePersonThumbnailName(mediaPath, photo.metadata.faces[0], size)); - - - req.resultPipe = thPath; - - // check if thumbnail already exist - if (fs.existsSync(thPath) === true) { - return next(); - } - - - const margin = { - x: Math.round(photo.metadata.faces[0].box.width * (Config.Server.Thumbnail.personFaceMargin)), - y: Math.round(photo.metadata.faces[0].box.height * (Config.Server.Thumbnail.personFaceMargin)) - }; - - - // run on other thread - const input = { - type: ThumbnailSourceType.Image, - mediaPath: mediaPath, - size: size, - thPath: thPath, - makeSquare: false, - cut: { - left: Math.round(Math.max(0, photo.metadata.faces[0].box.left - margin.x / 2)), - top: Math.round(Math.max(0, photo.metadata.faces[0].box.top - margin.y / 2)), - width: photo.metadata.faces[0].box.width + margin.x, - height: photo.metadata.faces[0].box.height + margin.y - }, - qualityPriority: Config.Server.Thumbnail.qualityPriority - }; - input.cut.width = Math.min(input.cut.width, photo.metadata.size.width - input.cut.left); - input.cut.height = Math.min(input.cut.height, photo.metadata.size.height - input.cut.top); try { - await ThumbnailGeneratorMWs.taskQue.execute(input); + req.resultPipe = await PhotoProcessing.generatePersonThumbnail(req.resultPipe); return next(); } catch (error) { return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, - 'Error during generating face thumbnail: ' + input.mediaPath, error.toString())); + 'Error during generating face thumbnail: ' + req.resultPipe, error.toString())); } } + public static generateThumbnailFactory(sourceType: ThumbnailSourceType) { - return (req: Request, res: Response, next: NextFunction) => { + return async (req: Request, res: Response, next: NextFunction) => { if (!req.resultPipe) { return next(); } // load parameters const mediaPath = req.resultPipe; - let size: number = parseInt(req.params.size, 10) || Config.Client.Thumbnail.thumbnailSizes[0]; + let size: number = parseInt(req.params.size, 10) || Config.Client.Media.Thumbnail.thumbnailSizes[0]; // validate size - if (Config.Client.Thumbnail.thumbnailSizes.indexOf(size) === -1) { - size = Config.Client.Thumbnail.thumbnailSizes[0]; + if (Config.Client.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1) { + size = Config.Client.Media.Thumbnail.thumbnailSizes[0]; } - ThumbnailGeneratorMWs.generateImage(mediaPath, size, sourceType, false, req, res, next); + + try { + req.resultPipe = await PhotoProcessing.generateThumbnail(mediaPath, size, sourceType, false); + return next(); + } catch (error) { + return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, + 'Error during generating thumbnail: ' + mediaPath, error.toString())); + } }; } public static generateIconFactory(sourceType: ThumbnailSourceType) { - return (req: Request, res: Response, next: NextFunction) => { + return async (req: Request, res: Response, next: NextFunction) => { if (!req.resultPipe) { return next(); } // load parameters const mediaPath = req.resultPipe; - const size: number = Config.Client.Thumbnail.iconSize; - ThumbnailGeneratorMWs.generateImage(mediaPath, size, sourceType, true, req, res, next); + const size: number = Config.Client.Media.Thumbnail.iconSize; + try { + req.resultPipe = await PhotoProcessing.generateThumbnail(mediaPath, size, sourceType, true); + return next(); + } catch (error) { + return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, + 'Error during generating thumbnail: ' + mediaPath, error.toString())); + } }; } - public static generateThumbnailName(mediaPath: string, size: number): string { - return crypto.createHash('md5').update(mediaPath).digest('hex') + '_' + size + '.jpg'; - } - - public static generatePersonThumbnailName(mediaPath: string, faceRegion: FaceRegion, size: number): string { - return crypto.createHash('md5').update(mediaPath + '_' + faceRegion.name + '_' + faceRegion.box.left + '_' + faceRegion.box.top) - .digest('hex') + '_' + size + '.jpg'; - } private static addThInfoTODir(directory: DirectoryDTO) { if (typeof directory.media !== 'undefined') { @@ -222,9 +149,9 @@ export class ThumbnailGeneratorMWs { const thumbnailFolder = ProjectPath.ThumbnailFolder; 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.Thumbnail.thumbnailSizes.length; j++) { - const size = Config.Client.Thumbnail.thumbnailSizes[j]; - const thPath = path.join(thumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(fullMediaPath, size)); + for (let j = 0; j < Config.Client.Media.Thumbnail.thumbnailSizes.length; j++) { + const size = Config.Client.Media.Thumbnail.thumbnailSizes[j]; + const thPath = path.join(thumbnailFolder, PhotoProcessing.generateThumbnailName(fullMediaPath, size)); if (fs.existsSync(thPath) === true) { if (typeof photos[i].readyThumbnails === 'undefined') { photos[i].readyThumbnails = []; @@ -233,46 +160,12 @@ export class ThumbnailGeneratorMWs { } } const iconPath = path.join(thumbnailFolder, - ThumbnailGeneratorMWs.generateThumbnailName(fullMediaPath, Config.Client.Thumbnail.iconSize)); + PhotoProcessing.generateThumbnailName(fullMediaPath, Config.Client.Media.Thumbnail.iconSize)); if (fs.existsSync(iconPath) === true) { photos[i].readyIcon = true; } } } - private static async generateImage(mediaPath: string, - size: number, - sourceType: ThumbnailSourceType, - makeSquare: boolean, - req: Request, res: Response, next: NextFunction) { - // generate thumbnail path - const thPath = path.join(ProjectPath.ThumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(mediaPath, size)); - - - req.resultPipe = thPath; - - // check if thumbnail already exist - if (fs.existsSync(thPath) === true) { - return next(); - } - - - // run on other thread - const input = { - type: sourceType, - mediaPath: mediaPath, - size: size, - thPath: thPath, - makeSquare: makeSquare, - qualityPriority: Config.Server.Thumbnail.qualityPriority - }; - try { - await this.taskQue.execute(input); - return next(); - } catch (error) { - return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, - 'Error during generating thumbnail: ' + input.mediaPath, error.toString())); - } - } } diff --git a/src/backend/model/ObjectManagers.ts b/src/backend/model/ObjectManagers.ts index 57b6e74a..4bba16fa 100644 --- a/src/backend/model/ObjectManagers.ts +++ b/src/backend/model/ObjectManagers.ts @@ -1,13 +1,13 @@ -import {IUserManager} from './interfaces/IUserManager'; -import {IGalleryManager} from './interfaces/IGalleryManager'; -import {ISearchManager} from './interfaces/ISearchManager'; -import {SQLConnection} from './sql/SQLConnection'; -import {ISharingManager} from './interfaces/ISharingManager'; +import {IUserManager} from './database/interfaces/IUserManager'; +import {IGalleryManager} from './database/interfaces/IGalleryManager'; +import {ISearchManager} from './database/interfaces/ISearchManager'; +import {SQLConnection} from './database/sql/SQLConnection'; +import {ISharingManager} from './database/interfaces/ISharingManager'; import {Logger} from '../Logger'; -import {IIndexingManager} from './interfaces/IIndexingManager'; -import {IPersonManager} from './interfaces/IPersonManager'; -import {IVersionManager} from './interfaces/IVersionManager'; -import {ITaskManager} from './interfaces/ITaskManager'; +import {IIndexingManager} from './database/interfaces/IIndexingManager'; +import {IPersonManager} from './database/interfaces/IPersonManager'; +import {IVersionManager} from './database/interfaces/IVersionManager'; +import {ITaskManager} from './database/interfaces/ITaskManager'; export class ObjectManagers { @@ -111,13 +111,13 @@ export class ObjectManagers { public static async InitMemoryManagers() { await ObjectManagers.reset(); - const GalleryManager = require('./memory/GalleryManager').GalleryManager; - const UserManager = require('./memory/UserManager').UserManager; - const SearchManager = require('./memory/SearchManager').SearchManager; - const SharingManager = require('./memory/SharingManager').SharingManager; - const IndexingManager = require('./memory/IndexingManager').IndexingManager; - const PersonManager = require('./memory/PersonManager').PersonManager; - const VersionManager = require('./memory/VersionManager').VersionManager; + const GalleryManager = require('./database/memory/GalleryManager').GalleryManager; + const UserManager = require('./database/memory/UserManager').UserManager; + const SearchManager = require('./database/memory/SearchManager').SearchManager; + const SharingManager = require('./database/memory/SharingManager').SharingManager; + const IndexingManager = require('./database/memory/IndexingManager').IndexingManager; + const PersonManager = require('./database/memory/PersonManager').PersonManager; + const VersionManager = require('./database/memory/VersionManager').VersionManager; ObjectManagers.getInstance().GalleryManager = new GalleryManager(); ObjectManagers.getInstance().UserManager = new UserManager(); ObjectManagers.getInstance().SearchManager = new SearchManager(); @@ -131,13 +131,13 @@ export class ObjectManagers { public static async InitSQLManagers() { await ObjectManagers.reset(); await SQLConnection.init(); - const GalleryManager = require('./sql/GalleryManager').GalleryManager; - const UserManager = require('./sql/UserManager').UserManager; - const SearchManager = require('./sql/SearchManager').SearchManager; - const SharingManager = require('./sql/SharingManager').SharingManager; - const IndexingManager = require('./sql/IndexingManager').IndexingManager; - const PersonManager = require('./sql/PersonManager').PersonManager; - const VersionManager = require('./sql/VersionManager').VersionManager; + const GalleryManager = require('./database/sql/GalleryManager').GalleryManager; + const UserManager = require('./database/sql/UserManager').UserManager; + const SearchManager = require('./database/sql/SearchManager').SearchManager; + const SharingManager = require('./database/sql/SharingManager').SharingManager; + const IndexingManager = require('./database/sql/IndexingManager').IndexingManager; + const PersonManager = require('./database/sql/PersonManager').PersonManager; + const VersionManager = require('./database/sql/VersionManager').VersionManager; ObjectManagers.getInstance().GalleryManager = new GalleryManager(); ObjectManagers.getInstance().UserManager = new UserManager(); ObjectManagers.getInstance().SearchManager = new SearchManager(); diff --git a/src/backend/model/interfaces/IGalleryManager.ts b/src/backend/model/database/interfaces/IGalleryManager.ts similarity index 68% rename from src/backend/model/interfaces/IGalleryManager.ts rename to src/backend/model/database/interfaces/IGalleryManager.ts index 3336f975..8128feb8 100644 --- a/src/backend/model/interfaces/IGalleryManager.ts +++ b/src/backend/model/database/interfaces/IGalleryManager.ts @@ -1,6 +1,6 @@ -import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; -import {PhotoDTO} from '../../../common/entities/PhotoDTO'; -import {OrientationType} from '../../../common/entities/RandomQueryDTO'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; +import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; +import {OrientationType} from '../../../../common/entities/RandomQueryDTO'; export interface RandomQuery { directory?: string; diff --git a/src/backend/model/interfaces/IIndexingManager.ts b/src/backend/model/database/interfaces/IIndexingManager.ts similarity index 66% rename from src/backend/model/interfaces/IIndexingManager.ts rename to src/backend/model/database/interfaces/IIndexingManager.ts index af042071..b02618cc 100644 --- a/src/backend/model/interfaces/IIndexingManager.ts +++ b/src/backend/model/database/interfaces/IIndexingManager.ts @@ -1,4 +1,4 @@ -import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; export interface IIndexingManager { indexDirectory(relativeDirectoryName: string): Promise; diff --git a/src/backend/model/interfaces/IPersonManager.ts b/src/backend/model/database/interfaces/IPersonManager.ts similarity index 75% rename from src/backend/model/interfaces/IPersonManager.ts rename to src/backend/model/database/interfaces/IPersonManager.ts index 6c5efdd6..28fed32c 100644 --- a/src/backend/model/interfaces/IPersonManager.ts +++ b/src/backend/model/database/interfaces/IPersonManager.ts @@ -1,6 +1,6 @@ import {PersonEntry} from '../sql/enitites/PersonEntry'; -import {PhotoDTO} from '../../../common/entities/PhotoDTO'; -import {PersonDTO} from '../../../common/entities/PersonDTO'; +import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; +import {PersonDTO} from '../../../../common/entities/PersonDTO'; export interface IPersonManager { getAll(): Promise; diff --git a/src/backend/model/interfaces/ISearchManager.ts b/src/backend/model/database/interfaces/ISearchManager.ts similarity index 57% rename from src/backend/model/interfaces/ISearchManager.ts rename to src/backend/model/database/interfaces/ISearchManager.ts index 41c49201..14eb5758 100644 --- a/src/backend/model/interfaces/ISearchManager.ts +++ b/src/backend/model/database/interfaces/ISearchManager.ts @@ -1,5 +1,5 @@ -import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem'; -import {SearchResultDTO} from '../../../common/entities/SearchResultDTO'; +import {AutoCompleteItem, SearchTypes} from '../../../../common/entities/AutoCompleteItem'; +import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO'; export interface ISearchManager { autocomplete(text: string): Promise; diff --git a/src/backend/model/interfaces/ISharingManager.ts b/src/backend/model/database/interfaces/ISharingManager.ts similarity index 75% rename from src/backend/model/interfaces/ISharingManager.ts rename to src/backend/model/database/interfaces/ISharingManager.ts index b88439d8..03d7790c 100644 --- a/src/backend/model/interfaces/ISharingManager.ts +++ b/src/backend/model/database/interfaces/ISharingManager.ts @@ -1,4 +1,4 @@ -import {SharingDTO} from '../../../common/entities/SharingDTO'; +import {SharingDTO} from '../../../../common/entities/SharingDTO'; export interface ISharingManager { findOne(filter: any): Promise; diff --git a/src/backend/model/interfaces/ITaskManager.ts b/src/backend/model/database/interfaces/ITaskManager.ts similarity index 63% rename from src/backend/model/interfaces/ITaskManager.ts rename to src/backend/model/database/interfaces/ITaskManager.ts index 0fe3baa8..bbc27ec0 100644 --- a/src/backend/model/interfaces/ITaskManager.ts +++ b/src/backend/model/database/interfaces/ITaskManager.ts @@ -1,5 +1,5 @@ -import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO'; -import {TaskDTO} from '../../../common/entities/task/TaskDTO'; +import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO'; +import {TaskDTO} from '../../../../common/entities/task/TaskDTO'; export interface ITaskManager { diff --git a/src/backend/model/interfaces/IUserManager.ts b/src/backend/model/database/interfaces/IUserManager.ts similarity index 81% rename from src/backend/model/interfaces/IUserManager.ts rename to src/backend/model/database/interfaces/IUserManager.ts index 235e0575..d190ad65 100644 --- a/src/backend/model/interfaces/IUserManager.ts +++ b/src/backend/model/database/interfaces/IUserManager.ts @@ -1,4 +1,4 @@ -import {UserDTO, UserRoles} from '../../../common/entities/UserDTO'; +import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO'; export interface IUserManager { findOne(filter: any): Promise; diff --git a/src/backend/model/interfaces/IVersionManager.ts b/src/backend/model/database/interfaces/IVersionManager.ts similarity index 100% rename from src/backend/model/interfaces/IVersionManager.ts rename to src/backend/model/database/interfaces/IVersionManager.ts diff --git a/src/backend/model/memory/GalleryManager.ts b/src/backend/model/database/memory/GalleryManager.ts similarity index 71% rename from src/backend/model/memory/GalleryManager.ts rename to src/backend/model/database/memory/GalleryManager.ts index 6894ec23..8b42b53d 100644 --- a/src/backend/model/memory/GalleryManager.ts +++ b/src/backend/model/database/memory/GalleryManager.ts @@ -1,13 +1,13 @@ -import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; import {IGalleryManager, RandomQuery} from '../interfaces/IGalleryManager'; import * as path from 'path'; import * as fs from 'fs'; -import {DiskManager} from '../DiskManger'; -import {ProjectPath} from '../../ProjectPath'; -import {Config} from '../../../common/config/private/Config'; -import {PhotoDTO} from '../../../common/entities/PhotoDTO'; -import {DiskMangerWorker} from '../threading/DiskMangerWorker'; -import {ServerConfig} from '../../../common/config/private/IPrivateConfig'; +import {DiskManager} from '../../DiskManger'; +import {ProjectPath} from '../../../ProjectPath'; +import {Config} from '../../../../common/config/private/Config'; +import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; +import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; +import {ServerConfig} from '../../../../common/config/private/IPrivateConfig'; export class GalleryManager implements IGalleryManager { diff --git a/src/backend/model/memory/IndexingManager.ts b/src/backend/model/database/memory/IndexingManager.ts similarity index 82% rename from src/backend/model/memory/IndexingManager.ts rename to src/backend/model/database/memory/IndexingManager.ts index 031d9aca..7eee4fef 100644 --- a/src/backend/model/memory/IndexingManager.ts +++ b/src/backend/model/database/memory/IndexingManager.ts @@ -1,5 +1,5 @@ import {IIndexingManager} from '../interfaces/IIndexingManager'; -import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; export class IndexingManager implements IIndexingManager { diff --git a/src/backend/model/memory/PersonManager.ts b/src/backend/model/database/memory/PersonManager.ts similarity index 84% rename from src/backend/model/memory/PersonManager.ts rename to src/backend/model/database/memory/PersonManager.ts index 9a1249ea..180a7931 100644 --- a/src/backend/model/memory/PersonManager.ts +++ b/src/backend/model/database/memory/PersonManager.ts @@ -1,6 +1,6 @@ import {IPersonManager} from '../interfaces/IPersonManager'; -import {PhotoDTO} from '../../../common/entities/PhotoDTO'; -import {PersonDTO} from '../../../common/entities/PersonDTO'; +import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; +import {PersonDTO} from '../../../../common/entities/PersonDTO'; export class PersonManager implements IPersonManager { diff --git a/src/backend/model/memory/SearchManager.ts b/src/backend/model/database/memory/SearchManager.ts similarity index 73% rename from src/backend/model/memory/SearchManager.ts rename to src/backend/model/database/memory/SearchManager.ts index 529b6192..0a4aacf3 100644 --- a/src/backend/model/memory/SearchManager.ts +++ b/src/backend/model/database/memory/SearchManager.ts @@ -1,6 +1,6 @@ -import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem'; +import {AutoCompleteItem, SearchTypes} from '../../../../common/entities/AutoCompleteItem'; import {ISearchManager} from '../interfaces/ISearchManager'; -import {SearchResultDTO} from '../../../common/entities/SearchResultDTO'; +import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO'; export class SearchManager implements ISearchManager { autocomplete(text: string): Promise { diff --git a/src/backend/model/memory/SharingManager.ts b/src/backend/model/database/memory/SharingManager.ts similarity index 86% rename from src/backend/model/memory/SharingManager.ts rename to src/backend/model/database/memory/SharingManager.ts index 4a07fe40..795af4b1 100644 --- a/src/backend/model/memory/SharingManager.ts +++ b/src/backend/model/database/memory/SharingManager.ts @@ -1,5 +1,5 @@ import {ISharingManager} from '../interfaces/ISharingManager'; -import {SharingDTO} from '../../../common/entities/SharingDTO'; +import {SharingDTO} from '../../../../common/entities/SharingDTO'; export class SharingManager implements ISharingManager { diff --git a/src/backend/model/memory/UserManager.ts b/src/backend/model/database/memory/UserManager.ts similarity index 89% rename from src/backend/model/memory/UserManager.ts rename to src/backend/model/database/memory/UserManager.ts index 6443a717..bb4df481 100644 --- a/src/backend/model/memory/UserManager.ts +++ b/src/backend/model/database/memory/UserManager.ts @@ -1,10 +1,10 @@ -import {UserDTO, UserRoles} from '../../../common/entities/UserDTO'; +import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO'; import {IUserManager} from '../interfaces/IUserManager'; -import {ProjectPath} from '../../ProjectPath'; -import {Utils} from '../../../common/Utils'; +import {ProjectPath} from '../../../ProjectPath'; +import {Utils} from '../../../../common/Utils'; import * as fs from 'fs'; -import {PasswordHelper} from '../PasswordHelper'; -import {Config} from '../../../common/config/private/Config'; +import {PasswordHelper} from '../../PasswordHelper'; +import {Config} from '../../../../common/config/private/Config'; export class UserManager implements IUserManager { diff --git a/src/backend/model/memory/VersionManager.ts b/src/backend/model/database/memory/VersionManager.ts similarity index 78% rename from src/backend/model/memory/VersionManager.ts rename to src/backend/model/database/memory/VersionManager.ts index 8d1b2c1b..dbcc0695 100644 --- a/src/backend/model/memory/VersionManager.ts +++ b/src/backend/model/database/memory/VersionManager.ts @@ -1,5 +1,5 @@ import {IVersionManager} from '../interfaces/IVersionManager'; -import {DataStructureVersion} from '../../../common/DataStructureVersion'; +import {DataStructureVersion} from '../../../../common/DataStructureVersion'; export class VersionManager implements IVersionManager { async getDataVersion(): Promise { diff --git a/src/backend/model/sql/GalleryManager.ts b/src/backend/model/database/sql/GalleryManager.ts similarity index 94% rename from src/backend/model/sql/GalleryManager.ts rename to src/backend/model/database/sql/GalleryManager.ts index 85d4eac1..e37cfa3e 100644 --- a/src/backend/model/sql/GalleryManager.ts +++ b/src/backend/model/database/sql/GalleryManager.ts @@ -1,24 +1,24 @@ import {IGalleryManager, RandomQuery} from '../interfaces/IGalleryManager'; -import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; import * as path from 'path'; import * as fs from 'fs'; import {DirectoryEntity} from './enitites/DirectoryEntity'; import {SQLConnection} from './SQLConnection'; import {PhotoEntity} from './enitites/PhotoEntity'; -import {ProjectPath} from '../../ProjectPath'; -import {Config} from '../../../common/config/private/Config'; +import {ProjectPath} from '../../../ProjectPath'; +import {Config} from '../../../../common/config/private/Config'; import {ISQLGalleryManager} from './IGalleryManager'; -import {PhotoDTO} from '../../../common/entities/PhotoDTO'; -import {OrientationType} from '../../../common/entities/RandomQueryDTO'; +import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; +import {OrientationType} from '../../../../common/entities/RandomQueryDTO'; import {Brackets, Connection, SelectQueryBuilder} from 'typeorm'; import {MediaEntity} from './enitites/MediaEntity'; import {VideoEntity} from './enitites/VideoEntity'; -import {DiskMangerWorker} from '../threading/DiskMangerWorker'; -import {Logger} from '../../Logger'; +import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; +import {Logger} from '../../../Logger'; import {FaceRegionEntry} from './enitites/FaceRegionEntry'; -import {ObjectManagers} from '../ObjectManagers'; -import {DuplicatesDTO} from '../../../common/entities/DuplicatesDTO'; -import {ServerConfig} from '../../../common/config/private/IPrivateConfig'; +import {ObjectManagers} from '../../ObjectManagers'; +import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO'; +import {ServerConfig} from '../../../../common/config/private/IPrivateConfig'; const LOG_TAG = '[GalleryManager]'; diff --git a/src/backend/model/sql/IGalleryManager.ts b/src/backend/model/database/sql/IGalleryManager.ts similarity index 77% rename from src/backend/model/sql/IGalleryManager.ts rename to src/backend/model/database/sql/IGalleryManager.ts index 5dd5fa89..f6f206ff 100644 --- a/src/backend/model/sql/IGalleryManager.ts +++ b/src/backend/model/database/sql/IGalleryManager.ts @@ -1,6 +1,6 @@ -import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; import {IGalleryManager} from '../interfaces/IGalleryManager'; -import {DuplicatesDTO} from '../../../common/entities/DuplicatesDTO'; +import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO'; export interface ISQLGalleryManager extends IGalleryManager { listDirectory(relativeDirectoryName: string, diff --git a/src/backend/model/sql/IndexingManager.ts b/src/backend/model/database/sql/IndexingManager.ts similarity index 95% rename from src/backend/model/sql/IndexingManager.ts rename to src/backend/model/database/sql/IndexingManager.ts index 0a6812ea..48a71f79 100644 --- a/src/backend/model/sql/IndexingManager.ts +++ b/src/backend/model/database/sql/IndexingManager.ts @@ -1,22 +1,22 @@ -import {DirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; import {DirectoryEntity} from './enitites/DirectoryEntity'; import {SQLConnection} from './SQLConnection'; -import {DiskManager} from '../DiskManger'; +import {DiskManager} from '../../DiskManger'; import {PhotoEntity} from './enitites/PhotoEntity'; -import {Utils} from '../../../common/Utils'; -import {FaceRegion, PhotoMetadata} from '../../../common/entities/PhotoDTO'; +import {Utils} from '../../../../common/Utils'; +import {FaceRegion, PhotoMetadata} from '../../../../common/entities/PhotoDTO'; import {Connection, Repository} from 'typeorm'; import {MediaEntity} from './enitites/MediaEntity'; -import {MediaDTO} from '../../../common/entities/MediaDTO'; +import {MediaDTO} from '../../../../common/entities/MediaDTO'; import {VideoEntity} from './enitites/VideoEntity'; import {FileEntity} from './enitites/FileEntity'; -import {FileDTO} from '../../../common/entities/FileDTO'; -import {NotificationManager} from '../NotifocationManager'; +import {FileDTO} from '../../../../common/entities/FileDTO'; +import {NotificationManager} from '../../NotifocationManager'; import {FaceRegionEntry} from './enitites/FaceRegionEntry'; -import {ObjectManagers} from '../ObjectManagers'; +import {ObjectManagers} from '../../ObjectManagers'; import {IIndexingManager} from '../interfaces/IIndexingManager'; -import {DiskMangerWorker} from '../threading/DiskMangerWorker'; -import {Logger} from '../../Logger'; +import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; +import {Logger} from '../../../Logger'; const LOG_TAG = '[IndexingManager]'; diff --git a/src/backend/model/sql/PersonManager.ts b/src/backend/model/database/sql/PersonManager.ts similarity index 95% rename from src/backend/model/sql/PersonManager.ts rename to src/backend/model/database/sql/PersonManager.ts index 7a1d9640..f929c146 100644 --- a/src/backend/model/sql/PersonManager.ts +++ b/src/backend/model/database/sql/PersonManager.ts @@ -1,11 +1,11 @@ import {IPersonManager} from '../interfaces/IPersonManager'; import {SQLConnection} from './SQLConnection'; import {PersonEntry} from './enitites/PersonEntry'; -import {PhotoDTO} from '../../../common/entities/PhotoDTO'; +import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; import {MediaEntity} from './enitites/MediaEntity'; import {FaceRegionEntry} from './enitites/FaceRegionEntry'; -import {PersonDTO} from '../../../common/entities/PersonDTO'; -import {Utils} from '../../../common/Utils'; +import {PersonDTO} from '../../../../common/entities/PersonDTO'; +import {Utils} from '../../../../common/Utils'; const LOG_TAG = '[PersonManager]'; diff --git a/src/backend/model/sql/SQLConnection.ts b/src/backend/model/database/sql/SQLConnection.ts similarity index 92% rename from src/backend/model/sql/SQLConnection.ts rename to src/backend/model/database/sql/SQLConnection.ts index 811f68d9..20920351 100644 --- a/src/backend/model/sql/SQLConnection.ts +++ b/src/backend/model/database/sql/SQLConnection.ts @@ -1,23 +1,23 @@ import 'reflect-metadata'; import {Connection, ConnectionOptions, createConnection, getConnection} from 'typeorm'; import {UserEntity} from './enitites/UserEntity'; -import {UserRoles} from '../../../common/entities/UserDTO'; +import {UserRoles} from '../../../../common/entities/UserDTO'; import {PhotoEntity} from './enitites/PhotoEntity'; import {DirectoryEntity} from './enitites/DirectoryEntity'; -import {Config} from '../../../common/config/private/Config'; +import {Config} from '../../../../common/config/private/Config'; import {SharingEntity} from './enitites/SharingEntity'; -import {PasswordHelper} from '../PasswordHelper'; -import {ProjectPath} from '../../ProjectPath'; +import {PasswordHelper} from '../../PasswordHelper'; +import {ProjectPath} from '../../../ProjectPath'; import {VersionEntity} from './enitites/VersionEntity'; -import {Logger} from '../../Logger'; +import {Logger} from '../../../Logger'; import {MediaEntity} from './enitites/MediaEntity'; import {VideoEntity} from './enitites/VideoEntity'; -import {DataStructureVersion} from '../../../common/DataStructureVersion'; +import {DataStructureVersion} from '../../../../common/DataStructureVersion'; import {FileEntity} from './enitites/FileEntity'; import {FaceRegionEntry} from './enitites/FaceRegionEntry'; import {PersonEntry} from './enitites/PersonEntry'; -import {Utils} from '../../../common/Utils'; -import {ServerConfig} from '../../../common/config/private/IPrivateConfig'; +import {Utils} from '../../../../common/Utils'; +import {ServerConfig} from '../../../../common/config/private/IPrivateConfig'; export class SQLConnection { diff --git a/src/backend/model/sql/SearchManager.ts b/src/backend/model/database/sql/SearchManager.ts similarity index 97% rename from src/backend/model/sql/SearchManager.ts rename to src/backend/model/database/sql/SearchManager.ts index 53a9f9a5..408dfa10 100644 --- a/src/backend/model/sql/SearchManager.ts +++ b/src/backend/model/database/sql/SearchManager.ts @@ -1,6 +1,6 @@ -import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem'; +import {AutoCompleteItem, SearchTypes} from '../../../../common/entities/AutoCompleteItem'; import {ISearchManager} from '../interfaces/ISearchManager'; -import {SearchResultDTO} from '../../../common/entities/SearchResultDTO'; +import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO'; import {SQLConnection} from './SQLConnection'; import {PhotoEntity} from './enitites/PhotoEntity'; import {DirectoryEntity} from './enitites/DirectoryEntity'; @@ -9,7 +9,7 @@ import {VideoEntity} from './enitites/VideoEntity'; import {PersonEntry} from './enitites/PersonEntry'; import {FaceRegionEntry} from './enitites/FaceRegionEntry'; import {SelectQueryBuilder} from 'typeorm'; -import {Config} from '../../../common/config/private/Config'; +import {Config} from '../../../../common/config/private/Config'; export class SearchManager implements ISearchManager { diff --git a/src/backend/model/sql/SharingManager.ts b/src/backend/model/database/sql/SharingManager.ts similarity index 91% rename from src/backend/model/sql/SharingManager.ts rename to src/backend/model/database/sql/SharingManager.ts index ea883678..efb4dd6a 100644 --- a/src/backend/model/sql/SharingManager.ts +++ b/src/backend/model/database/sql/SharingManager.ts @@ -1,9 +1,9 @@ import {ISharingManager} from '../interfaces/ISharingManager'; -import {SharingDTO} from '../../../common/entities/SharingDTO'; +import {SharingDTO} from '../../../../common/entities/SharingDTO'; import {SQLConnection} from './SQLConnection'; import {SharingEntity} from './enitites/SharingEntity'; -import {Config} from '../../../common/config/private/Config'; -import {PasswordHelper} from '../PasswordHelper'; +import {Config} from '../../../../common/config/private/Config'; +import {PasswordHelper} from '../../PasswordHelper'; import {DeleteResult} from 'typeorm'; export class SharingManager implements ISharingManager { diff --git a/src/backend/model/sql/UserManager.ts b/src/backend/model/database/sql/UserManager.ts similarity index 94% rename from src/backend/model/sql/UserManager.ts rename to src/backend/model/database/sql/UserManager.ts index 0beee265..028950ec 100644 --- a/src/backend/model/sql/UserManager.ts +++ b/src/backend/model/database/sql/UserManager.ts @@ -1,8 +1,8 @@ -import {UserDTO, UserRoles} from '../../../common/entities/UserDTO'; +import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO'; import {IUserManager} from '../interfaces/IUserManager'; import {UserEntity} from './enitites/UserEntity'; import {SQLConnection} from './SQLConnection'; -import {PasswordHelper} from '../PasswordHelper'; +import {PasswordHelper} from '../../PasswordHelper'; export class UserManager implements IUserManager { diff --git a/src/backend/model/sql/VersionManager.ts b/src/backend/model/database/sql/VersionManager.ts similarity index 95% rename from src/backend/model/sql/VersionManager.ts rename to src/backend/model/database/sql/VersionManager.ts index 72c641fa..5683f8ca 100644 --- a/src/backend/model/sql/VersionManager.ts +++ b/src/backend/model/database/sql/VersionManager.ts @@ -1,6 +1,6 @@ import * as crypto from 'crypto'; import {IVersionManager} from '../interfaces/IVersionManager'; -import {DataStructureVersion} from '../../../common/DataStructureVersion'; +import {DataStructureVersion} from '../../../../common/DataStructureVersion'; import {SQLConnection} from './SQLConnection'; import {DirectoryEntity} from './enitites/DirectoryEntity'; import {MediaEntity} from './enitites/MediaEntity'; diff --git a/src/backend/model/sql/enitites/DirectoryEntity.ts b/src/backend/model/database/sql/enitites/DirectoryEntity.ts similarity index 95% rename from src/backend/model/sql/enitites/DirectoryEntity.ts rename to src/backend/model/database/sql/enitites/DirectoryEntity.ts index e6c76510..6fcb0b1b 100644 --- a/src/backend/model/sql/enitites/DirectoryEntity.ts +++ b/src/backend/model/database/sql/enitites/DirectoryEntity.ts @@ -1,5 +1,5 @@ import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique} from 'typeorm'; -import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; +import {DirectoryDTO} from '../../../../../common/entities/DirectoryDTO'; import {MediaEntity} from './MediaEntity'; import {FileEntity} from './FileEntity'; import {columnCharsetCS} from './EntityUtils'; diff --git a/src/backend/model/sql/enitites/EntityUtils.ts b/src/backend/model/database/sql/enitites/EntityUtils.ts similarity index 75% rename from src/backend/model/sql/enitites/EntityUtils.ts rename to src/backend/model/database/sql/enitites/EntityUtils.ts index cc32ee7f..3679670b 100644 --- a/src/backend/model/sql/enitites/EntityUtils.ts +++ b/src/backend/model/database/sql/enitites/EntityUtils.ts @@ -1,5 +1,5 @@ -import {Config} from '../../../../common/config/private/Config'; -import {ServerConfig} from '../../../../common/config/private/IPrivateConfig'; +import {Config} from '../../../../../common/config/private/Config'; +import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig'; import {ColumnOptions} from 'typeorm/decorator/options/ColumnOptions'; export class ColumnCharsetCS implements ColumnOptions { diff --git a/src/backend/model/sql/enitites/FaceRegionEntry.ts b/src/backend/model/database/sql/enitites/FaceRegionEntry.ts similarity index 94% rename from src/backend/model/sql/enitites/FaceRegionEntry.ts rename to src/backend/model/database/sql/enitites/FaceRegionEntry.ts index aaa44736..0959fa0a 100644 --- a/src/backend/model/sql/enitites/FaceRegionEntry.ts +++ b/src/backend/model/database/sql/enitites/FaceRegionEntry.ts @@ -1,4 +1,4 @@ -import {FaceRegion, FaceRegionBox} from '../../../../common/entities/PhotoDTO'; +import {FaceRegion, FaceRegionBox} from '../../../../../common/entities/PhotoDTO'; import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm'; import {PersonEntry} from './PersonEntry'; import {MediaEntity} from './MediaEntity'; diff --git a/src/backend/model/sql/enitites/FileEntity.ts b/src/backend/model/database/sql/enitites/FileEntity.ts similarity index 88% rename from src/backend/model/sql/enitites/FileEntity.ts rename to src/backend/model/database/sql/enitites/FileEntity.ts index 4b36c874..e23dcfa1 100644 --- a/src/backend/model/sql/enitites/FileEntity.ts +++ b/src/backend/model/database/sql/enitites/FileEntity.ts @@ -1,6 +1,6 @@ import {Column, Entity, ManyToOne, PrimaryGeneratedColumn, Index} from 'typeorm'; import {DirectoryEntity} from './DirectoryEntity'; -import {FileDTO} from '../../../../common/entities/FileDTO'; +import {FileDTO} from '../../../../../common/entities/FileDTO'; import {columnCharsetCS} from './EntityUtils'; diff --git a/src/backend/model/sql/enitites/MediaEntity.ts b/src/backend/model/database/sql/enitites/MediaEntity.ts similarity index 98% rename from src/backend/model/sql/enitites/MediaEntity.ts rename to src/backend/model/database/sql/enitites/MediaEntity.ts index d184ba80..1e85817f 100644 --- a/src/backend/model/sql/enitites/MediaEntity.ts +++ b/src/backend/model/database/sql/enitites/MediaEntity.ts @@ -1,6 +1,6 @@ import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance, Unique} from 'typeorm'; import {DirectoryEntity} from './DirectoryEntity'; -import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../common/entities/MediaDTO'; +import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../../common/entities/MediaDTO'; import {OrientationTypes} from 'ts-exif-parser'; import {CameraMetadataEntity, PositionMetaDataEntity} from './PhotoEntity'; import {FaceRegionEntry} from './FaceRegionEntry'; diff --git a/src/backend/model/sql/enitites/PersonEntry.ts b/src/backend/model/database/sql/enitites/PersonEntry.ts similarity index 88% rename from src/backend/model/sql/enitites/PersonEntry.ts rename to src/backend/model/database/sql/enitites/PersonEntry.ts index c1499cd1..49ba5bb3 100644 --- a/src/backend/model/sql/enitites/PersonEntry.ts +++ b/src/backend/model/database/sql/enitites/PersonEntry.ts @@ -1,6 +1,6 @@ import {Column, Entity, Index, OneToMany, PrimaryGeneratedColumn, Unique} from 'typeorm'; import {FaceRegionEntry} from './FaceRegionEntry'; -import {PersonDTO} from '../../../../common/entities/PersonDTO'; +import {PersonDTO} from '../../../../../common/entities/PersonDTO'; @Entity() diff --git a/src/backend/model/sql/enitites/PhotoEntity.ts b/src/backend/model/database/sql/enitites/PhotoEntity.ts similarity index 96% rename from src/backend/model/sql/enitites/PhotoEntity.ts rename to src/backend/model/database/sql/enitites/PhotoEntity.ts index a6b58429..fa23058d 100644 --- a/src/backend/model/sql/enitites/PhotoEntity.ts +++ b/src/backend/model/database/sql/enitites/PhotoEntity.ts @@ -1,5 +1,5 @@ import {ChildEntity, Column} from 'typeorm'; -import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../common/entities/PhotoDTO'; +import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../../common/entities/PhotoDTO'; import {MediaEntity, MediaMetadataEntity} from './MediaEntity'; export class CameraMetadataEntity implements CameraMetadata { diff --git a/src/backend/model/sql/enitites/SharingEntity.ts b/src/backend/model/database/sql/enitites/SharingEntity.ts similarity index 84% rename from src/backend/model/sql/enitites/SharingEntity.ts rename to src/backend/model/database/sql/enitites/SharingEntity.ts index 7af3d2b5..7b69d184 100644 --- a/src/backend/model/sql/enitites/SharingEntity.ts +++ b/src/backend/model/database/sql/enitites/SharingEntity.ts @@ -1,7 +1,7 @@ import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm'; -import {SharingDTO} from '../../../../common/entities/SharingDTO'; +import {SharingDTO} from '../../../../../common/entities/SharingDTO'; import {UserEntity} from './UserEntity'; -import {UserDTO} from '../../../../common/entities/UserDTO'; +import {UserDTO} from '../../../../../common/entities/UserDTO'; @Entity() export class SharingEntity implements SharingDTO { diff --git a/src/backend/model/sql/enitites/UserEntity.ts b/src/backend/model/database/sql/enitites/UserEntity.ts similarity index 82% rename from src/backend/model/sql/enitites/UserEntity.ts rename to src/backend/model/database/sql/enitites/UserEntity.ts index aeacb7ee..9eb20b16 100644 --- a/src/backend/model/sql/enitites/UserEntity.ts +++ b/src/backend/model/database/sql/enitites/UserEntity.ts @@ -1,4 +1,4 @@ -import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO'; +import {UserDTO, UserRoles} from '../../../../../common/entities/UserDTO'; import {Column, Entity, PrimaryGeneratedColumn, Unique} from 'typeorm'; @Entity() diff --git a/src/backend/model/sql/enitites/VersionEntity.ts b/src/backend/model/database/sql/enitites/VersionEntity.ts similarity index 100% rename from src/backend/model/sql/enitites/VersionEntity.ts rename to src/backend/model/database/sql/enitites/VersionEntity.ts diff --git a/src/backend/model/sql/enitites/VideoEntity.ts b/src/backend/model/database/sql/enitites/VideoEntity.ts similarity index 88% rename from src/backend/model/sql/enitites/VideoEntity.ts rename to src/backend/model/database/sql/enitites/VideoEntity.ts index 205ae951..eaa0fa81 100644 --- a/src/backend/model/sql/enitites/VideoEntity.ts +++ b/src/backend/model/database/sql/enitites/VideoEntity.ts @@ -1,6 +1,6 @@ import {ChildEntity, Column} from 'typeorm'; import {MediaEntity, MediaMetadataEntity} from './MediaEntity'; -import {VideoDTO, VideoMetadata} from '../../../../common/entities/VideoDTO'; +import {VideoDTO, VideoMetadata} from '../../../../../common/entities/VideoDTO'; export class VideoMetadataEntity extends MediaMetadataEntity implements VideoMetadata { diff --git a/src/backend/model/diagnostics/ConfigDiagnostics.ts b/src/backend/model/diagnostics/ConfigDiagnostics.ts index 3e8dea64..59863082 100644 --- a/src/backend/model/diagnostics/ConfigDiagnostics.ts +++ b/src/backend/model/diagnostics/ConfigDiagnostics.ts @@ -2,7 +2,7 @@ import {Config} from '../../../common/config/private/Config'; import {Logger} from '../../Logger'; import {NotificationManager} from '../NotifocationManager'; import {ProjectPath} from '../../ProjectPath'; -import {SQLConnection} from '../sql/SQLConnection'; +import {SQLConnection} from '../database/sql/SQLConnection'; import * as fs from 'fs'; import {ClientConfig} from '../../../common/config/public/ConfigClass'; import {FFmpegFactory} from '../FFmpegFactory'; @@ -74,7 +74,7 @@ export class ConfigDiagnostics { } static async testServerVideoConfig(videoConfig: ServerConfig.VideoConfig, config: IPrivateConfig) { - if (config.Client.Video.enabled === true) { + if (config.Client.Media.Video.enabled === true) { if (videoConfig.transcoding.fps <= 0) { throw new Error('fps should be grater than 0'); } @@ -101,15 +101,8 @@ export class ConfigDiagnostics { } } - static testThumbnailFolder(folder: string) { - return new Promise((resolve, reject) => { - fs.access(folder, fs.constants.W_OK, (err) => { - if (err) { - reject({message: 'Error during getting write access to temp folder', error: err.toString()}); - } - }); - resolve(); - }); + static async testTempFolder(folder: string) { + await this.checkReadWritePermission(folder); } static testImageFolder(folder: string) { @@ -129,7 +122,6 @@ export class ConfigDiagnostics { static async testServerThumbnailConfig(thumbnailConfig: ServerConfig.ThumbnailConfig) { await ConfigDiagnostics.testThumbnailLib(thumbnailConfig.processingLibrary); - await ConfigDiagnostics.testThumbnailFolder(thumbnailConfig.folder); } static async testClientThumbnailConfig(thumbnailConfig: ClientConfig.ThumbnailConfig) { @@ -226,24 +218,24 @@ export class ConfigDiagnostics { } } - if (Config.Server.Thumbnail.processingLibrary !== ServerConfig.ThumbnailProcessingLib.Jimp) { + if (Config.Server.Media.Thumbnail.processingLibrary !== ServerConfig.ThumbnailProcessingLib.Jimp) { try { - await ConfigDiagnostics.testThumbnailLib(Config.Server.Thumbnail.processingLibrary); + await ConfigDiagnostics.testThumbnailLib(Config.Server.Media.Thumbnail.processingLibrary); } catch (ex) { const err: Error = ex; NotificationManager.warning('Thumbnail hardware acceleration is not possible.' + - ' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Thumbnail.processingLibrary] + '\' node module is not found.' + + ' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Media.Thumbnail.processingLibrary] + '\' node module is not found.' + ' Falling back temporally to JS based thumbnail generation', err.toString()); Logger.warn(LOG_TAG, '[Thumbnail hardware acceleration] module error: ', err.toString()); Logger.warn(LOG_TAG, 'Thumbnail hardware acceleration is not possible.' + - ' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Thumbnail.processingLibrary] + '\' node module is not found.' + + ' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Media.Thumbnail.processingLibrary] + '\' node module is not found.' + ' Falling back temporally to JS based thumbnail generation'); - Config.Server.Thumbnail.processingLibrary = ServerConfig.ThumbnailProcessingLib.Jimp; + Config.Server.Media.Thumbnail.processingLibrary = ServerConfig.ThumbnailProcessingLib.Jimp; } } try { - await ConfigDiagnostics.testThumbnailFolder(Config.Server.Thumbnail.folder); + await ConfigDiagnostics.testTempFolder(Config.Server.Media.tempFolder); } catch (ex) { const err: Error = ex; NotificationManager.error('Thumbnail folder error', err.toString()); @@ -252,13 +244,13 @@ export class ConfigDiagnostics { try { - await ConfigDiagnostics.testClientVideoConfig(Config.Client.Video); - await ConfigDiagnostics.testServerVideoConfig(Config.Server.Video, Config); + await ConfigDiagnostics.testClientVideoConfig(Config.Client.Media.Video); + await ConfigDiagnostics.testServerVideoConfig(Config.Server.Media.Video, Config); } catch (ex) { const err: Error = ex; NotificationManager.warning('Video support error, switching off..', err.toString()); Logger.warn(LOG_TAG, 'Video support error, switching off..', err.toString()); - Config.Client.Video.enabled = false; + Config.Client.Media.Video.enabled = false; } try { @@ -272,14 +264,14 @@ export class ConfigDiagnostics { try { - await ConfigDiagnostics.testImageFolder(Config.Server.imagesFolder); + await ConfigDiagnostics.testImageFolder(Config.Server.Media.folder); } catch (ex) { const err: Error = ex; NotificationManager.error('Images folder error', err.toString()); Logger.error(LOG_TAG, 'Images folder error', err.toString()); } try { - await ConfigDiagnostics.testClientThumbnailConfig(Config.Client.Thumbnail); + await ConfigDiagnostics.testClientThumbnailConfig(Config.Client.Media.Thumbnail); } catch (ex) { const err: Error = ex; NotificationManager.error('Thumbnail settings error', err.toString()); diff --git a/src/backend/model/fileprocessing/PhotoProcessing.ts b/src/backend/model/fileprocessing/PhotoProcessing.ts new file mode 100644 index 00000000..3cff51c0 --- /dev/null +++ b/src/backend/model/fileprocessing/PhotoProcessing.ts @@ -0,0 +1,179 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as crypto from 'crypto'; +import {ProjectPath} from '../../ProjectPath'; +import {Config} from '../../../common/config/private/Config'; +import {ThumbnailTH} from '../threading/ThreadPool'; +import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../threading/ThumbnailWorker'; +import {ITaskExecuter, TaskExecuter} from '../threading/TaskExecuter'; +import {ServerConfig} from '../../../common/config/private/IPrivateConfig'; +import {FaceRegion, PhotoDTO} from '../../../common/entities/PhotoDTO'; + + +export class PhotoProcessing { + + private static initDone = false; + private static taskQue: ITaskExecuter = null; + + public static init() { + if (this.initDone === true) { + return; + } + + + if (Config.Server.Threading.enable === true) { + if (Config.Server.Threading.thumbnailThreads > 0) { + Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads; + } else { + Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1); + } + } else { + Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = 1; + } + + if (Config.Server.Threading.enable === true && + Config.Server.Media.Thumbnail.processingLibrary === ServerConfig.ThumbnailProcessingLib.Jimp) { + this.taskQue = new ThumbnailTH(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations); + } else { + this.taskQue = new TaskExecuter(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations, + (input => ThumbnailWorker.render(input, Config.Server.Media.Thumbnail.processingLibrary))); + } + + this.initDone = true; + } + + + public static async generatePersonThumbnail(photo: PhotoDTO) { + + // load parameters + + if (!photo.metadata.faces || photo.metadata.faces.length !== 1) { + throw new Error('Photo does not contain a face'); + } + + // load parameters + const mediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name); + const size: number = Config.Client.Media.Thumbnail.personThumbnailSize; + // generate thumbnail path + const thPath = path.join(ProjectPath.ThumbnailFolder, + PhotoProcessing.generatePersonThumbnailName(mediaPath, photo.metadata.faces[0], size)); + + + // check if thumbnail already exist + if (fs.existsSync(thPath) === true) { + return null; + } + + + const margin = { + x: Math.round(photo.metadata.faces[0].box.width * (Config.Server.Media.Thumbnail.personFaceMargin)), + y: Math.round(photo.metadata.faces[0].box.height * (Config.Server.Media.Thumbnail.personFaceMargin)) + }; + + + // run on other thread + const input = { + type: ThumbnailSourceType.Photo, + mediaPath: mediaPath, + size: size, + outPath: thPath, + makeSquare: false, + cut: { + left: Math.round(Math.max(0, photo.metadata.faces[0].box.left - margin.x / 2)), + top: Math.round(Math.max(0, photo.metadata.faces[0].box.top - margin.y / 2)), + width: photo.metadata.faces[0].box.width + margin.x, + height: photo.metadata.faces[0].box.height + margin.y + }, + qualityPriority: Config.Server.Media.Thumbnail.qualityPriority + }; + input.cut.width = Math.min(input.cut.width, photo.metadata.size.width - input.cut.left); + input.cut.height = Math.min(input.cut.height, photo.metadata.size.height - input.cut.top); + + await PhotoProcessing.taskQue.execute(input); + return thPath; + } + + + public static generateThumbnailName(mediaPath: string, size: number): string { + return crypto.createHash('md5').update(mediaPath).digest('hex') + '_' + size + '.jpg'; + } + + public static generatePersonThumbnailName(mediaPath: string, faceRegion: FaceRegion, size: number): string { + return crypto.createHash('md5').update(mediaPath + '_' + faceRegion.name + '_' + faceRegion.box.left + '_' + faceRegion.box.top) + .digest('hex') + '_' + size + '.jpg'; + } + + + public static generateConvertedFileName(photoPath: string): string { + const extension = path.extname(photoPath); + const file = path.basename(photoPath, extension); + const postfix = Config.Server.Media.Photo.converting.resolution; + return path.join(ProjectPath.TranscodedFolder, + ProjectPath.getRelativePathToImages(path.dirname(photoPath)), file + + '_' + postfix + '.jpg'); + } + + + public static async convertPhoto(mediaPath: string, size: number) { + // generate thumbnail path + const outPath = PhotoProcessing.generateConvertedFileName(mediaPath); + + + // check if file already exist + if (fs.existsSync(outPath) === true) { + return outPath; + } + + + // run on other thread + const input = { + type: ThumbnailSourceType.Photo, + mediaPath: mediaPath, + size: size, + outPath: outPath, + makeSquare: false, + qualityPriority: Config.Server.Media.Thumbnail.qualityPriority + }; + + const outDir = path.dirname(input.outPath); + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir, {recursive: true}); + } + await this.taskQue.execute(input); + return outPath; + } + + public static async generateThumbnail(mediaPath: string, + size: number, + sourceType: ThumbnailSourceType, + makeSquare: boolean) { + // generate thumbnail path + const outPath = path.join(ProjectPath.ThumbnailFolder, PhotoProcessing.generateThumbnailName(mediaPath, size)); + + + // check if thumbnail already exist + if (fs.existsSync(outPath) === true) { + return outPath; + } + + + // run on other thread + const input = { + type: sourceType, + mediaPath: mediaPath, + size: size, + outPath: outPath, + makeSquare: makeSquare, + qualityPriority: Config.Server.Media.Thumbnail.qualityPriority + }; + + const outDir = path.dirname(input.outPath); + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir, {recursive: true}); + } + await this.taskQue.execute(input); + return outPath; + } +} + diff --git a/src/backend/model/fileprocessing/VideoProcessing.ts b/src/backend/model/fileprocessing/VideoProcessing.ts new file mode 100644 index 00000000..f7009471 --- /dev/null +++ b/src/backend/model/fileprocessing/VideoProcessing.ts @@ -0,0 +1,67 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import * as util from 'util'; +import {ITaskExecuter, TaskExecuter} from '../../model/threading/TaskExecuter'; +import {VideoConverterInput, VideoConverterWorker} from '../../model/threading/VideoConverterWorker'; +import {MetadataLoader} from '../../model/threading/MetadataLoader'; +import {Config} from '../../../common/config/private/Config'; +import {ProjectPath} from '../../ProjectPath'; + +const existPr = util.promisify(fs.exists); + +export class VideoProcessing { + private static taskQue: ITaskExecuter = + new TaskExecuter(1, (input => VideoConverterWorker.convert(input))); + + + public static generateConvertedFileName(videoPath: string): string { + const extension = path.extname(videoPath); + const file = path.basename(videoPath, extension); + const postfix = Math.round(Config.Server.Media.Video.transcoding.bitRate / 1024) + 'k' + + Config.Server.Media.Video.transcoding.codec.toString().toLowerCase() + + Config.Server.Media.Video.transcoding.resolution; + return path.join(ProjectPath.TranscodedFolder, + ProjectPath.getRelativePathToImages(path.dirname(videoPath)), file + + '_' + postfix + '.' + Config.Server.Media.Video.transcoding.format); + } + + public static async convertVideo(videoPath: string): Promise { + + + const outPath = this.generateConvertedFileName(videoPath); + + if (await existPr(outPath)) { + return; + } + const metaData = await MetadataLoader.loadVideoMetadata(videoPath); + + const renderInput: VideoConverterInput = { + videoPath: videoPath, + output: { + path: outPath, + codec: Config.Server.Media.Video.transcoding.codec, + format: Config.Server.Media.Video.transcoding.format + } + }; + + if (metaData.bitRate > Config.Server.Media.Video.transcoding.bitRate) { + renderInput.output.bitRate = Config.Server.Media.Video.transcoding.bitRate; + } + if (metaData.fps > Config.Server.Media.Video.transcoding.fps) { + renderInput.output.fps = Config.Server.Media.Video.transcoding.fps; + } + + if (Config.Server.Media.Video.transcoding.resolution < metaData.size.height) { + renderInput.output.resolution = Config.Server.Media.Video.transcoding.resolution; + } + + const outDir = path.dirname(renderInput.output.path); + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir, {recursive: true}); + } + + await VideoProcessing.taskQue.execute(renderInput); + + } +} + diff --git a/src/backend/model/tasks/TaskManager.ts b/src/backend/model/tasks/TaskManager.ts index beb87801..5bfc6b88 100644 --- a/src/backend/model/tasks/TaskManager.ts +++ b/src/backend/model/tasks/TaskManager.ts @@ -1,4 +1,4 @@ -import {ITaskManager} from '../interfaces/ITaskManager'; +import {ITaskManager} from '../database/interfaces/ITaskManager'; import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO'; import {ITask} from './tasks/ITask'; import {TaskRepository} from './TaskRepository'; diff --git a/src/backend/model/tasks/tasks/FileTask.ts b/src/backend/model/tasks/tasks/FileTask.ts new file mode 100644 index 00000000..8796ada9 --- /dev/null +++ b/src/backend/model/tasks/tasks/FileTask.ts @@ -0,0 +1,66 @@ +import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/TaskProgressDTO'; +import {ConfigTemplateEntry} from '../../../../common/entities/task/TaskDTO'; +import {Task} from './Task'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as util from 'util'; +import {DiskManager} from '../../DiskManger'; +import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; + +declare var global: NodeJS.Global; + + +const LOG_TAG = '[FileTask]'; +const existsPr = util.promisify(fs.exists); + + +export abstract class FileTask extends Task { + public readonly ConfigTemplate: ConfigTemplateEntry[] = null; + directoryQueue: string[] = []; + fileQueue: T[] = []; + + + protected constructor(private scanFilter: DiskMangerWorker.DirectoryScanSettings) { + super(); + } + + protected async init() { + this.directoryQueue = []; + this.fileQueue = []; + this.directoryQueue.push('/'); + } + + protected abstract async processDirectory(directory: DirectoryDTO): Promise; + + protected abstract async processFile(file: T): Promise; + + protected async step(): Promise { + if ((this.directoryQueue.length === 0 && this.fileQueue.length === 0) + || this.state !== TaskState.running) { + if (global.gc) { + global.gc(); + } + return null; + } + + this.progress.time.current = Date.now(); + if (this.directoryQueue.length > 0) { + const directory = this.directoryQueue.shift(); + this.progress.comment = 'scanning directory: ' + directory; + const scanned = await DiskManager.scanDirectory(directory, this.scanFilter); + for (let i = 0; i < scanned.directories.length; i++) { + this.directoryQueue.push(path.join(scanned.directories[i].path, scanned.directories[i].name)); + } + this.fileQueue.push(...await this.processDirectory(scanned)); + } else if (this.fileQueue.length > 0) { + const file = this.fileQueue.shift(); + this.progress.left = this.fileQueue.length; + this.progress.progress++; + this.progress.comment = 'processing: ' + file; + await this.processFile(file); + } + return this.progress; + } + +} diff --git a/src/backend/model/tasks/tasks/IndexingTask.ts b/src/backend/model/tasks/tasks/IndexingTask.ts index 650a25d6..2ac2fb55 100644 --- a/src/backend/model/tasks/tasks/IndexingTask.ts +++ b/src/backend/model/tasks/tasks/IndexingTask.ts @@ -11,6 +11,7 @@ import {ThumbnailGeneratorMWs} from '../../../middlewares/thumbnail/ThumbnailGen import {Task} from './Task'; import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO'; import {ServerConfig} from '../../../../common/config/private/IPrivateConfig'; +import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing'; declare var global: NodeJS.Global; const LOG_TAG = '[IndexingTask]'; @@ -59,18 +60,18 @@ export class IndexingTask extends Task<{ createThumbnails: boolean }> { const media = scanned.media[i]; const mPath = path.join(ProjectPath.ImageFolder, media.directory.path, media.directory.name, media.name); const thPath = path.join(ProjectPath.ThumbnailFolder, - ThumbnailGeneratorMWs.generateThumbnailName(mPath, Config.Client.Thumbnail.thumbnailSizes[0])); + PhotoProcessing.generateThumbnailName(mPath, Config.Client.Media.Thumbnail.thumbnailSizes[0])); if (fs.existsSync(thPath)) { // skip existing thumbnails continue; } await ThumbnailWorker.render({ - type: MediaDTO.isVideo(media) ? ThumbnailSourceType.Video : ThumbnailSourceType.Image, + type: MediaDTO.isVideo(media) ? ThumbnailSourceType.Video : ThumbnailSourceType.Photo, mediaPath: mPath, - size: Config.Client.Thumbnail.thumbnailSizes[0], - thPath: thPath, + size: Config.Client.Media.Thumbnail.thumbnailSizes[0], + outPath: thPath, makeSquare: false, - qualityPriority: Config.Server.Thumbnail.qualityPriority - }, Config.Server.Thumbnail.processingLibrary); + qualityPriority: Config.Server.Media.Thumbnail.qualityPriority + }, Config.Server.Media.Thumbnail.processingLibrary); } catch (e) { console.error(e); Logger.error(LOG_TAG, 'Error during indexing job: ' + e.toString()); diff --git a/src/backend/model/tasks/tasks/PhotoConvertingTask.ts b/src/backend/model/tasks/tasks/PhotoConvertingTask.ts new file mode 100644 index 00000000..abc81fd6 --- /dev/null +++ b/src/backend/model/tasks/tasks/PhotoConvertingTask.ts @@ -0,0 +1,47 @@ +import {Config} from '../../../../common/config/private/Config'; +import {DefaultsTasks} from '../../../../common/entities/task/TaskDTO'; +import {ProjectPath} from '../../../ProjectPath'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as util from 'util'; +import {FileTask} from './FileTask'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; +import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing'; +import {ThumbnailSourceType} from '../../threading/ThumbnailWorker'; + +const LOG_TAG = '[PhotoConvertingTask]'; +const existsPr = util.promisify(fs.exists); + + +export class PhotoConvertingTask extends FileTask { + public readonly Name = DefaultsTasks[DefaultsTasks['Video Converting']]; + + constructor() { + super({noVideo: true, noMetaFile: true}); + } + + public get Supported(): boolean { + return Config.Server.Media.Photo.converting.enabled === true; + } + + protected async processDirectory(directory: DirectoryDTO): Promise { + const ret = []; + for (let i = 0; i < directory.media.length; ++i) { + const photoPath = path.join(ProjectPath.ImageFolder, + directory.media[i].directory.path, + directory.media[i].directory.name, + directory.media[i].name); + + if (await existsPr(PhotoProcessing.generateConvertedFileName(photoPath)) === false) { + ret.push(photoPath); + } + } + return ret; + } + + protected async processFile(file: string): Promise { + await PhotoProcessing.generateThumbnail(file, Config.Server.Media.Photo.converting.resolution, ThumbnailSourceType.Photo, false); + } + + +} diff --git a/src/backend/model/tasks/tasks/VideoConvertingTask.ts b/src/backend/model/tasks/tasks/VideoConvertingTask.ts index ab06d67f..823dd968 100644 --- a/src/backend/model/tasks/tasks/VideoConvertingTask.ts +++ b/src/backend/model/tasks/tasks/VideoConvertingTask.ts @@ -1,83 +1,46 @@ -import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/TaskProgressDTO'; import {Config} from '../../../../common/config/private/Config'; -import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO'; -import {Task} from './Task'; +import {DefaultsTasks} from '../../../../common/entities/task/TaskDTO'; import {ProjectPath} from '../../../ProjectPath'; -import {MediaDTO} from '../../../../common/entities/MediaDTO'; -import {Logger} from '../../../Logger'; import * as path from 'path'; import * as fs from 'fs'; import * as util from 'util'; -import {DiskManager} from '../../DiskManger'; -import {VideoConverterMWs} from '../../../middlewares/VideoConverterMWs'; - -declare var global: NodeJS.Global; - +import {FileTask} from './FileTask'; +import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; +import {VideoProcessing} from '../../fileprocessing/VideoProcessing'; const LOG_TAG = '[VideoConvertingTask]'; const existsPr = util.promisify(fs.exists); -export class VideoConvertingTask extends Task { +export class VideoConvertingTask extends FileTask { public readonly Name = DefaultsTasks[DefaultsTasks['Video Converting']]; - public readonly ConfigTemplate: ConfigTemplateEntry[] = null; - directoryQueue: string[] = []; - videoQueue: string[] = []; + + constructor() { + super({noPhoto: true, noMetaFile: true}); + } public get Supported(): boolean { - return Config.Client.Video.enabled === true; + return Config.Client.Media.Video.enabled === true; } - protected async init() { - this.directoryQueue = []; - this.videoQueue = []; - this.directoryQueue.push('/'); - } + protected async processDirectory(directory: DirectoryDTO): Promise { + const ret = []; + for (let i = 0; i < directory.media.length; ++i) { + const videoPath = path.join(ProjectPath.ImageFolder, + directory.media[i].directory.path, + directory.media[i].directory.name, + directory.media[i].name); - protected async step(): Promise { - if ((this.directoryQueue.length === 0 && this.videoQueue.length === 0) - || this.state !== TaskState.running) { - if (global.gc) { - global.gc(); - } - return null; - } - - this.progress.left = this.videoQueue.length; - this.progress.time.current = Date.now(); - if (this.directoryQueue.length > 0) { - const directory = this.directoryQueue.shift(); - this.progress.comment = 'scanning directory: ' + directory; - const scanned = await DiskManager.scanDirectory(directory, {noPhoto: true, noMetaFile: true}); - for (let i = 0; i < scanned.directories.length; i++) { - this.directoryQueue.push(path.join(scanned.directories[i].path, scanned.directories[i].name)); - } - - for (let i = 0; i < scanned.media.length; ++i) { - if (!MediaDTO.isVideo(scanned.media[i])) { - continue; - } - const videoPath = path.join(ProjectPath.ImageFolder, - scanned.media[i].directory.path, - scanned.media[i].directory.name, - scanned.media[i].name); - - if (await existsPr(VideoConverterMWs.generateConvertedFileName(videoPath)) === false) { - this.videoQueue.push(videoPath); - } - } - } else if (this.videoQueue.length > 0) { - const videoPath = this.videoQueue.shift(); - this.progress.progress++; - this.progress.comment = 'transcoding: ' + videoPath; - try { - await VideoConverterMWs.convertVideo(videoPath); - } catch (e) { - console.error(e); - Logger.error(LOG_TAG, 'Error during transcoding a video: ' + e.toString()); + if (await existsPr(VideoProcessing.generateConvertedFileName(videoPath)) === false) { + ret.push(videoPath); } } - return this.progress; + return ret; } + protected async processFile(file: string): Promise { + await VideoProcessing.convertVideo(file); + } + + } diff --git a/src/backend/model/threading/DiskMangerWorker.ts b/src/backend/model/threading/DiskMangerWorker.ts index 717d80e6..80a6a1c5 100644 --- a/src/backend/model/threading/DiskMangerWorker.ts +++ b/src/backend/model/threading/DiskMangerWorker.ts @@ -139,7 +139,7 @@ export class DiskMangerWorker { break; } } else if (DiskMangerWorker.isVideo(fullFilePath)) { - if (Config.Client.Video.enabled === false || settings.noVideo) { + if (Config.Client.Media.Video.enabled === false || settings.noVideo) { continue; } try { diff --git a/src/backend/model/threading/ThreadPool.ts b/src/backend/model/threading/ThreadPool.ts index d718ebc2..bae1b9b0 100644 --- a/src/backend/model/threading/ThreadPool.ts +++ b/src/backend/model/threading/ThreadPool.ts @@ -103,7 +103,7 @@ export class ThumbnailTH extends ThreadPool implements ITaskExecuter{ type: WorkerTaskTypes.thumbnail, input: input, - renderer: Config.Server.Thumbnail.processingLibrary + renderer: Config.Server.Media.Thumbnail.processingLibrary }); } } diff --git a/src/backend/model/threading/ThumbnailWorker.ts b/src/backend/model/threading/ThumbnailWorker.ts index c4c4941b..7135d26f 100644 --- a/src/backend/model/threading/ThumbnailWorker.ts +++ b/src/backend/model/threading/ThumbnailWorker.ts @@ -12,7 +12,7 @@ export class ThumbnailWorker { private static rendererType: ServerConfig.ThumbnailProcessingLib = null; public static render(input: RendererInput, renderer: ServerConfig.ThumbnailProcessingLib): Promise { - if (input.type === ThumbnailSourceType.Image) { + if (input.type === ThumbnailSourceType.Photo) { return this.renderFromImage(input, renderer); } return this.renderFromVideo(input); @@ -37,7 +37,7 @@ export class ThumbnailWorker { } export enum ThumbnailSourceType { - Image, Video + Photo = 1, Video = 2 } export interface RendererInput { @@ -45,7 +45,7 @@ export interface RendererInput { mediaPath: string; size: number; makeSquare: boolean; - thPath: string; + outPath: string; qualityPriority: boolean; cut?: { left: number, @@ -83,8 +83,8 @@ export class VideoRendererFactory { } const ratio = height / width; const command: FfmpegCommand = ffmpeg(input.mediaPath); - const fileName = path.basename(input.thPath); - const folder = path.dirname(input.thPath); + const fileName = path.basename(input.outPath); + const folder = path.dirname(input.outPath); let executedCmd = ''; command .on('start', (cmd) => { @@ -98,9 +98,9 @@ export class VideoRendererFactory { }) .outputOptions(['-qscale:v 4']); if (input.makeSquare === false) { - const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio)); + const newSize = width < height ? Math.min(input.size, width) + 'x?' : '?x' + Math.min(input.size, height); command.takeScreenshots({ - timemarks: ['10%'], size: newWidth + 'x?', filename: fileName, folder: folder + timemarks: ['10%'], size: newSize, filename: fileName, folder: folder }); @@ -156,9 +156,12 @@ export class ImageRendererFactory { ); } if (input.makeSquare === false) { - const newWidth = Math.sqrt((input.size * input.size) / ratio); + if (image.bitmap.width < image.bitmap.height) { + image.resize(Math.min(input.size, image.bitmap.width), Jimp.AUTO, algo); + } else { + image.resize(Jimp.AUTO, Math.min(image.size, image.bitmap.height), algo); + } - image.resize(newWidth, Jimp.AUTO, algo); } else { image.resize(input.size / Math.min(ratio, 1), Jimp.AUTO, algo); image.crop(0, 0, input.size, input.size); @@ -166,7 +169,7 @@ export class ImageRendererFactory { image.quality(60); // set JPEG quality await new Promise((resolve, reject) => { - image.write(input.thPath, (err: Error | null) => { // save + image.write(input.outPath, (err: Error | null) => { // save if (err) { return reject('[JimpThRenderer] ' + err.toString()); } @@ -202,10 +205,16 @@ export class ImageRendererFactory { image.extract(input.cut); } if (input.makeSquare === false) { - const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio)); - image.resize(newWidth, null, { - kernel: kernel - }); + if (metadata.height > metadata.width) { + image.resize(Math.min(input.size, metadata.width), null, { + kernel: kernel + }); + } else { + image.resize(null, Math.min(input.size, metadata.height), { + kernel: kernel + }); + } + } else { image @@ -215,7 +224,7 @@ export class ImageRendererFactory { fit: 'cover' }); } - await image.jpeg().toFile(input.thPath); + await image.jpeg().toFile(input.outPath); }; } @@ -252,7 +261,7 @@ export class ImageRendererFactory { image = image.resize(input.size, input.size) .crop(input.size, input.size); } - image.write(input.thPath, (e) => { + image.write(input.outPath, (e) => { if (e) { return reject('[GMThRenderer] ' + e.toString()); } diff --git a/src/backend/routes/GalleryRouter.ts b/src/backend/routes/GalleryRouter.ts index 62cc2a9e..b893b93c 100644 --- a/src/backend/routes/GalleryRouter.ts +++ b/src/backend/routes/GalleryRouter.ts @@ -7,17 +7,19 @@ import {UserRoles} from '../../common/entities/UserDTO'; import {ThumbnailSourceType} from '../model/threading/ThumbnailWorker'; import {VersionMWs} from '../middlewares/VersionMWs'; import {SupportedFormats} from '../../common/SupportedFormats'; +import {PhotoConverterMWs} from '../middlewares/thumbnail/PhotoConverterMWs'; export class GalleryRouter { public static route(app: Express) { this.addGetImageIcon(app); this.addGetVideoIcon(app); - this.addGetImageThumbnail(app); + this.addGetPhotoThumbnail(app); this.addGetVideoThumbnail(app); + this.addGetBestFitImage(app); this.addGetImage(app); - this.addGetVideo(app); this.addGetBestFitVideo(app); + this.addGetVideo(app); this.addGetMetaFile(app); this.addRandom(app); this.addDirectoryList(app); @@ -51,6 +53,17 @@ export class GalleryRouter { ); } + private static addGetBestFitImage(app: Express) { + app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/bestFit'], + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + GalleryMWs.loadFile, + PhotoConverterMWs.convertPhoto, + RenderingMWs.renderFile + ); + } + private static addGetVideo(app: Express) { app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))'], AuthenticationMWs.authenticate, @@ -66,6 +79,7 @@ export class GalleryRouter { AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), AuthenticationMWs.authorisePath('mediaPath', false), + GalleryMWs.loadFile, GalleryMWs.loadBestFitVideo, RenderingMWs.renderFile ); @@ -92,13 +106,13 @@ export class GalleryRouter { ); } - private static addGetImageThumbnail(app: Express) { + private static addGetPhotoThumbnail(app: Express) { app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/thumbnail/:size?', AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), AuthenticationMWs.authorisePath('mediaPath', false), GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Image), + ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Photo), RenderingMWs.renderFile ); } @@ -132,7 +146,7 @@ export class GalleryRouter { AuthenticationMWs.normalizePathParam('mediaPath'), AuthenticationMWs.authorisePath('mediaPath', false), GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Image), + ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Photo), RenderingMWs.renderFile ); } diff --git a/src/backend/server.ts b/src/backend/server.ts index 5b698ed5..4315ce77 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -16,6 +16,7 @@ import {Localizations} from './model/Localizations'; import {CookieNames} from '../common/CookieNames'; import {Router} from './routes/Router'; import {ServerConfig} from '../common/config/private/IPrivateConfig'; +import {PhotoProcessing} from './model/fileprocessing/PhotoProcessing'; const _session = require('cookie-session'); @@ -72,7 +73,7 @@ export class Server { this.app.use(cookieParser()); DiskManager.init(); - ThumbnailGeneratorMWs.init(); + PhotoProcessing.init(); Localizations.init(); this.app.use(locale(Config.Client.languages, 'en')); diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 6b418993..31bd029e 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -170,7 +170,7 @@ export class Utils { } - public static findClosest(number: number, arr: Array) { + public static findClosest(number: number, arr: number[]): number { let curr = arr[0]; let diff = Math.abs(number - curr); @@ -189,6 +189,25 @@ export class Utils { return curr; } + + public static findClosestinSorted(number: number, arr: number[]): number { + + let curr = arr[0]; + let diff = Math.abs(number - curr); + for (let i = 0; i < arr.length; ++i) { + + const newDiff = Math.abs(number - arr[i]); + if (newDiff > diff) { + break; + } + diff = newDiff; + curr = arr[i]; + } + + + return curr; + } + public static isUInt32(value: number, max: number = 4294967295) { return !isNaN(value) && value >= 0 && value <= max; } diff --git a/src/common/config/private/IPrivateConfig.ts b/src/common/config/private/IPrivateConfig.ts index 8f89ce1b..d4da08d1 100644 --- a/src/common/config/private/IPrivateConfig.ts +++ b/src/common/config/private/IPrivateConfig.ts @@ -43,7 +43,6 @@ export module ServerConfig { } export interface ThumbnailConfig { - folder: string; processingLibrary: ThumbnailProcessingLib; qualityPriority: boolean; personFaceMargin: number; // in ration [0-1] @@ -98,21 +97,36 @@ export module ServerConfig { } + export interface PhotoConfig { + converting: { + enabled: boolean; + onTheFly: boolean; + resolution: resolutionType + }; + } + + export interface MediaConfig { + folder: string; + tempFolder: string; + Video: VideoConfig; + Photo: PhotoConfig; + Thumbnail: ThumbnailConfig; + } + + export interface Config { port: number; host: string; - imagesFolder: string; - Thumbnail: ThumbnailConfig; + Media: MediaConfig; Threading: ThreadingConfig; Database: DataBaseConfig; Sharing: SharingConfig; sessionTimeout: number; Indexing: IndexingConfig; - photoMetadataSize: number; + photoMetadataSize: number; // only this many bites will be loaded when scanning photo for metadata Duplicates: DuplicatesConfig; Log: LogConfig; Tasks: TaskConfig; - Video: VideoConfig; } } diff --git a/src/common/config/private/PrivateConfigDefaultsClass.ts b/src/common/config/private/PrivateConfigDefaultsClass.ts index 4e57c361..2b122f71 100644 --- a/src/common/config/private/PrivateConfigDefaultsClass.ts +++ b/src/common/config/private/PrivateConfigDefaultsClass.ts @@ -11,12 +11,30 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr public Server: ServerConfig.Config = { port: 80, host: '0.0.0.0', - imagesFolder: 'demo/images', - Thumbnail: { - folder: 'demo/tmp', - processingLibrary: ServerConfig.ThumbnailProcessingLib.sharp, - qualityPriority: true, - personFaceMargin: 0.6 + Media: { + folder: 'demo/images', + tempFolder: 'demo/tmp', + Thumbnail: { + processingLibrary: ServerConfig.ThumbnailProcessingLib.sharp, + qualityPriority: true, + personFaceMargin: 0.6 + }, + Photo: { + converting: { + enabled: true, + onTheFly: true, + resolution: 1080 + } + }, + Video: { + transcoding: { + bitRate: 5 * 1024 * 1024, + codec: 'libx264', + format: 'mp4', + fps: 25, + resolution: 720 + } + } }, Log: { level: ServerConfig.LogLevel.info, @@ -71,15 +89,6 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr config: {}, trigger: {type: TaskTriggerType.never} }] - }, - Video: { - transcoding: { - bitRate: 5 * 1024 * 1024, - codec: 'libx264', - format: 'mp4', - fps: 25, - resolution: 720 - } } }; } diff --git a/src/common/config/public/ConfigClass.ts b/src/common/config/public/ConfigClass.ts index ce85cf17..eee35187 100644 --- a/src/common/config/public/ConfigClass.ts +++ b/src/common/config/public/ConfigClass.ts @@ -68,6 +68,11 @@ export module ClientConfig { enabled: boolean; } + export interface MediaConfig { + Thumbnail: ThumbnailConfig; + Video: VideoConfig; + } + export interface MetaFileConfig { enabled: boolean; } @@ -83,7 +88,6 @@ export module ClientConfig { applicationTitle: string; publicUrl: string; urlBase: string; - Thumbnail: ThumbnailConfig; Search: SearchConfig; Sharing: SharingConfig; Map: MapConfig; @@ -92,7 +96,7 @@ export module ClientConfig { authenticationRequired: boolean; unAuthenticatedUserRole: UserRoles; languages: string[]; - Video: VideoConfig; + Media: MediaConfig; MetaFile: MetaFileConfig; Faces: FacesConfig; } @@ -107,11 +111,16 @@ export class PublicConfigClass { public Client: ClientConfig.Config = { applicationTitle: 'PiGallery 2', appVersion: '', - Thumbnail: { - concurrentThumbnailGenerations: 1, - thumbnailSizes: [200, 400, 600], - iconSize: 45, - personThumbnailSize: 200 + Media: { + Video: { + enabled: true + }, + Thumbnail: { + concurrentThumbnailGenerations: 1, + thumbnailSizes: [160, 240, 480], + iconSize: 45, + personThumbnailSize: 200 + } }, Search: { enabled: true, @@ -139,9 +148,6 @@ export class PublicConfigClass { RandomPhoto: { enabled: true }, - Video: { - enabled: true - }, MetaFile: { enabled: true }, diff --git a/src/frontend/app/ui/gallery/Media.ts b/src/frontend/app/ui/gallery/Media.ts index e0cb7125..c2709e65 100644 --- a/src/frontend/app/ui/gallery/Media.ts +++ b/src/frontend/app/ui/gallery/Media.ts @@ -5,6 +5,8 @@ import {MediaDTO} from '../../../../common/entities/MediaDTO'; export class Media extends MediaIcon { + static readonly sortedThumbnailSizes = Config.Client.Media.Thumbnail.thumbnailSizes + .sort((a, b) => a - b); constructor(media: MediaDTO, public renderWidth: number, public renderHeight: number) { super(media); @@ -19,8 +21,8 @@ export class Media extends MediaIcon { } getThumbnailSize() { - const renderSize = Math.sqrt(this.renderWidth * this.renderHeight); - return Utils.findClosest(renderSize, Config.Client.Thumbnail.thumbnailSizes); + const longerEdge = Math.max(this.renderWidth * this.renderHeight); + return Utils.findClosestinSorted(longerEdge, Media.sortedThumbnailSizes); } getReplacementThumbnailSize(): number { diff --git a/src/frontend/app/ui/gallery/lightbox/media/media.lightbox.gallery.component.ts b/src/frontend/app/ui/gallery/lightbox/media/media.lightbox.gallery.component.ts index afa515e1..3920b6a2 100644 --- a/src/frontend/app/ui/gallery/lightbox/media/media.lightbox.gallery.component.ts +++ b/src/frontend/app/ui/gallery/lightbox/media/media.lightbox.gallery.component.ts @@ -27,6 +27,7 @@ export class GalleryLightboxMediaComponent implements OnChanges { public imageLoadFinished = false; thumbnailSrc: string = null; photoSrc: string = null; + isPhotoSrcBestFit = true; public transcodeNeedVideos = SupportedFormats.TranscodeNeed.Videos; private mediaLoaded = false; private videoProgress = 0; @@ -84,9 +85,6 @@ export class GalleryLightboxMediaComponent implements OnChanges { return this.video.nativeElement.paused; } - public get PhotoSrc(): string { - return this.gridMedia.getMediaPath(); - } private get ThumbnailUrl(): string { if (this.gridMedia.isThumbnailAvailable() === true) { @@ -113,10 +111,25 @@ export class GalleryLightboxMediaComponent implements OnChanges { .then((src) => this.thumbnailSrc = src); } - if (this.photoSrc == null && this.gridMedia && this.loadMedia) { + if (this.zoom === 1) { + if (this.photoSrc == null && this.gridMedia && this.loadMedia) { + FixOrientationPipe.transform(this.gridMedia.getBestFitMediaPath(), this.gridMedia.Orientation) + .then((src) => { + this.photoSrc = src; + this.isPhotoSrcBestFit = true; + }); + } + // on zoom load high res photo + } else if ((this.isPhotoSrcBestFit === true || + this.photoSrc == null) && this.gridMedia && this.loadMedia) { FixOrientationPipe.transform(this.gridMedia.getMediaPath(), this.gridMedia.Orientation) - .then((src) => this.photoSrc = src); + .then((src) => { + this.photoSrc = src; + this.isPhotoSrcBestFit = false; + }); } + + } public mute() { @@ -141,7 +154,7 @@ export class GalleryLightboxMediaComponent implements OnChanges { onImageError() { // TODO:handle error this.imageLoadFinished = true; - console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getMediaPath()); + console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getBestFitMediaPath()); } onImageLoad() { diff --git a/src/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts b/src/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts index 8b0577f9..89f2e5d8 100644 --- a/src/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts +++ b/src/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts @@ -35,8 +35,8 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit { paths: LatLng[][] = []; @ViewChild('root', {static: true}) elementRef: ElementRef; @ViewChild('yagaMap', {static: true}) yagaMap: MapComponent; - public smallIconSize = new Point(Config.Client.Thumbnail.iconSize * 0.75, Config.Client.Thumbnail.iconSize * 0.75); - public iconSize = new Point(Config.Client.Thumbnail.iconSize, Config.Client.Thumbnail.iconSize); + public smallIconSize = new Point(Config.Client.Media.Thumbnail.iconSize * 0.75, Config.Client.Media.Thumbnail.iconSize * 0.75); + public iconSize = new Point(Config.Client.Media.Thumbnail.iconSize, Config.Client.Media.Thumbnail.iconSize); private startPosition: Dimension = null; constructor(public fullScreenService: FullScreenService, diff --git a/src/frontend/app/ui/gallery/thumbnailLoader.service.ts b/src/frontend/app/ui/gallery/thumbnailLoader.service.ts index 98f9c2f6..a3dc3c71 100644 --- a/src/frontend/app/ui/gallery/thumbnailLoader.service.ts +++ b/src/frontend/app/ui/gallery/thumbnailLoader.service.ts @@ -19,7 +19,7 @@ export class ThumbnailLoaderService { } run = () => { - if (this.que.length === 0 || this.runningRequests >= Config.Client.Thumbnail.concurrentThumbnailGenerations) { + if (this.que.length === 0 || this.runningRequests >= Config.Client.Media.Thumbnail.concurrentThumbnailGenerations) { return; } const task = this.getNextTask(); diff --git a/src/frontend/app/ui/settings/basic/basic.settings.component.ts b/src/frontend/app/ui/settings/basic/basic.settings.component.ts index 8b390b2d..4477f945 100644 --- a/src/frontend/app/ui/settings/basic/basic.settings.component.ts +++ b/src/frontend/app/ui/settings/basic/basic.settings.component.ts @@ -28,7 +28,7 @@ export class BasicSettingsComponent extends SettingsComponent { super(i18n('Basic'), _authService, _navigation, _settingsService, notification, i18n, s => ({ port: s.Server.port, host: s.Server.host, - imagesFolder: s.Server.imagesFolder, + imagesFolder: s.Server.Media.folder, applicationTitle: s.Client.applicationTitle, publicUrl: s.Client.publicUrl, urlBase: s.Client.urlBase diff --git a/src/frontend/app/ui/settings/tasks/progress/progress.tasks.settings.component.html b/src/frontend/app/ui/settings/tasks/progress/progress.tasks.settings.component.html index c7e04178..38be99c0 100644 --- a/src/frontend/app/ui/settings/tasks/progress/progress.tasks.settings.component.html +++ b/src/frontend/app/ui/settings/tasks/progress/progress.tasks.settings.component.html @@ -11,8 +11,8 @@
-
{{TimeElapsed| duration}}
-
+
{{TimeElapsed| duration}}
+
-
{{TimeAll| duration}}
+
{{TimeAll| duration}}
diff --git a/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.html b/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.html index 65165072..f512fb92 100644 --- a/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.html +++ b/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.html @@ -34,18 +34,18 @@
-
- -
- - Thumbnails will be saved in this folder. Write access is required - + + + + + + + + + -
-
+ +
diff --git a/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.ts b/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.ts index 5df8688f..e2a548fe 100644 --- a/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.ts +++ b/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.ts @@ -28,8 +28,8 @@ export class ThumbnailSettingsComponent notification: NotificationService, i18n: I18n) { super(i18n('Thumbnail'), _authService, _navigation, _settingsService, notification, i18n, s => ({ - client: s.Client.Thumbnail, - server: s.Server.Thumbnail + client: s.Client.Media.Thumbnail, + server: s.Server.Media.Thumbnail })); } diff --git a/src/frontend/app/ui/settings/video/video.settings.component.ts b/src/frontend/app/ui/settings/video/video.settings.component.ts index f2f2548a..e7ab463d 100644 --- a/src/frontend/app/ui/settings/video/video.settings.component.ts +++ b/src/frontend/app/ui/settings/video/video.settings.component.ts @@ -36,8 +36,8 @@ export class VideoSettingsComponent extends SettingsComponent<{ server: ServerCo notification: NotificationService, i18n: I18n) { super(i18n('Video'), _authService, _navigation, _settingsService, notification, i18n, s => ({ - client: s.Client.Video, - server: s.Server.Video + client: s.Client.Media.Video, + server: s.Server.Media.Video })); } diff --git a/test/backend/SQLTestHelper.ts b/test/backend/SQLTestHelper.ts index 51c43dc0..221327a9 100644 --- a/test/backend/SQLTestHelper.ts +++ b/test/backend/SQLTestHelper.ts @@ -1,7 +1,7 @@ import {Config} from '../../src/common/config/private/Config'; import * as fs from 'fs'; import * as path from 'path'; -import {SQLConnection} from '../../src/backend/model/sql/SQLConnection'; +import {SQLConnection} from '../../src/backend/model/database/sql/SQLConnection'; import {ServerConfig} from '../../src/common/config/private/IPrivateConfig'; declare let describe: any; diff --git a/test/backend/integration/model/sql/typeorm.ts b/test/backend/integration/model/sql/typeorm.ts index b7c05d8c..3c717fe9 100644 --- a/test/backend/integration/model/sql/typeorm.ts +++ b/test/backend/integration/model/sql/typeorm.ts @@ -2,20 +2,20 @@ import {expect} from 'chai'; import * as fs from 'fs'; import * as path from 'path'; import {Config} from '../../../../../src/common/config/private/Config'; -import {SQLConnection} from '../../../../../src/backend/model/sql/SQLConnection'; -import {UserEntity} from '../../../../../src/backend/model/sql/enitites/UserEntity'; +import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection'; +import {UserEntity} from '../../../../../src/backend/model/database/sql/enitites/UserEntity'; import {UserRoles} from '../../../../../src/common/entities/UserDTO'; import {PasswordHelper} from '../../../../../src/backend/model/PasswordHelper'; -import {DirectoryEntity} from '../../../../../src/backend/model/sql/enitites/DirectoryEntity'; +import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity'; import { CameraMetadataEntity, GPSMetadataEntity, PhotoEntity, PhotoMetadataEntity, PositionMetaDataEntity -} from '../../../../../src/backend/model/sql/enitites/PhotoEntity'; -import {MediaDimensionEntity} from '../../../../../src/backend/model/sql/enitites/MediaEntity'; -import {VersionEntity} from '../../../../../src/backend/model/sql/enitites/VersionEntity'; +} from '../../../../../src/backend/model/database/sql/enitites/PhotoEntity'; +import {MediaDimensionEntity} from '../../../../../src/backend/model/database/sql/enitites/MediaEntity'; +import {VersionEntity} from '../../../../../src/backend/model/database/sql/enitites/VersionEntity'; import {ServerConfig} from '../../../../../src/common/config/private/IPrivateConfig'; describe('Typeorm integration', () => { diff --git a/test/backend/unit/middlewares/user/AuthenticationMWs.ts b/test/backend/unit/middlewares/user/AuthenticationMWs.ts index f6427259..68cda1f5 100644 --- a/test/backend/unit/middlewares/user/AuthenticationMWs.ts +++ b/test/backend/unit/middlewares/user/AuthenticationMWs.ts @@ -3,9 +3,9 @@ import {AuthenticationMWs} from '../../../../../src/backend/middlewares/user/Aut import {ErrorCodes, ErrorDTO} from '../../../../../src/common/entities/Error'; import {UserDTO, UserRoles} from '../../../../../src/common/entities/UserDTO'; import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers'; -import {UserManager} from '../../../../../src/backend/model/memory/UserManager'; +import {UserManager} from '../../../../../src/backend/model/database/memory/UserManager'; import {Config} from '../../../../../src/common/config/private/Config'; -import {IUserManager} from '../../../../../src/backend/model/interfaces/IUserManager'; +import {IUserManager} from '../../../../../src/backend/model/database/interfaces/IUserManager'; import * as path from 'path'; diff --git a/test/backend/unit/model/sql/GalleryManager.ts b/test/backend/unit/model/sql/GalleryManager.ts index 3e22c9b8..1e409a1e 100644 --- a/test/backend/unit/model/sql/GalleryManager.ts +++ b/test/backend/unit/model/sql/GalleryManager.ts @@ -1,14 +1,14 @@ import {expect} from 'chai'; import {TestHelper} from './TestHelper'; import {SQLTestHelper} from '../../../SQLTestHelper'; -import {GalleryManager} from '../../../../../src/backend/model/sql/GalleryManager'; -import {IndexingManager} from '../../../../../src/backend/model/sql/IndexingManager'; +import {GalleryManager} from '../../../../../src/backend/model/database/sql/GalleryManager'; +import {IndexingManager} from '../../../../../src/backend/model/database/sql/IndexingManager'; import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO'; import {Utils} from '../../../../../src/common/Utils'; import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers'; -import {PersonManager} from '../../../../../src/backend/model/sql/PersonManager'; -import {MediaEntity} from '../../../../../src/backend/model/sql/enitites/MediaEntity'; -import {VersionManager} from '../../../../../src/backend/model/sql/VersionManager'; +import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager'; +import {MediaEntity} from '../../../../../src/backend/model/database/sql/enitites/MediaEntity'; +import {VersionManager} from '../../../../../src/backend/model/database/sql/VersionManager'; class IndexingManagerTest extends IndexingManager { diff --git a/test/backend/unit/model/sql/IndexingManager.ts b/test/backend/unit/model/sql/IndexingManager.ts index 9bcf93a3..1cd4c0bb 100644 --- a/test/backend/unit/model/sql/IndexingManager.ts +++ b/test/backend/unit/model/sql/IndexingManager.ts @@ -1,20 +1,20 @@ import {expect} from 'chai'; import * as fs from 'fs'; import {Config} from '../../../../../src/common/config/private/Config'; -import {SQLConnection} from '../../../../../src/backend/model/sql/SQLConnection'; -import {GalleryManager} from '../../../../../src/backend/model/sql/GalleryManager'; +import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection'; +import {GalleryManager} from '../../../../../src/backend/model/database/sql/GalleryManager'; import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO'; import {TestHelper} from './TestHelper'; import {Connection} from 'typeorm'; -import {DirectoryEntity} from '../../../../../src/backend/model/sql/enitites/DirectoryEntity'; +import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity'; import {Utils} from '../../../../../src/common/Utils'; import {MediaDTO} from '../../../../../src/common/entities/MediaDTO'; import {FileDTO} from '../../../../../src/common/entities/FileDTO'; -import {IndexingManager} from '../../../../../src/backend/model/sql/IndexingManager'; +import {IndexingManager} from '../../../../../src/backend/model/database/sql/IndexingManager'; import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers'; -import {PersonManager} from '../../../../../src/backend/model/sql/PersonManager'; +import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager'; import {SQLTestHelper} from '../../../SQLTestHelper'; -import {VersionManager} from '../../../../../src/backend/model/sql/VersionManager'; +import {VersionManager} from '../../../../../src/backend/model/database/sql/VersionManager'; import {DiskMangerWorker} from '../../../../../src/backend/model/threading/DiskMangerWorker'; import {ServerConfig} from '../../../../../src/common/config/private/IPrivateConfig'; diff --git a/test/backend/unit/model/sql/PersonManager.ts b/test/backend/unit/model/sql/PersonManager.ts index 0e539c6e..a9178618 100644 --- a/test/backend/unit/model/sql/PersonManager.ts +++ b/test/backend/unit/model/sql/PersonManager.ts @@ -1,4 +1,4 @@ -import {PersonManager} from '../../../../../src/backend/model/sql/PersonManager'; +import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager'; // to help WebStorm to handle the test cases diff --git a/test/backend/unit/model/sql/SearchManager.ts b/test/backend/unit/model/sql/SearchManager.ts index ef584ca5..d0c9a6b5 100644 --- a/test/backend/unit/model/sql/SearchManager.ts +++ b/test/backend/unit/model/sql/SearchManager.ts @@ -1,15 +1,15 @@ import {expect} from 'chai'; -import {SQLConnection} from '../../../../../src/backend/model/sql/SQLConnection'; -import {PhotoEntity} from '../../../../../src/backend/model/sql/enitites/PhotoEntity'; -import {SearchManager} from '../../../../../src/backend/model/sql/SearchManager'; +import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection'; +import {PhotoEntity} from '../../../../../src/backend/model/database/sql/enitites/PhotoEntity'; +import {SearchManager} from '../../../../../src/backend/model/database/sql/SearchManager'; import {AutoCompleteItem, SearchTypes} from '../../../../../src/common/entities/AutoCompleteItem'; import {SearchResultDTO} from '../../../../../src/common/entities/SearchResultDTO'; -import {DirectoryEntity} from '../../../../../src/backend/model/sql/enitites/DirectoryEntity'; +import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity'; import {Utils} from '../../../../../src/common/Utils'; import {TestHelper} from './TestHelper'; -import {VideoEntity} from '../../../../../src/backend/model/sql/enitites/VideoEntity'; -import {PersonEntry} from '../../../../../src/backend/model/sql/enitites/PersonEntry'; -import {FaceRegionEntry} from '../../../../../src/backend/model/sql/enitites/FaceRegionEntry'; +import {VideoEntity} from '../../../../../src/backend/model/database/sql/enitites/VideoEntity'; +import {PersonEntry} from '../../../../../src/backend/model/database/sql/enitites/PersonEntry'; +import {FaceRegionEntry} from '../../../../../src/backend/model/database/sql/enitites/FaceRegionEntry'; import {PhotoDTO} from '../../../../../src/common/entities/PhotoDTO'; import {SQLTestHelper} from '../../../SQLTestHelper'; import {Config} from '../../../../../src/common/config/private/Config'; diff --git a/test/backend/unit/model/sql/SharingManager.ts b/test/backend/unit/model/sql/SharingManager.ts index 86d05499..06e29ed3 100644 --- a/test/backend/unit/model/sql/SharingManager.ts +++ b/test/backend/unit/model/sql/SharingManager.ts @@ -1,8 +1,8 @@ import {expect} from 'chai'; -import {SQLConnection} from '../../../../../src/backend/model/sql/SQLConnection'; -import {SharingManager} from '../../../../../src/backend/model/sql/SharingManager'; +import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection'; +import {SharingManager} from '../../../../../src/backend/model/database/sql/SharingManager'; import {SharingDTO} from '../../../../../src/common/entities/SharingDTO'; -import {UserEntity} from '../../../../../src/backend/model/sql/enitites/UserEntity'; +import {UserEntity} from '../../../../../src/backend/model/database/sql/enitites/UserEntity'; import {UserDTO, UserRoles} from '../../../../../src/common/entities/UserDTO'; import {SQLTestHelper} from '../../../SQLTestHelper'; diff --git a/test/backend/unit/model/sql/TestHelper.ts b/test/backend/unit/model/sql/TestHelper.ts index cae95d79..5bc32dc1 100644 --- a/test/backend/unit/model/sql/TestHelper.ts +++ b/test/backend/unit/model/sql/TestHelper.ts @@ -1,14 +1,14 @@ -import {MediaDimensionEntity} from '../../../../../src/backend/model/sql/enitites/MediaEntity'; +import {MediaDimensionEntity} from '../../../../../src/backend/model/database/sql/enitites/MediaEntity'; import { CameraMetadataEntity, GPSMetadataEntity, PhotoEntity, PhotoMetadataEntity, PositionMetaDataEntity -} from '../../../../../src/backend/model/sql/enitites/PhotoEntity'; +} from '../../../../../src/backend/model/database/sql/enitites/PhotoEntity'; import {OrientationTypes} from 'ts-exif-parser'; -import {DirectoryEntity} from '../../../../../src/backend/model/sql/enitites/DirectoryEntity'; -import {VideoEntity, VideoMetadataEntity} from '../../../../../src/backend/model/sql/enitites/VideoEntity'; +import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity'; +import {VideoEntity, VideoMetadataEntity} from '../../../../../src/backend/model/database/sql/enitites/VideoEntity'; import {MediaDimension} from '../../../../../src/common/entities/MediaDTO'; import {CameraMetadata, FaceRegion, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../../src/common/entities/PhotoDTO'; import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO'; diff --git a/test/backend/unit/model/threading/DiskMangerWorker.spec.ts b/test/backend/unit/model/threading/DiskMangerWorker.spec.ts index 91cbd8b9..c0e307d1 100644 --- a/test/backend/unit/model/threading/DiskMangerWorker.spec.ts +++ b/test/backend/unit/model/threading/DiskMangerWorker.spec.ts @@ -8,7 +8,7 @@ import {Utils} from '../../../../../src/common/Utils'; describe('DiskMangerWorker', () => { it('should parse metadata', async () => { - Config.Server.imagesFolder = path.join(__dirname, '/../../assets'); + Config.Server.Media.folder = path.join(__dirname, '/../../assets'); ProjectPath.ImageFolder = path.join(__dirname, '/../../assets'); const dir = await DiskMangerWorker.scanDirectory('/'); expect(dir.media.length).to.be.equals(4); diff --git a/test/common/unit/Utils.spec.ts b/test/common/unit/Utils.spec.ts index 56831ed5..0c387b8a 100644 --- a/test/common/unit/Utils.spec.ts +++ b/test/common/unit/Utils.spec.ts @@ -15,4 +15,21 @@ describe('Utils', () => { expect(Utils.concatUrls('abc\\/', '/cde/')).to.be.equal('abc/cde'); expect(Utils.concatUrls('abc\\/', '/cde/', 'fgh')).to.be.equal('abc/cde/fgh'); }); + + it('should find closest number', () => { + + expect(Utils.findClosest(10, [10, 13, 4, 20])).to.be.equal(10); + expect(Utils.findClosest(10, [13, 4, 20])).to.be.equal(13); + expect(Utils.findClosest(10, [4, 20])).to.be.equal(4); + expect(Utils.findClosest(10, [20])).to.be.equal(20); + }); + it('should find closest number in sorted array', () => { + + expect(Utils.findClosestinSorted(10, [3, 5, 8, 10, 15, 20])).to.be.equal(10); + expect(Utils.findClosestinSorted(10, [3, 5, 8, 15, 20])).to.be.equal(8); + expect(Utils.findClosestinSorted(10, [3, 5, 15, 20])).to.be.equal(15); + expect(Utils.findClosestinSorted(10, [3, 5, 20])).to.be.equal(5); + expect(Utils.findClosestinSorted(10, [3, 20])).to.be.equal(3); + expect(Utils.findClosestinSorted(10, [20])).to.be.equal(20); + }); });