OOB MSDP working with direct sending of data from various parts of the system. Tracking as well as support for the default MSDP commands (LIST, REPORT etc) are not yet tested/implemented.

This commit is contained in:
Griatch 2013-10-15 20:00:18 +02:00
parent 16bbe009c3
commit bdcc8de5bc
10 changed files with 390 additions and 298 deletions

View file

@ -240,7 +240,7 @@ class AMPProtocol(amp.AMP):
def errback(self, e, info): def errback(self, e, info):
"error handler, to avoid dropping connections on server tracebacks." "error handler, to avoid dropping connections on server tracebacks."
e.trap(Exception) f = e.trap(Exception)
print "AMP Error for %(info)s: %(e)s" % {'info': info, 'e': e.getErrorMessage()} print "AMP Error for %(info)s: %(e)s" % {'info': info, 'e': e.getErrorMessage()}
def send_split_msg(self, sessid, msg, data, command): def send_split_msg(self, sessid, msg, data, command):
@ -286,7 +286,7 @@ class AMPProtocol(amp.AMP):
data comes in multiple chunks; if so (nparts>1) we buffer the data data comes in multiple chunks; if so (nparts>1) we buffer the data
and wait for the remaining parts to arrive before continuing. and wait for the remaining parts to arrive before continuing.
""" """
#print "msg portal -> server (server side):", sessid, msg #print "msg portal -> server (server side):", sessid, msg, data
global MSGBUFFER global MSGBUFFER
if nparts > 1: if nparts > 1:
# a multipart message # a multipart message
@ -311,7 +311,7 @@ class AMPProtocol(amp.AMP):
try: try:
return self.callRemote(MsgPortal2Server, return self.callRemote(MsgPortal2Server,
sessid=sessid, sessid=sessid,
msg=msg, msg=to_str(msg) if msg!=None else "",
ipart=0, ipart=0,
nparts=1, nparts=1,
data=dumps(data)).addErrback(self.errback, "MsgPortal2Server") data=dumps(data)).addErrback(self.errback, "MsgPortal2Server")
@ -351,7 +351,7 @@ class AMPProtocol(amp.AMP):
try: try:
return self.callRemote(MsgServer2Portal, return self.callRemote(MsgServer2Portal,
sessid=sessid, sessid=sessid,
msg=to_str(msg), msg=to_str(msg) if msg!=None else "",
ipart=0, ipart=0,
nparts=1, nparts=1,
data=dumps(data)).addErrback(self.errback, "MsgServer2Portal") data=dumps(data)).addErrback(self.errback, "MsgServer2Portal")

View file

