indiaai-hackathon
/
datalab
/web
/node_modules
/bunyan-rotating-file-stream
/lib
/datestampedfileops.js
var fs = require('fs'); | |
var async = require('async'); | |
var path = require('path'); | |
var strftime = require('strftime'); | |
var _ = require('lodash'); | |
var _DEBUG = false; | |
function DateStampedFileOps(logpath, totalFiles, totalSize, gzip) { | |
var filenameTimestamp = new Date(); | |
var nonce = 0; | |
var parsedPath = path.parse(logpath); | |
var reopenedFilePath = null; | |
function getFilesInLogDirectory(next) { | |
fs.readdir(path.resolve(parsedPath.dir), function (err, files) { | |
if (err) throw err; | |
next(null, files); | |
}); | |
} | |
function removeN(originalName) { | |
return originalName | |
.replace('.%N', '') | |
.replace('_%N', '') | |
.replace('-%N', '') | |
.replace('%N', ''); | |
} | |
function filterJustOurLogFiles(includeZipFiles) { | |
return function (files, next) { | |
var logfiles = _.filter(files, function (file) { | |
var parsedFile = path.parse( | |
path.resolve( | |
path.join( | |
parsedPath.dir, | |
file | |
) | |
) | |
); | |
var prefixes = []; | |
var parts = parsedPath.name.split('%'); | |
prefixes.push(parts.slice(0, 1).join('')); | |
if (parts.slice(1,2).join('').slice(0,1) === 'N') { | |
// First substitution part is %N, which might not be used | |
// Need to check the prefix when %N isn't used | |
var altname = removeN(parsedPath.name); | |
parts = altname.split('%'); | |
prefixes.push(parts.slice(0, 1).join('')); | |
} | |
if (includeZipFiles && parsedFile.ext === '.gz') { | |
var splitname = parsedFile.name.split('.'); | |
parsedFile.ext = '.' + splitname.slice(-1).join(''); | |
if (parsedFile.ext === '.') { | |
parsedFile.ext === ''; | |
} | |
parsedFile.name = splitname.slice(0, -1).join('.'); | |
} | |
return (_.some(prefixes, function (prefix) { return parsedFile.name.indexOf(prefix) === 0; }) && | |
parsedFile.ext === parsedPath.ext); | |
}); | |
next(null, logfiles); | |
} | |
} | |
function statEachFile(logfiles, next) { | |
async.map(logfiles, function (logfile, next) { | |
var fullpath = path.resolve(path.join(parsedPath.dir, logfile)); | |
fs.stat(fullpath, function (err, stat) { | |
next(err, { | |
stat: stat, | |
path: fullpath | |
}); | |
}); | |
}, function (err, stats) { | |
next(err, stats); | |
}); | |
} | |
function sortFilesByModifiedTime(logstats, next) { | |
async.sortBy(logstats, function (logstat, next) { | |
next(null, -logstat.stat.mtime); | |
}, next); | |
} | |
function deleteFilesAfterCountBreach(logstats, next) { | |
var currentCount = 0; | |
var toDelete = []; | |
var toContinue = []; | |
logstats.forEach(function (logstat) { | |
currentCount += 1; | |
if (totalFiles && currentCount > totalFiles) { | |
toDelete.push(logstat); | |
} else { | |
toContinue.push(logstat); | |
} | |
}); | |
async.each(toDelete, function (logstat, next) { | |
fs.unlink(logstat.path, next); | |
}, function (err) { | |
next(err, toContinue); | |
}); | |
} | |
function deleteFilesAfterSizeBreach(logstats, next) { | |
var currentSize = 0; | |
var toDelete = []; | |
var toContinue = []; | |
logstats.forEach(function (logstat) { | |
currentSize += logstat.stat.size; | |
if (totalSize && currentSize > totalSize) { | |
toDelete.push(logstat); | |
} else { | |
toContinue.push(logstat); | |
} | |
}); | |
async.each(toDelete, function (logstat, next) { | |
fs.unlink(logstat.path, next); | |
}, function (err) { | |
next(err, toContinue); | |
}); | |
} | |
function getSortedLogFiles(matchzippedfiles) { | |
return function (next) { | |
async.waterfall([ | |
getFilesInLogDirectory, | |
filterJustOurLogFiles(matchzippedfiles), | |
statEachFile, | |
sortFilesByModifiedTime | |
], function (err, logfiles) { | |
next(err, logfiles); | |
}); | |
} | |
} | |
function deleteFiles(next) { | |
async.waterfall([ | |
getSortedLogFiles(true), | |
deleteFilesAfterCountBreach, | |
deleteFilesAfterSizeBreach | |
], function (err) { | |
next(err); | |
}); | |
}; | |
function moveIntermediateFiles(next) { | |
process.nextTick(function () { | |
next(); | |
}); | |
}; | |
function internalGetStreamFilepath(gzipped, nonce) { | |
var result; | |
if (reopenedFilePath !== null) { | |
result = path.parse(reopenedFilePath); | |
} else { | |
result = _.extend({}, parsedPath); | |
if (nonce === 0) { | |
result.name = removeN(result.name); | |
} else if (result.name.indexOf('%N') >= 0) { | |
result.name = result.name.replace('%N', String(nonce)); | |
} else { | |
result.name = result.name + '.' + String(nonce); | |
} | |
result.name = strftime(result.name, filenameTimestamp); | |
} | |
if (gzipped) { | |
result.ext += '.gz'; | |
} | |
result.base = result.name + result.ext; | |
return path.resolve(path.format(result)); | |
} | |
function getStreamFilepath(gzipped) { | |
return internalGetStreamFilepath(gzipped, nonce); | |
} | |
function findUnusedFile(nonce, next) { | |
var filepath = internalGetStreamFilepath(false, nonce); | |
var filepathgz = internalGetStreamFilepath(true, nonce); | |
async.each([ | |
filepath, | |
filepathgz | |
], function (potentialpath, pathresult) { | |
fs.stat(potentialpath, function (err, stats) { | |
if (err && err.code === 'ENOENT') { | |
// File doesn't already exist, this is good | |
pathresult(); | |
} else if (err) { | |
// Something else failed | |
pathresult(err); | |
} else { | |
// Path existed, use something else | |
pathresult('inuse'); | |
} | |
}) | |
}, function (err) { | |
if (err && err === 'inuse') { | |
findUnusedFile(nonce + 1, next); | |
} else if (err) { | |
next(err); | |
} else { | |
// Open the file exclusively to ensure we own it | |
fs.open(filepath, 'wx', function (err, fd) { | |
if (err && err.code === 'EEXIST') { | |
findUnusedFile(nonce + 1, next); | |
} else if (err) { | |
return next(err); | |
} else { | |
fs.close(fd, function () { | |
next(null, filepath, nonce); | |
}); | |
} | |
}); | |
} | |
}) | |
} | |
function createNewFile(next) { | |
reopenedFilePath = null; | |
findUnusedFile(0, function (err, filepath, foundnonce) { | |
nonce = foundnonce || 0; | |
next(err, filepath); | |
}); | |
} | |
function newStreamFilepath(triggerinfo, next) { | |
filenameTimestamp = new Date(triggerinfo.date || Date.now()); | |
var startNewFile = !triggerinfo.hasOwnProperty('startNewFile') || | |
triggerinfo.startNewFile; | |
if (startNewFile) { | |
createNewFile(next); | |
} else { | |
getSortedLogFiles(false)(function (err, logfiles) { | |
if (err) { | |
return next(err); | |
} | |
if (logfiles.length === 0) { | |
return createNewFile(next); | |
} else { | |
reopenedFilePath = logfiles[0].path; | |
next(null, reopenedFilePath); | |
} | |
}); | |
} | |
} | |
return { | |
getStreamFilepath: getStreamFilepath, | |
newStreamFilepath: newStreamFilepath, | |
deleteFiles: deleteFiles, | |
moveIntermediateFiles: moveIntermediateFiles | |
}; | |
} | |
DateStampedFileOps.isDateStamped = function (logpath) { | |
var parsed = path.parse(logpath); | |
var withoutN = parsed.name.replace('%N', ''); | |
return (withoutN !== strftime(withoutN)); | |
} | |
module.exports = DateStampedFileOps; | |