|  | var path = require( 'path' ); | 
|  | var crypto = require( 'crypto' ); | 
|  |  | 
|  | module.exports = { | 
|  | createFromFile: function ( filePath, useChecksum ) { | 
|  | var fname = path.basename( filePath ); | 
|  | var dir = path.dirname( filePath ); | 
|  | return this.create( fname, dir, useChecksum ); | 
|  | }, | 
|  |  | 
|  | create: function ( cacheId, _path, useChecksum ) { | 
|  | var fs = require( 'fs' ); | 
|  | var flatCache = require( 'flat-cache' ); | 
|  | var cache = flatCache.load( cacheId, _path ); | 
|  | var normalizedEntries = { }; | 
|  |  | 
|  | var removeNotFoundFiles = function removeNotFoundFiles() { | 
|  | const cachedEntries = cache.keys(); | 
|  | // remove not found entries | 
|  | cachedEntries.forEach( function remover( fPath ) { | 
|  | try { | 
|  | fs.statSync( fPath ); | 
|  | } catch (err) { | 
|  | if ( err.code === 'ENOENT' ) { | 
|  | cache.removeKey( fPath ); | 
|  | } | 
|  | } | 
|  | } ); | 
|  | }; | 
|  |  | 
|  | removeNotFoundFiles(); | 
|  |  | 
|  | return { | 
|  | /** | 
|  | * the flat cache storage used to persist the metadata of the `files | 
|  | * @type {Object} | 
|  | */ | 
|  | cache: cache, | 
|  |  | 
|  | /** | 
|  | * Given a buffer, calculate md5 hash of its content. | 
|  | * @method getHash | 
|  | * @param  {Buffer} buffer   buffer to calculate hash on | 
|  | * @return {String}          content hash digest | 
|  | */ | 
|  | getHash: function ( buffer ) { | 
|  | return crypto | 
|  | .createHash( 'md5' ) | 
|  | .update( buffer ) | 
|  | .digest( 'hex' ); | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * Return whether or not a file has changed since last time reconcile was called. | 
|  | * @method hasFileChanged | 
|  | * @param  {String}  file  the filepath to check | 
|  | * @return {Boolean}       wheter or not the file has changed | 
|  | */ | 
|  | hasFileChanged: function ( file ) { | 
|  | return this.getFileDescriptor( file ).changed; | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * given an array of file paths it return and object with three arrays: | 
|  | *  - changedFiles: Files that changed since previous run | 
|  | *  - notChangedFiles: Files that haven't change | 
|  | *  - notFoundFiles: Files that were not found, probably deleted | 
|  | * | 
|  | * @param  {Array} files the files to analyze and compare to the previous seen files | 
|  | * @return {[type]}       [description] | 
|  | */ | 
|  | analyzeFiles: function ( files ) { | 
|  | var me = this; | 
|  | files = files || [ ]; | 
|  |  | 
|  | var res = { | 
|  | changedFiles: [], | 
|  | notFoundFiles: [], | 
|  | notChangedFiles: [] | 
|  | }; | 
|  |  | 
|  | me.normalizeEntries( files ).forEach( function ( entry ) { | 
|  | if ( entry.changed ) { | 
|  | res.changedFiles.push( entry.key ); | 
|  | return; | 
|  | } | 
|  | if ( entry.notFound ) { | 
|  | res.notFoundFiles.push( entry.key ); | 
|  | return; | 
|  | } | 
|  | res.notChangedFiles.push( entry.key ); | 
|  | } ); | 
|  | return res; | 
|  | }, | 
|  |  | 
|  | getFileDescriptor: function ( file ) { | 
|  | var fstat; | 
|  |  | 
|  | try { | 
|  | fstat = fs.statSync( file ); | 
|  | } catch (ex) { | 
|  | this.removeEntry( file ); | 
|  | return { key: file, notFound: true, err: ex }; | 
|  | } | 
|  |  | 
|  | if ( useChecksum ) { | 
|  | return this._getFileDescriptorUsingChecksum( file ); | 
|  | } | 
|  |  | 
|  | return this._getFileDescriptorUsingMtimeAndSize( file, fstat ); | 
|  | }, | 
|  |  | 
|  | _getFileDescriptorUsingMtimeAndSize: function ( file, fstat ) { | 
|  | var meta = cache.getKey( file ); | 
|  | var cacheExists = !!meta; | 
|  |  | 
|  | var cSize = fstat.size; | 
|  | var cTime = fstat.mtime.getTime(); | 
|  |  | 
|  | var isDifferentDate; | 
|  | var isDifferentSize; | 
|  |  | 
|  | if ( !meta ) { | 
|  | meta = { size: cSize, mtime: cTime }; | 
|  | } else { | 
|  | isDifferentDate = cTime !== meta.mtime; | 
|  | isDifferentSize = cSize !== meta.size; | 
|  | } | 
|  |  | 
|  | var nEntry = normalizedEntries[ file ] = { | 
|  | key: file, | 
|  | changed: !cacheExists || isDifferentDate || isDifferentSize, | 
|  | meta: meta | 
|  | }; | 
|  |  | 
|  | return nEntry; | 
|  | }, | 
|  |  | 
|  | _getFileDescriptorUsingChecksum: function ( file ) { | 
|  | var meta = cache.getKey( file ); | 
|  | var cacheExists = !!meta; | 
|  |  | 
|  | var contentBuffer; | 
|  | try { | 
|  | contentBuffer = fs.readFileSync( file ); | 
|  | } catch (ex) { | 
|  | contentBuffer = ''; | 
|  | } | 
|  |  | 
|  | var isDifferent = true; | 
|  | var hash = this.getHash( contentBuffer ); | 
|  |  | 
|  | if ( !meta ) { | 
|  | meta = { hash: hash }; | 
|  | } else { | 
|  | isDifferent = hash !== meta.hash; | 
|  | } | 
|  |  | 
|  | var nEntry = normalizedEntries[ file ] = { | 
|  | key: file, | 
|  | changed: !cacheExists || isDifferent, | 
|  | meta: meta | 
|  | }; | 
|  |  | 
|  | return nEntry; | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * Return the list o the files that changed compared | 
|  | * against the ones stored in the cache | 
|  | * | 
|  | * @method getUpdated | 
|  | * @param files {Array} the array of files to compare against the ones in the cache | 
|  | * @returns {Array} | 
|  | */ | 
|  | getUpdatedFiles: function ( files ) { | 
|  | var me = this; | 
|  | files = files || [ ]; | 
|  |  | 
|  | return me.normalizeEntries( files ).filter( function ( entry ) { | 
|  | return entry.changed; | 
|  | } ).map( function ( entry ) { | 
|  | return entry.key; | 
|  | } ); | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * return the list of files | 
|  | * @method normalizeEntries | 
|  | * @param files | 
|  | * @returns {*} | 
|  | */ | 
|  | normalizeEntries: function ( files ) { | 
|  | files = files || [ ]; | 
|  |  | 
|  | var me = this; | 
|  | var nEntries = files.map( function ( file ) { | 
|  | return me.getFileDescriptor( file ); | 
|  | } ); | 
|  |  | 
|  | //normalizeEntries = nEntries; | 
|  | return nEntries; | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * Remove an entry from the file-entry-cache. Useful to force the file to still be considered | 
|  | * modified the next time the process is run | 
|  | * | 
|  | * @method removeEntry | 
|  | * @param entryName | 
|  | */ | 
|  | removeEntry: function ( entryName ) { | 
|  | delete normalizedEntries[ entryName ]; | 
|  | cache.removeKey( entryName ); | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * Delete the cache file from the disk | 
|  | * @method deleteCacheFile | 
|  | */ | 
|  | deleteCacheFile: function () { | 
|  | cache.removeCacheFile(); | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * remove the cache from the file and clear the memory cache | 
|  | */ | 
|  | destroy: function () { | 
|  | normalizedEntries = { }; | 
|  | cache.destroy(); | 
|  | }, | 
|  |  | 
|  | _getMetaForFileUsingCheckSum: function ( cacheEntry ) { | 
|  | var contentBuffer = fs.readFileSync( cacheEntry.key ); | 
|  | var hash = this.getHash( contentBuffer ); | 
|  | var meta = Object.assign( cacheEntry.meta, { hash: hash } ); | 
|  | return meta; | 
|  | }, | 
|  |  | 
|  | _getMetaForFileUsingMtimeAndSize: function ( cacheEntry ) { | 
|  | var stat = fs.statSync( cacheEntry.key ); | 
|  | var meta = Object.assign( cacheEntry.meta, { | 
|  | size: stat.size, | 
|  | mtime: stat.mtime.getTime() | 
|  | } ); | 
|  | return meta; | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * Sync the files and persist them to the cache | 
|  | * @method reconcile | 
|  | */ | 
|  | reconcile: function ( noPrune ) { | 
|  | removeNotFoundFiles(); | 
|  |  | 
|  | noPrune = typeof noPrune === 'undefined' ? true : noPrune; | 
|  |  | 
|  | var entries = normalizedEntries; | 
|  | var keys = Object.keys( entries ); | 
|  |  | 
|  | if ( keys.length === 0 ) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | var me = this; | 
|  |  | 
|  | keys.forEach( function ( entryName ) { | 
|  | var cacheEntry = entries[ entryName ]; | 
|  |  | 
|  | try { | 
|  | var meta = useChecksum ? me._getMetaForFileUsingCheckSum( cacheEntry ) : me._getMetaForFileUsingMtimeAndSize( cacheEntry ); | 
|  | cache.setKey( entryName, meta ); | 
|  | } catch (err) { | 
|  | // if the file does not exists we don't save it | 
|  | // other errors are just thrown | 
|  | if ( err.code !== 'ENOENT' ) { | 
|  | throw err; | 
|  | } | 
|  | } | 
|  | } ); | 
|  |  | 
|  | cache.save( noPrune ); | 
|  | } | 
|  | }; | 
|  | } | 
|  | }; |