You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

333 lines
8.1 KiB

var http = require('http');
var process = require('child_process');
var WebSocketServer = require("ws").Server;
var events = require("events");
var jsoncompress = require('jsoncompress');
var net = require('net');
var fs = require('fs');
var config = require('./config');
var logger = require("./logger");
var _cycle = require("./cycle");
var CycleBuffer = require("./graphBuffer");
/// TODO: Buffer cycles. Clear them out every 24 hours
/// TODO: Add total aggregate (url /total) that can be served
function timeAverage(times)
{
if(times.length<1) return 0;
else if(times.length==1) return times[0];
else {
var cul=0;
for(var i=0;i<times.length;i++)
{
cul+=times[i];
}
return cul/times.length;
}
}
function diff(t1, t2)
{
if( t1>t2) return t1-t2;
else return t2-t1;
}
function bein(hay,nee)
{
return (nee in hay) || (("has_"+nee) in hay);
}
function createCandle(buffer)
{
var cdl = {
number: {
o:0,
c:0,
h:0,
l:0
}
};
if(buffer.length<1) return cdl;
if(buffer.length<2) {
cdl.number.c = cdl.number.o = cdl.number.h = cdl.number.l = buffer[0].number;
return cdl;
}
cdl.number.l = -1;
for(var i in buffer)
{
if(buffer[i].number>cdl.number.h)
cdl.number.h = buffer[i].number;
if(buffer[i].number<cdl.number.l||cdl.number.l==-1)
cdl.number.l = buffer[i].number;
}
cdl.number.o = buffer[0].number;
cdl.number.c = buffer[buffer.length-1].number;
return cdl;
}
var lastNumber = 0; /*we don't really need this, but it can fix some things*/
var lastCycle=new _cycle.Cycle();
var allTime = new _cycle.Cycle();
var allTimeAverage = new _cycle.Cycle();
var allTimeAverageN = 0;
var buffer = new CycleBuffer(config.BUFFER_PURGE_TIME);
var superbuffer = new CycleBuffer(config.SUPERBUFFER_SIZE, true);
var beInfo = null;
var upSince = new Date();
cycleEmitter = new events.EventEmitter();
buffer.onclear = function(bu) {
logger.log("Clearing and adding supercycle");
var ac = _cycle.Cycle.accumulate(bu.buffer);
ac._cycles = bu.buffer.length;
ac._candle = createCandle(bu.buffer);
superbuffer.add(ac);
cycleEmitter.emit("newSuperCycle", ac);
};
superbuffer.onclear = function(bu) {
logger.log("Clearing superbuffer");
};
logger.log("Buffer set to clear every "+(config.BUFFER_PURGE_TIME/1000/60)+" minutes");
logger.log("Superbuffer set to fill at "+(config.SUPERBUFFER_SIZE)+" entries");
process.execFile(config.PYTHON_EXECUTABLE, [config.RTBWCTL_FILENAME, config.BACKEND_SOCKET, "info"], function(error,stdout,stderr) {
try {
beInfo = JSON.parse(stdout);
} catch(e) {
logger.log("Recieved invalid JSON from info backend call.");
return;
}
logger.log("Backend info received");
});
var server = http.createServer(function(req, res) {
if(req.url == config.URL_BASE+"/cycle") {
logger.log("Recieved cycle request");
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(JSON.stringify(lastCycle));
}
else if(req.url == config.URL_BASE+"/info") {
logger.log("Recieved info request");
if(!beInfo) {
res.writeHead(500);
res.end("");
}
else {
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(JSON.stringify({
interval: config.POLL_INTERVAL,
purgeInterval: config.BUFFER_PURGE_TIME,
superPurgeInterval: config.SUPERBUFFER_SIZE,
backendInterval: beInfo.timeout,
board: beInfo.board,
upSince: upSince
}));
}
}
else if(req.url == config.URL_BASE+"/template")
{
logger.log("Recieved template request.");
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(JSON.stringify(new _cycle.Cycle()));
}
else if(req.url == config.URL_BASE+"/all/average")
{
logger.log("Recieved all time average request.");
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
allTimeAverage._n = allTimeAverageN;
res.end(JSON.stringify(allTimeAverage));
}
else if(req.url == config.URL_BASE+"/all")
{
logger.log("Recieved all time stats request.");
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(JSON.stringify(allTime));
}
else if(req.url == config.URL_BASE+"/buffer")
{
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(JSON.stringify(buffer.all()));
}
else if(req.url == config.URL_BASE+"/buffer/super")
{
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(JSON.stringify(superbuffer.all()));
}
else if(req.url.startsWith(config.URL_BASE+"/buffer/"))
{
var lst = req.url.split("/").slice(-1)[0];
if(!isNaN(lst))
{
lst = Number(lst);
var bcpy = buffer.all();
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
if(bcpy.length<=lst)
res.end(JSON.stringify(bcpy));
else
res.end(JSON.stringify(bcpy.slice(-lst)));
}
else {
res.writeHead(422);
res.end("Invalid request");
}
}
else {
res.writeHead(404);
res.end("404");
}
});
var wss = new WebSocketServer({
server: server,
autoAcceptConnections: false
});
wss.on('request', function(req) { //why isn't this getting called?
var url = require('url').parse(req.httpRequest.url);
if(url!=config.URL_BASE+"/cycle") return req.reject();
return req.accept();
});
wss.on('connection', function (ws) {
var f = function(cycle) {
ws.send(JSON.stringify({type: "cycle", data: cycle}));
};
var g = function(supercycle) {
ws.send(JSON.stringify({type: "supercycle", data: supercycle}));
};
logger.log("New connection");
ws.on("close", function(x,y) {
cycleEmitter.removeListener("newCycle", f);
cycleEmitter.removeListener("newSuperCycle", g);
logger.log("Connection closed");
});
cycleEmitter.addListener("newCycle", f);
cycleEmitter.addListener("newSuperCycle", g);
});
server.listen(config.PORT);
logger.log("Listening on port "+config.PORT);
function addNames(cycle, json)
{
var str = "Anonymous";
if("name" in json)
str = json["name"];
if("trip" in json)
str = str+json["trip"];
if(str!="Anonymous")
{
if(str in cycle.namefags)
cycle.namefags[str] += 1;
else
cycle.namefags[str] = 1;
}
}
var backend = setInterval(function() {
//logger.log("Polling backend");
process.execFile(config.PYTHON_EXECUTABLE, [config.RTBWCTL_FILENAME, config.BACKEND_SOCKET, "get-clear", "--data", lastNumber], function(error,stdout,stderr) {
var json = undefined;
try {
json = JSON.parse(stdout);
} catch(e) {
logger.log("Recieved invalid JSON from backend.");
return;
}
var cycle = new _cycle.Cycle();
var timeInters= [];
for (var i=0; i<json.length;i++)
{
if(json[i].no>lastNumber)
lastNumber = json[i].no;
cycle.number+=1;
if(json[i].country in cycle.countries)
cycle.countries[json[i].country]+=1;
else
cycle.countries[json[i].country]=1;
if(! ("thread" in json[i]))
cycle.threads+=1;
if(i<json.length-1)
{
timeInters.push(diff(Date.parse(json[i].time), Date.parse(json[i+1].time)));
}
if(bein(json[i], "com"))
cycle.comments+=1;
if(bein(json[i], "sub"))
cycle.subjects+=1;
if(bein(json[i], "id") && json[i]["id"] != "")
cycle.ids +=1;
if(bein(json[i], "filename"))
cycle.images+=1;
if(bein(json[i], "name") && bein(json[i], "trip"))
cycle.nametrips+=1;
else if(bein(json[i], "name"))
cycle.names+=1;
else if(bein(json[i], "trip"))
cycle.trips+=1;
addNames(cycle, json[i]);
}
cycle.interval = timeAverage(timeInters);
cycle.last = lastNumber;
lastCycle=cycle;
buffer.add(cycle);
allTime = _cycle.Cycle.accumulate([allTime, cycle]);
allTimeAverage = _cycle.Cycle.average(allTimeAverage, [cycle], allTimeAverageN);
allTimeAverageN += 1;
cycleEmitter.emit("newCycle", lastCycle);
});
}, config.POLL_INTERVAL);
/*var ctlServer = net.createServer();
if(!!config.CTL_SOCKET) {
ctlServer.listen(config.CTL_SOCKET, function() {
logger.log("Control server up on socket "+config.CTL_SOCKET);
});
ctlServer.on('connection', function(con) {
logger.log("Connection");
con.on('data', function (data) {
logger.log("CTL: "+data);
});
});
ctlServer.on('error', function(e) {
logger.log("Control server error ["+config.CTL_SOCKET+"]: "+e.code);
ctlServer.close();
});
}*/