diff --git a/src/server/amp.py b/src/server/amp.py index 528aa0bab8..f6ec7b86e3 100644 --- a/src/server/amp.py +++ b/src/server/amp.py @@ -240,7 +240,7 @@ class AMPProtocol(amp.AMP): def errback(self, e, info): "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()} 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 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 if nparts > 1: # a multipart message @@ -311,7 +311,7 @@ class AMPProtocol(amp.AMP): try: return self.callRemote(MsgPortal2Server, sessid=sessid, - msg=msg, + msg=to_str(msg) if msg!=None else "", ipart=0, nparts=1, data=dumps(data)).addErrback(self.errback, "MsgPortal2Server") @@ -351,7 +351,7 @@ class AMPProtocol(amp.AMP): try: return self.callRemote(MsgServer2Portal, sessid=sessid, - msg=to_str(msg), + msg=to_str(msg) if msg!=None else "", ipart=0, nparts=1, data=dumps(data)).addErrback(self.errback, "MsgServer2Portal") diff --git a/src/server/oobhandler.py b/src/server/oobhandler.py index 98dd32fdcd..6286b69ccf 100644 --- a/src/server/oobhandler.py +++ b/src/server/oobhandler.py @@ -22,6 +22,7 @@ oob trackers should inherit from the OOBTracker class in this """ +from inspect import isfunction from django.conf import settings from src.server.models import ServerConfig 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.dbserialize import dbserialize, dbunserialize, pack_dbobj 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__ _GA = object.__getattribute__ -_DA = object.__delattribute__ +_DA = object.__delattr__ -# trackers track property changes and keep returning until they are removed -_OOB_TRACKERS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_TRACKERS", default={}) -# functions return immediately -_OOB_FUNCS = variable_from_module(settings.OBB_PLUGIN_MODULE, "OBB_FUNCS", default={}) +# load from plugin module +_OOB_FUNCS = dict((key, func) for key, func in all_from_module(settings.OOB_PLUGIN_MODULE).items() if isfunction(func)) +_OOB_ERROR = _OOB_FUNCS.get("oob_error", None) class TrackerHandler(object): @@ -123,10 +123,8 @@ class OOBTracker(TrackerBase): def update(self, new_value, *args, **kwargs): "Called by cache when updating the tracked entitiy" - SESSIONS.session_from_sessid(self.sessid).msg(oob={"cmdkey":"trackreturn", - "name":self.fieldname, - "value":new_value}) - + SESSIONS.session_from_sessid(self.sessid).msg(oob=("trackreturn", + (self.fieldname, new_value))) class _RepeaterPool(object): """ @@ -194,14 +192,6 @@ class _RepeaterPool(object): 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 class OOBHandler(object): @@ -214,6 +204,7 @@ class OOBHandler(object): """ Initialize handler """ + self.sessionhandler = SESSIONS self.oob_tracker_storage = {} self.oob_repeat_storage = {} self.oob_tracker_pool = _RepeaterPool() @@ -247,7 +238,7 @@ class OOBHandler(object): 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, 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" 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): """ Start a repeating action. Every interval seconds, @@ -339,7 +319,31 @@ class OOBHandler(object): self.oob_tracker_pool.remove(store_key, interval) 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 OOB_HANDLER = OOBHandler() diff --git a/src/server/portal/msdp.py b/src/server/portal/msdp.py index 6eaa9a1eb5..6c21ac2800 100644 --- a/src/server/portal/msdp.py +++ b/src/server/portal/msdp.py @@ -12,7 +12,7 @@ etc. """ import re 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 # MSDP-relevant telnet cmd/opt-codes @@ -28,10 +28,13 @@ IAC = chr(255) SB = chr(250) SE = chr(240) +force_str = lambda inp: to_str(inp, force_string=True) + # 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_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_COMMANDS = ("LIST", "REPORT", "RESET", "SEND", "UNREPORT") @@ -138,59 +141,75 @@ class Msdp(object): def no_msdp(self, option): "No msdp supported or wanted" - print "No msdp supported" pass def do_msdp(self, option): """ Called when client confirms that it can do MSDP. """ - print "msdp supported" 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 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), or a dictionary (will be converted to MSDP_TABLE). - OBS - this supports nested tables and even arrays nested - inside tables, as opposed to the receive method. Arrays - cannot hold tables by definition (the table must be named - with MSDP_VAR, and an array can only contain MSDP_VALs). + Obs - this normally only returns tables and lists (var val val ...) rather than + arrays. It will convert *args to lists and **kwargs to tables and + if both are given to this method, this will result in a list followed + 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." - string += MSDP_VAR + name + MSDP_VAL + MSDP_TABLE_OPEN - for key, val in datadict.items(): - if type(val) == type({}): - string += make_table(key, val, string) + string = MSDP_VAR + force_str(name) + MSDP_VAL + MSDP_TABLE_OPEN + for key, val in kwargs.items(): + if isinstance(val, dict): + string += make_table(string, key, **val) elif hasattr(val, '__iter__'): - string += make_array(key, val, string) + string += make_array(string, key, *val) else: - string += MSDP_VAR + key + MSDP_VAL + val + string += MSDP_VAR + force_str(key) + MSDP_VAL + force_str(val) string += MSDP_TABLE_CLOSE return string - def make_array(name, datalist, string): - "build a simple array. Arrays may not nest tables by definition." - print "make_array", datalist, string - string += MSDP_VAR + name + MSDP_ARRAY_OPEN - for val in datalist: - string += MSDP_VAL + val + def make_array(name, *args): + "build a array. Arrays may not nest tables by definition." + string = MSDP_VAR + force_str(name) + MSDP_ARRAY_OPEN + string += MSDP_VAL.join(force_str(arg) for arg in args) string += MSDP_ARRAY_CLOSE return string - if isinstance(data, dict): - msdp_string = make_table(cmdname, data, "") - elif hasattr(data, '__iter__'): - msdp_string = make_array(cmdname, data, "") - else: - msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data if data!=None else "" - return msdp_string + def make_list(name, *args): + "build a simple list - an array without start/end markers" + string = MSDP_VAR + force_str(name) + string += MSDP_VAL.join(force_str(arg) for arg in args) + return 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): """ @@ -216,16 +235,24 @@ class Msdp(object): if hasattr(data, "__iter__"): data = "".join(data) - logger.log_infomsg("MSDP SUBNEGOTIATION: %s" % data) + #logger.log_infomsg("MSDP SUBNEGOTIATION: %s" % data) - for table in regex_table.findall(data): - tables[table[0].upper()] = dict(regex_varval.findall(table[1])) - for array in regex_array.findall(data): - arrays[array[0].upper()] = dict(regex_varval.findall(array[1])) - # get all stand-alone variables, but first we must clean out all tables and arrays (which also contain vars) - variables = dict((key.upper(), val) for key, val in regex_varval.findall(regex_array.sub("", regex_table.sub("", data)))) + for key, table in regex_table.findall(data): + tables[key] = {} + for varval in regex_var.split(table): + parts = regex_val.split(varval) + tables[key].expand({parts[0] : tuple(parts[1:]) if len(parts)>1 else ("",)}) + 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. # 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) # - - ret = "" - # default MSDP functions 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: - ret += self.evennia_to_msdp("REPORT", self.msdp_cmd_report(*(variables.pop("REPORT"),))) + self.data_in("report", *variables.pop("REPORT")) 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: - ret += self.evennia_to_msdp("RESET", self.msdp_cmd_reset(*(variables.pop("RESET"),))) + self.data_in("reset", *variables.pop("RESET")) 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: - ret += self.evennia_to_msdp("SEND", self.msdp_cmd_send(*(variables.pop("SEND",)))) + self.data_in("send", *variables.pop("SEND")) 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(): # a simple function + argument - ooc_func = MSDP_COMMANDS_CUSTOM.get(varname.upper()) - if ooc_func: - ret += self.evennia_to_msdp(varname, ooc_func(var)) + self.data_in(varname, (var,)) for arrayname, array in arrays.items(): # we assume the array are multiple arguments to the function - ooc_func = MSDP_COMMANDS_CUSTOM.get(arrayname.upper()) - if ooc_func: - ret += self.evennia_to_msdp(arrayname, ooc_func(*array)) + self.data_in(arrayname, *array) for tablename, table in tables.items(): # we assume tables are keyword arguments to the function - ooc_func = MSDP_COMMANDS_CUSTOM.get(arrayname.upper()) - if ooc_func: - ret += self.evennia_to_msdp(tablename, ooc_func(**table)) - - # return any result - if ret: - self.data_out(ret) + self.data_in(tablename, **table) def data_out(self, msdp_string): """ 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 - # 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): + def data_in(self, funcname, *args, **kwargs): """ - The List command allows for retrieving various info about the server/client + Send oob data to Evennia """ - 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) + #print "msdp data_in:", funcname, args, kwargs + self.protocol.data_in(text=None, oob=(funcname, args, kwargs)) - # 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): - """ - 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() + # # default msdp commands - def msdp_cmd_unreport(self, arg): - """ - Unreport a previously reported variable - """ - try: - MSDP_REPORTABLE[arg](report=False) - except Exception: - self.logger.log_trace() + # def msdp_cmd_report(self, *arg): + # """ + # 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_reset(self, arg): - """ - The reset command resets a variable to its initial state. - """ - try: - MSDP_REPORTABLE[arg](reset=True) - except Exception: - logger.log_trace() + # def msdp_cmd_unreport(self, arg): + # """ + # Unreport a previously reported variable + # """ + # try: + # MSDP_REPORTABLE[arg](report=False) + # except Exception: + # self.logger.log_trace() - def msdp_cmd_send(self, arg): - """ - Request the server to send a particular variable - to the client. + # def msdp_cmd_reset(self, arg): + # """ + # The reset command resets a variable to its initial state. + # """ + # try: + # MSDP_REPORTABLE[arg](reset=True) + # except Exception: + # logger.log_trace() - arg - this is a list of variables the client wants. - """ - ret = [] - if arg: - 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 + # def msdp_cmd_send(self, *args): + # """ + # Request the server to send a particular variable + # to the client. + + # arg - this is a list of variables the client wants. + # """ + # ret = [] + # for var in make_iter(arg) + + + + + # 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 diff --git a/src/server/portal/portalsessionhandler.py b/src/server/portal/portalsessionhandler.py index f70d6296f8..fffbf28c25 100644 --- a/src/server/portal/portalsessionhandler.py +++ b/src/server/portal/portalsessionhandler.py @@ -127,7 +127,6 @@ class PortalSessionHandler(SessionHandler): in from the protocol to the server. data is serialized before passed on. """ - #print "portal_data_in:", string self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid, msg=text, data=kwargs) diff --git a/src/server/portal/telnet.py b/src/server/portal/telnet.py index 21fa14fef8..ca886690e4 100644 --- a/src/server/portal/telnet.py +++ b/src/server/portal/telnet.py @@ -85,13 +85,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): be handled in line mode. Some clients also sends an erroneous 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: try: @@ -102,8 +95,16 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): else: self.iaw_mode = False return - except Exception: - logger.log_trace() + except Exception, err1: + 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. # We make sure to add it, to fix some clients messing this up. 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 connection. We pass it on to the game engine directly. """ - self.sessionhandler.data_in(self, string) + self.data_in(text=string) # Session hooks @@ -144,11 +145,17 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.data_out(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): """ + Data Evennia -> Player. generic hook method for engine to call in order to send data through the telnet connection. - Data Evennia -> Player. valid telnet kwargs: 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: oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob")) if "MSDP" in self.protocol_flags: - print "oobstruct:", oobstruct - for cmdname, args in oobstruct: - print "cmdname, args:", cmdname, args - msdp_string = self.msdp.func_to_msdp(cmdname, args) - print "msdp_string:", msdp_string + for cmdname, args, kwargs in oobstruct: + #print "cmdname, args, kwargs:", cmdname, args, kwargs + msdp_string = self.msdp.evennia_to_msdp(cmdname, *args, **kwargs) + #print "msdp_string:", msdp_string self.msdp.data_out(msdp_string) ttype = self.protocol_flags.get('TTYPE', {}) diff --git a/src/server/serversession.py b/src/server/serversession.py index 49dd89851b..5f395d557f 100644 --- a/src/server/serversession.py +++ b/src/server/serversession.py @@ -137,6 +137,14 @@ class ServerSession(Session): return self.logged_in and self.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): """ Emits session info to the appropriate outputs and info channels. @@ -182,10 +190,11 @@ class ServerSession(Session): # handle oob instructions global _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)) 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 diff --git a/src/server/sessionhandler.py b/src/server/sessionhandler.py index fa4634bbba..0b5b4dddd1 100644 --- a/src/server/sessionhandler.py +++ b/src/server/sessionhandler.py @@ -97,6 +97,55 @@ class SessionHandler(object): """ 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 #------------------------------------------------------------ @@ -358,48 +407,6 @@ class ServerSessionHandler(SessionHandler): return self.sessions.get(sessid) 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): """ diff --git a/src/settings_default.py b/src/settings_default.py index ec2a0ce384..84d2b9a230 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -199,7 +199,7 @@ MSSP_META_MODULE = "" # 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_MODULE = "" +OOB_PLUGIN_MODULE = "src.server.oob_defaults" # Tuple of modules implementing lock functions. All callable functions # inside these modules will be available as lock functions. LOCK_FUNC_MODULES = ("src.locks.lockfuncs",) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 77c44ba2ba..67c704567a 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -437,7 +437,7 @@ class NickHandler(AttributeHandler): with categories nick_ """ def has(self, key, category="inputline"): - categry = "nick_%s" % category + category = "nick_%s" % category return super(NickHandler, self).has(key, category=category) def get(self, key=None, category="inputline", **kwargs): @@ -462,6 +462,34 @@ class NickHandler(AttributeHandler): return super(NickHandler, self).all(category=category) 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 @@ -645,6 +673,7 @@ class TypedObject(SharedMemoryModel): _SA(self, "dbobj", self) # this allows for self-reference _SA(self, "locks", LockHandler(self)) _SA(self, "permissions", PermissionHandler(self)) + _SA(self, "nattributes", NAttributeHandler(self)) class Meta: """ @@ -1148,6 +1177,9 @@ class TypedObject(SharedMemoryModel): if hperm in perms and hpos > ppos) return False + # + # Memory management + # def flush_from_cache(self): """ @@ -1157,6 +1189,60 @@ class TypedObject(SharedMemoryModel): """ 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 # @@ -1202,35 +1288,12 @@ class TypedObject(SharedMemoryModel): raise Exception("Cannot delete the ndb object!") 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! _GA(self, "attributes").add(attribute_name, value, accessing_obj=accessing_object, default_access=default_access_create) - #@property - def __db_get(self): + def nattr(self, attribute_name=None, value=None, delete=False): """ - A second convenience wrapper for the the attribute methods. It - 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). + 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. """ - 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) + logger.log_depmsg("obj.nattr() is deprecated. Use obj.nattributes instead.") + 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) diff --git a/src/utils/utils.py b/src/utils/utils.py index 368eb07764..3ef27553de 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -729,10 +729,10 @@ def mod_import(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) - 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): """