| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 | "use strict";const path = require('path'),			Promise = require('bluebird'),			fs = Promise.promisifyAll(require('fs-extra')),			multer  = require('multer'),			request = require('request'),			url = require('url'),			farmhash = require('farmhash'),			_ = require('lodash');var regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");const maxDownloadFileSize = 3145728; // 3 MB/** * Uploads */module.exports = {	_uploadsPath: './repo/uploads',	_uploadsThumbsPath: './data/thumbs',	/**	 * Initialize Local Data Storage model	 *	 * @param      {Object}  appconfig  The application config	 * @return     {Object}  Uploads model instance	 */	init(appconfig) {		this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');		this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');		return this;	},	/**	 * Gets the thumbnails folder path.	 *	 * @return     {String}  The thumbs path.	 */	getThumbsPath() {		return this._uploadsThumbsPath;	},	/**	 * Gets the uploads folders.	 *	 * @return     {Array<String>}  The uploads folders.	 */	getUploadsFolders() {		return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {			return (results) ? _.map(results, 'name') : [{ name: '' }];		});	},	/**	 * Creates an uploads folder.	 *	 * @param      {String}  folderName  The folder name	 * @return     {Promise}  Promise of the operation	 */	createUploadsFolder(folderName) {		let self = this;		folderName = _.kebabCase(_.trim(folderName));		if(_.isEmpty(folderName) || !regFolderName.test(folderName)) {			return Promise.resolve(self.getUploadsFolders());		}		return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {			return db.UplFolder.findOneAndUpdate({				_id: 'f:' + folderName			}, {				name: folderName			}, {				upsert: true			});		}).then(() => {			return self.getUploadsFolders();		});	},	/**	 * Check if folder is valid and exists	 *	 * @param      {String}  folderName  The folder name	 * @return     {Boolean}   True if valid	 */	validateUploadsFolder(folderName) {		return db.UplFolder.findOne({ name: folderName }).then((f) => {			return (f) ? path.resolve(this._uploadsPath, folderName) : false;		});	},	/**	 * Adds one or more uploads files.	 *	 * @param      {Array<Object>}  arrFiles  The uploads files	 * @return     {Void}  Void	 */	addUploadsFiles(arrFiles) {		if(_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {			//this._uploadsDb.Files.insert(arrFiles);		}		return;	},	/**	 * Gets the uploads files.	 *	 * @param      {String}  cat     Category type	 * @param      {String}  fld     Folder	 * @return     {Array<Object>}  The files matching the query	 */	getUploadsFiles(cat, fld) {		return db.UplFile.find({			category: cat,			folder: 'f:' + fld		}).sort('filename').exec();	},	/**	 * Deletes an uploads file.	 *	 * @param      {string}   uid     The file unique ID	 * @return     {Promise}  Promise of the operation	 */	deleteUploadsFile(uid) {		let self = this;		return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {			if(f) {				return self.deleteUploadsFileTry(f, 0);			}			return true;		});	},	deleteUploadsFileTry(f, attempt) {		let self = this;		let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './';		return Promise.join(			fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),			fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename))		).catch((err) => {			if(err.code === 'EBUSY' && attempt < 5) {				return Promise.delay(100).then(() => {					return self.deleteUploadsFileTry(f, attempt + 1);				});			} else {				winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.');				return true;			}		});	},	/**	 * Downloads a file from url.	 *	 * @param      {String}   fFolder  The folder	 * @param      {String}   fUrl     The full URL	 * @return     {Promise}  Promise of the operation	 */	downloadFromUrl(fFolder, fUrl) {		let self = this;		let fUrlObj = url.parse(fUrl);		let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'));		let destFolder = _.chain(fFolder).trim().toLower().value();		return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {						if(!destFolderPath) {				return Promise.reject(new Error('Invalid Folder'));			}			return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {								let destFilePath = path.resolve(destFolderPath, destFilename);				return new Promise((resolve, reject) => {					let rq = request({						url: fUrl,						method: 'GET',						followRedirect: true,						maxRedirects: 5,						timeout: 10000					});					let destFileStream = fs.createWriteStream(destFilePath);					let curFileSize = 0;					rq.on('data', (data) => {						curFileSize += data.length;						if(curFileSize > maxDownloadFileSize) {							rq.abort();							destFileStream.destroy();							fs.remove(destFilePath);							reject(new Error('Remote file is too large!'));						}					}).on('error', (err) => {						destFileStream.destroy();						fs.remove(destFilePath);						reject(err);					});					destFileStream.on('finish', () => {						resolve(true);					})					rq.pipe(destFileStream);				});			});		});	},	/**	 * Move/Rename a file	 *	 * @param      {String}   uid        The file ID	 * @param      {String}   fld        The destination folder	 * @param      {String}   nFilename  The new filename (optional)	 * @return     {Promise}  Promise of the operation	 */	moveUploadsFile(uid, fld, nFilename) {		let self = this;		return db.UplFolder.findById('f:' + fld).then((folder) => {			if(folder) {				return db.UplFile.findById(uid).then((originFile) => {					//-> Check if rename is valid					let nameCheck = null;					if(nFilename) {						let originFileObj = path.parse(originFile.filename);						nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name);					} else {						nameCheck = Promise.resolve(originFile.filename);					}					return nameCheck.then((destFilename) => {						let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './';						let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename);						let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename);						let preMoveOps = [];						//-> Check for invalid operations						if(sourceFilePath === destFilePath) {							return Promise.reject(new Error('Invalid Operation!'));						}						//-> Delete DB entry						preMoveOps.push(db.UplFile.findByIdAndRemove(uid));						//-> Move thumbnail ahead to avoid re-generation						if(originFile.category === 'image') {							let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename);							let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png');							let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png');							preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath));						} else {							preMoveOps.push(Promise.resolve(true));						}						//-> Proceed to move actual file						return Promise.all(preMoveOps).then(() => {							return fs.moveAsync(sourceFilePath, destFilePath, {								clobber: false							});						});					})				});			} else {				return Promise.reject(new Error('Invalid Destination Folder'));			}		});	}};
 |