diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index e55d5d942..107a42886 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -461,25 +461,34 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs) - def msg(self, text=None, from_obj=None, session=None, **kwargs): + def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): """ Emits something to a session attached to the object. Args: - text (str, optional): The message to send + text (str or tuple, optional): The message to send. This + is treated internally like any send-command, so its + value can be a tuple if sending multiple arguments to + the `text` oob command. from_obj (obj, optional): object that is sending. If - given, at_msg_send will be called + given, at_msg_send will be called. This value will be + passed on to the protocol. session (Session or list, optional): Session or list of - Sessions to relay data to, if any. If set, will - force send to these sessions. If unset, who receives the - message depends on the MULTISESSION_MODE. + Sessions to relay data to, if any. If set, will force send + to these sessions. If unset, who receives the message + depends on the MULTISESSION_MODE. + options (dict, optional): Message-specific option-value + pairs. These will be applied at the protocol level. + Kwargs: + any (string or tuples): All kwarg keys not listed above + will be treated as send-command names and their arguments + (which can be a string or a tuple). Notes: `at_msg_receive` will be called on this Object. All extra kwargs will be passed on to the protocol. """ - text = to_str(text, force_string=True) if text != None else "" # try send hooks if from_obj: try: @@ -493,10 +502,12 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): except Exception: logger.log_trace() + kwargs["options"] = options + # relay to session(s) sessions = make_iter(session) if session else self.sessions.all() for session in sessions: - session.msg(text=text, **kwargs) + session.data_out(text=text, **kwargs) def for_contents(self, func, exclude=None, **kwargs): """ diff --git a/evennia/players/players.py b/evennia/players/players.py index a9f1336b6..77e652da7 100644 --- a/evennia/players/players.py +++ b/evennia/players/players.py @@ -137,7 +137,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): * Helper methods - - msg(outgoing_string, from_obj=None, **kwargs) + - msg(text=None, from_obj=None, session=None, options=None, **kwargs) - execute_cmd(raw_string) - search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, @@ -384,7 +384,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): super(PlayerDB, self).delete(*args, **kwargs) ## methods inherited from database model - def msg(self, text=None, from_obj=None, session=None, **kwargs): + def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): """ Evennia -> User This is the main route for sending data back to the user from the @@ -398,12 +398,11 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): Sessions to receive this send. If given, overrules the default send behavior for the current MULTISESSION_MODE. - Notes: - All other keywords are passed on to the protocol. + options (list): Protocol-specific options. Passed on to the protocol. + Kwargs: + any (dict): All other keywords are passed on to the protocol. """ - text = to_str(text, force_string=True) if text else "" - if from_obj: # call hook try: @@ -414,7 +413,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): # session relay sessions = make_iter(session) if session else self.sessions.all() for session in sessions: - session.msg(text=text, **kwargs) + session.data_out(text=text, **kwargs) def execute_cmd(self, raw_string, session=None, **kwargs): """ diff --git a/evennia/server/amp.py b/evennia/server/amp.py index b5be0a94f..18ad21599 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -424,20 +424,19 @@ class AMPProtocol(amp.AMP): self.factory.server.sessions.data_in(self.factory.server.sessions[sessid], **kwargs) return {} - def send_MsgPortal2Server(self, session, text="", **kwargs): + def send_MsgPortal2Server(self, session, **kwargs): """ Access method called by the Portal and executed on the Portal. Args: sessid (int): Unique Session id. - text (str): Message to send over the wire. kwargs (any, optional): Optional data. Returns: deferred (Deferred): Asynchronous return. """ - return self.send_data(MsgPortal2Server, session.sessid, text=text, **kwargs) + return self.send_data(MsgPortal2Server, session.sessid, **kwargs) # Server -> Portal message @@ -455,18 +454,17 @@ class AMPProtocol(amp.AMP): return {} - def send_MsgServer2Portal(self, session, text="", **kwargs): + def send_MsgServer2Portal(self, session, **kwargs): """ Access method - executed on the Server for sending data to Portal. Args: session (Session): Unique Session. - msg (str, optional): Message to send over the wire. kwargs (any, optiona): Extra data. """ - return self.send_data(MsgServer2Portal, session.sessid, text=text, **kwargs) + return self.send_data(MsgServer2Portal, session.sessid, **kwargs) # Server administration from the Portal side @AdminPortal2Server.responder diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 531f39d86..58c17c04e 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -9,6 +9,7 @@ from collections import deque from twisted.internet import reactor from django.conf import settings from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC +from evennia.utils.logger import log_trace # module import _MOD_IMPORT = None @@ -295,74 +296,9 @@ class PortalSessionHandler(SessionHandler): """ for session in self.values(): - session.data_out(message) + session.data_out(text=message) - def oobstruct_parser(self, oobstruct): - """ - Helper method for each session to use to parse oob structures - (The 'oob' kwarg of the msg() method). - - Args: - oobstruct (str or iterable): A structure representing - an oob command on one of the following forms: - - "cmdname" - - "cmdname", "cmdname" - - ("cmdname", arg) - - ("cmdname",(args)) - - ("cmdname",{kwargs} - - ("cmdname", (args), {kwargs}) - - (("cmdname", (args,), {kwargs}), ("cmdname", (args,), {kwargs})) - and any combination of argument-less commands or commands with only - args, only kwargs or both. - - Returns: - structure (tuple): A generic OOB structure on the form - `((cmdname, (args,), {kwargs}), ...)`, where the two last - args and kwargs may be empty - - """ - def _parse(oobstruct): - slen = len(oobstruct) - if not oobstruct: - return tuple(None, (), {}) - elif not hasattr(oobstruct, "__iter__"): - # a singular command name, without arguments or kwargs - return (oobstruct, (), {}) - # regardless of number of args/kwargs, the first element must be - # the function name. We will not catch this error if not, but - # allow it to propagate. - if slen == 1: - return (oobstruct[0], (), {}) - elif slen == 2: - if isinstance(oobstruct[1], dict): - # (cmdname, {kwargs}) - return (oobstruct[0], (), dict(oobstruct[1])) - elif isinstance(oobstruct[1], (tuple, list)): - # (cmdname, (args,)) - return (oobstruct[0], tuple(oobstruct[1]), {}) - else: - # (cmdname, arg) - return (oobstruct[0], (oobstruct[1],), {}) - else: - # (cmdname, (args,), {kwargs}) - return (oobstruct[0], tuple(oobstruct[1]), dict(oobstruct[2])) - - if hasattr(oobstruct, "__iter__"): - # differentiate between (cmdname, cmdname), - # (cmdname, (args), {kwargs}) and ((cmdname,(args),{kwargs}), - # (cmdname,(args),{kwargs}), ...) - - if oobstruct and isinstance(oobstruct[0], basestring): - return (list(_parse(oobstruct)),) - else: - out = [] - for oobpart in oobstruct: - out.append(_parse(oobpart)) - return (list(out),) - return (_parse(oobstruct),) - - - def data_in(self, session, text="", **kwargs): + def data_in(self, session, **kwargs): """ Called by portal sessions for relaying data coming in from the protocol to the server. @@ -371,7 +307,6 @@ class PortalSessionHandler(SessionHandler): session (PortalSession): Session receiving data. Kwargs: - text (str): Text from protocol. kwargs (any): Other data from protocol. Notes: @@ -394,24 +329,24 @@ class PortalSessionHandler(SessionHandler): if self.command_overflow: self.data_out(session, text=_ERROR_COMMAND_OVERFLOW) return + # scrub data + kwargs = self.clean_senddata(session, kwargs) + # relay data to Server self.command_counter += 1 session.cmd_last = now self.portal.amp_protocol.send_MsgPortal2Server(session, - text=text, - **kwargs) + **kwargs) else: # called by the callLater callback if self.command_overflow: self.command_overflow = False reactor.callLater(1.0, self.data_in, None) - - def data_out(self, session, text=None, **kwargs): + def data_out(self, session, **kwargs): """ Called by server for having the portal relay messages and data - to the correct session protocol. We also convert oob input to - a generic form here. + to the correct session protocol. Args: session (Session): Session sending data. @@ -424,10 +359,16 @@ class PortalSessionHandler(SessionHandler): #from evennia.server.profiling.timetrace import timetrace #text = timetrace(text, "portalsessionhandler.data_out") + # distribute outgoing data to the correct session methods. if session: - # convert oob to the generic format - if "oob" in kwargs: - kwargs["oob"] = self.oobstruct_parser(kwargs["oob"]) - session.data_out(text=text, **kwargs) + print ("portalsessionhandler.data_out:", session, kwargs, session.datamap) + for cmdname, args in kwargs.items(): + try: + if cmdname in session.datamap: + session.datamap[cmdname](session, *args) + else: + session.datamap["_default"](session, *args) + except Exception: + log_trace() PORTAL_SESSIONS = PortalSessionHandler() diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 234db4cf1..8c5d00035 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -10,6 +10,7 @@ sessions etc. import re from twisted.internet.task import LoopingCall from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE, GA, WILL, WONT, ECHO +from django.conf import settings from evennia.server.session import Session from evennia.server.portal import ttype, mssp, telnet_oob, naws from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP @@ -21,6 +22,7 @@ NOP = chr(241) _RE_N = re.compile(r"\{n$") _RE_LEND = re.compile(r"\n$|\r$", re.MULTILINE) +_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): """ @@ -69,6 +71,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.keep_alive = LoopingCall(self._write, IAC + NOP) self.keep_alive.start(30, now=False) + self.datamap = {"text": self.send_text, + "prompt": self.send_prompt, + "_default": self.send_oob} + def handshake_done(self, force=False): """ @@ -242,98 +248,117 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.data_out(reason) self.connectionLost(reason) - def data_in(self, text=None, **kwargs): + def data_in(self, **kwargs): """ Data User -> Evennia Kwargs: - text (str): Incoming text. kwargs (any): Options from the protocol. """ #from evennia.server.profiling.timetrace import timetrace #text = timetrace(text, "telnet.data_in") - self.sessionhandler.data_in(self, text=text, **kwargs) + self.sessionhandler.data_in(self, **kwargs) - def data_out(self, text=None, **kwargs): + def data_out(self, **kwargs): """ - Data Evennia -> User. A generic hook method for engine to call - in order to send data through the telnet connection. + Data Evennia -> User Kwargs: - text (str): Text to send. - oob (list): `[(cmdname,args,kwargs), ...]`, supply an - Out-of-Band instruction. - xterm256 (bool): Enforce xterm256 setting. If not given, - ttype result is used. If client does not suport xterm256, - the ansi fallback will be used - mxp (bool): Enforce mxp setting. If not given, enables if - we detected client support for it - ansi (bool): Enforce ansi setting. If not given, ttype - result is used. - nomarkup (bool): If True, strip all ansi markup (this is - the same as `xterm256=False, ansi=False`) - raw (bool):Pass string through without any ansi processing - (i.e. include Evennia ansi markers but do not convert them - into ansi tokens) - prompt (str): Supply a prompt text which gets sent without - a newline added to the end. - echo (str): Turn on/off line echo on the client, if the - client supports it (e.g. for password input). Remember - that you must manually activate it again later. + kwargs (any): Options to the protocol + """ + self.sessionhandler.data_out(self, **kwargs) - Notes: - The telnet TTYPE negotiation flags, if any, are used if no kwargs - are given. + + @staticmethod + def send_text(session, *args, **kwargs): + """ + Send text data. This is an in-band telnet operation. + + Args: + text (str): The first argument is always the text string to send. No other arguments + are considered. + *options (str): All other arguments are considered option flags. + Available flags are (if not set, TTYPE will be used, turning on if available): + mxp: Enforce MXP link support. + ansi: Enforce no ANSI colors. + xterm256: Enforce xterm256 colors, regardless of TTYPE. + noxterm256: Enforce no xterm256 color support, regardless of TTYPE. + nomarkup: Strip all ANSI markup. This is the same as noxterm256,noansi + raw: Pass string through without any ansi processing + (i.e. include Evennia ansi markers but do not + convert them into ansi tokens) + echo: Turn on/off line echo on the client. Turn + off line echo for client, for example for password. + Note that it must be actively turned back on again! """ - ## profiling, debugging - #if text.startswith("TEST_MESSAGE"): 1/0 - #from evennia.server.profiling.timetrace import timetrace - #text = timetrace(text, "telnet.data_out", final=True) + if args: + text = args[0] + if text is None: + return - try: - text = utils.to_str(text if text else "", encoding=self.encoding) - except Exception as e: - self.sendLine(str(e)) - return - if "oob" in kwargs and "OOB" in self.protocol_flags: - # oob is a list of [(cmdname, arg, kwarg), ...] - for cmdname, args, okwargs in kwargs["oob"]: - self.oob.data_out(cmdname, *args, **okwargs) + # handle arguments + options = kwargs.get("options", {}) + ttype = session.protocol_flags.get('TTYPE', {}) + xterm256 = options.get("xterm256", ttype.get('256 COLORS', False) if ttype.get("init_done") else True) + useansi = options.get("ansi", ttype and ttype.get('ANSI', False) if ttype.get("init_done") else True) + raw = options.get("raw", False) + nomarkup = options.get("nomarkup", not (xterm256 or useansi)) + echo = options.get("echo", None) + mxp = options.get("mxp", session.protocol_flags.get("MXP", False)) + screenreader = options.get("screenreader", session.screenreader) - # parse **kwargs, falling back to ttype if nothing is given explicitly - ttype = self.protocol_flags.get('TTYPE', {}) - xterm256 = kwargs.get("xterm256", ttype.get('256 COLORS', False) if ttype.get("init_done") else True) - useansi = kwargs.get("ansi", ttype and ttype.get('ANSI', False) if ttype.get("init_done") else True) - raw = kwargs.get("raw", False) - nomarkup = kwargs.get("nomarkup", not (xterm256 or useansi)) - prompt = kwargs.get("prompt") - echo = kwargs.get("echo", None) - mxp = kwargs.get("mxp", self.protocol_flags.get("MXP", False)) + if screenreader: + # screenreader mode cleans up output + text = ansi.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) + text = _RE_SCREENREADER_REGEX.sub("", text) - if raw: - # no processing whatsoever - self.sendLine(text) - elif text: - # we need to make sure to kill the color at the end in order - # to match the webclient output. - linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256, mxp=mxp) - if mxp: - linetosend = mxp_parse(linetosend) - self.sendLine(linetosend) - - if prompt: - # Send prompt separately - prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + "{n", strip_ansi=nomarkup, xterm256=xterm256) - if mxp: - prompt = mxp_parse(prompt) + if options.get("send_prompt"): + # send a prompt instead. + if not raw: + # processing + prompt = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256) + if mxp: + prompt = mxp_parse(prompt) prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n') prompt += IAC + GA - self.transport.write(mccp_compress(self, prompt)) - if echo: - self.transport.write(mccp_compress(self, IAC+WONT+ECHO)) - elif echo == False: - self.transport.write(mccp_compress(self, IAC+WILL+ECHO)) + session.transport.write(mccp_compress(session, prompt)) + else: + if raw: + # no processing + session.sendLine(text) + return + else: + # we need to make sure to kill the color at the end in order + # to match the webclient output. + linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256, mxp=mxp) + if mxp: + linetosend = mxp_parse(linetosend) + session.sendLine(linetosend) + if echo is not None: + # turn on/off echo + if echo: + session.transport.write(mccp_compress(session, IAC+WILL+ECHO)) + else: + session.transport.write(mccp_compress(session, IAC+WONT+ECHO)) + + + @staticmethod + def send_prompt(session, *args, **kwargs): + """ + Send a prompt - a text without a line end. See send_text for argument options. + + """ + kwargs["options"].update({"send_prompt": True}) + session.send_text(*args, **kwargs) + + + @staticmethod + def send_oob(session, *args, **kwargs): + """ + Send oob data + """ + print "telnet.send_oob not implemented yet! ", args diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index 90b2aeb10..cfd788785 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -28,7 +28,6 @@ _SA = object.__setattr__ _ObjectDB = None _ANSI = None _INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED -_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) # i18n from django.utils.translation import ugettext as _ @@ -169,6 +168,8 @@ class ServerSession(Session): self.player = None self.cmdset_storage_string = "" self.cmdset = CmdSetHandler(self, True) + self.datamap = {"text": self.recv_text, + "_default": self.recv_text} def __cmdset_storage_get(self): return [path.strip() for path in self.cmdset_storage_string.split(',')] @@ -336,22 +337,22 @@ class ServerSession(Session): # Player-visible idle time, not used in idle timeout calcs. self.cmd_last_visible = self.cmd_last - def data_in(self, text=None, **kwargs): + @staticmethod + def recv_text(session, *args, **kwargs): """ - Send data User->Evennia. This will in effect execute a command + Recv command data User->Evennia. This will in effect execute a command string on the server. - Note that oob data is already sent separately to the - oobhandler at this point. - - Kwargs: - text (str): A text to relay - kwargs (any): Other parameters from the protocol. + Args: + text (str): First arg is used as text-command input. Other + arguments are ignored. """ #from evennia.server.profiling.timetrace import timetrace #text = timetrace(text, "ServerSession.data_in") + text = args[0] if args else None + #explicitly check for None since text can be an empty string, which is #also valid if text is not None: @@ -359,47 +360,47 @@ class ServerSession(Session): #text = to_unicode(escape_control_sequences(text), encoding=self.encoding) # handle the 'idle' command if text.strip() == _IDLE_COMMAND: - self.update_session_counters(idle=True) + session.update_session_counters(idle=True) return - if self.player: + if session.player: # nick replacement - puppet = self.puppet + puppet = session.puppet if puppet: text = puppet.nicks.nickreplace(text, categories=("inputline", "channel"), include_player=True) else: - text = self.player.nicks.nickreplace(text, + text = session.player.nicks.nickreplace(text, categories=("inputline", "channels"), include_player=False) - cmdhandler(self, text, callertype="session", session=self) - self.update_session_counters() - - execute_cmd = data_in # alias + cmdhandler(session, text, callertype="session", session=session) + session.update_session_counters() def data_out(self, text=None, **kwargs): """ - Send Evennia -> User + Sending data from Evennia->Player Kwargs: - text (str): A text to relay - kwargs (any): Other parameters to the protocol. + text (str or tuple) + any (str or tuple): Send-commands identified + by their keys. Or "options", carrying options + for the protocol(s). """ - #from evennia.server.profiling.timetrace import timetrace - #text = timetrace(text, "ServerSession.data_out") + print "serversession.data_out:", text, kwargs + if text: + if hasattr(text, "__iter__"): + text, args = text[0], list(text[1:]) + else: + text, args = text, [] + options = kwargs.get("options", {}) + raw = options.get("raw", False) + strip_inlinefunc = options.get("strip_inlinefunc", False) + if _INLINEFUNC_ENABLED and not raw: + text = parse_inlinefunc(text, strip=strip_inlinefunc, session=self) + text = parse_nested_inlinefunc(text, strip=strip_inlinefunc, session=self) + text = [text] + args - text = text if text else "" - if _INLINEFUNC_ENABLED and not "raw" in kwargs: - text = parse_inlinefunc(text, strip="strip_inlinefunc" in kwargs, session=self) - text = parse_nested_inlinefunc(text, strip="strip_inlinefunc" in kwargs, session=self) - if self.screenreader: - global _ANSI - if not _ANSI: - from evennia.utils import ansi as _ANSI - text = _ANSI.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) - text = _RE_SCREENREADER_REGEX.sub("", text) self.sessionhandler.data_out(self, text=text, **kwargs) - # alias - msg = data_out + msg = data_out # alias def __eq__(self, other): "Handle session comparisons" @@ -427,6 +428,7 @@ class ServerSession(Session): "Unicode representation" return u"%s" % str(self) + # Dummy API hooks for use during non-loggedin operation def at_cmdset_get(self, **kwargs): diff --git a/evennia/server/session.py b/evennia/server/session.py index 16a2d2c27..4391f40aa 100644 --- a/evennia/server/session.py +++ b/evennia/server/session.py @@ -85,6 +85,9 @@ class Session(object): self.protocol_flags = {} self.server_data = {} + # map of input data to session methods + self.datamap = {} + # a back-reference to the relevant sessionhandler this # session is stored in. self.sessionhandler = sessionhandler @@ -135,25 +138,23 @@ class Session(object): """ pass - def data_out(self, text=None, **kwargs): + def data_out(self, **kwargs): """ Generic hook for sending data out through the protocol. Server protocols can use this right away. Portal sessions should overload this to format/handle the outgoing data as needed. Kwargs: - text (str): Text data kwargs (any): Other data to the protocol. """ pass - def data_in(self, text=None, **kwargs): + def data_in(self, **kwargs): """ Hook for protocols to send incoming data to the engine. Kwargs: - text (str): Text data kwargs (any): Other data from the protocol. """ diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 54c38fb31..969e0ec0b 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -18,6 +18,7 @@ from future.utils import listvalues from time import time from django.conf import settings from evennia.commands.cmdhandler import CMD_LOGINSTART +from evennia.utils.logger import log_trace from evennia.utils.utils import variable_from_module, is_iter, \ to_str, to_unicode, strip_control_sequences, make_iter @@ -57,7 +58,7 @@ _MULTISESSION_MODE = settings.MULTISESSION_MODE _IDLE_TIMEOUT = settings.IDLE_TIMEOUT _MAX_SERVER_COMMANDS_PER_SECOND = 100.0 _MAX_SESSION_COMMANDS_PER_SECOND = 5.0 - +_MODEL_MAP = None def delayed_import(): """ @@ -116,6 +117,47 @@ class SessionHandler(dict): """ return dict((sessid, sess.get_sync_data()) for sessid, sess in self.items()) + def clean_senddata(self, session, kwargs): + """ + Clean up data for sending across the AMP wire. + + Args: + session (Session): The relevant session instance. + kwargs (dict): Every keyword represents a send-instruction. + + Returns: + kwargs (dict): A cleaned dictionary of cmdname:args pairs, + where the keys and args have all been converted to + send-safe entities (strings or numbers). + + """ + def _validate(data): + if isinstance(data, dict): + newdict = {} + for key, part in data.items(): + newdict[key] = _validate(part) + return newdict + elif hasattr(data, "__iter__"): + return [_validate(part) for part in data] + elif isinstance(data, basestring): + try: + return data and to_str(to_unicode(data), encoding=session.encoding) + except LookupError: + # wrong encoding set on the session. Set it to a safe one + session.encoding = "utf-8" + return to_str(to_unicode(data), encoding=session.encoding) + elif hasattr(data, "id") and hasattr(data, "db_date_created") and hasattr(data, '__dbclass__'): + # convert database-object to their string representation. + return _validate(unicode(data)) + else: + return data + clean_kwargs = {"options":kwargs.pop("options", {})} + for key in kwargs: + args = _validate(kwargs[key]) + clean_kwargs[_validate(key)] = (args,) if args is not None and \ + not hasattr(args, "__iter__") else args + return clean_kwargs + #------------------------------------------------------------ # Server-SessionHandler class @@ -170,7 +212,7 @@ class ServerSessionHandler(SessionHandler): # validate all scripts _ScriptDB.objects.validate() self[sess.sessid] = sess - sess.data_in(CMD_LOGINSTART) + sess.data_in(text=CMD_LOGINSTART) def portal_session_sync(self, portalsessiondata): """ @@ -498,7 +540,7 @@ class ServerSessionHandler(SessionHandler): for sess in self.values(): self.data_out(sess, message) - def data_out(self, session, text="", **kwargs): + def data_out(self, session, **kwargs): """ Sending data Server -> Portal @@ -506,24 +548,18 @@ class ServerSessionHandler(SessionHandler): session (Session): Session to relay to. text (str, optional): text data to return + Notes: + The outdata will be scrubbed for sending across + the wire here. """ - #from evennia.server.profiling.timetrace import timetrace - #text = timetrace(text, "ServerSessionHandler.data_out") - - try: - text = text and to_str(to_unicode(text), encoding=session.encoding) - except LookupError: - # wrong encoding set on the session. Set it to a safe one - session.encoding = "utf-8" - text = to_str(to_unicode(text), encoding=session.encoding) - - + # clean output for sending + kwargs = self.clean_senddata(session, kwargs) # send across AMP + print "sessionhandler.data_out:", kwargs self.server.amp_protocol.send_MsgServer2Portal(session, - text=text, **kwargs) - def data_in(self, session, text="", **kwargs): + def data_in(self, session, **kwargs): """ Data Portal -> Server. We also intercept OOB communication here. @@ -532,25 +568,21 @@ class ServerSessionHandler(SessionHandler): sessions (Session): Session. Kwargs: - text (str): Text from protocol. kwargs (any): Other data from protocol. """ - #from evennia.server.profiling.timetrace import timetrace - #text = timetrace(text, "ServerSessionHandler.data_in") - if session: - text = text and to_unicode(strip_control_sequences(text), encoding=session.encoding) - if "oob" in kwargs: - # incoming data is always on the form (cmdname, args, kwargs) - global _OOB_HANDLER - if not _OOB_HANDLER: - from evennia.server.oobhandler import OOB_HANDLER as _OOB_HANDLER - funcname, args, kwargs = kwargs.pop("oob") - if funcname: - _OOB_HANDLER.execute_cmd(session, funcname, *args, **kwargs) - # pass the rest off to the session - session.data_in(text=text, **kwargs) + # distribute incoming data to the correct receiving methods. + if session: + for cmdname, args in kwargs.items(): + try: + if cmdname in session.datamap: + print "sessionhandler: data_in", cmdname, args + session.datamap[cmdname](session, *args) + else: + session.datamap["_default"](session, *args) + except Exception: + log_trace() SESSION_HANDLER = ServerSessionHandler() SESSIONS = SESSION_HANDLER # legacy