Changed the OOB message structure to include sending text data as well; still not working fully.

This commit is contained in:
Griatch 2016-01-28 22:19:23 +01:00
parent 529f13c689
commit 4817ec90b3
8 changed files with 249 additions and 240 deletions

View file

@ -461,25 +461,34 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs) 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. Emits something to a session attached to the object.
Args: 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 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 session (Session or list, optional): Session or list of
Sessions to relay data to, if any. If set, will Sessions to relay data to, if any. If set, will force send
force send to these sessions. If unset, who receives the to these sessions. If unset, who receives the message
message depends on the MULTISESSION_MODE. 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: Notes:
`at_msg_receive` will be called on this Object. `at_msg_receive` will be called on this Object.
All extra kwargs will be passed on to the protocol. All extra kwargs will be passed on to the protocol.
""" """
text = to_str(text, force_string=True) if text != None else ""
# try send hooks # try send hooks
if from_obj: if from_obj:
try: try:
@ -493,10 +502,12 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
except Exception: except Exception:
logger.log_trace() logger.log_trace()
kwargs["options"] = options
# relay to session(s) # relay to session(s)
sessions = make_iter(session) if session else self.sessions.all() sessions = make_iter(session) if session else self.sessions.all()
for session in sessions: for session in sessions:
session.msg(text=text, **kwargs) session.data_out(text=text, **kwargs)
def for_contents(self, func, exclude=None, **kwargs): def for_contents(self, func, exclude=None, **kwargs):
""" """

View file

