// ------------------------------------------------------------------ // $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;