Browse Source

update gundb lib

Nikolay Suslov 5 years ago
parent
commit
1749d84126

+ 12 - 8
public/app.js

@@ -656,7 +656,8 @@ class App {
               file = _app.helpers.replaceSubStringALL(fileOriginal, "~", '/');
             }
 
-            var worldFile = await _LCSDB.user().get(worldType).get(worldName).get(file).then();
+            _LCSDB.user().get(worldType).get(worldName).get(file).load(worldFile => {
+
             if (worldFile) {
               var source = worldFile.file;
               if (type == 'state') {
@@ -792,7 +793,8 @@ class App {
                     $text: "Close",
                     onclick: function (e) {
                       console.log("close");
-                      window.history.back();
+                      window.location.pathname = "/" + user + '/' + worldName + '/about'
+                      //window.history.back();
                       // if (type == "proto")
                       //   window.location.pathname = "/" + user + '/' + worldName + '/about'
                       // if (type == "state")
@@ -808,6 +810,8 @@ class App {
                 ]
               })
             }
+          });
+
           }
         }
       })
@@ -1205,7 +1209,7 @@ class App {
 
     _app.showProgressBar();
 
-    let users = await _LCSDB.get('users');
+    let users = await _LCSDB.get('users').then();
     let userPub = await _LCSDB.get('users').get(userName).get('pub').then();
     let newOwner = _LCSDB.user().is.pub;
 
@@ -1311,7 +1315,7 @@ class App {
     let myWorldProtos = await _LCSDB.user().get('worlds').then();
     let userName = this.helpers.worldUser;
 
-    let users = await _LCSDB.get('users');
+    let users = await _LCSDB.get('users').then();
 
     let userPub = await _LCSDB.get('users').get(userName).get('pub').then();
     let protoUserRoot = this.helpers.getRoot(true).root;
@@ -1757,7 +1761,7 @@ class App {
 
   async getAllStateWorldsInfoForUser(userAlias, cb) {
 
-    let users = await _LCSDB.get('users');
+    let users = await _LCSDB.get('users').then();
 
     let userPub = await _LCSDB.get('users').get(userAlias).get('pub').then();
 
@@ -1833,7 +1837,7 @@ class App {
 
   async getAllStateWorldsInfoForUserPromise(userAlias) {
 
-    let users = await _LCSDB.get('users');
+    let users = await _LCSDB.get('users').then();
     let userPub = await _LCSDB.get('users').get(userAlias).get('pub').then();
 
     var db = _LCSDB.user(userPub);
@@ -1865,7 +1869,7 @@ class App {
 
   async getAllProtoWorldsInfoForUser(userAlias, cb) {
 
-    let users = await _LCSDB.get('users');
+    let users = await _LCSDB.get('users').then();
     let userPub = await _LCSDB.get('users').get(userAlias).get('pub').then();
 
     var db = _LCSDB.user(userPub);
@@ -1921,7 +1925,7 @@ class App {
 
   async getAllProtoWorldsInfoForUserPromise(userAlias) {
 
-    let users = await _LCSDB.get('users');
+    let users = await _LCSDB.get('users').then();
     let userPub = await _LCSDB.get('users').get(userAlias).get('pub').then();
 
     var db = _LCSDB.user(userPub);

+ 19 - 10
public/lib/gundb/gun.js

@@ -1235,7 +1235,7 @@
 				gun = gun.$;
 			} else
 			if(key instanceof Function){
-				if(true === cb){ return soul(this, key, cb, as) }
+				if(true === cb){ return soul(this, key, cb, as), this }
 				gun = this;
 				var at = gun._, root = at.root, tmp = root.now, ev;
 				as = cb || {};
@@ -1292,15 +1292,22 @@
 		}
 		function soul(gun, cb, opt, as){
 			var cat = gun._, acks = 0, tmp;
-			if(tmp = cat.soul || cat.link || cat.dub){ return cb(tmp, as, cat), gun }
-			gun.get(function(msg, ev){
+			if(tmp = cat.soul || cat.link || cat.dub){ return cb(tmp, as, cat) }
+			if(cat.jam){ return cat.jam.push([cb, as]) }
+			cat.jam = [[cb,as]];
+			gun.get(function(msg, eve){
 				if(u === msg.put && (tmp = Object.keys(cat.root.opt.peers).length) && ++acks < tmp){
 					return;
 				}
-				ev.rid(msg);
+				eve.rid(msg);
 				var at = ((at = msg.$) && at._) || {};
-				tmp = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub;
-				cb(tmp, as, msg, ev);
+				tmp = cat.jam; Gun.obj.del(cat, 'jam');
+				Gun.obj.map(tmp, function(as, cb){
+					cb = as[0]; as = as[1];
+					if(!cb){ return }
+					var id = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub;
+					cb(id, as, msg, eve);
+				});
 			}, {out: {get: {'.':true}}});
 			return gun;
 		}
@@ -1361,9 +1368,9 @@
 		Gun.chain.put = function(data, cb, as){
 			// #soul.has=value>state
 			// ~who#where.where=what>when@was
-			// TODO: BUG! Put probably cannot handle plural chains!
+			// TODO: BUG! Put probably cannot handle plural chains! `!as` is quickfix test.
 			var gun = this, at = (gun._), root = at.root.$, ctx = root._, M = 100, tmp;
-			if(!ctx.puta){ if(tmp = ctx.puts){ if(tmp > M){ // without this, when synchronous, writes to a 'not found' pile up, when 'not found' resolves it recursively calls `put` which incrementally resolves each write. Stack overflow limits can be as low as 10K, so this limit is hardcoded to 1% of 10K.
+			/*if(!ctx.puta && !as){ if(tmp = ctx.puts){ if(tmp > M){ // without this, when synchronous, writes to a 'not found' pile up, when 'not found' resolves it recursively calls `put` which incrementally resolves each write. Stack overflow limits can be as low as 10K, so this limit is hardcoded to 1% of 10K.
 				(ctx.stack || (ctx.stack = [])).push([gun, data, cb, as]);
 				if(ctx.puto){ return }
 				ctx.puto = setTimeout(function drain(){
@@ -1373,7 +1380,7 @@
 					ctx.stack = ctx.puts = ctx.puto = null;
 				}, 0);
 				return gun;
-			} ++ctx.puts } else { ctx.puts = 1 } }
+			} ++ctx.puts } else { ctx.puts = 1 } }*/
 			as = as || {};
 			as.data = data;
 			as.via = as.$ = as.via || as.$ || gun;
@@ -1496,6 +1503,7 @@
 					ref = ref.get(path[i]);
 				}
 				if(is){ ref = v }
+				//if(as.not){ (ref._).dub = Gun.text.random() } // This might optimize stuff? Maybe not needed anymore. Make sure it doesn't introduce bugs.
 				var id = (ref._).dub;
 				if(id || (id = Gun.node.soul(at.obj))){
 					ref.back(-1).get(id);
@@ -1556,6 +1564,7 @@
 						if(at.link || at.soul){ return at.link || at.soul }
 						as.data = obj_put({}, at.get, as.data);
 					});
+					as.not = true; // maybe consider this?
 				}
 				tmp = tmp || at.soul || at.link || at.dub;// || at.get;
 				at = tmp? (at.root.$.get(tmp)._) : at;
@@ -1815,7 +1824,7 @@
 			// See the next 'opt' code below for actual saving of data.
 			var ev = this.to, opt = root.opt;
 			if(root.once){ return ev.next(root) }
-			//if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless!
+			if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless! // actually, this doesn't help, per @go1dfish 's observation. Disabling for now, will need better solution later.
 			opt.prefix = opt.file || 'gun/';
 			var gap = Gun.obj.ify(store.getItem('gap/'+opt.prefix)) || {};
 			var empty = Gun.obj.empty, id, to, go;

File diff suppressed because it is too large
+ 0 - 0
public/lib/gundb/gun.min.js


+ 1 - 1
public/lib/gundb/lib/multicast.js

@@ -22,7 +22,7 @@ Gun.on('create', function(root){
   socket.bind(udp.port);
 
   socket.on("listening", function(){
-    socket.addMembership(udp.address);
+    try { socket.addMembership(udp.address) }catch(e){ return }
     udp.peer = {id: udp.address + ':' + udp.port, wire: socket};
 
     udp.peer.say = function(raw){

+ 124 - 0
public/lib/gundb/lib/promise.js

@@ -0,0 +1,124 @@
+/* Promise Library v1.0 for GUN DB
+*  Turn any part of a gun chain into a promise, that you can then use
+*  .then().catch() pattern.
+*  In normal gun doing var item = gun.get('someKey'), gun returns a reference
+*  to the someKey synchroneously. Using a reference is quite helpful in making
+*  graph structures, so I have chosen to follow the following paradigm.
+*  Whenever a promise is resolved, gun will return an object of data, I will
+*  wrap that data in an object together with the reference like so:
+*  {ref: gunRef, data: data}.
+* This code is freely given in the spirit of open source MIT license.
+* Author: Jachen Duschletta / 2019
+*/
+
+/*
+* Function promOnce
+* @param limit - due to promises resolving too fast if we do not set a timer
+*  we will not be able receive any data back from gun before returning the promise
+*  works both following a Chain.get and a Chain.map (limit only applies to map)
+*  If no limit is chosen, defaults to 100 ms (quite sufficient to fetch about 2000 nodes or more)
+* @param opt - option object
+* @return {ref: gunReference, data: object / string (data), key: string (soulOfData)}
+*/
+
+Gun.chain.promOnce = async function (limit, opt) {
+ var gun = this, cat = gun._;
+ if(!limit){limit = 100}
+ if(cat.subs){
+  var array = [];
+  gun.map().once((data, key)=>{
+    var gun = this;
+    array.push(new Promise((res, rej)=>{
+      res({ref: gun, data:data, key:key});
+    })
+   )
+ }, opt);
+  await sleep(limit);
+  return Promise.all(array)
+} else {
+  return (new Promise((res, rej)=>{
+    gun.once(function (data, key) {
+      var gun = this;
+      res({ref:gun,data:data,key:key});
+      }, opt);
+    }))
+  }
+ var chain = gun.chain();
+ return chain;
+}
+
+function sleep (limit) {
+ return (new Promise((res, rej)=>{
+   setTimeout(res, limit);
+ }))
+}
+
+/*
+* Function promPut
+* @param item (string / object) - item to be put to that key in the chain
+* @param opt - option object
+* @return object - Returns an object with the ref to that node that was just
+*  created as well as the 'ack' which acknowledges the put was succesful
+*  object {ref: gunReference, ack: acknowledgmentObject}
+* If put had an error we can catch the return via .catch
+*/
+
+Gun.chain.promPut = async function (item, opt) {
+  var gun = this;
+  return (new Promise((res, rej)=>{
+    gun.put(item, function(ack) {
+        if(ack.err){rej(ack.err)}
+        res({ref:gun, ack:ack});
+    }, opt);
+  }))
+}
+
+/*
+* Function promSet
+* @param item (string / object) - item to be set into a list at this key
+* @param opt - option object
+* @return object - Returns object with the ref to that node that was just
+*  created as well as the 'ack' which acknowledges the set was succesful
+*  object {ref: gunReference, ack: acknowledgmentObject}
+* If set had an error we can catch the return via .catch
+*/
+
+Gun.chain.promSet = async function(item, opt){
+	var gun = this, soul;
+  var cb = cb || function(){};
+	opt = opt || {}; opt.item = opt.item || item;
+  return (new Promise(async function (res,rej) {
+    if(soul = Gun.node.soul(item)){ item = Gun.obj.put({}, soul, Gun.val.link.ify(soul)) }
+		if(!Gun.is(item)){
+			if(Gun.obj.is(item)){;
+				item = await gun.back(-1).get(soul = soul || Gun.node.soul(item) || gun.back('opt.uuid')()).promPut(item);
+        item = item.ref;
+			}
+			res(gun.get(soul || (Gun.state.lex() + Gun.text.random(7))).promPut(item));
+		}
+		item.get(function(soul, o, msg){
+      var ack = {};
+			if(!soul){ rej({ack:{err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}} ) }
+			gun.put(Gun.obj.put({}, soul, Gun.val.link.ify(soul)), cb, opt);
+		},true);
+		res({ref:item, ack:{ok:0}});
+  }))
+}
+
+/*
+* Function promOn
+* @param callback (function) - function to be called upon changes to data
+* @param option (object) - {change: true} only allow changes to trigger the callback
+* @return - data and key
+* subscribes callback to data
+*/
+
+Gun.chain.promOn = async function (callback, option) {
+  var gun = this;
+  return (new Promise((res, rej)=>{
+    gun.on(function (data, key){
+      callback(data, key);
+      res(data, key);
+    }, option);
+  }));
+}

+ 20 - 14
public/lib/gundb/lib/radisk.js

@@ -14,12 +14,12 @@
 		opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB
 		opt.code = opt.code || {};
 		opt.code.from = opt.code.from || '!';
-		//opt.jsonify = true; // TODO: REMOVE!!!!
+		//opt.jsonify = true; if(opt.jsonify){ console.log("JSON RAD!!!") } // TODO: REMOVE!!!!
 
 		function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
 		function atomic(v){ return u !== v && (!v || 'object' != typeof v) }
 		var map = Gun.obj.map;
-		var LOG = false;
+		var LOG = false;//true;
 
 		if(!opt.store){
 			return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!");
@@ -83,12 +83,13 @@
 			r.batch = Radix();
 			r.batch.acks = [];
 			r.batch.ed = 0;
-			//var id = Gun.text.random(2), S = (+new Date); console.log("<<<<<<<<<<<<", id);
+			//console.debug(99); var ID = Gun.text.random(2), S = (+new Date); console.log("[[[[[[[[", ID, batch.acks.length);
 			r.save(batch, function(err, ok){
 				if(++i > 1){ opt.log('RAD ERR: Radisk has callbacked multiple times, please report this as a BUG at github.com/amark/gun/issues ! ' + i); return }
 				if(err){ opt.log('err', err) }
-				//console.log(">>>>>>>>>>>>", id, ((+new Date) - S), batch.acks.length);
+				//console.debug(99); var TMP; console.log("]]]]]]]]", ID, batch.acks.length, (TMP = +new Date) - S, 'more?', thrash.more);
 				map(batch.acks, function(cb){ cb(err, ok) });
+				//console.log("][", +new Date - TMP, thrash.more);
 				thrash.at = null;
 				thrash.ing = false;
 				if(thrash.more){ thrash() }
@@ -171,9 +172,9 @@
 			}
 			f.write = function(){
 				var tmp = ename(file);
-				var start; LOG && (start = (+new Date)); // comment this out!
+				var start; LOG && (start = +new Date); // comment this out!
 				opt.store.put(tmp, f.text, function(err){
-					LOG && console.log("wrote JSON in", (+new Date) - start); // comment this out!
+					LOG && console.log("wrote to disk in", (+new Date) - start, tmp); // comment this out!
 					if(err){ return cb(err) }
 					r.list.add(tmp, cb);
 				});
@@ -202,10 +203,10 @@
 
 		r.write.jsonify = function(f, file, rad, cb, o){
 			var raw;
-			var start; LOG && (start = (+new Date)); // comment this out!
+			var start; LOG && (start = +new Date); // comment this out!
 			try{raw = JSON.stringify(rad.$);
 			}catch(e){ return cb("Record too big!") }
-			LOG && console.log("stringified JSON in", (+new Date) - start); // comment this out!
+			LOG && console.log("stringified JSON in", +new Date - start); // comment this out!
 			if(opt.chunk < raw.length && !o.force){
 				if(Radix.map(rad, f.each, true)){ return }
 			}
@@ -220,7 +221,7 @@
 			var sub = Radix();
 			Radix.map(tree, function(v,k){
 				sub(k,v);
-			}, o)
+			}, o);
 			return sub('');
 		}
 
@@ -266,7 +267,7 @@
 				}
 				g.ack = function(as){
 					if(!as.ack){ return }
-					var tmp = as.key, o = as.opt, info = g.info, rad = g.disk || noop, data = r.range(rad(tmp), o), last = rad.last;
+					var tmp = as.key, o = as.opt, info = g.info, rad = g.disk || noop, data = r.range(rad(tmp), o), last = rad.last || Radix.map(rad, rev, revo);
 					o.parsed = (o.parsed || 0) + (info.parsed||0);
 					o.chunks = (o.chunks || 0) + 1;
 					if(!o.some){ o.some = (u !== data) }
@@ -283,6 +284,8 @@
 				if(o.reverse){ g.lex.reverse = true }
 				r.list(g.lex);
 			}
+			function rev(a,b){ return b }
+			var revo = {reverse: true};
 		}());
 
 		;(function(){
@@ -299,6 +302,7 @@
 				var p = function Parse(){}, info = {};
 				p.disk = Radix();
 				p.read = function(err, data){ var tmp;
+					LOG && console.log('read disk in', +new Date - start, ename(file)); // keep this commented out in 
 					delete Q[file];
 					if((p.err = err) || (p.not = !data)){
 						return map(q, p.ack);
@@ -315,12 +319,12 @@
 					}
 					info.parsed = data.length;
 
-					var start; LOG && (start = (+new Date)); // keep this commented out in production!
+					LOG && (start = +new Date); // keep this commented out in production!
 					if(opt.jsonify){ // temporary testing idea
 						try{
 							var json = JSON.parse(data);
 							p.disk.$ = json;
-							LOG && console.log('parsed JSON in', (+new Date) - start); // keep this commented out in production!
+							LOG && console.log('parsed JSON in', +new Date - start); // keep this commented out in production!
 							map(q, p.ack);
 							return;
 						}catch(e){ tmp = e }
@@ -329,7 +333,7 @@
 							return map(q, p.ack);
 						}
 					}
-					var start; LOG && (start = (+new Date)); // keep this commented out in production!
+					LOG && (start = +new Date); // keep this commented out in production!
 					var tmp = p.split(data), pre = [], i, k, v;
 					if(!tmp || 0 !== tmp[1]){
 						p.err = "File '"+file+"' does not have root radix! ";
@@ -352,7 +356,7 @@
 						if(u !== k && u !== v){ p.disk(pre.join(''), v) }
 						tmp = p.split(tmp[2]);
 					}
-					LOG && console.log('parsed JSON in', (+new Date) - start); // keep this commented out in production!
+					LOG && console.log('parsed RAD in', +new Date - start); // keep this commented out in production!
 					//cb(err, p.disk);
 					map(q, p.ack);
 				};
@@ -372,6 +376,7 @@
 					if(p.err || p.not){ return cb(p.err, u, info) }
 					cb(u, p.disk, info);
 				}
+				var start; LOG && (start = +new Date); // keep this commented out in production!
 				if(raw){ return p.read(null, raw) }
 				opt.store.get(ename(file), p.read);
 			}
@@ -504,6 +509,7 @@
 	} else { 
 	  var Gun = require('../gun');
 		var Radix = require('./radix');
+		//var Radix = require('./radix2'); Radisk = require('./radisk2');
 		try{ module.exports = Radisk }catch(e){}
 	}
 

+ 525 - 0
public/lib/gundb/lib/radisk2.js

@@ -0,0 +1,525 @@
+;(function(){
+	console.log("RADISK 2!!!!");
+
+	function Radisk(opt){
+
+		opt = opt || {};
+		opt.log = opt.log || console.log;
+		opt.file = String(opt.file || 'radata');
+		var has = (Radisk.has || (Radisk.has = {}))[opt.file];
+		if(has){ return has }
+
+		opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB.
+		opt.until = opt.until || opt.wait || 250;
+		opt.batch = opt.batch || (10 * 1000);
+		opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB
+		opt.code = opt.code || {};
+		opt.code.from = opt.code.from || '!';
+		//opt.jsonify = true; // TODO: REMOVE!!!!
+
+		function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
+		function atomic(v){ return u !== v && (!v || 'object' != typeof v) }
+		var map = Gun.obj.map;
+		var LOG = false;
+
+		if(!opt.store){
+			return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!");
+		}
+		if(!opt.store.put){
+			return opt.log("ERROR: Radisk needs `store.put` interface with `(file, data, cb)`!");
+		}
+		if(!opt.store.get){
+			return opt.log("ERROR: Radisk needs `store.get` interface with `(file, cb)`!");
+		}
+		if(!opt.store.list){
+			//opt.log("WARNING: `store.list` interface might be needed!");
+		}
+
+		/*
+			Any and all storage adapters should...
+			1. Because writing to disk takes time, we should batch data to disk. This improves performance, and reduces potential disk corruption.
+			2. If a batch exceeds a certain number of writes, we should immediately write to disk when physically possible. This caps total performance, but reduces potential loss.
+		*/
+		var r = function(key, val, cb){
+			key = ''+key;
+			if(val instanceof Function){
+				var o = cb || {};
+				cb = val;
+				val = r.batch(key);
+				if(u !== val){
+					cb(u, r.range(val, o), o);
+					if(atomic(val)){ return }
+					// if a node is requested and some of it is cached... the other parts might not be.
+				}
+				if(r.thrash.at){
+					val = r.thrash.at(key);
+					if(u !== val){
+						cb(u, r.range(val, o), o);
+						if(atomic(val)){ cb(u, val, o); return }
+						// if a node is requested and some of it is cached... the other parts might not be.
+					}
+				}
+				return r.read(key, cb, o);
+			}
+			r.batch(key, val);
+			if(cb){ r.batch.acks.push(cb) }
+			if(++r.batch.ed >= opt.batch){ return r.thrash() } // (2)
+			if(r.batch.to){ return }
+			//clearTimeout(r.batch.to); // (1) // THIS LINE IS EVIL! NEVER USE IT! ALSO NEVER DELETE THIS SO WE NEVER MAKE THE SAME MISTAKE AGAIN!
+			r.batch.to = setTimeout(r.thrash, opt.until || 1);
+		}
+
+		r.batch = Radix();
+		r.batch.acks = [];
+		r.batch.ed = 0;
+
+		r.thrash = function(){
+			var thrash = r.thrash;
+			if(thrash.ing){ return thrash.more = true }
+			thrash.more = false;
+			thrash.ing = true;
+			var batch = thrash.at = r.batch, i = 0;
+			clearTimeout(r.batch.to);
+			r.batch = null;
+			r.batch = Radix();
+			r.batch.acks = [];
+			r.batch.ed = 0;
+			//var id = Gun.text.random(2), S = (+new Date); console.log("<<<<<<<<<<<<", id);
+			r.save(batch, function(err, ok){
+				if(++i > 1){ opt.log('RAD ERR: Radisk has callbacked multiple times, please report this as a BUG at github.com/amark/gun/issues ! ' + i); return }
+				if(err){ opt.log('err', err) }
+				//console.log(">>>>>>>>>>>>", id, ((+new Date) - S), batch.acks.length);
+				map(batch.acks, function(cb){ cb(err, ok) });
+				thrash.at = null;
+				thrash.ing = false;
+				if(thrash.more){ thrash() }
+			});
+		}
+
+		/*
+			1. Find the first radix item in memory.
+			2. Use that as the starting index in the directory of files.
+			3. Find the first file that is lexically larger than it,
+			4. Read the previous file to that into memory
+			5. Scan through the in memory radix for all values lexically less than the limit.
+			6. Merge and write all of those to the in-memory file and back to disk.
+			7. If file too large, split. More details needed here.
+		*/
+		r.save = function(rad, cb){
+			var s = function Span(){};
+			s.find = function(tree, key){
+				if(key < s.start){ return }
+				s.start = key;
+				r.list(s.lex);
+				return true;
+			}
+			s.lex = function(file){
+				file = (u === file)? u : decodeURIComponent(file);
+				if(!file || file > s.start){
+					s.mix(s.file || opt.code.from, s.start, s.end = file);
+					return true;
+				}
+				s.file = file;
+			}
+			s.mix = function(file, start, end){
+				s.start = s.end = s.file = u;
+				r.parse(file, function(err, disk){
+					if(err){ return cb(err) }
+					disk = disk || Radix();
+					Radix.map(rad, function(val, key){
+						if(key < start){ return }
+						if(end && end < key){ return s.start = key }
+						// PLUGIN: consider adding HAM as an extra layer of protection
+						disk(key, val); // merge batch[key] -> disk[key]
+					});
+					r.write(file, disk, s.next);
+				});
+			}
+			s.next = function(err, ok){
+				if(s.err = err){ return cb(err) }
+				if(s.start){ return Radix.map(rad, s.find) }
+				cb(err, ok);
+			}
+			Radix.map(rad, s.find);
+		}
+
+		/*
+			Any storage engine at some point will have to do a read in order to write.
+			This is true of even systems that use an append only log, if they support updates.
+			Therefore it is unavoidable that a read will have to happen,
+			the question is just how long you delay it.
+		*/
+		r.write = function(file, rad, cb, o){
+			o = ('object' == typeof o)? o : {force: o};
+			var f = function Fractal(){};
+			f.text = '';
+			f.count = 0;
+			f.file = file;
+			f.each = function(val, key, k, pre){
+				//console.log("RAD:::", JSON.stringify([val, key, k, pre]));
+				if(u !== val){ f.count++ }
+				if(opt.pack <= (val||'').length){ return cb("Record too big!"), true }
+				var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n';
+				if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !o.force){
+					f.text = '';
+					f.limit = Math.ceil(f.count/2);
+					f.count = 0;
+					f.sub = Radix();
+					Radix.map(rad, f.slice);
+					return true;
+				}
+				f.text += enc;
+			}
+			f.write = function(){
+				var tmp = ename(file);
+				var start; LOG && (start = (+new Date)); // comment this out!
+				opt.store.put(tmp, f.text, function(err){
+					LOG && console.log("wrote JSON in", (+new Date) - start); // comment this out!
+					if(err){ return cb(err) }
+					r.list.add(tmp, cb);
+				});
+			}
+			f.slice = function(val, key){
+				if(key < f.file){ return }
+				if(f.limit < (++f.count)){
+					var name = f.file;
+					f.file = key;
+					f.count = 0;
+					r.write(name, f.sub, f.next, o);
+					return true;
+				}
+				f.sub(key, val);
+			}
+			f.next = function(err){
+				if(err){ return cb(err) }
+				f.sub = Radix();
+				if(!Radix.map(rad, f.slice)){
+					r.write(f.file, f.sub, cb, o);
+				}
+			}
+			if(opt.jsonify){ return r.write.jsonify(f, file, rad, cb, o) } // temporary testing idea
+			if(!Radix.map(rad, f.each, true)){ f.write() }
+		}
+
+		r.write.jsonify = function(f, file, rad, cb, o){
+			var raw;
+			var start; LOG && (start = (+new Date)); // comment this out!
+			try{raw = JSON.stringify(rad.$);
+			}catch(e){ return cb("Record too big!") }
+			LOG && console.log("stringified JSON in", (+new Date) - start); // comment this out!
+			if(opt.chunk < raw.length && !o.force){
+				if(Radix.map(rad, f.each, true)){ return }
+			}
+			f.text = raw;
+			f.write();
+		}
+
+		r.range = function(tree, o){
+			if(!tree || !o){ return }
+			if(u === o.start && u === o.end){ return tree }
+			if(atomic(tree)){ return tree }
+			var sub = Radix();
+			Radix.map(tree, function(v,k){
+				sub(k,v);
+			}, o)
+			return sub('');
+		}
+
+		;(function(){
+			var Q = {};
+			r.read = function(key, cb, o){
+				o = o || {};
+				if(RAD && !o.next){ // cache
+					var val = RAD(key);
+					//if(u !== val){
+						//cb(u, val, o);
+						if(atomic(val)){ cb(u, val, o); return }
+						// if a node is requested and some of it is cached... the other parts might not be.
+					//}
+				}
+				o.span = (u !== o.start) || (u !== o.end);
+				var g = function Get(){};
+				g.lex = function(file){ var tmp;
+					file = (u === file)? u : decodeURIComponent(file);
+					tmp = o.next || key || (o.reverse? o.end || '\uffff' : o.start || '');
+					if(!file || (o.reverse? file < tmp : file > tmp)){
+						if(o.next || o.reverse){ g.file = file }
+						if(tmp = Q[g.file]){
+							tmp.push({key: key, ack: cb, file: g.file, opt: o});
+							return true;
+						}
+						Q[g.file] = [{key: key, ack: cb, file: g.file, opt: o}];
+						if(!g.file){
+							g.it(null, u, {});
+							return true; 
+						}
+						r.parse(g.file, g.it);
+						return true;
+					}
+					g.file = file;
+				}
+				g.it = function(err, disk, info){
+					if(g.err = err){ opt.log('err', err) }
+					g.info = info;
+					if(disk){ RAD = g.disk = disk }
+					disk = Q[g.file]; delete Q[g.file];
+					map(disk, g.ack);
+				}
+				g.ack = function(as){
+					if(!as.ack){ return }
+					var tmp = as.key, o = as.opt, info = g.info, rad = g.disk || noop, data = r.range(rad(tmp), o), last = rad.last;
+					o.parsed = (o.parsed || 0) + (info.parsed||0);
+					o.chunks = (o.chunks || 0) + 1;
+					if(!o.some){ o.some = (u !== data) }
+					if(u !== data){ as.ack(g.err, data, o) }
+					else if(!as.file){ !o.some && as.ack(g.err, u, o); return }
+					if(!o.span){
+						if(/*!last || */last === tmp){ !o.some && as.ack(g.err, u, o); return }
+						if(last && last > tmp && 0 != last.indexOf(tmp)){ !o.some && as.ack(g.err, u, o); return }
+					}
+					if(o.some && o.parsed >= o.limit){ return }
+					o.next = as.file;
+					r.read(tmp, as.ack, o);
+				}
+				if(o.reverse){ g.lex.reverse = true }
+				r.list(g.lex);
+			}
+		}());
+
+		;(function(){
+			/*
+				Let us start by assuming we are the only process that is
+				changing the directory or bucket. Not because we do not want
+				to be multi-process/machine, but because we want to experiment
+				with how much performance and scale we can get out of only one.
+				Then we can work on the harder problem of being multi-process.
+			*/
+			var Q = {}, s = String.fromCharCode(31);
+			r.parse = function(file, cb, raw){ var q;
+				if(q = Q[file]){ return q.push(cb) } q = Q[file] = [cb];
+				var p = function Parse(){}, info = {};
+				p.disk = Radix();
+				p.read = function(err, data){ var tmp;
+					delete Q[file];
+					if((p.err = err) || (p.not = !data)){
+						return map(q, p.ack);
+					}
+					if(typeof data !== 'string'){
+						try{
+							if(opt.pack <= data.length){
+								p.err = "Chunk too big!";
+							} else {
+								data = data.toString(); // If it crashes, it crashes here. How!?? We check size first!
+							}
+						}catch(e){ p.err = e }
+						if(p.err){ return map(q, p.ack) }
+					}
+					info.parsed = data.length;
+
+					var start; LOG && (start = (+new Date)); // keep this commented out in production!
+					if(opt.jsonify){ // temporary testing idea
+						try{
+							var json = JSON.parse(data);
+							p.disk.$ = json;
+							LOG && console.log('parsed JSON in', (+new Date) - start); // keep this commented out in production!
+							map(q, p.ack);
+							return;
+						}catch(e){ tmp = e }
+						if('{' === data[0]){
+							p.err = tmp || "JSON error!";
+							return map(q, p.ack);
+						}
+					}
+					var start; LOG && (start = (+new Date)); // keep this commented out in production!
+					var tmp = p.split(data), pre = [], i, k, v, at, ats=[];
+					if(!tmp || 0 !== tmp[1]){
+						p.err = "File '"+file+"' does not have root radix! ";
+						return map(q, p.ack);
+					}
+					while(tmp){
+						k = v = u;
+						i = tmp[1];
+						tmp = p.split(tmp[2])||'';
+						if('#' == tmp[0]){
+							k = tmp[1];
+							pre = pre.slice(0,i);
+							if(i <= pre.length){
+								pre.push(k);
+							}
+						}
+						tmp = p.split(tmp[2])||'';
+						if('\n' == tmp[0]){
+							at = ats[i] || p.disk.at;
+							p.disk(k, u, at);
+							ats[i] = p.disk.at;
+							ats[i+1] = p.disk.at[k] || (p.disk.at[k]={});
+							continue;
+						}
+						if('=' == tmp[0] || ':' == tmp[0]){ v = tmp[1] }
+						if(u !== k && u !== v){
+// 							p.disk(pre.join(''), v)// mark's code
+							at = ats[i];// || p.disk.at;
+							p.disk(k, v, at);
+							ats[i] = p.disk.at;
+							ats[i+1] = p.disk.at[k];
+						}
+						tmp = p.split(tmp[2]);
+					}
+					LOG && console.log('parsed JSON in', (+new Date) - start); // keep this commented out in production!
+					//cb(err, p.disk);
+					map(q, p.ack);
+				};
+				p.split = function(t){
+					if(!t){ return }
+					var l = [], o = {}, i = -1, a = '', b, c;
+					i = t.indexOf(s);
+					if(!t[i]){ return }
+					a = t.slice(0, i);
+					l[0] = a;
+					l[1] = b = Radisk.decode(t.slice(i), o);
+					l[2] = t.slice(i + o.i);
+					return l;
+				}
+				p.ack = function(cb){ 
+					if(!cb){ return }
+					if(p.err || p.not){ return cb(p.err, u, info) }
+					cb(u, p.disk, info);
+				}
+				if(raw){ return p.read(null, raw) }
+				opt.store.get(ename(file), p.read);
+			}
+		}());
+
+		;(function(){
+			var dir, q, f = String.fromCharCode(28), ef = ename(f);
+			r.list = function(cb){
+				if(dir){
+					var tmp = {reverse: (cb.reverse)? 1 : 0};
+					Radix.map(dir, function(val, key){
+						return cb(key);
+					}, tmp) || cb();
+					return;
+				}
+				if(q){ return q.push(cb) } q = [cb];
+				r.parse(f, r.list.init);
+			}
+			r.list.add = function(file, cb){
+				var has = dir(file);
+				if(has || file === ef){
+					return cb(u, 1);
+				}
+				dir(file, true);
+				cb.listed = (cb.listed || 0) + 1;
+				r.write(f, dir, function(err, ok){
+					if(err){ return cb(err) }
+					cb.listed = (cb.listed || 0) - 1;
+					if(cb.listed !== 0){ return }
+					cb(u, 1);
+				}, true);
+			}
+			r.list.init = function(err, disk){
+				if(err){
+					opt.log('list', err);
+					setTimeout(function(){ r.parse(f, r.list.init) }, 1000);
+					return;
+				}
+				if(disk){
+					r.list.drain(disk);
+					return;
+				}
+				if(!opt.store.list){
+					r.list.drain(Radix());
+					return;
+				}
+				// import directory.
+				opt.store.list(function(file){
+					dir = dir || Radix();
+					if(!file){ return r.list.drain(dir) }
+					r.list.add(file, noop);
+				});
+			}
+			r.list.drain = function(rad, tmp){
+				r.list.dir = dir = rad;
+				tmp = q; q = null;
+				Gun.list.map(tmp, function(cb){
+					r.list(cb);
+				});
+			}
+		}());
+
+		var noop = function(){}, RAD, u;
+		Radisk.has[opt.file] = r;
+		return r;
+	}
+
+
+
+	;(function(){
+		var _ = String.fromCharCode(31), u;
+		Radisk.encode = function(d, o, s){ s = s || _;
+			var t = s, tmp;
+			if(typeof d == 'string'){
+				var i = d.indexOf(s);
+				while(i != -1){ t += s; i = d.indexOf(s, i+1) }
+				return t + '"' + d + s;
+			} else
+			if(d && d['#'] && (tmp = Gun.val.link.is(d))){
+				return t + '#' + tmp + t;
+			} else
+			if(Gun.num.is(d)){
+				return t + '+' + (d||0) + t;
+			} else
+			if(null === d){
+				return t + ' ' + t;
+			} else
+			if(true === d){
+				return t + '+' + t;
+			} else
+			if(false === d){
+				return t + '-' + t;
+			}// else
+			//if(binary){}
+		}
+		Radisk.decode = function(t, o, s){ s = s || _;
+			var d = '', i = -1, n = 0, c, p;
+			if(s !== t[0]){ return }
+			while(s === t[++i]){ ++n }
+			p = t[c = n] || true;
+			while(--n >= 0){ i = t.indexOf(s, i+1) }
+			if(i == -1){ i = t.length }
+			d = t.slice(c+1, i);
+			if(o){ o.i = i+1 }
+			if('"' === p){
+				return d;
+			} else
+			if('#' === p){
+				return Gun.val.link.ify(d);
+			} else
+			if('+' === p){
+				if(0 === d.length){
+					return true;
+				}
+				return parseFloat(d);
+			} else
+			if(' ' === p){
+				return null;
+			} else
+			if('-' === p){
+				return false;
+			}
+		}
+	}());
+
+	if(typeof window !== "undefined"){
+	  var Gun = window.Gun;
+	  var Radix = window.Radix;
+	  window.Radisk = Radisk;
+	} else { 
+	  var Gun = require('../gun');
+		var Radix = require('./radix2');
+		try{ module.exports = Radisk }catch(e){}
+	}
+
+	Radisk.Radix = Radix;
+
+}());

+ 10 - 4
public/lib/gundb/lib/radix.js

@@ -54,10 +54,10 @@
 	Radix.map = function map(radix, cb, opt, pre){ pre = pre || [];
 		var t = ('function' == typeof radix)? radix.$ || {} : radix;
 		if(!t){ return }
-		var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort;
+		var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort, rev;
 		//var keys = Object.keys(t).sort();
 		opt = (true === opt)? {branch: true} : (opt || {});
-		if(opt.reverse){ keys = keys.slice().reverse() }
+		if(rev = opt.reverse){ keys = keys.slice().reverse() }
 		var start = opt.start, end = opt.end;
 		var i = 0, l = keys.length;
 		for(;i < l; i++){ var key = keys[i], tree = t[key], tmp, p, pt;
@@ -66,6 +66,10 @@
 			pt = p.join('');
 			if(u !== start && pt < (start||'').slice(0,pt.length)){ continue }
 			if(u !== end && (end || '\uffff') < pt){ continue }
+			if(rev){ // children must be checked first when going in reverse.
+				tmp = map(tree, cb, opt, p);
+				if(u !== tmp){ return tmp }
+			}
 			if(u !== (tmp = tree[''])){
 				tmp = cb(tmp, pt, key, pre);
 				if(u !== tmp){ return tmp }
@@ -75,8 +79,10 @@
 				if(u !== tmp){ return tmp }
 			}
 			pre = p;
-			tmp = map(tree, cb, opt, pre);
-			if(u !== tmp){ return tmp }
+			if(!rev){
+				tmp = map(tree, cb, opt, pre);
+				if(u !== tmp){ return tmp }
+			}
 			pre.pop();
 		}
 	};

+ 98 - 0
public/lib/gundb/lib/radix2.js

@@ -0,0 +1,98 @@
+;(function(){
+
+	function Radix(){
+		var radix = function(key, val, t){
+			key = ''+key;
+			if(!t && u !== val){ 
+				radix.last = (key < radix.last)? radix.last : key;
+				delete (radix.$||{})[_];
+			}
+			t = t || radix.$ || (radix.$ = {});
+			if(!key && Object.keys(t).length){ return t }
+			var i = 0, l = key.length-1, k = key[i], at, tmp;
+			while(!(at = t[k]) && i < l){
+				k += key[++i];
+			}
+			radix.at = t; /// caching to external access.
+			if(!at){
+				if(!map(t, function(r, s){
+					var ii = 0, kk = '';
+					if((s||'').length){ while(s[ii] == key[ii]){
+						kk += s[ii++];
+					} }
+					if(kk){
+						if(u === val){
+							if(ii <= l){ return }
+							return (tmp || (tmp = {}))[s.slice(ii)] = r;
+						}
+						var __ = {};
+						__[s.slice(ii)] = r;
+						ii = key.slice(ii);
+						('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val);
+						t[kk] = __;
+						delete t[s];
+						return true;
+					}
+				})){
+					if(u === val){ return; }
+					(t[k] || (t[k] = {}))[''] = val;
+				}
+				if(u === val){
+					return tmp;
+				}
+			} else 
+			if(i == l){
+				if(u === val){ return (u === (tmp = at['']))? at : tmp }
+				at[''] = val;
+			} else {
+				if(u !== val){ delete at[_] }
+				return radix(key.slice(++i), val, at || (at = {}));
+			}
+		}
+		return radix;
+	};
+
+	Radix.map = function map(radix, cb, opt, pre){ pre = pre || [];
+		var t = ('function' == typeof radix)? radix.$ || {} : radix;
+		if(!t){ return }
+		var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort;
+		//var keys = Object.keys(t).sort();
+		opt = (true === opt)? {branch: true} : (opt || {});
+		if(opt.reverse){ keys = keys.slice().reverse() }
+		var start = opt.start, end = opt.end;
+		var i = 0, l = keys.length;
+		for(;i < l; i++){ var key = keys[i], tree = t[key], tmp, p, pt;
+			if(!tree || '' === key || _ === key){ continue }
+			p = pre.slice(); p.push(key);
+			pt = p.join('');
+			if(u !== start && pt < (start||'').slice(0,pt.length)){ continue }
+			if(u !== end && (end || '\uffff') < pt){ continue }
+			if(u !== (tmp = tree[''])){
+				tmp = cb(tmp, pt, key, pre);
+				if(u !== tmp){ return tmp }
+			} else
+			if(opt.branch){
+				tmp = cb(u, pt, key, pre);
+				if(u !== tmp){ return tmp }
+			}
+			pre = p;
+			tmp = map(tree, cb, opt, pre);
+			if(u !== tmp){ return tmp }
+			pre.pop();
+		}
+	};
+
+	Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) }
+
+	if(typeof window !== "undefined"){
+	  var Gun = window.Gun;
+	  window.Radix = Radix;
+	} else { 
+	  var Gun = require('../gun');
+		try{ module.exports = Radix }catch(e){}
+	}
+	
+	var map = Gun.obj.map, no = {}, u;
+	var _ = String.fromCharCode(24);
+	
+}());

+ 1 - 2
public/lib/gundb/lib/stats.js

@@ -19,7 +19,6 @@ Gun.on('opt', function(root){
 	os.loadavg = os.loadavg || noop;
 	os.cpus = os.cpus || noop;
 	setTimeout(function(){
-		root.opt.file += (process.argv[2]||'');
 		root.stats = Gun.obj.ify((fs.existsSync(__dirname+'/../stats.'+root.opt.file) && fs.readFileSync(__dirname+'/../stats.'+root.opt.file).toString())) || {};
 		root.stats.up = root.stats.up || {};
 		root.stats.up.start = root.stats.up.start || +(new Date);
@@ -56,4 +55,4 @@ Gun.on('opt', function(root){
 		stats.gap = {};
 	}, 1000 * 15);
 	Object.keys = Object.keys || function(o){ return Gun.obj.map(o, function(v,k,t){t(k)}) }
-});
+});

+ 0 - 3
public/lib/gundb/lib/store.js

@@ -67,14 +67,12 @@ Gun.on('create', function(root){
             o.limit = (tmp <= (o.pack || (1000 * 100)))? tmp : 1;
         }
         if(has['-'] || (soul||{})['-']){ o.reverse = true }
-        //console.log("RAD get:", key, o);
         var start = (+new Date); // STATS! // console.log("GET!", id, JSON.stringify(key));
         rad(key||'', function(err, data, o){
             try{opt.store.stats.get.time[statg % 50] = (+new Date) - start; ++statg;
                 opt.store.stats.get.count++;
                 if(err){ opt.store.stats.get.err = err }
             }catch(e){} // STATS!
-            //console.log("RAD gat:", err, data, o);
             if(data){
                 if(typeof data !== 'string'){
                     if(o.atom){
@@ -85,7 +83,6 @@ Gun.on('create', function(root){
                 }
                 if(!graph && data){ each(data, '') }
             }
-            //console.log("GOT!", id, JSON.stringify(key), ((+new Date) - start));
             root.on('in', {'@': id, put: graph, err: err? err : u, rad: Radix});
         }, o);
         function each(val, has, a,b){

+ 12 - 7
public/lib/gundb/lib/then.js

@@ -1,16 +1,21 @@
 var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
 
+// Returns a gun reference in a promise and then calls a callback if specified
 Gun.chain.promise = function(cb) {
   var gun = this, cb = cb || function(ctx) { return ctx };
   return (new Promise(function(res, rej) {
     gun.once(function(data, key){
-    	res({put: data, get: key, gun: this});
+    	res({put: data, get: key, gun: this}); // gun reference is returned by promise
     });
-  })).then(cb);
+  })).then(cb); //calling callback with resolved data
 };
 
-Gun.chain.then = function(cb) {
-	return this.promise(function(res){
-		return cb? cb(res.put) : res.put;
-	});
-};
+// Returns a promise for the data, key of the gun call
+Gun.chain.then = function() {
+	var gun = this;
+  return (new Promise((res, rej)=>{
+    gun.once(function (data, key) {
+      res(data, key); //call resolve when data is returned
+    })
+  }))
+};

+ 1 - 1
public/lib/gundb/sea.js

@@ -591,7 +591,7 @@
       var epriv = pair.epriv;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var pubKeyData = keysToEcdhJwk(pub);
-      var props = Object.assign(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) });
+      var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh); // Thanks to @sirpy !
       var privKeyData = keysToEcdhJwk(epub, epriv);
       var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => {
         // privateKey scope doesn't leak out from here!

+ 2 - 2
public/lib/gundb/sea/secret.js

@@ -13,7 +13,7 @@
       var epriv = pair.epriv;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var pubKeyData = keysToEcdhJwk(pub);
-      var props = Object.assign(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) });
+      var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh);
       var privKeyData = keysToEcdhJwk(epub, epriv);
       var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => {
         // privateKey scope doesn't leak out from here!
@@ -47,4 +47,4 @@
     }
 
     module.exports = SEA.secret;
-  
+  

+ 1 - 1
public/vwf/view/document.js

@@ -58,7 +58,7 @@ define( [ "module", "vwf/view", "vwf/utility"], function( module, view, utility)
               let worldName = path.slice(1);
               let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);
 
-              userDB.get('worlds').get(worldName).get(dbPath).get('file').then(function(res) {
+              userDB.get('worlds').get(worldName).get(dbPath).get('file').once(function(res) {
                    
                    var responseText = "";
                    

Some files were not shown because too many files changed in this diff