|
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(); |
|
|
|
cachedEntries.forEach(function remover(fPath) { |
|
try { |
|
fs.statSync(fPath); |
|
} catch (err) { |
|
if (err.code === 'ENOENT') { |
|
cache.removeKey(fPath); |
|
} |
|
} |
|
}); |
|
}; |
|
|
|
removeNotFoundFiles(); |
|
|
|
return { |
|
|
|
|
|
|
|
|
|
cache: cache, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getHash: function (buffer) { |
|
return crypto.createHash('md5').update(buffer).digest('hex'); |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hasFileChanged: function (file) { |
|
return this.getFileDescriptor(file).changed; |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getUpdatedFiles: function (files) { |
|
var me = this; |
|
files = files || []; |
|
|
|
return me |
|
.normalizeEntries(files) |
|
.filter(function (entry) { |
|
return entry.changed; |
|
}) |
|
.map(function (entry) { |
|
return entry.key; |
|
}); |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
normalizeEntries: function (files) { |
|
files = files || []; |
|
|
|
var me = this; |
|
var nEntries = files.map(function (file) { |
|
return me.getFileDescriptor(file); |
|
}); |
|
|
|
|
|
return nEntries; |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
removeEntry: function (entryName) { |
|
delete normalizedEntries[entryName]; |
|
cache.removeKey(entryName); |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
deleteCacheFile: function () { |
|
cache.removeCacheFile(); |
|
}, |
|
|
|
|
|
|
|
|
|
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 }); |
|
delete meta.size; |
|
delete meta.mtime; |
|
return meta; |
|
}, |
|
|
|
_getMetaForFileUsingMtimeAndSize: function (cacheEntry) { |
|
var stat = fs.statSync(cacheEntry.key); |
|
var meta = Object.assign(cacheEntry.meta, { |
|
size: stat.size, |
|
mtime: stat.mtime.getTime(), |
|
}); |
|
delete meta.hash; |
|
return meta; |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
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 (err.code !== 'ENOENT') { |
|
throw err; |
|
} |
|
} |
|
}); |
|
|
|
cache.save(noPrune); |
|
}, |
|
}; |
|
}, |
|
}; |
|
|