327 lines
7.3 KiB
JavaScript
327 lines
7.3 KiB
JavaScript
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;
|