|
|
|
@ -6,6 +6,8 @@ var fs = require('fs') |
|
|
|
|
, path = require('path') |
|
|
|
|
, model = require('./model') |
|
|
|
|
, customUtils = require('./customUtils') |
|
|
|
|
, async = require('async') |
|
|
|
|
, mkdirp = require('mkdirp') |
|
|
|
|
; |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -18,6 +20,18 @@ function Persistence (options) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Check if a directory exists and create it on the fly if it is not the case |
|
|
|
|
* cb is optional, signature: err |
|
|
|
|
*/ |
|
|
|
|
Persistence.ensureDirectoryExists = function (dir, cb) { |
|
|
|
|
var callback = cb || function () {} |
|
|
|
|
; |
|
|
|
|
|
|
|
|
|
mkdirp(dir, function (err) { return callback(err); }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Persist cached database |
|
|
|
|
* This serves as a compaction function since the cache always contains only the number of documents in the collection |
|
|
|
@ -68,5 +82,92 @@ Persistence.prototype.persistNewState = function (newDocs, cb) { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* From a database's raw data, return the corresponding |
|
|
|
|
* machine understandable collection |
|
|
|
|
*/ |
|
|
|
|
Persistence.treatRawData = function (rawData) { |
|
|
|
|
var data = rawData.split('\n') |
|
|
|
|
, dataById = {} |
|
|
|
|
, res = [] |
|
|
|
|
, i; |
|
|
|
|
|
|
|
|
|
for (i = 0; i < data.length; i += 1) { |
|
|
|
|
var doc; |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
doc = model.deserialize(data[i]); |
|
|
|
|
if (doc._id) { |
|
|
|
|
if (doc.$$deleted === true) { |
|
|
|
|
delete dataById[doc._id]; |
|
|
|
|
} else { |
|
|
|
|
dataById[doc._id] = doc; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Object.keys(dataById).forEach(function (k) { |
|
|
|
|
res.push(dataById[k]); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
return res; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Load the database |
|
|
|
|
* This means pulling data out of the data file or creating it if it doesn't exist |
|
|
|
|
* Also, all data is persisted right away, which has the effect of compacting the database file |
|
|
|
|
* This operation is very quick at startup for a big collection (60ms for ~10k docs) |
|
|
|
|
* @param {Function} cb Optional callback, signature: err |
|
|
|
|
* |
|
|
|
|
* @api private Use loadDatabase |
|
|
|
|
*/ |
|
|
|
|
Persistence.prototype._loadDatabase = function (cb) { |
|
|
|
|
var callback = cb || function () {} |
|
|
|
|
, self = this |
|
|
|
|
; |
|
|
|
|
|
|
|
|
|
self.db.resetIndexes(); |
|
|
|
|
self.db.datafileSize = 0; |
|
|
|
|
|
|
|
|
|
// In-memory only datastore
|
|
|
|
|
if (self.db.inMemoryOnly) { return callback(null); } |
|
|
|
|
|
|
|
|
|
async.waterfall([ |
|
|
|
|
function (cb) { |
|
|
|
|
Persistence.ensureDirectoryExists(path.dirname(self.db.filename), function (err) { |
|
|
|
|
fs.exists(self.db.filename, function (exists) { |
|
|
|
|
if (!exists) { return fs.writeFile(self.db.filename, '', 'utf8', function (err) { cb(err); }); } |
|
|
|
|
|
|
|
|
|
fs.readFile(self.db.filename, 'utf8', function (err, rawData) { |
|
|
|
|
if (err) { return cb(err); } |
|
|
|
|
var treatedData = Persistence.treatRawData(rawData); |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
self.db.resetIndexes(treatedData); |
|
|
|
|
} catch (e) { |
|
|
|
|
self.db.resetIndexes(); // Rollback any index which didn't fail
|
|
|
|
|
self.db.datafileSize = 0; |
|
|
|
|
return cb(e); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.db.datafileSize = treatedData.length; |
|
|
|
|
self.db.persistence.persistCachedDatabase(cb); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
], function (err) { |
|
|
|
|
if (err) { return callback(err); } |
|
|
|
|
|
|
|
|
|
self.db.executor.processBuffer(); |
|
|
|
|
return callback(null); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Interface
|
|
|
|
|
module.exports = Persistence; |
|
|
|
|