Compare commits
14 Commits
Author | SHA1 | Date |
---|---|---|
Dan Ballard | 15b9abbc4a | |
Dan Ballard | 18d640089e | |
Dan Ballard | 05e650c541 | |
Dan Ballard | 5c5d71e064 | |
Dan Ballard | a3ccd10c8f | |
Dan Ballard | a5b83691e6 | |
Dan Ballard | 9153c0896b | |
Dan Ballard | f84a926317 | |
Dan Ballard | 14f98cba53 | |
Dan Ballard | 922ab08c86 | |
Dan Ballard | 872929e730 | |
Dan Ballard | 27c3dfc692 | |
Dan Ballard | fa74d77158 | |
Dan Ballard | 1123dc8d0e |
|
@ -340,7 +340,7 @@ class ConnHandler implements Runnable {
|
||||||
userAgent = header;
|
userAgent = header;
|
||||||
|
|
||||||
}
|
}
|
||||||
System.out.println(i + ": '" + header + "'");
|
//System.out.println(i + ": '" + header + "'");
|
||||||
i++;
|
i++;
|
||||||
header = sin.readLine();
|
header = sin.readLine();
|
||||||
}
|
}
|
||||||
|
@ -353,6 +353,7 @@ class ConnHandler implements Runnable {
|
||||||
byte[] get_resp= null;
|
byte[] get_resp= null;
|
||||||
String CODE = "200 OK";
|
String CODE = "200 OK";
|
||||||
int resp_length = 0;
|
int resp_length = 0;
|
||||||
|
String content_type = "text/html";
|
||||||
if (type == TYPE_GET) {
|
if (type == TYPE_GET) {
|
||||||
reqLine = reqLine.substring(4);
|
reqLine = reqLine.substring(4);
|
||||||
res.log("GET " + reqLine);
|
res.log("GET " + reqLine);
|
||||||
|
@ -361,6 +362,11 @@ class ConnHandler implements Runnable {
|
||||||
CODE = "404 Not Found";
|
CODE = "404 Not Found";
|
||||||
}
|
}
|
||||||
resp_length = get_resp.length;
|
resp_length = get_resp.length;
|
||||||
|
String end = reqLine.substring(reqLine.length()-4);
|
||||||
|
if (end == ".js")
|
||||||
|
content_type = "text/javascript";
|
||||||
|
else if (end == "css")
|
||||||
|
content_type= "text/css";
|
||||||
} else {
|
} else {
|
||||||
// No forward
|
// No forward
|
||||||
if (reqLine.equals("")) {
|
if (reqLine.equals("")) {
|
||||||
|
@ -385,7 +391,7 @@ class ConnHandler implements Runnable {
|
||||||
SimpleDateFormat formater = new SimpleDateFormat("E, d M y H:m:s z");
|
SimpleDateFormat formater = new SimpleDateFormat("E, d M y H:m:s z");
|
||||||
sout.print("HTTP/1.1 " + CODE + "\r\n"+
|
sout.print("HTTP/1.1 " + CODE + "\r\n"+
|
||||||
"Date: " + formater.format(now) + "\r\n" +
|
"Date: " + formater.format(now) + "\r\n" +
|
||||||
"Content-Type: text/html; charset=UTF-8\r\n" +
|
"Content-Type: " + content_type + "; charset=UTF-8\r\n" +
|
||||||
"Server: Cortex @ " + Integer.toString(res.PORT) + "\r\n");
|
"Server: Cortex @ " + Integer.toString(res.PORT) + "\r\n");
|
||||||
|
|
||||||
// Print response
|
// Print response
|
||||||
|
|
20
LICENSE
20
LICENSE
|
@ -1,20 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2010 Dan Ballard
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
||||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
2
README
2
README
|
@ -1,6 +1,6 @@
|
||||||
Cortex: P2P in JavaScript
|
Cortex: P2P in JavaScript
|
||||||
Dan Ballard (2010-2011) <dan@mindstab.net>
|
Dan Ballard (2010-2011) <dan@mindstab.net>
|
||||||
https://danballard.com/projects/cortex/
|
cortex.mindstab.net
|
||||||
|
|
||||||
Cortex is a P2P framework that runs without installation in your web browser. It's one small part Java Applet (HTTP server) and one large part JavaScript. The JavaScript is all the P2P networking code and utilities you need to get off the ground and go.
|
Cortex is a P2P framework that runs without installation in your web browser. It's one small part Java Applet (HTTP server) and one large part JavaScript. The JavaScript is all the P2P networking code and utilities you need to get off the ground and go.
|
||||||
|
|
||||||
|
|
|
@ -146,6 +146,9 @@ class ResManager {
|
||||||
putSite("shell.html", readURL(new URL(baseurl + "shell.html")));
|
putSite("shell.html", readURL(new URL(baseurl + "shell.html")));
|
||||||
putSite("ajax.js", readURL(new URL(baseurl + "ajax.js")));
|
putSite("ajax.js", readURL(new URL(baseurl + "ajax.js")));
|
||||||
|
|
||||||
|
putSite("cortex.js", readURL(new URL(baseurl + "cortex.js")));
|
||||||
|
putSite("basicClient.html", readURL(new URL(baseurl + "basicClient.html")));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>Client</title>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
.page {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 40px;
|
||||||
|
left: 10px;
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 5px;
|
||||||
|
width: 550px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
border: 1px solid black;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
padding: 0px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="cortex.js" ></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="statusLabel"></div>
|
||||||
|
<div id="numberOfNodes">Number of nodes: <script> document.write(numberOfNodes); </script></div>
|
||||||
|
<div id="connections">Connected to:</div>
|
||||||
|
<div id="dlc">DLC:</div>
|
||||||
|
<input id="networkAddr"><input type="button" value="Join Network" onClick="join_network(document.getElementById('networkAddr').value);"><br/>
|
||||||
|
<br />
|
||||||
|
<b>Log:</b> <input type="checkbox" value="log" id="logCheck"/>Log?<br/>
|
||||||
|
<textarea rows="7" cols="60" id="cortexLog" readonly="true"></textarea>
|
||||||
|
<br/>
|
||||||
|
<input type="button" value="Reload HTML" onclick="reloadHTML()" />
|
||||||
|
<br/><br/>
|
||||||
|
<input type="button" value="Kill Server" onClick="killServer()" />
|
||||||
|
|
||||||
|
<script language="javascript">cortex_start()</script>
|
807
client.html
807
client.html
|
@ -35,16 +35,10 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var localNodeAddr = '';
|
|
||||||
var originURL = 'Unknown Source';
|
|
||||||
var connected = false;
|
|
||||||
var numberOfNodes = 0;
|
|
||||||
var connections = new Array();
|
|
||||||
|
|
||||||
var tests = new Array();
|
var tests = new Array();
|
||||||
var testLocks = new Array();
|
var testLocks = new Array();
|
||||||
var lockTrys = new Array();
|
|
||||||
var myLocks = new Array();
|
|
||||||
var runningTests = new Array();
|
var runningTests = new Array();
|
||||||
|
|
||||||
var rangeLocks = new Array();
|
var rangeLocks = new Array();
|
||||||
|
@ -54,280 +48,8 @@ var doneStage = 0;
|
||||||
|
|
||||||
var runTry = null;
|
var runTry = null;
|
||||||
|
|
||||||
var dlc = 0;
|
|
||||||
var msgQ = new Array();
|
|
||||||
|
|
||||||
function error(msg) {
|
|
||||||
log("ERROR: " + msg);
|
|
||||||
alert("ERROR: " + msg);
|
|
||||||
clearInterval(cronID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* basic ping function for local node
|
|
||||||
* if successful,
|
|
||||||
* if we haven't greeted the server, we do,
|
|
||||||
* otherwise nothing (just a heartbeat)
|
|
||||||
* if it fails, lets the user know we have lost
|
|
||||||
* connection to the node server
|
|
||||||
*/
|
|
||||||
function ping() {
|
|
||||||
var http = ajaxConnect();
|
|
||||||
|
|
||||||
var retfn = returnfn(http,
|
|
||||||
function(resp) {
|
|
||||||
if (!connected) {
|
|
||||||
connected = true;
|
|
||||||
greet();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(resp) {
|
|
||||||
connected = false;
|
|
||||||
var html = "Connection to local Node failed. Try reloading from <a href=\"" + originURL + "\" target=\"window\">" + originURL + "</a>";
|
|
||||||
document.getElementById('statusLabel').innerHTML = html;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
ajaxSend(http, "cmd=ping\n\n", retfn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Greets the server
|
|
||||||
* so far just gets the server's IP and origin URL
|
|
||||||
*/
|
|
||||||
function greet() {
|
|
||||||
var http = ajaxConnect();
|
|
||||||
|
|
||||||
var retfn = returnfn(http,
|
|
||||||
function(resp) {
|
|
||||||
var arr = resp.split(" ");
|
|
||||||
localNodeAddr = arr[0];
|
|
||||||
originURL = arr[1];
|
|
||||||
document.getElementById('statusLabel').innerHTML = "I am <b>" + localNodeAddr + "</b>";
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ajaxSend(http, "cmd=greet\n\n", retfn);
|
|
||||||
}
|
|
||||||
|
|
||||||
function killServer() {
|
|
||||||
announceDead(localNodeAddr);
|
|
||||||
var http = ajaxConnect();
|
|
||||||
|
|
||||||
var retfn = returnfn(http,
|
|
||||||
function(resp) {
|
|
||||||
// got a resp, server not dead
|
|
||||||
killServer();
|
|
||||||
});
|
|
||||||
ajaxSend(http, "cmd=kill\n\n", retfn);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLog() {
|
|
||||||
var http = ajaxConnect();
|
|
||||||
|
|
||||||
|
|
||||||
var retfn = returnfn(http,
|
|
||||||
function(resp) {
|
|
||||||
var log = document.getElementById('log');
|
|
||||||
//alert("getLog() resp: " + resp);
|
|
||||||
log.value = resp + log.value;
|
|
||||||
// Since getLog returns (a call back is called later)
|
|
||||||
// this is tail recursive friendly
|
|
||||||
getLog();
|
|
||||||
});
|
|
||||||
ajaxSend(http, "cmd=getLog\n\n", retfn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// two digits
|
|
||||||
function d2(str) {
|
|
||||||
if (str <10)
|
|
||||||
return "0"+str;
|
|
||||||
else
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTime() {
|
|
||||||
var date = new Date();
|
|
||||||
return d2(date.getHours()) + ":" + d2(date.getMinutes()) + ":" + d2(date.getSeconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
var logCheckElem = null;
|
|
||||||
function log(str) {
|
|
||||||
if (logCheckElem == null)
|
|
||||||
logCheckElem = document.getElementById("logCheck");
|
|
||||||
if (logCheckElem.checked == true) {
|
|
||||||
str = getTime() + ": " + str+"\n";
|
|
||||||
var log = document.getElementById('log');
|
|
||||||
log.value = str + log.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function log2(str) {
|
|
||||||
str = getTime() + ": " + str+"\n";
|
|
||||||
var log = document.getElementById('log');
|
|
||||||
log.value = str + log.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMsgs() {
|
|
||||||
var http = ajaxConnect();
|
|
||||||
var retfn = returnfn(http,
|
|
||||||
function(resp) {
|
|
||||||
//log("getMsgs: len > 0");
|
|
||||||
log("getMsgs: " + resp);
|
|
||||||
queueMsgs(resp);
|
|
||||||
// Since getMsgs returns (a call back is called later)
|
|
||||||
// this is tail recursive friendly
|
|
||||||
getMsgs();
|
|
||||||
});
|
|
||||||
ajaxSend(http, "cmd=getMsgs\n\n", retfn);
|
|
||||||
}
|
|
||||||
|
|
||||||
function connectedTo(addr) {
|
|
||||||
if (addr == localNodeAddr)
|
|
||||||
return true;
|
|
||||||
for(i = 0; i < connections.length; i++) {
|
|
||||||
if (connections[i] == addr)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addConnection(addr) {
|
|
||||||
if(!connectedTo(addr)) {
|
|
||||||
connections[connections.length] = addr;
|
|
||||||
numberOfNodes++;
|
|
||||||
return true;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function genNodeList() {
|
|
||||||
var str = "";//localNodeAddr+",";
|
|
||||||
for (var i =0; i < connections.length; i++) {
|
|
||||||
str += connections[i] + ",";
|
|
||||||
}
|
|
||||||
if (connections.length > 0)
|
|
||||||
str = str.substring(0,str.length-1);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
function advertiseNewNode(addr) {
|
|
||||||
var m = new Object();
|
|
||||||
m['query'] = "newNode";
|
|
||||||
m['addr'] = addr;
|
|
||||||
bcastMsg(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
function announceDead(addr) {
|
|
||||||
var m = new Object();
|
|
||||||
m['query'] = "deadNode";
|
|
||||||
m['addr'] = addr;
|
|
||||||
bcastMsg(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleNewNode(resp, welcome) {
|
|
||||||
|
|
||||||
if (welcome == null)
|
|
||||||
welcome = false;
|
|
||||||
var addr = resp.addr;
|
|
||||||
//log2("handle new node " + addr + " from " + resp.origin);
|
|
||||||
|
|
||||||
// handles re adding if reloaded html
|
|
||||||
//if (!connectedTo(addr))
|
|
||||||
//{
|
|
||||||
var ret = genNodeList();
|
|
||||||
var n = addConnection(addr);
|
|
||||||
if (welcome == true) {
|
|
||||||
sendDLC(addr);
|
|
||||||
sendWelcome(ret, addr);
|
|
||||||
sendLocks(addr);
|
|
||||||
sendTests(addr);
|
|
||||||
}
|
|
||||||
if (n == true) {
|
|
||||||
sendReqs(addr);
|
|
||||||
advertiseNewNode(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendDLC(addr) {
|
|
||||||
var m = new Object();
|
|
||||||
m.query = "DLC";
|
|
||||||
sendMsg(m, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendReqs(addr) {
|
|
||||||
for(i in lockTrys) {
|
|
||||||
var lock = lockTrys[i];
|
|
||||||
if (lock == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var m = new Object();
|
|
||||||
m["query"] = "getLock";
|
|
||||||
m["type"] = i;
|
|
||||||
m["name"] = lock["name"];
|
|
||||||
m["time"] = lock.time;
|
|
||||||
m["addr"] = localNodeAddr;
|
|
||||||
if (i == "range") {
|
|
||||||
m["start"] = lock.start;
|
|
||||||
m["end"] = lock.end;
|
|
||||||
}
|
|
||||||
sendMsg(m, addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendWelcome(addrList, addr) {
|
|
||||||
var m = new Object();
|
|
||||||
m["query"] = "welcome";
|
|
||||||
m["addrList"] = addrList;
|
|
||||||
sendMsg(m, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendLocks(addr) {
|
|
||||||
for (var i in testLocks) {
|
|
||||||
log ("testLog[" + i + "]");
|
|
||||||
var l = testLocks[i];
|
|
||||||
var m = new Object();
|
|
||||||
|
|
||||||
m["query"] = "addLock";
|
|
||||||
m["type"] = "test";
|
|
||||||
m["name"] = l["name"];
|
|
||||||
m["addr"] = l["addr"];
|
|
||||||
m["locked"] = l["locked"];
|
|
||||||
m["perjob"] =
|
|
||||||
|
|
||||||
|
|
||||||
m["time"] = l["time"];
|
|
||||||
if (l.running) {
|
|
||||||
m["running"] = true;
|
|
||||||
m["min"] = l["min"];
|
|
||||||
m["max"] = l["max"];
|
|
||||||
m["perjob"] = l["perjob"];
|
|
||||||
}
|
|
||||||
sendMsg(m, addr);
|
|
||||||
}
|
|
||||||
for (var i in rangeLocks) {
|
|
||||||
var r = rangeLocks[i];
|
|
||||||
while(r) {
|
|
||||||
log("rangeLock[" + i +" [" + r.start + "-" + r.end +"]]");
|
|
||||||
var m = new Object();
|
|
||||||
m["query"] = "addLock";
|
|
||||||
m["type"] = "range";
|
|
||||||
m["name"] = i;
|
|
||||||
m["addr"] = r["addr"]
|
|
||||||
m["locked"] = r["locked"];
|
|
||||||
m["start"] = r["start"];
|
|
||||||
m["end"] = r["end"];
|
|
||||||
m["done"] = r["done"];
|
|
||||||
if (r['results'])
|
|
||||||
m["results"] = r["results"];
|
|
||||||
|
|
||||||
sendMsg(m, addr);
|
|
||||||
r = r.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendTests(addr) {
|
function sendTests(addr) {
|
||||||
for(var i in tests) {
|
for(var i in tests) {
|
||||||
|
@ -336,32 +58,6 @@ function sendTests(addr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddLock(resp) {
|
|
||||||
if(resp['locked'] == null)
|
|
||||||
resp['locked'] = false;
|
|
||||||
// dont add/overwrite if exists?
|
|
||||||
var lock = retrieveLock(resp);
|
|
||||||
|
|
||||||
if (resp['type'] == "test" ) {//&& (lock == null || lock.addr == null)) {
|
|
||||||
var locks = getLockType(resp['type']);
|
|
||||||
|
|
||||||
|
|
||||||
locks[resp['name']] = resp
|
|
||||||
if(resp['running'] == true) {
|
|
||||||
runningTests.push(resp['name']);
|
|
||||||
}
|
|
||||||
} else if(resp['type'] == "range" && lock.addr == null) {
|
|
||||||
log("Add range lock: " + resp['name'] + " (" + resp['start'] + " to " + resp['end'] + ") : ");
|
|
||||||
var lock = getRangeLock(resp['name'], resp['start'], resp['end']);
|
|
||||||
|
|
||||||
lock.addr = resp['addr'];
|
|
||||||
lock.locked = resp['locked'];
|
|
||||||
lock.done = resp['done'];
|
|
||||||
lock.results = resp['results'];
|
|
||||||
lock.name = resp['name'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function packArray(arr) {
|
function packArray(arr) {
|
||||||
if (arr instanceof Object) {
|
if (arr instanceof Object) {
|
||||||
var str = "{";
|
var str = "{";
|
||||||
|
@ -432,108 +128,6 @@ function packResults(res) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
function packObject(m, defaultValue) {
|
|
||||||
|
|
||||||
var str = "";
|
|
||||||
for (var i in m) {
|
|
||||||
if (m[i] == '')
|
|
||||||
{
|
|
||||||
if (defaultValue == null) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
m[i] = defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i == "results") {
|
|
||||||
str += i + "=" + packResults(m[i]) + "\n";
|
|
||||||
} else {
|
|
||||||
str += i + "=" + m[i] + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNodeRangeLocks(addr) {
|
|
||||||
for(var i in rangeLocks) {
|
|
||||||
log("freeing " + i + " locks");
|
|
||||||
var rlock = rangeLocks[i];
|
|
||||||
while (rlock != null) {
|
|
||||||
log("looking at " + i + " (" + rlock.start + " to " + rlock.end + ") : " + rlock.addr);
|
|
||||||
if (rlock.addr == addr) {
|
|
||||||
log("unlocking");
|
|
||||||
rlock.locked = false;
|
|
||||||
}
|
|
||||||
rlock = rlock.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function disconnect(addr) {
|
|
||||||
log("--------------DISCONNECT " + addr + "----------");
|
|
||||||
// remove from connections list
|
|
||||||
for (var i=0; i < connections.length; i++) {
|
|
||||||
if (connections[i] == addr) {
|
|
||||||
connections.splice(i,1);
|
|
||||||
numberOfNodes--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove locks
|
|
||||||
// test locks
|
|
||||||
for(var i in testLocks) {
|
|
||||||
var lock = testLocks[i];
|
|
||||||
if (!lock)
|
|
||||||
continue;
|
|
||||||
if (lock.addr == addr) {
|
|
||||||
lock.locked = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// range locks
|
|
||||||
removeNodeRangeLocks(addr);
|
|
||||||
|
|
||||||
log("disconnect: freeing range locks");
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMsg(m, addr, time, mtype) {
|
|
||||||
if(time == null)
|
|
||||||
time = dlc;
|
|
||||||
if(mtype == null)
|
|
||||||
mtype = "single";
|
|
||||||
|
|
||||||
var str = "cmd=sendMsg\n";
|
|
||||||
str += "origin=" + localNodeAddr + "\n";
|
|
||||||
str += "dlc=" + time + "\n";
|
|
||||||
str += "mtype=" + mtype + "\n";
|
|
||||||
|
|
||||||
str += packObject(m);
|
|
||||||
|
|
||||||
str += "\n";
|
|
||||||
log ("SEND: " + str);
|
|
||||||
|
|
||||||
var http = ajaxConnect();
|
|
||||||
var retfn = returnfn(http, null,
|
|
||||||
function() {
|
|
||||||
disconnect(addr);
|
|
||||||
announceDead(addr);
|
|
||||||
log("ERROR> message to " + addr + " not delivered: '" + str + "'");
|
|
||||||
});
|
|
||||||
|
|
||||||
ajaxSend(http, str, retfn, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
function bcastMsg(m) {
|
|
||||||
if (m.query != "heartBeat")
|
|
||||||
dlc ++;
|
|
||||||
//log("BCAST conLen: " + connections.length);
|
|
||||||
for(var i=0; i < connections.length; i++) {
|
|
||||||
log("bcast "+ i +" " + m.query + " to " + connections[i] + " dlc:" + dlc);
|
|
||||||
sendMsg(m, connections[i], dlc, "bcast");
|
|
||||||
//log("back from sendMsg, i:" + i + " conlen:" + connections.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeStr(str) {
|
function encodeStr(str) {
|
||||||
var res="";
|
var res="";
|
||||||
|
@ -573,69 +167,19 @@ function decodeStr(str) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMsg(resp) {
|
|
||||||
//log("addMsg mdlc: " + resp.dlc + " mqlen: " + msgQ.length + " type:" + resp.mtype + " query: " + resp.query);
|
|
||||||
for(var i=msgQ.length-1; i>=0; i--) {
|
|
||||||
//log("resp.dlc:" + resp.dlc + " msgQ[i].dlc:" + msgQ[i].dlc);
|
|
||||||
if (resp.dlc > msgQ[i].dlc) {
|
|
||||||
//log("moving " + i + " to " + (i+1));
|
|
||||||
msgQ[i+1] = msgQ[i];
|
|
||||||
} else {
|
|
||||||
//log("first, moving " + i + " to " + (i+1));
|
|
||||||
//msgQ[i+1] = msgQ[i];
|
|
||||||
//log("putting msg in " + i + " + 1, RETURN");
|
|
||||||
msgQ[i+1] = resp;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//log("END: putting msg in 0");
|
|
||||||
msgQ[0] = resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function queueMsgs(mstr) {
|
|
||||||
log("GOT messages: " + mstr);
|
|
||||||
var resps = mstr.split("\n\n");
|
|
||||||
for(var rcount=0; rcount < resps.length; rcount++) {
|
|
||||||
var resp = resps[rcount];
|
|
||||||
if (resp == "")
|
|
||||||
continue;
|
|
||||||
resp = parseResp(resp);
|
|
||||||
if (resp.query == "DLC") {
|
|
||||||
dlc = resp.dlc;
|
|
||||||
//var m = new Object();
|
|
||||||
//m.query = "DLC";
|
|
||||||
//bcastMsg(m);
|
|
||||||
} else {
|
|
||||||
addMsg(resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var s = "QUEUE: "
|
|
||||||
for (var i =0; i< msgQ.length; i++) {
|
|
||||||
s += msgQ[i].dlc + " ";
|
|
||||||
}
|
|
||||||
log(s);
|
|
||||||
log("mqlen:" + msgQ.length);
|
|
||||||
while (msgQ.length>0 && ((msgQ[msgQ.length-1].dlc - 1) <= dlc)) {
|
|
||||||
var r = msgQ.pop();
|
|
||||||
log("mdlc: " + r.dlc + " dlc: " + dlc + " mqlen: " + msgQ.length + " type:" + r.mtype + " query: " + r.query);
|
|
||||||
if (r.mtype == "bcast")
|
|
||||||
dlc = r.dlc
|
|
||||||
processMsg(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function processMsg(resp) {
|
function processMsg(resp) {
|
||||||
if (resp["query"] == "join_network") {
|
// if (resp["query"] == "join_network") {
|
||||||
log("Message join_network received");
|
// log("Message join_network received");
|
||||||
handleNewNode(resp, true);
|
// handleNewNode(resp, true);
|
||||||
} else if (resp["query"] == "welcome") {
|
// } else if (resp["query"] == "welcome") {
|
||||||
handleWelcome(resp);
|
// handleWelcome(resp);
|
||||||
} else if (resp["query"] == "newNode") {
|
// } else if (resp["query"] == "newNode") {
|
||||||
//addConnection(resp["addr"]);
|
// //addConnection(resp["addr"]);
|
||||||
handleNewNode(resp, false);
|
// handleNewNode(resp, false);
|
||||||
} else if (resp["query"] == "deadNode") {
|
// } else if (resp["query"] == "deadNode") {
|
||||||
disconnect(resp["addr"]);
|
// disconnect(resp["addr"]);
|
||||||
} else if(resp["query"] == "addLock") {
|
} else if(resp["query"] == "addLock") {
|
||||||
handleAddLock(resp);
|
handleAddLock(resp);
|
||||||
} else if (resp["query"] == "getLock") {
|
} else if (resp["query"] == "getLock") {
|
||||||
|
@ -650,120 +194,16 @@ function processMsg(resp) {
|
||||||
handleActivateJob(resp);
|
handleActivateJob(resp);
|
||||||
} else if(resp["query"] == "results") {
|
} else if(resp["query"] == "results") {
|
||||||
handleResults(resp);
|
handleResults(resp);
|
||||||
} else if(resp["query"] == "new_network") {
|
// } else if(resp["query"] == "new_network") {
|
||||||
join_network(resp["addr"], false);
|
// join_network(resp["addr"], false);
|
||||||
} else if(resp["query"] == "heartBeat") {
|
// } else if(resp["query"] == "heartBeat") {
|
||||||
// do nothing;
|
// // do nothing;
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
log("Error: Unkown message '" + resp["query"] + "' received from " + resp["origin"]);
|
log("Error: Unkown message '" + resp["query"] + "' received from " + resp["origin"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWelcome(resp) {
|
|
||||||
log("Welcome message received");
|
|
||||||
//var myNodes = genNodeList();
|
|
||||||
addConnection(resp['origin']);
|
|
||||||
if (resp['addrList']){
|
|
||||||
var nodes = resp['addrList'].split(",");
|
|
||||||
for(var i=0; i < nodes.length; i++){
|
|
||||||
addConnection(nodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(var i =0; i< connections.length; i++) {
|
|
||||||
var addr = connections[i];
|
|
||||||
if (addr == localNodeAddr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
advertiseNewNode(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractHost(url) {
|
|
||||||
// http: or file:
|
|
||||||
var host = url.substr(5);
|
|
||||||
while (host.charAt(0) == '/')
|
|
||||||
host = host.substr(1);
|
|
||||||
var end = host.indexOf('/');
|
|
||||||
|
|
||||||
if (end > 0 )
|
|
||||||
host = host.substr(0, end);
|
|
||||||
return host;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function setStatus() {
|
|
||||||
document.getElementById('numberOfNodes').innerHTML = "Number of nodes: " + numberOfNodes;
|
|
||||||
document.getElementById('connections').innerHTML = "Connected to: " + genNodeList();
|
|
||||||
document.getElementById('dlc').innerHTML = "DLC: " + dlc;
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
if (connected == false)
|
|
||||||
return;
|
|
||||||
numberOfNodes = 1;
|
|
||||||
if (originURL.substr(0, 4) == "file") {
|
|
||||||
// we are the first
|
|
||||||
//numberOfNodes = 1; // ourself
|
|
||||||
log("Loaded from file");
|
|
||||||
// now we sit and wait
|
|
||||||
} else { // it was http, so we were loaded from another node
|
|
||||||
log("Loaded from " + originURL);
|
|
||||||
originAddress = extractHost(originURL);
|
|
||||||
|
|
||||||
join_network(originAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseResp(resp) {
|
|
||||||
var lines = resp.split("\n");
|
|
||||||
var arr = [];
|
|
||||||
for(var i=0; i < lines.length; i++) {
|
|
||||||
//var kv = lines[i].split("=");
|
|
||||||
var mid = lines[i].indexOf("=");
|
|
||||||
var kv = new Array();
|
|
||||||
kv[0] = lines[i].substring(0, mid);
|
|
||||||
kv[1] = lines[i].substring(mid+1);
|
|
||||||
if (kv[1] == "true")
|
|
||||||
kv[1] = true;
|
|
||||||
else if(kv[0] == "false")
|
|
||||||
kv[1] = false;
|
|
||||||
else if (kv[0] == "dlc" ||
|
|
||||||
kv[0] == "start" ||
|
|
||||||
kv[0] == "end" ||
|
|
||||||
kv[0] == "min" ||
|
|
||||||
kv[0] == "max")
|
|
||||||
kv[1] = Number(kv[1]);
|
|
||||||
else if (kv[0] == "results")
|
|
||||||
kv[1] = unpackResults(kv[1]);
|
|
||||||
arr[kv[0]] = kv[1];
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function join_network(address, bcast) {
|
|
||||||
if(bcast == null) {
|
|
||||||
bcast = true;
|
|
||||||
}
|
|
||||||
log("join_network -> " + address);
|
|
||||||
var m = new Object();
|
|
||||||
m["query"] = "join_network";
|
|
||||||
m["addr"] = localNodeAddr;
|
|
||||||
//m["nodes"] = genNodeList();
|
|
||||||
sendMsg(m, address);
|
|
||||||
if (bcast == true) {
|
|
||||||
m = new Object();
|
|
||||||
m["query"] = "new_network";
|
|
||||||
m["addr"] = address;
|
|
||||||
bcastMsg(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var runCount = 0;
|
var runCount = 0;
|
||||||
var workMod = 1;
|
var workMod = 1;
|
||||||
|
@ -816,7 +256,7 @@ function cron() {
|
||||||
if (runCount % 10 == 0) {
|
if (runCount % 10 == 0) {
|
||||||
ping();
|
ping();
|
||||||
//getLog();
|
//getLog();
|
||||||
setStatus();
|
//setStatus();
|
||||||
checkLocks();
|
checkLocks();
|
||||||
updateTestsStatus();
|
updateTestsStatus();
|
||||||
if ( doneStage == 1 ) {
|
if ( doneStage == 1 ) {
|
||||||
|
@ -836,46 +276,16 @@ function cron() {
|
||||||
if (runCount % 20 == 0)
|
if (runCount % 20 == 0)
|
||||||
updateResults();
|
updateResults();
|
||||||
|
|
||||||
if (runCount % 300 == 0)
|
//if (runCount % 300 == 0)
|
||||||
heartBeat();
|
// heartBeat();
|
||||||
|
|
||||||
// 180 seconds, or 3 minutes
|
// 180 seconds, or 3 minutes
|
||||||
if (runCount == 1800)
|
/*if (runCount == 1800)
|
||||||
runCount = 0;
|
runCount = 0;
|
||||||
runCount++;
|
runCount++; */
|
||||||
}
|
}
|
||||||
|
|
||||||
function heartBeat() {
|
|
||||||
var m = new Object();
|
|
||||||
m.query = "heartBeat";
|
|
||||||
bcastMsg(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reloadHTML() {
|
|
||||||
var http = ajaxConnect();
|
|
||||||
var retfn = returnfn(http);
|
|
||||||
ajaxSend(http, "cmd=reloadHTML\n\n",retfn);
|
|
||||||
}
|
|
||||||
|
|
||||||
function poke() {
|
|
||||||
var http = ajaxConnect();
|
|
||||||
document.getElementById("pingNodeResp").innerHTML = "";
|
|
||||||
var dest = document.getElementById("pingNode").value;
|
|
||||||
|
|
||||||
var retfn = returnfn(http,
|
|
||||||
function(resp) {
|
|
||||||
document.getElementById("pingNodeResp").innerHTML += resp + "<br>";
|
|
||||||
var retfn = returnfn(http,
|
|
||||||
function(resp) {
|
|
||||||
document.getElementById("pingNodeResp").innerHTML += resp + "<br>";
|
|
||||||
});
|
|
||||||
ajaxSend(http, "cmd=greet\n\n", retfn, dest);
|
|
||||||
});
|
|
||||||
|
|
||||||
ajaxSend(http, "cmd=ping\n\n", retfn, dest);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTestStatus(str, col) {
|
function setTestStatus(str, col) {
|
||||||
var e = document.getElementById('testStatus');
|
var e = document.getElementById('testStatus');
|
||||||
|
@ -895,23 +305,6 @@ function getLockType(type) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLocked(lockType, lockName, start, end) {
|
|
||||||
if (lockType == "range") {
|
|
||||||
var rlock = getRangeLock(lockname, start, end);
|
|
||||||
if (rlock)
|
|
||||||
return rlock.locked;
|
|
||||||
else return false;
|
|
||||||
} else {
|
|
||||||
var locks = getLockType(lockType);
|
|
||||||
if (locks[lockName] == null)
|
|
||||||
return false;
|
|
||||||
else if(locks[lockName].locked != true)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function loadTest(tname) {
|
function loadTest(tname) {
|
||||||
if(tname == '') {
|
if(tname == '') {
|
||||||
|
@ -933,68 +326,6 @@ function loadTest(tname) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function retrieveLock(struct) {
|
|
||||||
if (struct.type == "test") {
|
|
||||||
return testLocks[struct.name];
|
|
||||||
} else if(struct.type == "range") {
|
|
||||||
var l = getRangeLock(struct.name, struct.start, struct.end);
|
|
||||||
if (l == null)
|
|
||||||
error("retrieveLock for range got null");
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLock(type, name, start, end) {
|
|
||||||
var locks = getLockType(type);
|
|
||||||
var lockTry;
|
|
||||||
|
|
||||||
if (type == "test") {
|
|
||||||
if(locks[name] == null) {
|
|
||||||
locks[name] = new Object();
|
|
||||||
}
|
|
||||||
lockTry = locks[name];
|
|
||||||
} else if (type == "range") {
|
|
||||||
lockTry = getRangeLock(name, start, end);
|
|
||||||
if (!lockTry) {
|
|
||||||
error("Cannot get range lock in getLock(" + name +", "+ start + ", " + end + ")");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
lockTry.time = getTime();
|
|
||||||
lockTry.dlc = dlc+1;
|
|
||||||
lockTry.name = name;
|
|
||||||
lockTry.addr = localNodeAddr;
|
|
||||||
lockTry.type = type;
|
|
||||||
lockTry.okay = 1;
|
|
||||||
lockTry.locked = false;
|
|
||||||
var d = new Date();
|
|
||||||
lockTry.localTime = d.getTime();
|
|
||||||
|
|
||||||
if (type == "test") {
|
|
||||||
lockTrys[type] = lockTry;
|
|
||||||
setTestStatus("Acquiring lock for test '" + name + "' " + lockTry.okay + "/" + numberOfNodes + "...", "yellow");
|
|
||||||
} else if (type == "range") {
|
|
||||||
lockTrys[type] = lockTry;
|
|
||||||
setWorkStatus("Acquiring lock for work '" + name + "' (" + lockTry.start + " to " + lockTry.end + ")" + lockTry.okay + "/" + numberOfNodes + "...", "yellow");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var m = new Object();
|
|
||||||
m["query"] = "getLock";
|
|
||||||
m["type"] = type;
|
|
||||||
m["name"] = name;
|
|
||||||
m["time"] = lockTry.time;
|
|
||||||
m["addr"] = localNodeAddr;
|
|
||||||
if (type == "range") {
|
|
||||||
m["start"] = start;
|
|
||||||
m["end"] = end;
|
|
||||||
}
|
|
||||||
bcastMsg(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
function lessTime(t1, t2) {
|
function lessTime(t1, t2) {
|
||||||
var t1a = t1.split(":");
|
var t1a = t1.split(":");
|
||||||
var t2a = t2.split(":");
|
var t2a = t2.split(":");
|
||||||
|
@ -1033,28 +364,6 @@ function cameFirst(a, b) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function locksEqual(a, b) {
|
|
||||||
if(a == null || b == null)
|
|
||||||
return false;
|
|
||||||
if(a['type'] != b['type'])
|
|
||||||
return false;
|
|
||||||
if(a['type'] == 'test') {
|
|
||||||
if (a.name == b.name)
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
} else if (a['type'] == "range") {
|
|
||||||
log("locksEqual? " + a.name + " (" + a.start + " to " + a.end + ") and " + b.name + " (" + b.start + " to " + b.end + ")");
|
|
||||||
if (a.name == b.name && a.start == b.start && a.end == b.end) {
|
|
||||||
log("true");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
log("false");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function grantLock(resp, lock) {
|
function grantLock(resp, lock) {
|
||||||
removeNodeRangeLocks(resp.origin);
|
removeNodeRangeLocks(resp.origin);
|
||||||
|
@ -1473,24 +782,6 @@ function handleTestUpdate(resp) {
|
||||||
tests[resp['name']]['reducefn'] = decodeStr(resp["reducefn"]);
|
tests[resp['name']]['reducefn'] = decodeStr(resp["reducefn"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function releaseLock(type) {
|
|
||||||
var lock = myLocks[type];
|
|
||||||
myLocks[type] = null;
|
|
||||||
|
|
||||||
lock.locked = false;
|
|
||||||
|
|
||||||
|
|
||||||
var m = new Object();
|
|
||||||
m["query"] = "releaseLock";
|
|
||||||
m["type"] = type;
|
|
||||||
m["name"] = lock.name;
|
|
||||||
if (type == "range") {
|
|
||||||
m["start"] = lock.start;
|
|
||||||
m["end"] = lock.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
bcastMsg(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLockRelease(resp) {
|
function handleLockRelease(resp) {
|
||||||
//var locks = getLockType(resp['type']);
|
//var locks = getLockType(resp['type']);
|
||||||
|
@ -1536,57 +827,6 @@ function lookForWork() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRangeLock(name, start, end) {
|
|
||||||
log("getRangeLock for (" + name + " from " + start + " to " + end + ")");
|
|
||||||
if (!rangeLocks[name]) {
|
|
||||||
log("no locks for " + name + ", so MAKING");
|
|
||||||
rangeLocks[name] = genRangeCell(name, start, end);
|
|
||||||
return rangeLocks[name];
|
|
||||||
}
|
|
||||||
log("locks exist for " + name + " so SEARCHING forward");
|
|
||||||
var rlock = rangeLocks[name];
|
|
||||||
|
|
||||||
if(end < rlock.start) {
|
|
||||||
log("Space before first lock, MAKING HERE");
|
|
||||||
rangeLocks[name] = genRangeCell(name, start, end);
|
|
||||||
rangeLocks[name].next = rlock;
|
|
||||||
return rangeLocks[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
while(rlock.next && rlock.start < rlock.end+1) {
|
|
||||||
log("looking at (" + rlock.start + " to " + rlock.end + ")");
|
|
||||||
if (rlock.start == start && rlock.end == end) {
|
|
||||||
log("FOUND IT");
|
|
||||||
return rlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rlock.end < start && rlock.next.start > end) {
|
|
||||||
log("GAP between (" + rlock.start + " to " + rlock.end + ") and (" + rlock.next.start + " to " + rlock.next.end + ") where we should be so MAKING THERE");
|
|
||||||
var nlock = rlock.next;
|
|
||||||
rlock.next = genRangeCell(name, start, end);
|
|
||||||
rlock.next.next = nlock;
|
|
||||||
return rlock.next;
|
|
||||||
}
|
|
||||||
|
|
||||||
rlock = rlock.next;
|
|
||||||
}
|
|
||||||
log("SEARCH ended");
|
|
||||||
if (rlock.start == start && rlock.end == end) {
|
|
||||||
log("Found it!");
|
|
||||||
return rlock;
|
|
||||||
} else {
|
|
||||||
if (rlock.end < start) {
|
|
||||||
log("There is space at the end to make what we want");
|
|
||||||
rlock.next = genRangeCell(name, start, end);
|
|
||||||
return rlock.next;
|
|
||||||
} else {
|
|
||||||
log("search ended after what we wanted, FAIL");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeRangeLock(tlock, suggestedSize) {
|
function makeRangeLock(tlock, suggestedSize) {
|
||||||
suggestedSize--;
|
suggestedSize--;
|
||||||
|
|
||||||
|
@ -1639,6 +879,7 @@ function makeRangeLock(tlock, suggestedSize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
function genRangeCell(name, start, end, next) {
|
function genRangeCell(name, start, end, next) {
|
||||||
var c = new Object();
|
var c = new Object();
|
||||||
c.name = name;
|
c.name = name;
|
||||||
|
@ -1648,7 +889,7 @@ function genRangeCell(name, start, end, next) {
|
||||||
c.locked = false;
|
c.locked = false;
|
||||||
c.done = false;
|
c.done = false;
|
||||||
return c;
|
return c;
|
||||||
}
|
} */
|
||||||
|
|
||||||
var map_test = null;
|
var map_test = null;
|
||||||
var reduce = null;
|
var reduce = null;
|
||||||
|
@ -1851,9 +1092,7 @@ function updateFinalResults() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var cronID = setInterval("cron()", 100);
|
|
||||||
getMsgs();
|
|
||||||
getLog();
|
|
||||||
|
|
||||||
|
|
||||||
function page(p) {
|
function page(p) {
|
||||||
|
|
|
@ -0,0 +1,706 @@
|
||||||
|
/* cortex.js
|
||||||
|
* PURPOSE
|
||||||
|
* AUTHOR
|
||||||
|
* LICENSE
|
||||||
|
*
|
||||||
|
* NOTES
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* HTML elements used
|
||||||
|
* cortexLog - textarea
|
||||||
|
* statusLabel - div
|
||||||
|
* numberOfNodes - div
|
||||||
|
* connections -div
|
||||||
|
* dlc - div
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***** Ajax Basics *****/
|
||||||
|
|
||||||
|
var port = location.href.substring( location.href.substring(7).indexOf(':')+8,
|
||||||
|
location.href.substring(7).indexOf('/')+7);
|
||||||
|
|
||||||
|
// Returns an ajax object
|
||||||
|
function ajaxConnect() {
|
||||||
|
var http = null;
|
||||||
|
if(window.XMLHttpRequest)
|
||||||
|
http = new XMLHttpRequest();
|
||||||
|
else if (window.ActiveXObject)
|
||||||
|
http = new ActiveXObject("Microsoft.XMLHTTP");
|
||||||
|
return http;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returns a function that can be passed to http.send
|
||||||
|
* that runs code fn on successful response
|
||||||
|
* Kind of a macro function builder
|
||||||
|
* http: ajax object
|
||||||
|
* fn: function taking one argument, the resp from the server,
|
||||||
|
* this function is run on a successful ajax call
|
||||||
|
* err: OPTIONAL argument that contains a funtion
|
||||||
|
* to be run if the connection failed
|
||||||
|
*/
|
||||||
|
function returnfn(http, fn, err) {
|
||||||
|
if (!fn)
|
||||||
|
fn = function(resp) {};
|
||||||
|
if (!err)
|
||||||
|
err = function() {};
|
||||||
|
return function() {
|
||||||
|
if (http.readyState == 4) {
|
||||||
|
if (http.responseText == '')
|
||||||
|
err();
|
||||||
|
else
|
||||||
|
fn(http.responseText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Does ajax magic. Makes ajax call with data and sets up
|
||||||
|
* retfn to be called on response
|
||||||
|
* forward is the final destination address of the call if
|
||||||
|
* it is not this local
|
||||||
|
*/
|
||||||
|
function ajaxSend(http, data, retfn, forward) {
|
||||||
|
if (!forward)
|
||||||
|
forward = '';
|
||||||
|
http.onreadystatechange = retfn;
|
||||||
|
http.open('POST', "http://localhost:"+port+"/"+ forward, true);
|
||||||
|
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
http.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** Utility Functions *****/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// make sure a number is two digits long, padding 0 on front
|
||||||
|
function d2(str) {
|
||||||
|
if (str <10)
|
||||||
|
return "0"+str;
|
||||||
|
else
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the time as a string
|
||||||
|
function getTime() {
|
||||||
|
var date = new Date();
|
||||||
|
return d2(date.getHours()) + ":" + d2(date.getMinutes()) + ":" + d2(date.getSeconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractHost(url) {
|
||||||
|
// http: or file:
|
||||||
|
var host = url.substr(5);
|
||||||
|
while (host.charAt(0) == '/')
|
||||||
|
host = host.substr(1);
|
||||||
|
var end = host.indexOf('/');
|
||||||
|
|
||||||
|
if (end > 0 )
|
||||||
|
host = host.substr(0, end);
|
||||||
|
return host;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var logging = true
|
||||||
|
|
||||||
|
function log(str) {
|
||||||
|
//if (logCheckElem == null)
|
||||||
|
// logCheckElem = document.getElementById("logCheck");
|
||||||
|
if (logging) { //logCheckElem.checked == true) {
|
||||||
|
str = getTime() + ": " + str+"\n";
|
||||||
|
var log = document.getElementById('cortexLog');
|
||||||
|
if(log)
|
||||||
|
log.value = str + log.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(msg) {
|
||||||
|
log("ERROR: " + msg);
|
||||||
|
alert("ERROR: " + msg);
|
||||||
|
clearInterval(cronID); // Shuts down cron
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***** Basic Server Communication *****/
|
||||||
|
|
||||||
|
var localNodeAddr = '';
|
||||||
|
var originURL = 'Unknown Source';
|
||||||
|
var connected = false;
|
||||||
|
var numberOfNodes = 0;
|
||||||
|
var connections = new Array();
|
||||||
|
|
||||||
|
var dlc = 0;
|
||||||
|
var msgQ = new Array();
|
||||||
|
|
||||||
|
/* basic ping function for local node
|
||||||
|
* if successful,
|
||||||
|
* if we haven't greeted the server, we do,
|
||||||
|
* otherwise nothing (just a heartbeat)
|
||||||
|
* if it fails, lets the user know we have lost
|
||||||
|
* connection to the node server
|
||||||
|
*/
|
||||||
|
function ping() {
|
||||||
|
var http = ajaxConnect();
|
||||||
|
|
||||||
|
var retfn = returnfn(http,
|
||||||
|
function(resp) {
|
||||||
|
//log("pong!");
|
||||||
|
if (!connected) {
|
||||||
|
connected = true;
|
||||||
|
greet();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(resp) {
|
||||||
|
connected = false;
|
||||||
|
var html = "Connection to local Node failed. Try reloading from <a href=\"" + originURL + "\" target=\"window\">" + originURL + "</a>";
|
||||||
|
|
||||||
|
stl = document.getElementById('statusLabel');
|
||||||
|
if (stl)
|
||||||
|
stl.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
//log("ping!");
|
||||||
|
ajaxSend(http, "cmd=ping\n\n", retfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
log("init");
|
||||||
|
if (connected == false)
|
||||||
|
return;
|
||||||
|
numberOfNodes = 1;
|
||||||
|
if (originURL.substr(0, 4) == "file") {
|
||||||
|
// we are the first
|
||||||
|
//numberOfNodes = 1; // ourself
|
||||||
|
log("Loaded from file");
|
||||||
|
// now we sit and wait
|
||||||
|
} else { // it was http, so we were loaded from another node
|
||||||
|
log("Loaded from " + originURL);
|
||||||
|
originAddress = extractHost(originURL);
|
||||||
|
join_network(originAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Greets the server
|
||||||
|
* so far just gets the server's IP and origin URL
|
||||||
|
*/
|
||||||
|
function greet() {
|
||||||
|
var http = ajaxConnect();
|
||||||
|
|
||||||
|
var retfn = returnfn(http,
|
||||||
|
function(resp) {
|
||||||
|
log("greet returned");
|
||||||
|
var arr = resp.split(" ");
|
||||||
|
localNodeAddr = arr[0];
|
||||||
|
originURL = arr[1];
|
||||||
|
stl = document.getElementById('statusLabel');
|
||||||
|
if (stl)
|
||||||
|
stl.innerHTML = "I am <b>" + localNodeAddr + "</b>";
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
log("send greet msg");
|
||||||
|
ajaxSend(http, "cmd=greet\n\n", retfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill the server
|
||||||
|
function killServer() {
|
||||||
|
announceDead(localNodeAddr);
|
||||||
|
var http = ajaxConnect();
|
||||||
|
|
||||||
|
var retfn = returnfn(http,
|
||||||
|
function(resp) {
|
||||||
|
// got a resp, server not dead
|
||||||
|
killServer();
|
||||||
|
});
|
||||||
|
ajaxSend(http, "cmd=kill\n\n", retfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest server logs
|
||||||
|
function getLog() {
|
||||||
|
var http = ajaxConnect();
|
||||||
|
|
||||||
|
var retfn = returnfn(http,
|
||||||
|
function(resp) {
|
||||||
|
var log = document.getElementById('cortexLog');
|
||||||
|
if (log)
|
||||||
|
log.value = resp + log.value;
|
||||||
|
// Since getLog returns (a call back is called later)
|
||||||
|
// this is tail recursive friendly
|
||||||
|
getLog();
|
||||||
|
});
|
||||||
|
ajaxSend(http, "cmd=getLog\n\n", retfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** Function Registry *****/
|
||||||
|
|
||||||
|
var fnreg = new Array;
|
||||||
|
|
||||||
|
function registerFn(fnname, fn, preplace) {
|
||||||
|
path = fnname.split(".");
|
||||||
|
root = fnreg;
|
||||||
|
for(i=0; i < path.length-1; i++) {
|
||||||
|
if (root[path[i]] == undefined)
|
||||||
|
root[path[i]] = new Array();
|
||||||
|
root = root[path[i]];
|
||||||
|
}
|
||||||
|
if (preplace)
|
||||||
|
root[path[i]] = fn;
|
||||||
|
else {
|
||||||
|
if (root[path[i]] == undefined)
|
||||||
|
root[path[i]] = [fn, null];
|
||||||
|
else {
|
||||||
|
j=0;
|
||||||
|
while(root[path[i]][j] != null)
|
||||||
|
j++;
|
||||||
|
root[path[i]][j] = fn;
|
||||||
|
root[path[i]][j+1] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finds the chain of functions,
|
||||||
|
* calls the nth
|
||||||
|
* stores its return
|
||||||
|
* calls the nth+1 with the return of the nth as well
|
||||||
|
* returns the results of the mth
|
||||||
|
*/
|
||||||
|
function execFn(name, args) {
|
||||||
|
root = fnreg;
|
||||||
|
names = name.split(".");
|
||||||
|
for (i=0; i< names.length; i++) {
|
||||||
|
root = root[names[i]];
|
||||||
|
if (root == undefined)
|
||||||
|
return false; // ERROR, NO FN
|
||||||
|
}
|
||||||
|
i=0;
|
||||||
|
ret = true;
|
||||||
|
while(root[i]) {
|
||||||
|
ret = root[i](args, ret);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***** Messeging between nodes *****/
|
||||||
|
|
||||||
|
|
||||||
|
function addMsgHandler(msgName, handlerFN, handlerReplace) {
|
||||||
|
registerFn("msgHandler." + msgName, handlerFN, handlerReplace);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processMsg(resp) {
|
||||||
|
log("processMsg: " + resp['query']);
|
||||||
|
if (!execFn("msgHandler." + resp["query"], resp))
|
||||||
|
log("Error: Uknnown message '" + resp['query'] + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
function packObject(m, defaultValue) {
|
||||||
|
|
||||||
|
var str = "";
|
||||||
|
for (var i in m) {
|
||||||
|
if (m[i] == '')
|
||||||
|
{
|
||||||
|
if (defaultValue == null) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
m[i] = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == "results") {
|
||||||
|
str += i + "=" + packResults(m[i]) + "\n";
|
||||||
|
} else {
|
||||||
|
str += i + "=" + m[i] + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMsg(m, addr, time, mtype) {
|
||||||
|
if(time == null)
|
||||||
|
time = dlc;
|
||||||
|
if(mtype == null)
|
||||||
|
mtype = "single";
|
||||||
|
|
||||||
|
var str = "cmd=sendMsg\n";
|
||||||
|
str += "origin=" + localNodeAddr + "\n";
|
||||||
|
str += "dlc=" + time + "\n";
|
||||||
|
str += "mtype=" + mtype + "\n";
|
||||||
|
|
||||||
|
str += packObject(m);
|
||||||
|
|
||||||
|
str += "\n";
|
||||||
|
//log ("SEND: " + str);
|
||||||
|
|
||||||
|
var http = ajaxConnect();
|
||||||
|
var retfn = returnfn(http, null,
|
||||||
|
function() {
|
||||||
|
disconnect(addr);
|
||||||
|
announceDead(addr);
|
||||||
|
log("ERROR> message to " + addr + " not delivered: '" + str + "'");
|
||||||
|
});
|
||||||
|
//log("sendMsg: " + str);
|
||||||
|
ajaxSend(http, str, retfn, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bcastMsg(m) {
|
||||||
|
if (m.query != "heartBeat")
|
||||||
|
dlc ++;
|
||||||
|
//log("BCAST conLen: " + connections.length);
|
||||||
|
for(var i=0; i < connections.length; i++) {
|
||||||
|
log("bcast "+ i +" " + m.query + " to " + connections[i] + " dlc:" + dlc);
|
||||||
|
sendMsg(m, connections[i], dlc, "bcast");
|
||||||
|
//log("back from sendMsg, i:" + i + " conlen:" + connections.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMsg(resp) {
|
||||||
|
|
||||||
|
for(var i=msgQ.length-1; i>=0; i--) {
|
||||||
|
|
||||||
|
if (resp.dlc > msgQ[i].dlc) {
|
||||||
|
msgQ[i+1] = msgQ[i];
|
||||||
|
} else {
|
||||||
|
msgQ[i+1] = resp;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msgQ[0] = resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseResp(resp) {
|
||||||
|
var lines = resp.split("\n");
|
||||||
|
var arr = [];
|
||||||
|
for(var i=0; i < lines.length; i++) {
|
||||||
|
//var kv = lines[i].split("=");
|
||||||
|
var mid = lines[i].indexOf("=");
|
||||||
|
var kv = new Array();
|
||||||
|
kv[0] = lines[i].substring(0, mid);
|
||||||
|
kv[1] = lines[i].substring(mid+1);
|
||||||
|
if (kv[1] == "true")
|
||||||
|
kv[1] = true;
|
||||||
|
else if(kv[0] == "false")
|
||||||
|
kv[1] = false;
|
||||||
|
else if (kv[0] == "dlc" ||
|
||||||
|
kv[0] == "start" ||
|
||||||
|
kv[0] == "end" ||
|
||||||
|
kv[0] == "min" ||
|
||||||
|
kv[0] == "max")
|
||||||
|
kv[1] = Number(kv[1]);
|
||||||
|
else if (kv[0] == "results")
|
||||||
|
kv[1] = unpackResults(kv[1]);
|
||||||
|
arr[kv[0]] = kv[1];
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueMsgs(mstr) {
|
||||||
|
log("GOT messages: " + mstr);
|
||||||
|
var resps = mstr.split("\n\n");
|
||||||
|
for(var rcount=0; rcount < resps.length; rcount++) {
|
||||||
|
var resp = resps[rcount];
|
||||||
|
if (resp == "")
|
||||||
|
continue;
|
||||||
|
resp = parseResp(resp);
|
||||||
|
if (resp.query == "DLC") {
|
||||||
|
dlc = resp.dlc;
|
||||||
|
//var m = new Object();
|
||||||
|
//m.query = "DLC";
|
||||||
|
//bcastMsg(m);
|
||||||
|
} else {
|
||||||
|
addMsg(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var s = "QUEUE: "
|
||||||
|
for (var i =0; i< msgQ.length; i++) {
|
||||||
|
s += msgQ[i].dlc + " ";
|
||||||
|
}
|
||||||
|
log(s);
|
||||||
|
log("mqlen:" + msgQ.length);
|
||||||
|
while (msgQ.length>0 && ((msgQ[msgQ.length-1].dlc - 1) <= dlc)) {
|
||||||
|
var r = msgQ.pop();
|
||||||
|
log("mdlc: " + r.dlc + " dlc: " + dlc + " mqlen: " + msgQ.length + " type:" + r.mtype + " query: " + r.query);
|
||||||
|
if (r.mtype == "bcast")
|
||||||
|
dlc = r.dlc
|
||||||
|
processMsg(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMsgs() {
|
||||||
|
var http = ajaxConnect();
|
||||||
|
var retfn = returnfn(http,
|
||||||
|
function(resp) {
|
||||||
|
//log("getMsgs: " + resp);
|
||||||
|
queueMsgs(resp);
|
||||||
|
// Since getMsgs returns (a call back is called later)
|
||||||
|
// this is tail recursive friendly
|
||||||
|
getMsgs();
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
log("ERROR: getMsg RETURN FAIL");
|
||||||
|
});
|
||||||
|
log("getMsgs");
|
||||||
|
ajaxSend(http, "cmd=getMsgs\n\n", retfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***** P2P Network Communication *****/
|
||||||
|
|
||||||
|
function connectedTo(addr) {
|
||||||
|
if (addr == localNodeAddr)
|
||||||
|
return true;
|
||||||
|
for(i = 0; i < connections.length; i++) {
|
||||||
|
if (connections[i] == addr)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addConnection(addr) {
|
||||||
|
if(!connectedTo(addr)) {
|
||||||
|
connections[connections.length] = addr;
|
||||||
|
numberOfNodes++;
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function join_network(address, bcast) {
|
||||||
|
if(bcast == null) {
|
||||||
|
bcast = true;
|
||||||
|
}
|
||||||
|
log("join_network -> " + address);
|
||||||
|
var m = new Object();
|
||||||
|
m["query"] = "join_network";
|
||||||
|
m["addr"] = localNodeAddr;
|
||||||
|
//m["nodes"] = genNodeList();
|
||||||
|
sendMsg(m, address);
|
||||||
|
if (bcast == true) {
|
||||||
|
m = new Object();
|
||||||
|
m["query"] = "new_network";
|
||||||
|
m["addr"] = address;
|
||||||
|
bcastMsg(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function genNodeList() {
|
||||||
|
var str = "";//localNodeAddr+",";
|
||||||
|
for (var i =0; i < connections.length; i++) {
|
||||||
|
str += connections[i] + ",";
|
||||||
|
}
|
||||||
|
if (connections.length > 0)
|
||||||
|
str = str.substring(0,str.length-1);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function advertiseNewNode(addr) {
|
||||||
|
var m = new Object();
|
||||||
|
m['query'] = "newNode";
|
||||||
|
m['addr'] = addr;
|
||||||
|
bcastMsg(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
function announceDead(addr) {
|
||||||
|
var m = new Object();
|
||||||
|
m['query'] = "deadNode";
|
||||||
|
m['addr'] = addr;
|
||||||
|
bcastMsg(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendDLC(addr) {
|
||||||
|
var m = new Object();
|
||||||
|
m.query = "DLC";
|
||||||
|
sendMsg(m, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendWelcome(addrList, addr) {
|
||||||
|
var m = new Object();
|
||||||
|
m["query"] = "welcome";
|
||||||
|
m["addrList"] = addrList;
|
||||||
|
sendMsg(m, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function heartBeat() {
|
||||||
|
var m = new Object();
|
||||||
|
m.query = "heartBeat";
|
||||||
|
bcastMsg(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadHTML() {
|
||||||
|
var http = ajaxConnect();
|
||||||
|
var retfn = returnfn(http);
|
||||||
|
ajaxSend(http, "cmd=reloadHTML\n\n",retfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
function poke() {
|
||||||
|
var http = ajaxConnect();
|
||||||
|
document.getElementById("pingNodeResp").innerHTML = "";
|
||||||
|
var dest = document.getElementById("pingNode").value;
|
||||||
|
|
||||||
|
var retfn = returnfn(http,
|
||||||
|
function(resp) {
|
||||||
|
document.getElementById("pingNodeResp").innerHTML += resp + "<br>";
|
||||||
|
var retfn = returnfn(http,
|
||||||
|
function(resp) {
|
||||||
|
document.getElementById("pingNodeResp").innerHTML += resp + "<br>";
|
||||||
|
});
|
||||||
|
ajaxSend(http, "cmd=greet\n\n", retfn, dest);
|
||||||
|
});
|
||||||
|
|
||||||
|
ajaxSend(http, "cmd=ping\n\n", retfn, dest);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***** Msg Handlers *****/
|
||||||
|
|
||||||
|
function handleNewNode(resp, welcome) {
|
||||||
|
|
||||||
|
if (welcome == null)
|
||||||
|
welcome = false;
|
||||||
|
var addr = resp.addr;
|
||||||
|
//log2("handle new node " + addr + " from " + resp.origin);
|
||||||
|
|
||||||
|
// handles re adding if reloaded html
|
||||||
|
//if (!connectedTo(addr))
|
||||||
|
//{
|
||||||
|
var ret = genNodeList();
|
||||||
|
var n = addConnection(addr);
|
||||||
|
if (welcome == true) {
|
||||||
|
sendDLC(addr);
|
||||||
|
sendWelcome(ret, addr);
|
||||||
|
//sendLocks(addr);
|
||||||
|
//sendTests(addr);
|
||||||
|
}
|
||||||
|
if (n == true) {
|
||||||
|
//sendReqs(addr);
|
||||||
|
advertiseNewNode(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addMsgHandler( "join_network",
|
||||||
|
function (resp) {
|
||||||
|
handleNewNode(resp, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
addMsgHandler( "newNode",
|
||||||
|
function (resp) {
|
||||||
|
handleNewNode(resp, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
addMsgHandler( "welcome",
|
||||||
|
function (resp) {
|
||||||
|
log("Welcome message received");
|
||||||
|
//var myNodes = genNodeList();
|
||||||
|
addConnection(resp['origin']);
|
||||||
|
if (resp['addrList']){
|
||||||
|
var nodes = resp['addrList'].split(",");
|
||||||
|
for(var i=0; i < nodes.length; i++){
|
||||||
|
addConnection(nodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i =0; i< connections.length; i++) {
|
||||||
|
var addr = connections[i];
|
||||||
|
if (addr == localNodeAddr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
advertiseNewNode(addr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addMsgHandler( "deadNode",
|
||||||
|
function (resp) {
|
||||||
|
var addr = resp['addr'];
|
||||||
|
log("DISCONNECT " + addr);
|
||||||
|
// remove from connumberOfNodesnections list
|
||||||
|
for (var i=0; i < connections.length; i++) {
|
||||||
|
if (connections[i] == addr) {
|
||||||
|
connections.splice(i,1);
|
||||||
|
numberOfNodes--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addMsgHandler( "new_network",
|
||||||
|
function (resp) {
|
||||||
|
join_network(resp["addr"], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
addMsgHandler( "heartBeat",
|
||||||
|
function (resp) {
|
||||||
|
// do nothing
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/***** CRON System *****/
|
||||||
|
|
||||||
|
// list of intervals
|
||||||
|
var crontabs = new Array();
|
||||||
|
|
||||||
|
/* add a function to be called repeatedly
|
||||||
|
* interval - in 1/10s of a second
|
||||||
|
* fn - function to be called
|
||||||
|
*/
|
||||||
|
function addCronTab(interval, fn) {
|
||||||
|
crontabs[interval] = true;
|
||||||
|
registerFn("cron."+interval, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cronI = 0;
|
||||||
|
|
||||||
|
function cron() {
|
||||||
|
if (connected == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (time in crontabs) {
|
||||||
|
if (cronI % Number(time) == 0) {
|
||||||
|
execFn("cron."+ time, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cronI += 1;
|
||||||
|
// Reset to 0 after a day
|
||||||
|
if (cronI > 864000) {
|
||||||
|
cronI = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCronTab(10, ping);
|
||||||
|
addCronTab(300, heartBeat);
|
||||||
|
|
||||||
|
|
||||||
|
/***** UI *****/
|
||||||
|
function setStatus() {
|
||||||
|
var nn = document.getElementById('numberOfNodes');
|
||||||
|
if (nn)
|
||||||
|
nn.innerHTML = "Number of nodes: " + numberOfNodes;
|
||||||
|
var c = document.getElementById('connections');
|
||||||
|
if (c)
|
||||||
|
c.innerHTML = "Connected to: " + genNodeList();
|
||||||
|
var dlcE = document.getElementById('dlc');
|
||||||
|
if (dlcE)
|
||||||
|
dlcE.innerHTML = "DLC: " + dlc;
|
||||||
|
}
|
||||||
|
|
||||||
|
addCronTab(10, setStatus);
|
||||||
|
|
||||||
|
/**** INIT *****/
|
||||||
|
|
||||||
|
function cortex_start(debug) {
|
||||||
|
if(debug)
|
||||||
|
logging = true;
|
||||||
|
ping();
|
||||||
|
var cronID = setInterval("cron()", 100);
|
||||||
|
getMsgs();
|
||||||
|
getLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/********** AND THAT's THE BASIS OF THE CORTEX P2P NETWORK **********/
|
||||||
|
/* We now have dumb do nothing nodes that can connect to each other
|
||||||
|
* and maintain state */
|
||||||
|
/********** NOW TO ADD SOME USEFUL THINGS ON TOP OF IT **********/
|
|
@ -0,0 +1,419 @@
|
||||||
|
/* cortexLocks.js
|
||||||
|
* PURPOSE
|
||||||
|
* AUTHOR
|
||||||
|
* LICENSE
|
||||||
|
*
|
||||||
|
* NOTES
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* locks -> [ class: [type, locks], ] */
|
||||||
|
var locks = new Array();
|
||||||
|
/* -> [ class : lock] (you can only req/acq 1 lock per class */
|
||||||
|
var locksReq = new Array();
|
||||||
|
var locksAcq = new Array();
|
||||||
|
|
||||||
|
function copyobj(arr) {
|
||||||
|
c = new Object();
|
||||||
|
for (i in arr) {
|
||||||
|
c[i] = arr[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function locksClassType(klass) {
|
||||||
|
return locks[klass]['type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function locksClassLocks(klass) {
|
||||||
|
return locks[klass]['locks'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerLockFn(ltype, fnName, fn) {
|
||||||
|
registerFn("locks." +ltype+"."+fnName, fn);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
function execLockFn(fnName, args) {
|
||||||
|
type = locksClassType(args['class']);
|
||||||
|
args['type'] = type;
|
||||||
|
locks = locksClassLocks(args['class']);
|
||||||
|
args['locks'] = locks;
|
||||||
|
return execFn("locks."+type+"."+fnName, args);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
function locksInitClass(klass, type) {
|
||||||
|
locks[klass]['type'] = type;
|
||||||
|
locks[klass]['locks'] = new Array();
|
||||||
|
|
||||||
|
locksReq[klass] = new Array();
|
||||||
|
locksAcq[klass] = new Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**** Core Lock Functions
|
||||||
|
* get(table, info) BR -> getLock
|
||||||
|
* equal(a, b) - > locksEqual
|
||||||
|
* ?msg(lock)
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getLock(info) {
|
||||||
|
type = locksClassType(info['class']);
|
||||||
|
info['type'] = type;
|
||||||
|
locks = locksClassLocks(info['class']);
|
||||||
|
info['locks'] = locks;
|
||||||
|
return execFn("locks."+type+".get", info);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerLockFn("basic", "get",
|
||||||
|
function (args) {
|
||||||
|
lock = args['locks'][args['name']];
|
||||||
|
if ( ! lock.name) {
|
||||||
|
lock.name =args['name'];
|
||||||
|
lock.locked = false;
|
||||||
|
lock.type = 'basic';
|
||||||
|
}
|
||||||
|
return lock;
|
||||||
|
});
|
||||||
|
|
||||||
|
function genRangeCell(name, start, end, next) {
|
||||||
|
var c = new Object();
|
||||||
|
c.name = name;
|
||||||
|
c.type = 'range';
|
||||||
|
c.start=start;
|
||||||
|
c.end = end;
|
||||||
|
c.next = next;
|
||||||
|
c.locked = false;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRangeLock(rangeLocks, name, start, end) {
|
||||||
|
log("getRangeLock for (" + name + " from " + start + " to " + end + ")");
|
||||||
|
if (!rangeLocks[name]) {
|
||||||
|
log("no locks for " + name + ", so MAKING");
|
||||||
|
rangeLocks[name] = genRangeCell(name, start, end);
|
||||||
|
return rangeLocks[name];
|
||||||
|
}
|
||||||
|
log("locks exist for " + name + " so SEARCHING forward");
|
||||||
|
var rlock = rangeLocks[name];
|
||||||
|
|
||||||
|
if(end < rlock.start) {
|
||||||
|
log("Space before first lock, MAKING HERE");
|
||||||
|
rangeLocks[name] = genRangeCell(name, start, end);
|
||||||
|
rangeLocks[name].next = rlock;
|
||||||
|
return rangeLocks[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
while(rlock.next && rlock.start < rlock.end+1) {
|
||||||
|
log("looking at (" + rlock.start + " to " + rlock.end + ")");
|
||||||
|
if (rlock.start == start && rlock.end == end) {
|
||||||
|
log("FOUND IT");
|
||||||
|
return rlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rlock.end < start && rlock.next.start > end) {
|
||||||
|
log("GAP between (" + rlock.start + " to " + rlock.end + ") and (" + rlock.next.start + " to " + rlock.next.end + ") where we should be so MAKING THERE");
|
||||||
|
var nlock = rlock.next;
|
||||||
|
rlock.next = genRangeCell(name, start, end);
|
||||||
|
rlock.next.next = nlock;
|
||||||
|
return rlock.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
rlock = rlock.next;
|
||||||
|
}
|
||||||
|
log("SEARCH ended");
|
||||||
|
if (rlock.start == start && rlock.end == end) {
|
||||||
|
log("Found it!");
|
||||||
|
return rlock;
|
||||||
|
} else {
|
||||||
|
if (rlock.end < start) {
|
||||||
|
log("There is space at the end to make what we want");
|
||||||
|
rlock.next = genRangeCell(name, start, end);
|
||||||
|
return rlock.next;
|
||||||
|
} else {
|
||||||
|
log("search ended after what we wanted, FAIL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
registerLockFn("range", "get",
|
||||||
|
function (args) {
|
||||||
|
var l = getRangeLock(arg['locks'] , args['name'], args['start'], args'[end']);
|
||||||
|
if (l == null)
|
||||||
|
error("retrieveLock for range got null");
|
||||||
|
return l;
|
||||||
|
});
|
||||||
|
|
||||||
|
function lockTypesEqual(a, b) {
|
||||||
|
if(a == null || b == null)
|
||||||
|
return false;
|
||||||
|
if(a['type'] != b['type'])
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerLockFn("basic", "equals",
|
||||||
|
function(args) { // locksEqual(a, b)
|
||||||
|
var a = args['a'];
|
||||||
|
var b = arbs['b'];
|
||||||
|
if (!lockTypesEqual(a, b))
|
||||||
|
return false;
|
||||||
|
if (a.name == b.name)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
registerLockFn("range", "equals",
|
||||||
|
function(args) { // locksEqual(a, b)
|
||||||
|
var a = args['a'];
|
||||||
|
var b = arbs['b'];
|
||||||
|
if (!lockTypesEqual(a, b))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
log("locksEqual? " + a.name + " (" + a.start + " to " + a.end + ") and " + b.name + " (" + b.start + " to " + b.end + ")");
|
||||||
|
if (a.name == b.name && a.start == b.start && a.end == b.end) {
|
||||||
|
log("true");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log("false");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
functions locksEqual(a, b) {
|
||||||
|
args = new Array();
|
||||||
|
args['a'] = a;
|
||||||
|
args['b'] = b;
|
||||||
|
return execFn("locks."+a['type']+".equals", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*** Non Networked Lock functions
|
||||||
|
* lock
|
||||||
|
* release
|
||||||
|
* isLocked
|
||||||
|
* removeLocks
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isLocked(lock) {
|
||||||
|
return lock.locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lock(lock) {
|
||||||
|
lock['locked'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function release(lock) {
|
||||||
|
lock['locked'] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/************** REVIEW HERE ******************/
|
||||||
|
|
||||||
|
registerLockFn("basic", "remove",
|
||||||
|
function(args) {
|
||||||
|
for(var i in args['locks']) {
|
||||||
|
var lock = args['locks'][i];
|
||||||
|
if (!lock)
|
||||||
|
continue;
|
||||||
|
if (lock.addr == addr) {
|
||||||
|
lock.locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
registerLockFn("range", "remove",
|
||||||
|
function(args) {
|
||||||
|
for(var i in args['locks']) {
|
||||||
|
log("freeing " + i + " locks");
|
||||||
|
var rlock = args['locks'][i];
|
||||||
|
while (rlock != null) {
|
||||||
|
log("looking at " + i + " (" + rlock.start + " to " + rlock.end + ") : " + rlock.addr);
|
||||||
|
if (rlock.addr == args['addr']) {
|
||||||
|
log("unlocking");
|
||||||
|
rlock.locked = false;
|
||||||
|
}
|
||||||
|
rlock = rlock.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function genLockTry(lockTry) {
|
||||||
|
lockTry.time = getTime();
|
||||||
|
lockTry.dlc = dlc+1;
|
||||||
|
lockTry.name = name;
|
||||||
|
lockTry.addr = localNodeAddr;
|
||||||
|
lockTry.type = type;
|
||||||
|
lockTry.okay = 1;
|
||||||
|
lockTry.locked = false;
|
||||||
|
var d = new Date();
|
||||||
|
lockTry.localTime = d.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
function genGetLockMsg() {
|
||||||
|
var m = new Object();
|
||||||
|
m["query"] = "getLock";
|
||||||
|
m["time"] = lockTry.time;
|
||||||
|
m["addr"] = localNodeAddr;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
registerLockFn("basic", "release",
|
||||||
|
function (args) {
|
||||||
|
var lock = myLocks[type];
|
||||||
|
myLocks[type] = null;
|
||||||
|
|
||||||
|
lock.locked = false;
|
||||||
|
|
||||||
|
|
||||||
|
var m = new Object();
|
||||||
|
m["query"] = "releaseLock";
|
||||||
|
m["type"] = type;
|
||||||
|
m["name"] = lock.name;
|
||||||
|
if (type == "range") {
|
||||||
|
m["start"] = lock.start;
|
||||||
|
m["end"] = lock.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
bcastMsg(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**** Netowkr Lock using functions
|
||||||
|
* aquire
|
||||||
|
* grant(resp, lock
|
||||||
|
* deny(resp, lock
|
||||||
|
* handleLockReq
|
||||||
|
* handleLockResp
|
||||||
|
* lockGranted
|
||||||
|
* checkLocks
|
||||||
|
* ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
registerLockFn("basic", "aquire",
|
||||||
|
function(args) {
|
||||||
|
var locks = args['locks'];
|
||||||
|
var name = args['name'];
|
||||||
|
var lockTry;
|
||||||
|
|
||||||
|
if(locks[name] == null) {
|
||||||
|
locks[name] = new Object();
|
||||||
|
}
|
||||||
|
lockTry = locks[name];
|
||||||
|
|
||||||
|
genLockTry(lockTry);
|
||||||
|
|
||||||
|
lockTrys[args['family']] = lockTry;
|
||||||
|
setTestStatus("Acquiring lock for test '" + name + "' " + lockTry.okay + "/" + numberOfNodes + "...", "yellow");
|
||||||
|
|
||||||
|
var m = getLockMsg();
|
||||||
|
m["name"] = name;
|
||||||
|
m["type"] = "basic";
|
||||||
|
|
||||||
|
bcastMsg(m);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerLockFn("range", "aquire",
|
||||||
|
function(args) {
|
||||||
|
var name = args['name'];
|
||||||
|
var start = args['start'];
|
||||||
|
var end = args['end'];
|
||||||
|
|
||||||
|
lockTry = getRangeLock(args['locks'], name, start, end);
|
||||||
|
if (!lockTry) {
|
||||||
|
error("Cannot get range lock in getLock(" + name +", "+ start + ", " + end + ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
genLockTry(lockTry);
|
||||||
|
|
||||||
|
lockTrys[args['family']] = lockTry;
|
||||||
|
setWorkStatus("Acquiring lock for work '" + name + "' (" + lockTry.start + " to " + lockTry.end + ")" + lockTry.okay + "/" + numberOfNodes + "...", "yellow");
|
||||||
|
|
||||||
|
var m = getLockMsg();
|
||||||
|
m["name"] = name;
|
||||||
|
m["type"] = "range";
|
||||||
|
|
||||||
|
|
||||||
|
m["start"] = start;
|
||||||
|
m["end"] = end;
|
||||||
|
|
||||||
|
bcastMsg(m);
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendReqs(addr) {
|
||||||
|
for(ltype in lockTrys) {
|
||||||
|
for (i in lockTrys[ltype]) {
|
||||||
|
var lock = lockTrys[ltype][i];
|
||||||
|
if (lock == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var m = copyobj(lock);
|
||||||
|
m["query"] = "getLock";
|
||||||
|
m["type"] = ltype;
|
||||||
|
sendMsg(m, addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendLocks(addr) {
|
||||||
|
for (ltype in locks) {
|
||||||
|
for (var i in locks[ltype]) {
|
||||||
|
log ("testLog[" + i + "]");
|
||||||
|
var l = testLocks[ltype][i];
|
||||||
|
var m = copyobj(l);
|
||||||
|
|
||||||
|
m["query"] = "addLock";
|
||||||
|
m["type"] = ltype;
|
||||||
|
|
||||||
|
sendMsg(m, addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAddLock(resp) {
|
||||||
|
if(resp['locked'] == null)
|
||||||
|
resp['locked'] = false;
|
||||||
|
// dont add/overwrite if exists?
|
||||||
|
var lock = retrieveLock(resp);
|
||||||
|
|
||||||
|
if (resp['type'] == "test" ) {//&& (lock == null || lock.addr == null)) {
|
||||||
|
var locks = getLockType(resp['type']);
|
||||||
|
|
||||||
|
|
||||||
|
locks[resp['name']] = resp
|
||||||
|
if(resp['running'] == true) {
|
||||||
|
runningTests.push(resp['name']);
|
||||||
|
}
|
||||||
|
} else if(resp['type'] == "range" && lock.addr == null) {
|
||||||
|
log("Add range lock: " + resp['name'] + " (" + resp['start'] + " to " + resp['end'] + ") : ");
|
||||||
|
var lock = getRangeLock(LOCKS, resp['name'], resp['start'], resp['end']);
|
||||||
|
|
||||||
|
lock.addr = resp['addr'];
|
||||||
|
lock.locked = resp['locked'];
|
||||||
|
lock.done = resp['done'];
|
||||||
|
lock.results = resp['results'];
|
||||||
|
lock.name = resp['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessageHandler("addLock", handlerAddLock());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function removeNodeLocks(addr) {
|
||||||
|
removeNodeRangeLocks(resp['addr']);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMsgHandler("deadNode",
|
||||||
|
function (resp) {
|
||||||
|
BASIC
|
||||||
|
|
||||||
|
removeNodeLocks(resp['addr']);
|
||||||
|
});
|
14
shell.html
14
shell.html
|
@ -3,24 +3,12 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Client</title>
|
<title>Client</title>
|
||||||
|
|
||||||
<script src="ajax.js" ></script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function load() {
|
function load() {
|
||||||
port = document.getElementById('port').value;
|
port = document.getElementById('port').value;
|
||||||
document.getElementById('client').src = "http://localhost:" + port + "/client.html";
|
document.getElementById('client').src = "http://localhost:" + port + "/basicClient.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLoaded() {
|
|
||||||
if(document.getElementById('cortex').isActive())
|
|
||||||
{
|
|
||||||
load();
|
|
||||||
clearInterval();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
Loading…
Reference in New Issue