From adea00478c0f3c7224677324d0fb97d867ab9653 Mon Sep 17 00:00:00 2001 From: Louis Chatriot Date: Sat, 23 Nov 2013 09:34:16 +0100 Subject: [PATCH] Created function ensuring datafile integrity --- lib/datastore.js | 7 +++ lib/persistence.js | 88 +++++++++++++++++++++++++++++++---- test/persistence.test.js | 4 +- test_lac/loadAndCrash.test.js | 2 +- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/lib/datastore.js b/lib/datastore.js index 863508d..60a719c 100644 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -542,4 +542,11 @@ Datastore.prototype.remove = function () { }; +var fs = require('fs'); + +console.log(fs.statSync('workspace/test.db')); + + + + module.exports = Datastore; \ No newline at end of file diff --git a/lib/persistence.js b/lib/persistence.js index 888347a..b428851 100644 --- a/lib/persistence.js +++ b/lib/persistence.js @@ -25,11 +25,11 @@ function Persistence (options) { this.filename = this.db.filename; if (!this.inMemoryOnly && this.filename) { - if (this.filename.charAt(this.filename.length - 1) === '~') { - throw "The datafile name can't end with a ~, which is reserved for automatic backup files"; - } else { - this.tempFilename = this.filename + '~'; - } + if (this.filename.charAt(this.filename.length - 1) === '~') { + throw "The datafile name can't end with a ~, which is reserved for automatic backup files"; + } else { + this.tempFilename = this.filename + '~'; + } } // 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) { var callback = cb || function () {} , toPersist = '' + , self = this ; - - if (this.inMemoryOnly) { return callback(null); } + + if (this.inMemoryOnly) { return callback(null); } this.db.getAllData().forEach(function (doc) { 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 * This means pulling data out of the data file or creating it if it doesn't exist diff --git a/test/persistence.test.js b/test/persistence.test.js index 7f474e1..7b9ef59 100644 --- a/test/persistence.test.js +++ b/test/persistence.test.js @@ -278,7 +278,9 @@ describe('Persistence', function () { cp.on('message', function (msg) { // Let the child process enough time to crash 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(); }, 100); }); diff --git a/test_lac/loadAndCrash.test.js b/test_lac/loadAndCrash.test.js index 500ff6b..5487d7d 100644 --- a/test_lac/loadAndCrash.test.js +++ b/test_lac/loadAndCrash.test.js @@ -6,6 +6,6 @@ var Nedb = require('../lib/datastore.js') setTimeout(function() { process.send('crash'); process.exit(); -}, 100); +}, 1880); db.loadDatabase(); \ No newline at end of file