total4
Version:
Total.js framework v4
799 lines (615 loc) • 14.6 kB
JavaScript
'use strict';
require('./utils');
const Fs = require('fs');
const Path = require('path');
const QueryBuilder = require('./textdb-builder').QueryBuilder;
const TextReader = require('./textdb-reader');
const REGDATE = /"\d{4}-\d{2}-\d{2}T[0-9.:]+Z"/g;
const MAXREADERS = 3;
const FILELIMIT = 512 * 1024; // Bytes
const READOPT = { encoding: 'utf8' };
function TextDB(filename, onetime) {
var t = this;
t.filename = filename;
t.duration = [];
t.pending_count = 0;
t.pending_update = [];
t.pending_append = [];
t.pending_reader = [];
t.pending_remove = [];
t.pending_reader2 = [];
t.pending_clear = [];
t.pending_locks = [];
t.step = 0;
t.$timeoutmeta;
t.$writing = false;
t.$reading = 0;
t.total = 0;
t.filesize = 0;
t.files = [];
t.writing = {};
t.next2 = function() {
t.next(0);
};
t.refresh();
}
const TD = TextDB.prototype;
function next_operation(self, type) {
self.next(type);
}
TD.refresh = function() {
setImmediate(next_operation, this, 99);
};
TD.$refresh = function() {
var self = this;
self.step = 99;
Fs.readdir(self.filename, function(err, files) {
if (err) {
PATH.mkdir(self.filename);
setTimeout(self.next2, 100);
return;
}
self.filesize = 0;
files.wait(function load(item, next) {
if (self.writing[item]) {
setTimeout(load, 10, item, next);
return;
}
var filename = Path.join(self.filename, item);
Fs.lstat(filename, function(err, stat) {
if (stat && stat.isFile()) {
self.files.push({ id: item, filename: filename, size: stat.size, sortindex: parseInt(item, 36) });
self.filesize += stat.size;
}
next();
});
}, function() {
self.files.quicksort('sortindex_desc');
self.next(0);
}, 5);
});
};
TD.insert = function() {
var self = this;
var builder = new QueryBuilder(self);
self.pending_append.push(builder);
setImmediate(next_operation, self, 1);
return builder;
};
TD.update = function() {
var self = this;
var builder = new QueryBuilder(self);
self.pending_update.push(builder);
setImmediate(next_operation, self, 2);
return builder;
};
TD.release = function() {
var self = this;
for (var key in F.databases) {
if (F.databases[key] === self)
delete F.databases[key];
}
return self;
};
TD.drop = function(callback) {
var self = this;
self.pending_drops = callback || NOOP;
setImmediate(next_operation, self, 7);
return self;
};
TD.clear = function(callback) {
var self = this;
self.pending_clear.push(callback || NOOP);
setImmediate(next_operation, self, 12);
return self;
};
TD.clean = function(callback) {
var self = this;
callback && callback();
setImmediate(next_operation, self, 13);
return self;
};
TD.remove = function() {
var self = this;
var builder = new QueryBuilder(self);
self.pending_remove.push(builder);
setImmediate(next_operation, self, 3);
return builder;
};
TD.find = TD.find2 = function(builder) {
var self = this;
if (builder instanceof QueryBuilder)
builder.db = self;
else
builder = new QueryBuilder(self);
self.pending_reader.push(builder);
setImmediate(next_operation, self, 4);
return builder;
};
TD.recount = function() {
var self = this;
self.pending_count++;
setImmediate(next_operation, self, 5);
};
/*
TD.view = function(name, fn) {
var self = this;
if (!self.$views)
self.$views = {};
var emit = function(key, doc) {
// name
// key
// doc
};
self.$views[name] = { emit: emit, fn: typeof(fn) === 'string' ? new Function('$', fn) : fn };
return self;
};
TD.$reduce = function($) {
for (var key in this.$views) {
var item = this.$views[key];
$.emit = item.emit;
item.fn($);
}
};
TD.rebuild = function() {
};*/
// 1 append
// 2 update
// 3 remove
// 4 reader
// 5 counting
// 7 drop
// 8 backup
// 9 restore
// 10 streamer
// 11 reader reverse
// 12 clear
// 13 clean
// 14 locks
const NEXTWAIT = { 99: true, 7: true, 8: true, 9: true, 12: true, 13: true, 14: true };
TD.next = function(type) {
if (type && NEXTWAIT[this.step])
return;
if (this.step !== 99 && type === 99) {
this.$refresh();
return;
}
if (!this.$writing && !this.$reading) {
if (this.step !== 12 && this.pending_clear.length) {
this.$clear();
return;
}
if (this.step !== 7 && this.pending_drops) {
this.$drop();
return;
}
if (this.step !== 14 && this.pending_locks.length) {
this.$lock();
return;
}
if (this.step !== 5 && this.pending_count) {
this.$count();
return;
}
}
if (!this.$writing) {
if (this.step !== 1 && this.pending_append.length) {
this.$append();
return;
}
if (this.step !== 2 && !this.$writing && this.pending_update.length) {
this.$update();
return;
}
if (this.step !== 3 && !this.$writing && this.pending_remove.length) {
this.$remove();
return;
}
}
if (this.$reading < MAXREADERS) {
// if (this.step !== 4 && this.pending_reader.length) {
if (this.pending_reader.length) {
this.$reader();
return;
}
}
if (this.step !== type) {
this.step = 0;
setImmediate(next_operation, this, 0);
}
};
// ======================================================================
// FILE OPERATIONS
// ======================================================================
function replacedate(text) {
return 'new Date(' + new Date(text.substring(1, text.length - 1)).getTime() + ')';
}
TD.lock = function(callback) {
var self = this;
self.pending_locks.push(callback || NOOP);
setImmediate(next_operation, self, 14);
return self;
};
TD.$lock = function() {
var self = this;
self.step = 14;
if (!self.pending_locks.length) {
self.next(0);
return;
}
var filter = self.pending_locks.splice(0);
filter.wait(function(fn, next) {
fn.call(self, next);
}, () => self.next(0));
};
TD.$append = function() {
var self = this;
self.step = 1;
if (!self.pending_append.length) {
self.next(0);
return;
}
self.$writing = true;
var now = Date.now();
var output = [];
self.files.wait(function load(item, next) {
if (self.writing[item.id]) {
setTimeout(load, 10, item, next);
return;
}
if (item.size > FILELIMIT || item.failed) {
next();
return;
}
Fs.readFile(item.filename, READOPT, function(err, body) {
var diff = FILELIMIT - item.size;
var arr;
try {
arr = body && body.length ? (new Function('return ' + body))() : [];
} catch (e) {
DEF.onError(e, item.filename);
item.failed = true;
next();
return;
}
var is = false;
while (self.pending_append.length) {
var builder = self.pending_append.shift();
var length = Buffer.from(JSON.stringify(builder.payload)).length;
diff -= length;
item.size += length;
self.filesize += length;
// Adds as a first item
arr.unshift(builder.payload);
self.total++;
if (builder.$callback || builder.$callback2)
output.push(builder);
is = true;
self.oninsert && self.oninsert(builder.payload);
if (diff <= 0)
break;
}
if (is) {
self.writing[item.id] = 1;
Fs.writeFile(item.filename, JSON.stringify(arr).replace(REGDATE, replacedate), function() {
delete self.writing[item.id];
next();
});
} else
next();
});
}, function() {
var arr = [];
var size = 0;
while (self.pending_append.length) {
var builder = self.pending_append.shift();
var length = Buffer.from(JSON.stringify(builder.payload)).length;
// adds as a first item
arr.unshift(builder.payload);
self.total++;
self.filesize += length;
if (builder.$callback || builder.$callback2)
output.push(builder);
size += length;
if (size > FILELIMIT || !self.pending_append.length) {
var id = Date.now();
var filename = Path.join(self.filename, id.toString(36) + '.json');
self.files.unshift({ filename: filename, size: size, sortindex: id });
Fs.writeFile(filename, JSON.stringify(arr).replace(REGDATE, replacedate), ERROR('textdb'));
arr = [];
size = 0;
}
}
var diff = Date.now() - now;
if (self.duration.push({ type: 'insert', duration: diff }) > 20)
self.duration.shift();
for (var i = 0; i < output.length; i++) {
var builder = output[i];
builder.duration = diff;
builder.response = builder.counter = builder.count = 1;
builder.logrule && builder.logrule();
builder.done();
}
setImmediate(next_append, self);
});
};
function next_append(self) {
self.$writing = false;
self.next(0);
}
TD.$reader = function() {
var self = this;
self.step = 4;
if (!self.pending_reader.length) {
self.next(0);
return self;
}
var filters = TextReader.make(self.pending_reader.splice(0));
filters.type = 'read';
filters.db = self;
self.$reading++;
var done = function() {
self.$reading--;
filters.done();
self.next(0);
};
self.files.wait(function load(item, next) {
if (!next)
return;
if (self.writing[item.id]) {
setTimeout(load, 10, item, next);
return;
}
if (item.error) {
next();
return;
}
Fs.readFile(item.filename, READOPT, function(err, body) {
var docs;
try {
docs = body ? (new Function('return ' + body))() : EMPTYARRAY;
} catch (e) {
item.failed = true;
DEF.onError(e, item.filename);
next();
return;
}
if (filters.compare(docs)) {
next = null;
done();
} else
next();
});
}, done, 3);
return self;
};
TD.$update = function() {
var self = this;
self.step = 2;
if (!self.pending_update.length) {
self.next(0);
return self;
}
self.$writing = true;
var filter = self.pending_update.splice(0);
var filters = TextReader.make();
filters.type = 'update';
filters.db = self;
for (var i = 0; i < filter.length; i++)
filters.add(filter[i], true);
var update = function(docs, doc, dindex, f) {
try {
f.modifyrule(docs[dindex], f.modifyarg);
f.backuprule && f.backuprule(doc);
self.onupdate && self.onupdate(docs[dindex]);
} catch (e) {
f.canceled = true;
f.error = e + '';
}
};
var done = function() {
self.$writing = false;
self.next(0);
var diff = filters.done().diff;
if (self.duration.push({ type: 'update', duration: diff }) > 20)
self.duration.shift();
if (filters.total > 0)
filters.db.total = filters.total;
};
self.files.wait(function load(item, next) {
if (!next)
return;
if (self.writing[item.id]) {
setTimeout(load, 10, item, next);
return;
}
if (item.failed) {
next();
return;
}
Fs.readFile(item.filename, READOPT, function(err, body) {
if (!next)
return;
var docs;
try {
docs = body ? (new Function('return ' + body))() : EMPTYARRAY;
} catch (e) {
item.failed = true;
DEF.onError(e, item.filename);
next();
return;
}
var r = filters.compare3(docs, update);
if (r === 0) {
next = null;
done();
return;
}
if (r === 2) {
self.writing[item.id] = 1;
var buffer = Buffer.from(JSON.stringify(docs).replace(REGDATE, replacedate));
item.size = buffer.length;
item.modified = true;
Fs.writeFile(item.filename, buffer, function() {
delete self.writing[item.id];
next();
});
} else
next();
});
}, done, 3);
return self;
};
TD.$remove = function() {
var self = this;
self.step = 3;
if (!self.pending_remove.length) {
self.next(0);
return;
}
self.$writing = true;
var filter = self.pending_remove.splice(0);
var filters = TextReader.make(filter);
filters.type = 'remove';
filters.db = self;
var removed = [];
var remove = function(docs, d, dindex, f) {
removed.push(d);
filters.total--;
f.backuprule && f.backuprule(d);
self.onremove && self.onremove(d);
return 1;
};
var done = function() {
var diff = filters.done().diff;
if (self.duration.push({ type: 'remove', duration: diff }) > 20)
self.duration.shift();
self.$writing = false;
self.next(0);
};
self.files.wait(function load(item, next) {
if (!next)
return;
if (self.writing[item.id]) {
setTimeout(load, 10, item, next);
return;
}
if (item.failed) {
next();
return;
}
Fs.readFile(item.filename, READOPT, function(err, body) {
if (!next)
return;
var docs;
try {
docs = body ? (new Function('return ' + body))() : EMPTYARRAY;
} catch (e) {
item.failed = true;
DEF.onError(e, item.filename);
next();
return;
}
var r = filters.compare3(docs, remove);
if (r === 0) {
next = null;
done();
return;
}
if (r === 2) {
while (removed.length) {
var doc = removed.shift();
var index = docs.indexOf(doc);
docs.splice(index, 1);
}
var buffer = Buffer.from(JSON.stringify(docs).replace(REGDATE, replacedate));
item.size = buffer.length;
item.modified = true;
self.writing[item.id] = 1;
Fs.writeFile(item.filename, buffer, function() {
delete self.writing[item.id];
next();
});
} else
next();
});
}, done, 3);
};
TD.$clear = function() {
var self = this;
self.step = 12;
if (!self.pending_clear.length) {
self.next(0);
return;
}
var filter = self.pending_clear.splice(0);
self.files.wait(function load(item, next) {
if (self.writing[item.id])
setTimeout(load, 10, item, next);
else
Fs.unlink(item.filename, next);
}, function() {
self.total = 0;
self.filesize = 0;
for (var i = 0; i < filter.length; i++)
filter[i]();
self.files = [];
self.next(0);
}, 5);
};
TD.$drop = function() {
var self = this;
self.step = 7;
if (self.pending_drops) {
self.total = 0;
self.filesize = 0;
self.files.wait((item, next) => Fs.unlink(item.filename, next), () => Fs.rmdir(self.filename, function() {
self.pending_drops();
self.pending_drops = null;
self.next(0);
}), 5);
} else
self.next(0);
};
TD.$count = function() {
var self = this;
self.step = 5;
if (!self.pending_count) {
self.next(0);
return self;
}
self.pending_count = 0;
self.$reading++;
self.filesize = 0;
self.total = 0;
self.files.wait(function load(item, next) {
if (item.failed) {
next();
return;
}
if (self.writing[item.id]) {
setTimeout(load, 10, item, next);
return;
}
self.filesize += item.size;
Fs.readFile(item.filename, READOPT, function(err, body) {
var docs;
try {
docs = body ? (new Function('return ' + body))() : EMPTYARRAY;
self.total += docs.length;
} catch (e) {
item.failed = true;
DEF.onError(e, item.filename);
}
next();
});
}, function() {
self.$reading--;
self.next(0);
});
return self;
};
exports.TextDB = function(name) {
var instance = new TextDB(name);
return instance;
};