use random tmpnames

This commit is contained in:
AJ ONeal 2015-12-14 22:07:54 -08:00
parent 5db9d90c90
commit b74d5f2644
3 changed files with 53 additions and 24 deletions

View File

@ -3,6 +3,9 @@ safe-replace
A micro-module for safely replacing a file.
This is intended to be generally safe even when a function that writes a file
is accidentally called twice (as may happen with node cluster).
Commandline Reference
---------------------
@ -10,7 +13,7 @@ If I want to safely replace a file with a new version, I would do so like this:
```bash
# create the new version
touch keep.txt.new
touch keep.txt.RANDOM.tmp
# remove the previous backup
rm -f keep.txt.bak
@ -19,7 +22,7 @@ rm -f keep.txt.bak
mv keep.txt keep.txt.bak
# move the new version to the current
mv keep.txt.new keep.txt
mv keep.txt.RANDOM.tmp keep.txt
```
If `keep.txt` became corrupt and I wanted to use the backup,
@ -48,8 +51,8 @@ safeReplace.writeFile('keep.txt', data, 'ascii').then(function () {
});
});
// let's say I wrote keep.txt.new with my own mechanism
safeReplace.commit('keep.txt').then(function () {
// let's say I wrote keep.txt.x7t7sq926.tmp with my own mechanism
safeReplace.commit('keep.txt.x7t7sq926.tmp', 'keep.txt').then(function () {
fs.readdir('.', function (nodes) {
console.log('file system nodes', nodes);
// keep.txt
@ -65,4 +68,14 @@ safeReplace.revert('keep.txt').then(function () {
// keep.txt.bak
});
});
// let's say I want to write a tmp file and not commit it... weird
safeReplace.stage('keep.txt', data, 'ascii').then(function (tmpname) {
fs.readdir('.', function (nodes) {
console.log('file system nodes', nodes);
// keep.txt
// keep.txt.bak
// keep.txt.ac71teh8mja.tmp
});
});
```

View File

@ -2,54 +2,65 @@
var PromiseA = require('bluebird').Promise;
var fs = PromiseA.promisifyAll(require('fs'));
var crypto = require('crypto');
function noop() {
}
function create(options) {
if (!options) {
options = {};
}
if (!options.new) {
options.new = 'new';
if (!options.tmp) {
options.tmp = 'tmp';
}
if (!options.bak) {
options.bak = 'bak';
}
if (options.new === options.bak) {
throw new Error("'new' and 'bak' suffixes cannot be the same... duh");
if (options.tmp === options.bak) {
throw new Error("'tmp' and 'bak' suffixes cannot be the same... duh");
}
var newnamefn = options.newnamefn || function (pathname) {
return pathname + '.' + options.new;
var tmpnamefn = options.tmpnamefn || function (pathname) {
return pathname + '.' + crypto.randomBytes(8).toString('hex') + '.' + options.tmp;
};
var baknamefn = options.baknamefn || function (pathname) {
return pathname + '.' + options.bak;
};
/*
var namefn = options.namefn || function (pathname) {
return pathname;
};
*/
var sfs = {
writeFile: function (filename, data, options) {
//console.log(newnamefn(filename));
return fs.writeFileAsync(newnamefn(filename), data, options).then(function () {
//console.log(namefn(filename));
return sfs.commit(namefn(filename));
return sfs.stage(filename, data, options).then(function (tmpname) {
//console.log(filename);
return sfs.commit(tmpname, filename);
});
}
, commit: function (filename) {
, stage: function (filename, data, options) {
var tmpname = tmpnamefn(filename);
//console.log(tmpname);
return fs.writeFileAsync(tmpname, data, options).then(function () {
return tmpname;
});
}
, commit: function (tmpname, filename) {
var bakname = baknamefn(filename);
// this may not exist
return fs.unlinkAsync(baknamefn(filename)).then(noop, noop).then(function () {
return fs.unlinkAsync(bakname).then(noop, noop).then(function () {
// this may not exist
//console.log(namefn(filename), '->', baknamefn(filename));
return fs.renameAsync(namefn(filename), baknamefn(filename)).then(function () {
//console.log(namefn(filename), '->', bakname);
return fs.renameAsync(filename, bakname).then(function () {
//console.log('created bak');
}, noop);
}).then(function () {
// this must be successful
//console.log(newnamefn(filename), '->', namefn(filename));
return fs.renameAsync(newnamefn(filename), namefn(filename)).then(noop, function (err) {
//console.log(filename, '->', filename);
return fs.renameAsync(tmpname, filename).then(noop, function (err) {
//console.error(err);
return sfs.revert(filename).then(function () {
return PromiseA.reject(err);
@ -59,14 +70,19 @@ function create(options) {
}
, revert: function (filename) {
return new PromiseA(function (resolve, reject) {
var reader = fs.createReadStream(baknamefn(filename));
var writer = fs.createWriteStream(namefn(filename));
var bakname = baknamefn(filename);
var tmpname = tmpnamefn(filename);
var reader = fs.createReadStream(bakname);
var writer = fs.createWriteStream(tmpname);
reader.on('error', reject);
writer.on('error', reject);
reader.pipe(writer);
writer.on('close', resolve);
writer.on('close', function () {
sfs.commit(tmpname, filename).then(resolve, reject);
});
});
}
};

View File

@ -5,7 +5,7 @@ var safeReplace = require('./').create();
var fs = require('fs');
safeReplace.writeFile('keep.txt', 'my precious').then(function () {
fs.readdir('.', function (nodes) {
fs.readdir('.', function (err, nodes) {
console.log('file system nodes', nodes);
});
});