Changes to the input-handling interface, to support function calls; also changing oob system to be inputhandler system, making it all work in the same way (no separation between text and oob anymore).

This commit is contained in:
Griatch 2016-02-10 15:31:09 +01:00
parent d966051558
commit e4d50ff74e
7 changed files with 133 additions and 97 deletions

View file

@ -59,16 +59,57 @@ from future.utils import viewkeys
from django.conf import settings
from evennia.utils.utils import to_str
from evennia.server.oobhandler import OOB_HANDLER
from evennia.commands.cmdhandler import cmdhandler
#from evennia.server.inputhandler import INPUT_HANDLER
_IDLE_COMMAND = settings.IDLE_COMMAND
_GA = object.__getattribute__
_SA = object.__setattr__
_NA = lambda o: "N/A"
def text(session, *args, **kwargs):
"""
Main text input from the client. This will execute a command
string on the server.
Args:
text (str): First arg is used as text-command input. Other
arguments are ignored.
"""
#from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "ServerSession.data_in")
text = args[0] if args else None
#explicitly check for None since text can be an empty string, which is
#also valid
if text is None:
return
# this is treated as a command input
#text = to_unicode(escape_control_sequences(text), encoding=self.encoding)
# handle the 'idle' command
if text.strip() == _IDLE_COMMAND:
session.update_session_counters(idle=True)
return
if session.player:
# nick replacement
puppet = session.puppet
if puppet:
text = puppet.nicks.nickreplace(text,
categories=("inputline", "channel"), include_player=True)
else:
text = session.player.nicks.nickreplace(text,
categories=("inputline", "channels"), include_player=False)
cmdhandler(session, text, callertype="session", session=session)
session.update_session_counters()
#------------------------------------------------------------------------------------
#------------------------------------------------------------
# All OOB commands must be on the form
# cmdname(oobhandler, session, *args, **kwargs)
# cmdname(session, *args, **kwargs)
#------------------------------------------------------------
#
@ -126,7 +167,7 @@ def oob_repeat(session, oobfuncname, interval, *args, **kwargs):
interval = 20 if not interval else (max(5, interval))
obj = session.get_puppet_or_player()
if obj and oobfuncname != "REPEAT":
OOB_HANDLER.add_repeater(obj, session, oobfuncname, interval, *args, **kwargs)
INPUT_HANDLER.add_repeater(obj, session, oobfuncname, interval, *args, **kwargs)
##OOB{"UNREPEAT":10}
@ -147,7 +188,7 @@ def oob_unrepeat(session, oobfuncname, interval):
"""
obj = session.get_puppet_or_player()
if obj:
OOB_HANDLER.remove_repeater(obj, session, oobfuncname, interval)
INPUT_HANDLER.remove_repeater(obj, session, oobfuncname, interval)
#
@ -165,7 +206,7 @@ def oob_unrepeat(session, oobfuncname, interval):
# mapping from MSDP standard names to Evennia variables
OOB_SENDABLE = {
_OOB_SENDABLE = {
"CHARACTER_NAME": lambda o: o.key,
"SERVER_ID": lambda o: settings.SERVERNAME,
"ROOM_NAME": lambda o: o.db_location.key,
@ -210,7 +251,7 @@ def oob_send(session, *args, **kwargs):
# mapping standard MSDP keys to Evennia field names
OOB_REPORTABLE = {
_OOB_REPORTABLE = {
"CHARACTER_NAME": "db_key",
"ROOM_NAME": "db_location",
"TEST" : "test"
@ -252,10 +293,10 @@ def oob_report(session, *args, **kwargs):
oob_error(session, "No Reportable property '%s'. Use LIST REPORTABLE_VARIABLES." % propname)
# the field_monitors require an oob function as a callback when they report a change.
elif propname.startswith("db_"):
OOB_HANDLER.add_field_monitor(obj, session, propname, "return_field_report")
INPUT_HANDLER.add_field_monitor(obj, session, propname, "return_field_report")
ret.append(to_str(_GA(obj, propname), force_string=True))
else:
OOB_HANDLER.add_attribute_monitor(obj, session, propname, "return_attribute_report")
INPUT_HANDLER.add_attribute_monitor(obj, session, propname, "return_attribute_report")
ret.append(_GA(obj, "db_value"))
session.msg(oob=("MSDP_ARRAY", ret))
else:
@ -314,9 +355,9 @@ def oob_unreport(session, *args, **kwargs):
if not propname:
oob_error(session, "No Un-Reportable property '%s'. Use LIST REPORTABLE_VARIABLES." % propname)
elif propname.startswith("db_"):
OOB_HANDLER.remove_field_monitor(obj, session, propname, "oob_return_field_report")
INPUT_HANDLER.remove_field_monitor(obj, session, propname, "oob_return_field_report")
else: # assume attribute
OOB_HANDLER.remove_attribute_monitor(obj, session, propname, "oob_return_attribute_report")
INPUT_HANDLER.remove_attribute_monitor(obj, session, propname, "oob_return_attribute_report")
else:
oob_error(session, "You must log in first.")
@ -359,7 +400,7 @@ def oob_list(session, mode, *args, **kwargs):
# we need to check so as to use the right return value depending on if it is
# an Attribute (identified by tracking the db_value field) or a normal database field
# reported is a list of tuples (obj, propname, args, kwargs)
reported = OOB_HANDLER.get_all_monitors(session)
reported = INPUT_HANDLER.get_all_monitors(session)
reported = [rep[0].key if rep[1] == "db_value" else rep[1] for rep in reported]
session.msg(oob=("REPORTED_VARIABLES", reported))
elif mode == "SENDABLE_VARIABLES":
@ -381,17 +422,17 @@ def oob_list(session, mode, *args, **kwargs):
# this maps the commands to the names available to use from
# the oob call. The standard MSDP commands are capitalized
# as per the protocol, Evennia's own commands are not.
CMD_MAP = {"oob_error": oob_error, # will get error messages
"return_field_report": oob_return_field_report,
"return_attribute_report": oob_return_attribute_report,
# MSDP
"REPEAT": oob_repeat,
"UNREPEAT": oob_unrepeat,
"SEND": oob_send,
"ECHO": oob_echo,
"REPORT": oob_report,
"UNREPORT": oob_unreport,
"LIST": oob_list,
# GMCP
}
#CMD_MAP = {"oob_error": oob_error, # will get error messages
# "return_field_report": oob_return_field_report,
# "return_attribute_report": oob_return_attribute_report,
# # MSDP
# "REPEAT": oob_repeat,
# "UNREPEAT": oob_unrepeat,
# "SEND": oob_send,
# "ECHO": oob_echo,
# "REPORT": oob_report,
# "UNREPORT": oob_unreport,
# "LIST": oob_list,
# # GMCP
# }

View file

@ -356,8 +356,10 @@ class PortalSessionHandler(SessionHandler):
session (Session): Session sending data.
Kwargs:
text (str): Text from protocol.
kwargs (any): Other data from protocol.
kwargs (any): Each key is a command instruction to the
protocol on the form key = [[args],{kwargs}]. This will
call a method send_<key> on the protocol. If no such
method exixts, it send the data to a method send_default.
"""
#from evennia.server.profiling.timetrace import timetrace
@ -365,13 +367,12 @@ class PortalSessionHandler(SessionHandler):
# distribute outgoing data to the correct session methods.
if session:
print ("portalsessionhandler.data_out:", session, kwargs, session.datamap)
for cmdname, args in kwargs.items():
print ("portalsessionhandler.data_out:", session, kwargs)
for cmdname, (cmdargs, cmdkwargs) in kwargs.iteritems():
try:
if cmdname in session.datamap:
session.datamap[cmdname](session, *args)
else:
session.datamap["_default"](session, *args)
getattr(session, "send_%s" % cmdname)(session, *cmdargs, **cmdkwargs)
except AttributeError:
session.send_default(session, *cmdargs, **cmdkwargs)
except Exception:
log_trace()

View file

@ -90,7 +90,7 @@ class WebSocketClient(Protocol, Session):
Method called when data is coming in over the websocket
connection. This is always a JSON object on the following
form:
[cmdname, arg, arg2, ...]
[cmdname, [args], {kwargs}]
"""

View file

@ -18,11 +18,9 @@ from evennia.utils import logger
from evennia.utils.inlinefunc import parse_inlinefunc
from evennia.utils.nested_inlinefuncs import parse_inlinefunc as parse_nested_inlinefunc
from evennia.utils.utils import make_iter, lazy_property
from evennia.commands.cmdhandler import cmdhandler
from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.server.session import Session
_IDLE_COMMAND = settings.IDLE_COMMAND
_GA = object.__getattribute__
_SA = object.__setattr__
_ObjectDB = None
@ -32,7 +30,6 @@ _INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED
# i18n
from django.utils.translation import ugettext as _
# Handlers for Session.db/ndb operation
class NDbHolder(object):
@ -168,8 +165,6 @@ class ServerSession(Session):
self.player = None
self.cmdset_storage_string = ""
self.cmdset = CmdSetHandler(self, True)
self.datamap = {"text": self.recv_text,
"_default": self.recv_text}
def __cmdset_storage_get(self):
return [path.strip() for path in self.cmdset_storage_string.split(',')]
@ -337,42 +332,6 @@ class ServerSession(Session):
# Player-visible idle time, not used in idle timeout calcs.
self.cmd_last_visible = self.cmd_last
@staticmethod
def recv_text(session, *args, **kwargs):
"""
Recv command data User->Evennia. This will in effect execute a command
string on the server.
Args:
text (str): First arg is used as text-command input. Other
arguments are ignored.
"""
#from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "ServerSession.data_in")
text = args[0] if args else None
#explicitly check for None since text can be an empty string, which is
#also valid
if text is not None:
# this is treated as a command input
#text = to_unicode(escape_control_sequences(text), encoding=self.encoding)
# handle the 'idle' command
if text.strip() == _IDLE_COMMAND:
session.update_session_counters(idle=True)
return
if session.player:
# nick replacement
puppet = session.puppet
if puppet:
text = puppet.nicks.nickreplace(text,
categories=("inputline", "channel"), include_player=True)
else:
text = session.player.nicks.nickreplace(text,
categories=("inputline", "channels"), include_player=False)
cmdhandler(session, text, callertype="session", session=session)
session.update_session_counters()
def data_out(self, text=None, **kwargs):
"""
@ -384,6 +343,11 @@ class ServerSession(Session):
by their keys. Or "options", carrying options
for the protocol(s).
Notes:
We need to handle inlinefunc-parsing at this point
since we must have access to the database and the
Server Session.
"""
print "serversession.data_out:", text, kwargs
if text:
@ -392,7 +356,7 @@ class ServerSession(Session):
else:
text, args = text, []
print("kwargs", kwargs, kwargs.get("options", {}))
options = kwargs.get("options", None) or {}
options = kwargs.get("options", [None, {}])[1]
raw = options.get("raw", False)
strip_inlinefunc = options.get("strip_inlinefunc", False)
if _INLINEFUNC_ENABLED and not raw:

View file

@ -19,14 +19,22 @@ from time import time
from django.conf import settings
from evennia.commands.cmdhandler import CMD_LOGINSTART
from evennia.utils.logger import log_trace
from evennia.utils.utils import variable_from_module, is_iter, \
to_str, to_unicode, strip_control_sequences, make_iter
from evennia.utils.utils import (variable_from_module, is_iter,
to_str, to_unicode,
make_iter,
callables_from_module)
try:
import cPickle as pickle
except ImportError:
import pickle
# input handlers
_INPUT_HANDLER_FUNCS = {}
for modname in make_iter(settings.INPUT_HANDLER_MODULES):
_INPUT_HANDLER_FUNCS.update(callables_from_module(modname))
# delayed imports
_PlayerDB = None
_ServerSession = None
@ -123,15 +131,24 @@ class SessionHandler(dict):
Args:
session (Session): The relevant session instance.
kwargs (dict): Every keyword represents a send-instruction.
kwargs (dict) Each keyword represents a
send-instruction, with the keyword itself being the name
of the instruction (like "text"). Suitable values for each
keyword are:
- arg -> [[arg], {}]
- [args] -> [[args], {}]
- {kwargs} -> [[], {kwargs}]
- [args, {kwargs}] -> [[arg], {kwargs}]
- [[args], {kwargs}] -> [[args], {kwargs}]
Returns:
kwargs (dict): A cleaned dictionary of cmdname:args pairs,
where the keys and args have all been converted to
kwargs (dict): A cleaned dictionary of cmdname:[[args],{kwargs}] pairs,
where the keys, args and kwargs have all been converted to
send-safe entities (strings or numbers).
"""
def _validate(data):
"Helper function to convert data to AMP-safe (picketable) values"
if isinstance(data, dict):
newdict = {}
for key, part in data.items():
@ -140,23 +157,36 @@ class SessionHandler(dict):
elif hasattr(data, "__iter__"):
return [_validate(part) for part in data]
elif isinstance(data, basestring):
# make sure strings are in a valid encoding
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__'):
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
rkwargs = {}
for key, data in kwargs.iteritems():
if not data:
rkwargs[key] = [ [], {} ]
elif isinstance(data, dict):
rkwargs[key] = [ [], _validate(data) ]
elif hasattr(data, "__iter__"):
if isinstance(data[-1], dict):
# last entry is a kwarg dict
rkwargs[key] = [ _validate(data[:-1]), _validate(data[-1]) ]
else:
rkwargs[key] = [ _validate(data), {} ]
else:
rkwargs[key] = [ [_validate(data)], {} ]
return rkwargs
#------------------------------------------------------------
@ -574,13 +604,13 @@ class ServerSessionHandler(SessionHandler):
# distribute incoming data to the correct receiving methods.
if session:
for cmdname, args in kwargs.items():
for cmdname, (cmdargs, cmdkwargs) in kwargs.iteritems():
try:
if cmdname in session.datamap:
print "sessionhandler: data_in", cmdname, args
session.datamap[cmdname](session, *args)
if cmdname in _INPUT_HANDLER_FUNCS:
print "sessionhandler: data_in", cmdname, cmdargs, cmdkwargs
_INPUT_HANDLER_FUNCS[cmdname](session, *cmdargs, **cmdkwargs)
else:
session.datamap["_default"](session, *args)
_INPUT_HANDLER_FUNCS["_default"](session, *cmdargs, **cmdkwargs)
except Exception:
log_trace()

View file

@ -33,7 +33,7 @@ TELNET_INTERFACES = ['0.0.0.0']
# special commands and data with enabled Telnet clients. This is used
# to create custom client interfaces over a telnet connection. To make
# full use of OOB, you need to prepare functions to handle the data
# server-side (see OOB_FUNC_MODULE). TELNET_ENABLED is required for this
# server-side (see INPUT_HANDLER_MODULES). TELNET_ENABLED is required for this
# to work.
TELNET_OOB_ENABLED = False
# Start the evennia django+twisted webserver so you can
@ -276,10 +276,10 @@ MSSP_META_MODULE = "server.conf.mssp"
# Tuple of modules implementing lock functions. All callable functions
# inside these modules will be available as lock functions.
LOCK_FUNC_MODULES = ("evennia.locks.lockfuncs", "server.conf.lockfuncs",)
# Module holding OOB (Out of Band) hook objects. This allows for customization
# and expansion of which hooks OOB protocols are allowed to call on the server
# protocols for attaching tracker hooks for when various object field change
OOB_PLUGIN_MODULES = ["evennia.server.oob_cmds", "server.conf.oobfuncs"]
# Module holding handlers for managing incoming data from the client. These
# will be loaded in order, meaning functions in later modules may overload
# previous ones if having the same name.
INPUT_HANDLER_MODULES = ["evennia.server.inputhandler_funcs", "server.conf.inputhandler_funcs"]
# Module holding settings/actions for the dummyrunner program (see the
# dummyrunner for more information)
DUMMYRUNNER_SETTINGS_MODULE = "evennia.server.profiling.dummyrunner_settings"