let fs = require('fs'); let path = require('path'); let cnst = require('constants'); let os = require('os'); let rimraf = require('rimraf'); let mkdirp = require('mkdirp'); let osTmpdir = require('os').tmpdir(); const rimrafSync = rimraf.sync; //== helpers // let dir = path.resolve(os.tmpdir()); let RDWR_EXCL = cnst.O_CREAT | cnst.O_TRUNC | cnst.O_RDWR | cnst.O_EXCL; let 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', function() { try { cleanupSync(); } catch(err) { console.warn("Fail to clean temporary files on exit : ", err); throw err; } }); exitListenerAttached = true; } } function cleanupFilesSync() { if (!tracking) { return false; } var count = 0; var toDelete; while ((toDelete = filesToDelete.shift()) !== undefined) { rimrafSync(toDelete, { maxBusyTries: 6 }); 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, { maxBusyTries: 6 }, rimrafCallback); } return promise; } function cleanupDirsSync() { if (!tracking) { return false; } var count = 0; var toDelete; while ((toDelete = dirsToDelete.shift()) !== undefined) { rimrafSync(toDelete, { maxBusyTries: 6 }); 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, { maxBusyTries: 6 }, 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 // const mkdir = (affixes, callback) => { const p = promisify(callback); const promise = p[0]; callback = p[1]; let dirPath = generateName(affixes, 'd-'); mkdirp(dirPath, 0o700, (err) => { if (!err) { deleteDirOnExit(dirPath); } callback(err, dirPath); }); return promise; } const mkdirSync = (affixes) => { let dirPath = generateName(affixes, 'd-'); mkdirp.sync(dirPath, 0o700); deleteDirOnExit(dirPath); return dirPath; } //== files // const open = (affixes, callback) => { const p = promisify(callback); const promise = p[0]; callback = p[1]; const path = generateName(affixes, 'f-'); fs.open(path, RDWR_EXCL, 0o600, (err, fd) => { if (!err) { deleteFileOnExit(path); } callback(err, { path, fd }); }); return promise; } const openSync = (affixes) => { const path = generateName(affixes, 'f-'); let fd = fs.openSync(path, RDWR_EXCL, 0o600); deleteFileOnExit(path); return { path, fd }; } const createWriteStream = (affixes) => { const path = generateName(affixes, 's-'); let stream = fs.createWriteStream(path, { flags: RDWR_EXCL, mode: 0o600 }); deleteFileOnExit(path); return stream; } //== 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;