You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							286 lines
						
					
					
						
							7.7 KiB
						
					
					
				
			
		
		
	
	
							286 lines
						
					
					
						
							7.7 KiB
						
					
					
				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 ); | 
						|
      } | 
						|
    }; | 
						|
  } | 
						|
};
 | 
						|
 |