@ -22,6 +22,7 @@ oob trackers should inherit from the OOBTracker class in this
""" """
from inspect import isfunction
from django.conf import settings from django.conf import settings
from src.server.models import ServerConfig from src.server.models import ServerConfig
from src.server.sessionhandler import SESSIONS from src.server.sessionhandler import SESSIONS
@ -29,16 +30,15 @@ from src.scripts.scripts import Script
from src.utils.create import create_script from src.utils.create import create_script
from src.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj from src.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj
from src.utils import logger from src.utils import logger
from src.utils.utils import variable_from_module, to_str, is_iter, make_iter from src.utils.utils import all_from_module, to_str, is_iter, make_iter
_SA = object.__setattr__ _SA = object.__setattr__
_GA = object.__getattribute__ _GA = object.__getattribute__
_DA = object.__delattribute__ _DA = object.__delattr__
# trackers track property changes and keep returning until they are removed # load from plugin module
_OOB_TRACKERS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_TRACKERS", default={}) _OOB_FUNCS = dict((key, func) for key, func in all_from_module(settings.OOB_PLUGIN_MODULE).items() if isfunction(func))
# functions return immediately _OOB_ERROR = _OOB_FUNCS.get("oob_error", None)
_OOB_FUNCS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_FUNCS", default={})
class TrackerHandler(object): class TrackerHandler(object):
@ -123,10 +123,8 @@ class OOBTracker(TrackerBase):
def update(self, new_value, *args, **kwargs): def update(self, new_value, *args, **kwargs):
"Called by cache when updating the tracked entitiy" "Called by cache when updating the tracked entitiy"
SESSIONS.session_from_sessid(self.sessid).msg(oob={"cmdkey":"trackreturn", SESSIONS.session_from_sessid(self.sessid).msg(oob=("trackreturn",
"name":self.fieldname, (self.fieldname, new_value)))
"value":new_value})
class _RepeaterPool(object): class _RepeaterPool(object):
""" """
@ -194,14 +192,6 @@ class _RepeaterPool(object):
self.scripts[interval].stop() self.scripts[interval].stop()
# Default OOB funcs
def OOB_get_attr_val(caller, attrname):
"Get the given attrback from caller"
caller.msg(oob={"cmdkey":"get_attr",
"name":attrname,
"value":to_str(caller.attributes.get(attrname))})
# Main OOB Handler # Main OOB Handler
class OOBHandler(object): class OOBHandler(object):
@ -214,6 +204,7 @@ class OOBHandler(object):
""" """
Initialize handler Initialize handler
""" """
self.sessionhandler = SESSIONS
self.oob_tracker_storage = {} self.oob_tracker_storage = {}
self.oob_repeat_storage = {} self.oob_repeat_storage = {}
self.oob_tracker_pool = _RepeaterPool() self.oob_tracker_pool = _RepeaterPool()
@ -247,7 +238,7 @@ class OOBHandler(object):
self.repeat(caller, func_key, interval, *args, **kwargs) self.repeat(caller, func_key, interval, *args, **kwargs)
def track(self, obj, sessid, fieldname, tracker_key, *args, **kwargs): def track(self, obj, sessid, fieldname, oobclass, *args, **kwargs):
""" """
Create an OOB obj of class _oob_MAPPING[tracker_key] on obj. args, Create an OOB obj of class _oob_MAPPING[tracker_key] on obj. args,
kwargs will be used to initialize the OOB hook before adding kwargs will be used to initialize the OOB hook before adding
@ -307,17 +298,6 @@ class OOBHandler(object):
oob_tracker_name = "_track_db_value_change" oob_tracker_name = "_track_db_value_change"
self.track(attrobj, tracker_key, attr_name, sessid, property_name=oob_tracker_name) self.track(attrobj, tracker_key, attr_name, sessid, property_name=oob_tracker_name)
def execute_cmd(self, func_key, *args, **kwargs):
"""
Retrieve oobfunc from OOB_FUNCS and execute it immediately
using *args and **kwargs
"""
oobfunc = _OOB_FUNCS[func_key] # raise traceback if not found
try:
oobfunc(*args, **kwargs)
except Exception:
logger.log_trace()
def repeat(self, caller, func_key, interval=20, *args, **kwargs): def repeat(self, caller, func_key, interval=20, *args, **kwargs):
""" """
Start a repeating action. Every interval seconds, Start a repeating action. Every interval seconds,
@ -339,7 +319,31 @@ class OOBHandler(object):
self.oob_tracker_pool.remove(store_key, interval) self.oob_tracker_pool.remove(store_key, interval)
self.oob_repeat_storage.pop(store_key, None) self.oob_repeat_storage.pop(store_key, None)
def msg(self, sessid, funcname, *args, **kwargs):
"Shortcut to relay oob data back to portal"
session = self.sessionhandler.session_from_sessid(sessid)
if session:
session.msg(oob=(funcname, args, kwargs))
def execute_cmd(self, session, func_key, *args, **kwargs):
"""
Retrieve oobfunc from OOB_FUNCS and execute it immediately
using *args and **kwargs
"""
try:
oobfunc = _OOB_FUNCS[func_key] # raise traceback if not found
oobfunc(self, session, *args, **kwargs)
except KeyError:
errmsg = "OOB Error: function '%s' not recognized." % func_key
if _OOB_ERROR:
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
else:
logger.log_trace(errmsg)
except Exception, err:
errmsg = "OOB Error: Exception in '%s'(%s, %s):\n%s" % (func_key, args, kwargs, err)
if _OOB_ERROR:
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
else:
logger.log_trace(errmsg)
# access object # access object
OOB_HANDLER = OOBHandler() OOB_HANDLER = OOBHandler()

View file

@ -12,7 +12,7 @@ etc.
""" """
import re import re
from django.conf import settings from django.conf import settings
from src.utils.utils import make_iter, mod_import from src.utils.utils import make_iter, mod_import, to_str
from src.utils import logger from src.utils import logger
# MSDP-relevant telnet cmd/opt-codes # MSDP-relevant telnet cmd/opt-codes
@ -28,10 +28,13 @@ IAC = chr(255)
SB = chr(250) SB = chr(250)
SE = chr(240) SE = chr(240)
force_str = lambda inp: to_str(inp, force_string=True)
# pre-compiled regexes # pre-compiled regexes
regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE)) # return 2-tuple regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE)) # return 2-tuple
regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)) # return 2-tuple (may be nested) regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)) # return 2-tuple (may be nested)
regex_varval = re.compile(r"%s(.*?)%s(.*)" % (MSDP_VAR, MSDP_VAL)) # return 2-tuple regex_var = re.compile(MSDP_VAR)
regex_val = re.compile(MSDP_VAL)
# MSDP default definition commands supported by Evennia (can be supplemented with custom commands as well) # MSDP default definition commands supported by Evennia (can be supplemented with custom commands as well)
MSDP_COMMANDS = ("LIST", "REPORT", "RESET", "SEND", "UNREPORT") MSDP_COMMANDS = ("LIST", "REPORT", "RESET", "SEND", "UNREPORT")
@ -138,59 +141,75 @@ class Msdp(object):
def no_msdp(self, option): def no_msdp(self, option):
"No msdp supported or wanted" "No msdp supported or wanted"
print "No msdp supported"
pass pass
def do_msdp(self, option): def do_msdp(self, option):
""" """
Called when client confirms that it can do MSDP. Called when client confirms that it can do MSDP.
""" """
print "msdp supported"
self.protocol.protocol_flags['MSDP'] = True self.protocol.protocol_flags['MSDP'] = True
def evennia_to_msdp(self, cmdname, data): def evennia_to_msdp(self, cmdname, *args, **kwargs):
""" """
handle return data from cmdname by converting it to handle return data from cmdname by converting it to
a proper msdp structure. data can either be a single value (will be a proper msdp structure. data can either be a single value (will be
converted to a string), a list (will be converted to an MSDP_ARRAY), converted to a string), a list (will be converted to an MSDP_ARRAY),
or a dictionary (will be converted to MSDP_TABLE). or a dictionary (will be converted to MSDP_TABLE).
OBS - this supports nested tables and even arrays nested Obs - this normally only returns tables and lists (var val val ...) rather than
inside tables, as opposed to the receive method. Arrays arrays. It will convert *args to lists and **kwargs to tables and
cannot hold tables by definition (the table must be named if both are given to this method, this will result in a list followed
with MSDP_VAR, and an array can only contain MSDP_VALs). by a table, both having the same names.
""" """
def make_table(name, datadict, string): def make_table(name, **kwargs):
"build a table that may be nested with other tables or arrays." "build a table that may be nested with other tables or arrays."
string += MSDP_VAR + name + MSDP_VAL + MSDP_TABLE_OPEN string = MSDP_VAR + force_str(name) + MSDP_VAL + MSDP_TABLE_OPEN
for key, val in datadict.items(): for key, val in kwargs.items():
if type(val) == type({}): if isinstance(val, dict):
string += make_table(key, val, string) string += make_table(string, key, **val)
elif hasattr(val, '__iter__'): elif hasattr(val, '__iter__'):
string += make_array(key, val, string) string += make_array(string, key, *val)
else: else:
string += MSDP_VAR + key + MSDP_VAL + val string += MSDP_VAR + force_str(key) + MSDP_VAL + force_str(val)
string += MSDP_TABLE_CLOSE string += MSDP_TABLE_CLOSE
return string return string
def make_array(name, datalist, string): def make_array(name, *args):
"build a simple array. Arrays may not nest tables by definition." "build a array. Arrays may not nest tables by definition."
print "make_array", datalist, string string = MSDP_VAR + force_str(name) + MSDP_ARRAY_OPEN
string += MSDP_VAR + name + MSDP_ARRAY_OPEN string += MSDP_VAL.join(force_str(arg) for arg in args)
for val in datalist:
string += MSDP_VAL + val
string += MSDP_ARRAY_CLOSE string += MSDP_ARRAY_CLOSE
return string return string
if isinstance(data, dict): def make_list(name, *args):
msdp_string = make_table(cmdname, data, "") "build a simple list - an array without start/end markers"
elif hasattr(data, '__iter__'): string = MSDP_VAR + force_str(name)
msdp_string = make_array(cmdname, data, "") string += MSDP_VAL.join(force_str(arg) for arg in args)
else: return string
msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data if data!=None else ""
return msdp_string
# Default MSDP commands
cupper = cmdname.upper()
if cupper == "LIST":
self.data_out(make_list("LIST", *args))
elif cupper == "REPORT":
self.data_out(make_list("REPORT", *args))
elif cupper == "UNREPORT":
self.data_out(make_list("UNREPORT", *args))
elif cupper == "RESET":
self.data_out(make_list("RESET", *args))
elif cupper == "SEND":
self.data_out(make_list("SEND", *args))
else:
# return list or tables. If both arg/kwarg is given, return one array and one table, both
# with the same name.
msdp_string = ""
if args:
msdp_string += make_list(cupper, *args)
if kwargs:
msdp_string += make_table(cupper, **kwargs)
self.data_out(msdp_string)
def msdp_to_evennia(self, data): def msdp_to_evennia(self, data):
""" """
@ -216,16 +235,24 @@ class Msdp(object):
if hasattr(data, "__iter__"): if hasattr(data, "__iter__"):
data = "".join(data) data = "".join(data)
logger.log_infomsg("MSDP SUBNEGOTIATION: %s" % data) #logger.log_infomsg("MSDP SUBNEGOTIATION: %s" % data)
for table in regex_table.findall(data): for key, table in regex_table.findall(data):
tables[table[0].upper()] = dict(regex_varval.findall(table[1])) tables[key] = {}
for array in regex_array.findall(data): for varval in regex_var.split(table):
arrays[array[0].upper()] = dict(regex_varval.findall(array[1])) parts = regex_val.split(varval)
# get all stand-alone variables, but first we must clean out all tables and arrays (which also contain vars) tables[key].expand({parts[0] : tuple(parts[1:]) if len(parts)>1 else ("",)})
variables = dict((key.upper(), val) for key, val in regex_varval.findall(regex_array.sub("", regex_table.sub("", data)))) for key, array in regex_array.findall(data):
arrays[key] = []
for val in regex_val.split(array):
arrays[key].append(val)
arrays[key] = tuple(arrays[key])
for varval in regex_var.split(regex_array.sub("", regex_table.sub("", data))):
# get remaining varvals after cleaning away tables/arrays
parts = regex_val.split(varval)
variables[parts[0].upper()] = tuple(parts[1:]) if len(parts)>1 else ("", )
print "MSDP: table, array, variables:", tables, arrays, variables #print "MSDP: table, array, variables:", tables, arrays, variables
# all variables sent through msdp to Evennia are considered commands with arguments. # all variables sent through msdp to Evennia are considered commands with arguments.
# there are three forms of commands possible through msdp: # there are three forms of commands possible through msdp:
@ -235,122 +262,124 @@ class Msdp(object):
# TABLENAME TABLE VARNAME VAL VARNAME VAL ENDTABLE -> tablename(varname=val, varname=val) # TABLENAME TABLE VARNAME VAL VARNAME VAL ENDTABLE -> tablename(varname=val, varname=val)
# #
ret = ""
# default MSDP functions # default MSDP functions
if "LIST" in variables: if "LIST" in variables:
ret += self.evennia_to_msdp("LIST", self.msdp_cmd_list(*(variables.pop("LIST"),))) self.data_in("list", *variables.pop("LIST"))
if "REPORT" in variables: if "REPORT" in variables:
ret += self.evennia_to_msdp("REPORT", self.msdp_cmd_report(*(variables.pop("REPORT"),))) self.data_in("report", *variables.pop("REPORT"))
if "REPORT" in arrays: if "REPORT" in arrays:
ret += self.evennia_to_msdp("REPORT", self.msdp_cmd_report(*arrays.pop("REPORT"))) self.data_in("report", *(arrays.pop("REPORT")))
if "UNREPORT" in variables:
self.data_in("unreport", *(arrays.pop("UNREPORT")))
if "RESET" in variables: if "RESET" in variables:
ret += self.evennia_to_msdp("RESET", self.msdp_cmd_reset(*(variables.pop("RESET"),))) self.data_in("reset", *variables.pop("RESET"))
if "RESET" in arrays: if "RESET" in arrays:
ret += self.evennia_to_msdp("RESET", self.msdp_cmd_reset(*arrays.pop("RESET",))) self.data_in("reset", *(arrays.pop("RESET")))
if "SEND" in variables: if "SEND" in variables:
ret += self.evennia_to_msdp("SEND", self.msdp_cmd_send(*(variables.pop("SEND",)))) self.data_in("send", *variables.pop("SEND"))
if "SEND" in arrays: if "SEND" in arrays:
ret += self.evennia_to_msdp("SEND",self.msdp_cmd_send(*arrays.pop("SEND"))) self.data_in("send", *(arrays.pop("SEND")))
# if there are anything left consider it a call to a custom function
# if there are anything left we look for a custom function
for varname, var in variables.items(): for varname, var in variables.items():
# a simple function + argument # a simple function + argument
ooc_func = MSDP_COMMANDS_CUSTOM.get(varname.upper()) self.data_in(varname, (var,))
if ooc_func:
ret += self.evennia_to_msdp(varname, ooc_func(var))
for arrayname, array in arrays.items(): for arrayname, array in arrays.items():
# we assume the array are multiple arguments to the function # we assume the array are multiple arguments to the function
ooc_func = MSDP_COMMANDS_CUSTOM.get(arrayname.upper()) self.data_in(arrayname, *array)
if ooc_func:
ret += self.evennia_to_msdp(arrayname, ooc_func(*array))
for tablename, table in tables.items(): for tablename, table in tables.items():
# we assume tables are keyword arguments to the function # we assume tables are keyword arguments to the function
ooc_func = MSDP_COMMANDS_CUSTOM.get(arrayname.upper()) self.data_in(tablename, **table)
if ooc_func:
ret += self.evennia_to_msdp(tablename, ooc_func(**table))
# return any result
if ret:
self.data_out(ret)
def data_out(self, msdp_string): def data_out(self, msdp_string):
""" """
Return a msdp-valid subnegotiation across the protocol. Return a msdp-valid subnegotiation across the protocol.
""" """
self.protocol._write(IAC + SB + MSDP + msdp_string + IAC + SE) #print "msdp data_out (without IAC SE):", msdp_string
self.protocol ._write(IAC + SB + MSDP + force_str(msdp_string) + IAC + SE)
# MSDP Commands def data_in(self, funcname, *args, **kwargs):
# Some given MSDP (varname, value) pairs can also be treated as command + argument.
# Generic msdp command map. The argument will be sent to the given command.
# See http://tintin.sourceforge.net/msdp/ for definitions of each command.
# These are client->server commands.
def msdp_cmd_list(self, arg):
""" """
The List command allows for retrieving various info about the server/client Send oob data to Evennia
""" """
if arg == 'COMMANDS': #print "msdp data_in:", funcname, args, kwargs
return self.evennia_to_msdp(arg, MSDP_COMMANDS) self.protocol.data_in(text=None, oob=(funcname, args, kwargs))
elif arg == 'LISTS':
return self.evennia_to_msdp(arg, ("COMMANDS", "LISTS", "CONFIGURABLE_VARIABLES",
"REPORTED_VARIABLES", "SENDABLE_VARIABLES"))
elif arg == 'CONFIGURABLE_VARIABLES':
return self.evennia_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
elif arg == 'REPORTABLE_VARIABLES':
return self.evennia_to_msdp(arg, MSDP_REPORTABLE.keys())
elif arg == 'REPORTED_VARIABLES':
# the dynamically set items to report
return self.evennia_to_msdp(arg, self.msdp_reported.keys())
elif arg == 'SENDABLE_VARIABLES':
return self.evennia_to_msdp(arg, MSDP_SENDABLE.keys())
else:
return self.evennia_to_msdp("LIST", arg)
# default msdp commands # # MSDP Commands
# # Some given MSDP (varname, value) pairs can also be treated as command + argument.
# # Generic msdp command map. The argument will be sent to the given command.
# # See http://tintin.sourceforge.net/msdp/ for definitions of each command.
# # These are client->server commands.
# def msdp_cmd_list(self, arg):
# """
# The List command allows for retrieving various info about the server/client
# """
# if arg == 'COMMANDS':
# return self.evennia_to_msdp(arg, MSDP_COMMANDS)
# elif arg == 'LISTS':
# return self.evennia_to_msdp(arg, ("COMMANDS", "LISTS", "CONFIGURABLE_VARIABLES",
# "REPORTED_VARIABLES", "SENDABLE_VARIABLES"))
# elif arg == 'CONFIGURABLE_VARIABLES':
# return self.evennia_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
# elif arg == 'REPORTABLE_VARIABLES':
# return self.evennia_to_msdp(arg, MSDP_REPORTABLE.keys())
# elif arg == 'REPORTED_VARIABLES':
# # the dynamically set items to report
# return self.evennia_to_msdp(arg, self.msdp_reported.keys())
# elif arg == 'SENDABLE_VARIABLES':
# return self.evennia_to_msdp(arg, MSDP_SENDABLE.keys())
# else:
# return self.evennia_to_msdp("LIST", arg)
def msdp_cmd_report(self, *arg): # # default msdp commands
"""
The report command instructs the server to start reporting a
reportable variable to the client.
"""
try:
return MSDP_REPORTABLE[arg](report=True)
except Exception:
logger.log_trace()
def msdp_cmd_unreport(self, arg): # def msdp_cmd_report(self, *arg):
""" # """
Unreport a previously reported variable # The report command instructs the server to start reporting a
""" # reportable variable to the client.
try: # """
MSDP_REPORTABLE[arg](report=False) # try:
except Exception: # return MSDP_REPORTABLE[arg](report=True)
self.logger.log_trace() # except Exception:
# logger.log_trace()
def msdp_cmd_reset(self, arg): # def msdp_cmd_unreport(self, arg):
""" # """
The reset command resets a variable to its initial state. # Unreport a previously reported variable
""" # """
try: # try:
MSDP_REPORTABLE[arg](reset=True) # MSDP_REPORTABLE[arg](report=False)
except Exception: # except Exception:
logger.log_trace() # self.logger.log_trace()
def msdp_cmd_send(self, arg): # def msdp_cmd_reset(self, arg):
""" # """
Request the server to send a particular variable # The reset command resets a variable to its initial state.
to the client. # """
# try:
# MSDP_REPORTABLE[arg](reset=True)
# except Exception:
# logger.log_trace()
arg - this is a list of variables the client wants. # def msdp_cmd_send(self, *args):
""" # """
ret = [] # Request the server to send a particular variable
if arg: # to the client.
for var in make_iter(arg):
try: # arg - this is a list of variables the client wants.
ret.append(MSDP_REPORTABLE[var.upper()])# (send=True)) # """
except Exception: # ret = []
ret.append("ERROR")#logger.log_trace() # for var in make_iter(arg)
return ret
# for var in make_iter(arg):
# try:
# ret.append(MSDP_REPORTABLE[var.upper()])# (send=True))
# except Exception:
# ret.append("ERROR")#logger.log_trace()
# return ret

