renderers.js 5.49 KB
Newer Older
1 2 3 4 5
const Model = require('objection').Model
const path = require('path')
const fs = require('fs-extra')
const _ = require('lodash')
const yaml = require('js-yaml')
6
const DepGraph = require('dependency-graph').DepGraph
7 8 9 10 11 12 13 14 15
const commonHelper = require('../helpers/common')

/* global WIKI */

/**
 * Renderer model
 */
module.exports = class Renderer extends Model {
  static get tableName() { return 'renderers' }
16
  static get idColumn() { return 'key' }
17 18 19 20 21 22 23 24

  static get jsonSchema () {
    return {
      type: 'object',
      required: ['key', 'isEnabled'],

      properties: {
        key: {type: 'string'},
25
        isEnabled: {type: 'boolean'}
26 27 28 29
      }
    }
  }

30 31 32 33
  static get jsonAttributes() {
    return ['config']
  }

34 35 36 37
  static async getRenderers() {
    return WIKI.models.renderers.query()
  }

38 39 40 41 42 43 44 45 46 47 48 49 50
  static async fetchDefinitions() {
    const rendererDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/rendering'))
    let diskRenderers = []
    for (let dir of rendererDirs) {
      const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/rendering', dir, 'definition.yml'), 'utf8')
      diskRenderers.push(yaml.safeLoad(def))
    }
    WIKI.data.renderers = diskRenderers.map(renderer => ({
      ...renderer,
      props: commonHelper.parseModuleProps(renderer.props)
    }))
  }

51 52 53 54 55 56
  static async refreshRenderersFromDisk() {
    let trx
    try {
      const dbRenderers = await WIKI.models.renderers.query()

      // -> Fetch definitions from disk
57
      await WIKI.models.renderers.fetchDefinitions()
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

      // -> Insert new Renderers
      let newRenderers = []
      for (let renderer of WIKI.data.renderers) {
        if (!_.some(dbRenderers, ['key', renderer.key])) {
          newRenderers.push({
            key: renderer.key,
            isEnabled: _.get(renderer, 'enabledDefault', true),
            config: _.transform(renderer.props, (result, value, key) => {
              _.set(result, key, value.default)
              return result
            }, {})
          })
        } else {
          const rendererConfig = _.get(_.find(dbRenderers, ['key', renderer.key]), 'config', {})
          await WIKI.models.renderers.query().patch({
            config: _.transform(renderer.props, (result, value, key) => {
              if (!_.has(result, key)) {
                _.set(result, key, value.default)
              }
              return result
            }, rendererConfig)
          }).where('key', renderer.key)
        }
      }
      if (newRenderers.length > 0) {
        trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex)
        for (let renderer of newRenderers) {
          await WIKI.models.renderers.query(trx).insert(renderer)
        }
        await trx.commit()
        WIKI.logger.info(`Loaded ${newRenderers.length} new renderers: [ OK ]`)
      } else {
        WIKI.logger.info(`No new renderers found: [ SKIPPED ]`)
      }
93 94 95 96 97 98 99 100

      // -> Delete removed Renderers
      for (const renderer of dbRenderers) {
        if (!_.some(WIKI.data.renderers, ['key', renderer.key])) {
          await WIKI.models.renderers.query().where('key', renderer.key).del()
          WIKI.logger.info(`Removed renderer ${renderer.key} because it is no longer present in the modules folder: [ OK ]`)
        }
      }
101 102 103 104 105 106 107 108 109
    } catch (err) {
      WIKI.logger.error(`Failed to scan or load new renderers: [ FAILED ]`)
      WIKI.logger.error(err)
      if (trx) {
        trx.rollback()
      }
    }
  }

110 111 112 113 114 115 116 117 118 119 120 121 122
  static async getRenderingPipeline(contentType) {
    const renderersDb = await WIKI.models.renderers.query().where('isEnabled', true)
    if (renderersDb && renderersDb.length > 0) {
      const renderers = renderersDb.map(rdr => {
        const renderer = _.find(WIKI.data.renderers, ['key', rdr.key])
        return {
          ...renderer,
          config: rdr.config
        }
      })

      // Build tree
      const rawCores = _.filter(renderers, renderer => !_.has(renderer, 'dependsOn')).map(core => {
123
        core.children = _.filter(renderers, ['dependsOn', core.key])
124 125 126 127 128 129 130 131 132 133 134 135 136
        return core
      })

      // Build dependency graph
      const graph = new DepGraph({ circular: true })
      rawCores.map(core => { graph.addNode(core.key) })
      rawCores.map(core => {
        rawCores.map(coreTarget => {
          if (core.key !== coreTarget.key) {
            if (core.output === coreTarget.input) {
              graph.addDependency(core.key, coreTarget.key)
            }
          }
137 138
        })
      })
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169

      // Filter unused cores
      let activeCoreKeys = _.filter(rawCores, ['input', contentType]).map(core => core.key)
      _.clone(activeCoreKeys).map(coreKey => {
        activeCoreKeys = _.union(activeCoreKeys, graph.dependenciesOf(coreKey))
      })
      const activeCores = _.filter(rawCores, core => _.includes(activeCoreKeys, core.key))

      // Rebuild dependency graph with active cores
      const graphActive = new DepGraph({ circular: true })
      activeCores.map(core => { graphActive.addNode(core.key) })
      activeCores.map(core => {
        activeCores.map(coreTarget => {
          if (core.key !== coreTarget.key) {
            if (core.output === coreTarget.input) {
              graphActive.addDependency(core.key, coreTarget.key)
            }
          }
        })
      })

      // Reorder cores in reverse dependency order
      let orderedCores = []
      _.reverse(graphActive.overallOrder()).map(coreKey => {
        orderedCores.push(_.find(rawCores, ['key', coreKey]))
      })

      return orderedCores
    } else {
      WIKI.logger.error(`Rendering pipeline is empty!`)
      return false
170 171 172
    }
  }
}