@ -137,7 +137,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
* Helper methods * Helper methods
- msg(outgoing_string, from_obj=None, **kwargs) - msg(text=None, from_obj=None, session=None, options=None, **kwargs)
- execute_cmd(raw_string) - execute_cmd(raw_string)
- search(ostring, global_search=False, attribute_name=None, - search(ostring, global_search=False, attribute_name=None,
use_nicks=False, location=None, use_nicks=False, location=None,
@ -384,7 +384,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
super(PlayerDB, self).delete(*args, **kwargs) super(PlayerDB, self).delete(*args, **kwargs)
## methods inherited from database model ## 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 Evennia -> User
This is the main route for sending data back to the user from the 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 Sessions to receive this send. If given, overrules the
default send behavior for the current default send behavior for the current
MULTISESSION_MODE. MULTISESSION_MODE.
Notes: options (list): Protocol-specific options. Passed on to the protocol.
All other keywords are 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: if from_obj:
# call hook # call hook
try: try:
@ -414,7 +413,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# session relay # session relay
sessions = make_iter(session) if session else self.sessions.all() sessions = make_iter(session) if session else self.sessions.all()
for session in sessions: for session in sessions:
session.msg(text=text, **kwargs) session.data_out(text=text, **kwargs)
def execute_cmd(self, raw_string, session=None, **kwargs): def execute_cmd(self, raw_string, session=None, **kwargs):
""" """

View file

@ -424,20 +424,19 @@ class AMPProtocol(amp.AMP):
self.factory.server.sessions.data_in(self.factory.server.sessions[sessid], **kwargs) self.factory.server.sessions.data_in(self.factory.server.sessions[sessid], **kwargs)
return {} 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. Access method called by the Portal and executed on the Portal.
Args: Args:
sessid (int): Unique Session id. sessid (int): Unique Session id.
text (str): Message to send over the wire.
kwargs (any, optional): Optional data. kwargs (any, optional): Optional data.
Returns: Returns:
deferred (Deferred): Asynchronous return. 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 # Server -> Portal message
@ -455,18 +454,17 @@ class AMPProtocol(amp.AMP):
return {} return {}
def send_MsgServer2Portal(self, session, text="", **kwargs): def send_MsgServer2Portal(self, session, **kwargs):
""" """
Access method - executed on the Server for sending data Access method - executed on the Server for sending data
to Portal. to Portal.
Args: Args:
session (Session): Unique Session. session (Session): Unique Session.
msg (str, optional): Message to send over the wire.
kwargs (any, optiona): Extra data. 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 # Server administration from the Portal side
@AdminPortal2Server.responder @AdminPortal2Server.responder

View file

@ -9,6 +9,7 @@ from collections import deque
from twisted.internet import reactor from twisted.internet import reactor
from django.conf import settings from django.conf import settings
from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC
from evennia.utils.logger import log_trace
# module import # module import
_MOD_IMPORT = None _MOD_IMPORT = None
@ -295,74 +296,9 @@ class PortalSessionHandler(SessionHandler):
""" """
for session in self.values(): for session in self.values():
session.data_out(message) session.data_out(text=message)
def oobstruct_parser(self, oobstruct): def data_in(self, session, **kwargs):
"""
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):
""" """
Called by portal sessions for relaying data coming Called by portal sessions for relaying data coming
in from the protocol to the server. in from the protocol to the server.
@ -371,7 +307,6 @@ class PortalSessionHandler(SessionHandler):
session (PortalSession): Session receiving data. session (PortalSession): Session receiving data.
Kwargs: Kwargs:
text (str): Text from protocol.
kwargs (any): Other data from protocol. kwargs (any): Other data from protocol.
Notes: Notes:
@ -394,24 +329,24 @@ class PortalSessionHandler(SessionHandler):
if self.command_overflow: if self.command_overflow:
self.data_out(session, text=_ERROR_COMMAND_OVERFLOW) self.data_out(session, text=_ERROR_COMMAND_OVERFLOW)
return return
# scrub data
kwargs = self.clean_senddata(session, kwargs)
# relay data to Server # relay data to Server
self.command_counter += 1 self.command_counter += 1
session.cmd_last = now session.cmd_last = now
self.portal.amp_protocol.send_MsgPortal2Server(session, self.portal.amp_protocol.send_MsgPortal2Server(session,
text=text, **kwargs)
**kwargs)
else: else:
# called by the callLater callback # called by the callLater callback
if self.command_overflow: if self.command_overflow:
self.command_overflow = False self.command_overflow = False
reactor.callLater(1.0, self.data_in, None) reactor.callLater(1.0, self.data_in, None)
def data_out(self, session, **kwargs):
def data_out(self, session, text=None, **kwargs):
""" """
Called by server for having the portal relay messages and data Called by server for having the portal relay messages and data
to the correct session protocol. We also convert oob input to to the correct session protocol.
a generic form here.
Args: Args:
session (Session): Session sending data. session (Session): Session sending data.
@ -424,10 +359,16 @@ class PortalSessionHandler(SessionHandler):
#from evennia.server.profiling.timetrace import timetrace #from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "portalsessionhandler.data_out") #text = timetrace(text, "portalsessionhandler.data_out")
# distribute outgoing data to the correct session methods.
if session: if session:
# convert oob to the generic format print ("portalsessionhandler.data_out:", session, kwargs, session.datamap)
if "oob" in kwargs: for cmdname, args in kwargs.items():
kwargs["oob"] = self.oobstruct_parser(kwargs["oob"]) try:
session.data_out(text=text, **kwargs) if cmdname in session.datamap:
session.datamap[cmdname](session, *args)
else:
session.datamap["_default"](session, *args)
except Exception:
log_trace()
PORTAL_SESSIONS = PortalSessionHandler() PORTAL_SESSIONS = PortalSessionHandler()

View file

@ -10,6 +10,7 @@ sessions etc.
import re import re
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE, GA, WILL, WONT, ECHO 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.session import Session
from evennia.server.portal import ttype, mssp, telnet_oob, naws from evennia.server.portal import ttype, mssp, telnet_oob, naws
from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP
@ -21,6 +22,7 @@ NOP = chr(241)
_RE_N = re.compile(r"\{n$") _RE_N = re.compile(r"\{n$")
_RE_LEND = re.compile(r"\n$|\r$", re.MULTILINE) _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): 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 = LoopingCall(self._write, IAC + NOP)
self.keep_alive.start(30, now=False) 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): def handshake_done(self, force=False):
""" """
@ -242,98 +248,117 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.data_out(reason) self.data_out(reason)
self.connectionLost(reason) self.connectionLost(reason)
def data_in(self, text=None, **kwargs): def data_in(self, **kwargs):
""" """
Data User -> Evennia Data User -> Evennia
Kwargs: Kwargs:
text (str): Incoming text.
kwargs (any): Options from the protocol. kwargs (any): Options from the protocol.
""" """
#from evennia.server.profiling.timetrace import timetrace #from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "telnet.data_in") #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 Data Evennia -> User
in order to send data through the telnet connection.
Kwargs: Kwargs:
text (str): Text to send. kwargs (any): Options to the protocol
oob (list): `[(cmdname,args,kwargs), ...]`, supply an """
Out-of-Band instruction. self.sessionhandler.data_out(self, **kwargs)
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.
Notes:
The telnet TTYPE negotiation flags, if any, are used if no kwargs @staticmethod
are given. 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 args:
#if text.startswith("TEST_MESSAGE"): 1/0 text = args[0]
#from evennia.server.profiling.timetrace import timetrace if text is None:
#text = timetrace(text, "telnet.data_out", final=True) return
try: # handle arguments
text = utils.to_str(text if text else "", encoding=self.encoding) options = kwargs.get("options", {})
except Exception as e: ttype = session.protocol_flags.get('TTYPE', {})
self.sendLine(str(e)) xterm256 = options.get("xterm256", ttype.get('256 COLORS', False) if ttype.get("init_done") else True)
return useansi = options.get("ansi", ttype and ttype.get('ANSI', False) if ttype.get("init_done") else True)
if "oob" in kwargs and "OOB" in self.protocol_flags: raw = options.get("raw", False)
# oob is a list of [(cmdname, arg, kwarg), ...] nomarkup = options.get("nomarkup", not (xterm256 or useansi))
for cmdname, args, okwargs in kwargs["oob"]: echo = options.get("echo", None)
self.oob.data_out(cmdname, *args, **okwargs) 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 if screenreader:
ttype = self.protocol_flags.get('TTYPE', {}) # screenreader mode cleans up output
xterm256 = kwargs.get("xterm256", ttype.get('256 COLORS', False) if ttype.get("init_done") else True) text = ansi.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False)
useansi = kwargs.get("ansi", ttype and ttype.get('ANSI', False) if ttype.get("init_done") else True) text = _RE_SCREENREADER_REGEX.sub("", text)
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 raw: if options.get("send_prompt"):
# no processing whatsoever # send a prompt instead.
self.sendLine(text) if not raw:
elif text: # processing
# we need to make sure to kill the color at the end in order prompt = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256)
# to match the webclient output. if mxp:
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256, mxp=mxp) prompt = mxp_parse(prompt)
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)
prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n') prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n')
prompt += IAC + GA prompt += IAC + GA
self.transport.write(mccp_compress(self, prompt)) session.transport.write(mccp_compress(session, prompt))
if echo: else:
self.transport.write(mccp_compress(self, IAC+WONT+ECHO)) if raw:
elif echo == False: # no processing
self.transport.write(mccp_compress(self, IAC+WILL+ECHO)) 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

