From 7ac0fc8f3f9fe308e41a918e967a7258f1f8cc7c Mon Sep 17 00:00:00 2001 From: Brendan Howell Date: Sun, 10 May 2015 23:59:35 +0200 Subject: [PATCH] add core Bureau, InhumanResources, HumorDept and kbtest bureaus. --- screenless/bureau/bureau.py | 194 ++++++++++++++++++++++++++++++++++++ screenless/bureau/ihr.py | 94 +++++++++++++++++ screenless/bureau/jokes.py | 29 ++++++ screenless/bureau/kbtest.py | 9 ++ screenless/officemgr.py | 3 + 5 files changed, 329 insertions(+) create mode 100644 screenless/bureau/bureau.py create mode 100644 screenless/bureau/ihr.py create mode 100644 screenless/bureau/jokes.py create mode 100644 screenless/bureau/kbtest.py diff --git a/screenless/bureau/bureau.py b/screenless/bureau/bureau.py new file mode 100644 index 0000000..552b302 --- /dev/null +++ b/screenless/bureau/bureau.py @@ -0,0 +1,194 @@ +# bureau +import functools +import json +import os.path +import subprocess +import tempfile +import threading + +import zmq +from mako.template import Template + + +def update_commands(cls): + for name, method in cls.__dict__.items(): + print("name %s method %s" % (name, method)) + if hasattr(method, "command"): + comstr = method.command + cls.commands[comstr] = method + return cls + + +def add_command(comstr, name=""): + def decorator(func): + @functools.wraps(func) + def func_wrap(*args, **kwargs): + return func(*args, **kwargs) + func_wrap.command = comstr + func_wrap.name = name + return func_wrap + return decorator + + +def add_api(apistr, name=""): + def decorator(func): + @functools.wraps(func) + def func_wrap(*args, **kwargs): + return func(*args, **kwargs) + func_wrap.api = apistr + func_wrap.name = name + return func_wrap + return decorator + + +# @update_commands +class Bureau: + name = "TEST" + prefix = "00" + version = 0 + + config = {} + + def __init__(self): + """ set up ZeroMQ connections and register commands""" + self.commands = {} + self.api = {} + self.context = zmq.Context() + self._recv = self.context.socket(zmq.SUB) + self._recv.connect("tcp://localhost:10101") + self._recv.setsockopt_string(zmq.SUBSCRIBE, self.prefix) + print(("bureau " + self.name + " waiting for messages")) + + self._send = self.context.socket(zmq.PUB) + self._send.connect("tcp://localhost:10100") + + # update_commands(self) + # self.registerCommands() + + print("commands: ") + print(self.commands) + + def send(self, message): + self._send.send_string(message) + + def _publish_methods(self): + # register bureau with Inhuman Resources + bureau_json = json.dumps({"name": self.name, "prefix": self.prefix, + "desc": self.__doc__}) + self.send("IRaddbureau." + bureau_json) + + # find and store all published methods + for member in dir(self): + method = getattr(self, member) + # ignore anything that is not a method with command or api details + if not (callable(method) and (hasattr(method, "command") or + hasattr(method, "api"))): + continue + if hasattr(method, "command"): + self.commands[method.command] = method + cmd_json = json.dumps({"cmdname": method.name, + "prefix": self.prefix, + "cmd": method.command, + "desc": method.__doc__}) + self.send("IRaddcommand." + cmd_json) + elif hasattr(method, "api"): + self.api[method.api] = method + cmd_json = json.dumps({"apiname": method.name, + "prefix": self.prefix, + "api": method.api, + "desc": method.__doc__}) + self.send("IRaddapi." + cmd_json) + + print("registered:") + print(self.commands) + print(self.api) + + def print_full(self, template, **kwargs): + """print a full page (A4) document """ + # TODO: look up the printer LPR name + + lpname = kwargs.get("printer", "default") + templ = Template(filename=template) + + texfile, texfilepath = tempfile.mkstemp(".tex") + texfile.write(templ.render_unicode( + **kwargs).encode('utf-8', 'replace')) + + texdir = os.path.dirname(texfilepath) + + subprocess.call("cd " + texdir + "; xelatex " + texfilepath) + + pdffile = texfilepath[0:-4] + ".pdf" + + subprocess.call("lpr -P " + lpname + " " + pdffile) + + def print_small(self, text, printer="/dev/usb/lp1"): + lp = open(printer, "w") + text += "\r\n" * 10 + text += ".d0" + lp.write(text) + lp.close() + + @add_command("test") + def test(self): + # stupid test to see if modules work + print(("hi! testing. " + self.name + " bureau seems to work!")) + + def run_io(self): + """process hardware or timed input + + This method can be ignored for most Bureaus. + It should be overloaded for any services that need to independently + generate messages as this is just a placeholder. + It is run in a separate thread and should handle any input or + timed events. Messages are then sent to other Bureaus via the + self.send connection to the OfficeManager. Don't forget to + to consier thread safety issues when accessing data! + """ + pass + + def run(self): + """main loop for processing messages + + This runs all relelvant event processing loops. + """ + + # start the hardware input handler + io = threading.Thread(target=self.run_io) + io.start() + + # register commands and api methods + self._publish_methods() + + while True: + msg = self._recv.recv_string() + try: + dot = msg.find(".") + ref = msg[2:dot] + if (dot < len(msg) - 1) and (dot > 0): + data = msg[dot + 1:] + else: + data = None + print("data: " + str(data)) + except IndexError as e: + print("invalid message: ", e) + continue + print(("got command: " + ref)) + + if (ref in self.commands) or (ref in self.api): + # catch TypeErrors for case of bogus params + try: + if data: + self.api[ref](data) + else: + self.commands[ref]() + except TypeError as e: + print(e) + print("invalid data for command '{}': {}".format(ref, data)) + else: + print("error! object not found") + + +if __name__ == "__main__": + test = Bureau("test", "00") + test.run() diff --git a/screenless/bureau/ihr.py b/screenless/bureau/ihr.py new file mode 100644 index 0000000..6daee1c --- /dev/null +++ b/screenless/bureau/ihr.py @@ -0,0 +1,94 @@ +import json + +from bureau import Bureau, add_command, add_api + + +class InhumanResources(Bureau): + """ + This is a core functional bureau of the Screenless office it keeps track + of all published methods provided by all other bureaus and can print + the menu for the whole system or an individual item + """ + + name = "Inhuman Resources" + prefix = "IR" + version = 0 + + def __init__(self): + Bureau.__init__(self) + self.menu = {} + # update_commands(self) + + @add_api("addbureau", "Register Bureau") + def add_bureau(self, data): + """ + Register Bureau with Inhuman Resources + + data = { prefix: "IR", + bureauname: "Inhuman Resources", + desc: "Keep track of public resources provided by bureaus" + } + """ + d = json.loads(data) + try: + name = d["name"] + prefix = d["prefix"] + desc = d["desc"] + except KeyError as e: + print("cannot add invalid bureau:", str(e)) + return + print("added menu") + self.menu[prefix] = {"name": name, + "desc": desc, + "commands": {}, + "apis": {}} + + @add_api("addcommand", "Register Command") + def add_cmd(self, data): + d = json.loads(data) + try: + prefix = d["prefix"] + cmd = d["cmd"] + cmdname = d["cmdname"] + desc = d["desc"] + except KeyError as e: + print("cannot add invalid command:", str(e)) + return + if prefix not in self.menu: + # TODO: this should throw some kind of error message/log + print("error: cannot add command ", cmd, "to non-existent prefix", + prefix) + else: + self.menu[prefix]["commands"][cmd] = {"name": cmdname, + "desc": desc} + + @add_api("addapi", "Register API Method") + def add_api_method(self, data): + d = json.loads(data) + try: + prefix = d["prefix"] + cmd = d["api"] + cmdname = d["apiname"] + desc = d["desc"] + except KeyError as e: + print("cannot add invalid command:", str(e)) + return + if prefix not in self.menu: + # TODO: this should throw some kind of error message/log + print("error: cannot add command ", cmd, "to non-existent prefix", + prefix) + else: + self.menu[prefix]["apis"][cmd] = {"name": cmdname, + "desc": desc} + + @add_command("menu", "Print Menu") + def print_menu(self): + """ + Prints the menu of commands for all operational bureaus. + """ + print(self.menu) + + +if __name__ == "__main__": + hr = InhumanResources() + hr.run() diff --git a/screenless/bureau/jokes.py b/screenless/bureau/jokes.py new file mode 100644 index 0000000..a3440df --- /dev/null +++ b/screenless/bureau/jokes.py @@ -0,0 +1,29 @@ +import subprocess + +from bureau import Bureau, add_command + + +class Humor(Bureau): + """ + This bureau entertains the modern worker and provides colorful + bons mots for managers who need to warm up an audience. + """ + + name = "Department of Humor" + prefix = "HA" + version = 0 + + def __init__(self): + Bureau.__init__(self) + + @add_command("joke", "Fortune Cookie") + def print_fortune(self): + """ + Prints a clever quip. + """ + print(str.decode(subprocess.check_output("fortune"))) + + +if __name__ == "__main__": + ha = Humor() + ha.run() diff --git a/screenless/bureau/kbtest.py b/screenless/bureau/kbtest.py new file mode 100644 index 0000000..8a4de70 --- /dev/null +++ b/screenless/bureau/kbtest.py @@ -0,0 +1,9 @@ +import zmq + +ctx = zmq.Context() +send = ctx.socket(zmq.PUB) +send.connect("tcp://localhost:10100") + +while True: + msg = input("> ") + send.send_string(msg) diff --git a/screenless/officemgr.py b/screenless/officemgr.py index 6a4afc3..d4a0c3c 100644 --- a/screenless/officemgr.py +++ b/screenless/officemgr.py @@ -1,5 +1,6 @@ import zmq + class OfficeManager: def __init__(self): @@ -13,6 +14,8 @@ class OfficeManager: def run(self): try: + # for each enabled buro in config + # proc = subprocess.Popen([sys.executable, "mybuero.py"]) zmq.device(zmq.FORWARDER, self.frontend, self.backend) except Exception as e: print(e)