diff options
Diffstat (limited to 'node_modules/write-file-atomic')
-rw-r--r-- | node_modules/write-file-atomic/README.md | 2 | ||||
-rw-r--r-- | node_modules/write-file-atomic/index.js | 177 | ||||
-rw-r--r-- | node_modules/write-file-atomic/package.json | 4 |
3 files changed, 125 insertions, 58 deletions
diff --git a/node_modules/write-file-atomic/README.md b/node_modules/write-file-atomic/README.md index 63f00420c..af385f3b7 100644 --- a/node_modules/write-file-atomic/README.md +++ b/node_modules/write-file-atomic/README.md @@ -15,6 +15,7 @@ atomic and allows you set ownership (uid/gid of the file). * encoding **String** | **Null** default = 'utf8' * fsync **Boolean** default = true * mode **Number** default = 438 (aka 0666 in Octal) + * Promise **Object** default = native Promise object callback **Function** Atomically and asynchronously writes data to a file, replacing the file if it already @@ -25,6 +26,7 @@ If writeFile completes successfully then, if passed the **chown** option it will the ownership of the file. Finally it renames the file back to the filename you specified. If it encounters errors at any of these steps it will attempt to unlink the temporary file and then pass the error back to the caller. +If multiple writes are concurrently issued to the same file, the write operations are put into a queue and serialized in the order they were called, using Promises. Native promises are used by default, but you can inject your own promise-like object with the **Promise** option. Writes to different files are still executed in parallel. If provided, the **chown** option requires both **uid** and **gid** properties or else you'll get an error. diff --git a/node_modules/write-file-atomic/index.js b/node_modules/write-file-atomic/index.js index c677ee962..3b5607d15 100644 --- a/node_modules/write-file-atomic/index.js +++ b/node_modules/write-file-atomic/index.js @@ -2,10 +2,13 @@ module.exports = writeFile module.exports.sync = writeFileSync module.exports._getTmpname = getTmpname // for testing +module.exports._cleanupOnExit = cleanupOnExit var fs = require('graceful-fs') -var chain = require('slide').chain var MurmurHash3 = require('imurmurhash') +var onExit = require('signal-exit') +var path = require('path') +var activeFiles = {} var invocations = 0 function getTmpname (filename) { @@ -16,75 +19,134 @@ function getTmpname (filename) { .result() } +function cleanupOnExit (tmpfile) { + return function () { + try { + fs.unlinkSync(typeof tmpfile === 'function' ? tmpfile() : tmpfile) + } catch (_) {} + } +} + function writeFile (filename, data, options, callback) { if (options instanceof Function) { callback = options options = null } if (!options) options = {} - fs.realpath(filename, function (_, realname) { - _writeFile(realname || filename, data, options, callback) - }) -} -function _writeFile (filename, data, options, callback) { - var tmpfile = getTmpname(filename) - if (options.mode && options.chown) { - return thenWriteFile() - } else { - // Either mode or chown is not explicitly set - // Default behavior is to copy it from original file - return fs.stat(filename, function (err, stats) { - if (err || !stats) return thenWriteFile() + var Promise = options.Promise || global.Promise + var truename + var fd + var tmpfile + var removeOnExit = cleanupOnExit(() => tmpfile) + var absoluteName = path.resolve(filename) - options = Object.assign({}, options) - if (!options.mode) { - options.mode = stats.mode - } - if (!options.chown && process.getuid) { - options.chown = { uid: stats.uid, gid: stats.gid } - } - return thenWriteFile() - }) - } + new Promise(function serializeSameFile (resolve) { + // make a queue if it doesn't already exist + if (!activeFiles[absoluteName]) activeFiles[absoluteName] = [] - function thenWriteFile () { - chain([ - [writeFileAsync, tmpfile, data, options.mode, options.encoding || 'utf8'], - options.chown && [fs, fs.chown, tmpfile, options.chown.uid, options.chown.gid], - options.mode && [fs, fs.chmod, tmpfile, options.mode], - [fs, fs.rename, tmpfile, filename] - ], function (err) { - err ? fs.unlink(tmpfile, function () { callback(err) }) - : callback() + activeFiles[absoluteName].push(resolve) // add this job to the queue + if (activeFiles[absoluteName].length === 1) resolve() // kick off the first one + }).then(function getRealPath () { + return new Promise(function (resolve) { + fs.realpath(filename, function (_, realname) { + truename = realname || filename + tmpfile = getTmpname(truename) + resolve() + }) }) - } + }).then(function stat () { + return new Promise(function stat (resolve) { + if (options.mode && options.chown) resolve() + else { + // Either mode or chown is not explicitly set + // Default behavior is to copy it from original file + fs.stat(truename, function (err, stats) { + if (err || !stats) resolve() + else { + options = Object.assign({}, options) - // doing this instead of `fs.writeFile` in order to get the ability to - // call `fsync`. - function writeFileAsync (file, data, mode, encoding, cb) { - fs.open(file, 'w', options.mode, function (err, fd) { - if (err) return cb(err) + if (!options.mode) { + options.mode = stats.mode + } + if (!options.chown && process.getuid) { + options.chown = { uid: stats.uid, gid: stats.gid } + } + resolve() + } + }) + } + }) + }).then(function thenWriteFile () { + return new Promise(function (resolve, reject) { + fs.open(tmpfile, 'w', options.mode, function (err, _fd) { + fd = _fd + if (err) reject(err) + else resolve() + }) + }) + }).then(function write () { + return new Promise(function (resolve, reject) { if (Buffer.isBuffer(data)) { - return fs.write(fd, data, 0, data.length, 0, syncAndClose) + fs.write(fd, data, 0, data.length, 0, function (err) { + if (err) reject(err) + else resolve() + }) } else if (data != null) { - return fs.write(fd, String(data), 0, String(encoding), syncAndClose) - } else { - return syncAndClose() - } - function syncAndClose (err) { - if (err) return cb(err) - if (options.fsync !== false) { - fs.fsync(fd, function (err) { - if (err) return cb(err) - fs.close(fd, cb) - }) - } else { - fs.close(fd, cb) - } - } + fs.write(fd, String(data), 0, String(options.encoding || 'utf8'), function (err) { + if (err) reject(err) + else resolve() + }) + } else resolve() }) - } + }).then(function syncAndClose () { + if (options.fsync !== false) { + return new Promise(function (resolve, reject) { + fs.fsync(fd, function (err) { + if (err) reject(err) + else fs.close(fd, resolve) + }) + }) + } + }).then(function chown () { + if (options.chown) { + return new Promise(function (resolve, reject) { + fs.chown(tmpfile, options.chown.uid, options.chown.gid, function (err) { + if (err) reject(err) + else resolve() + }) + }) + } + }).then(function chmod () { + if (options.mode) { + return new Promise(function (resolve, reject) { + fs.chmod(tmpfile, options.mode, function (err) { + if (err) reject(err) + else resolve() + }) + }) + } + }).then(function rename () { + return new Promise(function (resolve, reject) { + fs.rename(tmpfile, truename, function (err) { + if (err) reject(err) + else resolve() + }) + }) + }).then(function success () { + removeOnExit() + callback() + }).catch(function fail (err) { + removeOnExit() + fs.unlink(tmpfile, function () { + callback(err) + }) + }).then(function checkQueue () { + activeFiles[absoluteName].shift() // remove the element added by serializeSameFile + if (activeFiles[absoluteName].length > 0) { + activeFiles[absoluteName][0]() // start next job if one is pending + } else delete activeFiles[absoluteName] + }) } function writeFileSync (filename, data, options) { @@ -114,6 +176,7 @@ function writeFileSync (filename, data, options) { } } + var removeOnExit = onExit(cleanupOnExit(tmpfile)) var fd = fs.openSync(tmpfile, 'w', options.mode) if (Buffer.isBuffer(data)) { fs.writeSync(fd, data, 0, data.length, 0) @@ -127,7 +190,9 @@ function writeFileSync (filename, data, options) { if (options.chown) fs.chownSync(tmpfile, options.chown.uid, options.chown.gid) if (options.mode) fs.chmodSync(tmpfile, options.mode) fs.renameSync(tmpfile, filename) + removeOnExit() } catch (err) { + removeOnExit() try { fs.unlinkSync(tmpfile) } catch (e) {} throw err } diff --git a/node_modules/write-file-atomic/package.json b/node_modules/write-file-atomic/package.json index b43cbe09f..ee494fcd1 100644 --- a/node_modules/write-file-atomic/package.json +++ b/node_modules/write-file-atomic/package.json @@ -1,6 +1,6 @@ { "name": "write-file-atomic", - "version": "2.1.0", + "version": "2.3.0", "description": "Write files in an atomic fashion w/configurable ownership", "main": "index.js", "scripts": { @@ -23,7 +23,7 @@ "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "slide": "^1.1.5" + "signal-exit": "^3.0.2" }, "devDependencies": { "mkdirp": "^0.5.1", |