|
|
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 ); } }; } };
|