View file

@ -127,7 +127,6 @@ class PortalSessionHandler(SessionHandler):
in from the protocol to the server. data is in from the protocol to the server. data is
serialized before passed on. serialized before passed on.
""" """
#print "portal_data_in:", string
self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid, self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid,
msg=text, msg=text,
data=kwargs) data=kwargs)

View file

@ -85,13 +85,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
be handled in line mode. Some clients also sends an erroneous be handled in line mode. Some clients also sends an erroneous
line break after IAC, which we must watch out for. line break after IAC, which we must watch out for.
""" """
#print "dataRcv (%s):" % data,
#try:
# for b in data:
# print ord(b),
# print ""
#except Exception, e:
# print str(e) + ":", str(data)
if data and data[0] == IAC or self.iaw_mode: if data and data[0] == IAC or self.iaw_mode:
try: try:
@ -102,8 +95,16 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
else: else:
self.iaw_mode = False self.iaw_mode = False
return return
except Exception: except Exception, err1:
logger.log_trace() conv = ""
try:
for b in data:
conv += " " + repr(ord(b))
except Exception, err2:
conv = str(err2) + ":", str(data)
out = "Telnet Error (%s): %s (%s)" % (err1, data, conv)
logger.log_trace(out)
return
# if we get to this point the command must end with a linebreak. # if we get to this point the command must end with a linebreak.
# We make sure to add it, to fix some clients messing this up. # We make sure to add it, to fix some clients messing this up.
data = data.rstrip("\r\n") + "\n" data = data.rstrip("\r\n") + "\n"
@ -130,7 +131,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
Telnet method called when data is coming in over the telnet Telnet method called when data is coming in over the telnet
connection. We pass it on to the game engine directly. connection. We pass it on to the game engine directly.
""" """
self.sessionhandler.data_in(self, string) self.data_in(text=string)
# Session hooks # Session hooks
@ -144,11 +145,17 @@ 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):
"""
Data Telnet -> Server
"""
self.sessionhandler.data_in(self, text=text, **kwargs)
def data_out(self, text=None, **kwargs): def data_out(self, text=None, **kwargs):
""" """
Data Evennia -> Player.
generic hook method for engine to call in order to send data generic hook method for engine to call in order to send data
through the telnet connection. through the telnet connection.
Data Evennia -> Player.
valid telnet kwargs: valid telnet kwargs:
raw=True - pass string through without any ansi processing (i.e. include Evennia raw=True - pass string through without any ansi processing (i.e. include Evennia
@ -165,11 +172,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if "oob" in kwargs: if "oob" in kwargs:
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob")) oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob"))
if "MSDP" in self.protocol_flags: if "MSDP" in self.protocol_flags:
print "oobstruct:", oobstruct for cmdname, args, kwargs in oobstruct:
for cmdname, args in oobstruct: #print "cmdname, args, kwargs:", cmdname, args, kwargs
print "cmdname, args:", cmdname, args msdp_string = self.msdp.evennia_to_msdp(cmdname, *args, **kwargs)
msdp_string = self.msdp.func_to_msdp(cmdname, args) #print "msdp_string:", msdp_string
print "msdp_string:", msdp_string
self.msdp.data_out(msdp_string) self.msdp.data_out(msdp_string)
ttype = self.protocol_flags.get('TTYPE', {}) ttype = self.protocol_flags.get('TTYPE', {})

View file

@ -137,6 +137,14 @@ class ServerSession(Session):
return self.logged_in and self.puppet return self.logged_in and self.puppet
get_character = get_puppet get_character = get_puppet
def get_puppet_or_player(self):
"""
Returns session if not logged in; puppet if one exists, otherwise return the player.
"""
if self.logged_in:
return self.puppet if self.puppet else self.player
return None
def log(self, message, channel=True): def log(self, message, channel=True):
""" """
Emits session info to the appropriate outputs and info channels. Emits session info to the appropriate outputs and info channels.
@ -182,10 +190,11 @@ class ServerSession(Session):
# handle oob instructions # handle oob instructions
global _OOB_HANDLER global _OOB_HANDLER
if not _OOB_HANDLER: if not _OOB_HANDLER:
from src.servever.oobhandler import OOB_HANDLER as _OOB_HANDLER from src.server.oobhandler import OOB_HANDLER as _OOB_HANDLER
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob", None)) oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob", None))
for (funcname, args, kwargs) in oobstruct: for (funcname, args, kwargs) in oobstruct:
_OOBHANDLER.execute_cmd(funcname, *args, **kwargs) if funcname:
_OOB_HANDLER.execute_cmd(self, funcname, *args, **kwargs)
execute_cmd = data_in # alias execute_cmd = data_in # alias

View file

@ -97,6 +97,55 @@ class SessionHandler(object):
""" """
return dict((sessid, sess.get_sync_data()) for sessid, sess in self.sessions.items()) return dict((sessid, sess.get_sync_data()) for sessid, sess in self.sessions.items())
def oobstruct_parser(self, oobstruct):
"""
Helper method for each session to use to parse oob structures
(The 'oob' kwarg of the msg() method)
allowed oob structures are
cmdname
((cmdname,), (cmdname,))
(cmdname,(arg, ))
(cmdname,(arg1,arg2))
(cmdname,{key:val,key2:val2})
(cmdname, (args,), {kwargs})
((cmdname, (arg1,arg2)), cmdname, (cmdname, (arg1,)))
outputs an ordered structure on the form
((cmdname, (args,), {kwargs}), ...), where the two last parts of each tuple 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.lower(), (), {})
# 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].lower(), (), {})
elif slen == 2:
if isinstance(oobstruct[1], dict):
# cmdname, {kwargs}
return (oobstruct[0].lower(), (), dict(oobstruct[1]))
elif isinstance(oobstruct[1], (tuple, list)):
# cmdname, (args,)
return (oobstruct[0].lower(), tuple(oobstruct[1]), {})
else:
# cmdname, (args,), {kwargs}
return (oobstruct[0].lower(), 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 (tuple(_parse(oobstruct)),)
else:
out = []
for oobpart in oobstruct:
out.append(_parse(oobpart))
return (tuple(out),)
return (_parse(oobstruct),)
#------------------------------------------------------------ #------------------------------------------------------------
# Server-SessionHandler class # Server-SessionHandler class
#------------------------------------------------------------ #------------------------------------------------------------
@ -358,48 +407,6 @@ class ServerSessionHandler(SessionHandler):
return self.sessions.get(sessid) return self.sessions.get(sessid)
return None return None
def oobstruct_parser(self, oobstruct):
"""
Helper method for each session to use to parse oob structures
(The 'oob' kwarg of the msg() method)
allowed oob structures are
cmdname
(cmdname, cmdname)
(cmdname,(arg, ))
(cmdname,(arg1,arg2))
(cmdname,{key:val,key2:val2})
(cmdname, (args,), {kwargs})
((cmdname, (arg1,arg2)), cmdname, (cmdname, (arg1,)))
outputs an ordered structure on the form
((cmdname, (args,), {kwargs}), ...), where the two last parts of each tuple may be empty
"""
slen = len(oobstruct)
if not oobstruct:
return tuple(None, (), {})
elif not hasattr(oobstruct, "__iter__"):
# a singular command name, without arguments or kwargs
return (oobstruct.lower(), (), {})
# 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].lower(), (), {})
elif slen == 2:
if isinstance(oobstruct[1], dict):
# cmdname, {kwargs}
return (oobstruct[0].lower(), (), dict((key.lower(), val) for key,val in oobstruct[1].items()))
elif isinstance(oobstruct[1], (tuple, list)):
# cmdname, (args,)
return (oobstruct[0].lower(), tuple(arg.lower() for arg in oobstruct[1]), {})
else:
# cmdname, (args,), {kwargs}
return (oobstruct[0].lower(), tuple(arg.lower for arg in oobstruct[1]),
dict((key.lower(), val) for key, val in oobstruct[2].items()))
# either multiple funcnames or multiple func tuples; descend recursively
out = []
for oobpart in oobstruct:
out.append(self.oobstruct_parser(oobpart)[0])
return tuple(out)
def announce_all(self, message): def announce_all(self, message):
""" """

View file

@ -199,7 +199,7 @@ MSSP_META_MODULE = ""
# Module holding OOB (Out of Band) hook objects. This allows for customization # 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 # 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 # protocols for attaching tracker hooks for when various object field change
OOB_PLUGIN_MODULE = "" OOB_PLUGIN_MODULE = "src.server.oob_defaults"
# Tuple of modules implementing lock functions. All callable functions # Tuple of modules implementing lock functions. All callable functions
# inside these modules will be available as lock functions. # inside these modules will be available as lock functions.
LOCK_FUNC_MODULES = ("src.locks.lockfuncs",) LOCK_FUNC_MODULES = ("src.locks.lockfuncs",)

View file

@ -437,7 +437,7 @@ class NickHandler(AttributeHandler):
with categories nick_<nicktype> with categories nick_<nicktype>
""" """
def has(self, key, category="inputline"): def has(self, key, category="inputline"):
categry = "nick_%s" % category category = "nick_%s" % category
return super(NickHandler, self).has(key, category=category) return super(NickHandler, self).has(key, category=category)
def get(self, key=None, category="inputline", **kwargs): def get(self, key=None, category="inputline", **kwargs):
@ -462,6 +462,34 @@ class NickHandler(AttributeHandler):
return super(NickHandler, self).all(category=category) return super(NickHandler, self).all(category=category)
return _GA(self.obj, self._m2m_fieldname).filter(db_category__startswith="nick_") return _GA(self.obj, self._m2m_fieldname).filter(db_category__startswith="nick_")
class NAttributeHandler(object):
"""
This stand-alone handler manages non-database saved properties by storing them
as properties on obj.ndb. It has the same methods as AttributeHandler, but they
are much simplified.
"""
def __init__(self, obj):
"initialized on the object"
self.ndb = _GA(obj, "ndb")
def has(self, key):
"Check if object has this attribute or not"
return _GA(self.ndb, key) # ndb returns None if not found
def get(self, key):
"Returns named key value"
return _GA(self.ndb, key)
def add(self, key, value):
"Add new key and value"
_SA(self.ndb, key, value)
def remove(self, key):
"Remove key from storage"
_DA(self.ndb, key)
def all(self):
"List all keys stored"
if callable(self.ndb.all):
return self.ndb.all()
else:
return [val for val in self.ndb.__dict__.keys() if not val.startswith('_')]
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Tags # Tags
@ -645,6 +673,7 @@ class TypedObject(SharedMemoryModel):
_SA(self, "dbobj", self) # this allows for self-reference _SA(self, "dbobj", self) # this allows for self-reference
_SA(self, "locks", LockHandler(self)) _SA(self, "locks", LockHandler(self))
_SA(self, "permissions", PermissionHandler(self)) _SA(self, "permissions", PermissionHandler(self))
_SA(self, "nattributes", NAttributeHandler(self))
class Meta: class Meta:
""" """
@ -1148,6 +1177,9 @@ class TypedObject(SharedMemoryModel):
if hperm in perms and hpos > ppos) if hperm in perms and hpos > ppos)
return False return False
#
# Memory management
#
def flush_from_cache(self): def flush_from_cache(self):
""" """
@ -1157,6 +1189,60 @@ class TypedObject(SharedMemoryModel):
""" """
self.__class__.flush_cached_instance(self) self.__class__.flush_cached_instance(self)
#
# Attribute storage
#
#@property db
def __db_get(self):
"""
Attribute handler wrapper. Allows for the syntax
obj.db.attrname = value
and
value = obj.db.attrname
and
del obj.db.attrname
and
all_attr = obj.db.all (unless there is no attribute named 'all', in which
case that will be returned instead).
"""
try:
return self._db_holder
except AttributeError:
class DbHolder(object):
"Holder for allowing property access of attributes"
def __init__(self, obj):
_SA(self, 'obj', obj)
_SA(self, "attrhandler", _GA(_GA(self, "obj"), "attributes"))
def __getattribute__(self, attrname):
if attrname == 'all':
# we allow to overload our default .all
attr = _GA(self, "attrhandler").get("all")
if attr:
return attr
return _GA(self, 'all')
return _GA(self, "attrhandler").get(attrname)
def __setattr__(self, attrname, value):
_GA(self, "attrhandler").add(attrname, value)
def __delattr__(self, attrname):
_GA(self, "attrhandler").remove(attrname)
def get_all(self):
return _GA(self, "attrhandler").all()
all = property(get_all)
self._db_holder = DbHolder(self)
return self._db_holder
#@db.setter
def __db_set(self, value):
"Stop accidentally replacing the db object"
string = "Cannot assign directly to db object! "
string += "Use db.attr=value instead."
raise Exception(string)
#@db.deleter
def __db_del(self):
"Stop accidental deletion."
raise Exception("Cannot delete the db object!")
db = property(__db_get, __db_set, __db_del)
# #
# Non-persistent (ndb) storage # Non-persistent (ndb) storage
# #
@ -1202,35 +1288,12 @@ class TypedObject(SharedMemoryModel):
raise Exception("Cannot delete the ndb object!") raise Exception("Cannot delete the ndb object!")
ndb = property(__ndb_get, __ndb_set, __ndb_del) ndb = property(__ndb_get, __ndb_set, __ndb_del)
#def nattr(self, attribute_name=None, value=None, delete=False):
# """
# This allows for assigning non-persistent data on the object using
# a method call. Will return None if trying to access a non-existing property.
# """
# if attribute_name == None:
# # act as a list method
# if callable(self.ndb.all):
# return self.ndb.all()
# else:
# return [val for val in self.ndb.__dict__.keys()
# if not val.startswith['_']]
# elif delete == True:
# if hasattr(self.ndb, attribute_name):
# _DA(_GA(self, "ndb"), attribute_name)
# elif value == None:
# # act as a getter.
# if hasattr(self.ndb, attribute_name):
# _GA(_GA(self, "ndb"), attribute_name)
# else:
# return None
# else:
# # act as a setter
# _SA(self.ndb, attribute_name, value)
# #
# Attribute handler methods - DEPRECATED! # ***** DEPRECATED METHODS BELOW *******
# #
# #
@ -1366,56 +1429,31 @@ class TypedObject(SharedMemoryModel):
# creating a new attribute - check access on storing object! # creating a new attribute - check access on storing object!
_GA(self, "attributes").add(attribute_name, value, accessing_obj=accessing_object, default_access=default_access_create) _GA(self, "attributes").add(attribute_name, value, accessing_obj=accessing_object, default_access=default_access_create)
#@property def nattr(self, attribute_name=None, value=None, delete=False):
def __db_get(self):
""" """
A second convenience wrapper for the the attribute methods. It This allows for assigning non-persistent data on the object using
allows for the syntax a method call. Will return None if trying to access a non-existing property.
obj.db.attrname = value
and
value = obj.db.attrname
and
del obj.db.attrname
and
all_attr = obj.db.all (unless there is no attribute named 'all', in which
case that will be returned instead).
""" """
try: logger.log_depmsg("obj.nattr() is deprecated. Use obj.nattributes instead.")
return self._db_holder if attribute_name == None:
except AttributeError: # act as a list method
class DbHolder(object): if callable(self.ndb.all):
"Holder for allowing property access of attributes" return self.ndb.all()
def __init__(self, obj): else:
_SA(self, 'obj', obj) return [val for val in self.ndb.__dict__.keys()
_SA(self, "attrhandler", _GA(_GA(self, "obj"), "attributes")) if not val.startswith['_']]
def __getattribute__(self, attrname): elif delete == True:
if attrname == 'all': if hasattr(self.ndb, attribute_name):
# we allow to overload our default .all _DA(_GA(self, "ndb"), attribute_name)
attr = _GA(self, "attrhandler").get("all") elif value == None:
if attr: # act as a getter.
return attr if hasattr(self.ndb, attribute_name):
return _GA(self, 'all') _GA(_GA(self, "ndb"), attribute_name)
return _GA(self, "attrhandler").get(attrname) else:
def __setattr__(self, attrname, value): return None
_GA(self, "attrhandler").add(attrname, value) else:
def __delattr__(self, attrname): # act as a setter
_GA(self, "attrhandler").remove(attrname) _SA(self.ndb, attribute_name, value)
def get_all(self):
return _GA(self, "attrhandler").all()
all = property(get_all)
self._db_holder = DbHolder(self)
return self._db_holder
#@db.setter
def __db_set(self, value):
"Stop accidentally replacing the db object"
string = "Cannot assign directly to db object! "
string += "Use db.attr=value instead."
raise Exception(string)
#@db.deleter
def __db_del(self):
"Stop accidental deletion."
raise Exception("Cannot delete the db object!")
db = property(__db_get, __db_set, __db_del)

View file

@ -729,10 +729,10 @@ def mod_import(module):
def all_from_module(module): def all_from_module(module):
""" """
Return all global-level variables from a module Return all global-level variables from a module as a dict
""" """
mod = mod_import(module) mod = mod_import(module)
return [val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val))] return dict((key, val) for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val)))
def variable_from_module(module, variable=None, default=None): def variable_from_module(module, variable=None, default=None):
""" """