new object $network: $root; var $root inited = 1; public method .bind_port() { arg port; return (> bind_port(port) <); }; new object $daemon: $network; var $root inited = 1; var $daemon default_port = 1138; var $daemon connection = 0; var $daemon next_connection = 0; var $daemon 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(); (> next_connection.bind_port(port) <); }; 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(); (> next_connection.bind_port(port) <); }; 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 { 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() <); }; new object $login_daemon: $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 $connection: $network; var $root inited = 1; var $connection buffer = `[]; var $connection host = ""; var $connection daemon = 0; var $connection active = 0; var $connection line_buffer = 0; var $connection interface = 0; var $connection read_block = []; var $connection started_at = 0; var $connection 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."); } cwrite(what); }; public method .parse() { arg incoming; var lines, line, index; lines = (buffer + 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 .cwritef() { arg fname; (> cwritef(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 $login_connection: $connection; var $root inited = 1; var $connection interface = $login_interface; new object $connection_interface: $network; var $root inited = 1; var $connection_interface connection = 0; var $connection_interface controller = 0; var $connection_interface 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 { 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_interface: $connection_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; };