Updated the monitorhandler to allow persistence as well as a unique id-key.

This commit is contained in:
Griatch 2016-03-26 19:52:26 +01:00
parent d8bfb6d4a2
commit c093fd3880
2 changed files with 94 additions and 48 deletions

View file

@ -10,18 +10,19 @@ functionality:
an action whenever that Attribute *changes* for whatever reason. an action whenever that Attribute *changes* for whatever reason.
""" """
import inspect
from builtins import object from builtins import object
from collections import defaultdict from collections import defaultdict
from evennia.server.models import ServerConfig from evennia.server.models import ServerConfig
from evennia.utils.dbserialize import dbserialize, dbunserialize from evennia.utils.dbserialize import dbserialize, dbunserialize
from evennia.utils import logger from evennia.utils import logger
from evennia.utils import variable_from_module
_SA = object.__setattr__ _SA = object.__setattr__
_GA = object.__getattribute__ _GA = object.__getattribute__
_DA = object.__delattr__ _DA = object.__delattr__
class MonitorHandler(object): class MonitorHandler(object):
""" """
This is a resource singleton that allows for registering This is a resource singleton that allows for registering
@ -32,7 +33,55 @@ class MonitorHandler(object):
Initialize the handler. Initialize the handler.
""" """
self.savekey = "_monitorhandler_save" self.savekey = "_monitorhandler_save"
self.monitors = defaultdict(lambda:defaultdict(dict)) self.monitors = defaultdict(lambda: defaultdict(dict))
def save(self):
"""
Store our monitors to the database. This is called
by the server process.
Since dbserialize can't handle defaultdicts, we convert to an
intermediary save format ((obj,fieldname, idstring, callback, kwargs), ...)
"""
savedata = []
if self.monitors:
for obj in self.monitors:
for fieldname in self.monitors[obj]:
for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].iteritems():
path = "%s.%s" % (callback.__module__, callback.func_name)
savedata.append((obj, fieldname, idstring, path, persistent, kwargs))
savedata = dbserialize(savedata)
ServerConfig.objects.conf(key=self.savekey, value=savedata)
def restore(self, server_reload=True):
"""
Restore our monitors after a reload. This is called
by the server process.
Args:
server_reload (bool, optional): If this is False, it means
the server went through a cold reboot and all
non-persistent tickers must be killed.
"""
self.monitors = defaultdict(lambda: defaultdict(dict))
restored_monitors = ServerConfig.objects.conf(key=self.savekey)
if restored_monitors:
restored_monitors = dbunserialize(restored_monitors)
for (obj, fieldname, idstring, path, persistent, kwargs) in restored_monitors:
try:
if not persistent and not server_reload:
# this monitor will not be restarted
continue
modname, varname = path.rsplit(".", 1)
callback = variable_from_module(modname, varname)
if obj and hasattr(obj, fieldname):
self.monitors[obj][fieldname][idstring] = (callback, persistent, kwargs)
except Exception:
continue
# make sure to clean data from database
ServerConfig.objects.conf(key=self.savekey, delete=True)
def at_update(self, obj, fieldname): def at_update(self, obj, fieldname):
""" """
@ -40,18 +89,18 @@ class MonitorHandler(object):
""" """
to_delete = [] to_delete = []
if obj in self.monitors and fieldname in self.monitors[obj]: if obj in self.monitors and fieldname in self.monitors[obj]:
for (callback, kwargs) in self.monitors[obj][fieldname].iteritems(): for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].iteritems():
kwargs.update({"obj": obj, "fieldname": fieldname}) kwargs.update({"obj": obj, "fieldname": fieldname})
try: try:
callback( **kwargs) callback(**kwargs)
except Exception: except Exception:
to_delete.append((obj, fieldname, callback)) to_delete.append((obj, fieldname, idstring))
logger.log_trace("Monitor callback was removed.") logger.log_trace("Monitor callback was removed.")
# we need to do the cleanup after loop has finished # we cleanup non-found monitors (has to be done after loop)
for (obj, fieldname, callback) in to_delete: for (obj, fieldname, idstring) in to_delete:
del self.monitors[obj][fieldname][callback] del self.monitors[obj][fieldname][idstring]
def add(self, obj, fieldname, callback, **kwargs): def add(self, obj, fieldname, callback, idstring="", persistent=False, **kwargs):
""" """
Add monitoring to a given field or Attribute. A field must Add monitoring to a given field or Attribute. A field must
be specified with the full db_* name or it will be assumed be specified with the full db_* name or it will be assumed
@ -62,11 +111,11 @@ class MonitorHandler(object):
field or Attribute. field or Attribute.
fieldname (str): Name of field (db_*) or Attribute to monitor. fieldname (str): Name of field (db_*) or Attribute to monitor.
callback (callable): A callable on the form `callable(**kwargs), callback (callable): A callable on the form `callable(**kwargs),
where kwargs holds keys fieldname and obj. where kwargs holds keys fieldname and obj.
uid (hashable): A unique id to identify this particular monitor. idstring (str, optional): An id to separate this monitor from other monitors
It is used together with obj to of the same field and object.
persistent (bool): If this monitor should survive a server persistent (bool, optional): If False, the monitor will survive
reboot or not (it will always survive a reload). a server reload but not a cold restart. This is default.
""" """
if not fieldname.startswith("db_") or not hasattr(obj, fieldname): if not fieldname.startswith("db_") or not hasattr(obj, fieldname):
@ -78,51 +127,50 @@ class MonitorHandler(object):
# we try to serialize this data to test it's valid. Otherwise we won't accept it. # we try to serialize this data to test it's valid. Otherwise we won't accept it.
try: try:
dbserialize((obj, fieldname, callback, kwargs)) if not inspect.isfunction(callback):
raise TypeError("callback is not a function.")
dbserialize((obj, fieldname, callback, idstring, persistent, kwargs))
except Exception: except Exception:
err = "Invalid monitor definition (skipped since it could not be serialized):\n" \ err = "Invalid monitor definition: \n" \
" (%s, %s, %s, %s)" % (obj, fieldname, callback, kwargs) " (%s, %s, %s, %s, %s, %s)" % (obj, fieldname, callback, idstring,
persistent, kwargs)
logger.log_trace(err) logger.log_trace(err)
else: else:
self.monitors[obj][fieldname][callback] = kwargs self.monitors[obj][fieldname][idstring] = (callback, persistent, kwargs)
def remove(self, obj, fieldname, callback): def remove(self, obj, fieldname, idstring=""):
""" """
Remove a monitor. Remove a monitor.
""" """
callback_dict = self.monitors[obj][fieldname] if not fieldname.startswith("db_") or not hasattr(obj, fieldname):
if callback in callback_dict: obj = obj.attributes.get(fieldname, return_obj=True)
del callback_dict[callback] if not obj:
return
fieldname = "db_value"
def save(self): idstring_dict = self.monitors[obj][fieldname]
if idstring in idstring_dict:
del self.monitors[obj][fieldname][idstring]
def clear(self):
""" """
Store our monitors to the database. This is called Delete all monitors.
by the server process. """
self.monitors = defaultdict(lambda: defaultdict(dict))
Since dbserialize can't handle defaultdicts, we convert to an def all(self):
intermediary save format ((obj,fieldname, callback, kwargs), ...) """
List all monitors.
""" """
savedata = [] output = []
for obj in self.monitors: for obj in self.monitors:
for fieldname in self.monitors[obj]: for fieldname in self.monitors[obj]:
for callback in self.monitors[obj][fieldname]: for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].iteritems():
savedata.append((obj, fieldname, callback, self.monitors[obj][fieldname][callback])) output.append((obj, fieldname, idstring, persistent, kwargs))
savedata = dbserialize(savedata) return output
ServerConfig.objects.conf(key=self.savekey,
value=savedata)
def restore(self):
"""
Restore our monitors after a reload. This is called
by the server process.
"""
savedata = ServerConfig.objects.conf(key=self.savekey)
if savedata:
for (obj, fieldname, callback, kwargs) in dbunserialize(savedata):
self.monitors[obj][fieldname][callback] = kwargs
ServerConfig.objects.conf(key=self.savekey, delete=True)
# access object # access object
MONITOR_HANDLER = MonitorHandler() MONITOR_HANDLER = MonitorHandler()

View file

@ -311,9 +311,7 @@ class TickerHandler(object):
def _store_key(self, obj, path, interval, callfunc, idstring="", persistent=True): def _store_key(self, obj, path, interval, callfunc, idstring="", persistent=True):
""" """
Tries to create a store_key for the object. Returns a tuple Tries to create a store_key for the object.
(isdb, store_key) where isdb is a boolean True if obj was a
database object, False otherwise.
Args: Args:
obj (Object or None): Subscribing object if any. obj (Object or None): Subscribing object if any.
@ -373,8 +371,8 @@ class TickerHandler(object):
restart. restart.
Args: Args:
server_reload (bool): If this is False, it means the server server_reload (bool, optional): If this is False, it means
went through a cold reboot and all the server went through a cold reboot and all
non-persistent tickers must be killed. non-persistent tickers must be killed.
""" """