Created function ensuring datafile integrity

pull/2/head
Louis Chatriot 11 years ago
parent 9ee4c8abf9
commit adea00478c
  1. 7
      lib/datastore.js
  2. 88
      lib/persistence.js
  3. 4
      test/persistence.test.js
  4. 2
      test_lac/loadAndCrash.test.js

@ -542,4 +542,11 @@ Datastore.prototype.remove = function () {
}; };
var fs = require('fs');
console.log(fs.statSync('workspace/test.db'));
module.exports = Datastore; module.exports = Datastore;

@ -25,11 +25,11 @@ function Persistence (options) {
this.filename = this.db.filename; this.filename = this.db.filename;
if (!this.inMemoryOnly && this.filename) { if (!this.inMemoryOnly && this.filename) {
if (this.filename.charAt(this.filename.length - 1) === '~') { if (this.filename.charAt(this.filename.length - 1) === '~') {
throw "The datafile name can't end with a ~, which is reserved for automatic backup files"; throw "The datafile name can't end with a ~, which is reserved for automatic backup files";
} else { } else {
this.tempFilename = this.filename + '~'; this.tempFilename = this.filename + '~';
} }
} }
// For NW apps, store data in the same directory where NW stores application data // For NW apps, store data in the same directory where NW stores application data
@ -93,15 +93,39 @@ Persistence.getNWAppFilename = function (appName, relativeFilename) {
Persistence.prototype.persistCachedDatabase = function (cb) { Persistence.prototype.persistCachedDatabase = function (cb) {
var callback = cb || function () {} var callback = cb || function () {}
, toPersist = '' , toPersist = ''
, self = this
; ;
if (this.inMemoryOnly) { return callback(null); } if (this.inMemoryOnly) { return callback(null); }
this.db.getAllData().forEach(function (doc) { this.db.getAllData().forEach(function (doc) {
toPersist += model.serialize(doc) + '\n'; toPersist += model.serialize(doc) + '\n';
}); });
fs.writeFile(this.filename, toPersist, function (err) { return callback(err); }); async.waterfall([
function (cb) {
fs.exists(self.tempFilename, function (exists) {
if (exists) {
fs.unlink(self.tempFilename, function (err) { return cb(err); });
} else {
return cb();
}
});
}
, function (cb) {
console.log("==== 3");
fs.rename(self.filename, self.tempFilename, function (err) {
console.log("==== 4");
if (err) { return cb(err); }
fs.writeFile(self.filename, toPersist, function (err) {
console.log("==== 5");
if (err) { return cb(err); }
fs.unlink(self.tempFilename, function (err) { return cb(err); });
});
});
}
], function (err) { return callback(err); })
}; };
@ -198,6 +222,52 @@ Persistence.treatRawData = function (rawData) {
}; };
/**
* Ensure that this.filename contains the most up-to-date version of the data
* Even if a loadDatabase crashed before
*/
Persistence.prototype.ensureDatafileIntegrity = function (callback) {
var self = this ;
fs.exists(self.filename, function (filenameExists) {
fs.exists(self.tempFilename, function (tempFilenameExists) {
// Normal case
if (filenameExists && !tempFilenameExists) {
return callback();
}
// Process crashed right after renaming filename
if (!filenameExists && tempFilenameExists) {
return fs.rename(self.tempFilename, self.filename, function (err) { return callback(err); });
}
// No file exists, create empty datafile
if (!filenameExists && !tempFilenameExists) {
return fs.writeFile(self.filename, '', 'utf8', function (err) { callback(err); });
}
// Process crashed after or during write to datafile
// If datafile is not empty, it means process crashed after the write so we use it
// If it is empty, we don't know whether the database was emptied or we had a crash during write. The safest option is to use the temp datafile
if (filenameExists && tempFilenameExists) {
return fs.stat(self.filename, function (err, stats) {
if (err) { return callback(err); }
if (stats.size > 0) {
fs.unlink(self.tempFilename, function (err) { return callback(err); });
} else {
fs.unlink(self.tempFilename, function (err) {
if (err) { return callback(err); }
fs.rename(self.tempFilename, self.filename, function (err) { return callback(err); });
});
}
});
}
});
});
};
/** /**
* Load the database * Load the database
* This means pulling data out of the data file or creating it if it doesn't exist * This means pulling data out of the data file or creating it if it doesn't exist

@ -278,7 +278,9 @@ describe('Persistence', function () {
cp.on('message', function (msg) { cp.on('message', function (msg) {
// Let the child process enough time to crash // Let the child process enough time to crash
setTimeout(function () { setTimeout(function () {
fs.readFileSync('workspace/lac.db', 'utf8').length.should.not.equal(0); // fs.readFileSync('workspace/lac.db', 'utf8').length.should.not.equal(0);
console.log(fs.readFileSync('workspace/lac.db').length);
console.log(fs.readFileSync('workspace/lac.db~').length);
done(); done();
}, 100); }, 100);
}); });

@ -6,6 +6,6 @@ var Nedb = require('../lib/datastore.js')
setTimeout(function() { setTimeout(function() {
process.send('crash'); process.send('crash');
process.exit(); process.exit();
}, 100); }, 1880);
db.loadDatabase(); db.loadDatabase();
Loading…
Cancel
Save