Continued work on reworking the oobhandler.
This commit is contained in:
parent
eda15ccc45
commit
03b4b9ddb4
2 changed files with 100 additions and 146 deletions
|
|
@ -69,128 +69,99 @@ if not _OOB_ERROR:
|
||||||
# but automatically through the OOBHandler.
|
# but automatically through the OOBHandler.
|
||||||
#
|
#
|
||||||
|
|
||||||
class TrackerHandler(object):
|
class FieldTracker(object):
|
||||||
"""
|
"""
|
||||||
This object is dynamically assigned to objects whenever one of its fields
|
This object should be stored on the
|
||||||
are to be tracked. It holds an internal dictionary mapping to the fields
|
tracked object as "_oob_at_<fieldname>_update".
|
||||||
on that object. Each field can be tracked by any number of trackers (each
|
the update() method will be called by the
|
||||||
tied to a different callback).
|
save mechanism, which in turn will call the
|
||||||
|
user-customizable func()
|
||||||
"""
|
"""
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
"""
|
"""
|
||||||
This is initiated and stored on the object as a
|
This initializes the tracker with the object it sits on.
|
||||||
property _trackerhandler.
|
|
||||||
"""
|
"""
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.ntrackers = 0
|
self.subscribers = {}
|
||||||
# initiate store only with valid on-object fieldnames
|
|
||||||
self.tracktargets = dict((key, {})
|
|
||||||
for key in _GA(_GA(self.obj, "_meta"), "get_all_field_names")())
|
|
||||||
|
|
||||||
def add(self, fieldname, tracker):
|
def add(self, session):
|
||||||
"""
|
"""
|
||||||
Add tracker to the handler. Raises KeyError if fieldname
|
Add a subscribing session to the tracker
|
||||||
does not exist.
|
|
||||||
"""
|
"""
|
||||||
trackerkey = tracker.__class__.__name__
|
self.subscribers[session.sessid] = session
|
||||||
self.tracktargets[fieldname][trackerkey] = tracker
|
|
||||||
self.ntrackers += 1
|
|
||||||
|
|
||||||
def remove(self, fieldname, trackerclass, *args, **kwargs):
|
def remove(self, session):
|
||||||
"""
|
"""
|
||||||
Remove identified tracker from TrackerHandler.
|
Remove a subsribing session from the tracker
|
||||||
Raises KeyError if tracker is not found.
|
|
||||||
"""
|
"""
|
||||||
trackerkey = trackerclass.__name__
|
self.subscribers.pop(session.sessid)
|
||||||
tracker = self.tracktargets[fieldname][trackerkey]
|
|
||||||
try:
|
|
||||||
tracker.at_remove(*args, **kwargs)
|
|
||||||
except Exception:
|
|
||||||
logger.log_trace()
|
|
||||||
del self.tracktargets[fieldname][trackerkey]
|
|
||||||
self.ntrackers -= 1
|
|
||||||
if self.ntrackers <= 0:
|
|
||||||
# if there are no more trackers, clean this handler
|
|
||||||
del self
|
|
||||||
|
|
||||||
def update(self, fieldname, new_value):
|
def trigger_update(self, fieldname, new_value):
|
||||||
"""
|
"""
|
||||||
Called by the field when it updates to a new value
|
Called by the save() mechanism when the given
|
||||||
|
field has updated.
|
||||||
"""
|
"""
|
||||||
for tracker in self.tracktargets[fieldname].values():
|
for session in self.subscribers.values():
|
||||||
try:
|
try:
|
||||||
tracker.update(new_value)
|
self.at_field_update(session, fieldname, new_value)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
pass
|
||||||
|
|
||||||
|
def at_field_update(self, session, fieldname, new_value):
|
||||||
|
"""
|
||||||
|
This needs to be overloaded for each tracking
|
||||||
|
command.
|
||||||
|
|
||||||
# On-object Trackers to load with TrackerHandler
|
Args:
|
||||||
|
session (Session): the session subscribing
|
||||||
class TrackerBase(object):
|
to this update.
|
||||||
"""
|
fieldname (str): the name of the updated field.
|
||||||
Base class for OOB Tracker objects. Inherit from this
|
value (any): the new value now in this field.
|
||||||
to define custom trackers.
|
"""
|
||||||
"""
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update(self, *args, **kwargs):
|
|
||||||
"Called by tracked objects"
|
|
||||||
pass
|
|
||||||
|
|
||||||
def at_remove(self, *args, **kwargs):
|
|
||||||
"Called when tracker is removed"
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ReportFieldTracker(TrackerBase):
|
class ReportFieldTracker(FieldTracker):
|
||||||
"""
|
"""
|
||||||
Tracker that passively sends data to a stored sessid whenever
|
Tracker that passively sends data to a stored sessid whenever
|
||||||
a named database field changes. The TrackerHandler calls this with
|
a named database field changes. The TrackerHandler calls this with
|
||||||
the correct arguments.
|
the correct arguments.
|
||||||
"""
|
"""
|
||||||
def __init__(self, oobhandler, fieldname, sessid, *args, **kwargs):
|
def at_field_update(self, session, fieldname, new_value):
|
||||||
"""
|
"""
|
||||||
name - name of entity to track, such as "db_key"
|
Called when field updates.
|
||||||
sessid - sessid of session to report to
|
|
||||||
"""
|
"""
|
||||||
self.oobhandler = oobhandler
|
# we must never relay objects across to the Portal, only
|
||||||
self.fieldname = fieldname
|
# text.
|
||||||
self.sessid = sessid
|
|
||||||
|
|
||||||
def update(self, new_value, *args, **kwargs):
|
|
||||||
"Called by cache when updating the tracked entitiy"
|
|
||||||
# use oobhandler to relay data
|
|
||||||
try:
|
try:
|
||||||
# we must never relay objects across the amp, only text data.
|
# it may be an object
|
||||||
new_value = new_value.key
|
new_value = new_value.key
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
# ... or not
|
||||||
new_value = to_str(new_value, force_string=True)
|
new_value = to_str(new_value, force_string=True)
|
||||||
kwargs[self.fieldname] = new_value
|
# return as an OOB call of type "report"
|
||||||
# this is a wrapper call for sending oob data back to session
|
session.msg(oob=("report", {fieldname:new_value}))
|
||||||
self.oobhandler.msg(self.sessid, "report", *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ReportAttributeTracker(TrackerBase):
|
class ReportAttributeTracker(FieldTracker):
|
||||||
"""
|
"""
|
||||||
Tracker that passively sends data to a stored sessid whenever
|
Tracker that passively sends data to a stored sessid whenever
|
||||||
the Attribute updates. Since the field here is always "db_key",
|
the Attribute updates. Since the Attribute's field is always
|
||||||
we instead store the name of the attribute to return.
|
db_value, we return the attribute's name instead.
|
||||||
"""
|
"""
|
||||||
def __init__(self, oobhandler, fieldname, sessid, attrname, *args, **kwargs):
|
def at_field_update(self, session, fieldname, new_value):
|
||||||
"""
|
"""
|
||||||
attrname - name of attribute to track
|
Called when field updates.
|
||||||
sessid - sessid of session to report to
|
|
||||||
"""
|
"""
|
||||||
self.oobhandler = oobhandler
|
# we must never relay objects across to the Portal, only
|
||||||
self.attrname = attrname
|
# text.
|
||||||
self.sessid = sessid
|
try:
|
||||||
|
# it may be an object
|
||||||
def update(self, new_value, *args, **kwargs):
|
new_value = new_value.key
|
||||||
"Called by cache when attribute's db_value field updates"
|
except AttributeError:
|
||||||
kwargs[self.attrname] = new_value
|
# ... or not
|
||||||
# this is a wrapper call for sending oob data back to session
|
new_value = to_str(new_value, force_string=True)
|
||||||
self.oobhandler.msg(self.sessid, "report", *args, **kwargs)
|
session.msg(oob=("report", {obj.db_key: new_value})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -222,46 +193,39 @@ class OOBTickerHandler(TickerHandler):
|
||||||
|
|
||||||
# Main OOB Handler
|
# Main OOB Handler
|
||||||
|
|
||||||
class OOBHandler(object):
|
class OOBHandler(TickerHandler):
|
||||||
"""
|
|
||||||
The OOBHandler maintains all dynamic on-object oob hooks. It will store the
|
|
||||||
creation instructions and and re-apply them at a server reload (but
|
|
||||||
not after a server shutdown)
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
"""
|
|
||||||
Initialize handler
|
|
||||||
"""
|
|
||||||
self.sessionhandler = SESSIONS
|
|
||||||
self.oob_tracker_storage = {}
|
|
||||||
self.tickerhandler = OOBTickerHandler("oob_ticker_storage")
|
|
||||||
|
|
||||||
def save(self):
|
class AtTick(object):
|
||||||
"""
|
"""
|
||||||
Save the command_storage as a serialized string into a temporary
|
A wrapper object with a hook to call at regular intervals
|
||||||
ServerConf field
|
|
||||||
"""
|
"""
|
||||||
if self.oob_tracker_storage:
|
global SESSIONS, _OOB_FUNCS
|
||||||
#print "saved tracker_storage:", self.oob_tracker_storage
|
|
||||||
ServerConfig.objects.conf(key="oob_tracker_storage",
|
|
||||||
value=dbserialize(self.oob_tracker_storage))
|
|
||||||
self.tickerhandler.save()
|
|
||||||
|
|
||||||
def restore(self):
|
def at_tick(self, oobhandler, cmdname, sessid, *args, **kwargs):
|
||||||
|
"Called at regular intervals. Calls the oob function"
|
||||||
|
session = SESSIONS.session_from_sessid(sessid):
|
||||||
|
cmd = _OOB_FUNCS.get(cmdname, None)
|
||||||
|
try:
|
||||||
|
cmd(oobhandler, session, *args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
logger.log_trace()
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.save_name = "oob_ticker_storage"
|
||||||
|
super(OOBHandler, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def set_repeat(self, obj, sessid, oobfunc, interval=20, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Restore the command_storage from database and re-initialize the handler from storage.. This is
|
Set an oob function to be repeatedly called.
|
||||||
only triggered after a server reload, not after a shutdown-restart
|
|
||||||
|
Args:
|
||||||
|
obj (Object) - the object registering the repeat
|
||||||
|
sessid (int) - session id of the session registering
|
||||||
|
oobfunc (str) - oob function name to call every interval seconds
|
||||||
|
interval (int) - interval to call oobfunc, in seconds
|
||||||
|
*args, **kwargs - are used as arguments to the oobfunc
|
||||||
"""
|
"""
|
||||||
# load stored command instructions and use them to re-initialize handler
|
|
||||||
tracker_storage = ServerConfig.objects.conf(key="oob_tracker_storage")
|
|
||||||
if tracker_storage:
|
|
||||||
self.oob_tracker_storage = dbunserialize(tracker_storage)
|
|
||||||
for (obj, sessid, fieldname, trackerclass, args, kwargs) in self.oob_tracker_storage.values():
|
|
||||||
#print "restoring tracking:",obj, sessid, fieldname, trackerclass
|
|
||||||
self._track(unpack_dbobj(obj), sessid, fieldname, trackerclass, *args, **kwargs)
|
|
||||||
# make sure to purge the storage
|
|
||||||
ServerConfig.objects.conf(key="oob_tracker_storage", delete=True)
|
|
||||||
self.tickerhandler.restore()
|
|
||||||
|
|
||||||
def _track(self, obj, sessid, propname, trackerclass, *args, **kwargs):
|
def _track(self, obj, sessid, propname, trackerclass, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -272,7 +236,7 @@ class OOBHandler(object):
|
||||||
named as propname, this will be used as the property name when assigning
|
named as propname, this will be used as the property name when assigning
|
||||||
the OOB to obj, otherwise tracker_key is used as the property name.
|
the OOB to obj, otherwise tracker_key is used as the property name.
|
||||||
"""
|
"""
|
||||||
if not "_trackerhandler" in _GA(obj, "__dict__"):
|
if not hasattr(obj, "_trackerhandler"):
|
||||||
# assign trackerhandler to object
|
# assign trackerhandler to object
|
||||||
_SA(obj, "_trackerhandler", TrackerHandler(obj))
|
_SA(obj, "_trackerhandler", TrackerHandler(obj))
|
||||||
# initialize object
|
# initialize object
|
||||||
|
|
|
||||||
|
|
@ -27,35 +27,6 @@ _GA = object.__getattribute__
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
_DA = object.__delattr__
|
_DA = object.__delattr__
|
||||||
|
|
||||||
def at_field_post_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
|
|
||||||
"""
|
|
||||||
Called at the beginning of the field save operation. The save method
|
|
||||||
must be called with the update_fields keyword in order to be most efficient.
|
|
||||||
This method should NOT save; rather it is the save() that triggers this
|
|
||||||
function. Its main purpose is to allow to plug-in a save handler and oob
|
|
||||||
handlers.
|
|
||||||
"""
|
|
||||||
if raw:
|
|
||||||
return
|
|
||||||
if update_fields:
|
|
||||||
# this is a list of strings at this point. We want field objects
|
|
||||||
update_fields = (_GA(_GA(instance, "_meta"), "get_field_by_name")(field)[0] for field in update_fields)
|
|
||||||
else:
|
|
||||||
# meta.fields are already field objects; get them all
|
|
||||||
update_fields = _GA(_GA(instance, "_meta"), "fields")
|
|
||||||
for field in update_fields:
|
|
||||||
fieldname = field.name
|
|
||||||
handlername = "_at_%s_postsave" % fieldname
|
|
||||||
handler = _GA(instance, handlername) if handlername in _GA(sender, '__dict__') else None
|
|
||||||
if callable(handler):
|
|
||||||
handler()
|
|
||||||
trackerhandler = _GA(instance, "_trackerhandler") if "_trackerhandler" in _GA(instance, '__dict__') else None
|
|
||||||
if trackerhandler:
|
|
||||||
trackerhandler.update(fieldname, _GA(instance, fieldname))
|
|
||||||
|
|
||||||
# connect the signal to catch field changes
|
|
||||||
post_save.connect(at_post_field_save, dispatch_uid="at_post_field_save")
|
|
||||||
|
|
||||||
|
|
||||||
# References to db-updated objects are stored here so the
|
# References to db-updated objects are stored here so the
|
||||||
# main process can be informed to re-cache itself.
|
# main process can be informed to re-cache itself.
|
||||||
|
|
@ -362,6 +333,25 @@ class SharedMemoryModel(Model):
|
||||||
super(SharedMemoryModel, cls).save(*args, **kwargs)
|
super(SharedMemoryModel, cls).save(*args, **kwargs)
|
||||||
callFromThread(_save_callback, self, *args, **kwargs)
|
callFromThread(_save_callback, self, *args, **kwargs)
|
||||||
|
|
||||||
|
# update field-update hooks and eventual OOB watchers
|
||||||
|
if "update_fields" in kwargs:
|
||||||
|
# get field objects from their names
|
||||||
|
update_fields = (self._meta.get_field_by_name(field)[0] for field in kwargs["update_fields"])
|
||||||
|
else:
|
||||||
|
# meta.fields are already field objects; get them all
|
||||||
|
update_fields = self._meta.fields
|
||||||
|
for field in update_fields:
|
||||||
|
fieldname = field.name
|
||||||
|
# if a hook is defined it must be named exactly on this form
|
||||||
|
hookname = "_at_%s_postsave" % fieldname
|
||||||
|
if hasattr(self, hookname) and callable(_GA(self, hookname)):
|
||||||
|
_GA(self, hookname)()
|
||||||
|
# if a trackerhandler is set on this object, update it with the
|
||||||
|
# fieldname and the new value
|
||||||
|
trackername = "_oob_at_%s_postsave" % fieldname
|
||||||
|
if hasattr(self, trackername):
|
||||||
|
_GA(self, trackername).trigger_update(fieldname, _GA(self, fieldname))
|
||||||
|
|
||||||
|
|
||||||
class WeakSharedMemoryModelBase(SharedMemoryModelBase):
|
class WeakSharedMemoryModelBase(SharedMemoryModelBase):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue