uploads-agent.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. "use strict";
  2. var path = require('path'),
  3. Promise = require('bluebird'),
  4. fs = Promise.promisifyAll(require('fs-extra')),
  5. readChunk = require('read-chunk'),
  6. fileType = require('file-type'),
  7. farmhash = require('farmhash'),
  8. moment = require('moment'),
  9. chokidar = require('chokidar'),
  10. _ = require('lodash');
  11. /**
  12. * Uploads
  13. *
  14. * @param {Object} appconfig The application configuration
  15. */
  16. module.exports = {
  17. _uploadsPath: './repo/uploads',
  18. _uploadsThumbsPath: './data/thumbs',
  19. _watcher: null,
  20. /**
  21. * Initialize Uploads model
  22. *
  23. * @param {Object} appconfig The application config
  24. * @return {Object} Uploads model instance
  25. */
  26. init(appconfig) {
  27. let self = this;
  28. self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
  29. self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
  30. return self;
  31. },
  32. watch() {
  33. let self = this;
  34. self._watcher = chokidar.watch(self._uploadsPath, {
  35. persistent: true,
  36. ignoreInitial: true,
  37. cwd: self._uploadsPath,
  38. depth: 1,
  39. awaitWriteFinish: true
  40. });
  41. //-> Add new upload file
  42. self._watcher.on('add', (p) => {
  43. let pInfo = self.parseUploadsRelPath(p);
  44. return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
  45. return db.UplFile.create(mData);
  46. }).then(() => {
  47. return git.commitUploads('Uploaded ' + p);
  48. });
  49. });
  50. //-> Remove upload file
  51. self._watcher.on('unlink', (p) => {
  52. let pInfo = self.parseUploadsRelPath(p);
  53. return db.UplFile.findOneAndRemove({
  54. folder: 'f:' + pInfo.folder,
  55. filename: pInfo.filename
  56. }).then(() => {
  57. return git.commitUploads('Deleted ' + p);
  58. });
  59. });
  60. },
  61. /**
  62. * Initial Uploads scan
  63. *
  64. * @return {Promise<Void>} Promise of the scan operation
  65. */
  66. initialScan() {
  67. let self = this;
  68. return fs.readdirAsync(self._uploadsPath).then((ls) => {
  69. // Get all folders
  70. return Promise.map(ls, (f) => {
  71. return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s }; });
  72. }).filter((s) => { return s.stat.isDirectory(); }).then((arrDirs) => {
  73. let folderNames = _.map(arrDirs, 'filename');
  74. folderNames.unshift('');
  75. // Add folders to DB
  76. return db.UplFolder.remove({}).then(() => {
  77. return db.UplFolder.insertMany(_.map(folderNames, (f) => {
  78. return { name: f };
  79. }));
  80. }).then(() => {
  81. // Travel each directory and scan files
  82. let allFiles = [];
  83. return Promise.map(folderNames, (fldName) => {
  84. let fldPath = path.join(self._uploadsPath, fldName);
  85. return fs.readdirAsync(fldPath).then((fList) => {
  86. return Promise.map(fList, (f) => {
  87. return upl.processFile(fldName, f).then((mData) => {
  88. if(mData) {
  89. allFiles.push(mData);
  90. }
  91. return true;
  92. });
  93. }, {concurrency: 3});
  94. });
  95. }, {concurrency: 1}).finally(() => {
  96. // Add files to DB
  97. return db.UplFile.remove({}).then(() => {
  98. if(_.isArray(allFiles) && allFiles.length > 0) {
  99. return db.UplFile.insertMany(allFiles);
  100. } else {
  101. return true;
  102. }
  103. });
  104. });
  105. });
  106. });
  107. }).then(() => {
  108. // Watch for new changes
  109. return upl.watch();
  110. })
  111. },
  112. /**
  113. * Parse relative Uploads path
  114. *
  115. * @param {String} f Relative Uploads path
  116. * @return {Object} Parsed path (folder and filename)
  117. */
  118. parseUploadsRelPath(f) {
  119. let fObj = path.parse(f);
  120. return {
  121. folder: fObj.dir,
  122. filename: fObj.base
  123. };
  124. },
  125. processFile(fldName, f) {
  126. let self = this;
  127. let fldPath = path.join(self._uploadsPath, fldName);
  128. let fPath = path.join(fldPath, f);
  129. let fPathObj = path.parse(fPath);
  130. let fUid = farmhash.fingerprint32(fldName + '/' + f);
  131. return fs.statAsync(fPath).then((s) => {
  132. if(!s.isFile()) { return false; }
  133. // Get MIME info
  134. let mimeInfo = fileType(readChunk.sync(fPath, 0, 262));
  135. // Images
  136. if(s.size < 3145728) { // ignore files larger than 3MB
  137. if(_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
  138. return self.getImageMetadata(fPath).then((mImgData) => {
  139. let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'));
  140. let cacheThumbnailPathStr = path.format(cacheThumbnailPath);
  141. let mData = {
  142. _id: fUid,
  143. category: 'image',
  144. mime: mimeInfo.mime,
  145. extra: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']),
  146. folder: 'f:' + fldName,
  147. filename: f,
  148. basename: fPathObj.name,
  149. filesize: s.size
  150. }
  151. // Generate thumbnail
  152. return fs.statAsync(cacheThumbnailPathStr).then((st) => {
  153. return st.isFile();
  154. }).catch((err) => {
  155. return false;
  156. }).then((thumbExists) => {
  157. return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
  158. return self.generateThumbnail(fPath, cacheThumbnailPathStr);
  159. }).return(mData);
  160. });
  161. })
  162. }
  163. }
  164. // Other Files
  165. return {
  166. _id: fUid,
  167. category: 'binary',
  168. mime: mimeInfo.mime,
  169. folder: fldName,
  170. filename: f,
  171. basename: fPathObj.name,
  172. filesize: s.size
  173. };
  174. });
  175. },
  176. /**
  177. * Generate thumbnail of image
  178. *
  179. * @param {String} sourcePath The source path
  180. * @return {Promise<Object>} Promise returning the resized image info
  181. */
  182. generateThumbnail(sourcePath, destPath) {
  183. let sharp = require('sharp');
  184. return sharp(sourcePath)
  185. .withoutEnlargement()
  186. .resize(150,150)
  187. .background('white')
  188. .embed()
  189. .flatten()
  190. .toFormat('png')
  191. .toFile(destPath);
  192. },
  193. /**
  194. * Gets the image metadata.
  195. *
  196. * @param {String} sourcePath The source path
  197. * @return {Object} The image metadata.
  198. */
  199. getImageMetadata(sourcePath) {
  200. let sharp = require('sharp');
  201. return sharp(sourcePath).metadata();
  202. }
  203. };