123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- "use strict";
- var path = require('path'),
- Promise = require('bluebird'),
- fs = Promise.promisifyAll(require('fs-extra')),
- readChunk = require('read-chunk'),
- fileType = require('file-type'),
- farmhash = require('farmhash'),
- moment = require('moment'),
- chokidar = require('chokidar'),
- sharp = require('sharp'),
- _ = require('lodash');
- /**
- * Uploads
- *
- * @param {Object} appconfig The application configuration
- */
- module.exports = {
- _uploadsPath: './repo/uploads',
- _uploadsThumbsPath: './data/thumbs',
- _watcher: null,
- /**
- * Initialize Uploads model
- *
- * @param {Object} appconfig The application config
- * @return {Object} Uploads model instance
- */
- init(appconfig) {
- let self = this;
- self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
- self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
- // Disable Sharp cache, as it cause file locks issues when deleting uploads.
- sharp.cache(false);
- 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('Uploaded ' + p);
- });
- });
- //-> Remove upload file
- self._watcher.on('unlink', (p) => {
- let pInfo = self.parseUploadsRelPath(p);
- return git.commitUploads('Deleted/Renamed ' + 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 = farmhash.fingerprint32(fldName + '/' + f);
- return fs.statAsync(fPath).then((s) => {
- if(!s.isFile()) { return false; }
- // Get MIME info
- let mimeInfo = fileType(readChunk.sync(fPath, 0, 262));
- // Images
- if(s.size < 3145728) { // ignore files larger than 3MB
- if(_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
- return self.getImageMetadata(fPath).then((mImgData) => {
- 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: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']),
- folder: 'f:' + fldName,
- filename: f,
- basename: fPathObj.name,
- filesize: s.size
- }
- // Generate thumbnail
- return fs.statAsync(cacheThumbnailPathStr).then((st) => {
- return st.isFile();
- }).catch((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: fldName,
- filename: f,
- basename: fPathObj.name,
- filesize: s.size
- };
- });
- },
- /**
- * Generate thumbnail of image
- *
- * @param {String} sourcePath The source path
- * @return {Promise<Object>} Promise returning the resized image info
- */
- generateThumbnail(sourcePath, destPath) {
- return sharp(sourcePath)
- .withoutEnlargement()
- .resize(150,150)
- .background('white')
- .embed()
- .flatten()
- .toFormat('png')
- .toFile(destPath);
- },
- /**
- * Gets the image metadata.
- *
- * @param {String} sourcePath The source path
- * @return {Object} The image metadata.
- */
- getImageMetadata(sourcePath) {
- return sharp(sourcePath).metadata();
- }
- };
|