--- /dev/null
+var fs = require('fs'),
+ path = require('path'),
+ cnst = require('constants');
+
+var rimraf = require('rimraf'),
+ osTmpdir = require('os-tmpdir'),
+ rimrafSync = rimraf.sync;
+
+/* HELPERS */
+
+var RDWR_EXCL = cnst.O_CREAT | cnst.O_TRUNC | cnst.O_RDWR | cnst.O_EXCL;
+
+var generateName = function(rawAffixes, defaultPrefix) {
+ var affixes = parseAffixes(rawAffixes, defaultPrefix);
+ var now = new Date();
+ var name = [affixes.prefix,
+ now.getYear(), now.getMonth(), now.getDate(),
+ '-',
+ process.pid,
+ '-',
+ (Math.random() * 0x100000000 + 1).toString(36),
+ affixes.suffix].join('');
+ return path.join(affixes.dir || exports.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) {
+ if (!tracking) {
+ if (callback) {
+ callback(new Error("not tracking"));
+ }
+ return;
+ }
+ var count = 0;
+ var left = filesToDelete.length;
+ if (!left) {
+ if (callback) {
+ callback(null, count);
+ }
+ return;
+ }
+ 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
+ if (callback) {
+ callback(err);
+ }
+ left = 0;
+ return;
+ } else {
+ count++;
+ }
+ left--;
+ if (!left && callback) {
+ callback(null, count);
+ }
+ };
+ while ((toDelete = filesToDelete.shift()) !== undefined) {
+ rimraf(toDelete, rimrafCallback);
+ }
+}
+
+function cleanupDirsSync() {
+ if (!tracking) {
+ return false;
+ }
+ var count = 0;
+ var toDelete;
+ while ((toDelete = dirsToDelete.shift()) !== undefined) {
+ rimrafSync(toDelete);
+ count++;
+ }
+ return count;
+}
+
+function cleanupDirs(callback) {
+ if (!tracking) {
+ if (callback) {
+ callback(new Error("not tracking"));
+ }
+ return;
+ }
+ var count = 0;
+ var left = dirsToDelete.length;
+ if (!left) {
+ if (callback) {
+ callback(null, count);
+ }
+ return;
+ }
+ 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
+ if (callback) {
+ callback(err, count);
+ }
+ left = 0;
+ return;
+ } else {
+ count;
+ }
+ left--;
+ if (!left && callback) {
+ callback(null, count);
+ }
+ };
+ while ((toDelete = dirsToDelete.shift()) !== undefined) {
+ rimraf(toDelete, rimrafCallback);
+ }
+}
+
+function cleanupSync() {
+ if (!tracking) {
+ return false;
+ }
+ var fileCount = cleanupFilesSync();
+ var dirCount = cleanupDirsSync();
+ return {files: fileCount, dirs: dirCount};
+}
+
+function cleanup(callback) {
+ if (!tracking) {
+ if (callback) {
+ callback(new Error("not tracking"));
+ }
+ return;
+ }
+ cleanupFiles(function(fileErr, fileCount) {
+ if (fileErr) {
+ if (callback) {
+ callback(fileErr, {files: fileCount})
+ }
+ } else {
+ cleanupDirs(function(dirErr, dirCount) {
+ if (callback) {
+ callback(dirErr, {files: fileCount, dirs: dirCount});
+ }
+ });
+ }
+ });
+}
+
+/* DIRECTORIES */
+
+function mkdir(affixes, callback) {
+ var dirPath = generateName(affixes, 'd-');
+ fs.mkdir(dirPath, 0700, function(err) {
+ if (!err) {
+ deleteDirOnExit(dirPath);
+ }
+ if (callback) {
+ callback(err, dirPath);
+ }
+ });
+}
+
+function mkdirSync(affixes) {
+ var dirPath = generateName(affixes, 'd-');
+ fs.mkdirSync(dirPath, 0700);
+ deleteDirOnExit(dirPath);
+ return dirPath;
+}
+
+/* FILES */
+
+function open(affixes, callback) {
+ var filePath = generateName(affixes, 'f-');
+ fs.open(filePath, RDWR_EXCL, 0600, function(err, fd) {
+ if (!err) {
+ deleteFileOnExit(filePath);
+ }
+ if (callback) {
+ callback(err, {path: filePath, fd: fd});
+ }
+ });
+}
+
+function openSync(affixes) {
+ var filePath = generateName(affixes, 'f-');
+ var fd = fs.openSync(filePath, RDWR_EXCL, 0600);
+ deleteFileOnExit(filePath);
+ return {path: filePath, fd: fd};
+}
+
+function createWriteStream(affixes) {
+ var filePath = generateName(affixes, 's-');
+ var stream = fs.createWriteStream(filePath, {flags: RDWR_EXCL, mode: 0600});
+ deleteFileOnExit(filePath);
+ return stream;
+}
+
+/* EXPORTS */
+// Settings
+exports.dir = path.resolve(osTmpdir());
+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;