// ------------------------------------------------------------------
// $network
//
// This object functions as a placeholder.
new object $network: $root;
var $root objname = 'network;
var $root inited = 1;
// ------------------------------------------------------------------
// $daemon
new object $daemon: $network;
var $root objname = 'daemon;
var $root inited = 1;
var default_port = 1138;
var connection = 0;
var next_connection = 0;
var port = 0;
public method stop_listening {
arg [port];
if (connection)
connection.daemon_shutdown();
next_connection = 0;
};
public method start_listening {
arg [port];
(| .stop_listening() |);
port = [@port, default_port][1];
next_connection = connection.new();
(> bind_port(port, next_connection) <);
};
public method new_connection {
var new_connection;
if (!valid(next_connection))
next_connection = connection.new();
new_connection = next_connection;
// This should fork, once the driver will allow it
new_connection.start();
// bind to the next connection
next_connection = connection.new();
(> bind_port(port, next_connection) <);
};
public method startup {
arg [args];
var name, opt;
catch any {
name = tostr(.objname('symbol));
name = "p" + name.subrange(1, ("_" in name) - 1);
args = $parse.getopt(args.to_string(), [[name, 1]]);
opt = name in args[2].slice(1);
port = (| toint(args[opt][4]) |) || default_port;
(| .stop_listening() |);
(> .start_listening(port) <);
.log("** Starting " + .objname() + " on port " + tostr(port) + " **");
} with handler {
switch (error()) {
case ~bind:
.log("** Unable to bind to port " + tostr(port) + "! **");
default:
.log($parse.traceback(traceback()));
}
}
};
public method port {
return port;
};
public method shutdown {
arg [args];
(> .stop_listening() <);
};
// ------------------------------------------------------------------
// $connection
new object $connection: $network;
var $root objname = 'connection;
var $root inited = 1;
var buffer = `[];
var host = "";
var daemon = 0;
var active = 0;
var line_buffer = 0;
var interface = 0;
var read_block = [];
var started_at = 0;
var port = 0;
root method init_connection {
buffer = `[];
host = "";
line_buffer = [];
};
root method uninit_connection {
(| close_connection() |);
active = 0;
if (interface)
(| interface.connection_going_away(.address(), daemon.port()) |);
interface = 0;
};
private method set_host {
arg host;
set_var('host, host);
};
private method new_interface {
arg interface;
set_var('interface, interface);
};
private method set_daemon {
arg daemon;
set_var('daemon, daemon);
};
public method new {
var child, port, i;
child = .spawn();
port = sender().port();
child.set_daemon(sender());
child.set_port(port);
child.new_interface(interface.new(child));
return child;
};
public method daemon_shutdown {
var c;
for c in (.children())
(> c.close() <);
};
public method address {
return host;
};
public method change_interface {
arg new;
var old;
if (interface) {
old = interface;
old.connection_going_away(.address(), port);
}
interface = new;
interface.connection_starting(.address(), port);
};
public method write {
arg what, [how];
var elem, sep;
sep = 'non_terminated in how ? `[] | `[10];
switch (type(what)) {
case 'string:
what = $buffer.from_strings([what], sep);
case 'list:
what = $buffer.from_strings(what, sep);
case 'buffer:
default:
throw(~type, "Write: strings, list of strings and buffers.");
}
echo(what);
};
public method parse {
arg incoming;
var lines, line, index;
lines = buffer.append(incoming).to_strings();
index = lines.length();
buffer = lines[index];
lines = lines.delete(index);
line_buffer = [@line_buffer, @lines];
while (line_buffer) {
line = line_buffer[1];
line_buffer = line_buffer.delete(1);
(| .parse_line(line) |);
}
};
private method parse_line {
arg line;
if (read_block) {
read_block = read_block.parse(line);
line = .rehash_read_status();
if (!line && line != "")
return;
}
if (interface.parse_line(line) == 'disconnect)
(> .close() <);
};
public method echo_file {
arg fname;
(> echo_file(fname) <);
};
public method is_reading_block {
return read_block ? 1 | 0;
};
public method finish_reading_block {
var task_id, lines;
task_id = read_block.task_id();
lines = read_block.lines();
read_block = 0;
resume(task_id, lines);
};
public method abort_reading_block {
read_block = read_block.add('lines, 'aborted);
.finish_reading_block();
};
public method start_reading_block {
arg count;
read_block = $read_parser.new(task_id(), count);
return suspend();
};
private method rehash_read_status {
switch (read_block.status()) {
case 'abort:
.abort_reading_block();
case 'not_done:
// do nothing
case 'done:
.finish_reading_block();
case 'pass_command:
return read_block.command();
}
return 0;
};
public method close {
(> .destroy() <);
};
driver method disconnect {
arg [args];
(| .close() |);
};
private method set_port {
arg port;
set_var('port, port);
};
public method active {
return active;
};
driver method connect {
arg host, socket;
set_var('host, host);
daemon.new_connection();
};
public method start: fork {
active = time();
interface.connection_starting(host, port);
};
public method interface {
return interface;
};
// ------------------------------------------------------------------
new object $connection_interface: $network;
var $root objname = 'connection_interface;
var $root inited = 1;
var connection = 0;
var controller = 0;
var commands = 0;
public method parse_line {
arg line;
var cmd, c, match, parsed, i, m, a, u;
catch any {
while (line && line[1] == " ")
line = line.subrange(2);
if (!line) {
return .null_cmd(line);
} else {
c = controller.match_command(line);
return (> .(c[1])(@c.subrange(2)) <);
}
} with handler {
if (error() != ~stop) {
.print($parse.traceback(traceback()));
return 'disconnect;
}
}
};
public method commands {
return commands;
};
public method match_command {
arg line;
var cmd, c, match;
if (!commands)
return ['invalid_cmd];
for c in [1 .. commands.length()] {
match = commands[c][1].match_template(line);
if (match)
return [commands[c][2], @match];
}
return ['invalid_cmd];
};
public method connection_going_away {
arg [args];
(| .destroy() |);
};
public method print {
arg what;
return (> connection.write(what) <);
};
protected method null_cmd {
arg [args];
return 'disconnect;
};
protected method invalid_cmd {
arg [args];
return 'disconnect;
};
public method connection {
return connection;
};
public method new {
arg c;
var i;
i = .spawn();
i.set_connection(c);
i.set_controller(this());
return i;
};
public method new_connection {
arg host, port;
};
private method set_connection {
arg c;
connection = c;
};
protected method controller {
return controller;
};
private method set_controller {
arg c;
controller = c;
};
public method daemon_shutdown {
var i;
for i in (.children())
(| i.destroy() |);
};
public method connection_starting {
arg host, port;
.print(["","** Minimal Core Mark I online, port "+tostr(port)+" **",""]);
};
// ------------------------------------------------------------------
new object $login_daemon: $daemon;
var $root objname = 'login_daemon;
var $root inited = 1;
var $daemon default_port = 1138;
var $daemon connection = $login_connection;
var $daemon next_connection = 0;
var $daemon port = 0;
// ------------------------------------------------------------------
new object $login_interface: $connection_interface;
var $root objname = 'login_interface;
var $root inited = 1;
var $connection_interface commands = [["QUIT", 'quit_cmd]];;
protected method null_cmd {
arg [args];
.invalid_cmd();
};
protected method invalid_cmd {
arg [args];
.print("Commands: " + .controller().commands().slice(1).to_english());
};
protected method quit_cmd {
arg [args];
.print("Goodbye.");
return 'disconnect;
};
// ------------------------------------------------------------------
new object $login_connection: $connection;
var $root objname = 'login_connection;
var $root inited = 1;
var $connection interface = $login_interface;