From b1362ffef515804c3c174dfa2c1cec32369f3d1a Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 24 Dec 2023 08:59:36 +0100 Subject: [PATCH] Refactor extension settings --- .../model/extension/ExtensionConfigWrapper.ts | 39 ++++++++-- .../model/extension/ExtensionManager.ts | 31 +++++--- .../model/extension/ExtensionObject.ts | 2 +- src/common/config/private/PrivateConfig.ts | 33 +------- .../{ => subconfigs}/MessagingConfig.ts | 2 +- .../subconfigs/ServerExtensionsConfig.ts | 78 +++++++++++++++++++ .../settings-entry.component.ts | 4 + 7 files changed, 139 insertions(+), 50 deletions(-) rename src/common/config/private/{ => subconfigs}/MessagingConfig.ts (97%) create mode 100644 src/common/config/private/subconfigs/ServerExtensionsConfig.ts diff --git a/src/backend/model/extension/ExtensionConfigWrapper.ts b/src/backend/model/extension/ExtensionConfigWrapper.ts index b11e5eaa..cad0ad24 100644 --- a/src/backend/model/extension/ExtensionConfigWrapper.ts +++ b/src/backend/model/extension/ExtensionConfigWrapper.ts @@ -1,9 +1,9 @@ -import {IConfigClass} from 'typeconfig/common'; +import {ConfigProperty, IConfigClass} from 'typeconfig/common'; import {Config, PrivateConfigClass} from '../../../common/config/private/Config'; import {ConfigClassBuilder} from 'typeconfig/node'; import {IExtensionConfig} from './IExtension'; -import {Utils} from '../../../common/Utils'; import {ObjectManagers} from '../ObjectManagers'; +import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig'; /** * Wraps to original config and makes sure all extension related config is loaded @@ -29,11 +29,29 @@ export class ExtensionConfigWrapper { export class ExtensionConfig implements IExtensionConfig { public template: new() => C; - constructor(private readonly extensionId: string) { + constructor(private readonly extensionFolder: string) { + } + + private findConfig(config: PrivateConfigClass) { + let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder); + if (!c) { + c = new ServerExtensionsEntryConfig(this.extensionFolder); + config.Extensions.extensions.push(c); + } + + if (!config.Extensions.extensions2[this.extensionFolder]) { + Object.defineProperty(config.Extensions.extensions2, this.extensionFolder, + ConfigProperty({type: ServerExtensionsEntryConfig})(config.Extensions.extensions2, this.extensionFolder)); + // config.Extensions.extensions2[this.extensionFolder] = c as any; + + config.Extensions.extensions2[this.extensionFolder] = c; + + } + return config.Extensions.extensions2[this.extensionFolder]; } public getConfig(): C { - return Config.Extensions.configs[this.extensionId] as C; + return this.findConfig(Config).configs as C; } public setTemplate(template: new() => C): void { @@ -45,8 +63,15 @@ export class ExtensionConfig implements IExtensionConfig { if (!this.template) { return; } - const conf = ConfigClassBuilder.attachPrivateInterface(new this.template()); - conf.__loadJSONObject(Utils.clone(config.Extensions.configs[this.extensionId] || {})); - config.Extensions.configs[this.extensionId] = conf; + + const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template()); + const extConf = this.findConfig(config); + // confTemplate.__loadJSONObject(Utils.clone(extConf.configs || {})); + //extConf.configs = confTemplate; + Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, + ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); + console.log(config.Extensions.extensions2[this.extensionFolder].configs); + config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; + console.log(config.Extensions.extensions2[this.extensionFolder].configs); } } diff --git a/src/backend/model/extension/ExtensionManager.ts b/src/backend/model/extension/ExtensionManager.ts index f02dfb28..11f50aa4 100644 --- a/src/backend/model/extension/ExtensionManager.ts +++ b/src/backend/model/extension/ExtensionManager.ts @@ -12,6 +12,7 @@ import {SQLConnection} from '../database/SQLConnection'; import {ExtensionObject} from './ExtensionObject'; import {ExtensionDecoratorObject} from './ExtensionDecorator'; import * as util from 'util'; +import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig'; // eslint-disable-next-line @typescript-eslint/no-var-requires const exec = util.promisify(require('child_process').exec); @@ -70,13 +71,23 @@ export class ExtensionManager implements IObjectManager { return; } - Config.Extensions.list = fs - .readdirSync(ProjectPath.ExtensionFolder) - .filter((f): boolean => - fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() - ); - Config.Extensions.list.sort(); - Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.list)); + + const extList = fs + .readdirSync(ProjectPath.ExtensionFolder) + .filter((f): boolean => + fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() + ); + extList.sort(); + + // delete not existing extensions + Config.Extensions.extensions = Config.Extensions.extensions.filter(ec => extList.indexOf(ec.path) !== -1); + + // Add new extensions + const ePaths = Config.Extensions.extensions.map(ec => ec.path); + extList.filter(ep => ePaths.indexOf(ep) === -1).forEach(ep => + Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep))); + + Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.extensions.map(ec => ec.path))); } private createUniqueExtensionObject(name: string, folder: string): IExtensionObject { @@ -95,8 +106,8 @@ export class ExtensionManager implements IObjectManager { private async initExtensions() { - for (let i = 0; i < Config.Extensions.list.length; ++i) { - const extFolder = Config.Extensions.list[i]; + for (let i = 0; i < Config.Extensions.extensions.length; ++i) { + const extFolder = Config.Extensions.extensions[i].path; let extName = extFolder; const extPath = path.join(ProjectPath.ExtensionFolder, extFolder); const serverExtPath = path.join(extPath, 'server.js'); @@ -122,7 +133,7 @@ export class ExtensionManager implements IObjectManager { const ext = require(serverExtPath); if (typeof ext?.init === 'function') { Logger.debug(LOG_TAG, 'Running init on extension: ' + extFolder); - await ext?.init(this.createUniqueExtensionObject(extName, extPath)); + await ext?.init(this.createUniqueExtensionObject(extName, extFolder)); } } if (Config.Extensions.cleanUpUnusedTables) { diff --git a/src/backend/model/extension/ExtensionObject.ts b/src/backend/model/extension/ExtensionObject.ts index 3ff1aec6..254c8b9d 100644 --- a/src/backend/model/extension/ExtensionObject.ts +++ b/src/backend/model/extension/ExtensionObject.ts @@ -26,7 +26,7 @@ export class ExtensionObject implements IExtensionObject { events: IExtensionEvents) { const logger = createLoggerWrapper(`[Extension][${extensionId}]`); this._app = new ExtensionApp(); - this.config = new ExtensionConfig(extensionId); + this.config = new ExtensionConfig(folder); this.db = new ExtensionDB(logger); this.paths = ProjectPath; this.Logger = logger; diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index a02dd3af..a537011d 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -11,7 +11,6 @@ import { } from '../../entities/job/JobScheduleDTO'; import { ClientConfig, - ClientExtensionsConfig, ClientGPXCompressingConfig, ClientMediaConfig, ClientMetaFileConfig, @@ -32,7 +31,8 @@ import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../entities/Sear import {SortByTypes} from '../../entities/SortingMethods'; import {UserRoles} from '../../entities/UserDTO'; import {MediaPickDTO} from '../../entities/MediaPickDTO'; -import {MessagingConfig} from './MessagingConfig'; +import {ServerExtensionsConfig} from './subconfigs/ServerExtensionsConfig'; +import {MessagingConfig} from './subconfigs/MessagingConfig'; declare let $localize: (s: TemplateStringsArray) => string; @@ -1025,35 +1025,6 @@ export class ServerServiceConfig extends ClientServiceConfig { } -@SubConfigClass({softReadonly: true}) -export class ServerExtensionsConfig extends ClientExtensionsConfig { - - @ConfigProperty({ - tags: { - name: $localize`Extension folder`, - priority: ConfigPriority.underTheHood, - dockerSensitive: true - }, - description: $localize`Folder where the app stores the extensions. Extensions live in their sub-folders.`, - }) - folder: string = 'extensions'; - - @ConfigProperty({volatile: true}) - list: string[] = []; - - @ConfigProperty({type: 'object'}) - configs: Record = {}; - - @ConfigProperty({ - tags: { - name: $localize`Clean up unused tables`, - priority: ConfigPriority.underTheHood, - }, - description: $localize`Automatically removes all tables from the DB that are not used anymore.`, - }) - cleanUpUnusedTables: boolean = true; -} - @SubConfigClass({softReadonly: true}) export class ServerEnvironmentConfig { @ConfigProperty({volatile: true}) diff --git a/src/common/config/private/MessagingConfig.ts b/src/common/config/private/subconfigs/MessagingConfig.ts similarity index 97% rename from src/common/config/private/MessagingConfig.ts rename to src/common/config/private/subconfigs/MessagingConfig.ts index 067a19f7..54ec6602 100644 --- a/src/common/config/private/MessagingConfig.ts +++ b/src/common/config/private/subconfigs/MessagingConfig.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-inferrable-types */ import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; -import {ConfigPriority, TAGS} from '../public/ClientConfig'; +import {ConfigPriority, TAGS} from '../../public/ClientConfig'; declare let $localize: (s: TemplateStringsArray) => string; diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts new file mode 100644 index 00000000..22f7ad66 --- /dev/null +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -0,0 +1,78 @@ +/* eslint-disable @typescript-eslint/no-inferrable-types */ +import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; +import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig'; +import {IConfigClassPrivate} from '../../../../../node_modules/typeconfig/src/decorators/class/IConfigClass'; + +@SubConfigClass({softReadonly: true}) +export class ServerExtensionsEntryConfig { + + constructor(path: string = '') { + this.path = path; + } + + @ConfigProperty({ + tags: { + name: $localize`Enabled`, + priority: ConfigPriority.advanced, + }, + }) + enabled: boolean = true; + + @ConfigProperty({ + tags: { + name: $localize`Extension folder`, + priority: ConfigPriority.underTheHood, + }, + description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`, + }) + path: string = ''; + + @ConfigProperty({ + tags: { + name: $localize`Config`, + priority: ConfigPriority.advanced + } + }) + configs: IConfigClassPrivate; +} + +@SubConfigClass({softReadonly: true}) +export class ServerExtensionsConfig extends ClientExtensionsConfig { + + @ConfigProperty({ + tags: { + name: $localize`Extension folder`, + priority: ConfigPriority.underTheHood, + dockerSensitive: true + }, + description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`, + }) + folder: string = 'extensions'; + + + @ConfigProperty({ + arrayType: ServerExtensionsEntryConfig, + tags: { + name: $localize`Installed extensions`, + priority: ConfigPriority.advanced + } + }) + extensions: ServerExtensionsEntryConfig[] = []; + + @ConfigProperty({ + tags: { + name: $localize`Installed extensions2`, + priority: ConfigPriority.advanced + } + }) + extensions2: Record = {}; + + @ConfigProperty({ + tags: { + name: $localize`Clean up unused tables`, + priority: ConfigPriority.underTheHood, + }, + description: $localize`Automatically removes all tables from the DB that are not used anymore.`, + }) + cleanUpUnusedTables: boolean = true; +} diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 333add00..1d235785 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -19,6 +19,7 @@ import {enumToTranslatedArray} from '../../../EnumTranslations'; import {BsModalService} from 'ngx-bootstrap/modal'; import {CustomSettingsEntries} from '../CustomSettingsEntries'; import {GroupByTypes, SortByTypes} from '../../../../../../common/entities/SortingMethods'; +import { ServerExtensionsEntryConfig } from '../../../../../../common/config/private/subconfigs/ServerExtensionsConfig'; interface IState { shouldHide(): boolean; @@ -232,6 +233,8 @@ export class SettingsEntryComponent this.arrayType = 'MapPathGroupThemeConfig'; } else if (this.state.arrayType === UserConfig) { this.arrayType = 'UserConfig'; + } else if (this.state.arrayType === ServerExtensionsEntryConfig) { + this.arrayType = 'ServerExtensionsEntryConfig'; } else if (this.state.arrayType === JobScheduleConfig) { this.arrayType = 'JobScheduleConfig'; } else { @@ -253,6 +256,7 @@ export class SettingsEntryComponent this.arrayType !== 'MapLayers' && this.arrayType !== 'NavigationLinkConfig' && this.arrayType !== 'MapPathGroupConfig' && + this.arrayType !== 'ServerExtensionsEntryConfig' && this.arrayType !== 'MapPathGroupThemeConfig' && this.arrayType !== 'JobScheduleConfig' && this.arrayType !== 'UserConfig') {