View file

@ -28,7 +28,6 @@ _SA = object.__setattr__
_ObjectDB = None _ObjectDB = None
_ANSI = None _ANSI = None
_INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED _INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
# i18n # i18n
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -169,6 +168,8 @@ class ServerSession(Session):
self.player = None self.player = None
self.cmdset_storage_string = "" self.cmdset_storage_string = ""
self.cmdset = CmdSetHandler(self, True) self.cmdset = CmdSetHandler(self, True)
self.datamap = {"text": self.recv_text,
"_default": self.recv_text}
def __cmdset_storage_get(self): def __cmdset_storage_get(self):
return [path.strip() for path in self.cmdset_storage_string.split(',')] 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. # Player-visible idle time, not used in idle timeout calcs.
self.cmd_last_visible = self.cmd_last 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. string on the server.
Note that oob data is already sent separately to the Args:
oobhandler at this point. text (str): First arg is used as text-command input. Other
arguments are ignored.
Kwargs:
text (str): A text to relay
kwargs (any): Other parameters from the protocol.
""" """
#from evennia.server.profiling.timetrace import timetrace #from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "ServerSession.data_in") #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 #explicitly check for None since text can be an empty string, which is
#also valid #also valid
if text is not None: if text is not None:
@ -359,47 +360,47 @@ class ServerSession(Session):
#text = to_unicode(escape_control_sequences(text), encoding=self.encoding) #text = to_unicode(escape_control_sequences(text), encoding=self.encoding)
# handle the 'idle' command # handle the 'idle' command
if text.strip() == _IDLE_COMMAND: if text.strip() == _IDLE_COMMAND:
self.update_session_counters(idle=True) session.update_session_counters(idle=True)
return return
if self.player: if session.player:
# nick replacement # nick replacement
puppet = self.puppet puppet = session.puppet
if puppet: if puppet:
text = puppet.nicks.nickreplace(text, text = puppet.nicks.nickreplace(text,
categories=("inputline", "channel"), include_player=True) categories=("inputline", "channel"), include_player=True)
else: else:
text = self.player.nicks.nickreplace(text, text = session.player.nicks.nickreplace(text,
categories=("inputline", "channels"), include_player=False) categories=("inputline", "channels"), include_player=False)
cmdhandler(self, text, callertype="session", session=self) cmdhandler(session, text, callertype="session", session=session)
self.update_session_counters() session.update_session_counters()
execute_cmd = data_in # alias
def data_out(self, text=None, **kwargs): def data_out(self, text=None, **kwargs):
""" """
Send Evennia -> User Sending data from Evennia->Player
Kwargs: Kwargs:
text (str): A text to relay text (str or tuple)
kwargs (any): Other parameters to the protocol. 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 print "serversession.data_out:", text, kwargs
#text = timetrace(text, "ServerSession.data_out") 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) self.sessionhandler.data_out(self, text=text, **kwargs)
# alias msg = data_out # alias
msg = data_out
def __eq__(self, other): def __eq__(self, other):
"Handle session comparisons" "Handle session comparisons"
@ -427,6 +428,7 @@ class ServerSession(Session):
"Unicode representation" "Unicode representation"
return u"%s" % str(self) return u"%s" % str(self)
# Dummy API hooks for use during non-loggedin operation # Dummy API hooks for use during non-loggedin operation
def at_cmdset_get(self, **kwargs): def at_cmdset_get(self, **kwargs):

