var fs = require('fs'), path = require('path'), cnst = require('constants'); var rimraf = require('rimraf'), os = require('os'), rimrafSync = rimraf.sync; /* HELPERS */ var dir = path.resolve(os.tmpdir()); var RDWR_EXCL = cnst.O_CREAT | cnst.O_TRUNC | cnst.O_RDWR | cnst.O_EXCL; var promisify = function(callback) { if (typeof callback === 'function') { return [undefined, callback]; } var promiseCallback; var promise = new Promise(function(resolve, reject) { promiseCallback = function() { var args = Array.from(arguments); var err = args.shift(); process.nextTick(function() { if (err) { reject(err); } else if (args.length === 1) { resolve(args[0]); } else { resolve(args); } }); }; }); return [promise, promiseCallback]; }; var generateName = function(rawAffixes, defaultPrefix) { var affixes = parseAffixes(rawAffixes, defaultPrefix); var now = new Date(); var name = [affixes.prefix, now.getFullYear(), now.getMonth(), now.getDate(), '-', process.pid, '-', (Math.random() * 0x100000000 + 1).toString(36), affixes.suffix].join(''); return path.join(affixes.dir || dir, name); }; var parseAffixes = function(rawAffixes, defaultPrefix) { var affixes = {prefix: null, suffix: null}; if(rawAffixes) { switch (typeof(rawAffixes)) { case 'string': affixes.prefix = rawAffixes; break; case 'object': affixes = rawAffixes; break; default: throw new Error("Unknown affix declaration: " + affixes); } } else { affixes.prefix = defaultPrefix; } return affixes; }; /* ------------------------------------------------------------------------- * Don't forget to call track() if you want file tracking and exit handlers! * ------------------------------------------------------------------------- * When any temp file or directory is created, it is added to filesToDelete * or dirsToDelete. The first time any temp file is created, a listener is * added to remove all temp files and directories at exit. */ var tracking = false; var track = function(value) { tracking = (value !== false); return module.exports; // chainable }; var exitListenerAttached = false; var filesToDelete = []; var dirsToDelete = []; function deleteFileOnExit(filePath) { if (!tracking) return false; attachExitListener(); filesToDelete.push(filePath); } function deleteDirOnExit(dirPath) { if (!tracking) return false; attachExitListener(); dirsToDelete.push(dirPath); } function attachExitListener() { if (!tracking) return false; if (!exitListenerAttached) { process.addListener('exit', cleanupSync); exitListenerAttached = true; } } function cleanupFilesSync() { if (!tracking) { return false; } var count = 0; var toDelete; while ((toDelete = filesToDelete.shift()) !== undefined) { rimrafSync(toDelete); count++; } return count; } function cleanupFiles(callback) { var p = promisify(callback); var promise = p[0]; callback = p[1]; if (!tracking) { callback(new Error("not tracking")); return promise; } var count = 0; var left = filesToDelete.length; if (!left) { callback(null, count); return promise; } var toDelete; var rimrafCallback = function(err) { if (!left) { // Prevent processing if aborted return; } if (err) { // This shouldn't happen; pass error to callback and abort // processing callback(err); left = 0; return; } else { count++; } left--; if (!left) { callback(null, count); } }; while ((toDelete = filesToDelete.shift()) !== undefined) { rimraf(toDelete, rimrafCallback); } return promise; } function cleanupDirsSync() { if (!tracking) { return false; } var count = 0; var toDelete; while ((toDelete = dirsToDelete.shift()) !== undefined) { rimrafSync(toDelete); count++; } return count; } function cleanupDirs(callback) { var p = promisify(callback); var promise = p[0]; callback = p[1]; if (!tracking) { callback(new Error("not tracking")); return promise; } var count = 0; var left = dirsToDelete.length; if (!left) { callback(null, count); return promise; } var toDelete; var rimrafCallback = function (err) { if (!left) { // Prevent processing if aborted return; } if (err) { // rimraf handles most "normal" errors; pass the error to the // callback and abort processing callback(err, count); left = 0; return; } else { count++; } left--; if (!left) { callback(null, count); } }; while ((toDelete = dirsToDelete.shift()) !== undefined) { rimraf(toDelete, rimrafCallback); } return promise; } function cleanupSync() { if (!tracking) { return false; } var fileCount = cleanupFilesSync(); var dirCount = cleanupDirsSync(); return {files: fileCount, dirs: dirCount}; } function cleanup(callback) { var p = promisify(callback); var promise = p[0]; callback = p[1]; if (!tracking) { callback(new Error("not tracking")); return promise; } cleanupFiles(function(fileErr, fileCount) { if (fileErr) { callback(fileErr, {files: fileCount}); } else { cleanupDirs(function(dirErr, dirCount) { callback(dirErr, {files: fileCount, dirs: dirCount}); }); } }); return promise; } /* DIRECTORIES */ function mkdir(affixes, callback) { var p = promisify(callback); var promise = p[0]; callback = p[1]; var dirPath = generateName(affixes, 'd-'); fs.mkdir(dirPath, parseInt('0700', 8), function(err) { if (!err) { deleteDirOnExit(dirPath); } callback(err, dirPath); }); return promise; } function mkdirSync(affixes) { var dirPath = generateName(affixes, 'd-'); fs.mkdirSync(dirPath, parseInt('0700', 8)); deleteDirOnExit(dirPath); return dirPath; } /* FILES */ function open(affixes, callback) { var p = promisify(callback); var promise = p[0]; callback = p[1]; var filePath = generateName(affixes, 'f-'); fs.open(filePath, RDWR_EXCL, parseInt('0600', 8), function(err, fd) { if (!err) { deleteFileOnExit(filePath); } callback(err, {path: filePath, fd: fd}); }); return promise; } function openSync(affixes) { var filePath = generateName(affixes, 'f-'); var fd = fs.openSync(filePath, RDWR_EXCL, parseInt('0600', 8)); deleteFileOnExit(filePath); return {path: filePath, fd: fd}; } function createWriteStream(affixes) { var filePath = generateName(affixes, 's-'); var stream = fs.createWriteStream(filePath, {flags: RDWR_EXCL, mode: parseInt('0600', 8)}); deleteFileOnExit(filePath); return stream; } /* EXPORTS */ // Settings exports.dir = dir; exports.track = track; // Functions exports.mkdir = mkdir; exports.mkdirSync = mkdirSync; exports.open = open; exports.openSync = openSync; exports.path = generateName; exports.cleanup = cleanup; exports.cleanupSync = cleanupSync; exports.createWriteStream = createWriteStream;