use random tmpnames
This commit is contained in:
parent
5db9d90c90
commit
b74d5f2644
21
README.md
21
README.md
|
@ -3,6 +3,9 @@ safe-replace
|
||||||
|
|
||||||
A micro-module for safely replacing a file.
|
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
|
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
|
```bash
|
||||||
# create the new version
|
# create the new version
|
||||||
touch keep.txt.new
|
touch keep.txt.RANDOM.tmp
|
||||||
|
|
||||||
# remove the previous backup
|
# remove the previous backup
|
||||||
rm -f keep.txt.bak
|
rm -f keep.txt.bak
|
||||||
|
@ -19,7 +22,7 @@ rm -f keep.txt.bak
|
||||||
mv keep.txt keep.txt.bak
|
mv keep.txt keep.txt.bak
|
||||||
|
|
||||||
# move the new version to the current
|
# 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,
|
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
|
// let's say I wrote keep.txt.x7t7sq926.tmp with my own mechanism
|
||||||
safeReplace.commit('keep.txt').then(function () {
|
safeReplace.commit('keep.txt.x7t7sq926.tmp', 'keep.txt').then(function () {
|
||||||
fs.readdir('.', function (nodes) {
|
fs.readdir('.', function (nodes) {
|
||||||
console.log('file system nodes', nodes);
|
console.log('file system nodes', nodes);
|
||||||
// keep.txt
|
// keep.txt
|
||||||
|
@ -65,4 +68,14 @@ safeReplace.revert('keep.txt').then(function () {
|
||||||
// keep.txt.bak
|
// 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
|
||||||
|
});
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
54
index.js
54
index.js
|
@ -2,54 +2,65 @@
|
||||||
|
|
||||||
var PromiseA = require('bluebird').Promise;
|
var PromiseA = require('bluebird').Promise;
|
||||||
var fs = PromiseA.promisifyAll(require('fs'));
|
var fs = PromiseA.promisifyAll(require('fs'));
|
||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
function noop() {
|
function noop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function create(options) {
|
function create(options) {
|
||||||
|
|
||||||
if (!options) {
|
if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
if (!options.new) {
|
if (!options.tmp) {
|
||||||
options.new = 'new';
|
options.tmp = 'tmp';
|
||||||
}
|
}
|
||||||
if (!options.bak) {
|
if (!options.bak) {
|
||||||
options.bak = 'bak';
|
options.bak = 'bak';
|
||||||
}
|
}
|
||||||
if (options.new === options.bak) {
|
if (options.tmp === options.bak) {
|
||||||
throw new Error("'new' and 'bak' suffixes cannot be the same... duh");
|
throw new Error("'tmp' and 'bak' suffixes cannot be the same... duh");
|
||||||
}
|
}
|
||||||
|
|
||||||
var newnamefn = options.newnamefn || function (pathname) {
|
var tmpnamefn = options.tmpnamefn || function (pathname) {
|
||||||
return pathname + '.' + options.new;
|
return pathname + '.' + crypto.randomBytes(8).toString('hex') + '.' + options.tmp;
|
||||||
};
|
};
|
||||||
var baknamefn = options.baknamefn || function (pathname) {
|
var baknamefn = options.baknamefn || function (pathname) {
|
||||||
return pathname + '.' + options.bak;
|
return pathname + '.' + options.bak;
|
||||||
};
|
};
|
||||||
|
/*
|
||||||
var namefn = options.namefn || function (pathname) {
|
var namefn = options.namefn || function (pathname) {
|
||||||
return pathname;
|
return pathname;
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
var sfs = {
|
var sfs = {
|
||||||
writeFile: function (filename, data, options) {
|
writeFile: function (filename, data, options) {
|
||||||
//console.log(newnamefn(filename));
|
return sfs.stage(filename, data, options).then(function (tmpname) {
|
||||||
return fs.writeFileAsync(newnamefn(filename), data, options).then(function () {
|
//console.log(filename);
|
||||||
//console.log(namefn(filename));
|
return sfs.commit(tmpname, filename);
|
||||||
return sfs.commit(namefn(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
|
// 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
|
// this may not exist
|
||||||
//console.log(namefn(filename), '->', baknamefn(filename));
|
//console.log(namefn(filename), '->', bakname);
|
||||||
return fs.renameAsync(namefn(filename), baknamefn(filename)).then(function () {
|
return fs.renameAsync(filename, bakname).then(function () {
|
||||||
//console.log('created bak');
|
//console.log('created bak');
|
||||||
}, noop);
|
}, noop);
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
// this must be successful
|
// this must be successful
|
||||||
//console.log(newnamefn(filename), '->', namefn(filename));
|
//console.log(filename, '->', filename);
|
||||||
return fs.renameAsync(newnamefn(filename), namefn(filename)).then(noop, function (err) {
|
return fs.renameAsync(tmpname, filename).then(noop, function (err) {
|
||||||
//console.error(err);
|
//console.error(err);
|
||||||
return sfs.revert(filename).then(function () {
|
return sfs.revert(filename).then(function () {
|
||||||
return PromiseA.reject(err);
|
return PromiseA.reject(err);
|
||||||
|
@ -59,14 +70,19 @@ function create(options) {
|
||||||
}
|
}
|
||||||
, revert: function (filename) {
|
, revert: function (filename) {
|
||||||
return new PromiseA(function (resolve, reject) {
|
return new PromiseA(function (resolve, reject) {
|
||||||
var reader = fs.createReadStream(baknamefn(filename));
|
var bakname = baknamefn(filename);
|
||||||
var writer = fs.createWriteStream(namefn(filename));
|
var tmpname = tmpnamefn(filename);
|
||||||
|
|
||||||
|
var reader = fs.createReadStream(bakname);
|
||||||
|
var writer = fs.createWriteStream(tmpname);
|
||||||
|
|
||||||
reader.on('error', reject);
|
reader.on('error', reject);
|
||||||
writer.on('error', reject);
|
writer.on('error', reject);
|
||||||
|
|
||||||
reader.pipe(writer);
|
reader.pipe(writer);
|
||||||
writer.on('close', resolve);
|
writer.on('close', function () {
|
||||||
|
sfs.commit(tmpname, filename).then(resolve, reject);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
2
test.js
2
test.js
|
@ -5,7 +5,7 @@ var safeReplace = require('./').create();
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
|
||||||
safeReplace.writeFile('keep.txt', 'my precious').then(function () {
|
safeReplace.writeFile('keep.txt', 'my precious').then(function () {
|
||||||
fs.readdir('.', function (nodes) {
|
fs.readdir('.', function (err, nodes) {
|
||||||
console.log('file system nodes', nodes);
|
console.log('file system nodes', nodes);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue