Implemented GMCP with the new oobhandler. Resolves #208.

This commit is contained in:
Griatch 2015-02-14 19:46:25 +01:00
parent 00aa28004c
commit 08525f11ee
8 changed files with 172 additions and 144 deletions

View file

@ -79,7 +79,7 @@ class Ticker(object):
appropriately. appropriately.
""" """
for key, (obj, args, kwargs) in self.subscriptions.items(): for key, (obj, args, kwargs) in self.subscriptions.items():
hook_key = yield kwargs.get("hook_key", "at_tick") hook_key = yield kwargs.get("_hook_key", "at_tick")
if not obj: if not obj:
# object was deleted between calls # object was deleted between calls
self.validate() self.validate()
@ -256,21 +256,48 @@ class TickerHandler(object):
def add(self, obj, interval, idstring="", hook_key="at_tick", *args, **kwargs): def add(self, obj, interval, idstring="", hook_key="at_tick", *args, **kwargs):
""" """
Add object to tickerhandler. The object must have an at_tick Add object to tickerhandler
method. This will be called every interval seconds until the
object is unsubscribed from the ticker. Args:
obj (Object): The object to subscribe to the ticker.
interval (int): Interval in seconds between calling
`hook_key` below.
idstring (str, optional): Identifier for separating
this ticker-subscription from others with the same
interval. Allows for managing multiple calls with
the same time interval
hook_key (str, optional): The name of the hook method
on `obj` to call every `interval` seconds. Defaults to
`at_tick()`.
args, kwargs (optional): These will be passed into the
method given by `hook_key` every time it is called.
Notes:
The combination of `obj`, `interval` and `idstring`
together uniquely defines the ticker subscription. They
must all be supplied in order to unsubscribe from it
later.
""" """
isdb, store_key = self._store_key(obj, interval, idstring) isdb, store_key = self._store_key(obj, interval, idstring)
if isdb: if isdb:
self.ticker_storage[store_key] = (args, kwargs) self.ticker_storage[store_key] = (args, kwargs)
self.save() self.save()
kwargs["hook_key"] = hook_key kwargs["_hook_key"] = hook_key
self.ticker_pool.add(store_key, obj, interval, *args, **kwargs) self.ticker_pool.add(store_key, obj, interval, *args, **kwargs)
def remove(self, obj, interval=None, idstring=""): def remove(self, obj, interval=None, idstring=""):
""" """
Remove object from ticker, or only this object ticking Remove object from ticker or only remove it from tickers with
at a given interval. a given interval.
Args:
obj (Object): The object subscribing to the ticker.
interval (int, optional): Interval of ticker to remove. If
`None`, all tickers on this object matching `idstring`
will be removed, regardless of their `interval` setting.
idstring (str, optional): Identifier id of ticker to remove.
""" """
if interval: if interval:
isdb, store_key = self._store_key(obj, interval, idstring) isdb, store_key = self._store_key(obj, interval, idstring)

View file

@ -373,10 +373,10 @@ class AMPProtocol(amp.AMP):
""" """
Relays message to Portal. This method is executed on the Portal. Relays message to Portal. This method is executed on the Portal.
""" """
#print "msg server->portal (portal side):", sessid, msg
ret = self.safe_recv(MsgServer2Portal, sessid, ret = self.safe_recv(MsgServer2Portal, sessid,
ipart, nparts, text=msg, data=data) ipart, nparts, text=msg, data=data)
if ret is not None: if ret is not None:
print "msg server->portal (portal side):", sessid, ret["text"], loads(ret["data"])
self.factory.portal.sessions.data_out(sessid, self.factory.portal.sessions.data_out(sessid,
text=ret["text"], text=ret["text"],
**loads(ret["data"])) **loads(ret["data"]))

View file

