Merge from Kelketek's clone. Added fixes to migrations of Tags. Issues with ContentTypes meaning that this revision is not possible to start.

This commit is contained in:
Griatch 2013-10-17 23:44:52 +02:00
commit acdea41a67
19 changed files with 601 additions and 468 deletions

View file

@ -316,6 +316,7 @@ def cmdhandler(called_on, raw_string, testing=False, callertype="session", sessi
else: else:
# fallback to default error text # fallback to default error text
sysarg = _("Command '%s' is not available.") % raw_string sysarg = _("Command '%s' is not available.") % raw_string
cmdset.get_all_cmd_keys_and_aliases(caller)
suggestions = string_suggestions(raw_string, cmdset.get_all_cmd_keys_and_aliases(caller), cutoff=0.7, maxnum=3) suggestions = string_suggestions(raw_string, cmdset.get_all_cmd_keys_and_aliases(caller), cutoff=0.7, maxnum=3)
if suggestions: if suggestions:
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True) sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True)
@ -332,7 +333,7 @@ def cmdhandler(called_on, raw_string, testing=False, callertype="session", sessi
if syscmd: if syscmd:
# replace system command with custom version # replace system command with custom version
cmd = syscmd cmd = syscmd
cmd.sessid = caller.sessid if callertype=="session" else None cmd.sessid = session.sessid if session else None
sysarg = "%s:%s" % (cmdname, args) sysarg = "%s:%s" % (cmdname, args)
raise ExecSystemCommand(cmd, sysarg) raise ExecSystemCommand(cmd, sysarg)
@ -398,7 +399,7 @@ def cmdhandler(called_on, raw_string, testing=False, callertype="session", sessi
syscmd.cmdstring = syscmd.key syscmd.cmdstring = syscmd.key
syscmd.args = sysarg syscmd.args = sysarg
syscmd.cmdset = cmdset syscmd.cmdset = cmdset
syscmd.sessid = caller.sessid if callertype=="session" else None syscmd.sessid = session.sessid if session else None
syscmd.raw_string = unformatted_raw_string syscmd.raw_string = unformatted_raw_string
if hasattr(syscmd, 'obj') and hasattr(syscmd.obj, 'scripts'): if hasattr(syscmd, 'obj') and hasattr(syscmd.obj, 'scripts'):

View file

@ -527,7 +527,7 @@ class CmdPerm(MuxCommand):
tstring = "" tstring = ""
if 'del' in switches: if 'del' in switches:
# delete the given permission(s) from object. # delete the given permission(s) from object.
obj.permission.remove(self.rhslist) obj.permissions.remove(self.rhslist)
cstring += "\nPermission(s) %s removed from %s (if they existed)." % (", ".join(self.rhslist), obj.name) cstring += "\nPermission(s) %s removed from %s (if they existed)." % (", ".join(self.rhslist), obj.name)
tstring += "\n%s revokes the permission(s) %s from you." % (caller.name, ", ".join(self.rhslist)) tstring += "\n%s revokes the permission(s) %s from you." % (caller.name, ", ".join(self.rhslist))
else: else:

View file

@ -8,7 +8,7 @@ class Migration(DataMigration):
def forwards(self, orm): def forwards(self, orm):
"Write your forwards methods here." "Write your forwards methods here."
# Note: Don't use "from appname.models import ModelName". # Note: Don't use "from appname.models import ModelName".
# Use orm.ModelName to refer to models in this application, # Use orm.ModelName to refer to models in this application,
# and orm['appname.ModelName'] for models in other applications. # and orm['appname.ModelName'] for models in other applications.
ChannelDB = orm['comms.ChannelDB'] ChannelDB = orm['comms.ChannelDB']
@ -28,8 +28,12 @@ class Migration(DataMigration):
new_channel.db_attributes.add(keep_log) new_channel.db_attributes.add(keep_log)
for name in [alias.strip() for alias in for name in [alias.strip() for alias in
channel.db_aliases.split(',')]: channel.db_aliases.split(',')]:
tag = Tag(db_key=name, db_category='comm_alias') tag = Tag.objects.filter(db_key=name.lower().strip(), db_category='comm_alias')
tag.save() if tag:
tag = tag[0]
else:
tag = Tag(db_key=name.lower().strip(), db_category='comm_alias')
tag.save()
new_channel.db_tags.add(tag) new_channel.db_tags.add(tag)
new_channel.save() new_channel.save()

View file

@ -342,10 +342,6 @@ class ChannelDB(TypedObject):
_typeclass_paths = settings.COMM_TYPECLASS_PATHS _typeclass_paths = settings.COMM_TYPECLASS_PATHS
_default_typeclass_path = settings.BASE_COMM_TYPECLASS or "src.comms.comms.Comm" _default_typeclass_path = settings.BASE_COMM_TYPECLASS or "src.comms.comms.Comm"
class Meta:
"Define Django meta options"
verbose_name = "Channel"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
TypedObject.__init__(self, *args, **kwargs) TypedObject.__init__(self, *args, **kwargs)
_SA(self, "tags", TagHandler(self, category_prefix="comm_")) _SA(self, "tags", TagHandler(self, category_prefix="comm_"))

View file

@ -11,12 +11,16 @@ class Migration(DataMigration):
# Note: Don't use "from appname.models import ModelName". # Note: Don't use "from appname.models import ModelName".
# Use orm.ModelName to refer to models in this application, # Use orm.ModelName to refer to models in this application,
# and orm['appname.ModelName'] for models in other applications. # and orm['appname.ModelName'] for models in other applications.
Tag = orm['typeclasses.Tag']
for obj in orm.ObjectDB.objects.all(): for obj in orm.ObjectDB.objects.all():
if obj.db_permissions: if obj.db_permissions:
for perm in [perm.strip() for perm in obj.db_permissions.split(",")]: for perm in [perm.strip() for perm in obj.db_permissions.split(",")]:
tag = orm['typeclasses.Tag'].create(db_key=perm, db_category="permissions") tag = Tag.objects.filter(db_key=perm.lower().strip(), db_category="permissions")
tag.save() if tag:
tag = tag[0]
else:
tag = Tag(db_key=perm.lower().strip(), db_category="permissions")
tag.save()
obj.db_tags.add(tag) obj.db_tags.add(tag)
def backwards(self, orm): def backwards(self, orm):

View file

@ -11,11 +11,16 @@ class Migration(DataMigration):
# Note: Don't use "from appname.models import ModelName". # Note: Don't use "from appname.models import ModelName".
# Use orm.ModelName to refer to models in this application, # Use orm.ModelName to refer to models in this application,
# and orm['appname.ModelName'] for models in other applications. # and orm['appname.ModelName'] for models in other applications.
Tag = orm['typeclasses.Tag']
for obj in orm.PlayerDB.objects.all(): for obj in orm.PlayerDB.objects.all():
if obj.db_permissions: if obj.db_permissions:
for perm in [perm.strip() for perm in obj.db_permissions.split(",")]: for perm in [perm.strip() for perm in obj.db_permissions.split(",")]:
tag = orm['typeclasses.Tag'].create(db_key=perm, db_category="permissions") tag = Tag.objects.filter(db_key=perm.lower().strip(), db_category="permissions")
tag.save() if tag:
tag = tag[0]
else:
tag = Tag(db_key=perm.lower().strip(), db_category="permissions")
tag.save()
obj.db_tags.add(tag) obj.db_tags.add(tag)
def backwards(self, orm): def backwards(self, orm):

View file

@ -11,11 +11,16 @@ class Migration(DataMigration):
# Note: Don't use "from appname.models import ModelName". # Note: Don't use "from appname.models import ModelName".
# Use orm.ModelName to refer to models in this application, # Use orm.ModelName to refer to models in this application,
# and orm['appname.ModelName'] for models in other applications. # and orm['appname.ModelName'] for models in other applications.
Tag = orm['typeclasses.Tag']
for obj in orm.ScriptDB.objects.all(): for obj in orm.ScriptDB.objects.all():
if obj.db_permissions: if obj.db_permissions:
for perm in [perm.strip() for perm in obj.db_permissions.split(",")]: for perm in [perm.strip() for perm in obj.db_permissions.split(",")]:
tag = orm['typeclasses.Tag'].create(db_key=perm, db_category="permissions") tag = Tag.objects.filter(db_key=perm.lower().strip(), db_category="permissions")
tag.save() if tag:
tag = tag[0]
else:
tag = Tag(db_key=perm.lower().strip(), db_category="permissions")
tag.save()
obj.db_tags.add(tag) obj.db_tags.add(tag)
def backwards(self, orm): def backwards(self, orm):

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,23 +22,23 @@ 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
from src.scripts.scripts import Script from src.scripts.scripts import Script
from src.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, unpack_dbobj
from src.utils import logger from src.utils import logger
from src.utils.utils import variable_from_module, to_str 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.lower(), 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):
@ -52,30 +52,34 @@ class TrackerHandler(object):
""" """
This is initiated and stored on the object as a property _trackerhandler. This is initiated and stored on the object as a property _trackerhandler.
""" """
self.obj = obj.dbobj try: obj = obj.dbobj
except AttributeError: pass
self.obj = obj
self.ntrackers = 0 self.ntrackers = 0
# initiate store only with valid on-object fieldnames # initiate store only with valid on-object fieldnames
self.tracktargets = dict((key, {}) for key in _GA(_GA(self.obj, "_meta"), "get_all_field_names")()) self.tracktargets = dict((key, {}) for key in _GA(_GA(self.obj, "_meta"), "get_all_field_names")())
def add(self, fieldname, trackerkey, trackerobj): def add(self, fieldname, tracker):
""" """
Add tracker to the handler. Raises KeyError if fieldname Add tracker to the handler. Raises KeyError if fieldname
does not exist. does not exist.
""" """
self.tracktargets[fieldname][trackerkey] = trackerobj trackerkey = tracker.__class__.__name__
self.tracktargets[fieldname][trackerkey] = tracker
self.ntrackers += 1 self.ntrackers += 1
def remove(self, fieldname, trackerkey, *args, **kwargs): def remove(self, fieldname, trackerclass, *args, **kwargs):
""" """
Remove tracker from handler. Raises KeyError if tracker Remove tracker from handler. Raises KeyError if tracker
is not found. is not found.
""" """
oobobj = self.tracktargets[fieldname][trackerkey] trackerkey = trackerclass.__name__
tracker = self.tracktargets[fieldname][trackerkey]
try: try:
oobobj.at_delete(*args, **kwargs) tracker.at_delete(*args, **kwargs)
except Exception: except Exception:
logger.log_trace() logger.log_trace()
del oobobj del tracker
self.ntrackers -= 1 self.ntrackers -= 1
if self.ntrackers <= 0: if self.ntrackers <= 0:
# if there are no more trackers, clean this handler # if there are no more trackers, clean this handler
@ -85,9 +89,9 @@ class TrackerHandler(object):
""" """
Called by the field when it updates to a new value Called by the field when it updates to a new value
""" """
for trackerobj in self.tracktargets[fieldname].values(): for tracker in self.tracktargets[fieldname].values():
try: try:
trackerobj.update(fieldname, new_value) tracker.update(new_value)
except Exception: except Exception:
logger.log_trace() logger.log_trace()
@ -104,84 +108,61 @@ class TrackerBase(object):
"Called when tracker is removed" "Called when tracker is removed"
pass pass
# Default tracker OOB class class _RepeaterScript(Script):
class OOBTracker(TrackerBase):
""" """
A OOB object that passively sends data to a stored sessid whenever Repeating and subscription-enabled script for triggering OOB
a named database field changes. functions. Maintained in a _RepeaterPool.
""" """
def __init__(self, fieldname, sessid, *args, **kwargs): def at_script_creation(self):
""" "Called when script is initialized"
name - name of entity to track, such as "db_key" self.key = "oob_func"
track_type - one of "field", "prop" or "attr" for Database fields, self.desc = "OOB functionality script"
non-database Property or Attribute self.persistent = False #oob scripts should always be non-persistent
sessid - sessid of session to report to self.ndb.subscriptions = {}
"""
self.fieldname = fieldname
self.sessid = sessid
def update(self, new_value, *args, **kwargs): def at_repeat(self):
"Called by cache when updating the tracked entitiy" """
SESSIONS.session_from_sessid(self.sessid).msg(oob={"cmdkey":"trackreturn", Calls subscriptions every self.interval seconds
"name":self.fieldname, """
"value":new_value}) for (func_key, sessid, interval, args, kwargs) in self.ndb.subscriptions.values():
session = SESSIONS.session_from_sessid(sessid)
OOB_HANDLER.execute_cmd(session, func_key, *args, **kwargs)
def subscribe(self, store_key, sessid, func_key, interval, *args, **kwargs):
"""
Sign up a subscriber to this oobfunction. Subscriber is
a database object with a dbref.
"""
self.ndb.subscriptions[store_key] = (func_key, sessid, interval, args, kwargs)
def unsubscribe(self, store_key):
"""
Unsubscribe from oobfunction. Returns True if removal was
successful, False otherwise
"""
self.ndb.subscriptions.pop(store_key, None)
class _RepeaterPool(object): class _RepeaterPool(object):
""" """
This maintains a pool of _RepeaterScript scripts, ordered one per interval. It This maintains a pool of _RepeaterScript scripts, ordered one per interval. It
will automatically cull itself once a given interval's script has no more will automatically cull itself once a given interval's script has no more
subscriptions. subscriptions.
This is used and accessed from oobhandler.repeat/unrepeat
""" """
class _RepeaterScript(Script):
"""
Repeating script for triggering OOB functions. Maintained in the pool.
"""
def at_script_creation(self):
"Called when script is initialized"
self.key = "oob_func"
self.desc = "OOB functionality script"
self.persistent = False #oob scripts should always be non-persistent
self.ndb.subscriptions = {}
def at_repeat(self):
"""
Calls subscriptions every self.interval seconds
"""
for (func_key, caller, interval, args, kwargs) in self.ndb.subscriptions.values():
try:
_OOB_FUNCS[func_key](caller, *args, **kwargs)
except Exception:
logger.log_trace()
def subscribe(self, store_key, caller, func_key, interval, *args, **kwargs):
"""
Sign up a subscriber to this oobfunction. Subscriber is
a database object with a dbref.
"""
self.ndb.subscriptions[store_key] = (func_key, caller, interval, args, kwargs)
def unsubscribe(self, store_key):
"""
Unsubscribe from oobfunction. Returns True if removal was
successful, False otherwise
"""
self.ndb.subscriptions.pop(store_key, None)
def __init__(self): def __init__(self):
self.scripts = {} self.scripts = {}
def add(self, store_key, caller, func_key, interval, *args, **kwargs): def add(self, store_key, sessid, func_key, interval, *args, **kwargs):
""" """
Add a new tracking Add a new tracking
""" """
if interval not in self.scripts: if interval not in self.scripts:
# if no existing interval exists, create new script to fill the gap # if no existing interval exists, create new script to fill the gap
new_tracker = create_script(self._RepeaterScript, key="oob_repeater_%is" % interval, interval=interval) new_tracker = create_script(_RepeaterScript, key="oob_repeater_%is" % interval, interval=interval)
self.scripts[interval] = new_tracker self.scripts[interval] = new_tracker
self.scripts[interval].subscribe(store_key, caller, func_key, interval, *args, **kwargs) self.scripts[interval].subscribe(store_key, sessid, func_key, interval, *args, **kwargs)
def remove(self, store_key, interval): def remove(self, store_key, interval):
""" """
@ -193,14 +174,14 @@ class _RepeaterPool(object):
# no more subscriptions for this interval. Clean out the script. # no more subscriptions for this interval. Clean out the script.
self.scripts[interval].stop() self.scripts[interval].stop()
def stop(self):
"""
Stop all scripts in pool. This is done at server reload since restoring the pool
will automatically re-populate the pool.
"""
for script in self.scripts.values():
script.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
@ -214,6 +195,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()
@ -224,9 +206,12 @@ class OOBHandler(object):
ServerConf field ServerConf field
""" """
if self.oob_tracker_storage: if self.oob_tracker_storage:
#print "saved tracker_storage:", self.oob_tracker_storage
ServerConfig.objects.conf(key="oob_tracker_storage", value=dbserialize(self.oob_tracker_storage)) ServerConfig.objects.conf(key="oob_tracker_storage", value=dbserialize(self.oob_tracker_storage))
if self.oob_repeat_storage: if self.oob_repeat_storage:
#print "saved repeat_storage:", self.oob_repeat_storage
ServerConfig.objects.conf(key="oob_repeat_storage", value=dbserialize(self.oob_repeat_storage)) ServerConfig.objects.conf(key="oob_repeat_storage", value=dbserialize(self.oob_repeat_storage))
self.oob_tracker_pool.stop()
def restore(self): def restore(self):
""" """
@ -237,88 +222,102 @@ class OOBHandler(object):
tracker_storage = ServerConfig.objects.conf(key="oob_tracker_storage") tracker_storage = ServerConfig.objects.conf(key="oob_tracker_storage")
if tracker_storage: if tracker_storage:
self.oob_tracker_storage = dbunserialize(tracker_storage) self.oob_tracker_storage = dbunserialize(tracker_storage)
for tracker_key, (obj, sessid, fieldname, args, kwargs) in self.oob_tracker_storage.items(): #print "recovered from tracker_storage:", self.oob_tracker_storage
self.track(obj, sessid, fieldname, tracker_key, *args, **kwargs) for (obj, sessid, fieldname, trackerclass, args, kwargs) in self.oob_tracker_storage.values():
self.track(unpack_dbobj(obj), sessid, fieldname, trackerclass, *args, **kwargs)
# make sure to purce the storage
ServerConfig.objects.conf(key="oob_tracker_storage", delete=True)
repeat_storage = ServerConfig.objects.conf(key="oob_repeat_storage") repeat_storage = ServerConfig.objects.conf(key="oob_repeat_storage")
if repeat_storage: if repeat_storage:
self.oob_repeat_storage = dbunserialize(repeat_storage) self.oob_repeat_storage = dbunserialize(repeat_storage)
for func_key, (caller, func_key, interval, args, kwargs) in self.oob_repeat_storage.items(): #print "recovered from repeat_storage:", self.oob_repeat_storage
self.repeat(caller, func_key, interval, *args, **kwargs) for (obj, sessid, func_key, interval, args, kwargs) in self.oob_repeat_storage.values():
self.repeat(unpack_dbobj(obj), sessid, func_key, interval, *args, **kwargs)
# make sure to purge the storage
ServerConfig.objects.conf(key="oob_repeat_storage", delete=True)
def track(self, obj, sessid, fieldname, trackerclass, *args, **kwargs):
def track(self, obj, sessid, fieldname, tracker_key, *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
it to obj. it to obj.
If property_key is not given, but the OOB has a class property property_name, this If property_key is not given, but the OOB has a class property property_name, this
will be used as the property name when assigning the OOB to will be used as the property name when assigning the OOB to
obj, otherwise tracker_key is ysed as the property name. obj, otherwise tracker_key is used as the property name.
""" """
oobclass = _OOB_TRACKERS[tracker_key] # raise traceback if not found try: obj = obj.dbobj
except AttributeError: pass
if not "_trackerhandler" in _GA(obj, "__dict__"): if not "_trackerhandler" in _GA(obj, "__dict__"):
# assign trackerhandler to object # assign trackerhandler to object
_SA(obj, "_trackerhandler", TrackerHandler(obj)) _SA(obj, "_trackerhandler", TrackerHandler(obj))
# initialize object # initialize object
oob = oobclass(obj, sessid, fieldname, *args, **kwargs) tracker = trackerclass(self, fieldname, sessid, *args, **kwargs)
_GA(obj, "_trackerhandler").add(oob, fieldname) _GA(obj, "_trackerhandler").add(fieldname, tracker)
# store calling arguments as a pickle for retrieval later # store calling arguments as a pickle for retrieval later
storekey = (pack_dbobj(obj), sessid, fieldname) obj_packed = pack_dbobj(obj)
stored = (obj, sessid, fieldname, args, kwargs) storekey = (obj_packed, sessid, fieldname)
stored = (obj_packed, sessid, fieldname, trackerclass, args, kwargs)
self.oob_tracker_storage[storekey] = stored self.oob_tracker_storage[storekey] = stored
def untrack(self, obj, sessid, fieldname, tracker_key, *args, **kwargs): def untrack(self, obj, sessid, fieldname, trackerclass, *args, **kwargs):
""" """
Remove the OOB from obj. If oob implements an Remove the OOB from obj. If oob implements an
at_delete hook, this will be called with args, kwargs at_delete hook, this will be called with args, kwargs
""" """
try: obj = obj.dbobj
except AttributeError: pass
try: try:
# call at_delete hook # call at_delete hook
_GA(obj, "_trackerhandler").remove(fieldname, tracker_key, *args, **kwargs) _GA(obj, "_trackerhandler").remove(fieldname, trackerclass, *args, **kwargs)
except AttributeError: except AttributeError:
pass pass
# remove the pickle from storage # remove the pickle from storage
store_key = (pack_dbobj(obj), sessid, fieldname) store_key = (pack_dbobj(obj), sessid, fieldname)
self.oob_tracker_storage.pop(store_key, None) self.oob_tracker_storage.pop(store_key, None)
def track_field(self, obj, sessid, field_name, tracker_key="oobtracker"): def track_field(self, obj, sessid, field_name, trackerclass):
""" """
Shortcut wrapper method for specifically tracking a database field. Shortcut wrapper method for specifically tracking a database field.
Uses OOBTracker by default (change tracker_key to redirect) Takes the tracker class as argument.
Will create a tracker with a property name that the field cache
expects
""" """
# all database field names starts with db_* # all database field names starts with db_*
field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name
oob_tracker_name = "_track_%s_change" % field_name # field cache looks for name on this form self.track(obj, sessid, field_name, trackerclass)
self.track(obj, tracker_key, field_name, sessid, property_name=oob_tracker_name)
def track_attribute(self, obj, sessid, attr_name, tracker_key="oobtracker"): def untrack_field(self, obj, sessid, field_name):
"""
Shortcut for untracking a database field. Uses OOBTracker by defualt
"""
field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name
self.untrack(obj, sessid, field_name)
def track_attribute(self, obj, sessid, attr_name, trackerclass):
""" """
Shortcut wrapper method for specifically tracking the changes of an Shortcut wrapper method for specifically tracking the changes of an
Attribute on an object. Will create a tracker on the Attribute Object and Attribute on an object. Will create a tracker on the Attribute Object and
name in a way the Attribute expects. name in a way the Attribute expects.
""" """
# get the attribute object if we can # get the attribute object if we can
try: obj = obj.dbobj
except AttributeError: pass
attrobj = _GA(obj, "attributes").get(attr_name, return_obj=True) attrobj = _GA(obj, "attributes").get(attr_name, return_obj=True)
if attrobj: if attrobj:
oob_tracker_name = "_track_db_value_change" self.track(attrobj, sessid, "db_value", trackerclass, attr_name)
self.track(attrobj, tracker_key, attr_name, sessid, property_name=oob_tracker_name)
def run(self, func_key, *args, **kwargs): def untrack_attribute(self, obj, sessid, attr_name, trackerclass):
""" """
Retrieve oobfunc from OOB_FUNCS and execute it immediately Shortcut for deactivating tracking for a given attribute.
using *args and **kwargs
""" """
oobfunc = _OOB_FUNCS[func_key] # raise traceback if not found try: obj = obj.dbobj
try: except AttributeError: pass
oobfunc(*args, **kwargs) attrobj = _GA(obj, "attributes").get(attr_name, return_obj=True)
except Exception: if attrobj:
logger.log_trace() self.untrack(attrobj, sessid, attr_name, trackerclass)
def repeat(self, caller, func_key, interval=20, *args, **kwargs): def repeat(self, obj, sessid, func_key, interval=20, *args, **kwargs):
""" """
Start a repeating action. Every interval seconds, Start a repeating action. Every interval seconds,
the oobfunc corresponding to func_key is called with the oobfunc corresponding to func_key is called with
@ -326,20 +325,59 @@ class OOBHandler(object):
""" """
if not func_key in _OOB_FUNCS: if not func_key in _OOB_FUNCS:
raise KeyError("%s is not a valid OOB function name.") raise KeyError("%s is not a valid OOB function name.")
store_key = (pack_dbobj(caller), func_key, interval) try:
obj = obj.dbobj
except AttributeError:
pass
store_obj = pack_dbobj(obj)
store_key = (store_obj, sessid, func_key, interval)
# prepare to store # prepare to store
self.oob_repeat_storage[store_key] = (caller, func_key, interval, args, kwargs) self.oob_repeat_storage[store_key] = (store_obj, sessid, func_key, interval, args, kwargs)
self.oob_tracker_pool.add(store_key, caller, func_key, interval, *args, **kwargs) self.oob_tracker_pool.add(store_key, sessid, func_key, interval, *args, **kwargs)
def unrepeat(self, caller, func_key, interval=20): def unrepeat(self, obj, sessid, func_key, interval=20):
""" """
Stop a repeating action Stop a repeating action
""" """
store_key = (pack_dbobj(caller), func_key, interval) try:
obj = obj.dbobj
except AttributeError:
pass
store_key = (pack_dbobj(obj), sessid, func_key, interval)
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)
#print "oobhandler msg:", sessid, session, funcname, args, kwargs
if session:
session.msg(oob=(funcname, args, kwargs))
# access method - called from msg()
def execute_cmd(self, session, func_key, *args, **kwargs):
"""
Retrieve oobfunc from OOB_FUNCS and execute it immediately
using *args and **kwargs
"""
try:
#print "OOB execute_cmd:", session, func_key, args, kwargs, _OOB_FUNCS.keys()
oobfunc = _OOB_FUNCS[func_key] # raise traceback if not found
oobfunc(self, session, *args, **kwargs)
except KeyError,e:
errmsg = "OOB Error: function '%s' not recognized: %s" % (func_key, e)
if _OOB_ERROR:
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
else:
logger.log_trace(errmsg)
raise
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)
raise
# access object # access object
OOB_HANDLER = OOBHandler() OOB_HANDLER = OOBHandler()

