cache.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. var path = require( 'path' );
  2. var crypto = require( 'crypto' );
  3. module.exports = {
  4. createFromFile: function ( filePath, useChecksum ) {
  5. var fname = path.basename( filePath );
  6. var dir = path.dirname( filePath );
  7. return this.create( fname, dir, useChecksum );
  8. },
  9. create: function ( cacheId, _path, useChecksum ) {
  10. var fs = require( 'fs' );
  11. var flatCache = require( 'flat-cache' );
  12. var cache = flatCache.load( cacheId, _path );
  13. var normalizedEntries = { };
  14. var removeNotFoundFiles = function removeNotFoundFiles() {
  15. const cachedEntries = cache.keys();
  16. // remove not found entries
  17. cachedEntries.forEach( function remover( fPath ) {
  18. try {
  19. fs.statSync( fPath );
  20. } catch (err) {
  21. if ( err.code === 'ENOENT' ) {
  22. cache.removeKey( fPath );
  23. }
  24. }
  25. } );
  26. };
  27. removeNotFoundFiles();
  28. return {
  29. /**
  30. * the flat cache storage used to persist the metadata of the `files
  31. * @type {Object}
  32. */
  33. cache: cache,
  34. /**
  35. * Given a buffer, calculate md5 hash of its content.
  36. * @method getHash
  37. * @param {Buffer} buffer buffer to calculate hash on
  38. * @return {String} content hash digest
  39. */
  40. getHash: function ( buffer ) {
  41. return crypto
  42. .createHash( 'md5' )
  43. .update( buffer )
  44. .digest( 'hex' );
  45. },
  46. /**
  47. * Return whether or not a file has changed since last time reconcile was called.
  48. * @method hasFileChanged
  49. * @param {String} file the filepath to check
  50. * @return {Boolean} wheter or not the file has changed
  51. */
  52. hasFileChanged: function ( file ) {
  53. return this.getFileDescriptor( file ).changed;
  54. },
  55. /**
  56. * given an array of file paths it return and object with three arrays:
  57. * - changedFiles: Files that changed since previous run
  58. * - notChangedFiles: Files that haven't change
  59. * - notFoundFiles: Files that were not found, probably deleted
  60. *
  61. * @param {Array} files the files to analyze and compare to the previous seen files
  62. * @return {[type]} [description]
  63. */
  64. analyzeFiles: function ( files ) {
  65. var me = this;
  66. files = files || [ ];
  67. var res = {
  68. changedFiles: [],
  69. notFoundFiles: [],
  70. notChangedFiles: []
  71. };
  72. me.normalizeEntries( files ).forEach( function ( entry ) {
  73. if ( entry.changed ) {
  74. res.changedFiles.push( entry.key );
  75. return;
  76. }
  77. if ( entry.notFound ) {
  78. res.notFoundFiles.push( entry.key );
  79. return;
  80. }
  81. res.notChangedFiles.push( entry.key );
  82. } );
  83. return res;
  84. },
  85. getFileDescriptor: function ( file ) {
  86. var fstat;
  87. try {
  88. fstat = fs.statSync( file );
  89. } catch (ex) {
  90. this.removeEntry( file );
  91. return { key: file, notFound: true, err: ex };
  92. }
  93. if ( useChecksum ) {
  94. return this._getFileDescriptorUsingChecksum( file );
  95. }
  96. return this._getFileDescriptorUsingMtimeAndSize( file, fstat );
  97. },
  98. _getFileDescriptorUsingMtimeAndSize: function ( file, fstat ) {
  99. var meta = cache.getKey( file );
  100. var cacheExists = !!meta;
  101. var cSize = fstat.size;
  102. var cTime = fstat.mtime.getTime();
  103. var isDifferentDate;
  104. var isDifferentSize;
  105. if ( !meta ) {
  106. meta = { size: cSize, mtime: cTime };
  107. } else {
  108. isDifferentDate = cTime !== meta.mtime;
  109. isDifferentSize = cSize !== meta.size;
  110. }
  111. var nEntry = normalizedEntries[ file ] = {
  112. key: file,
  113. changed: !cacheExists || isDifferentDate || isDifferentSize,
  114. meta: meta
  115. };
  116. return nEntry;
  117. },
  118. _getFileDescriptorUsingChecksum: function ( file ) {
  119. var meta = cache.getKey( file );
  120. var cacheExists = !!meta;
  121. var contentBuffer;
  122. try {
  123. contentBuffer = fs.readFileSync( file );
  124. } catch (ex) {
  125. contentBuffer = '';
  126. }
  127. var isDifferent = true;
  128. var hash = this.getHash( contentBuffer );
  129. if ( !meta ) {
  130. meta = { hash: hash };
  131. } else {
  132. isDifferent = hash !== meta.hash;
  133. }
  134. var nEntry = normalizedEntries[ file ] = {
  135. key: file,
  136. changed: !cacheExists || isDifferent,
  137. meta: meta
  138. };
  139. return nEntry;
  140. },
  141. /**
  142. * Return the list o the files that changed compared
  143. * against the ones stored in the cache
  144. *
  145. * @method getUpdated
  146. * @param files {Array} the array of files to compare against the ones in the cache
  147. * @returns {Array}
  148. */
  149. getUpdatedFiles: function ( files ) {
  150. var me = this;
  151. files = files || [ ];
  152. return me.normalizeEntries( files ).filter( function ( entry ) {
  153. return entry.changed;
  154. } ).map( function ( entry ) {
  155. return entry.key;
  156. } );
  157. },
  158. /**
  159. * return the list of files
  160. * @method normalizeEntries
  161. * @param files
  162. * @returns {*}
  163. */
  164. normalizeEntries: function ( files ) {
  165. files = files || [ ];
  166. var me = this;
  167. var nEntries = files.map( function ( file ) {
  168. return me.getFileDescriptor( file );
  169. } );
  170. //normalizeEntries = nEntries;
  171. return nEntries;
  172. },
  173. /**
  174. * Remove an entry from the file-entry-cache. Useful to force the file to still be considered
  175. * modified the next time the process is run
  176. *
  177. * @method removeEntry
  178. * @param entryName
  179. */
  180. removeEntry: function ( entryName ) {
  181. delete normalizedEntries[ entryName ];
  182. cache.removeKey( entryName );
  183. },
  184. /**
  185. * Delete the cache file from the disk
  186. * @method deleteCacheFile
  187. */
  188. deleteCacheFile: function () {
  189. cache.removeCacheFile();
  190. },
  191. /**
  192. * remove the cache from the file and clear the memory cache
  193. */
  194. destroy: function () {
  195. normalizedEntries = { };
  196. cache.destroy();
  197. },
  198. _getMetaForFileUsingCheckSum: function ( cacheEntry ) {
  199. var contentBuffer = fs.readFileSync( cacheEntry.key );
  200. var hash = this.getHash( contentBuffer );
  201. var meta = Object.assign( cacheEntry.meta, { hash: hash } );
  202. return meta;
  203. },
  204. _getMetaForFileUsingMtimeAndSize: function ( cacheEntry ) {
  205. var stat = fs.statSync( cacheEntry.key );
  206. var meta = Object.assign( cacheEntry.meta, {
  207. size: stat.size,
  208. mtime: stat.mtime.getTime()
  209. } );
  210. return meta;
  211. },
  212. /**
  213. * Sync the files and persist them to the cache
  214. * @method reconcile
  215. */
  216. reconcile: function ( noPrune ) {
  217. removeNotFoundFiles();
  218. noPrune = typeof noPrune === 'undefined' ? true : noPrune;
  219. var entries = normalizedEntries;
  220. var keys = Object.keys( entries );
  221. if ( keys.length === 0 ) {
  222. return;
  223. }
  224. var me = this;
  225. keys.forEach( function ( entryName ) {
  226. var cacheEntry = entries[ entryName ];
  227. try {
  228. var meta = useChecksum ? me._getMetaForFileUsingCheckSum( cacheEntry ) : me._getMetaForFileUsingMtimeAndSize( cacheEntry );
  229. cache.setKey( entryName, meta );
  230. } catch (err) {
  231. // if the file does not exists we don't save it
  232. // other errors are just thrown
  233. if ( err.code !== 'ENOENT' ) {
  234. throw err;
  235. }
  236. }
  237. } );
  238. cache.save( noPrune );
  239. }
  240. };
  241. }
  242. };