View file

@ -85,6 +85,9 @@ class Session(object):
self.protocol_flags = {} self.protocol_flags = {}
self.server_data = {} self.server_data = {}
# map of input data to session methods
self.datamap = {}
# a back-reference to the relevant sessionhandler this # a back-reference to the relevant sessionhandler this
# session is stored in. # session is stored in.
self.sessionhandler = sessionhandler self.sessionhandler = sessionhandler
@ -135,25 +138,23 @@ class Session(object):
""" """
pass pass
def data_out(self, text=None, **kwargs): def data_out(self, **kwargs):
""" """
Generic hook for sending data out through the protocol. Server Generic hook for sending data out through the protocol. Server
protocols can use this right away. Portal sessions protocols can use this right away. Portal sessions
should overload this to format/handle the outgoing data as needed. should overload this to format/handle the outgoing data as needed.
Kwargs: Kwargs:
text (str): Text data
kwargs (any): Other data to the protocol. kwargs (any): Other data to the protocol.
""" """
pass pass
def data_in(self, text=None, **kwargs): def data_in(self, **kwargs):
""" """
Hook for protocols to send incoming data to the engine. Hook for protocols to send incoming data to the engine.
Kwargs: Kwargs:
text (str): Text data
kwargs (any): Other data from the protocol. kwargs (any): Other data from the protocol.
""" """

View file

@ -18,6 +18,7 @@ from future.utils import listvalues
from time import time from time import time
from django.conf import settings from django.conf import settings
from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.commands.cmdhandler import CMD_LOGINSTART
from evennia.utils.logger import log_trace
from evennia.utils.utils import variable_from_module, is_iter, \ from evennia.utils.utils import variable_from_module, is_iter, \
to_str, to_unicode, strip_control_sequences, make_iter to_str, to_unicode, strip_control_sequences, make_iter
@ -57,7 +58,7 @@ _MULTISESSION_MODE = settings.MULTISESSION_MODE
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT _IDLE_TIMEOUT = settings.IDLE_TIMEOUT
_MAX_SERVER_COMMANDS_PER_SECOND = 100.0 _MAX_SERVER_COMMANDS_PER_SECOND = 100.0
_MAX_SESSION_COMMANDS_PER_SECOND = 5.0 _MAX_SESSION_COMMANDS_PER_SECOND = 5.0
_MODEL_MAP = None
def delayed_import(): def delayed_import():
""" """
@ -116,6 +117,47 @@ class SessionHandler(dict):
""" """
return dict((sessid, sess.get_sync_data()) for sessid, sess in self.items()) 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 # Server-SessionHandler class
@ -170,7 +212,7 @@ class ServerSessionHandler(SessionHandler):
# validate all scripts # validate all scripts
_ScriptDB.objects.validate() _ScriptDB.objects.validate()
self[sess.sessid] = sess self[sess.sessid] = sess
sess.data_in(CMD_LOGINSTART) sess.data_in(text=CMD_LOGINSTART)
def portal_session_sync(self, portalsessiondata): def portal_session_sync(self, portalsessiondata):
""" """
@ -498,7 +540,7 @@ class ServerSessionHandler(SessionHandler):
for sess in self.values(): for sess in self.values():
self.data_out(sess, message) self.data_out(sess, message)
def data_out(self, session, text="", **kwargs): def data_out(self, session, **kwargs):
""" """
Sending data Server -> Portal Sending data Server -> Portal
@ -506,24 +548,18 @@ class ServerSessionHandler(SessionHandler):
session (Session): Session to relay to. session (Session): Session to relay to.
text (str, optional): text data to return 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 # clean output for sending
#text = timetrace(text, "ServerSessionHandler.data_out") kwargs = self.clean_senddata(session, kwargs)
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)
# send across AMP # send across AMP
print "sessionhandler.data_out:", kwargs
self.server.amp_protocol.send_MsgServer2Portal(session, self.server.amp_protocol.send_MsgServer2Portal(session,
text=text,
**kwargs) **kwargs)
def data_in(self, session, text="", **kwargs): def data_in(self, session, **kwargs):
""" """
Data Portal -> Server. Data Portal -> Server.
We also intercept OOB communication here. We also intercept OOB communication here.
@ -532,25 +568,21 @@ class ServerSessionHandler(SessionHandler):
sessions (Session): Session. sessions (Session): Session.
Kwargs: Kwargs:
text (str): Text from protocol.
kwargs (any): Other data 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 # distribute incoming data to the correct receiving methods.
session.data_in(text=text, **kwargs) 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() SESSION_HANDLER = ServerSessionHandler()
SESSIONS = SESSION_HANDLER # legacy SESSIONS = SESSION_HANDLER # legacy