]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
WIP plugins: static files
authorChocobozzz <me@florianbigard.com>
Mon, 8 Jul 2019 12:02:03 +0000 (14:02 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Wed, 24 Jul 2019 08:58:16 +0000 (10:58 +0200)
scripts/plugin/install.ts
scripts/plugin/uninstall.ts [new file with mode: 0755]
server/controllers/plugins.ts
server/lib/plugins/plugin-manager.ts
server/models/server/plugin.ts
shared/models/plugins/plugin-package-json.model.ts

index 8e9c9897f437a26260996a169d301145eb52d4ad..1725cbeb6a437a86f08e56a7f9811164fb666e9f 100755 (executable)
@@ -4,9 +4,9 @@ import { PluginManager } from '../../server/lib/plugins/plugin-manager'
 import { isAbsolute } from 'path'
 
 program
-  .option('-n, --pluginName [pluginName]', 'Plugin name to install')
-  .option('-v, --pluginVersion [pluginVersion]', 'Plugin version to install')
-  .option('-p, --pluginPath [pluginPath]', 'Path of the plugin you want to install')
+  .option('-n, --plugin-name [pluginName]', 'Plugin name to install')
+  .option('-v, --plugin-version [pluginVersion]', 'Plugin version to install')
+  .option('-p, --plugin-path [pluginPath]', 'Path of the plugin you want to install')
   .parse(process.argv)
 
 if (!program['pluginName'] && !program['pluginPath']) {
diff --git a/scripts/plugin/uninstall.ts b/scripts/plugin/uninstall.ts
new file mode 100755 (executable)
index 0000000..7dcc234
--- /dev/null
@@ -0,0 +1,27 @@
+import { initDatabaseModels } from '../../server/initializers/database'
+import * as program from 'commander'
+import { PluginManager } from '../../server/lib/plugins/plugin-manager'
+import { isAbsolute } from 'path'
+
+program
+  .option('-n, --package-name [packageName]', 'Package name to install')
+  .parse(process.argv)
+
+if (!program['packageName']) {
+  console.error('You need to specify the plugin name.')
+  process.exit(-1)
+}
+
+run()
+  .then(() => process.exit(0))
+  .catch(err => {
+    console.error(err)
+    process.exit(-1)
+  })
+
+async function run () {
+  await initDatabaseModels(true)
+
+  const toUninstall = program['packageName']
+  await PluginManager.Instance.uninstall(toUninstall)
+}
index a6705d9c7c803550110342b5a2a908bbb60e84aa..05f03324d80f69f8e2b4095925a4b85678784c42 100644 (file)
@@ -1,21 +1,21 @@
 import * as express from 'express'
 import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
-import { join } from 'path'
+import { basename, join } from 'path'
 import { RegisteredPlugin } from '../lib/plugins/plugin-manager'
 import { servePluginStaticDirectoryValidator } from '../middlewares/validators/plugins'
 
 const pluginsRouter = express.Router()
 
 pluginsRouter.get('/global.css',
-  express.static(PLUGIN_GLOBAL_CSS_PATH, { fallthrough: false })
+  servePluginGlobalCSS
 )
 
-pluginsRouter.get('/:pluginName/:pluginVersion/statics/:staticEndpoint',
+pluginsRouter.get('/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
   servePluginStaticDirectoryValidator,
   servePluginStaticDirectory
 )
 
-pluginsRouter.get('/:pluginName/:pluginVersion/client-scripts/:staticEndpoint',
+pluginsRouter.get('/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)',
   servePluginStaticDirectoryValidator,
   servePluginClientScripts
 )
@@ -28,21 +28,33 @@ export {
 
 // ---------------------------------------------------------------------------
 
+function servePluginGlobalCSS (req: express.Request, res: express.Response) {
+  return res.sendFile(PLUGIN_GLOBAL_CSS_PATH)
+}
+
 function servePluginStaticDirectory (req: express.Request, res: express.Response) {
   const plugin: RegisteredPlugin = res.locals.registeredPlugin
   const staticEndpoint = req.params.staticEndpoint
 
-  const staticPath = plugin.staticDirs[staticEndpoint]
+  const [ directory, ...file ] = staticEndpoint.split('/')
+
+  const staticPath = plugin.staticDirs[directory]
   if (!staticPath) {
     return res.sendStatus(404)
   }
 
-  return express.static(join(plugin.path, staticPath), { fallthrough: false })
+  const filepath = file.join('/')
+  return res.sendFile(join(plugin.path, staticPath, filepath))
 }
 
 function servePluginClientScripts (req: express.Request, res: express.Response) {
   const plugin: RegisteredPlugin = res.locals.registeredPlugin
   const staticEndpoint = req.params.staticEndpoint
 
-  return express.static(join(plugin.path, staticEndpoint), { fallthrough: false })
+  const file = plugin.clientScripts[staticEndpoint]
+  if (!file) {
+    return res.sendStatus(404)
+  }
+
+  return res.sendFile(join(plugin.path, staticEndpoint))
 }
index 533ed4391335905643a22065a5ae3a786440a142..b898e64fa2084a16cd7de0809c96cc6e4da7ff89 100644 (file)
@@ -4,12 +4,13 @@ import { RegisterHookOptions } from '../../../shared/models/plugins/register.mod
 import { basename, join } from 'path'
 import { CONFIG } from '../../initializers/config'
 import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins'
-import { PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model'
+import { ClientScript, PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model'
 import { PluginLibrary } from '../../../shared/models/plugins/plugin-library.model'
 import { createReadStream, createWriteStream } from 'fs'
 import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants'
 import { PluginType } from '../../../shared/models/plugins/plugin.type'
 import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn'
+import { outputFile } from 'fs-extra'
 
 export interface RegisteredPlugin {
   name: string
@@ -22,6 +23,7 @@ export interface RegisteredPlugin {
   path: string
 
   staticDirs: { [name: string]: string }
+  clientScripts: { [name: string]: ClientScript }
 
   css: string[]
 
@@ -46,6 +48,8 @@ export class PluginManager {
   }
 
   async registerPlugins () {
+    await this.resetCSSGlobalFile()
+
     const plugins = await PluginModel.listEnabledPluginsAndThemes()
 
     for (const plugin of plugins) {
@@ -83,6 +87,16 @@ export class PluginManager {
     }
 
     await plugin.unregister()
+
+    // Remove hooks of this plugin
+    for (const key of Object.keys(this.hooks)) {
+      this.hooks[key] = this.hooks[key].filter(h => h.pluginName !== name)
+    }
+
+    delete this.registeredPlugins[plugin.name]
+
+    logger.info('Regenerating registered plugin CSS to global file.')
+    await this.regeneratePluginGlobalCSS()
   }
 
   async install (toInstall: string, version: string, fromDisk = false) {
@@ -132,9 +146,30 @@ export class PluginManager {
   }
 
   async uninstall (packageName: string) {
-    await PluginModel.uninstall(this.normalizePluginName(packageName))
+    logger.info('Uninstalling plugin %s.', packageName)
+
+    const pluginName = this.normalizePluginName(packageName)
+
+    try {
+      await this.unregister(pluginName)
+    } catch (err) {
+      logger.warn('Cannot unregister plugin %s.', pluginName, { err })
+    }
+
+    const plugin = await PluginModel.load(pluginName)
+    if (!plugin || plugin.uninstalled === true) {
+      logger.error('Cannot uninstall plugin %s: it does not exist or is already uninstalled.', packageName)
+      return
+    }
+
+    plugin.enabled = false
+    plugin.uninstalled = true
+
+    await plugin.save()
 
     await removeNpmPlugin(packageName)
+
+    logger.info('Plugin %s uninstalled.', packageName)
   }
 
   private async registerPluginOrTheme (plugin: PluginModel) {
@@ -152,6 +187,11 @@ export class PluginManager {
       library = await this.registerPlugin(plugin, pluginPath, packageJSON)
     }
 
+    const clientScripts: { [id: string]: ClientScript } = {}
+    for (const c of packageJSON.clientScripts) {
+      clientScripts[c.script] = c
+    }
+
     this.registeredPlugins[ plugin.name ] = {
       name: plugin.name,
       type: plugin.type,
@@ -160,6 +200,7 @@ export class PluginManager {
       peertubeEngine: plugin.peertubeEngine,
       path: pluginPath,
       staticDirs: packageJSON.staticDirs,
+      clientScripts,
       css: packageJSON.css,
       unregister: library ? library.unregister : undefined
     }
@@ -199,6 +240,10 @@ export class PluginManager {
     }
   }
 
+  private resetCSSGlobalFile () {
+    return outputFile(PLUGIN_GLOBAL_CSS_PATH, '')
+  }
+
   private async addCSSToGlobalFile (pluginPath: string, cssRelativePaths: string[]) {
     for (const cssPath of cssRelativePaths) {
       await this.concatFiles(join(pluginPath, cssPath), PLUGIN_GLOBAL_CSS_PATH)
@@ -207,8 +252,8 @@ export class PluginManager {
 
   private concatFiles (input: string, output: string) {
     return new Promise<void>((res, rej) => {
-      const outputStream = createWriteStream(input)
-      const inputStream = createReadStream(output)
+      const inputStream = createReadStream(input)
+      const outputStream = createWriteStream(output, { flags: 'a' })
 
       inputStream.pipe(outputStream)
 
@@ -233,6 +278,16 @@ export class PluginManager {
     return name.replace(/^peertube-((theme)|(plugin))-/, '')
   }
 
+  private async regeneratePluginGlobalCSS () {
+    await this.resetCSSGlobalFile()
+
+    for (const key of Object.keys(this.registeredPlugins)) {
+      const plugin = this.registeredPlugins[key]
+
+      await this.addCSSToGlobalFile(plugin.path, plugin.css)
+    }
+  }
+
   static get Instance () {
     return this.instance || (this.instance = new this())
   }
index 1fbfd208f0d20c18cf533bd38fc1d4bb35f4721a..b3b8276dffe87b7fa15b97ae7953fa1fa887cad8 100644 (file)
@@ -75,6 +75,16 @@ export class PluginModel extends Model<PluginModel> {
     return PluginModel.findAll(query)
   }
 
+  static load (pluginName: string) {
+    const query = {
+      where: {
+        name: pluginName
+      }
+    }
+
+    return PluginModel.findOne(query)
+  }
+
   static uninstall (pluginName: string) {
     const query = {
       where: {
index d5aa9017992aef2dfa7aa638a20659a026ec4d9c..f8029ec3435d5b2cfa0654498e9a05d6a47c761e 100644 (file)
@@ -1,3 +1,8 @@
+export type ClientScript = {
+  script: string,
+  scopes: string[]
+}
+
 export type PluginPackageJson = {
   name: string
   version: string
@@ -12,5 +17,5 @@ export type PluginPackageJson = {
   staticDirs: { [ name: string ]: string }
   css: string[]
 
-  clientScripts: { script: string, scopes: string[] }[]
+  clientScripts: ClientScript[]
 }