/*! * Tmp * * Copyright (c) 2011-2013 KARASZI Istvan * * MIT Licensed */ /** * Module dependencies. */ var fs = require('fs'), path = require('path'), os = require('os'), exists = fs.exists || path.exists, tmpDir = os.tmpDir || _getTMPDir, _c = require('constants'); /** * The working inner variables. */ var // store the actual TMP directory _TMP = tmpDir(), // the random characters to choose from randomChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz", randomCharsLength = randomChars.length, // this will hold the objects need to be removed on exit _removeObjects = [], _gracefulCleanup = false, _uncaughtException = false; /** * Gets the temp directory. * * @return {String} * @api private */ function _getTMPDir() { var tmpNames = [ 'TMPDIR', 'TMP', 'TEMP' ]; for (var i = 0, length = tmpNames.length; i < length; i++) { if (_isUndefined(process.env[tmpNames[i]])) continue; return process.env[tmpNames[i]]; } // fallback to the default return '/tmp'; } /** * Checks whether the `obj` parameter is defined or not. * * @param {Object} obj * @return {Boolean} * @api private */ function _isUndefined(obj) { return typeof obj === 'undefined'; } /** * Parses the function arguments. * * This function helps to have optional arguments. * * @param {Object} options * @param {Function} callback * @api private */ function _parseArguments(options, callback) { if (!callback || typeof callback != "function") { callback = options; options = {}; } return [ options, callback ]; } /** * Gets a temporary file name. * * @param {Object} opts * @param {Function} cb * @api private */ function _getTmpName(options, callback) { var args = _parseArguments(options, callback), opts = args[0], cb = args[1], template = opts.template, templateDefined = !_isUndefined(template), tries = opts.tries || 3; if (isNaN(tries) || tries < 0) return cb(new Error('Invalid tries')); if (templateDefined && !template.match(/XXXXXX/)) return cb(new Error('Invalid template provided')); function _getName() { // prefix and postfix if (!templateDefined) { var name = [ (_isUndefined(opts.prefix)) ? 'tmp-' : opts.prefix, process.pid, (Math.random() * 0x1000000000).toString(36), opts.postfix ].join(''); return path.join(opts.dir || _TMP, name); } // mkstemps like template var chars = []; for (var i = 0; i < 6; i++) { chars.push(randomChars.substr(Math.floor(Math.random() * randomCharsLength), 1)); } return template.replace(/XXXXXX/, chars.join('')); } (function _getUniqueName() { var name = _getName(); // check whether the path exists then retry if needed exists(name, function _pathExists(pathExists) { if (pathExists) { if (tries-- > 0) return _getUniqueName(); return cb(new Error('Could not get a unique tmp filename, max tries reached')); } cb(null, name); }); }()); } /** * Creates and opens a temporary file. * * @param {Object} options * @param {Function} callback * @api public */ function _createTmpFile(options, callback) { var args = _parseArguments(options, callback), opts = args[0], cb = args[1]; opts.postfix = (_isUndefined(opts.postfix)) ? '.tmp' : opts.postfix; // gets a temporary filename _getTmpName(opts, function _tmpNameCreated(err, name) { if (err) return cb(err); // create and open the file fs.open(name, _c.O_CREAT | _c.O_EXCL | _c.O_RDWR, opts.mode || 0600, function _fileCreated(err, fd) { if (err) return cb(err); var removeCallback = _prepareRemoveCallback(fs.unlinkSync.bind(fs), name); if (!opts.keep) { _removeObjects.unshift(removeCallback); } cb(null, name, fd, removeCallback); }); }); } /** * Removes files and folders in a directory recursively. * * @param {String} dir */ function _rmdirRecursiveSync(dir) { var files = fs.readdirSync(dir); for (var i = 0, length = files.length; i < length; i++) { var file = path.join(dir, files[i]); // lstat so we don't recurse into symlinked directories. var stat = fs.lstatSync(file); if (stat.isDirectory()) { _rmdirRecursiveSync(file); } else { fs.unlinkSync(file); } } fs.rmdirSync(dir); } /** * * @param {Function} removeFunction * @param {String} path * @returns {Function} * @private */ function _prepareRemoveCallback(removeFunction, path) { var called = false; return function() { if (called) { return; } removeFunction(path); called = true; }; } /** * Creates a temporary directory. * * @param {Object} options * @param {Function} callback * @api public */ function _createTmpDir(options, callback) { var args = _parseArguments(options, callback), opts = args[0], cb = args[1]; // gets a temporary filename _getTmpName(opts, function _tmpNameCreated(err, name) { if (err) return cb(err); // create the directory fs.mkdir(name, opts.mode || 0700, function _dirCreated(err) { if (err) return cb(err); var removeCallback = _prepareRemoveCallback( opts.unsafeCleanup ? _rmdirRecursiveSync : fs.rmdirSync.bind(fs), name ); if (!opts.keep) { _removeObjects.unshift(removeCallback); } cb(null, name, removeCallback); }); }); } /** * The garbage collector. * * @api private */ function _garbageCollector() { if (_uncaughtException && !_gracefulCleanup) { return; } for (var i = 0, length = _removeObjects.length; i < length; i++) { try { _removeObjects[i].call(null); } catch (e) { // already removed? } } } function _setGracefulCleanup() { _gracefulCleanup = true; } var version = process.versions.node.split('.').map(function (value) { return parseInt(value, 10); }); if (version[0] === 0 && (version[1] < 9 || version[1] === 9 && version[2] < 5)) { process.addListener('uncaughtException', function _uncaughtExceptionThrown( err ) { _uncaughtException = true; _garbageCollector(); throw err; }); } process.addListener('exit', function _exit(code) { if (code) _uncaughtException = true; _garbageCollector(); }); // exporting all the needed methods module.exports.tmpdir = _TMP; module.exports.dir = _createTmpDir; module.exports.file = _createTmpFile; module.exports.tmpName = _getTmpName; module.exports.setGracefulCleanup = _setGracefulCleanup;