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)
- }
- }
|