You need to sign in or sign up before continuing.
storage.js 6.83 KB
Newer Older
1 2
const Model = require('objection').Model
const path = require('path')
3
const fs = require('fs-extra')
4
const _ = require('lodash')
5
const yaml = require('js-yaml')
6
const commonHelper = require('../helpers/common')
7 8 9 10 11 12 13 14

/* global WIKI */

/**
 * Storage model
 */
module.exports = class Storage extends Model {
  static get tableName() { return 'storage' }
15
  static get idColumn() { return 'key' }
16 17 18 19

  static get jsonSchema () {
    return {
      type: 'object',
20
      required: ['key', 'isEnabled'],
21 22 23 24

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

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

34
  static async getTargets() {
35
    return WIKI.models.storage.query()
36 37 38
  }

  static async refreshTargetsFromDisk() {
39
    let trx
40
    try {
41
      const dbTargets = await WIKI.models.storage.query()
42 43 44 45 46 47 48 49

      // -> Fetch definitions from disk
      const storageDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/storage'))
      let diskTargets = []
      for (let dir of storageDirs) {
        const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/storage', dir, 'definition.yml'), 'utf8')
        diskTargets.push(yaml.safeLoad(def))
      }
50 51
      WIKI.data.storage = diskTargets.map(target => ({
        ...target,
52
        isAvailable: _.get(target, 'isAvailable', false),
53
        props: commonHelper.parseModuleProps(target.props)
54
      }))
55 56

      // -> Insert new targets
57
      let newTargets = []
58
      for (let target of WIKI.data.storage) {
59 60 61 62
        if (!_.some(dbTargets, ['key', target.key])) {
          newTargets.push({
            key: target.key,
            isEnabled: false,
63
            mode: target.defaultMode || 'push',
Nick's avatar
Nick committed
64
            syncInterval: target.schedule || 'P0D',
65
            config: _.transform(target.props, (result, value, key) => {
66
              _.set(result, key, value.default)
67
              return result
Nick's avatar
Nick committed
68 69 70
            }, {}),
            state: {
              status: 'pending',
Nick's avatar
Nick committed
71 72
              message: '',
              lastAttempt: null
Nick's avatar
Nick committed
73
            }
74
          })
75 76 77 78 79 80 81 82 83 84
        } else {
          const targetConfig = _.get(_.find(dbTargets, ['key', target.key]), 'config', {})
          await WIKI.models.storage.query().patch({
            config: _.transform(target.props, (result, value, key) => {
              if (!_.has(result, key)) {
                _.set(result, key, value.default)
              }
              return result
            }, targetConfig)
          }).where('key', target.key)
85
        }
86
      }
87
      if (newTargets.length > 0) {
88 89 90 91 92
        trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex)
        for (let target of newTargets) {
          await WIKI.models.storage.query(trx).insert(target)
        }
        await trx.commit()
93 94 95 96
        WIKI.logger.info(`Loaded ${newTargets.length} new storage targets: [ OK ]`)
      } else {
        WIKI.logger.info(`No new storage targets found: [ SKIPPED ]`)
      }
97 98 99 100 101 102 103 104

      // -> Delete removed targets
      for (const target of dbTargets) {
        if (!_.some(WIKI.data.storage, ['key', target.key])) {
          await WIKI.models.storage.query().where('key', target.key).del()
          WIKI.logger.info(`Removed target ${target.key} because it is no longer present in the modules folder: [ OK ]`)
        }
      }
105 106 107
    } catch (err) {
      WIKI.logger.error(`Failed to scan or load new storage providers: [ FAILED ]`)
      WIKI.logger.error(err)
108 109 110
      if (trx) {
        trx.rollback()
      }
111 112
    }
  }
113

Nick's avatar
Nick committed
114 115 116
  /**
   * Initialize active storage targets
   */
Nick's avatar
Nick committed
117
  static async initTargets() {
Nick's avatar
Nick committed
118
    this.targets = await WIKI.models.storage.query().where('isEnabled', true).orderBy('key')
Nick's avatar
Nick committed
119
    try {
Nick's avatar
Nick committed
120 121 122 123 124 125 126
      // -> Stop and delete existing jobs
      const prevjobs = _.remove(WIKI.scheduler.jobs, job => job.name === `sync-storage`)
      if (prevjobs.length > 0) {
        prevjobs.forEach(job => job.stop())
      }

      // -> Initialize targets
127
      for (let target of this.targets) {
Nick's avatar
Nick committed
128
        const targetDef = _.find(WIKI.data.storage, ['key', target.key])
Nick's avatar
Nick committed
129
        target.fn = require(`../modules/storage/${target.key}/storage`)
130 131
        target.fn.config = target.config
        target.fn.mode = target.mode
Nick's avatar
Nick committed
132 133
        try {
          await target.fn.init()
Nick's avatar
Nick committed
134 135

          // -> Save succeeded init state
Nick's avatar
Nick committed
136 137 138
          await WIKI.models.storage.query().patch({
            state: {
              status: 'operational',
Nick's avatar
Nick committed
139 140
              message: '',
              lastAttempt: new Date().toISOString()
Nick's avatar
Nick committed
141 142
            }
          }).where('key', target.key)
Nick's avatar
Nick committed
143 144 145 146 147 148 149 150 151 152

          // -> Set recurring sync job
          if (targetDef.schedule && target.syncInterval !== `P0D`) {
            WIKI.scheduler.registerJob({
              name: `sync-storage`,
              immediate: false,
              schedule: target.syncInterval,
              repeat: true
            }, target.key)
          }
153 154

          // -> Set internal recurring sync job
155
          if (targetDef.internalSchedule && targetDef.internalSchedule !== `P0D`) {
156 157 158
            WIKI.scheduler.registerJob({
              name: `sync-storage`,
              immediate: false,
159
              schedule: target.internalSchedule,
160 161 162
              repeat: true
            }, target.key)
          }
Nick's avatar
Nick committed
163
        } catch (err) {
Nick's avatar
Nick committed
164
          // -> Save initialization error
Nick's avatar
Nick committed
165 166 167
          await WIKI.models.storage.query().patch({
            state: {
              status: 'error',
Nick's avatar
Nick committed
168 169
              message: err.message,
              lastAttempt: new Date().toISOString()
Nick's avatar
Nick committed
170 171 172
            }
          }).where('key', target.key)
        }
Nick's avatar
Nick committed
173 174 175 176 177 178 179
      }
    } catch (err) {
      WIKI.logger.warn(err)
      throw err
    }
  }

180
  static async pageEvent({ event, page }) {
Nick's avatar
Nick committed
181
    try {
182
      for (let target of this.targets) {
183
        await target.fn[event](page)
Nick's avatar
Nick committed
184 185 186 187
      }
    } catch (err) {
      WIKI.logger.warn(err)
      throw err
188 189
    }
  }
190

191 192 193 194 195 196 197 198 199 200 201
  static async assetEvent({ event, asset }) {
    try {
      for (let target of this.targets) {
        await target.fn[`asset${_.capitalize(event)}`](asset)
      }
    } catch (err) {
      WIKI.logger.warn(err)
      throw err
    }
  }

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
  static async getLocalLocations({ asset }) {
    const locations = []
    const promises = this.targets.map(async (target) => {
      try {
        const path = await target.fn.getLocalLocation(asset)
        locations.push({
          path,
          key: target.key
        })
      } catch (err) {
        WIKI.logger.warn(err)
      }
    })
    await Promise.all(promises)
    return locations
  }

219 220 221 222
  static async executeAction(targetKey, handler) {
    try {
      const target = _.find(this.targets, ['key', targetKey])
      if (target) {
223
        if (_.hasIn(target.fn, handler)) {
224 225 226 227 228 229 230 231 232 233 234 235
          await target.fn[handler]()
        } else {
          throw new Error('Invalid Handler for Storage Target')
        }
      } else {
        throw new Error('Invalid or Inactive Storage Target')
      }
    } catch (err) {
      WIKI.logger.warn(err)
      throw err
    }
  }
236
}