Skip to content
Snippets Groups Projects
Commit 5b3b3de1 authored by mcmlxxix's avatar mcmlxxix
Browse files

release locks and subscriptions when client disconnects. send object location...

release locks and subscriptions when client disconnects. send object location and object itself with update packets.
parent 6c4b1eb3
No related branches found
No related tags found
No related merge requests found
load("synchronet-json.js");
/* /*
JSON database - for Synchronet 3.15a+ (2011) JSON database - for Synchronet 3.15a+ (2011)
...@@ -11,17 +10,16 @@ load("synchronet-json.js"); ...@@ -11,17 +10,16 @@ load("synchronet-json.js");
methods: methods:
- Repository.query(client,query); - Database.query(client,query);
- Repository.cycle(Date.now()); - Database.cycle(Date.now());
- Repository.load(); - Database.load();
- Repository.save(Date.now()); - Database.save(Date.now());
- Repository.lock(client,record,child_name,lock_type); - Database.lock(client,record,child_name,lock_type);
- Repository.read(client,record,child_name); - Database.read(client,record,child_name);
- Repository.write(client,record,child_name,data); - Database.write(client,record,child_name,data);
- Repository.create(client,record,child_name,data); - Database.delete(client,record,child_name);
- Repository.delete(client,record,child_name); - Database.subscribe(client,record,child_name);
- Repository.subscribe(client,record,child_name); - Database.unsubscribe(client,record,child_name);
- Repository.unsubscribe(client,record,child_name);
optional arguments: optional arguments:
...@@ -29,11 +27,13 @@ load("synchronet-json.js"); ...@@ -29,11 +27,13 @@ load("synchronet-json.js");
*/ */
if(!this.Repository) { if(!this.Database) {
Repository = new (function() { Database = new (function() {
var settings={ this.VERSION = "$Revision$".split(' ')[1];
this.settings={
/* record lock constants, incremental (do not change the order or value) */ /* record lock constants, incremental (do not change the order or value) */
LOCK_UNLOCK:-1, LOCK_UNLOCK:-1,
...@@ -57,8 +57,8 @@ Repository = new (function() { ...@@ -57,8 +57,8 @@ Repository = new (function() {
ERROR_DUPLICATE_LOCK:5 ERROR_DUPLICATE_LOCK:5
}; };
/**************************** repository objects *****************************/ /**************************** database objects *****************************/
/* locking object generated by Repository.lock() method /* locking object generated by Database.lock() method
contains the type of lock requested, and the time at which the request came in contains the type of lock requested, and the time at which the request came in
TODO: possibly "expire" unfulfilled lock requests after a certain period */ TODO: possibly "expire" unfulfilled lock requests after a certain period */
function Lock(lock_type,timestamp) { function Lock(lock_type,timestamp) {
...@@ -70,29 +70,7 @@ Repository = new (function() { ...@@ -70,29 +70,7 @@ Repository = new (function() {
function Error(error_num,error_desc) { function Error(error_num,error_desc) {
this.operation="ERROR"; this.operation="ERROR";
this.error_num=error_num; this.error_num=error_num;
switch(error_num) { this.error_desc=error_desc;
case settings.ERROR_INVALID_REQUEST:
this.error_desc="Invalid record request";
break;
case settings.ERROR_OBJECT_NOT_FOUND:
this.error_desc="Record not found";
break;
case settings.ERROR_NOT_LOCKED:
this.error_desc="Record not locked";
break;
case settings.ERROR_LOCKED_WRITE:
this.error_desc="Record locked for writing";
break;
case settings.ERROR_LOCKED_READ:
this.error_desc="Record locked for reading";
break;
case settings.ERROR_DUPLICATE_LOCK:
this.error_desc="Duplicate record lock request";
break;
default:
this.error_desc="Unknown error";
break;
}
} }
/* shadow properties generated by composite_sketch() /* shadow properties generated by composite_sketch()
...@@ -107,13 +85,15 @@ Repository = new (function() { ...@@ -107,13 +85,15 @@ Repository = new (function() {
contains the requested object, its shadow object, contains the requested object, its shadow object,
the prevailing lock state of that object (affected by parents/children), the prevailing lock state of that object (affected by parents/children),
and a list of subscribers associated with that object or its parents */ and a list of subscribers associated with that object or its parents */
function Record(data,shadow,info) { function Record(data,shadow,parent_name,child_name,info) {
this.data=data; this.data=data;
this.shadow=shadow; this.shadow=shadow;
this.parent_name=parent_name;
this.child_name=child_name;
this.info=info; this.info=info;
} }
/* request object generated by Repository.queue() method /* request object generated by Database.queue() method
contains the requested object parent, the specific child property requested, contains the requested object parent, the specific child property requested,
data (in the case of a PUT operation ) */ data (in the case of a PUT operation ) */
function Request(client,operation,parent_name,child_name,data) { function Request(client,operation,parent_name,child_name,data) {
...@@ -131,14 +111,12 @@ Repository = new (function() { ...@@ -131,14 +111,12 @@ Repository = new (function() {
it would also allow for a much simpler mechanism for removing it would also allow for a much simpler mechanism for removing
games you no longer wish to host (simply delete the db file and edit your service inis) */ games you no longer wish to host (simply delete the db file and edit your service inis) */
/*************************** repository data ******************************/ /*************************** database data ******************************/
/* database storage file */ /* database storage file */
if(file_exists(argv[0])) { if(argv[0])
this.file=new File(argv[0]); this.file=new File(argv[0]);
} else
else { this.file=undefined;
this.file=new File(system.data_dir + "db.json");
}
/* master database object */ /* master database object */
this.data={}; this.data={};
...@@ -149,48 +127,54 @@ Repository = new (function() { ...@@ -149,48 +127,54 @@ Repository = new (function() {
/* queued array of data requests (get/put/create/delete) */ /* queued array of data requests (get/put/create/delete) */
this.queue=[]; this.queue=[];
/*************************** repository methods ****************************/ /*************************** database methods ****************************/
/* subscribe a client to an object */ /* subscribe a client to an object */
this.subscribe = function(client,record,child_name) { this.subscribe = function(client,record) {
record.shadow[child_name]._subscribers[client.id]=client; record.shadow[record.child_name]._subscribers[client.id]=client;
/* TODO: track duplicate subscribers (deny?) and /* TODO: track duplicate subscribers (deny?) and
expire existing subscribers after certain amount of time, maybe */ expire existing subscribers after certain amount of time, maybe */
return true;
}; };
/* unsubscribe a client from an object */ /* unsubscribe a client from an object */
this.unsubscribe = function(client,record,child_name) { this.unsubscribe = function(client,record) {
delete record.shadow[child_name]._subscribers[client.id]; delete record.shadow[record.child_name]._subscribers[client.id];
/* TODO: validate existing subscription /* TODO: validate existing subscription
before attempting to delete */ before attempting to delete */
return true;
}; };
/* The point of a read lock is *only* to prevent someone from /* The point of a read lock is *only* to prevent someone from
getting a write lock... just a simple counter is usually enough. */ getting a write lock... just a simple counter is usually enough. */
this.lock = function(client,record,child_name,lock_type) { this.lock = function(client,record,lock_type) {
switch(lock_type) { switch(lock_type) {
/* if the client wants to read... */ /* if the client wants to read... */
case settings.LOCK_READ: case this.settings.LOCK_READ:
/* if this is a duplicate lock attempt */
if(record.info.lock[client.id]) {
this.error(client,this.settings.ERROR_DUPLICATE_LOCK);
return true;
}
switch(record.info.lock_type) { switch(record.info.lock_type) {
case settings.LOCK_READ: case this.settings.LOCK_READ:
/* if there are any pending write locks, deny */ /* if there are any pending write locks, deny */
if(record.info.lock_pending) { if(record.info.lock_pending) {
return false; return false;
} }
/* otherwise, we can read lock */ /* otherwise, we can read lock */
else { else {
record.shadow[child_name]._lock[client.id] = new Lock( record.shadow[record.child_name]._lock[client.id] = new Lock(
lock_type, lock_type,
Date.now() Date.now()
); );
return true; return true;
} }
/* we cant lock a record that is already locked for reading */ /* we cant lock a record that is already locked for reading */
case settings.LOCK_WRITE: case this.settings.LOCK_WRITE:
return false; return false;
/* if the record isnt locked at all, we can lock */ /* if the record isnt locked at all, we can lock */
case settings.LOCK_NONE: case this.settings.LOCK_NONE:
record.shadow[child_name]._lock[client.id] = new Lock( record.shadow[record.child_name]._lock[client.id] = new Lock(
lock_type, lock_type,
Date.now() Date.now()
); );
...@@ -198,33 +182,39 @@ Repository = new (function() { ...@@ -198,33 +182,39 @@ Repository = new (function() {
} }
break; break;
/* if the client wants to write... */ /* if the client wants to write... */
case settings.LOCK_WRITE: case this.settings.LOCK_WRITE:
/* if this is a duplicate lock attempt */
if(record.info.lock[client.id]) {
this.error(client,this.settings.ERROR_DUPLICATE_LOCK);
return true;
}
switch(record.info.lock_type) { switch(record.info.lock_type) {
/* ...and the record is already locked, flag for pending write lock */ /* ...and the record is already locked, flag for pending write lock */
case settings.LOCK_READ: case this.settings.LOCK_READ:
case settings.LOCK_WRITE: case this.settings.LOCK_WRITE:
record.shadow[child_name]._lock_pending=true; record.shadow[record.child_name]._lock_pending=true;
return false; return false;
/* ...and the record isnt locked, lock for writing and remove flag */ /* ...and the record isnt locked, lock for writing and remove flag */
case settings.LOCK_NONE: case this.settings.LOCK_NONE:
record.shadow[child_name]._lock[client.id] = new Lock( record.shadow[record.child_name]._lock[client.id] = new Lock(
lock_type, lock_type,
Date.now() Date.now()
); );
record.shadow[child_name]._lock_pending=false; record.shadow[record.child_name]._lock_pending=false;
return true; return true;
} }
break; break;
/* if the client wants to unlock, check credentials */ /* if the client wants to unlock, check credentials */
case settings.LOCK_UNLOCK: case this.settings.LOCK_UNLOCK:
/* if the client has a lock on this record, release the lock */ /* if the client has a lock on this record, release the lock */
if(record.shadow[child_name]._lock[client.id]) { if(record.shadow[record.child_name]._lock[client.id]) {
delete record.shadow[child_name]._lock[client.id]; delete record.shadow[record.child_name]._lock[client.id];
return true; return true;
} }
/* otherwise deny */ /* otherwise deny */
else { else {
return false; this.error(client,this.settings.ERROR_NOT_LOCKED);
return true;
} }
} }
/* fallthrough? */ /* fallthrough? */
...@@ -232,128 +222,141 @@ Repository = new (function() { ...@@ -232,128 +222,141 @@ Repository = new (function() {
}; };
/* server's data retrieval method (not directly called by client) */ /* server's data retrieval method (not directly called by client) */
this.read = function(client,record,child_name) { this.read = function(client,record) {
/* if this client has this record locked, read */ /* if this client has this record locked, read */
if(record.shadow[child_name]._lock[client.id]) { if(record.info.lock[client.id]) {
client.send(record.data[child_name]); var data = clone(record.data[record.child_name]);
send_packet(client,data,"RESPONSE");
return true; return true;
} }
/* if there is no lock for this client, error */ /* if there is no lock for this client, error */
else { else {
client.send(new Error(settings.ERROR_NOT_LOCKED));
return false; return false;
} }
}; };
/* server's data submission method (not directly called by client) */ /* server's data submission method (not directly called by client) */
this.write = function(client,record,child_name,data) { this.write = function(client,record,data) {
/* if this client has this record locked */ /* if this client has this record locked */
if(record.shadow[child_name]._lock[client.id] && if(record.info.lock[client.id] &&
record.shadow[child_name]._lock[client.id].type == settings.LOCK_WRITE) { record.info.lock[client.id].type == this.settings.LOCK_WRITE) {
record.data=data; record.data[record.child_name]=data;
record.shadow=composite_sketch(data); /* populate this object's children with shadow objects */
composite_sketch(record.data[record.child_name],record.shadow[record.child_name]);
/* send data updates to all subscribers */ /* send data updates to all subscribers */
send_updates(record); send_updates(record);
this.updates=true;
return true; return true;
} }
/* if there is no lock for this client, error */ /* if there is no lock for this client, error */
else { else {
client.send(new Error(settings.ERROR_NOT_LOCKED));
return false; return false;
} }
}; };
/* remove a record from the database (requires WRITE_LOCK) */ /* remove a record from the database (requires WRITE_LOCK) */
this.delete = function(client,record,child_name) { this.delete = function(client,record) {
/* if this client has this record locked */ /* if this client has this record locked */
if(record.shadow[child_name]._lock[client.id] && if(record.shadow[record.child_name]._lock[client.id] &&
record.shadow[child_name]._lock[client.id].type == settings.LOCK_WRITE) { record.shadow[record.child_name]._lock[client.id].type == this.settings.LOCK_WRITE) {
delete record.data[child_name]; delete record.data[record.child_name];
delete record.shadow[child_name]; delete record.shadow[record.child_name];
/* send data updates to all subscribers */ /* send data updates to all subscribers */
send_updates(record); send_updates(record);
this.updates=true;
return true; return true;
} }
/* if there is no lock for this client, error */ /* if there is no lock for this client, error */
else { else {
client.send(new Error(settings.ERROR_NOT_LOCKED)); this.error(client,this.settings.ERROR_NOT_LOCKED);
return false; return false;
} }
}; };
/* create a new database record (requires WRITE_LOCK) /* return an object representing a record's overall subscriber and lock status */
TODO: it seems this method may end up doing the exact same thing this.status = function(client,record) {
as the put() method, do we need it? can we lock an object that var sub=[];
doesn't yet exist? does it matter? */ for(var s in record.info.subscribers)
this.create = function(client,record,child_name,data) { sub.push(s);
var data={
/* let parent adopt this new child */ subscribers:sub,
record.data[child_name] = data; lock:record.info.lock_type,
/* create shadow data for this object */ pending:record.info.lock_pending,
record.shadow[child_name] = composite_sketch(data); location:record.parent_name + "." + record.child_name
/* send data updates to all subscribers */ }
send_updates(record); send_packet(client,data,"RESPONSE");
/* TODO: track subscribers and distribute updates */
return true; return true;
}; }
/* generic query handler, will process locks, reads, writes, and unlocks /* generic query handler, will process locks, reads, writes, and unlocks
and put them into the appropriate queues */ and put them into the appropriate queues */
this.query = function(client,query) { this.query = function(client,query) {
/* strip the last child identifier from the string */ /* strip the last child identifier from the string */
var parent_name = query.object_name.substring(0,query.object_name.lastIndexOf(".")); var parent_name = query.location.substring(0,query.location.lastIndexOf("."));
/* store the child name */ /* store the child name */
var child_name = query.object_name.substr(query.object_name.lastIndexOf(".")+1); var child_name = query.location.substr(query.location.lastIndexOf(".")+1);
/* temporary array for queue additions */ /* temporary array for queue additions */
var q=[]; var q=[];
/* if an operation is requested */
if(query.operation !== undefined) {
request = new Request(client,query.operation,parent_name,child_name,query.data);
/* push this query into a queue to be processed at the next response cycle (this.cycle()) */; /* push this query into a queue to be processed at the next response cycle (this.cycle()) */;
q.push(new Request( q.push(request);
client, }
query.operation,
parent_name,
child_name,
query.data
));
/* if there is an attached lock operation, process accordingly */ /* if there is an attached lock operation, process accordingly */
switch(query.lock) { if(query.lock !== undefined) {
request = new Request(client,"LOCK",parent_name,child_name,query.lock);
/* if this is a read or write lock, put it ahead of the operation in the request queue */ /* if this is a read or write lock, put it ahead of the operation in the request queue */
case settings.LOCK_READ: if(query.lock == this.settings.LOCK_UNLOCK)
case settings.LOCK_WRITE: q.unshift(request);
q.unshift(new Request(
client,
"LOCK",
parent_name,
child_name,
query.lock
));
break;
/* if this is an unlock, process it after the operation in the request queue */ /* if this is an unlock, process it after the operation in the request queue */
case settings.LOCK_UNLOCK: else if(query.lock == this.settings.LOCK_READ || query.lock == this.settings.LOCK_WRITE)
q.push(new Request( q.push(request);
client,
"LOCK",
parent_name,
child_name,
query.lock
));
break;
/* if no lock has been specified, do nothing */
case settings.LOCK_NONE:
break;
} }
/* add the temporary queue to the main queue */ /* add the temporary queue to the main queue */
this.queue=this.queue.concat(q); this.queue=this.queue.concat(q);
} }
/* generate an error object and send it to client */
this.error = function(client,error_num) {
var error_desc="Unknown error";
switch(error_num) {
case this.settings.ERROR_INVALID_REQUEST:
error_desc="Invalid record request";
break;
case this.settings.ERROR_OBJECT_NOT_FOUND:
error_desc="Record not found";
break;
case this.settings.ERROR_NOT_LOCKED:
error_desc="Record not locked";
break;
case this.settings.ERROR_LOCKED_WRITE:
error_desc="Record locked for writing";
break;
case this.settings.ERROR_LOCKED_READ:
error_desc="Record locked for reading";
break;
case this.settings.ERROR_DUPLICATE_LOCK:
error_desc="Duplicate record lock request";
break;
}
var error=new Error(error_num,error_desc);
send_packet(client,error,"ERROR");
}
/* internal periodic data storage method /* internal periodic data storage method
TODO: this should probably eventually be a background TODO: this should probably eventually be a background
thread to prevent lag when writing a large database to file */ thread to prevent lag when writing a large database to file */
this.save = function(timestamp) { this.save = function(timestamp) {
/* strip _location tags from data before saving */ if(!this.file)
dismember(this.data); return;
if(!timestamp)
timestamp = Date.now();
/* if we are due for a data update, save everything to file */
if(timestamp - this.settings.LAST_SAVE >= this.settings.AUTO_SAVE
&& this.updates) {
//TODO: create n backups before overwriting data file //TODO: create n backups before overwriting data file
this.file.open("w"); this.file.open("w");
// This function gets called every 30 seconds or so // This function gets called every 30 seconds or so
...@@ -361,34 +364,46 @@ Repository = new (function() { ...@@ -361,34 +364,46 @@ Repository = new (function() {
// Also, this is called on clean exit. // Also, this is called on clean exit.
this.file.write(JSON.stringify(this.data)); this.file.write(JSON.stringify(this.data));
this.file.close(); this.file.close();
this.settings.LAST_SAVE=timestamp;
settings.LAST_SAVE=timestamp; }
}; };
/* data initialization (likely happens only once) */ /* data initialization (likely happens only once) */
this.load = function() { this.load = function() {
if(!this.file)
return;
if(file_exists(this.file.name)) {
this.file.open("r"); this.file.open("r");
this.data=JSON.parse(this.file.readAll(settings.FILE_BUFFER).join('\n')); this.data = JSON.parse(
this.file.readAll(this.settings.FILE_BUFFER).join('\n')
);
this.file.close(); this.file.close();
}
}; };
/* create a copy of data object keys and give them /* create a copy of data object keys and give them
locking properties */ locking properties */
this.init = function() { this.init = function() {
this.load();
this.shadow=composite_sketch(this.data); this.shadow=composite_sketch(this.data);
/* initialize autosave timer */ /* initialize autosave timer */
settings.LAST_SAVE = Date.now(); this.settings.LAST_SAVE = Date.now();
log(LOG_INFO,"JSON database initialized (v" + this.VERSION + ")");
}
/* release any locks or subscriptions held by a disconnected client */
this.release = function(client_id) {
free_prisoner(client_id,this.shadow);
for(var c=0;c<this.queue.length;c++) {
if(this.queue[c].client.id == client_id)
this.queue.splice(c--,1);
}
} }
/* main "loop" called by server */ /* main "loop" called by server */
this.cycle = function(timestamp) { this.cycle = function(timestamp) {
this.save();
if(!timestamp)
timestamp = Date.now();
/* if we are due for a data update, save everything to file */
if(timestamp - settings.LAST_SAVE >= settings.AUTO_SAVE)
this.save(timestamp);
/* process request queue, removing successful operations */ /* process request queue, removing successful operations */
for(var r=0;r<this.queue.length;r++) { for(var r=0;r<this.queue.length;r++) {
...@@ -396,128 +411,157 @@ Repository = new (function() { ...@@ -396,128 +411,157 @@ Repository = new (function() {
var result=false; var result=false;
/* locate the requested record within the database */ /* locate the requested record within the database */
var record=identify_remains.call(this,request.client,request.parent_name); var record=identify_remains.call(this,request.client,request.parent_name,request.child_name);
if(!record) {
log(LOG_DEBUG,"db: bad request removed from queue");
this.queue.splice(r--,1);
}
switch(request.operation.toUpperCase()) { switch(request.operation.toUpperCase()) {
case "READ": case "READ":
result=this.read(request.client,record,request.child_name); result=this.read(request.client,record);
break; break;
case "WRITE": case "WRITE":
result=this.write(request.client,record,request.child_name,request.data); result=this.write(request.client,record,request.data);
break; break;
case "DELETE": case "DELETE":
result=this.delete(request.client,record,request.child_name); result=this.delete(request.client,record);
break; break;
case "CREATE": case "CREATE":
result=this.create(request.client,record,request.child_name,request.data); result=this.create(request.client,record,request.data);
break; break;
case "SUBSCRIBE": case "SUBSCRIBE":
result=this.subscribe(request.client,record,request.child_name); result=this.subscribe(request.client,record);
break; break;
case "UNSUBSCRIBE": case "UNSUBSCRIBE":
result=this.unsubscribe(request.client,record,request.child_name); result=this.unsubscribe(request.client,record);
break; break;
case "LOCK": case "LOCK":
result=this.lock(request.client,record,request.child_name,request.data); result=this.lock(request.client,record,request.data);
break;
case "STATUS":
result=this.status(request.client,record);
break; break;
} }
if(result == true) { if(result == true) {
log(LOG_DEBUG, log(LOG_DEBUG,"db: " +
"done: " +
request.operation + " " + request.operation + " " +
request.parent_name + "." + request.parent_name + "." +
request.child_name request.child_name + " OK"
); );
this.queue.splice(r--,1); this.queue.splice(r--,1);
} }
else { else {
log(LOG_DEBUG, log(LOG_DEBUG,"db: " +
"fail: " +
request.operation + " " + request.operation + " " +
request.parent_name + "." + request.parent_name + "." +
request.child_name request.child_name + " FAILED"
); );
} }
} }
}; };
/*************************** repository functions ****************************/ /*************************** database functions ****************************/
/* traverse object and create a shadow copy of the object structure /* traverse object and create a shadow copy of the object structure
for record locking and subscribers, and create hash names for database objects */ for record locking and subscribers, and create location names for database objects */
function composite_sketch(obj,name,shadow,location) { function composite_sketch(obj,shadow) {
/* generate serialized location (maybe this can be removed )*/
if(!location)
location=name;
else
location+="."+name;
/* create shadow object */ /* create shadow object */
if(!shadow)
shadow=new Shadow(); shadow=new Shadow();
/* iterate object members */ /* iterate object members */
for(var p in obj) { for(var p in obj) {
shadow[p]=composite_sketch(obj[p],p,shadow[p],location); if(typeof obj[p] == "object")
shadow[p]=composite_sketch(obj[p],shadow[p]);
} }
/* assign generated location id to this object */
obj._location=location;
/* returns an object containing the passed objects property keys /* returns an object containing the passed objects property keys
with their own lock, subscribers, and name properties with their own lock, subscribers, and name properties
also adds a location property to keys of original object */ also adds a location property to keys of original object */
return shadow;
}
/* traverse object and remove location (hash name) tags */ return shadow;
function dismember(obj) {
delete obj._location;
for(var p in obj) {
dismember(obj[p]);
}
} }
/* parse an object hash name and return the object (ex: dicewarz2.games.1.players.1.tiles.0) /* 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 */ an object containing the corresponding data and its shadow object */
function identify_remains(client,object_name) { function identify_remains(client,parent_name,child_name) {
var p=object_name.split(/\./); var p=parent_name.split(/\./);
/* if the data request is invalid or empty, return an empty object */ /* if the data request is invalid or empty, return an error */
if(!p) { if(!p) {
// Error! this.error(client,this.settings.ERROR_INVALID_REQUEST);
client.send(new Error(settings.ERROR_INVALID_REQUEST));
return false; return false;
} }
var data=this.data; var data=this.data;
var shadow=this.shadow; var shadow=this.shadow;
var info={ var info={
lock_type:settings.LOCK_NONE, lock:[],
lock_pending:false lock_type:this.settings.LOCK_NONE,
lock_pending:false,
subscribers:[]
} }
/* iterate through split object name checking the keys against the database and /* iterate through split object name checking the keys against the database and
checking the lock statuses against the shadow copy */ checking the lock statuses against the shadow copy */
for each(var c in p) { for each(var c in p) {
verify(data,shadow,c);
/* keep track of current object, and store the immediate parent of the request object */
data=data[c]; data=data[c];
shadow=shadow[c]; shadow=shadow[c];
/* if this key doesnt exist in the database, return error */
if(!data || !shadow) {
client.send(new Error(settings.ERROR_OBJECT_NOT_FOUND));
return false;
}
/* check the current object's lock and subscriber status along the way */ /* check the current object's lock and subscriber status along the way */
info = investigate(shadow,info); info = investigate(shadow,info);
} }
verify(data,shadow,child_name);
/* continue on through the selected shadow object's children to check for locked children */ /* continue on through the selected shadow object's children to check for locked children */
info = search_party(data,shadow,info); info = search_party(data[child_name],shadow[child_name],info);
/* return selected database object, shadow object, and overall lock status of the chosen tree */ /* return selected database object, shadow object, and overall lock status of the chosen tree */
return new Record(data,shadow,info); return new Record(data,shadow,parent_name,child_name,info);
}
/* if the requested child object does not exist, create it */
function verify(data,shadow,child_name) {
if(!data[child_name]) {
log(LOG_DEBUG,"creating new data: " + child_name);
data[child_name]={};
}
if(!shadow[child_name]) {
log(LOG_DEBUG,"creating new shadow: " + child_name);
shadow[child_name]=new Shadow();
}
}
/* release subscriptions and locks on an object recursively */
function free_prisoner(client_id,shadow) {
if(shadow._lock) {
if(shadow._lock[client_id]) {
log(LOG_DEBUG,"releasing lock: " + client_id);
delete shadow._lock[client_id];
}
}
if(shadow._subscribers) {
if(shadow._subscribers[client_id]) {
log(LOG_DEBUG,"releasing subscriber: " + client_id);
delete shadow._subscribers[client_id];
}
}
for(var s in shadow) {
if(typeof shadow[s] == "object")
free_prisoner(client_id,shadow[s]);
}
}
/* make a copy of an object to avoid modifying the original */
function clone(obj) {
var newobj=eval(obj.toSource());
return newobj;
} }
/* return the prevailing lock type and pending lock status for an object */ /* return the prevailing lock type and pending lock status for an object */
...@@ -526,7 +570,7 @@ Repository = new (function() { ...@@ -526,7 +570,7 @@ Repository = new (function() {
if(info.lock_type == undefined) { if(info.lock_type == undefined) {
for(var l in shadow._lock) { for(var l in shadow._lock) {
info.lock_type = shadow._lock[l].type; info.lock_type = shadow._lock[l].type;
break; info.lock[l] = shadow._lock[l];
} }
} }
for(var s in shadow._subscribers) { for(var s in shadow._subscribers) {
...@@ -552,12 +596,24 @@ Repository = new (function() { ...@@ -552,12 +596,24 @@ Repository = new (function() {
/* send updates of this object to all subscribers */ /* send updates of this object to all subscribers */
function send_updates(record) { function send_updates(record) {
for each(var c in record.info.subscribers) { for each(var c in record.info.subscribers) {
c.send(JSON.stringify(record.data)+"\r\n"); var data = {
location:record.parent_name + "." + record.child_name,
data:record.data[record.child_name]
};
send_packet(c,data,"UPDATE");
}
} }
/* send data packet to a client */
function send_packet(client,object,func) {
var data={
func:func,
data:object
};
client.sendJSON(data);
} }
/* constructor */ /* constructor */
this.load();
this.init(); this.init();
})(); })();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment