improve settings

This commit is contained in:
Patrik J. Braun 2019-12-15 14:40:31 +01:00
parent 95b06ffc63
commit 5160e19a35
36 changed files with 428 additions and 212 deletions

View File

@ -359,7 +359,7 @@ export class SettingsMWs {
original.Client.Other.enableOnScrollThumbnailPrioritising = settings.Client.enableOnScrollThumbnailPrioritising;
original.Client.Other.defaultPhotoSortingMethod = settings.Client.defaultPhotoSortingMethod;
original.Client.Other.NavBar.showItemCount = settings.Client.NavBar.showItemCount;
original.Server.Threading.enable = settings.Server.enable;
original.Server.Threading.enabled = settings.Server.enabled;
original.Server.Threading.thumbnailThreads = settings.Server.thumbnailThreads;
original.save();
await ConfigDiagnostics.runDiagnostics();

View File

@ -11,7 +11,7 @@ export class DiskManager {
static threadPool: DiskManagerTH = null;
public static init() {
if (Config.Server.Threading.enable === true) {
if (Config.Server.Threading.enabled === true) {
DiskManager.threadPool = new DiskManagerTH(1);
}
}
@ -23,7 +23,7 @@ export class DiskManager {
let directory: DirectoryDTO;
if (Config.Server.Threading.enable === true) {
if (Config.Server.Threading.enabled === true) {
directory = await DiskManager.threadPool.execute(relativeDirectoryName, settings);
} else {
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings);

View File

@ -81,13 +81,13 @@ export class ConfigDiagnostics {
}
}
static async testThumbnailLib(processingLibrary: ServerConfig.ThumbnailProcessingLib) {
static async testThumbnailLib(processingLibrary: ServerConfig.PhotoProcessingLib) {
switch (processingLibrary) {
case ServerConfig.ThumbnailProcessingLib.sharp:
case ServerConfig.PhotoProcessingLib.sharp:
const sharp = require('sharp');
sharp();
break;
case ServerConfig.ThumbnailProcessingLib.gm:
case ServerConfig.PhotoProcessingLib.gm:
const gm = require('gm');
await new Promise((resolve, reject) => {
gm(ProjectPath.FrontendFolder + '/assets/icon.png').size((err: Error) => {
@ -120,8 +120,10 @@ export class ConfigDiagnostics {
}
static async testServerThumbnailConfig(thumbnailConfig: ServerConfig.ThumbnailConfig) {
await ConfigDiagnostics.testThumbnailLib(thumbnailConfig.processingLibrary);
public static async testServerThumbnailConfig(server: ServerConfig.ThumbnailConfig) {
if (server.personFaceMargin < 0 || server.personFaceMargin > 1) {
throw new Error('personFaceMargin should be between 0 and 1');
}
}
static async testClientThumbnailConfig(thumbnailConfig: ClientConfig.ThumbnailConfig) {
@ -218,19 +220,19 @@ export class ConfigDiagnostics {
}
}
if (Config.Server.Media.Thumbnail.processingLibrary !== ServerConfig.ThumbnailProcessingLib.Jimp) {
if (Config.Server.Media.photoProcessingLibrary !== ServerConfig.PhotoProcessingLib.Jimp) {
try {
await ConfigDiagnostics.testThumbnailLib(Config.Server.Media.Thumbnail.processingLibrary);
await ConfigDiagnostics.testThumbnailLib(Config.Server.Media.photoProcessingLibrary);
} catch (ex) {
const err: Error = ex;
NotificationManager.warning('Thumbnail hardware acceleration is not possible.' +
' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Media.Thumbnail.processingLibrary] + '\' node module is not found.' +
' \'' + ServerConfig.PhotoProcessingLib[Config.Server.Media.photoProcessingLibrary] + '\' 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.Media.Thumbnail.processingLibrary] + '\' node module is not found.' +
' \'' + ServerConfig.PhotoProcessingLib[Config.Server.Media.photoProcessingLibrary] + '\' node module is not found.' +
' Falling back temporally to JS based thumbnail generation');
Config.Server.Media.Thumbnail.processingLibrary = ServerConfig.ThumbnailProcessingLib.Jimp;
Config.Server.Media.photoProcessingLibrary = ServerConfig.PhotoProcessingLib.Jimp;
}
}
@ -343,4 +345,5 @@ export class ConfigDiagnostics {
}
}
}

View File

@ -22,7 +22,7 @@ export class PhotoProcessing {
}
if (Config.Server.Threading.enable === true) {
if (Config.Server.Threading.enabled === true) {
if (Config.Server.Threading.thumbnailThreads > 0) {
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads;
} else {
@ -32,12 +32,12 @@ export class PhotoProcessing {
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = 1;
}
if (Config.Server.Threading.enable === true &&
Config.Server.Media.Thumbnail.processingLibrary === ServerConfig.ThumbnailProcessingLib.Jimp) {
if (Config.Server.Threading.enabled === true &&
Config.Server.Media.photoProcessingLibrary === ServerConfig.PhotoProcessingLib.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)));
(input => ThumbnailWorker.render(input, Config.Server.Media.photoProcessingLibrary)));
}
this.initDone = true;

View File

@ -2,6 +2,7 @@ import {ITask} from './tasks/ITask';
import {IndexingTask} from './tasks/IndexingTask';
import {DBRestTask} from './tasks/DBResetTask';
import {VideoConvertingTask} from './tasks/VideoConvertingTask';
import {PhotoConvertingTask} from './tasks/PhotoConvertingTask';
export class TaskRepository {
@ -28,3 +29,4 @@ export class TaskRepository {
TaskRepository.Instance.register(new IndexingTask());
TaskRepository.Instance.register(new DBRestTask());
TaskRepository.Instance.register(new VideoConvertingTask());
TaskRepository.Instance.register(new PhotoConvertingTask());

View File

@ -2,17 +2,15 @@ import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/T
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';
import {Logger} from '../../../Logger';
declare var global: NodeJS.Global;
const LOG_TAG = '[FileTask]';
const existsPr = util.promisify(fs.exists);
export abstract class FileTask<T> extends Task {
@ -58,7 +56,12 @@ export abstract class FileTask<T> extends Task {
this.progress.left = this.fileQueue.length;
this.progress.progress++;
this.progress.comment = 'processing: ' + file;
try {
await this.processFile(file);
} catch (e) {
console.error(e);
Logger.error(LOG_TAG, 'Error during processing file: ' + e.toString());
}
}
return this.progress;
}

View File

@ -1,30 +1,18 @@
import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/TaskProgressDTO';
import {ObjectManagers} from '../../ObjectManagers';
import * as path from 'path';
import * as fs from 'fs';
import {Logger} from '../../../Logger';
import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../../threading/ThumbnailWorker';
import {Config} from '../../../../common/config/private/Config';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {ProjectPath} from '../../../ProjectPath';
import {ThumbnailGeneratorMWs} from '../../../middlewares/thumbnail/ThumbnailGeneratorMWs';
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]';
export class IndexingTask extends Task<{ createThumbnails: boolean }> {
export class IndexingTask extends Task {
public readonly Name = DefaultsTasks[DefaultsTasks.Indexing];
directoriesToIndex: string[] = [];
public readonly ConfigTemplate: ConfigTemplateEntry[] = [{
id: 'createThumbnails',
type: 'boolean',
name: 'With thumbnails',
defaultValue: false
}];
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
public get Supported(): boolean {
return Config.Server.Database.type !== ServerConfig.DatabaseType.memory;
@ -54,31 +42,6 @@ export class IndexingTask extends Task<{ createThumbnails: boolean }> {
for (let i = 0; i < scanned.directories.length; i++) {
this.directoriesToIndex.push(path.join(scanned.directories[i].path, scanned.directories[i].name));
}
if (this.config.createThumbnails) {
for (let i = 0; i < scanned.media.length; i++) {
try {
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,
PhotoProcessing.generateThumbnailName(mPath, Config.Client.Media.Thumbnail.thumbnailSizes[0]));
if (fs.existsSync(thPath)) { // skip existing thumbnails
continue;
}
await ThumbnailWorker.render(<RendererInput>{
type: MediaDTO.isVideo(media) ? ThumbnailSourceType.Video : ThumbnailSourceType.Photo,
mediaPath: mPath,
size: Config.Client.Media.Thumbnail.thumbnailSizes[0],
outPath: thPath,
makeSquare: false,
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());
}
}
}
return this.progress;
}

View File

@ -14,14 +14,14 @@ const existsPr = util.promisify(fs.exists);
export class PhotoConvertingTask extends FileTask<string> {
public readonly Name = DefaultsTasks[DefaultsTasks['Video Converting']];
public readonly Name = DefaultsTasks[DefaultsTasks['Photo Converting']];
constructor() {
super({noVideo: true, noMetaFile: true});
}
public get Supported(): boolean {
return Config.Server.Media.Photo.Converting.enabled === true;
return Config.Client.Media.Photo.Converting.enabled === true;
}
protected async processDirectory(directory: DirectoryDTO): Promise<string[]> {
@ -40,7 +40,7 @@ export class PhotoConvertingTask extends FileTask<string> {
}
protected async processFile(file: string): Promise<void> {
await PhotoProcessing.generateThumbnail(file, Config.Server.Media.Photo.Converting.resolution, ThumbnailSourceType.Photo, false);
await PhotoProcessing.convertPhoto(file, Config.Server.Media.Photo.Converting.resolution);
}

View File

@ -103,7 +103,7 @@ export class ThumbnailTH extends ThreadPool<void> implements ITaskExecuter<Rende
return super.executeTask(<ThumbnailTask>{
type: WorkerTaskTypes.thumbnail,
input: input,
renderer: Config.Server.Media.Thumbnail.processingLibrary
renderer: Config.Server.Media.photoProcessingLibrary
});
}
}

View File

@ -9,16 +9,16 @@ export class ThumbnailWorker {
private static imageRenderer: (input: RendererInput) => Promise<void> = null;
private static videoRenderer: (input: RendererInput) => Promise<void> = null;
private static rendererType: ServerConfig.ThumbnailProcessingLib = null;
private static rendererType: ServerConfig.PhotoProcessingLib = null;
public static render(input: RendererInput, renderer: ServerConfig.ThumbnailProcessingLib): Promise<void> {
public static render(input: RendererInput, renderer: ServerConfig.PhotoProcessingLib): Promise<void> {
if (input.type === ThumbnailSourceType.Photo) {
return this.renderFromImage(input, renderer);
}
return this.renderFromVideo(input);
}
public static renderFromImage(input: RendererInput, renderer: ServerConfig.ThumbnailProcessingLib): Promise<void> {
public static renderFromImage(input: RendererInput, renderer: ServerConfig.PhotoProcessingLib): Promise<void> {
if (ThumbnailWorker.rendererType !== renderer) {
ThumbnailWorker.imageRenderer = ImageRendererFactory.build(renderer);
ThumbnailWorker.rendererType = renderer;
@ -117,13 +117,13 @@ export class VideoRendererFactory {
export class ImageRendererFactory {
public static build(renderer: ServerConfig.ThumbnailProcessingLib): (input: RendererInput) => Promise<void> {
public static build(renderer: ServerConfig.PhotoProcessingLib): (input: RendererInput) => Promise<void> {
switch (renderer) {
case ServerConfig.ThumbnailProcessingLib.Jimp:
case ServerConfig.PhotoProcessingLib.Jimp:
return ImageRendererFactory.Jimp();
case ServerConfig.ThumbnailProcessingLib.gm:
case ServerConfig.PhotoProcessingLib.gm:
return ImageRendererFactory.Gm();
case ServerConfig.ThumbnailProcessingLib.sharp:
case ServerConfig.PhotoProcessingLib.sharp:
return ImageRendererFactory.Sharp();
}
throw new Error('unknown renderer');

View File

@ -54,7 +54,7 @@ export interface DiskManagerTask extends WorkerTask {
export interface ThumbnailTask extends WorkerTask {
input: RendererInput;
renderer: ServerConfig.ThumbnailProcessingLib;
renderer: ServerConfig.PhotoProcessingLib;
}
export module WorkerTask {

View File

@ -14,7 +14,7 @@ export module ServerConfig {
none = 1, error = 2, all = 3
}
export enum ThumbnailProcessingLib {
export enum PhotoProcessingLib {
sharp = 3,
Jimp = 1,
gm = 2,
@ -43,7 +43,6 @@ export module ServerConfig {
}
export interface ThumbnailConfig {
processingLibrary: ThumbnailProcessingLib;
qualityPriority: boolean;
personFaceMargin: number; // in ration [0-1]
}
@ -65,7 +64,7 @@ export module ServerConfig {
}
export interface ThreadingConfig {
enable: boolean;
enabled: boolean;
thumbnailThreads: number;
}
@ -107,6 +106,7 @@ export module ServerConfig {
export interface MediaConfig {
folder: string;
tempFolder: string;
photoProcessingLibrary: PhotoProcessingLib;
Video: VideoConfig;
Photo: PhotoConfig;
Thumbnail: ThumbnailConfig;

View File

@ -14,8 +14,8 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
Media: {
folder: 'demo/images',
tempFolder: 'demo/tmp',
photoProcessingLibrary: ServerConfig.PhotoProcessingLib.sharp,
Thumbnail: {
processingLibrary: ServerConfig.ThumbnailProcessingLib.sharp,
qualityPriority: true,
personFaceMargin: 0.6
},
@ -61,7 +61,7 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
updateTimeout: 1000 * 60 * 5
},
Threading: {
enable: true,
enabled: true,
thumbnailThreads: 0
},
Indexing: {

View File

@ -2,7 +2,7 @@ export type fieldType = 'string' | 'number' | 'boolean';
export enum DefaultsTasks {
Indexing = 1, 'Database Reset' = 2, 'Video Converting' = 3
Indexing = 1, 'Database Reset' = 2, 'Video Converting' = 3, 'Photo Converting' = 4, 'Thumbnail Generation' = 5
}
export interface ConfigTemplateEntry {

View File

@ -87,6 +87,7 @@ import {TimepickerModule} from 'ngx-bootstrap/timepicker';
import {TimeStampDatePickerComponent} from './ui/utils/timestamp-datepicker/datepicker.component';
import {TimeStampTimePickerComponent} from './ui/utils/timestamp-timepicker/timepicker.component';
import {TasksProgressComponent} from './ui/settings/tasks/progress/progress.tasks.settings.component';
import {PhotoSettingsComponent} from './ui/settings/photo/photo.settings.component';
@ -188,6 +189,7 @@ export function translationsFactory(locale: string) {
MapSettingsComponent,
ThumbnailSettingsComponent,
VideoSettingsComponent,
PhotoSettingsComponent,
MetaFileSettingsComponent,
SearchSettingsComponent,
ShareSettingsComponent,

View File

@ -53,8 +53,10 @@
<button class="btn btn-link nav-link text-md-left py-md-1 px-md-0"
*ngFor="let s of contents; let i=index;"
(click)="scrollTo(i)"
[hidden]="!s.hasAvailableSettings"
>{{s.Name}}</button>
[hidden]="!s.HasAvailableSettings">
{{s.Name}}<!--
--><ng-container *ngIf="s.Changed">*</ng-container>
</button>
</div>
</div>
@ -63,44 +65,47 @@
<div class="col-md-10">
<app-settings-basic #setting #basic
[simplifiedMode]="simplifiedMode"
[hidden]="!basic.hasAvailableSettings"></app-settings-basic>
[hidden]="!basic.HasAvailableSettings"></app-settings-basic>
<app-settings-usermanager #setting #userManager
[hidden]="!userManager.hasAvailableSettings"></app-settings-usermanager>
[hidden]="!userManager.HasAvailableSettings"></app-settings-usermanager>
<app-settings-database #setting #database
[simplifiedMode]="simplifiedMode"
[hidden]="!database.hasAvailableSettings"></app-settings-database>
<app-settings-thumbnail #setting #thumbnail
[hidden]="!thumbnail.hasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-thumbnail>
[hidden]="!database.HasAvailableSettings"></app-settings-database>
<app-settings-photo #setting #photo
[hidden]="!photo.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-photo>
<app-settings-video #setting #video
[hidden]="!video.hasAvailableSettings"
[hidden]="!video.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-video>
<app-settings-thumbnail #setting #thumbnail
[hidden]="!thumbnail.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-thumbnail>
<app-settings-search #setting #search
[hidden]="!search.hasAvailableSettings"
[hidden]="!search.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-search>
<app-settings-share #setting #share
[hidden]="!share.hasAvailableSettings"
[hidden]="!share.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-share>
<app-settings-map #setting #map
[hidden]="!map.hasAvailableSettings"
[hidden]="!map.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-map>
<app-settings-meta-file #setting #metaFile
[hidden]="!metaFile.hasAvailableSettings"
[hidden]="!metaFile.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-meta-file>
<app-settings-other #setting #other
[hidden]="!other.hasAvailableSettings"
[hidden]="!other.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-other>
<app-settings-random-photo #setting #random
[hidden]="!random.hasAvailableSettings"
[hidden]="!random.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-random-photo>
<app-settings-faces #setting #faces
[hidden]="!faces.hasAvailableSettings"
[hidden]="!faces.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-faces>
<app-settings-indexing #setting #indexing
[hidden]="!indexing.hasAvailableSettings"
[hidden]="!indexing.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-indexing>
<app-settings-tasks #setting #tasks
[hidden]="!tasks.hasAvailableSettings"
[hidden]="!tasks.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-tasks>
</div>
</div>

View File

@ -1,5 +1,6 @@
export interface ISettingsComponent {
hasAvailableSettings: boolean;
HasAvailableSettings: boolean;
Name: string;
Changed: boolean;
}

View File

@ -60,6 +60,14 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
return this.name;
}
get Changed(): boolean {
return this.changed;
}
get HasAvailableSettings(): boolean {
return this.hasAvailableSettings;
}
onNewSettings = (s: IPrivateConfig) => {
this.settings = Utils.clone(this.sliceFN(s));
this.original = Utils.clone(this.settings);

View File

@ -18,7 +18,12 @@ import {UserRoles} from '../../../../../common/entities/UserDTO';
})
export class FacesSettingsComponent extends SettingsComponent<ClientConfig.FacesConfig> {
public userRoles: Array<any> = [];
public readonly userRoles = Utils
.enumToArray(UserRoles)
.filter(r => r.key !== UserRoles.LimitedGuest)
.filter(r => r.key <= this._authService.user.value.role)
.sort((a, b) => a.key - b.key);
constructor(_authService: AuthenticationService,
_navigation: NavigationService,
_settingsService: FacesSettingsService,
@ -26,11 +31,6 @@ export class FacesSettingsComponent extends SettingsComponent<ClientConfig.Faces
i18n: I18n) {
super(i18n('Faces'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.Faces);
this.userRoles = Utils
.enumToArray(UserRoles)
.filter(r => r.key !== UserRoles.LimitedGuest)
.filter(r => r.key <= this._authService.user.value.role)
.sort((a, b) => a.key - b.key);
}

View File

@ -99,43 +99,39 @@
<hr/>
</ng-container>
<div class="alert alert-secondary" role="alert">
<ng-container i18n>If you add a new folder to your gallery, the site indexes it automatically.</ng-container>&nbsp;
<ng-container i18n>If you would like to trigger indexing manually, click index button.</ng-container>
<br/>
(
<ng-container i18n>Note: search only works among the indexed directories</ng-container>
)<br/>
(<ng-container i18n>Note: search only works among the indexed directories</ng-container>)
</div>
<div *ngIf="Progress != null">
<app-settings-tasks-progress [progress]="Progress"></app-settings-tasks-progress>
</div>
<div class="row justify-content-center buttons-row">
<button class="btn btn-success"
<button class="btn btn-success ml-0"
*ngIf="Progress == null"
[disabled]="inProgress"
title="Indexes the folders"
i18n-title
(click)="index(false)" i18n>Index
(click)="index()">
<ng-container i18n>Index folders now</ng-container>
<span class="oi oi-media-play ml-2"></span>
</button>
<button class="btn btn-primary"
title="Indexes the folders and also creates the thumbnails"
i18n-title
*ngIf="Progress == null"
[disabled]="inProgress"
(click)="index(true)" i18n>Index with Thumbnails
</button>
<button class="btn btn-secondary"
<button class="btn btn-secondary ml-0"
*ngIf="Progress != null"
[disabled]="inProgress || Progress.state !== TaskState.running"
(click)="cancelIndexing()" i18n>Cancel
(click)="cancelIndexing()" i18n>Cancel converting
</button>
<button class="btn btn-danger"
<button class="btn btn-danger ml-2"
[disabled]="inProgress"
(click)="resetDatabase()" i18n>Reset Indexes
</button>
</div>
<hr/>
<div class="row statics">
<div class="col-md-4 col-12" i18n>

View File

@ -36,7 +36,7 @@ export class IndexingSettingsComponent extends SettingsComponent<ServerConfig.In
super(i18n('Folder indexing'),
_authService,
_navigation,
<any>_settingsService,
_settingsService,
notification,
i18n,
s => s.Server.Indexing);
@ -89,11 +89,11 @@ export class IndexingSettingsComponent extends SettingsComponent<ServerConfig.In
}
async index(createThumbnails: boolean) {
async index() {
this.inProgress = true;
this.error = '';
try {
await this.tasksService.start(DefaultsTasks[DefaultsTasks.Indexing], {createThumbnails: !!createThumbnails});
await this.tasksService.start(DefaultsTasks[DefaultsTasks.Indexing]);
this.notification.info(this.i18n('Folder indexing started'));
this.inProgress = false;
return true;

View File

@ -26,7 +26,7 @@ export class MapSettingsComponent extends SettingsComponent<ClientConfig.MapConf
_settingsService: MapSettingsService,
notification: NotificationService,
i18n: I18n) {
super(i18n('Map'), _authService, _navigation, <any>_settingsService, notification, i18n, s => s.Client.Map);
super(i18n('Map'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.Map);
this.mapProviders = Utils.enumToArray(ClientConfig.MapProviders);
}

View File

@ -22,7 +22,7 @@ export class MetaFileSettingsComponent extends SettingsComponent<ClientConfig.Me
_settingsService: MetaFileSettingsService,
notification: NotificationService,
i18n: I18n) {
super(i18n('Meta file'), _authService, _navigation, <any>_settingsService, notification, i18n, s => s.Client.MetaFile);
super(i18n('Meta file'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.MetaFile);
}

View File

@ -21,7 +21,7 @@
[switch-on-text]="text.Enabled"
[switch-handle-width]="100"
[switch-label-width]="20"
[(ngModel)]="settings.Server.enable">
[(ngModel)]="settings.Server.enabled">
</bSwitch>
<small class="form-text text-muted" i18n>Runs directory scanning and thumbnail generation (only for Jimp) in a
different thread
@ -29,7 +29,7 @@
</div>
</div>
<div class="form-group row" [hidden]="simplifiedMode || settings.Server.enable == false">
<div class="form-group row" [hidden]="simplifiedMode || settings.Server.enabled == false">
<label class="col-md-2 control-label" for="thumbnailThreads" i18n>Thumbnail threads</label>
<div class="col-md-10">
<select id="thumbnailThreads" class="form-control" [(ngModel)]="settings.Server.thumbnailThreads"

View File

@ -0,0 +1,4 @@
.buttons-row {
margin-top: 10px;
margin-bottom: 20px;
}

View File

@ -0,0 +1,129 @@
<form #settingsForm="ngForm" class="form-horizontal">
<div class="card mb-4">
<h5 class="card-header">{{Name}}<span *ngIf="changed">*</span></h5>
<div class="card-body">
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<div [hidden]="settings.photoProcessingLibrary!=PhotoProcessingLib.Jimp"
class="alert alert-warning"
role="alert" i18n>It is highly recommended to use hardware accelerated (sharp or gm) lib for thumbnail
generation
</div>
<div [hidden]="simplifiedMode">
<div class="form-group row">
<label class="col-md-2 control-label" for="lib" i18n>Thumbnail generation library</label>
<div class="col-md-10">
<select id="lib" class="form-control" [(ngModel)]="settings.photoProcessingLibrary"
name="type" required>
<option *ngFor="let type of libTypes" [ngValue]="type.key">{{type.value}}
</option>
</select>
<small *ngIf="settings.photoProcessingLibrary==PhotoProcessingLib.sharp"
class="form-text text-muted" i18n>Make sure that sharp node module is installed (npm install sharp).
</small>
<small *ngIf="settings.photoProcessingLibrary==PhotoProcessingLib.gm"
class="form-text text-muted">
<ng-container i18n>Make sure that gm node module and</ng-container>
<a
href="http://www.graphicsmagick.org/" i18n>GraphicsMagick</a>
<ng-container i18n>are installed (npm install sharp).</ng-container>
</small>
</div>
</div>
<hr/>
</div>
<p class="title" i18n>Photo converting:</p>
<div class="form-group row">
<label class="col-md-2 control-label" for="enablePhotoConverting" i18n>Converting</label>
<div class="col-md-10">
<bSwitch
id="enablePhotoConverting"
class="switch"
name="enablePhotoConverting"
[switch-on-color]="'primary'"
[switch-inverse]="true"
[switch-off-text]="text.Disabled"
[switch-on-text]="text.Enabled"
[switch-handle-width]="100"
[switch-label-width]="20"
[(ngModel)]="settings.client.Converting.enabled">
</bSwitch>
<small class="form-text text-muted" i18n>Downsizes photos for faster preview loading. (Zooming in to the photo
loads the original)</small>
</div>
</div>
<div class="form-group row" [hidden]="!settings.client.Converting.enabled">
<label class="col-md-2 control-label" for="onTheFlyConverting" i18n>On the fly converting </label>
<div class="col-md-10">
<bSwitch
id="onTheFlyConverting"
class="switch"
name="onTheFlyConverting"
[switch-on-color]="'primary'"
[switch-inverse]="true"
[switch-off-text]="text.Disabled"
[switch-on-text]="text.Enabled"
[switch-handle-width]="100"
[switch-label-width]="20"
[(ngModel)]="settings.server.Converting.onTheFly">
</bSwitch>
<small class="form-text text-muted" i18n>Converts photos on the fly, when they are requested.</small>
</div>
</div>
<div class="form-group row" [hidden]="!settings.client.Converting.enabled">
<label class="col-md-2 control-label" for="resolution" i18n>Resolution</label>
<div class="col-md-10">
<select id="resolution" class="form-control" [(ngModel)]="settings.server.Converting.resolution"
name="resolution" required>
<option *ngFor="let resolution of resolutions" [ngValue]="resolution">{{resolution}}px
</option>
</select>
<small class="form-text text-muted" i18n>The shorter edge of the converted photo will be scaled down to this,
while
keeping the aspect ratio.</small>
</div>
</div>
<button class="btn btn-success float-right"
[disabled]="!settingsForm.form.valid || !changed || inProgress"
(click)="save()" i18n>Save
</button>
<button class="btn btn-secondary float-right"
[disabled]=" !changed || inProgress"
(click)="reset()" i18n>Reset
</button>
<div [hidden]="!settings.client.Converting.enabled">
<button class="btn btn-success float-left ml-0"
*ngIf="Progress == null"
[disabled]="inProgress"
title="Indexes the folders"
i18n-title
(click)="convertPhoto()">
<ng-container i18n>Convert photos now</ng-container>
<span class="oi oi-media-play ml-2"></span>
</button>
<button class="btn btn-secondary float-left ml-0"
*ngIf="Progress != null"
[disabled]="inProgress || Progress.state !== TaskState.running"
(click)="cancelPhotoConverting()" i18n>Cancel converting
</button>
<ng-container *ngIf="Progress != null">
<br/>
<hr/>
<app-settings-tasks-progress [progress]="Progress"></app-settings-tasks-progress>
</ng-container>
</div>
</div>
</div>
</form>

View File

@ -0,0 +1,105 @@
import {Component} from '@angular/core';
import {PhotoSettingsService} from './photo.settings.service';
import {SettingsComponent} from '../_abstract/abstract.settings.component';
import {AuthenticationService} from '../../../model/network/authentication.service';
import {NavigationService} from '../../../model/navigation.service';
import {NotificationService} from '../../../model/notification.service';
import {ClientConfig} from '../../../../../common/config/public/ConfigClass';
import {I18n} from '@ngx-translate/i18n-polyfill';
import {ScheduledTasksService} from '../scheduled-tasks.service';
import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig';
import {Utils} from '../../../../../common/Utils';
import {DefaultsTasks} from '../../../../../common/entities/task/TaskDTO';
import {ErrorDTO} from '../../../../../common/entities/Error';
import {TaskState} from '../../../../../common/entities/settings/TaskProgressDTO';
@Component({
selector: 'app-settings-photo',
templateUrl: './photo.settings.component.html',
styleUrls: ['./photo.settings.component.css',
'../_abstract/abstract.settings.component.css'],
providers: [PhotoSettingsService],
})
export class PhotoSettingsComponent extends SettingsComponent<{
photoProcessingLibrary: ServerConfig.PhotoProcessingLib,
server: ServerConfig.PhotoConfig,
client: ClientConfig.PhotoConfig
}> {
resolutions = [720, 1080, 1440, 2160, 4320];
PhotoProcessingLib = ServerConfig.PhotoProcessingLib;
TaskState = TaskState;
libTypes = Utils
.enumToArray(ServerConfig.PhotoProcessingLib).map((v) => {
if (v.value.toLowerCase() === 'sharp') {
v.value += ' ' + this.i18n('(recommended)');
} else {
v.value += ' ' + this.i18n('(deprecated, will be removed)');
}
return v;
});
constructor(_authService: AuthenticationService,
_navigation: NavigationService,
_settingsService: PhotoSettingsService,
public tasksService: ScheduledTasksService,
notification: NotificationService,
i18n: I18n) {
super(i18n('Photo'), _authService, _navigation, _settingsService, notification, i18n, s => ({
photoProcessingLibrary: s.Server.Media.photoProcessingLibrary,
client: s.Client.Media.Photo,
server: s.Server.Media.Photo
}));
const currentRes = _settingsService.Settings.value.Server.Media.Photo.Converting.resolution;
if (this.resolutions.indexOf(currentRes) === -1) {
this.resolutions.push(currentRes);
}
}
get Progress() {
return this.tasksService.progress.value[DefaultsTasks[DefaultsTasks['Photo Converting']]];
}
async convertPhoto() {
this.inProgress = true;
this.error = '';
try {
await this.tasksService.start(DefaultsTasks[DefaultsTasks['Photo Converting']]);
this.notification.info(this.i18n('Photo converting started'));
this.inProgress = false;
return true;
} catch (err) {
console.log(err);
if (err.message) {
this.error = (<ErrorDTO>err).message;
}
}
this.inProgress = false;
return false;
}
async cancelPhotoConverting() {
this.inProgress = true;
this.error = '';
try {
await this.tasksService.stop(DefaultsTasks[DefaultsTasks['Photo Converting']]);
this.notification.info(this.i18n('Photo converting interrupted'));
this.inProgress = false;
return true;
} catch (err) {
console.log(err);
if (err.message) {
this.error = (<ErrorDTO>err).message;
}
}
this.inProgress = false;
return false;
}
}

View File

@ -0,0 +1,21 @@
import {Injectable} from '@angular/core';
import {NetworkService} from '../../../model/network/network.service';
import {ClientConfig} from '../../../../../common/config/public/ConfigClass';
import {SettingsService} from '../settings.service';
import {AbstractSettingsService} from '../_abstract/abstract.settings.service';
import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig';
@Injectable()
export class PhotoSettingsService extends AbstractSettingsService<{ server: ServerConfig.PhotoConfig, client: ClientConfig.PhotoConfig }> {
constructor(private _networkService: NetworkService,
_settingsService: SettingsService) {
super(_settingsService);
}
public updateSettings(settings: { server: ServerConfig.PhotoConfig, client: ClientConfig.PhotoConfig }): Promise<void> {
return this._networkService.putJson('/settings/photo', {settings: settings});
}
}

View File

@ -2,7 +2,8 @@
<div class="card mb-4"
[ngClass]="settings.enabled && !_settingsService.isSupported()?'panel-warning':''">
<h5 class="card-header">
{{Name}}<ng-container *ngIf="changed">*</ng-container>
{{Name}}
<ng-container *ngIf="changed">*</ng-container>
<div class="switch-wrapper">
<bSwitch
class="switch"
@ -22,7 +23,8 @@
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<ng-container *ngIf="settings.enabled || _settingsService.isSupported()">
<div class="panel-info" i18n>
<div class="alert alert-secondary" role="alert" i18n>
This feature enables you to generate 'random photo' urls.
That URL returns a photo random selected from your gallery.
You can use the url with 3rd party application like random changing desktop background.

View File

@ -46,7 +46,7 @@ export class TasksSettingsComponent extends SettingsComponent<ServerConfig.TaskC
super(i18n('Tasks'),
_authService,
_navigation,
<any>_settingsService,
_settingsService,
notification,
i18n,
s => s.Server.Tasks);

View File

@ -5,34 +5,7 @@
</h5>
<div class="card-body">
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<div [hidden]="settings.server.processingLibrary!=ThumbnailProcessingLib.Jimp"
class="alert alert-warning"
role="alert" i18n>It is highly recommended to use hardware accelerated (sharp or gm) lib for thumbnail
generation
</div>
<fieldset>
<div class="form-group row">
<label class="col-md-2 control-label" for="lib" i18n>Thumbnail generation library</label>
<div class="col-md-10">
<select id="lib" class="form-control" [(ngModel)]="settings.server.processingLibrary"
name="type" required>
<option *ngFor="let type of types" [ngValue]="type.key">{{type.value}}
</option>
</select>
<small *ngIf="settings.server.processingLibrary==ThumbnailProcessingLib.sharp"
class="form-text text-muted" i18n>Make sure that sharp node module is installed (npm install sharp).
</small>
<small *ngIf="settings.server.processingLibrary==ThumbnailProcessingLib.gm"
class="form-text text-muted">
<ng-container i18n>Make sure that gm node module and</ng-container>
<a
href="http://www.graphicsmagick.org/" i18n>GraphicsMagick</a>
<ng-container i18n>are installed (npm install sharp).</ng-container>
</small>
</div>
</div>
<div class="form-group row" [hidden]="simplifiedMode">
<label class="col-md-2 control-label" for="quality" i18n>Thumbnail Quality</label>
@ -84,7 +57,6 @@
</div>
</div>
</fieldset>
<button class="btn btn-success float-right"
[disabled]="!settingsForm.form.valid || !changed || inProgress"

View File

@ -19,7 +19,6 @@ import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig'
export class ThumbnailSettingsComponent
extends SettingsComponent<{ server: ServerConfig.ThumbnailConfig, client: ClientConfig.ThumbnailConfig }>
implements OnInit {
types: Array<any> = [];
ThumbnailProcessingLib: any;
constructor(_authService: AuthenticationService,
@ -47,16 +46,6 @@ export class ThumbnailSettingsComponent
ngOnInit() {
super.ngOnInit();
this.types = Utils
.enumToArray(ServerConfig.ThumbnailProcessingLib).map((v) => {
if (v.value.toLowerCase() === 'sharp') {
v.value += ' ' + this.i18n('(recommended)');
} else {
v.value += ' ' + this.i18n('(deprecated, will be removed)');
}
return v;
});
this.ThumbnailProcessingLib = ServerConfig.ThumbnailProcessingLib;
}
}

View File

@ -26,7 +26,8 @@ export class UserMangerSettingsComponent implements OnInit, ISettingsComponent {
public error: string = null;
public inProgress = false;
Name: string;
hasAvailableSettings = true;
HasAvailableSettings = true;
Changed = false;
text = {

View File

@ -1,7 +1,8 @@
<form #settingsForm="ngForm" class="form-horizontal">
<div class="card mb-4">
<h5 class="card-header">
{{Name}}<ng-container *ngIf="changed">*</ng-container>
{{Name}}
<ng-container *ngIf="changed">*</ng-container>
<div class="switch-wrapper">
<bSwitch
class="switch"
@ -20,13 +21,27 @@
<div class="card-body">
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<ng-container i18n>Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or the
<div class="alert alert-secondary" role="alert">
<ng-container i18n>Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or
the
@ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed.
</ng-container>
</div>
<hr/>
<p class="title" i18n>Video transcoding:</p>
<div class="alert alert-secondary" role="alert">
<ng-container i18n>To ensure smooth video playback, video transcoding is recommended to a lower bit rate than
the
server's upload rate.
</ng-container>&nbsp;
<ng-container i18n>The transcoded videos will be save to the thumbnail folder.</ng-container>&nbsp;
<ng-container i18n>You can trigger the transcoding manually, but you can also create an automatic encoding task
in
advanced settings mode.
</ng-container>&nbsp;
</div>
<div class="form-group row" [hidden]="simplifiedMode">
<label class="col-md-2 control-label" for="format" i18n>Format</label>
<div class="col-md-10">
@ -109,28 +124,17 @@
(click)="reset()" i18n>Reset
</button>
<br/>
<hr/>
<ng-container i18n>To ensure smooth video playback, video transcoding is recommended to a lower bit rate than the
server's upload rate.
</ng-container>&nbsp;
<ng-container i18n>The transcoded videos will be save to the thumbnail folder.</ng-container>&nbsp;
<ng-container i18n>You can trigger the transcoding manually, but you can also create an automatic encoding task in
advanced settings mode.
</ng-container>&nbsp;
<br/>
<button class="btn btn-success float-right"
<button class="btn btn-success float-left ml-0"
*ngIf="Progress == null"
[disabled]="inProgress"
title="Indexes the folders"
i18n-title
(click)="transcode()" i18n>Transcode videos now
(click)="transcode()">
<ng-container i18n>Transcode videos now</ng-container>
<span class="oi oi-media-play ml-2"></span>
</button>
<button class="btn btn-secondary float-right"
<button class="btn btn-secondary float-left ml-0"
*ngIf="Progress != null"
[disabled]="inProgress || Progress.state !== TaskState.running"
(click)="cancelTranscoding()" i18n>Cancel transcoding

View File

@ -35,10 +35,15 @@ export class VideoSettingsComponent extends SettingsComponent<{ server: ServerCo
public tasksService: ScheduledTasksService,
notification: NotificationService,
i18n: I18n) {
super(i18n('Video'), _authService, _navigation, <any>_settingsService, notification, i18n, s => ({
super(i18n('Video'), _authService, _navigation, _settingsService, notification, i18n, s => ({
client: s.Client.Media.Video,
server: s.Server.Media.Video
}));
const currentRes = _settingsService.Settings.value.Server.Media.Video.transcoding.resolution;
if (this.resolutions.indexOf(currentRes) === -1) {
this.resolutions.push(currentRes);
}
}

View File

@ -3,16 +3,17 @@ import {NetworkService} from '../../../model/network/network.service';
import {ClientConfig} from '../../../../../common/config/public/ConfigClass';
import {SettingsService} from '../settings.service';
import {AbstractSettingsService} from '../_abstract/abstract.settings.service';
import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig';
@Injectable()
export class VideoSettingsService extends AbstractSettingsService<ClientConfig.MapConfig> {
export class VideoSettingsService extends AbstractSettingsService<{ server: ServerConfig.VideoConfig, client: ClientConfig.VideoConfig }> {
constructor(private _networkService: NetworkService,
_settingsService: SettingsService) {
super(_settingsService);
}
public updateSettings(settings: ClientConfig.VideoConfig): Promise<void> {
public updateSettings(settings: { server: ServerConfig.VideoConfig, client: ClientConfig.VideoConfig }): Promise<void> {
return this._networkService.putJson('/settings/video', {settings: settings});
}