4 * Copyright (c) 2011-2015 KARASZI Istvan <github@spam.raszi.hu>
10 * Module dependencies.
14 path = require('path'),
16 crypto = require('crypto'),
17 exists = fs.exists || path.exists,
18 existsSync = fs.existsSync || path.existsSync,
19 tmpDir = require('os-tmpdir'),
20 _c = require('constants');
24 * The working inner variables.
27 // store the actual TMP directory
30 // the random characters to choose from
31 RANDOM_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
33 TEMPLATE_PATTERN = /XXXXXX/,
37 CREATE_FLAGS = _c.O_CREAT | _c.O_EXCL | _c.O_RDWR,
39 DIR_MODE = 448 /* 0700 */,
40 FILE_MODE = 384 /* 0600 */,
42 // this will hold the objects need to be removed on exit
45 _gracefulCleanup = false,
46 _uncaughtException = false;
49 * Random name generator based on crypto.
50 * Adapted from http://blog.tompawlak.org/how-to-generate-random-values-nodejs-javascript
52 * @param {Number} howMany
56 function _randomChars(howMany) {
61 // make sure that we do not fail because we ran out of entropy
63 rnd = crypto.randomBytes(howMany);
65 rnd = crypto.pseudoRandomBytes(howMany);
68 for (var i = 0; i < howMany; i++) {
69 value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]);
72 return value.join('');
76 * Checks whether the `obj` parameter is defined or not.
82 function _isUndefined(obj) {
83 return typeof obj === 'undefined';
87 * Parses the function arguments.
89 * This function helps to have optional arguments.
91 * @param {Object} options
92 * @param {Function} callback
95 function _parseArguments(options, callback) {
96 if (typeof options == 'function') {
99 options = callback || {};
101 } else if (typeof options == 'undefined') {
105 return [options, callback];
109 * Generates a new temporary name.
111 * @param {Object} opts
115 function _generateTmpName(opts) {
117 return path.join(opts.dir || _TMP, opts.name);
120 // mkstemps like template
122 return opts.template.replace(TEMPLATE_PATTERN, _randomChars(6));
125 // prefix and postfix
127 opts.prefix || 'tmp-',
133 return path.join(opts.dir || _TMP, name);
137 * Gets a temporary file name.
139 * @param {Object} options
140 * @param {Function} callback
143 function _getTmpName(options, callback) {
145 args = _parseArguments(options, callback),
148 tries = opts.tries || DEFAULT_TRIES;
150 if (isNaN(tries) || tries < 0)
151 return cb(new Error('Invalid tries'));
153 if (opts.template && !opts.template.match(TEMPLATE_PATTERN))
154 return cb(new Error('Invalid template provided'));
156 (function _getUniqueName() {
157 var name = _generateTmpName(opts);
159 // check whether the path exists then retry if needed
160 exists(name, function _pathExists(pathExists) {
162 if (tries-- > 0) return _getUniqueName();
164 return cb(new Error('Could not get a unique tmp filename, max tries reached ' + name));
173 * Synchronous version of _getTmpName.
175 * @param {Object} options
179 function _getTmpNameSync(options) {
181 args = _parseArguments(options),
183 tries = opts.tries || DEFAULT_TRIES;
185 if (isNaN(tries) || tries < 0)
186 throw new Error('Invalid tries');
188 if (opts.template && !opts.template.match(TEMPLATE_PATTERN))
189 throw new Error('Invalid template provided');
192 var name = _generateTmpName(opts);
193 if (!existsSync(name)) {
196 } while (tries-- > 0);
198 throw new Error('Could not get a unique tmp filename, max tries reached');
202 * Creates and opens a temporary file.
204 * @param {Object} options
205 * @param {Function} callback
208 function _createTmpFile(options, callback) {
210 args = _parseArguments(options, callback),
214 opts.postfix = (_isUndefined(opts.postfix)) ? '.tmp' : opts.postfix;
216 // gets a temporary filename
217 _getTmpName(opts, function _tmpNameCreated(err, name) {
218 if (err) return cb(err);
220 // create and open the file
221 fs.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err, fd) {
222 if (err) return cb(err);
224 cb(null, name, fd, _prepareTmpFileRemoveCallback(name, fd, opts));
230 * Synchronous version of _createTmpFile.
232 * @param {Object} options
233 * @returns {Object} object consists of name, fd and removeCallback
236 function _createTmpFileSync(options) {
238 args = _parseArguments(options),
241 opts.postfix = opts.postfix || '.tmp';
243 var name = _getTmpNameSync(opts);
244 var fd = fs.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
249 removeCallback : _prepareTmpFileRemoveCallback(name, fd, opts)
254 * Removes files and folders in a directory recursively.
256 * @param {String} root
259 function _rmdirRecursiveSync(root) {
266 files = fs.readdirSync(dir);
268 for (var i = 0, length = files.length; i < length; i++) {
270 file = path.join(dir, files[i]),
271 stat = fs.lstatSync(file); // lstat so we don't recurse into symlinked directories
273 if (stat.isDirectory()) {
287 } while (dirs.length !== 0);
291 * Creates a temporary directory.
293 * @param {Object} options
294 * @param {Function} callback
297 function _createTmpDir(options, callback) {
299 args = _parseArguments(options, callback),
303 // gets a temporary filename
304 _getTmpName(opts, function _tmpNameCreated(err, name) {
305 if (err) return cb(err);
307 // create the directory
308 fs.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err) {
309 if (err) return cb(err);
311 cb(null, name, _prepareTmpDirRemoveCallback(name, opts));
317 * Synchronous version of _createTmpDir.
319 * @param {Object} options
320 * @returns {Object} object consists of name and removeCallback
323 function _createTmpDirSync(options) {
325 args = _parseArguments(options),
328 var name = _getTmpNameSync(opts);
329 fs.mkdirSync(name, opts.mode || DIR_MODE);
333 removeCallback : _prepareTmpDirRemoveCallback(name, opts)
338 * Prepares the callback for removal of the temporary file.
340 * @param {String} name
342 * @param {Object} opts
344 * @returns {Function} the callback
346 function _prepareTmpFileRemoveCallback(name, fd, opts) {
347 var removeCallback = _prepareRemoveCallback(function _removeCallback(fdPath) {
349 fs.closeSync(fdPath[0]);
352 // under some node/windows related circumstances, a temporary file
353 // may have not be created as expected or the file was already closed
354 // by the user, in which case we will simply ignore the error
355 if (e.errno != -_c.EBADF && e.errno != -c.ENOENT) {
356 // reraise any unanticipated error
360 fs.unlinkSync(fdPath[1]);
364 _removeObjects.unshift(removeCallback);
367 return removeCallback;
371 * Prepares the callback for removal of the temporary directory.
373 * @param {String} name
374 * @param {Object} opts
375 * @returns {Function} the callback
378 function _prepareTmpDirRemoveCallback(name, opts) {
379 var removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs.rmdirSync.bind(fs);
380 var removeCallback = _prepareRemoveCallback(removeFunction, name);
383 _removeObjects.unshift(removeCallback);
386 return removeCallback;
390 * Creates a guarded function wrapping the removeFunction call.
392 * @param {Function} removeFunction
393 * @param {Object} arg
394 * @returns {Function}
397 function _prepareRemoveCallback(removeFunction, arg) {
400 return function _cleanupCallback() {
403 var index = _removeObjects.indexOf(removeFunction);
405 _removeObjects.splice(index, 1);
414 * The garbage collector.
418 function _garbageCollector() {
419 if (_uncaughtException && !_gracefulCleanup) {
423 for (var i = 0, length = _removeObjects.length; i < length; i++) {
425 _removeObjects[i].call(null);
432 function _setGracefulCleanup() {
433 _gracefulCleanup = true;
436 var version = process.versions.node.split('.').map(function (value) {
437 return parseInt(value, 10);
440 if (version[0] === 0 && (version[1] < 9 || version[1] === 9 && version[2] < 5)) {
441 process.addListener('uncaughtException', function _uncaughtExceptionThrown(err) {
442 _uncaughtException = true;
449 process.addListener('exit', function _exit(code) {
450 if (code) _uncaughtException = true;
454 // exporting all the needed methods
455 module.exports.tmpdir = _TMP;
456 module.exports.dir = _createTmpDir;
457 module.exports.dirSync = _createTmpDirSync;
458 module.exports.file = _createTmpFile;
459 module.exports.fileSync = _createTmpFileSync;
460 module.exports.tmpName = _getTmpName;
461 module.exports.tmpNameSync = _getTmpNameSync;
462 module.exports.setGracefulCleanup = _setGracefulCleanup;