@ -89,7 +89,7 @@ def oob_error(oobhandler, session, errmsg, *args, **kwargs):
management. management.
""" """
session.msg(oob=("error", ("OOB ERROR: %s" % errmsg))) session.msg(oob=("error", ("OOB ERROR: %s" % errmsg,)))
def oob_echo(oobhandler, session, *args, **kwargs): def oob_echo(oobhandler, session, *args, **kwargs):
""" """
@ -107,7 +107,7 @@ def oob_echo(oobhandler, session, *args, **kwargs):
##OOB{"repeat":10} ##OOB{"repeat":10}
def oob_repeat(oobhandler, session, oobfuncname, interval, *args, **kwargs): def oob_repeat(oobhandler, session, oobfuncname, interval, *args, **kwargs):
""" """
Called as REPEAT <oobfunc> <interval> Called as REPEAT <oobfunc> <interval> <args>
Repeats a given OOB command with a certain frequency. Repeats a given OOB command with a certain frequency.
Args: Args:
@ -120,8 +120,14 @@ def oob_repeat(oobhandler, session, oobfuncname, interval, *args, **kwargs):
The command checks so that it cannot repeat itself. The command checks so that it cannot repeat itself.
""" """
if oobfuncname != "REPEAT": if not oobfuncname:
oobhandler.add_repeat(None, session.sessid, oobfuncname, interval, *args, **kwargs) oob_error(oobhandler, session, "Usage: REPEAT <oobfuncname>, <interval>")
return
# limit repeat actions to minimum 5 seconds interval
interval = 20 if not interval else (max(5, interval))
obj = session.get_puppet_or_player()
if obj and oobfuncname != "REPEAT":
oobhandler.add_repeater(obj, session.sessid, oobfuncname, interval, *args, **kwargs)
##OOB{"UNREPEAT":10} ##OOB{"UNREPEAT":10}
@ -132,16 +138,18 @@ def oob_unrepeat(oobhandler, session, oobfuncname, interval):
Args: Args:
oobhandler (OOBHandler): main OOB handler. oobhandler (OOBHandler): main OOB handler.
session (Session): Session controlling the repeat session (Session): Session controlling the repeater
oobfuncname (str): OOB function called every interval seconds oobfuncname (str): OOB function called every interval seconds
interval (int): Interval of repeat, in seconds. interval (int): Interval of repeater, in seconds.
Notes: Notes:
The command checks so that it cannot repeat itself. The command checks so that it cannot repeat itself.
""" """
oobhandler.remove_repeat(None, session.sessid, oobfuncname, interval) obj = session.get_puppet_or_player()
if obj:
oobhandler.remove_repeater(obj, session.sessid, oobfuncname, interval)
# #
@ -194,7 +202,7 @@ def oob_send(oobhandler, session, *args, **kwargs):
if obj: if obj:
for name in (a.upper() for a in args if a): for name in (a.upper() for a in args if a):
try: try:
print "MSDP SEND inp:", name #print "MSDP SEND inp:", name
value = OOB_SENDABLE.get(name, _NA)(obj) value = OOB_SENDABLE.get(name, _NA)(obj)
ret[name] = value ret[name] = value
except Exception, e: except Exception, e:
@ -253,7 +261,7 @@ def oob_report(oobhandler, session, *args, **kwargs):
else: else:
oobhandler.add_attribute_monitor(obj, session.sessid, propname, "return_attribute_report") oobhandler.add_attribute_monitor(obj, session.sessid, propname, "return_attribute_report")
ret.append(_GA(obj, "db_value")) ret.append(_GA(obj, "db_value"))
print "ret:", ret #print "ret:", ret
session.msg(oob=("MSDP_ARRAY", ret)) session.msg(oob=("MSDP_ARRAY", ret))
else: else:
oob_error(oobhandler, session, "You must log in first.") oob_error(oobhandler, session, "You must log in first.")
@ -336,11 +344,6 @@ def oob_list(oobhandler, session, mode, *args, **kwargs):
"UNREPORT", "UNREPORT",
# "RESET", # "RESET",
"SEND"))) "SEND")))
elif mode == "LISTS":
session.msg(oob=("LISTS",("REPORTABLE_VARIABLES",
"REPORTED_VARIABLES",
# "CONFIGURABLE_VARIABLES",
"SENDABLE_VARIABLES")))
elif mode == "REPORTABLE_VARIABLES": elif mode == "REPORTABLE_VARIABLES":
session.msg(oob=("REPORTABLE_VARIABLES", tuple(key for key in OOB_REPORTABLE.keys()))) session.msg(oob=("REPORTABLE_VARIABLES", tuple(key for key in OOB_REPORTABLE.keys())))
elif mode == "REPORTED_VARIABLES": elif mode == "REPORTED_VARIABLES":
@ -356,7 +359,11 @@ def oob_list(oobhandler, session, mode, *args, **kwargs):
# Not implemented (game specific) # Not implemented (game specific)
oob_error(oobhandler, session, "Not implemented (game specific)") oob_error(oobhandler, session, "Not implemented (game specific)")
else: else:
oob_error(oobhandler, session, "Unsupported mode") # mode == "LISTS" or not given
session.msg(oob=("LISTS",("REPORTABLE_VARIABLES",
"REPORTED_VARIABLES",
# "CONFIGURABLE_VARIABLES",
"SENDABLE_VARIABLES")))
# #
# Cmd mapping # Cmd mapping
@ -368,8 +375,8 @@ def oob_list(oobhandler, session, mode, *args, **kwargs):
CMD_MAP = {"oob_error": oob_error, # will get error messages CMD_MAP = {"oob_error": oob_error, # will get error messages
"return_field_report": oob_return_field_report, "return_field_report": oob_return_field_report,
"return_attribute_report": oob_return_attribute_report, "return_attribute_report": oob_return_attribute_report,
"repeat": oob_repeat, "REPEAT": oob_repeat,
"unrepeat": oob_unrepeat, "UNREPEAT": oob_unrepeat,
"SEND": oob_send, "SEND": oob_send,
"ECHO": oob_echo, "ECHO": oob_echo,
"REPORT": oob_report, "REPORT": oob_report,

View file

@ -1,36 +1,14 @@
""" """
OOBHandler - Out Of Band Handler OOBHandler - Out Of Band Handler
The OOBHandler.execute_cmd is called by the sessionhandler when it detects The OOBHandler.execute_cmd is called by the sessionhandler when it
an OOB instruction (exactly how this looked depends on the protocol; at this detects an `oob` keyword in the outgoing data (usually called via
point all oob calls should look the same) `msg(oob=...)`
The handler pieces of functionality: How this works is that the handler executes an oobfunction, which is
defined in a user-supplied module. This function can then make use of
function execution - the oob protocol can execute a function directly on the oobhandler's functionality to return data, register a monitor on
the server. The available functions must be defined an object's properties or start a repeating action.
as global functions in settings.OOB_PLUGIN_MODULES.
repeat func execution - the oob protocol can request a given function be
executed repeatedly at a regular interval. This
uses an internal script pool.
tracking - the oob protocol can request Evennia to track changes to
fields on objects, as well as changes in Attributes. This is
done by dynamically adding tracker-objects on entities. The
behaviour of those objects can be customized by adding new
tracker classes in settings.OOB_PLUGIN_MODULES.
What goes into the OOB_PLUGIN_MODULES is a (list of) modules that contains
the working server-side code available to the OOB system: oob functions and
tracker classes.
oob functions have the following call signature:
function(caller, session, *args, **kwargs)
oob trackers should inherit from the OOBTracker class (in this
module) and implement a minimum of the same functionality.
If a function named "oob_error" is given, this will be called with error
messages.
""" """
@ -132,17 +110,21 @@ class OOBFieldMonitor(object):
self.subscribers.pop(sessid, None) self.subscribers.pop(sessid, None)
class OOBAtRepeat(object): class OOBAtRepeater(object):
""" """
This object should be stored on a target object, named This object is created and used by the `OOBHandler.repeat` method.
as the hook to call repeatedly, e.g. It will be assigned to a target object as a custom variable, e.g.:
`obj._oob_ECHO_every_20s_for_sessid_1 = AtRepater()`
It will be called every interval seconds by the OOBHandler,
triggering whatever OOB function it is set to use.
_oob_listen_every_20s_for_sessid_1 = AtRepat()
""" """
def __call__(self, sessid, oobfuncname, *args, **kwargs): def __call__(self, *args, **kwargs):
"Called at regular intervals. Calls the oob function" "Called at regular intervals. Calls the oob function"
OOB_HANDLER.execute_cmd(sessid, oobfuncname, *args, **kwargs) OOB_HANDLER.execute_cmd(kwargs["_sessid"], kwargs["_oobfuncname"], *args, **kwargs)
# Main OOB Handler # Main OOB Handler
@ -153,13 +135,13 @@ class OOBHandler(TickerHandler):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(OOBHandler, self).__init__(*args, **kwargs)
self.save_name = "oob_ticker_storage" self.save_name = "oob_ticker_storage"
self.oob_save_name = "oob_monitor_storage" self.oob_save_name = "oob_monitor_storage"
self.oob_monitor_storage = {} self.oob_monitor_storage = {}
super(OOBHandler, self).__init__(*args, **kwargs)
def _get_repeat_hook_name(self, oobfuncname, interval, sessid): def _get_repeater_hook_name(self, oobfuncname, interval, sessid):
"Return the unique repeat call hook name for this object" "Return the unique repeater call hook name for this object"
return "_oob_%s_every_%ss_for_sessid_%s" % (oobfuncname, interval, sessid) return "_oob_%s_every_%ss_for_sessid_%s" % (oobfuncname, interval, sessid)
def _get_fieldmonitor_name(self, fieldname): def _get_fieldmonitor_name(self, fieldname):
@ -220,7 +202,7 @@ class OOBHandler(TickerHandler):
by the Server process as part of the reload upstart. Here we by the Server process as part of the reload upstart. Here we
overload the tickerhandler's restore method completely to make overload the tickerhandler's restore method completely to make
sure we correctly re-apply and re-initialize the correct sure we correctly re-apply and re-initialize the correct
monitor and repeat objects on all saved objects. monitor and repeater objecth on all saved objects.
""" """
# load the oob monitors and initialize them # load the oob monitors and initialize them
oob_storage = ServerConfig.objects.conf(key=self.oob_save_name) oob_storage = ServerConfig.objects.conf(key=self.oob_save_name)
@ -232,7 +214,7 @@ class OOBHandler(TickerHandler):
obj = unpack_dbobj(obj) obj = unpack_dbobj(obj)
self._add_monitor(obj, sessid, fieldname, oobfuncname, *args, **kwargs) self._add_monitor(obj, sessid, fieldname, oobfuncname, *args, **kwargs)
# handle the tickers (same as in TickerHandler except we call # handle the tickers (same as in TickerHandler except we call
# the add_repeat method which makes sure to add the hooks before # the add_repeater method which makes sure to add the hooks before
# starting the tickerpool) # starting the tickerpool)
ticker_storage = ServerConfig.objects.conf(key=self.save_name) ticker_storage = ServerConfig.objects.conf(key=self.save_name)
if ticker_storage: if ticker_storage:
@ -240,12 +222,12 @@ class OOBHandler(TickerHandler):
for store_key, (args, kwargs) in self.ticker_storage.items(): for store_key, (args, kwargs) in self.ticker_storage.items():
obj, interval, idstring = store_key obj, interval, idstring = store_key
obj = unpack_dbobj(obj) obj = unpack_dbobj(obj)
# we saved these in add_repeat before, can now retrieve them # we saved these in add_repeater before, can now retrieve them
sessid = kwargs["sessid"] sessid = kwargs["_sessid"]
oobfuncname = kwargs["oobfuncname"] oobfuncname = kwargs["_oobfuncname"]
self.add_repeat(obj, sessid, oobfuncname, interval, *args, **kwargs) self.add_repeater(obj, sessid, oobfuncname, interval, *args, **kwargs)
def add_repeat(self, obj, sessid, oobfuncname, interval=20, *args, **kwargs): def add_repeater(self, obj, sessid, oobfuncname, interval=20, *args, **kwargs):
""" """
Set an oob function to be repeatedly called. Set an oob function to be repeatedly called.
@ -261,22 +243,20 @@ class OOBHandler(TickerHandler):
if not isinstance(sessid, int): if not isinstance(sessid, int):
sessid = sessid.sessid sessid = sessid.sessid
hook = OOBAtRepeat() hook = OOBAtRepeater()
hookname = self._get_repeat_hook_name(oobfuncname, interval, sessid) hookname = self._get_repeater_hook_name(oobfuncname, interval, sessid)
_SA(obj, hookname, hook) _SA(obj, hookname, hook)
kwargs.update({"sessid":sessid, "oobfuncname":oobfuncname})
# we store these in kwargs so that tickerhandler saves them with the rest # we store these in kwargs so that tickerhandler saves them with the rest
kwargs["sessid"] = sessid kwargs.update({"_sessid":sessid, "_oobfuncname":oobfuncname})
kwargs["oobfuncbame"] = oobfuncname super(OOBHandler, self).add(obj, int(interval), oobfuncname, hookname, *args, **kwargs)
self.add(obj, interval, idstring=oobfuncname, hook_key=hookname, *args, **kwargs)
def remove_repeat(self, obj, sessid, oobfuncname, interval=20): def remove_repeater(self, obj, sessid, oobfuncname, interval=20):
""" """
Remove the repeatedly calling oob function Remove the repeatedly calling oob function
Args: Args:
obj (Object): The object on which the repeater sits obj (Object): The object on which the repeater sits
sessid (int): Session id of the Session that registered the repeat sessid (int): Session id of the Session that registered the repeater
oobfuncname (str): Name of oob function to call at repeat oobfuncname (str): Name of oob function to call at repeat
interval (int, optional): Number of seconds between repeats interval (int, optional): Number of seconds between repeats
@ -284,8 +264,8 @@ class OOBHandler(TickerHandler):
# check so we didn't get a session instead of a sessid # check so we didn't get a session instead of a sessid
if not isinstance(sessid, int): if not isinstance(sessid, int):
sessid = sessid.sessid sessid = sessid.sessid
self.remove(obj, interval, idstring=oobfuncname) super(OOBHandler, self).remove(obj, interval, idstring=oobfuncname)
hookname = self._get_repeat_hook_name(oobfuncname, interval, sessid) hookname = self._get_repeater_hook_name(oobfuncname, interval, sessid)
try: try:
_DA(obj, hookname) _DA(obj, hookname)
except AttributeError: except AttributeError:
@ -406,8 +386,8 @@ class OOBHandler(TickerHandler):
oobfuncname (str): The name of the oob command (case sensitive) oobfuncname (str): The name of the oob command (case sensitive)
Notes: Notes:
If the oobfuncname is a valid oob function, the `*args` and If the oobfuncname is a valid oob function, `args` and
`**kwargs` are passed into the oob command. `kwargs` are passed into the oob command.
""" """
if isinstance(session, int): if isinstance(session, int):
@ -418,13 +398,19 @@ class OOBHandler(TickerHandler):
(session, oobfuncname, args, kwargs) (session, oobfuncname, args, kwargs)
raise RuntimeError(errmsg) raise RuntimeError(errmsg)
print "execute_oob:", session, oobfuncname, args, kwargs #print "execute_oob:", session, oobfuncname, args, kwargs
# don't catch this, wrong oobfuncname should be reported try:
oobfunc = _OOB_FUNCS[oobfuncname] oobfunc = _OOB_FUNCS[oobfuncname]
except Exception:
errmsg = "'%s' is not a valid OOB command. Commands available:\n %s" % (oobfuncname, ", ".join(_OOB_FUNCS))
if _OOB_ERROR:
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
errmsg = "OOB ERROR: %s" % errmsg
logger.log_trace(errmsg)
raise
# we found an oob command. Execute it. # we found an oob command. Execute it.
try: try:
#print "OOB execute_cmd:", session, func_key, args, kwargs, _OOB_FUNCS.keys()
oobfunc(self, session, *args, **kwargs) oobfunc(self, session, *args, **kwargs)
except Exception, err: except Exception, err:
errmsg = "Exception in %s(*%s, **%s):\n%s" % (oobfuncname, args, kwargs, err) errmsg = "Exception in %s(*%s, **%s):\n%s" % (oobfuncname, args, kwargs, err)

View file

@ -191,17 +191,22 @@ class PortalSessionHandler(SessionHandler):
Helper method for each session to use to parse oob structures Helper method for each session to use to parse oob structures
(The 'oob' kwarg of the msg() method). (The 'oob' kwarg of the msg() method).
Allowed input oob structures are: Args: oobstruct (str or iterable): A structure representing
cmdname an oob command on one of the following forms:
((cmdname,), (cmdname,)) - "cmdname"
(cmdname,(arg, )) - "cmdname", "cmdname"
(cmdname,(arg1,arg2)) - ("cmdname", arg)
(cmdname,{key:val,key2:val2}) - ("cmdname",(args))
(cmdname, (args,), {kwargs}) - ("cmdname",{kwargs}
((cmdname, (arg1,arg2)), cmdname, (cmdname, (arg1,))) - ("cmdname", (args), {kwargs})
outputs an ordered structure on the form - (("cmdname", (args,), {kwargs}), ("cmdname", (args,), {kwargs}))
((cmdname, (args,), {kwargs}), ...), where the two last and any combination of argument-less commands or commands with only
parts of each tuple may be empty 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): def _parse(oobstruct):
slen = len(oobstruct) slen = len(oobstruct)
@ -217,17 +222,17 @@ class PortalSessionHandler(SessionHandler):
return (oobstruct[0], (), {}) return (oobstruct[0], (), {})
elif slen == 2: elif slen == 2:
if isinstance(oobstruct[1], dict): if isinstance(oobstruct[1], dict):
# cmdname, {kwargs} # (cmdname, {kwargs})
return (oobstruct[0], (), dict(oobstruct[1])) return (oobstruct[0], (), dict(oobstruct[1]))
elif isinstance(oobstruct[1], (tuple, list)): elif isinstance(oobstruct[1], (tuple, list)):
# cmdname, (args,) # (cmdname, (args,))
return (oobstruct[0], list(oobstruct[1]), {}) return (oobstruct[0], tuple(oobstruct[1]), {})
else: else:
# cmdname, cmdname # (cmdname, arg)
return ((oobstruct[0], (), {}), (oobstruct[1].lower(), (), {})) return (oobstruct[0], (oobstruct[1],), {})
else: else:
# cmdname, (args,), {kwargs} # (cmdname, (args,), {kwargs})
return (oobstruct[0], list(oobstruct[1]), dict(oobstruct[2])) return (oobstruct[0], tuple(oobstruct[1]), dict(oobstruct[2]))
if hasattr(oobstruct, "__iter__"): if hasattr(oobstruct, "__iter__"):
# differentiate between (cmdname, cmdname), # differentiate between (cmdname, cmdname),
@ -264,7 +269,9 @@ class PortalSessionHandler(SessionHandler):
if session: if session:
# convert oob to the generic format # convert oob to the generic format
if "oob" in kwargs: if "oob" in kwargs:
print "oobstruct_parser in:", kwargs["oob"]
kwargs["oob"] = self.oobstruct_parser(kwargs["oob"]) kwargs["oob"] = self.oobstruct_parser(kwargs["oob"])
print "oobstruct_parser out:", kwargs["oob"]
session.data_out(text=text, **kwargs) session.data_out(text=text, **kwargs)
PORTAL_SESSIONS = PortalSessionHandler() PORTAL_SESSIONS = PortalSessionHandler()

View file

@ -26,8 +26,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
""" """
def connectionMade(self): def connectionMade(self):
""" """
This is called when the connection is first This is called when the connection is first established.
established.
""" """
# initialize the session # initialize the session
self.iaw_mode = False self.iaw_mode = False
@ -35,7 +34,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
client_address = self.transport.client client_address = self.transport.client
# this number is counted down for every handshake that completes. # this number is counted down for every handshake that completes.
# when it reaches 0 the portal/server syncs their data # when it reaches 0 the portal/server syncs their data
self.handshakes = 6 # naws, ttype, mccp, mssp, oob, mxp self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
self.init_session("telnet", client_address, self.factory.sessionhandler) self.init_session("telnet", client_address, self.factory.sessionhandler)
# negotiate client size # negotiate client size
@ -47,7 +46,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.mccp = Mccp(self) self.mccp = Mccp(self)
# negotiate mssp (crawler communication) # negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self) self.mssp = mssp.Mssp(self)
# oob communication (MSDP, GMCP) # oob communication (MSDP, GMCP) - two handshake calls!
self.oob = telnet_oob.TelnetOOB(self) self.oob = telnet_oob.TelnetOOB(self)
# mxp support # mxp support
self.mxp = Mxp(self) self.mxp = Mxp(self)
@ -67,7 +66,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
When all have reported, a sync with the server is performed. When all have reported, a sync with the server is performed.
The system will force-call this sync after a small time to handle The system will force-call this sync after a small time to handle
clients that don't reply to handshakes at all. clients that don't reply to handshakes at all.
info - debug text from the protocol calling
""" """
if self.handshakes > 0: if self.handshakes > 0:
if force: if force:
@ -239,7 +237,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if "oob" in kwargs and "OOB" in self.protocol_flags: if "oob" in kwargs and "OOB" in self.protocol_flags:
# oob is a list of [(cmdname, arg, kwarg), ...] # oob is a list of [(cmdname, arg, kwarg), ...]
for cmdname, args, kwargs in kwargs["oob"]: for cmdname, args, kwargs in kwargs["oob"]:
print "telnet oob data_out:", cmdname, args, kwargs #print "telnet oob data_out:", cmdname, args, kwargs
self.oob.data_out(cmdname, *args, **kwargs) self.oob.data_out(cmdname, *args, **kwargs)
# parse **kwargs, falling back to ttype if nothing is given explicitly # parse **kwargs, falling back to ttype if nothing is given explicitly

View file

@ -33,7 +33,7 @@ MSDP_ARRAY_OPEN = chr(5)
MSDP_ARRAY_CLOSE = chr(6) MSDP_ARRAY_CLOSE = chr(6)
# GMCP # GMCP
GMCP = chr(200) GMCP = chr(201)
# General Telnet # General Telnet
IAC = chr(255) IAC = chr(255)
@ -71,16 +71,18 @@ class TelnetOOB(object):
self.protocol.protocol_flags['OOB'] = False self.protocol.protocol_flags['OOB'] = False
self.MSDP = False self.MSDP = False
self.GMCP = False self.GMCP = False
# detect MSDP first # ask for the available protocols and assign decoders
self.protocol.negotiationMap[MSDP] = self.data_in # (note that handshake_done() will be called twice!)
self.protocol.negotiationMap[MSDP] = self.decode_msdp
self.protocol.negotiationMap[GMCP] = self.decode_gmcp
self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp) self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp)
self.protocol.will(GMCP).addCallbacks(self.do_gmcp, self.no_gmcp)
self.oob_reported = {} self.oob_reported = {}
def no_msdp(self, option): def no_msdp(self, option):
"No msdp supported or wanted" "No msdp supported or wanted"
# no msdp, check GMCP # no msdp, check GMCP
self.protocol.negotiationMap[GMCP] = self.data_in self.protocol.handshake_done()
self.protocol.will(GMCP).addCallbacks(self.do_oob, self.no_oob)
def do_msdp(self, option): def do_msdp(self, option):
"MSDP supported by client" "MSDP supported by client"
@ -122,16 +124,16 @@ class TelnetOOB(object):
if cmdname == "MSDP_ARRAY": if cmdname == "MSDP_ARRAY":
msdp_string = "".join(["%s%s" % (MSDP_VAL, val) for val in args]) msdp_string = "".join(["%s%s" % (MSDP_VAL, val) for val in args])
else: else:
msdp_string = "%s%s%s" % (MSDP_VAR, cmdname.upper(), "".join( msdp_string = "%s%s%s" % (MSDP_VAR, cmdname, "".join(
"%s%s" % (MSDP_VAL, val) for val in args)) "%s%s" % (MSDP_VAL, val) for val in args))
elif kwargs: elif kwargs:
if cmdname == "MSDP_TABLE": if cmdname == "MSDP_TABLE":
msdp_string = "".join(["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val) msdp_string = "".join(["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val)
for key, val in kwargs.items()]) for key, val in kwargs.items()])
else: else:
msdp_string = "%s%s%s" % (MSDP_VAR. cmdname.upper(), "".join( msdp_string = "%s%s%s" % (MSDP_VAR. cmdname, "".join(
["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val) for key, val in kwargs.items()])) ["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val) for key, val in kwargs.items()]))
print "encode msdp result:", cmdname, args, kwargs, "->", msdp_string #print "encode msdp result:", cmdname, args, kwargs, "->", msdp_string
return force_str(msdp_string) return force_str(msdp_string)
def encode_gmcp(self, cmdname, *args, **kwargs): def encode_gmcp(self, cmdname, *args, **kwargs):
@ -145,7 +147,7 @@ class TelnetOOB(object):
cmdname is generally recommended to be a string on the form cmdname is generally recommended to be a string on the form
Module.Submodule.Function Module.Submodule.Function
""" """
if cmdname in ("SEND", "ECHO", "REPORT", "UNREPORT", "LIST"): if cmdname in ("SEND", "REPORT", "UNREPORT", "LIST"):
# we wrap the standard MSDP commands in a MSDP.submodule # we wrap the standard MSDP commands in a MSDP.submodule
# here as far as GMCP is concerned. # here as far as GMCP is concerned.
cmdname = "MSDP.%s" % cmdname cmdname = "MSDP.%s" % cmdname
@ -156,15 +158,16 @@ class TelnetOOB(object):
gmcp_string = "%s %s" % (cmdname, json.dumps(args)) gmcp_string = "%s %s" % (cmdname, json.dumps(args))
elif kwargs: elif kwargs:
gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs)) gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs))
print "gmcp_encode", cmdname, args, kwargs, "->", gmcp_string
return force_str(gmcp_string).strip() return force_str(gmcp_string).strip()
def decode_msdp(self, data): def decode_msdp(self, data):
""" """
Decodes incoming MSDP data Decodes incoming MSDP data
cmdname, var --> cmdname arg cmdname var --> cmdname arg
cmdname, array --> cmdname *args cmdname array --> cmdname *args
cmdname, table --> cmdname **kwargs cmdname table --> cmdname **kwargs
""" """
tables = {} tables = {}
@ -174,8 +177,6 @@ class TelnetOOB(object):
if hasattr(data, "__iter__"): if hasattr(data, "__iter__"):
data = "".join(data) data = "".join(data)
#logger.log_infomsg("MSDP SUBNEGOTIATION: %s" % data)
# decode # decode
for key, table in msdp_regex_table.findall(data): for key, table in msdp_regex_table.findall(data):
tables[key] = {} tables[key] = {}
@ -190,9 +191,9 @@ class TelnetOOB(object):
for varval in msdp_regex_var.split(msdp_regex_array.sub("", msdp_regex_table.sub("", data))): for varval in msdp_regex_var.split(msdp_regex_array.sub("", msdp_regex_table.sub("", data))):
# get remaining varvals after cleaning away tables/arrays # get remaining varvals after cleaning away tables/arrays
parts = msdp_regex_val.split(varval) parts = msdp_regex_val.split(varval)
variables[parts[0].upper()] = tuple(parts[1:]) if len(parts) > 1 else ("", ) variables[parts[0]] = tuple(parts[1:]) if len(parts) > 1 else ("", )
print "OOB: MSDP decode:", data, "->", variables, arrays, tables #print "OOB: MSDP decode:", data, "->", variables, arrays, tables
# send to the sessionhandler # send to the sessionhandler
if data: if data:
@ -215,16 +216,27 @@ class TelnetOOB(object):
cmdname {key:arg, key:arg, ...} -> cmdname **kwargs cmdname {key:arg, key:arg, ...} -> cmdname **kwargs
""" """
if hasattr(data, "__iter__"):
data = "".join(data)
print "decode_gmcp:", data
if data: if data:
splits = data.split(" ", 1) splits = data.split(" ", 1)
cmdname = splits[0] cmdname = splits[0]
if len(splits) < 2: if len(splits) < 2:
self.protocol.data_in(oob=(cmdname, (), {})) self.protocol.data_in(oob=(cmdname, (), {}))
elif splits[1]:
struct = json.loads(json.dumps(splits[1]))
args, kwargs = (), {}
if hasattr(struct, "__iter__"):
if isinstance(struct, dict):
kwargs = struct
else: else:
struct = json.loads(splits[1]) args = tuple(struct)
self.protocol.data_in(oob=(cmdname, else:
struct if isinstance(struct, list) else (), args = (struct,)
struct if isinstance(struct, dict) else {})) print "gmcp decode:", data, "->", cmdname, args, kwargs
self.protocol.data_in(oob=(cmdname, args, kwargs))
# access methods # access methods
@ -232,20 +244,10 @@ class TelnetOOB(object):
""" """
Return a msdp-valid subnegotiation across the protocol. Return a msdp-valid subnegotiation across the protocol.
""" """
#print "data_out:", encoded_oob
if self.MSDP: if self.MSDP:
encoded_oob = self.encode_msdp(cmdname, *args, **kwargs) encoded_oob = self.encode_msdp(cmdname, *args, **kwargs)
print "data_out:", encoded_oob
self.protocol._write(IAC + SB + MSDP + encoded_oob + IAC + SE) self.protocol._write(IAC + SB + MSDP + encoded_oob + IAC + SE)
else: if self.GMCP:
encoded_oob = self.encode_gmcp(cmdname, *args, **kwargs) encoded_oob = self.encode_gmcp(cmdname, *args, **kwargs)
self.protocol._write(IAC + SB + GMCP + encoded_oob + IAC + SE) self.protocol._write(IAC + SB + GMCP + encoded_oob + IAC + SE)
def data_in(self, data):
"""
Send oob data to Evennia. The self.decode_* methods send to
protocol.data_in() themselves.
"""
if self.MSDP:
self.decode_msdp(data)
else:
self.decode_gmcp(data)

View file

@ -438,6 +438,7 @@ class ServerSessionHandler(SessionHandler):
if not _OOB_HANDLER: if not _OOB_HANDLER:
from evennia.server.oobhandler import OOB_HANDLER as _OOB_HANDLER from evennia.server.oobhandler import OOB_HANDLER as _OOB_HANDLER
funcname, args, kwargs = kwargs.pop("oob") funcname, args, kwargs = kwargs.pop("oob")
print "OOB session.data_in:", funcname, args, kwargs
if funcname: if funcname:
_OOB_HANDLER.execute_cmd(session, funcname, *args, **kwargs) _OOB_HANDLER.execute_cmd(session, funcname, *args, **kwargs)