def monitor(session, *args, **kwargs): """ Adds monitoring to a given property or Attribute. Kwargs: name (str): The name of the property or Attribute to report. No db_* prefix is needed. Only names in the _monitorable dict earlier in this module are accepted. stop (bool): Stop monitoring the above name. """ from evennia.scripts.monitorhandler import MONITOR_HANDLER name = kwargs.get("name", None) if name and name in _monitorable and session.puppet: field_name = _monitorable[name] obj = session.puppet if kwargs.get("stop", False): MONITOR_HANDLER.remove(obj, field_name, idstring=session.sessid) else: # the handler will add fieldname and obj to the kwargs automatically MONITOR_HANDLER.add( obj, field_name, _on_monitor_change, idstring=session.sessid, persistent=False, name=name, session=session, )
def run_init_hooks(self): """ Called every server start """ from evennia.objects.models import ObjectDB #from evennia.players.models import PlayerDB #update eventual changed defaults self.update_defaults() [o.at_init() for o in ObjectDB.get_all_cached_instances()] [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 evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.restore() from evennia.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.restore(mode in ('True', 'reload')) # call correct server hook based on start file value if mode in ('True', 'reload'): # True was the old reload flag, kept for compatibilty self.at_server_reload_start() elif mode == 'reset': # only run hook, don't purge sessions self.at_server_cold_start() elif mode in ('reset', 'shutdown'): self.at_server_cold_start() # clear eventual lingering session storages ObjectDB.objects.clear_all_sessids() # always call this regardless of start type self.at_server_start()
def at_post_portal_sync(self, mode): """ This is called just after the portal has finished syncing back data to the server after reconnecting. Args: mode (str): One of reload, reset or shutdown. """ from evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.restore(mode == 'reload') from evennia.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.restore(mode == 'reload') # after sync is complete we force-validate all scripts # (this also starts any that didn't yet start) ScriptDB.objects.validate(init_mode=mode) # start the task handler from evennia.scripts.taskhandler import TASK_HANDLER TASK_HANDLER.load() TASK_HANDLER.create_delays() # delete the temporary setting ServerConfig.objects.conf("server_restart_mode", delete=True)
def at_post_portal_sync(self): """ This is called just after the portal has finished syncing back data to the server after reconnecting. """ # one of reload, reset or shutdown mode = self.getset_restart_mode() from evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.restore(mode == 'reload') from evennia.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.restore(mode == 'reload') # after sync is complete we force-validate all scripts # (this also starts any that didn't yet start) ScriptDB.objects.validate(init_mode=mode) # start the task handler from evennia.scripts.taskhandler import TASK_HANDLER TASK_HANDLER.load() TASK_HANDLER.create_delays() # delete the temporary setting ServerConfig.objects.conf("server_restart_mode", delete=True)
def monitor(session, *args, **kwargs): """ Adds monitoring to a given property or Attribute. Kwargs: name (str): The name of the property or Attribute to report. No db_* prefix is needed. Only names in the _monitorable dict earlier in this module are accepted. stop (bool): Stop monitoring the above name. """ from evennia.scripts.monitorhandler import MONITOR_HANDLER name = kwargs.get("name", None) if name and name in _monitorable and session.puppet: field_name = _monitorable[name] obj = session.puppet if kwargs.get("stop", False): MONITOR_HANDLER.remove(obj, field_name, idstring=session.sessid) else: # the handler will add fieldname and obj to the kwargs automatically MONITOR_HANDLER.add(obj, field_name, _on_monitor_change, idstring=session.sessid, persistent=False, name=name, session=session)
def at_post_portal_sync(self, mode): """ This is called just after the portal has finished syncing back data to the server after reconnecting. Args: mode (str): One of reload, reset or shutdown. """ from evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.restore(mode == "reload") from evennia.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.restore(mode == "reload") # after sync is complete we force-validate all scripts # (this also starts any that didn't yet start) ScriptDB.objects.validate(init_mode=mode) # start the task handler from evennia.scripts.taskhandler import TASK_HANDLER TASK_HANDLER.load() TASK_HANDLER.create_delays() # check so default channels exist from evennia.comms.models import ChannelDB from evennia.accounts.models import AccountDB from evennia.utils.create import create_channel god_account = AccountDB.objects.get(id=1) # mudinfo mudinfo_chan = settings.CHANNEL_MUDINFO if not mudinfo_chan: raise RuntimeError("settings.CHANNEL_MUDINFO must be defined.") if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]): channel = create_channel(**mudinfo_chan) channel.connect(god_account) # connectinfo connectinfo_chan = settings.CHANNEL_MUDINFO if connectinfo_chan: if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]): channel = create_channel(**connectinfo_chan) # default channels for chan_info in settings.DEFAULT_CHANNELS: if not ChannelDB.objects.filter(db_key=chan_info["key"]): channel = create_channel(**chan_info) channel.connect(god_account) # delete the temporary setting ServerConfig.objects.conf("server_restart_mode", delete=True)
def webclient_options(session, *args, **kwargs): """ Handles retrieving and changing of options related to the webclient. If kwargs is empty (or contains just a "cmdid"), the saved options will be sent back to the session. A monitor handler will be created to inform the client of any future options that changes. If kwargs is not empty, the key/values stored in there will be persisted to the account object. Keyword Args: <option name>: an option to save """ account = session.account clientoptions = account.db._saved_webclient_options if not clientoptions: # No saved options for this account, copy and save the default. account.db._saved_webclient_options = settings.WEBCLIENT_OPTIONS.copy() # Get the _SaverDict created by the database. clientoptions = account.db._saved_webclient_options # The webclient adds a cmdid to every kwargs, but we don't need it. try: del kwargs["cmdid"] except KeyError: pass if not kwargs: # No kwargs: we are getting the stored options # Convert clientoptions to regular dict for sending. session.msg(webclient_options=dict(clientoptions)) # Create a monitor. If a monitor already exists then it will replace # the previous one since it would use the same idstring from evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.add( account, "_saved_webclient_options", _on_webclient_options_change, idstring=session.sessid, persistent=False, session=session, ) else: # kwargs provided: persist them to the account object for key, value in kwargs.items(): clientoptions[key] = value
def save(self, *args, **kwargs): """ Central database save operation. Notes: Arguments as per Django documentation. Calls `self.at_<fieldname>_postsave(new)` (this is a wrapper set by oobhandler: self._oob_at_<fieldname>_postsave()) """ global _MONITOR_HANDLER if not _MONITOR_HANDLER: from evennia.scripts.monitorhandler import MONITOR_HANDLER as _MONITOR_HANDLER if _IS_SUBPROCESS: # we keep a store of objects modified in subprocesses so # we know to update their caches in the central process global PROC_MODIFIED_COUNT, PROC_MODIFIED_OBJS PROC_MODIFIED_COUNT += 1 PROC_MODIFIED_OBJS[PROC_MODIFIED_COUNT] = self if _IS_MAIN_THREAD: # in main thread - normal operation super(SharedMemoryModel, self).save(*args, **kwargs) else: # in another thread; make sure to save in reactor thread def _save_callback(cls, *args, **kwargs): super(SharedMemoryModel, cls).save(*args, **kwargs) callFromThread(_save_callback, self, *args, **kwargs) # update field-update hooks and eventual OOB watchers new = False if "update_fields" in kwargs and kwargs["update_fields"]: # get field objects from their names update_fields = (self._meta.get_field(fieldname) for fieldname in kwargs.get("update_fields")) else: # meta.fields are already field objects; get them all new = True update_fields = self._meta.fields for field in update_fields: fieldname = field.name # trigger eventual monitors _MONITOR_HANDLER.at_update(self, fieldname) # 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)(new)
def save(self, *args, **kwargs): """ Central database save operation. Notes: Arguments as per Django documentation. Calls `self.at_<fieldname>_postsave(new)` (this is a wrapper set by oobhandler: self._oob_at_<fieldname>_postsave()) """ global _MONITOR_HANDLER if not _MONITOR_HANDLER: from evennia.scripts.monitorhandler import MONITOR_HANDLER as _MONITORHANDLER if _IS_SUBPROCESS: # we keep a store of objects modified in subprocesses so # we know to update their caches in the central process global PROC_MODIFIED_COUNT, PROC_MODIFIED_OBJS PROC_MODIFIED_COUNT += 1 PROC_MODIFIED_OBJS[PROC_MODIFIED_COUNT] = self if _IS_MAIN_THREAD: # in main thread - normal operation super(SharedMemoryModel, self).save(*args, **kwargs) else: # in another thread; make sure to save in reactor thread def _save_callback(cls, *args, **kwargs): super(SharedMemoryModel, cls).save(*args, **kwargs) callFromThread(_save_callback, self, *args, **kwargs) # update field-update hooks and eventual OOB watchers new = False if "update_fields" in kwargs and kwargs["update_fields"]: # get field objects from their names update_fields = (self._meta.get_field(fieldname) for fieldname in kwargs.get("update_fields")) else: # meta.fields are already field objects; get them all new =True update_fields = self._meta.fields for field in update_fields: fieldname = field.name # trigger eventual monitors _MONITORHANDLER.at_update(self, fieldname) # 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)(new)
def msdp_list(session, *args, **kwargs): """ MSDP LIST command """ from evennia.scripts.monitorhandler import MONITOR_HANDLER args_lower = [arg.lower() for arg in args] if "commands" in args_lower: inputfuncs = [ key[5:] if key.startswith("msdp_") else key for key in session.sessionhandler.get_inputfuncs().keys() ] session.msg(commands=(inputfuncs, {})) if "lists" in args_lower: session.msg(lists=([ 'commands', 'lists', 'configurable_variables', 'reportable_variables', 'reported_variables', 'sendable_variables' ], {})) if "configurable_variables" in args_lower: session.msg(configurable_variables=(_CLIENT_OPTIONS, {})) if "reportable_variables" in args_lower: session.msg(reportable_variables=(_monitorable, {})) if "reported_variables" in args_lower: obj = session.puppet monitor_infos = MONITOR_HANDLER.all(obj=obj) fieldnames = [tup[1] for tup in monitor_infos] session.msg(reported_variables=(fieldnames, {})) if "sendable_variables" in args_lower: # no default sendable variables session.msg(sendable_variables=([], {}))
def webclient_options(session, *args, **kwargs): """ Handles retrieving and changing of options related to the webclient. If kwargs is empty (or contains just a "cmdid"), the saved options will be sent back to the session. A monitor handler will be created to inform the client of any future options that changes. If kwargs is not empty, the key/values stored in there will be persisted to the account object. Kwargs: <option name>: an option to save """ account = session.account clientoptions = account.db._saved_webclient_options if not clientoptions: # No saved options for this account, copy and save the default. account.db._saved_webclient_options = settings.WEBCLIENT_OPTIONS.copy() # Get the _SaverDict created by the database. clientoptions = account.db._saved_webclient_options # The webclient adds a cmdid to every kwargs, but we don't need it. try: del kwargs["cmdid"] except KeyError: pass if not kwargs: # No kwargs: we are getting the stored options # Convert clientoptions to regular dict for sending. session.msg(webclient_options=dict(clientoptions)) # Create a monitor. If a monitor already exists then it will replace # the previous one since it would use the same idstring from evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.add(account, "_saved_webclient_options", _on_webclient_options_change, idstring=session.sessid, persistent=False, session=session) else: # kwargs provided: persist them to the account object for key, value in kwargs.iteritems(): clientoptions[key] = value
def webclient_options(session, *args, **kwargs): """ Handles retrieving and changing of options related to the webclient. If kwargs is empty (or contains just a "cmdid"), the saved options will be sent back to the session. A monitor handler will be created to inform the client of any future options that changes. If kwargs is not empty, the key/values stored in there will be persisted to the player object. Kwargs: <option name>: an option to save """ player = session.player clientoptions = settings.WEBCLIENT_OPTIONS.copy() storedoptions = player.db._saved_webclient_options or {} clientoptions.update(storedoptions) # The webclient adds a cmdid to every kwargs, but we don't need it. try: del kwargs["cmdid"] except KeyError: pass if not kwargs: # No kwargs: we are getting the stored options session.msg(webclient_options=clientoptions) # Create a monitor. If a monitor already exists then it will replace # the previous one since it would use the same idstring from evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.add(player, "_saved_webclient_options", _on_webclient_options_change, idstring=session.sessid, persistent=False, session=session) else: # kwargs provided: persist them to the player object for key, value in kwargs.iteritems(): clientoptions[key] = value player.db._saved_webclient_options = clientoptions
def monitored(session, *args, **kwargs): """ Report on what is being monitored """ from evennia.scripts.monitorhandler import MONITOR_HANDLER obj = session.puppet monitors = MONITOR_HANDLER.all(obj=obj) session.msg(monitored=(monitors, {}))
def at_disconnect(self, reason=None): """ Hook called by sessionhandler when disconnecting this session. """ if self.logged_in: account = self.account if self.puppet: account.unpuppet_object(self) uaccount = account uaccount.last_login = timezone.now() uaccount.save() # calling account hook account.at_disconnect(reason) self.logged_in = False if not self.sessionhandler.sessions_from_account(account): # no more sessions connected to this account account.is_connected = False # this may be used to e.g. delete account after disconnection etc account.at_post_disconnect() # remove any webclient settings monitors associated with this # session MONITOR_HANDLER.remove(account, "_saved_webclient_options", self.sessid)
def at_disconnect(self): """ Hook called by sessionhandler when disconnecting this session. """ if self.logged_in: player = self.player if self.puppet: player.unpuppet_object(self) uaccount = player uaccount.last_login = timezone.now() uaccount.save() # calling player hook player.at_disconnect() self.logged_in = False if not self.sessionhandler.sessions_from_player(player): # no more sessions connected to this player player.is_connected = False # this may be used to e.g. delete player after disconnection etc player.at_post_disconnect() # remove any webclient settings monitors associated with this # session MONITOR_HANDLER.remove(player, "_saved_webclient_options", self.sessid)
def monitor(session, *args, **kwargs): """ Adds monitoring to a given property or Attribute. Keyword Args: name (str): The name of the property or Attribute to report. No db_* prefix is needed. Only names in the _monitorable dict earlier in this module are accepted. stop (bool): Stop monitoring the above name. outputfunc_name (str, optional): Change the name of the outputfunc name. This is used e.g. by MSDP which has its own specific output format. """ from evennia.scripts.monitorhandler import MONITOR_HANDLER name = kwargs.get("name", None) outputfunc_name = kwargs("outputfunc_name", "monitor") if name and name in _monitorable and session.puppet: field_name = _monitorable[name] obj = session.puppet if kwargs.get("stop", False): MONITOR_HANDLER.remove(obj, field_name, idstring=session.sessid) else: # the handler will add fieldname and obj to the kwargs automatically MONITOR_HANDLER.add( obj, field_name, _on_monitor_change, idstring=session.sessid, persistent=False, name=name, session=session, outputfunc_name=outputfunc_name, )
def shutdown(self, mode=None, _reactor_stopping=False): """ Shuts down the server from inside it. mode - sets the server restart mode. 'reload' - server restarts, no "persistent" scripts are stopped, at_reload hooks called. 'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called but sessions will not be disconnected. 'shutdown' - like reset, but server will not auto-restart. None - keep currently set flag from flag file. _reactor_stopping - this is set if server is stopped by a kill command OR this method was already called once - in both cases the reactor is dead/stopping already. """ if _reactor_stopping and hasattr(self, "shutdown_complete"): # this means we have already passed through this method # once; we don't need to run the shutdown procedure again. defer.returnValue(None) mode = self.set_restart_mode(mode) from evennia.objects.models import ObjectDB #from evennia.players.models import PlayerDB from evennia.server.models import ServerConfig if mode == 'reload': # call restart hooks ServerConfig.objects.conf("server_restart_mode", "reload") yield [o.at_server_reload() for o in ObjectDB.get_all_cached_instances()] yield [p.at_server_reload() for p in PlayerDB.get_all_cached_instances()] yield [(s.pause(manual_pause=False), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances() if s.is_active] yield self.sessions.all_sessions_portal_sync() self.at_server_reload_stop() # only save monitor state on reload, not on shutdown/reset from evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.save() else: if mode == 'reset': # like shutdown but don't unset the is_connected flag and don't disconnect sessions yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()] yield [p.at_server_shutdown() for p in PlayerDB.get_all_cached_instances()] if self.amp_protocol: yield self.sessions.all_sessions_portal_sync() else: # shutdown yield [_SA(p, "is_connected", False) for p in PlayerDB.get_all_cached_instances()] yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()] yield [(p.unpuppet_all(), p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()] yield ObjectDB.objects.clear_all_sessids() yield [(s.pause(manual_pause=False), s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()] ServerConfig.objects.conf("server_restart_mode", "reset") self.at_server_cold_stop() # tickerhandler state should always be saved. from evennia.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.save() # always called, also for a reload self.at_server_stop() # if _reactor_stopping is true, reactor does not need to # be stopped again. if os.name == 'nt' and os.path.exists(SERVER_PIDFILE): # for Windows we need to remove pid files manually os.remove(SERVER_PIDFILE) if not _reactor_stopping: # this will also send a reactor.stop signal, so we set a # flag to avoid loops. self.shutdown_complete = True # kill the server reactor.callLater(0, reactor.stop)
def save(self, *args, **kwargs): """ Central database save operation. Notes: Arguments as per Django documentation. Calls `self.at_<fieldname>_postsave(new)` (this is a wrapper set by oobhandler: self._oob_at_<fieldname>_postsave()) """ global _MONITOR_HANDLER if not _MONITOR_HANDLER: from evennia.scripts.monitorhandler import MONITOR_HANDLER as _MONITOR_HANDLER if _IS_SUBPROCESS: # we keep a store of objects modified in subprocesses so # we know to update their caches in the central process global PROC_MODIFIED_COUNT, PROC_MODIFIED_OBJS PROC_MODIFIED_COUNT += 1 PROC_MODIFIED_OBJS[PROC_MODIFIED_COUNT] = self if _IS_MAIN_THREAD: # in main thread - normal operation try: super().save(*args, **kwargs) except DatabaseError: # we handle the 'update_fields did not update any rows' error that # may happen due to timing issues with attributes ufields_removed = kwargs.pop('update_fields', None) if ufields_removed: super().save(*args, **kwargs) else: raise else: # in another thread; make sure to save in reactor thread def _save_callback(cls, *args, **kwargs): super().save(*args, **kwargs) callFromThread(_save_callback, self, *args, **kwargs) if not self.pk: # this can happen if some of the startup methods immediately # delete the object (an example are Scripts that start and die immediately) return # update field-update hooks and eventual OOB watchers new = False if "update_fields" in kwargs and kwargs["update_fields"]: # get field objects from their names update_fields = (self._meta.get_field(fieldname) for fieldname in kwargs.get("update_fields")) else: # meta.fields are already field objects; get them all new = True update_fields = self._meta.fields for field in update_fields: fieldname = field.name # trigger eventual monitors _MONITOR_HANDLER.at_update(self, fieldname) # 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)(new) # # if a trackerhandler is set on this object, update it with the # # fieldname and the new value # fieldtracker = "_oob_at_%s_postsave" % fieldname # if hasattr(self, fieldtracker): # _GA(self, fieldtracker)(fieldname) pass
def shutdown(self, mode="reload", _reactor_stopping=False): """ Shuts down the server from inside it. mode - sets the server restart mode. 'reload' - server restarts, no "persistent" scripts are stopped, at_reload hooks called. 'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called but sessions will not be disconnected. 'shutdown' - like reset, but server will not auto-restart. _reactor_stopping - this is set if server is stopped by a kill command OR this method was already called once - in both cases the reactor is dead/stopping already. """ if _reactor_stopping and hasattr(self, "shutdown_complete"): # this means we have already passed through this method # once; we don't need to run the shutdown procedure again. defer.returnValue(None) from evennia.objects.models import ObjectDB from evennia.server.models import ServerConfig from evennia.utils import gametime as _GAMETIME_MODULE if mode == "reload": # call restart hooks ServerConfig.objects.conf("server_restart_mode", "reload") yield [ o.at_server_reload() for o in ObjectDB.get_all_cached_instances() ] yield [ p.at_server_reload() for p in AccountDB.get_all_cached_instances() ] yield [ (s.pause(manual_pause=False), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances() if s.id and (s.is_active or s.attributes.has("_manual_pause")) ] yield self.sessions.all_sessions_portal_sync() self.at_server_reload_stop() # only save monitor state on reload, not on shutdown/reset from evennia.scripts.monitorhandler import MONITOR_HANDLER MONITOR_HANDLER.save() else: if mode == "reset": # like shutdown but don't unset the is_connected flag and don't disconnect sessions yield [ o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances() ] yield [ p.at_server_shutdown() for p in AccountDB.get_all_cached_instances() ] if self.amp_protocol: yield self.sessions.all_sessions_portal_sync() else: # shutdown yield [ _SA(p, "is_connected", False) for p in AccountDB.get_all_cached_instances() ] yield [ o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances() ] yield [(p.unpuppet_all(), p.at_server_shutdown()) for p in AccountDB.get_all_cached_instances()] yield ObjectDB.objects.clear_all_sessids() yield [( s.pause(manual_pause=s.attributes.get("_manual_pause", False)), s.at_server_shutdown(), ) for s in ScriptDB.get_all_cached_instances()] ServerConfig.objects.conf("server_restart_mode", "reset") self.at_server_cold_stop() # tickerhandler state should always be saved. from evennia.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.save() # always called, also for a reload self.at_server_stop() if hasattr(self, "web_root"): # not set very first start yield self.web_root.empty_threadpool() if not _reactor_stopping: # kill the server self.shutdown_complete = True reactor.callLater(1, reactor.stop) # we make sure the proper gametime is saved as late as possible ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime())