uploads.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. ws.emit('uploadsAddFiles', {
  46. auth: WSInternalKey,
  47. content: mData
  48. });
  49. }).then(() => {
  50. return git.commitUploads('Uploaded ' + p);
  51. });
  52. });
  53. //-> Remove upload file
  54. self._watcher.on('unlink', (p) => {
  55. let pInfo = self.parseUploadsRelPath(p);
  56. return self.deleteFile(pInfo.folder, pInfo.filename).then((uID) => {
  57. ws.emit('uploadsRemoveFiles', {
  58. auth: WSInternalKey,
  59. content: uID
  60. });
  61. }).then(() => {
  62. return git.commitUploads('Deleted ' + p);
  63. });
  64. });
  65. },
  66. /**
  67. * Initial Uploads scan
  68. *
  69. * @return {Promise<Void>} Promise of the scan operation
  70. */
  71. initialScan() {
  72. let self = this;
  73. return fs.readdirAsync(self._uploadsPath).then((ls) => {
  74. // Get all folders
  75. return Promise.map(ls, (f) => {
  76. return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s }; });
  77. }).filter((s) => { return s.stat.isDirectory(); }).then((arrDirs) => {
  78. let folderNames = _.map(arrDirs, 'filename');
  79. folderNames.unshift('');
  80. // Add folders to DB
  81. return db.UplFolder.remove({}).then(() => {
  82. return db.UplFolder.insertMany(_.map(folderNames, (f) => {
  83. return { name: f };
  84. }));
  85. }).then(() => {
  86. // Travel each directory and scan files
  87. let allFiles = [];
  88. return Promise.map(folderNames, (fldName) => {
  89. let fldPath = path.join(self._uploadsPath, fldName);
  90. return fs.readdirAsync(fldPath).then((fList) => {
  91. return Promise.map(fList, (f) => {
  92. return upl.processFile(fldName, f).then((mData) => {
  93. if(mData) {
  94. allFiles.push(mData);
  95. }
  96. return true;
  97. });
  98. }, {concurrency: 3});
  99. });
  100. }, {concurrency: 1}).finally(() => {
  101. // Add files to DB
  102. return db.UplFile.remove({}).then(() => {
  103. if(_.isArray(allFiles) && allFiles.length > 0) {
  104. return db.UplFile.insertMany(allFiles);
  105. } else {
  106. return true;
  107. }
  108. });
  109. });
  110. });
  111. });
  112. }).then(() => {
  113. // Watch for new changes
  114. return upl.watch();
  115. })
  116. },
  117. /**
  118. * Parse relative Uploads path
  119. *
  120. * @param {String} f Relative Uploads path
  121. * @return {Object} Parsed path (folder and filename)
  122. */
  123. parseUploadsRelPath(f) {
  124. let fObj = path.parse(f);
  125. return {
  126. folder: fObj.dir,
  127. filename: fObj.base
  128. };
  129. },
  130. processFile(fldName, f) {
  131. let self = this;
  132. let fldPath = path.join(self._uploadsPath, fldName);
  133. let fPath = path.join(fldPath, f);
  134. let fPathObj = path.parse(fPath);
  135. let fUid = farmhash.fingerprint32(fldName + '/' + f);
  136. return fs.statAsync(fPath).then((s) => {
  137. if(!s.isFile()) { return false; }
  138. // Get MIME info
  139. let mimeInfo = fileType(readChunk.sync(fPath, 0, 262));
  140. // Images
  141. if(s.size < 3145728) { // ignore files larger than 3MB
  142. if(_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
  143. return self.getImageMetadata(fPath).then((mImgData) => {
  144. let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'));
  145. let cacheThumbnailPathStr = path.format(cacheThumbnailPath);
  146. let mData = {
  147. _id: fUid,
  148. category: 'image',
  149. mime: mimeInfo.mime,
  150. extra: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']),
  151. folder: null,
  152. filename: f,
  153. basename: fPathObj.name,
  154. filesize: s.size
  155. }
  156. // Generate thumbnail
  157. return fs.statAsync(cacheThumbnailPathStr).then((st) => {
  158. return st.isFile();
  159. }).catch((err) => {
  160. return false;
  161. }).then((thumbExists) => {
  162. return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
  163. return self.generateThumbnail(fPath, cacheThumbnailPathStr);
  164. }).return(mData);
  165. });
  166. })
  167. }
  168. }
  169. // Other Files
  170. return {
  171. _id: fUid,
  172. category: 'binary',
  173. mime: mimeInfo.mime,
  174. folder: fldName,
  175. filename: f,
  176. basename: fPathObj.name,
  177. filesize: s.size
  178. };
  179. });
  180. },
  181. /**
  182. * Generate thumbnail of image
  183. *
  184. * @param {String} sourcePath The source path
  185. * @return {Promise<Object>} Promise returning the resized image info
  186. */
  187. generateThumbnail(sourcePath, destPath) {
  188. let sharp = require('sharp');
  189. return sharp(sourcePath)
  190. .withoutEnlargement()
  191. .resize(150,150)
  192. .background('white')
  193. .embed()
  194. .flatten()
  195. .toFormat('png')
  196. .toFile(destPath);
  197. },
  198. /**
  199. * Gets the image metadata.
  200. *
  201. * @param {String} sourcePath The source path
  202. * @return {Object} The image metadata.
  203. */
  204. getImageMetadata(sourcePath) {
  205. let sharp = require('sharp');
  206. return sharp(sourcePath).metadata();
  207. }
  208. };