From 6f5fc9616f566220a98fc4ad22a55be54104ef0e Mon Sep 17 00:00:00 2001
From: mcmlxxix <>
Date: Fri, 21 Oct 2011 18:18:32 +0000
Subject: [PATCH] non-existant record reads return undefined (and do not create
 the record in the process), methods updated, initialize _locks and
 _subscriptions as {} instead of [], a myriad of miscellaneous varieties of
 other assorted sporadic changes.

---
 exec/load/json-db.js | 226 +++++++++++++++++++++++++------------------
 1 file changed, 132 insertions(+), 94 deletions(-)

diff --git a/exec/load/json-db.js b/exec/load/json-db.js
index a725aa9a73..7ba2144c2a 100644
--- a/exec/load/json-db.js
+++ b/exec/load/json-db.js
@@ -23,7 +23,7 @@
 	-	Database.write(client,record,child_name,data);
 	-	Database.push(client,record,child_name,data);
 	-	Database.unshift(client,record,child_name,data);
-	-	Database.delete(client,record,child_name);
+	-	Database.remove(client,record,child_name);
 	-	Database.subscribe(client,record,child_name);
 	-	Database.unsubscribe(client,record,child_name);
 	
@@ -56,18 +56,9 @@ function JSONdb (fileName) {
 	
 	/* database settings */
 	this.settings={
-
-		/* record lock constants, incremental (do not change the order or value)  */
-		LOCK_UNLOCK:-1,
-		LOCK_NONE:undefined,
-		LOCK_READ:1,
-		LOCK_WRITE:2,
-		
-		/* file read buffer */
-		FILE_BUFFER:524288,
-		
 		/* misc settings */
 		FILE:system.data_dir + "json-db.ini",
+		FILE_BUFFER:524288,
 		LAST_SAVE:-1,
 		SAVE_INTERVAL:-1,
 		KEEP_READABLE:false,
@@ -75,8 +66,16 @@ function JSONdb (fileName) {
 		UPDATES:false,
 	};
 	
+	/* lock constants */
+	var locks = {
+		UNLOCK:-1,
+		NONE:undefined,
+		READ:1,
+		WRITE:2,
+	}
+	
 	/* error constants */
-	this.errors = {
+	var errors = {
 		INVALID_REQUEST:0,
 		OBJECT_NOT_FOUND:1,
 		NOT_LOCKED:2,
@@ -101,7 +100,7 @@ function JSONdb (fileName) {
 			send_subscriber_updates(client,record,"SUBSCRIBE");
 		}
 		else {
-			this.error(client,this.errors.DUPLICATE_SUB);
+			this.error(client,errors.DUPLICATE_SUB);
 		}
 		return true;
     };
@@ -116,7 +115,7 @@ function JSONdb (fileName) {
 			send_subscriber_updates(client,record,"UNSUBSCRIBE");
 		}
 		else {
-			this.error(client,this.errors.INVALID_REQUEST);
+			this.error(client,errors.INVALID_REQUEST);
 		}
 		return true;
     };
@@ -126,14 +125,14 @@ function JSONdb (fileName) {
     this.lock = function(client,record,lock_type) {
 		switch(lock_type) {
 		/* if the client wants to read... */
-		case this.settings.LOCK_READ:
+		case locks.READ:
 			/* if this is a duplicate lock attempt */
 			if(record.info.lock[client.id]) {
-				this.error(client,this.errors.DUPLICATE_LOCK);
+				this.error(client,errors.DUPLICATE_LOCK);
 				return true;
 			}
 			switch(record.info.lock_type) {
-			case this.settings.LOCK_READ:
+			case locks.READ:
 				/* if there are any pending write locks, deny */
 				if(record.info.lock_pending) {
 					return false;
@@ -147,10 +146,10 @@ function JSONdb (fileName) {
 					return true;
 				}
 			/* we cant lock a record that is already locked for reading */
-			case this.settings.LOCK_WRITE:
+			case locks.WRITE:
 				return false;
 			/* if the record isnt locked at all, we can lock */
-			case this.settings.LOCK_NONE:
+			case locks.NONE:
 				record.shadow[record.child_name]._lock[client.id] = new Lock(
 					lock_type,
 					Date.now()
@@ -159,25 +158,25 @@ function JSONdb (fileName) {
 			}
 			break;
 		/* if the client wants to write... */
-		case this.settings.LOCK_WRITE:
+		case locks.WRITE:
 			/* if this db is read-only */
 			if(this.settings.READ_ONLY) {
-				this.error(client,this.errors.READ_ONLY);
+				this.error(client,errors.READ_ONLY);
 				return true;
 			}
 			/* if this is a duplicate lock attempt */
 			if(record.info.lock[client.id]) {
-				this.error(client,this.errors.DUPLICATE_LOCK);
+				this.error(client,errors.DUPLICATE_LOCK);
 				return true;
 			}
 			switch(record.info.lock_type) {
 			/* ...and the record is already locked, flag for pending write lock */
-			case this.settings.LOCK_READ:
-			case this.settings.LOCK_WRITE:
+			case locks.READ:
+			case locks.WRITE:
 				record.shadow[record.child_name]._lock_pending=true;
 				return false;
 			/* ...and the record isnt locked, lock for writing and remove flag */
-			case this.settings.LOCK_NONE:
+			case locks.NONE:
 				record.shadow[record.child_name]._lock[client.id] = new Lock(
 					lock_type,
 					Date.now()
@@ -187,12 +186,12 @@ function JSONdb (fileName) {
 			}
 			break;
 		/* if the client wants to unlock, check credentials */
-		case this.settings.LOCK_UNLOCK:
+		case locks.UNLOCK:
 			var client_lock=record.shadow[record.child_name]._lock[client.id];
 			/* if the client has a lock on this record, release the lock */
 			if(client_lock) {
 				/* if this was a write lock, send an update to all record subscribers */
-				if(client_lock.type == this.settings.LOCK_WRITE) {
+				if(client_lock.type == locks.WRITE) {
 					this.settings.UPDATES=true;
 					send_data_updates(client,record);
 				}
@@ -201,7 +200,7 @@ function JSONdb (fileName) {
 			}
 			/* otherwise deny */
 			else {
-				this.error(client,this.errors.NOT_LOCKED);
+				this.error(client,errors.NOT_LOCKED);
 				return true;
 			}
 		}
@@ -211,8 +210,13 @@ function JSONdb (fileName) {
     
     /* server's data retrieval method (not directly called by client) */    
     this.read = function(client,record) {
+		/* if the requested data does not exist, result is undefined */
+		if(record.data === undefined) {
+            send_packet(client,undefined,"RESPONSE");
+			return true;
+		}
 		/* if this client has this record locked, read */
-		if(record.info.lock[client.id]) {
+		else if(record.info.lock[client.id]) {
             send_packet(client,record.data[record.child_name],"RESPONSE");
 			return true;
 		}
@@ -224,13 +228,18 @@ function JSONdb (fileName) {
     
 	/* pop a record off the end of an array */
 	this.pop = function(client,record) {
+		/* if the requested data does not exist, result is undefined */
+		if(record.data === undefined) {
+            send_packet(client,undefined,"RESPONSE");
+			return true;
+		}
 		/* if this client has this record locked  */
-		if(record.info.lock[client.id] && 
-		record.info.lock[client.id].type == this.settings.LOCK_WRITE) {
+		else if(record.info.lock[client.id] && 
+			record.info.lock[client.id].type == locks.WRITE) {
 			if(typeof record.data[record.child_name].pop == "function") 
 				send_packet(client,record.data[record.child_name].pop(),"RESPONSE");
 			else
-				this.error(client,this.errors.NON_ARRAY);
+				this.error(client,errors.NON_ARRAY);
 			return true;
 		}
         /* if there is no lock for this client, error */
@@ -241,13 +250,18 @@ function JSONdb (fileName) {
 	
 	/* shift a record off the end of an array */
 	this.shift = function(client,record) {
+		/* if the requested data does not exist, result is undefined */
+		if(record.data === undefined) {
+            send_packet(client,undefined,"RESPONSE");
+			return true;
+		}
 		/* if this client has this record locked  */
-		if(record.info.lock[client.id] && 
-		record.info.lock[client.id].type == this.settings.LOCK_WRITE) {
+		else if(record.info.lock[client.id] && 
+		record.info.lock[client.id].type == locks.WRITE) {
 			if(typeof record.data[record.child_name].shift == "function") 
 				send_packet(client,record.data[record.child_name].shift(),"RESPONSE");
 			else
-				this.error(client,this.errors.NON_ARRAY);
+				this.error(client,errors.NON_ARRAY);
 			return true;
 		}
         /* if there is no lock for this client, error */
@@ -258,9 +272,14 @@ function JSONdb (fileName) {
 
 	/* push a record onto the end of an array */
 	this.push = function(client,record,data) {
+		/* if the requested data does not exist, result is undefined */
+		if(record.data === undefined) {
+			this.error(client,errors.OBJECT_NOT_FOUND);
+			return true;
+		}
 		/* if this client has this record locked  */
-		if(record.info.lock[client.id] && 
-		record.info.lock[client.id].type == this.settings.LOCK_WRITE) {
+		else if(record.info.lock[client.id] && 
+			record.info.lock[client.id].type == locks.WRITE) {
 			if(typeof record.data[record.child_name].pop == "function") {
 				var index = record.data[record.child_name].length;
 				record.data[record.child_name].push(data);
@@ -268,7 +287,7 @@ function JSONdb (fileName) {
 				composite_sketch(record.data[record.child_name][index],record.shadow[record.child_name][index]);
 			}
 			else
-				this.error(client,this.errors.NON_ARRAY);
+				this.error(client,errors.NON_ARRAY);
 			return true;
 		}
         /* if there is no lock for this client, error */
@@ -279,9 +298,14 @@ function JSONdb (fileName) {
 
 	/* push a record onto the end of an array */
 	this.unshift = function(client,record,data) {
+		/* if the requested data does not exist, result is undefined */
+		if(record.data === undefined) {
+			this.error(client,errors.OBJECT_NOT_FOUND);
+			return true;
+		}
 		/* if this client has this record locked  */
-		if(record.info.lock[client.id] && 
-		record.info.lock[client.id].type == this.settings.LOCK_WRITE) {
+		else if(record.info.lock[client.id] && 
+			record.info.lock[client.id].type == locks.WRITE) {
 			if(typeof record.data[record.child_name].unshift == "function") {
 				var index = record.data[record.child_name].length;
 				record.data[record.child_name].unshift(data);
@@ -289,7 +313,7 @@ function JSONdb (fileName) {
 				composite_sketch(record.data[record.child_name][index],record.shadow[record.child_name][index]);
 			}
 			else
-				this.error(client,this.errors.NON_ARRAY);
+				this.error(client,errors.NON_ARRAY);
 			return true;
 		}
         /* if there is no lock for this client, error */
@@ -303,7 +327,7 @@ function JSONdb (fileName) {
 	
 		/* if this client has this record locked  */
 		if(record.info.lock[client.id] && 
-		record.info.lock[client.id].type == this.settings.LOCK_WRITE) {
+		record.info.lock[client.id].type == locks.WRITE) {
 			record.data[record.child_name]=data;
 			/* populate this object's children with shadow objects */
 			composite_sketch(record.data[record.child_name],record.shadow[record.child_name]);
@@ -317,10 +341,13 @@ function JSONdb (fileName) {
     };
     
     /* remove a record from the database (requires WRITE_LOCK) */
-    this.delete = function(client,record) {
+    this.remove = function(client,record) {
+		/* if the requested data does not exist, do nothing */
+		if(record.data === undefined) 
+			return true;
 		/* if this client has this record locked */
-		if(record.shadow[record.child_name]._lock[client.id] && 
-		record.shadow[record.child_name]._lock[client.id].type == this.settings.LOCK_WRITE) {
+		else if(record.shadow[record.child_name]._lock[client.id] && 
+		record.shadow[record.child_name]._lock[client.id].type == locks.WRITE) {
 			delete record.data[record.child_name];
 			delete record.shadow[record.child_name];
 			/* send data updates to all subscribers */
@@ -328,7 +355,7 @@ function JSONdb (fileName) {
 		}
         /* if there is no lock for this client, error */
         else {
-			this.error(client,this.errors.NOT_LOCKED);
+			this.error(client,errors.NOT_LOCKED);
             return false;
         }
     };
@@ -365,7 +392,7 @@ function JSONdb (fileName) {
 		
 		/* if the data request is invalid or empty, return an error */
 		if(!child_name) {
-			this.error(client,this.errors.INVALID_REQUEST);
+			this.error(client,errors.INVALID_REQUEST);
 			return false;
 		}
 
@@ -397,7 +424,7 @@ function JSONdb (fileName) {
 			));
 			/* put unlock after the operation in the request queue */
 			q.push(new Request(
-				client,"LOCK",parent_name,child_name,this.settings.LOCK_UNLOCK
+				client,"LOCK",parent_name,child_name,locks.UNLOCK
 			));
 		}
 		
@@ -409,28 +436,28 @@ function JSONdb (fileName) {
 	this.error = function(client,error_num) {
 		var error_desc="Unknown error";
 		switch(error_num) {
-		case this.errors.INVALID_REQUEST:
+		case errors.INVALID_REQUEST:
 			error_desc="Invalid record request";
 			break;
-		case this.errors.OBJECT_NOT_FOUND:
+		case errors.OBJECT_NOT_FOUND:
 			error_desc="Record not found";
 			break;
-		case this.errors.NOT_LOCKED:
+		case errors.NOT_LOCKED:
 			error_desc="Record not locked";
 			break;
-		case this.errors.LOCKED_WRITE:
+		case errors.LOCKED_WRITE:
 			error_desc="Record locked for writing";
 			break;
-		case this.errors.LOCKED_READ:
+		case errors.LOCKED_READ:
 			error_desc="Record locked for reading";
 			break;
-		case this.errors.DUPLICATE_LOCK:
+		case errors.DUPLICATE_LOCK:
 			error_desc="Duplicate record lock request";
 			break;
-		case this.errors.DUPLICATE_SUBSCRIPTION:
+		case errors.DUPLICATE_SUBSCRIPTION:
 			error_desc="Duplicate record subscription request";
 			break;
-		case this.errors.NON_ARRAY:
+		case errors.NON_ARRAY:
 			error_desc="Record is not an array";
 			break;
 		case this.settings.READ_ONLY:
@@ -516,10 +543,10 @@ function JSONdb (fileName) {
 		for(var r=0;r<this.queue.length;r++) {
 			var request=this.queue[r];
 			var result=false;
-			
 			/* locate the requested record within the database */
 			var record=identify_remains.call(
-				this,request.client,request.parent_name,request.child_name
+				this,request.client,request.parent_name,request.child_name,
+				request.oper.toUpperCase() == "WRITE"
 			);
 			
 			if(!record) {
@@ -548,7 +575,7 @@ function JSONdb (fileName) {
 				result=this.unshift(request.client,record,request.data);
 				break;
 			case "DELETE":
-				result=this.delete(request.client,record);
+				result=this.remove(request.client,record);
 				break;
 			case "CREATE":
 				result=this.create(request.client,record,request.data);
@@ -612,9 +639,9 @@ function JSONdb (fileName) {
 	/* shadow properties generated by composite_sketch() 
 	contains a relative object's subscribers, locks, and child name */
 	function Shadow() {
-		this._lock=[];
+		this._lock={};
 		this._lock_pending=false;
-		this._subscribers=[];
+		this._subscribers={};
 	}
 
 	/* record object returned by identify_remains() 
@@ -663,57 +690,61 @@ function JSONdb (fileName) {
 
 	/* parse an object location name and return the object (ex: dicewarz2.games.1.players.1.tiles.0)
 	an object containing the corresponding data and its shadow object */
-	function identify_remains(client,parent_name,child_name) {
+	function identify_remains(client,parent_name,child_name,create_new) {
 
 		var data=this.data;
 		var shadow=this.shadow;
 		var location=child_name;
 		var info={
-			lock:[],
-			lock_type:this.settings.LOCK_NONE,
+			lock:{},
+			lock_type:locks.NONE,
 			lock_pending:false,
-			subscribers:[]
-		}
+			subscribers:{}
+		};
 
 		if(parent_name !== undefined) {
 			/* iterate through split object name checking the keys against the database and 
 			checking the lock statuses against the shadow copy */
 			var p=parent_name.split(/\./);
 			for each(var c in p) {
-				
-				verify(data,shadow,c);
-
+				/* in the event of a write request, create new data if it does not exist*/
+				if(data[c] === undefined && create_new) 
+					create_data(data,c);
+				/* ensure that the shadow object exists in order to allow for non-read operations */
+				if(shadow[c] === undefined) 
+					create_shadow(data,c);
 				/* keep track of current object, and store the immediate parent of the request object */
-				data=data[c];
+				if(data !== undefined)
+					data=data[c];
 				shadow=shadow[c];
-
 				/* check the current object's lock and subscriber status along the way */
 				info = investigate(shadow,info);
 			}
 			location = parent_name + "." + child_name;
 		}
 		
-		/* ensure requested object's existance */
-		verify(data,shadow,child_name);
+		/* ensure requested shadow object's existance */
+		if(shadow[child_name] === undefined) 
+			create_shadow(shadow,child_name);
 			
 		/* continue on through the selected shadow object's children to check for locked children */
-		info = search_party(data[child_name],shadow[child_name],info);
+		info = search_party(shadow[child_name],info);
 		
 		/* return selected database object, shadow object, and overall lock status of the chosen tree */
 		return new Record(data,shadow,location,child_name,info);
 	}
 	
 	/* if the requested child object does not exist, create it */
-	function verify(data,shadow,child_name) {
-		if(data[child_name] === undefined) {
-			log(LOG_DEBUG,"creating new data: " + child_name);
-			data[child_name]={};
-		}
-		if(shadow[child_name] === undefined) {
-			log(LOG_DEBUG,"creating new shadow: " + child_name);
-			shadow[child_name]=new Shadow();
-		}
-	 }
+	function create_shadow(shadow,child_name) {
+		log(LOG_DEBUG,"creating new shadow: " + child_name);
+		shadow[child_name]=new Shadow();
+	}
+	
+	/* if the requested child object does not exist, create it */
+	function create_data(data,child_name) {
+		log(LOG_DEBUG,"creating new data: " + child_name);
+		data[child_name]={};
+	}
 	
 	/* release locks on an object recursively */
 	function free_prisoners(client,shadow) {
@@ -731,8 +762,8 @@ function JSONdb (fileName) {
 	function cancel_subscriptions(client,records) {
 		for(var r in records) {
 			var record = records[r];
-			log(LOG_DEBUG,"releasing subscription: " + client.id);
-			delete record.shadow._subscribers[client.id];
+			log(LOG_DEBUG,"releasing subscription: " + record.location);
+			delete record.shadow[record.child_name]._subscribers[client.id];
 			send_subscriber_updates(client,record,"UNSUBSCRIBE");
 		}
 	}
@@ -754,15 +785,14 @@ function JSONdb (fileName) {
 	}
 
 	/* recursively search object for any existing locked children, 
-	return highest lock level (LOCK_WRITE > LOCK_READ > LOCK_NONE)  */
-	function search_party(data,shadow,info) {
-		if(!shadow)
+	return highest lock level (WRITE > READ > NONE)  */
+	function search_party(shadow,info) {
+		if(shadow == undefined)
 			return info;
-			
 		info = investigate(shadow,info);
-		for(var i in data) {
-			info = search_party(data[i],shadow[i],info);
-		}
+		for each(var i in shadow) 
+			if(i instanceof Shadow)
+				info = search_party(i,info);
 		return info;
 	}
 
@@ -774,12 +804,19 @@ function JSONdb (fileName) {
 				continue;
 			var data = {
 				location:record.location,
-				data:record.data[record.child_name]
+				data:get_record_data(record)
 			};
 			send_packet(c,data,"UPDATE");
 		}
 	}
 	
+	/* retrieve record data, if present */
+	function get_record_data(record) {
+		if(record.data === undefined)
+			return undefined;
+		return record.data[record.child_name];
+	}
+	
 	/* send update of client subscription to all subscribers */
 	function send_subscriber_updates(client,record,oper) {
 		var data = {
@@ -795,6 +832,7 @@ function JSONdb (fileName) {
 		}
 	}
 	
+	/* retrieve client nickname and system name, if present, from client socket */
 	function get_client_info(client) {
 		return {
 			id:client.id,
-- 
GitLab