| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 | 'use strict'/* global db, git, lang, upl */const path = require('path')const Promise = require('bluebird')const fs = Promise.promisifyAll(require('fs-extra'))const readChunk = require('read-chunk')const fileType = require('file-type')const mime = require('mime-types')const crypto = require('crypto')const chokidar = require('chokidar')const jimp = require('jimp')const imageSize = Promise.promisify(require('image-size'))const _ = require('lodash')/** * Uploads - Agent */module.exports = {  _uploadsPath: './repo/uploads',  _uploadsThumbsPath: './data/thumbs',  _watcher: null,  /**   * Initialize Uploads model   *   * @return     {Object}  Uploads model instance   */  init () {    let self = this    self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')    self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')    return self  },  /**   * Watch the uploads folder for changes   *   * @return     {Void}  Void   */  watch () {    let self = this    self._watcher = chokidar.watch(self._uploadsPath, {      persistent: true,      ignoreInitial: true,      cwd: self._uploadsPath,      depth: 1,      awaitWriteFinish: true    })    // -> Add new upload file    self._watcher.on('add', (p) => {      let pInfo = self.parseUploadsRelPath(p)      return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {        return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })      }).then(() => {        return git.commitUploads(lang.t('git:uploaded', { path: p }))      })    })    // -> Remove upload file    self._watcher.on('unlink', (p) => {      return git.commitUploads(lang.t('git:deleted', { path: p }))    })  },  /**   * Initial Uploads scan   *   * @return     {Promise<Void>}  Promise of the scan operation   */  initialScan () {    let self = this    return fs.readdirAsync(self._uploadsPath).then((ls) => {      // Get all folders      return Promise.map(ls, (f) => {        return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s } })      }).filter((s) => { return s.stat.isDirectory() }).then((arrDirs) => {        let folderNames = _.map(arrDirs, 'filename')        folderNames.unshift('')        // Add folders to DB        return db.UplFolder.remove({}).then(() => {          return db.UplFolder.insertMany(_.map(folderNames, (f) => {            return {              _id: 'f:' + f,              name: f            }          }))        }).then(() => {          // Travel each directory and scan files          let allFiles = []          return Promise.map(folderNames, (fldName) => {            let fldPath = path.join(self._uploadsPath, fldName)            return fs.readdirAsync(fldPath).then((fList) => {              return Promise.map(fList, (f) => {                return upl.processFile(fldName, f).then((mData) => {                  if (mData) {                    allFiles.push(mData)                  }                  return true                })              }, {concurrency: 3})            })          }, {concurrency: 1}).finally(() => {            // Add files to DB            return db.UplFile.remove({}).then(() => {              if (_.isArray(allFiles) && allFiles.length > 0) {                return db.UplFile.insertMany(allFiles)              } else {                return true              }            })          })        })      })    }).then(() => {      // Watch for new changes      return upl.watch()    })  },  /**   * Parse relative Uploads path   *   * @param      {String}  f       Relative Uploads path   * @return     {Object}  Parsed path (folder and filename)   */  parseUploadsRelPath (f) {    let fObj = path.parse(f)    return {      folder: fObj.dir,      filename: fObj.base    }  },  /**   * Get metadata from file and generate thumbnails if necessary   *   * @param      {String}  fldName  The folder name   * @param      {String}  f        The filename   * @return     {Promise<Object>}  Promise of the file metadata   */  processFile (fldName, f) {    let self = this    let fldPath = path.join(self._uploadsPath, fldName)    let fPath = path.join(fldPath, f)    let fPathObj = path.parse(fPath)    let fUid = crypto.createHash('md5').update(fldName + '/' + f).digest('hex')    return fs.statAsync(fPath).then((s) => {      if (!s.isFile()) { return false }      // Get MIME info      let mimeInfo = fileType(readChunk.sync(fPath, 0, 262))      if (_.isNil(mimeInfo)) {        mimeInfo = {          mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'        }      }      // Images      if (s.size < 3145728) { // ignore files larger than 3MB        if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/bmp'], mimeInfo.mime)) {          return self.getImageSize(fPath).then((mImgSize) => {            let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'))            let cacheThumbnailPathStr = path.format(cacheThumbnailPath)            let mData = {              _id: fUid,              category: 'image',              mime: mimeInfo.mime,              extra: mImgSize,              folder: 'f:' + fldName,              filename: f,              basename: fPathObj.name,              filesize: s.size            }            // Generate thumbnail            return fs.statAsync(cacheThumbnailPathStr).then((st) => {              return st.isFile()            }).catch((err) => { // eslint-disable-line handle-callback-err              return false            }).then((thumbExists) => {              return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {                return self.generateThumbnail(fPath, cacheThumbnailPathStr)              }).return(mData)            })          })        }      }      // Other Files      return {        _id: fUid,        category: 'binary',        mime: mimeInfo.mime,        folder: 'f:' + fldName,        filename: f,        basename: fPathObj.name,        filesize: s.size      }    })  },  /**   * Generate thumbnail of image   *   * @param      {String}           sourcePath  The source path   * @param      {String}           destPath    The destination path   * @return     {Promise<Object>}  Promise returning the resized image info   */  generateThumbnail (sourcePath, destPath) {    return jimp.read(sourcePath).then(img => {      return img              .contain(150, 150)              .write(destPath)    })  },  /**   * Gets the image dimensions.   *   * @param      {String}  sourcePath  The source path   * @return     {Object}  The image dimensions.   */  getImageSize (sourcePath) {    return imageSize(sourcePath)  }}
 |