|
|
@ -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 |
|
|
|