Implemented GMCP with the new oobhandler. Resolves #208.
This commit is contained in:
parent
00aa28004c
commit
08525f11ee
8 changed files with 172 additions and 144 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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"]))
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,8 +147,8 @@ 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
|
||||||
elif cmdname in ("MSDP_ARRAY", "MSDP_TABLE"):
|
elif cmdname in ("MSDP_ARRAY", "MSDP_TABLE"):
|
||||||
|
|
@ -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, (), {}))
|
||||||
else:
|
elif splits[1]:
|
||||||
struct = json.loads(splits[1])
|
struct = json.loads(json.dumps(splits[1]))
|
||||||
self.protocol.data_in(oob=(cmdname,
|
args, kwargs = (), {}
|
||||||
struct if isinstance(struct, list) else (),
|
if hasattr(struct, "__iter__"):
|
||||||
struct if isinstance(struct, dict) else {}))
|
if isinstance(struct, dict):
|
||||||
|
kwargs = struct
|
||||||
|
else:
|
||||||
|
args = tuple(struct)
|
||||||
|
else:
|
||||||
|
args = (struct,)
|
||||||
|
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)
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue