blob-stores/mdbBlobStore.js

const BlobStoreCustom = require('./blobStoreCustom')
const path = require('path')
const fs = require('fs')
const os = require('os')

// model's public folder may not exists - in this we will create it
// during `getPermanentFileName` and cache verified path's here
const VERIFIED_PATH = {}
/**
 *  @classdesc
 *  Blob store implementation for storing content inside models `public` folders.
 *  Key conceptions:
 *
 *    - relative path created in format modelName|relativePathFromModelDir to hide real file place from client
 *    - OS user temp folder used for store temporary content
 *    - delete operation is forbidden since models must be under version control
 *
 *  Used in:
 *
 *    - ubm_form for store form def & js inside /public/forms
 *    - ubm_diagrams for store diagram inside /public/erdiagrams
 *    - ubs_report for store report template inside /public/reports
 *    - e.t.c.
 *
 * @singleton
 */
class MdbBlobStore extends BlobStoreCustom {
  /**
   * @inheritDoc
   * @param {BlobStoreRequest} request Request params
   * @param {UBEntityAttribute} attribute
   * @param {ArrayBuffer} content
   * @returns {BlobStoreItem}
   */
  saveContentToTempStore (request, attribute, content) {
    let fn = this.getTempFileName(request)
    console.debug('temp file is written to', fn)
    fs.writeFileSync(fn, content)
    // TODO md5val = CryptoJS.MD5(content)
    return {
      store: attribute.storeName,
      fName: request.fileName,
      origName: request.fileName,
      ct: '', // TODO
      size: content.byteLength,
      md5: '',
      isDirty: true
    }
  }
  /**
   * Retrieve BLOB content from blob store.
   * @abstract
   * @param {BlobStoreRequest} request
   * @param {BlobStoreItem} blobInfo JSON retrieved from a DB.
   * @param {Object} [options]
   * @param {String|Null} [options.encoding] Default to 'bin'. Possible values: 'bin'|'ascii'|'utf-8'
   *   If `undefined` UB will send query to entity anf get it from DB.
   *   At last one parameter {store: storeName} should be defined to prevent loading actual JSON from DB
   * @returns {String|ArrayBuffer}
   */
  getContent (request, blobInfo, options) {
    let filePath = request.isDirty ? this.getTempFileName(request) : this.getPermanentFileName(blobInfo)
    return fs.readFileSync(filePath, options)
  }
  /**
   * Fill HTTP response for getDocument request
   * @param {BlobStoreRequest} requestParams
   * @param {BlobStoreItem} blobItem
   * @param {THTTPRequest} req
   * @param {THTTPResponse} resp
   * @return {Boolean}
   */
  fillResponse (requestParams, blobItem, req, resp) {
    let filePath = requestParams.isDirty ? this.getTempFileName(requestParams) : this.getPermanentFileName(blobItem)
    if (filePath) {
      resp.statusCode = 200
      if (this.PROXY_SEND_FILE_HEADER) {
        let storeRelPath = path.relative(process.configPath, filePath)
        let head = `${this.PROXY_SEND_FILE_HEADER}: /${this.PROXY_SEND_FILE_LOCATION_ROOT}/app/${storeRelPath}`
        console.debug(`<- `, head)
        head += `\r\nContent-Type: ${blobItem.ct}`
        resp.writeHead(head)
        resp.writeEnd('')
      } else {
        resp.writeHead(`Content-Type: !STATICFILE\r\nContent-Type: ${blobItem.ct}`)
        resp.writeEnd(filePath)
      }
    } else {
      return resp.notFound('mdb store item ' + filePath)
    }
  }
  /**
   * Move content defined by `dirtyItem` from temporary to permanent store.
   * In case `oldItem` is present store implementation & parameters should be taken from oldItem.store.
   * Return a new attribute content which describe a place of BLOB in permanent store
   *
   * @param {UBEntityAttribute} attribute
   * @param {Number} ID
   * @param {BlobStoreItem} dirtyItem
   * @param {number} newRevision
   * @return {BlobStoreItem}
   */
  persist (attribute, ID, dirtyItem, newRevision) {
    let tempPath = this.getTempFileName({
      entity: attribute.entity.name,
      attribute: attribute.name,
      ID: ID
    })
    let permanentPath = this.getPermanentFileName(dirtyItem)
    fs.renameSync(tempPath, permanentPath)
    let nameWoPath = path.basename(permanentPath)
    return {
      store: attribute.storeName,
      fName: nameWoPath,
      origName: nameWoPath,
      relPath: dirtyItem.relPath,
      ct: dirtyItem.ct,
      size: dirtyItem.size,
      md5: dirtyItem.md5
    }
  }
  /**
   * @override
   */
  doDeletion (attribute, ID, blobInfo) {
    throw new Error(`${this.name} store not support deletion. Developer must delete file manually`)
  }
  /**
   * For MDB blob store relPath === '[modelCode]|folderPath'
   * @private
   * @param {BlobStoreItem} bsItem
   */
  getPermanentFileName (bsItem) {
    let pathPart = bsItem.relPath.split('|')
    if (pathPart.length !== 2) return '' // this is error
    let model = this.App.domainInfo.models[pathPart[0]]
    if (!model) throw new Error('MDB blob store - not existed model' + pathPart[0])
    let folder = path.join(model.realPublicPath, pathPart[1])
    if (!VERIFIED_PATH[folder]) {
      // verify public path exists
      if (!fs.existsSync(model.realPublicPath)) {
        fs.mkdirSync(model.realPublicPath)
      }
      if (!fs.existsSync(folder)) {
        fs.mkdirSync(folder)
      }
      VERIFIED_PATH[folder] = true
    }
    return path.join(folder, bsItem.fName)
  }
}

module.exports = MdbBlobStore