From 1896bca09e088b0da9d5e845407ecebae330618c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 28 Jan 2021 15:52:44 +0100 Subject: Support transcoding options/encoders by plugins --- server/lib/plugins/plugin-helpers-builder.ts | 133 ++++++++ server/lib/plugins/plugin-helpers.ts | 133 -------- server/lib/plugins/plugin-manager.ts | 35 ++- server/lib/plugins/register-helpers-store.ts | 357 --------------------- server/lib/plugins/register-helpers.ts | 444 +++++++++++++++++++++++++++ 5 files changed, 595 insertions(+), 507 deletions(-) create mode 100644 server/lib/plugins/plugin-helpers-builder.ts delete mode 100644 server/lib/plugins/plugin-helpers.ts delete mode 100644 server/lib/plugins/register-helpers-store.ts create mode 100644 server/lib/plugins/register-helpers.ts (limited to 'server/lib/plugins') diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts new file mode 100644 index 000000000..39773f693 --- /dev/null +++ b/server/lib/plugins/plugin-helpers-builder.ts @@ -0,0 +1,133 @@ +import { PeerTubeHelpers } from '@server/types/plugins' +import { sequelizeTypescript } from '@server/initializers/database' +import { buildLogger } from '@server/helpers/logger' +import { VideoModel } from '@server/models/video/video' +import { WEBSERVER } from '@server/initializers/constants' +import { ServerModel } from '@server/models/server/server' +import { getServerActor } from '@server/models/application/application' +import { addServerInBlocklist, removeServerFromBlocklist, addAccountInBlocklist, removeAccountFromBlocklist } from '../blocklist' +import { ServerBlocklistModel } from '@server/models/server/server-blocklist' +import { AccountModel } from '@server/models/account/account' +import { VideoBlacklistCreate } from '@shared/models' +import { blacklistVideo, unblacklistVideo } from '../video-blacklist' +import { VideoBlacklistModel } from '@server/models/video/video-blacklist' +import { AccountBlocklistModel } from '@server/models/account/account-blocklist' + +function buildPluginHelpers (npmName: string): PeerTubeHelpers { + const logger = buildPluginLogger(npmName) + + const database = buildDatabaseHelpers() + const videos = buildVideosHelpers() + + const config = buildConfigHelpers() + + const server = buildServerHelpers() + + const moderation = buildModerationHelpers() + + return { + logger, + database, + videos, + config, + moderation, + server + } +} + +export { + buildPluginHelpers +} + +// --------------------------------------------------------------------------- + +function buildPluginLogger (npmName: string) { + return buildLogger(npmName) +} + +function buildDatabaseHelpers () { + return { + query: sequelizeTypescript.query.bind(sequelizeTypescript) + } +} + +function buildServerHelpers () { + return { + getServerActor: () => getServerActor() + } +} + +function buildVideosHelpers () { + return { + loadByUrl: (url: string) => { + return VideoModel.loadByUrl(url) + }, + + removeVideo: (id: number) => { + return sequelizeTypescript.transaction(async t => { + const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id, t) + + await video.destroy({ transaction: t }) + }) + } + } +} + +function buildModerationHelpers () { + return { + blockServer: async (options: { byAccountId: number, hostToBlock: string }) => { + const serverToBlock = await ServerModel.loadOrCreateByHost(options.hostToBlock) + + await addServerInBlocklist(options.byAccountId, serverToBlock.id) + }, + + unblockServer: async (options: { byAccountId: number, hostToUnblock: string }) => { + const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(options.byAccountId, options.hostToUnblock) + if (!serverBlock) return + + await removeServerFromBlocklist(serverBlock) + }, + + blockAccount: async (options: { byAccountId: number, handleToBlock: string }) => { + const accountToBlock = await AccountModel.loadByNameWithHost(options.handleToBlock) + if (!accountToBlock) return + + await addAccountInBlocklist(options.byAccountId, accountToBlock.id) + }, + + unblockAccount: async (options: { byAccountId: number, handleToUnblock: string }) => { + const targetAccount = await AccountModel.loadByNameWithHost(options.handleToUnblock) + if (!targetAccount) return + + const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(options.byAccountId, targetAccount.id) + if (!accountBlock) return + + await removeAccountFromBlocklist(accountBlock) + }, + + blacklistVideo: async (options: { videoIdOrUUID: number | string, createOptions: VideoBlacklistCreate }) => { + const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) + if (!video) return + + await blacklistVideo(video, options.createOptions) + }, + + unblacklistVideo: async (options: { videoIdOrUUID: number | string }) => { + const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) + if (!video) return + + const videoBlacklist = await VideoBlacklistModel.loadByVideoId(video.id) + if (!videoBlacklist) return + + await unblacklistVideo(videoBlacklist, video) + } + } +} + +function buildConfigHelpers () { + return { + getWebserverUrl () { + return WEBSERVER.URL + } + } +} diff --git a/server/lib/plugins/plugin-helpers.ts b/server/lib/plugins/plugin-helpers.ts deleted file mode 100644 index 39773f693..000000000 --- a/server/lib/plugins/plugin-helpers.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { PeerTubeHelpers } from '@server/types/plugins' -import { sequelizeTypescript } from '@server/initializers/database' -import { buildLogger } from '@server/helpers/logger' -import { VideoModel } from '@server/models/video/video' -import { WEBSERVER } from '@server/initializers/constants' -import { ServerModel } from '@server/models/server/server' -import { getServerActor } from '@server/models/application/application' -import { addServerInBlocklist, removeServerFromBlocklist, addAccountInBlocklist, removeAccountFromBlocklist } from '../blocklist' -import { ServerBlocklistModel } from '@server/models/server/server-blocklist' -import { AccountModel } from '@server/models/account/account' -import { VideoBlacklistCreate } from '@shared/models' -import { blacklistVideo, unblacklistVideo } from '../video-blacklist' -import { VideoBlacklistModel } from '@server/models/video/video-blacklist' -import { AccountBlocklistModel } from '@server/models/account/account-blocklist' - -function buildPluginHelpers (npmName: string): PeerTubeHelpers { - const logger = buildPluginLogger(npmName) - - const database = buildDatabaseHelpers() - const videos = buildVideosHelpers() - - const config = buildConfigHelpers() - - const server = buildServerHelpers() - - const moderation = buildModerationHelpers() - - return { - logger, - database, - videos, - config, - moderation, - server - } -} - -export { - buildPluginHelpers -} - -// --------------------------------------------------------------------------- - -function buildPluginLogger (npmName: string) { - return buildLogger(npmName) -} - -function buildDatabaseHelpers () { - return { - query: sequelizeTypescript.query.bind(sequelizeTypescript) - } -} - -function buildServerHelpers () { - return { - getServerActor: () => getServerActor() - } -} - -function buildVideosHelpers () { - return { - loadByUrl: (url: string) => { - return VideoModel.loadByUrl(url) - }, - - removeVideo: (id: number) => { - return sequelizeTypescript.transaction(async t => { - const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id, t) - - await video.destroy({ transaction: t }) - }) - } - } -} - -function buildModerationHelpers () { - return { - blockServer: async (options: { byAccountId: number, hostToBlock: string }) => { - const serverToBlock = await ServerModel.loadOrCreateByHost(options.hostToBlock) - - await addServerInBlocklist(options.byAccountId, serverToBlock.id) - }, - - unblockServer: async (options: { byAccountId: number, hostToUnblock: string }) => { - const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(options.byAccountId, options.hostToUnblock) - if (!serverBlock) return - - await removeServerFromBlocklist(serverBlock) - }, - - blockAccount: async (options: { byAccountId: number, handleToBlock: string }) => { - const accountToBlock = await AccountModel.loadByNameWithHost(options.handleToBlock) - if (!accountToBlock) return - - await addAccountInBlocklist(options.byAccountId, accountToBlock.id) - }, - - unblockAccount: async (options: { byAccountId: number, handleToUnblock: string }) => { - const targetAccount = await AccountModel.loadByNameWithHost(options.handleToUnblock) - if (!targetAccount) return - - const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(options.byAccountId, targetAccount.id) - if (!accountBlock) return - - await removeAccountFromBlocklist(accountBlock) - }, - - blacklistVideo: async (options: { videoIdOrUUID: number | string, createOptions: VideoBlacklistCreate }) => { - const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) - if (!video) return - - await blacklistVideo(video, options.createOptions) - }, - - unblacklistVideo: async (options: { videoIdOrUUID: number | string }) => { - const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) - if (!video) return - - const videoBlacklist = await VideoBlacklistModel.loadByVideoId(video.id) - if (!videoBlacklist) return - - await unblacklistVideo(videoBlacklist, video) - } - } -} - -function buildConfigHelpers () { - return { - getWebserverUrl () { - return WEBSERVER.URL - } - } -} diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 8e7491257..c19b40135 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts @@ -20,7 +20,7 @@ import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants' import { PluginModel } from '../../models/server/plugin' import { PluginLibrary, RegisterServerAuthExternalOptions, RegisterServerAuthPassOptions, RegisterServerOptions } from '../../types/plugins' import { ClientHtml } from '../client-html' -import { RegisterHelpersStore } from './register-helpers-store' +import { RegisterHelpers } from './register-helpers' import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' export interface RegisteredPlugin { @@ -40,7 +40,7 @@ export interface RegisteredPlugin { css: string[] // Only if this is a plugin - registerHelpersStore?: RegisterHelpersStore + registerHelpers?: RegisterHelpers unregister?: Function } @@ -109,7 +109,7 @@ export class PluginManager implements ServerHook { npmName: p.npmName, name: p.name, version: p.version, - idAndPassAuths: p.registerHelpersStore.getIdAndPassAuths() + idAndPassAuths: p.registerHelpers.getIdAndPassAuths() })) .filter(v => v.idAndPassAuths.length !== 0) } @@ -120,7 +120,7 @@ export class PluginManager implements ServerHook { npmName: p.npmName, name: p.name, version: p.version, - externalAuths: p.registerHelpersStore.getExternalAuths() + externalAuths: p.registerHelpers.getExternalAuths() })) .filter(v => v.externalAuths.length !== 0) } @@ -129,14 +129,14 @@ export class PluginManager implements ServerHook { const result = this.getRegisteredPluginOrTheme(npmName) if (!result || result.type !== PluginType.PLUGIN) return [] - return result.registerHelpersStore.getSettings() + return result.registerHelpers.getSettings() } getRouter (npmName: string) { const result = this.getRegisteredPluginOrTheme(npmName) if (!result || result.type !== PluginType.PLUGIN) return null - return result.registerHelpersStore.getRouter() + return result.registerHelpers.getRouter() } getTranslations (locale: string) { @@ -194,7 +194,7 @@ export class PluginManager implements ServerHook { logger.error('Cannot find plugin %s to call on settings changed.', name) } - for (const cb of registered.registerHelpersStore.getOnSettingsChangedCallbacks()) { + for (const cb of registered.registerHelpers.getOnSettingsChangedCallbacks()) { try { cb(settings) } catch (err) { @@ -268,8 +268,9 @@ export class PluginManager implements ServerHook { this.hooks[key] = this.hooks[key].filter(h => h.npmName !== npmName) } - const store = plugin.registerHelpersStore + const store = plugin.registerHelpers store.reinitVideoConstants(plugin.npmName) + store.reinitTranscodingProfilesAndEncoders(plugin.npmName) logger.info('Regenerating registered plugin CSS to global file.') await this.regeneratePluginGlobalCSS() @@ -375,11 +376,11 @@ export class PluginManager implements ServerHook { this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, plugin.type) let library: PluginLibrary - let registerHelpersStore: RegisterHelpersStore + let registerHelpers: RegisterHelpers if (plugin.type === PluginType.PLUGIN) { const result = await this.registerPlugin(plugin, pluginPath, packageJSON) library = result.library - registerHelpersStore = result.registerStore + registerHelpers = result.registerStore } const clientScripts: { [id: string]: ClientScript } = {} @@ -398,7 +399,7 @@ export class PluginManager implements ServerHook { staticDirs: packageJSON.staticDirs, clientScripts, css: packageJSON.css, - registerHelpersStore: registerHelpersStore || undefined, + registerHelpers: registerHelpers || undefined, unregister: library ? library.unregister : undefined } @@ -512,8 +513,8 @@ export class PluginManager implements ServerHook { const plugin = this.getRegisteredPluginOrTheme(npmName) if (!plugin || plugin.type !== PluginType.PLUGIN) return null - let auths: (RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions)[] = plugin.registerHelpersStore.getIdAndPassAuths() - auths = auths.concat(plugin.registerHelpersStore.getExternalAuths()) + let auths: (RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions)[] = plugin.registerHelpers.getIdAndPassAuths() + auths = auths.concat(plugin.registerHelpers.getExternalAuths()) return auths.find(a => a.authName === authName) } @@ -538,7 +539,7 @@ export class PluginManager implements ServerHook { private getRegisterHelpers ( npmName: string, plugin: PluginModel - ): { registerStore: RegisterHelpersStore, registerOptions: RegisterServerOptions } { + ): { registerStore: RegisterHelpers, registerOptions: RegisterServerOptions } { const onHookAdded = (options: RegisterServerHookOptions) => { if (!this.hooks[options.target]) this.hooks[options.target] = [] @@ -550,11 +551,11 @@ export class PluginManager implements ServerHook { }) } - const registerHelpersStore = new RegisterHelpersStore(npmName, plugin, onHookAdded.bind(this)) + const registerHelpers = new RegisterHelpers(npmName, plugin, onHookAdded.bind(this)) return { - registerStore: registerHelpersStore, - registerOptions: registerHelpersStore.buildRegisterHelpers() + registerStore: registerHelpers, + registerOptions: registerHelpers.buildRegisterHelpers() } } diff --git a/server/lib/plugins/register-helpers-store.ts b/server/lib/plugins/register-helpers-store.ts deleted file mode 100644 index c73079302..000000000 --- a/server/lib/plugins/register-helpers-store.ts +++ /dev/null @@ -1,357 +0,0 @@ -import * as express from 'express' -import { logger } from '@server/helpers/logger' -import { - VIDEO_CATEGORIES, - VIDEO_LANGUAGES, - VIDEO_LICENCES, - VIDEO_PLAYLIST_PRIVACIES, - VIDEO_PRIVACIES -} from '@server/initializers/constants' -import { onExternalUserAuthenticated } from '@server/lib/auth' -import { PluginModel } from '@server/models/server/plugin' -import { - RegisterServerAuthExternalOptions, - RegisterServerAuthExternalResult, - RegisterServerAuthPassOptions, - RegisterServerExternalAuthenticatedResult, - RegisterServerOptions -} from '@server/types/plugins' -import { - PluginPlaylistPrivacyManager, - PluginSettingsManager, - PluginStorageManager, - PluginVideoCategoryManager, - PluginVideoLanguageManager, - PluginVideoLicenceManager, - PluginVideoPrivacyManager, - RegisterServerHookOptions, - RegisterServerSettingOptions -} from '@shared/models' -import { serverHookObject } from '@shared/models/plugins/server-hook.model' -import { buildPluginHelpers } from './plugin-helpers' - -type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' -type VideoConstant = { [key in number | string]: string } - -type UpdatedVideoConstant = { - [name in AlterableVideoConstant]: { - added: { key: number | string, label: string }[] - deleted: { key: number | string, label: string }[] - } -} - -export class RegisterHelpersStore { - private readonly updatedVideoConstants: UpdatedVideoConstant = { - playlistPrivacy: { added: [], deleted: [] }, - privacy: { added: [], deleted: [] }, - language: { added: [], deleted: [] }, - licence: { added: [], deleted: [] }, - category: { added: [], deleted: [] } - } - - private readonly settings: RegisterServerSettingOptions[] = [] - - private idAndPassAuths: RegisterServerAuthPassOptions[] = [] - private externalAuths: RegisterServerAuthExternalOptions[] = [] - - private readonly onSettingsChangeCallbacks: ((settings: any) => void)[] = [] - - private readonly router: express.Router - - constructor ( - private readonly npmName: string, - private readonly plugin: PluginModel, - private readonly onHookAdded: (options: RegisterServerHookOptions) => void - ) { - this.router = express.Router() - } - - buildRegisterHelpers (): RegisterServerOptions { - const registerHook = this.buildRegisterHook() - const registerSetting = this.buildRegisterSetting() - - const getRouter = this.buildGetRouter() - - const settingsManager = this.buildSettingsManager() - const storageManager = this.buildStorageManager() - - const videoLanguageManager = this.buildVideoLanguageManager() - - const videoLicenceManager = this.buildVideoLicenceManager() - const videoCategoryManager = this.buildVideoCategoryManager() - - const videoPrivacyManager = this.buildVideoPrivacyManager() - const playlistPrivacyManager = this.buildPlaylistPrivacyManager() - - const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth() - const registerExternalAuth = this.buildRegisterExternalAuth() - const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth() - const unregisterExternalAuth = this.buildUnregisterExternalAuth() - - const peertubeHelpers = buildPluginHelpers(this.npmName) - - return { - registerHook, - registerSetting, - - getRouter, - - settingsManager, - storageManager, - - videoLanguageManager, - videoCategoryManager, - videoLicenceManager, - - videoPrivacyManager, - playlistPrivacyManager, - - registerIdAndPassAuth, - registerExternalAuth, - unregisterIdAndPassAuth, - unregisterExternalAuth, - - peertubeHelpers - } - } - - reinitVideoConstants (npmName: string) { - const hash = { - language: VIDEO_LANGUAGES, - licence: VIDEO_LICENCES, - category: VIDEO_CATEGORIES, - privacy: VIDEO_PRIVACIES, - playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES - } - const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ] - - for (const type of types) { - const updatedConstants = this.updatedVideoConstants[type][npmName] - if (!updatedConstants) continue - - for (const added of updatedConstants.added) { - delete hash[type][added.key] - } - - for (const deleted of updatedConstants.deleted) { - hash[type][deleted.key] = deleted.label - } - - delete this.updatedVideoConstants[type][npmName] - } - } - - getSettings () { - return this.settings - } - - getRouter () { - return this.router - } - - getIdAndPassAuths () { - return this.idAndPassAuths - } - - getExternalAuths () { - return this.externalAuths - } - - getOnSettingsChangedCallbacks () { - return this.onSettingsChangeCallbacks - } - - private buildGetRouter () { - return () => this.router - } - - private buildRegisterSetting () { - return (options: RegisterServerSettingOptions) => { - this.settings.push(options) - } - } - - private buildRegisterHook () { - return (options: RegisterServerHookOptions) => { - if (serverHookObject[options.target] !== true) { - logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName) - return - } - - return this.onHookAdded(options) - } - } - - private buildRegisterIdAndPassAuth () { - return (options: RegisterServerAuthPassOptions) => { - if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') { - logger.error('Cannot register auth plugin %s: authName, getWeight or login are not valid.', this.npmName, { options }) - return - } - - this.idAndPassAuths.push(options) - } - } - - private buildRegisterExternalAuth () { - const self = this - - return (options: RegisterServerAuthExternalOptions) => { - if (!options.authName || typeof options.authDisplayName !== 'function' || typeof options.onAuthRequest !== 'function') { - logger.error('Cannot register auth plugin %s: authName, authDisplayName or onAuthRequest are not valid.', this.npmName, { options }) - return - } - - this.externalAuths.push(options) - - return { - userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void { - onExternalUserAuthenticated({ - npmName: self.npmName, - authName: options.authName, - authResult: result - }).catch(err => { - logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err }) - }) - } - } as RegisterServerAuthExternalResult - } - } - - private buildUnregisterExternalAuth () { - return (authName: string) => { - this.externalAuths = this.externalAuths.filter(a => a.authName !== authName) - } - } - - private buildUnregisterIdAndPassAuth () { - return (authName: string) => { - this.idAndPassAuths = this.idAndPassAuths.filter(a => a.authName !== authName) - } - } - - private buildSettingsManager (): PluginSettingsManager { - return { - getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name, this.settings), - - getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names, this.settings), - - setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value), - - onSettingsChange: (cb: (settings: any) => void) => this.onSettingsChangeCallbacks.push(cb) - } - } - - private buildStorageManager (): PluginStorageManager { - return { - getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key), - - storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data) - } - } - - private buildVideoLanguageManager (): PluginVideoLanguageManager { - return { - addLanguage: (key: string, label: string) => { - return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label }) - }, - - deleteLanguage: (key: string) => { - return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key }) - } - } - } - - private buildVideoCategoryManager (): PluginVideoCategoryManager { - return { - addCategory: (key: number, label: string) => { - return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }) - }, - - deleteCategory: (key: number) => { - return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key }) - } - } - } - - private buildVideoPrivacyManager (): PluginVideoPrivacyManager { - return { - deletePrivacy: (key: number) => { - return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key }) - } - } - } - - private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager { - return { - deletePlaylistPrivacy: (key: number) => { - return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key }) - } - } - } - - private buildVideoLicenceManager (): PluginVideoLicenceManager { - return { - addLicence: (key: number, label: string) => { - return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label }) - }, - - deleteLicence: (key: number) => { - return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key }) - } - } - } - - private addConstant (parameters: { - npmName: string - type: AlterableVideoConstant - obj: VideoConstant - key: T - label: string - }) { - const { npmName, type, obj, key, label } = parameters - - if (obj[key]) { - logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key) - return false - } - - if (!this.updatedVideoConstants[type][npmName]) { - this.updatedVideoConstants[type][npmName] = { - added: [], - deleted: [] - } - } - - this.updatedVideoConstants[type][npmName].added.push({ key, label }) - obj[key] = label - - return true - } - - private deleteConstant (parameters: { - npmName: string - type: AlterableVideoConstant - obj: VideoConstant - key: T - }) { - const { npmName, type, obj, key } = parameters - - if (!obj[key]) { - logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key) - return false - } - - if (!this.updatedVideoConstants[type][npmName]) { - this.updatedVideoConstants[type][npmName] = { - added: [], - deleted: [] - } - } - - this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] }) - delete obj[key] - - return true - } -} diff --git a/server/lib/plugins/register-helpers.ts b/server/lib/plugins/register-helpers.ts new file mode 100644 index 000000000..3a38a4835 --- /dev/null +++ b/server/lib/plugins/register-helpers.ts @@ -0,0 +1,444 @@ +import * as express from 'express' +import { logger } from '@server/helpers/logger' +import { + VIDEO_CATEGORIES, + VIDEO_LANGUAGES, + VIDEO_LICENCES, + VIDEO_PLAYLIST_PRIVACIES, + VIDEO_PRIVACIES +} from '@server/initializers/constants' +import { onExternalUserAuthenticated } from '@server/lib/auth' +import { PluginModel } from '@server/models/server/plugin' +import { + RegisterServerAuthExternalOptions, + RegisterServerAuthExternalResult, + RegisterServerAuthPassOptions, + RegisterServerExternalAuthenticatedResult, + RegisterServerOptions +} from '@server/types/plugins' +import { + EncoderOptionsBuilder, + PluginPlaylistPrivacyManager, + PluginSettingsManager, + PluginStorageManager, + PluginVideoCategoryManager, + PluginVideoLanguageManager, + PluginVideoLicenceManager, + PluginVideoPrivacyManager, + RegisterServerHookOptions, + RegisterServerSettingOptions +} from '@shared/models' +import { serverHookObject } from '@shared/models/plugins/server-hook.model' +import { VideoTranscodingProfilesManager } from '../video-transcoding-profiles' +import { buildPluginHelpers } from './plugin-helpers-builder' + +type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' +type VideoConstant = { [key in number | string]: string } + +type UpdatedVideoConstant = { + [name in AlterableVideoConstant]: { + added: { key: number | string, label: string }[] + deleted: { key: number | string, label: string }[] + } +} + +export class RegisterHelpers { + private readonly updatedVideoConstants: UpdatedVideoConstant = { + playlistPrivacy: { added: [], deleted: [] }, + privacy: { added: [], deleted: [] }, + language: { added: [], deleted: [] }, + licence: { added: [], deleted: [] }, + category: { added: [], deleted: [] } + } + + private readonly transcodingProfiles: { + [ npmName: string ]: { + type: 'vod' | 'live' + encoder: string + profile: string + }[] + } = {} + + private readonly transcodingEncoders: { + [ npmName: string ]: { + type: 'vod' | 'live' + streamType: 'audio' | 'video' + encoder: string + priority: number + }[] + } = {} + + private readonly settings: RegisterServerSettingOptions[] = [] + + private idAndPassAuths: RegisterServerAuthPassOptions[] = [] + private externalAuths: RegisterServerAuthExternalOptions[] = [] + + private readonly onSettingsChangeCallbacks: ((settings: any) => void)[] = [] + + private readonly router: express.Router + + constructor ( + private readonly npmName: string, + private readonly plugin: PluginModel, + private readonly onHookAdded: (options: RegisterServerHookOptions) => void + ) { + this.router = express.Router() + } + + buildRegisterHelpers (): RegisterServerOptions { + const registerHook = this.buildRegisterHook() + const registerSetting = this.buildRegisterSetting() + + const getRouter = this.buildGetRouter() + + const settingsManager = this.buildSettingsManager() + const storageManager = this.buildStorageManager() + + const videoLanguageManager = this.buildVideoLanguageManager() + + const videoLicenceManager = this.buildVideoLicenceManager() + const videoCategoryManager = this.buildVideoCategoryManager() + + const videoPrivacyManager = this.buildVideoPrivacyManager() + const playlistPrivacyManager = this.buildPlaylistPrivacyManager() + + const transcodingManager = this.buildTranscodingManager() + + const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth() + const registerExternalAuth = this.buildRegisterExternalAuth() + const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth() + const unregisterExternalAuth = this.buildUnregisterExternalAuth() + + const peertubeHelpers = buildPluginHelpers(this.npmName) + + return { + registerHook, + registerSetting, + + getRouter, + + settingsManager, + storageManager, + + videoLanguageManager, + videoCategoryManager, + videoLicenceManager, + + videoPrivacyManager, + playlistPrivacyManager, + + transcodingManager, + + registerIdAndPassAuth, + registerExternalAuth, + unregisterIdAndPassAuth, + unregisterExternalAuth, + + peertubeHelpers + } + } + + reinitVideoConstants (npmName: string) { + const hash = { + language: VIDEO_LANGUAGES, + licence: VIDEO_LICENCES, + category: VIDEO_CATEGORIES, + privacy: VIDEO_PRIVACIES, + playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES + } + const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ] + + for (const type of types) { + const updatedConstants = this.updatedVideoConstants[type][npmName] + if (!updatedConstants) continue + + for (const added of updatedConstants.added) { + delete hash[type][added.key] + } + + for (const deleted of updatedConstants.deleted) { + hash[type][deleted.key] = deleted.label + } + + delete this.updatedVideoConstants[type][npmName] + } + } + + reinitTranscodingProfilesAndEncoders (npmName: string) { + const profiles = this.transcodingProfiles[npmName] + if (Array.isArray(profiles)) { + for (const profile of profiles) { + VideoTranscodingProfilesManager.Instance.removeProfile(profile) + } + } + + const encoders = this.transcodingEncoders[npmName] + if (Array.isArray(encoders)) { + for (const o of encoders) { + VideoTranscodingProfilesManager.Instance.removeEncoderPriority(o.type, o.streamType, o.encoder, o.priority) + } + } + } + + getSettings () { + return this.settings + } + + getRouter () { + return this.router + } + + getIdAndPassAuths () { + return this.idAndPassAuths + } + + getExternalAuths () { + return this.externalAuths + } + + getOnSettingsChangedCallbacks () { + return this.onSettingsChangeCallbacks + } + + private buildGetRouter () { + return () => this.router + } + + private buildRegisterSetting () { + return (options: RegisterServerSettingOptions) => { + this.settings.push(options) + } + } + + private buildRegisterHook () { + return (options: RegisterServerHookOptions) => { + if (serverHookObject[options.target] !== true) { + logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName) + return + } + + return this.onHookAdded(options) + } + } + + private buildRegisterIdAndPassAuth () { + return (options: RegisterServerAuthPassOptions) => { + if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') { + logger.error('Cannot register auth plugin %s: authName, getWeight or login are not valid.', this.npmName, { options }) + return + } + + this.idAndPassAuths.push(options) + } + } + + private buildRegisterExternalAuth () { + const self = this + + return (options: RegisterServerAuthExternalOptions) => { + if (!options.authName || typeof options.authDisplayName !== 'function' || typeof options.onAuthRequest !== 'function') { + logger.error('Cannot register auth plugin %s: authName, authDisplayName or onAuthRequest are not valid.', this.npmName, { options }) + return + } + + this.externalAuths.push(options) + + return { + userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void { + onExternalUserAuthenticated({ + npmName: self.npmName, + authName: options.authName, + authResult: result + }).catch(err => { + logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err }) + }) + } + } as RegisterServerAuthExternalResult + } + } + + private buildUnregisterExternalAuth () { + return (authName: string) => { + this.externalAuths = this.externalAuths.filter(a => a.authName !== authName) + } + } + + private buildUnregisterIdAndPassAuth () { + return (authName: string) => { + this.idAndPassAuths = this.idAndPassAuths.filter(a => a.authName !== authName) + } + } + + private buildSettingsManager (): PluginSettingsManager { + return { + getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name, this.settings), + + getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names, this.settings), + + setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value), + + onSettingsChange: (cb: (settings: any) => void) => this.onSettingsChangeCallbacks.push(cb) + } + } + + private buildStorageManager (): PluginStorageManager { + return { + getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key), + + storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data) + } + } + + private buildVideoLanguageManager (): PluginVideoLanguageManager { + return { + addLanguage: (key: string, label: string) => { + return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label }) + }, + + deleteLanguage: (key: string) => { + return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key }) + } + } + } + + private buildVideoCategoryManager (): PluginVideoCategoryManager { + return { + addCategory: (key: number, label: string) => { + return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }) + }, + + deleteCategory: (key: number) => { + return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key }) + } + } + } + + private buildVideoPrivacyManager (): PluginVideoPrivacyManager { + return { + deletePrivacy: (key: number) => { + return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key }) + } + } + } + + private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager { + return { + deletePlaylistPrivacy: (key: number) => { + return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key }) + } + } + } + + private buildVideoLicenceManager (): PluginVideoLicenceManager { + return { + addLicence: (key: number, label: string) => { + return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label }) + }, + + deleteLicence: (key: number) => { + return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key }) + } + } + } + + private addConstant (parameters: { + npmName: string + type: AlterableVideoConstant + obj: VideoConstant + key: T + label: string + }) { + const { npmName, type, obj, key, label } = parameters + + if (obj[key]) { + logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key) + return false + } + + if (!this.updatedVideoConstants[type][npmName]) { + this.updatedVideoConstants[type][npmName] = { + added: [], + deleted: [] + } + } + + this.updatedVideoConstants[type][npmName].added.push({ key, label }) + obj[key] = label + + return true + } + + private deleteConstant (parameters: { + npmName: string + type: AlterableVideoConstant + obj: VideoConstant + key: T + }) { + const { npmName, type, obj, key } = parameters + + if (!obj[key]) { + logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key) + return false + } + + if (!this.updatedVideoConstants[type][npmName]) { + this.updatedVideoConstants[type][npmName] = { + added: [], + deleted: [] + } + } + + this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] }) + delete obj[key] + + return true + } + + private buildTranscodingManager () { + const self = this + + function addProfile (type: 'live' | 'vod', encoder: string, profile: string, builder: EncoderOptionsBuilder) { + if (profile === 'default') { + logger.error('A plugin cannot add a default live transcoding profile') + return false + } + + VideoTranscodingProfilesManager.Instance.addProfile({ + type, + encoder, + profile, + builder + }) + + if (!self.transcodingProfiles[self.npmName]) self.transcodingProfiles[self.npmName] = [] + self.transcodingProfiles[self.npmName].push({ type, encoder, profile }) + + return true + } + + function addEncoderPriority (type: 'live' | 'vod', streamType: 'audio' | 'video', encoder: string, priority: number) { + VideoTranscodingProfilesManager.Instance.addEncoderPriority(type, streamType, encoder, priority) + + if (!self.transcodingEncoders[self.npmName]) self.transcodingEncoders[self.npmName] = [] + self.transcodingEncoders[self.npmName].push({ type, streamType, encoder, priority }) + } + + return { + addLiveProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder) { + return addProfile('live', encoder, profile, builder) + }, + + addVODProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder) { + return addProfile('vod', encoder, profile, builder) + }, + + addLiveEncoderPriority (streamType: 'audio' | 'video', encoder: string, priority: number) { + return addEncoderPriority('live', streamType, encoder, priority) + }, + + addVODEncoderPriority (streamType: 'audio' | 'video', encoder: string, priority: number) { + return addEncoderPriority('vod', streamType, encoder, priority) + } + } + } +} -- cgit v1.2.3