View file

@ -7,12 +7,10 @@ http://tintin.sourceforge.net/msdp/. MSDP manages out-of-band
communication between the client and server, for updating health bars communication between the client and server, for updating health bars
etc. etc.
!TODO - this is just a partial implementation and not used by telnet yet.
""" """
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,94 +26,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_COMMANDS = ("LIST", "REPORT", "RESET", "SEND", "UNREPORT")
# fallbacks if no custom OOB module is available
MSDP_COMMANDS_CUSTOM = {}
# MSDP_REPORTABLE is a standard suggestions for making it easy to create generic guis.
# this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It
# is up to these commands to return data on proper form. This is overloaded if
# OOB_REPORTABLE is defined in the custom OOB module below.
MSDP_REPORTABLE = {
# General
"CHARACTER_NAME": "get_character_name",
"SERVER_ID": "get_server_id",
"SERVER_TIME": "get_server_time",
# Character
"AFFECTS": "char_affects",
"ALIGNMENT": "char_alignment",
"EXPERIENCE": "char_experience",
"EXPERIENCE_MAX": "char_experience_max",
"EXPERIENCE_TNL": "char_experience_tnl",
"HEALTH": "char_health",
"HEALTH_MAX": "char_health_max",
"LEVEL": "char_level",
"RACE": "char_race",
"CLASS": "char_class",
"MANA": "char_mana",
"MANA_MAX": "char_mana_max",
"WIMPY": "char_wimpy",
"PRACTICE": "char_practice",
"MONEY": "char_money",
"MOVEMENT": "char_movement",
"MOVEMENT_MAX": "char_movement_max",
"HITROLL": "char_hitroll",
"DAMROLL": "char_damroll",
"AC": "char_ac",
"STR": "char_str",
"INT": "char_int",
"WIS": "char_wis",
"DEX": "char_dex",
"CON": "char_con",
# Combat
"OPPONENT_HEALTH": "opponent_health",
"OPPONENT_HEALTH_MAX":"opponent_health_max",
"OPPONENT_LEVEL": "opponent_level",
"OPPONENT_NAME": "opponent_name",
# World
"AREA_NAME": "area_name",
"ROOM_EXITS": "area_room_exits",
"ROOM_NAME": "room_name",
"ROOM_VNUM": "room_dbref",
"WORLD_TIME": "world_time",
# Configurable variables
"CLIENT_ID": "client_id",
"CLIENT_VERSION": "client_version",
"PLUGIN_ID": "plugin_id",
"ANSI_COLORS": "ansi_colours",
"XTERM_256_COLORS": "xterm_256_colors",
"UTF_8": "utf_8",
"SOUND": "sound",
"MXP": "mxp",
# GUI variables
"BUTTON_1": "button1",
"BUTTON_2": "button2",
"BUTTON_3": "button3",
"BUTTON_4": "button4",
"BUTTON_5": "button5",
"GAUGE_1": "gauge1",
"GAUGE_2": "gauge2",
"GAUGE_3": "gauge3",
"GAUGE_4": "gauge4",
"GAUGE_5": "gauge5"}
MSDP_SENDABLE = MSDP_REPORTABLE
# try to load custom OOB module
OOB_MODULE = None#mod_import(settings.OOB_FUNC_MODULE)
if OOB_MODULE:
# loading customizations from OOB_FUNC_MODULE if available
try: MSDP_REPORTABLE = OOB_MODULE.OOB_REPORTABLE # replaces the default MSDP definitions
except AttributeError: pass
try: MSDP_SENDABLE = OOB_MODULE.OOB_SENDABLE
except AttributeError: MSDP_SENDABLE = MSDP_REPORTABLE
try: MSDP_COMMANDS_CUSTOM = OOB_MODULE.OOB_COMMANDS
except: pass
# Msdp object handler # Msdp object handler
@ -132,69 +49,97 @@ class Msdp(object):
""" """
self.protocol = protocol self.protocol = protocol
self.protocol.protocol_flags['MSDP'] = False self.protocol.protocol_flags['MSDP'] = False
self.protocol.negotiationMap[MSDP] = self.msdp_to_func self.protocol.negotiationMap[MSDP] = self.msdp_to_evennia
self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp) self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp)
self.msdp_reported = {} self.msdp_reported = {}
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 parse_msdp(self, args): def evennia_to_msdp(self, cmdname, *args, **kwargs):
"Called with arguments to subnegotiation"
def func_to_msdp(self, cmdname, data):
""" """
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 - there is no actual use of arrays and tables in the MSDP
inside tables, as opposed to the receive method. Arrays specification or default commands -- are returns are implemented
cannot hold tables by definition (the table must be named as simple lists or named lists (our name for them here, these
with MSDP_VAR, and an array can only contain MSDP_VALs). un-bounded structures are not named in the specification). So for
now, this routine will not explicitly create arrays nor tables,
although there are helper methods ready should it be needed in
the future.
""" """
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 stringk
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."
string += MSDP_VAR + name + MSDP_ARRAY_OPEN string = MSDP_VAR + force_str(name) + MSDP_ARRAY_OPEN
for val in datalist: string += MSDP_VAL.join(force_str(arg) for arg in args)
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)
return string
def make_named_list(name, **kwargs):
"build a named list - a table without start/end markers"
string = MSDP_VAR + force_str(name)
for key, val in kwargs.items():
string += MSDP_VAR + force_str(key) + MSDP_VAL + force_str(val)
return string
# Default MSDP commands
print "MSDP outgoing:", cmdname, args, kwargs
cupper = cmdname.upper()
if cupper == "LIST":
if args:
args = list(args)
mode = args.pop(0).upper()
self.data_out(make_array(mode, *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_named_list("SEND", **kwargs))
else: else:
msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data # return list or named lists.
return msdp_string msdp_string = ""
if args:
msdp_string += make_list(cupper, *args)
if kwargs:
msdp_string += make_named_list(cupper, **kwargs)
self.data_out(msdp_string)
def msdp_to_evennia(self, data):
def msdp_to_func(self, data):
""" """
Handle a client's requested negotiation, converting Handle a client's requested negotiation, converting
it into a function mapping - either one of the MSDP it into a function mapping - either one of the MSDP
@ -218,143 +163,151 @@ 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
ret = "" # all variables sent through msdp to Evennia are considered commands with arguments.
# there are three forms of commands possible through msdp:
#
# VARNAME VAR -> varname(var)
# ARRAYNAME VAR VAL VAR VAL VAR VAL ENDARRAY -> arrayname(val,val,val)
# TABLENAME TABLE VARNAME VAL VARNAME VAL ENDTABLE -> tablename(varname=val, varname=val)
#
# default MSDP functions # default MSDP functions
if "LIST" in variables: if "LIST" in variables:
ret += self.func_to_msdp("LIST", self.msdp_cmd_list(variables["LIST"])) self.data_in("list", *variables.pop("LIST"))
del variables["LIST"]
if "REPORT" in variables: if "REPORT" in variables:
ret += self.func_to_msdp("REPORT", self.msdp_cmd_report(*(variables["REPORT"],))) self.data_in("report", *variables.pop("REPORT"))
del variables["REPORT"]
if "REPORT" in arrays: if "REPORT" in arrays:
ret += self.func_to_msdp("REPORT", self.msdp_cmd_report(*arrays["REPORT"])) self.data_in("report", *(arrays.pop("REPORT")))
del arrays["REPORT"] if "UNREPORT" in variables:
self.data_in("unreport", *(arrays.pop("UNREPORT")))
if "RESET" in variables: if "RESET" in variables:
ret += self.func_to_msdp("RESET", self.msdp_cmd_reset(*(variables["RESET"],))) self.data_in("reset", *variables.pop("RESET"))
del variables["RESET"]
if "RESET" in arrays: if "RESET" in arrays:
ret += self.func_to_msdp("RESET", self.msdp_cmd_reset(*arrays["RESET"])) self.data_in("reset", *(arrays.pop("RESET")))
del arrays["RESET"]
if "SEND" in variables: if "SEND" in variables:
ret += self.func_to_msdp("SEND", self.msdp_cmd_send(*(variables["SEND"],))) self.data_in("send", *variables.pop("SEND"))
del variables["SEND"]
if "SEND" in arrays: if "SEND" in arrays:
ret += self.func_to_msdp("SEND",self.msdp_cmd_send(*arrays["SEND"])) self.data_in("send", *(arrays.pop("SEND")))
del arrays["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.func_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.func_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.func_to_msdp(tablename, ooc_func(**table))
if ret: def data_out(self, msdp_string):
# send return value if it exists
self.msdp_send(ret)
ret = IAC + SB + MSDP + ret + IAC + SE
#ret = IAC + SB + MSDP + MSDP_VAR + "SEND" + MSDP_VAL + "Testsend" + IAC + SE
self.protocol._write(ret)
logger.log_infomsg("MSDP_RESULT: %s" % ret)
def msdp_send(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.func_to_msdp(arg, MSDP_COMMANDS) self.protocol.data_in(text=None, oob=(funcname, args, kwargs))
elif arg == 'LISTS':
return self.func_to_msdp(arg, ("COMMANDS", "LISTS", "CONFIGURABLE_VARIABLES",
"REPORTED_VARIABLES", "SENDABLE_VARIABLES"))
elif arg == 'CONFIGURABLE_VARIABLES':
return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
elif arg == 'REPORTABLE_VARIABLES':
return self.func_to_msdp(arg, MSDP_REPORTABLE.keys())
elif arg == 'REPORTED_VARIABLES':
# the dynamically set items to report
return self.func_to_msdp(arg, self.msdp_reported.keys())
elif arg == 'SENDABLE_VARIABLES':
return self.func_to_msdp(arg, MSDP_SENDABLE.keys())
else:
return self.func_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)
@ -138,6 +137,7 @@ class PortalSessionHandler(SessionHandler):
for session in self.sessions.values(): for session in self.sessions.values():
session.data_out(message) session.data_out(message)
def data_out(self, sessid, text=None, **kwargs): def data_out(self, sessid, text=None, **kwargs):
""" """
Called by server for having the portal relay messages and data Called by server for having the portal relay messages and data

View file

@ -13,6 +13,7 @@ from src.server.session import Session
from src.server.portal import ttype, mssp, msdp from src.server.portal import ttype, mssp, msdp
from src.server.portal.mccp import Mccp, mccp_compress, MCCP from src.server.portal.mccp import Mccp, mccp_compress, MCCP
from src.utils import utils, ansi, logger from src.utils import utils, ansi, logger
from src.utils.utils import make_iter, is_iter
_RE_N = re.compile(r"\{n$") _RE_N = re.compile(r"\{n$")
@ -36,14 +37,13 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# negotiate ttype (client info) # negotiate ttype (client info)
#self.ttype = ttype.Ttype(self) #self.ttype = ttype.Ttype(self)
# negotiate mssp (crawler communication) # negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self) #self.mssp = mssp.Mssp(self)
# msdp # msdp
#self.msdp = msdp.Msdp(self) self.msdp = msdp.Msdp(self)
# add this new connection to sessionhandler so # add this new connection to sessionhandler so
# the Server becomes aware of it. # the Server becomes aware of it.
self.sessionhandler.connect(self) self.sessionhandler.connect(self)
def enableRemote(self, option): def enableRemote(self, option):
""" """
This sets up the remote-activated options we allow for this protocol. This sets up the remote-activated options we allow for this protocol.
@ -69,7 +69,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
else: else:
return super(TelnetProtocol, self).disableLocal(option) return super(TelnetProtocol, self).disableLocal(option)
def connectionLost(self, reason): def connectionLost(self, reason):
""" """
This is executed when the connection is lost for This is executed when the connection is lost for
@ -86,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:
@ -103,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"
@ -131,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
@ -145,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
@ -163,6 +169,15 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
except Exception, e: except Exception, e:
self.sendLine(str(e)) self.sendLine(str(e))
return return
if "oob" in kwargs:
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob"))
if "MSDP" in self.protocol_flags:
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', {}) ttype = self.protocol_flags.get('TTYPE', {})
raw = kwargs.get("raw", False) raw = kwargs.get("raw", False)
nomarkup = not (ttype or ttype.get('256 COLORS') or ttype.get('ANSI') or not ttype.get("init_done")) nomarkup = not (ttype or ttype.get('256 COLORS') or ttype.get('ANSI') or not ttype.get("init_done"))

View file

@ -214,10 +214,14 @@ class Evennia(object):
[(o.typeclass, o.at_init()) for o in ObjectDB.get_all_cached_instances()] [(o.typeclass, o.at_init()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_init()) for p in PlayerDB.get_all_cached_instances()] [(p.typeclass, p.at_init()) for p in PlayerDB.get_all_cached_instances()]
with open(SERVER_RESTART, 'r') as f:
mode = f.read()
if mode in ('True', 'reload'):
from src.server.oobhandler import OOB_HANDLER
OOB_HANDLER.restore()
if SERVER_STARTSTOP_MODULE: if SERVER_STARTSTOP_MODULE:
# call correct server hook based on start file value # call correct server hook based on start file value
with open(SERVER_RESTART, 'r') as f:
mode = f.read()
if mode in ('True', 'reload'): if mode in ('True', 'reload'):
# True was the old reload flag, kept for compatibilty # True was the old reload flag, kept for compatibilty
SERVER_STARTSTOP_MODULE.at_server_reload_start() SERVER_STARTSTOP_MODULE.at_server_reload_start()
@ -280,6 +284,9 @@ class Evennia(object):
yield self.sessions.all_sessions_portal_sync() yield self.sessions.all_sessions_portal_sync()
ServerConfig.objects.conf("server_restart_mode", "reload") ServerConfig.objects.conf("server_restart_mode", "reload")
from src.server.oobhandler import OOB_HANDLER
OOB_HANDLER.save()
if SERVER_STARTSTOP_MODULE: if SERVER_STARTSTOP_MODULE:
SERVER_STARTSTOP_MODULE.at_server_reload_stop() SERVER_STARTSTOP_MODULE.at_server_reload_stop()

View file

@ -20,6 +20,7 @@ from src.server.session import Session
IDLE_COMMAND = settings.IDLE_COMMAND IDLE_COMMAND = settings.IDLE_COMMAND
_GA = object.__getattribute__ _GA = object.__getattribute__
_ObjectDB = None _ObjectDB = None
_OOB_HANDLER = None
# load optional out-of-band function module # load optional out-of-band function module
OOB_PLUGIN_MODULE = settings.OOB_PLUGIN_MODULE OOB_PLUGIN_MODULE = settings.OOB_PLUGIN_MODULE
@ -136,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.
@ -178,8 +187,14 @@ class ServerSession(Session):
cmdhandler.cmdhandler(self, text, callertype="session", sessid=self.sessid) cmdhandler.cmdhandler(self, text, callertype="session", sessid=self.sessid)
self.update_session_counters() self.update_session_counters()
if "oob" in kwargs: if "oob" in kwargs:
# relay to OOB handler # handle oob instructions
pass global _OOB_HANDLER
if not _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:
if funcname:
_OOB_HANDLER.execute_cmd(self, funcname, *args, **kwargs)
execute_cmd = data_in # alias execute_cmd = data_in # alias

View file

@ -27,6 +27,7 @@ _ServerSession = None
_ServerConfig = None _ServerConfig = None
_ScriptDB = None _ScriptDB = None
# AMP signals # AMP signals
PCONN = chr(1) # portal session connect PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect PDISCONN = chr(2) # portal session disconnect
@ -96,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
#------------------------------------------------------------ #------------------------------------------------------------
@ -357,6 +407,7 @@ class ServerSessionHandler(SessionHandler):
return self.sessions.get(sessid) return self.sessions.get(sessid)
return None return None
def announce_all(self, message): def announce_all(self, message):
""" """
Send message to all connected sessions Send message to all connected sessions
@ -379,5 +430,4 @@ class ServerSessionHandler(SessionHandler):
if session: if session:
session.data_in(text=text, **kwargs) session.data_in(text=text, **kwargs)
SESSIONS = ServerSessionHandler() SESSIONS = ServerSessionHandler()

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

@ -196,7 +196,7 @@ def pack_dbobj(item):
# build the internal representation as a tuple ("__packed_dbobj__", key, creation_time, id) # build the internal representation as a tuple ("__packed_dbobj__", key, creation_time, id)
return natural_key and ('__packed_dbobj__', natural_key, _TO_DATESTRING(obj), _GA(obj, "id")) or item return natural_key and ('__packed_dbobj__', natural_key, _TO_DATESTRING(obj), _GA(obj, "id")) or item
def _unpack_dbobj(item): def unpack_dbobj(item):
""" """
Check and convert internal representations back to Django database models. Check and convert internal representations back to Django database models.
The fact that item is a packed dbobj should be checked before this call. The fact that item is a packed dbobj should be checked before this call.
@ -209,7 +209,9 @@ def _unpack_dbobj(item):
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
# even if we got back a match, check the sanity of the date (some databases may 're-use' the id) # even if we got back a match, check the sanity of the date (some databases may 're-use' the id)
return _TO_DATESTRING(obj.dbobj) == item[2] and obj or None try: dbobj = obj.dbobj
except AttributeError: dbobj = obj
return _TO_DATESTRING(dbobj) == item[2] and obj or None
# #
# Access methods # Access methods
@ -267,7 +269,7 @@ def from_pickle(data, db_obj=None):
return item return item
elif _IS_PACKED_DBOBJ(item): elif _IS_PACKED_DBOBJ(item):
# this must be checked before tuple # this must be checked before tuple
return _unpack_dbobj(item) return unpack_dbobj(item)
elif dtype == tuple: elif dtype == tuple:
return tuple(process_item(val) for val in item) return tuple(process_item(val) for val in item)
elif dtype == dict: elif dtype == dict:
@ -289,7 +291,7 @@ def from_pickle(data, db_obj=None):
return item return item
elif _IS_PACKED_DBOBJ(item): elif _IS_PACKED_DBOBJ(item):
# this must be checked before tuple # this must be checked before tuple
return _unpack_dbobj(item) return unpack_dbobj(item)
elif dtype == tuple: elif dtype == tuple:
return tuple(process_tree(val) for val in item) return tuple(process_tree(val) for val in item)
elif dtype == list: elif dtype == list:

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):
""" """