From 11dc2ee5610a6eca2ee9cf7dad40d0b5ee111d9f Mon Sep 17 00:00:00 2001 From: Andrew Bastien Date: Fri, 12 Apr 2019 20:16:30 -0400 Subject: [PATCH 1/2] Added some Signals. --- evennia/accounts/accounts.py | 11 ++++++++++- evennia/accounts/models.py | 3 +++ evennia/server/sessionhandler.py | 4 +++- evennia/utils/signals.py | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 evennia/utils/signals.py diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index f1cee5321e..610c3bc7e7 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -29,6 +29,8 @@ from evennia.utils import class_from_module, create, logger from evennia.utils.utils import (lazy_property, to_str, make_iter, is_iter, variable_from_module) +from evennia.utils.signals import (ACCOUNT_CREATE, OBJECT_PUPPET, + OBJECT_UNPUPPET, ACCOUNT_LOGOUT) from evennia.typeclasses.attributes import NickHandler from evennia.scripts.scripthandler import ScriptHandler from evennia.commands.cmdsethandler import CmdSetHandler @@ -51,6 +53,7 @@ _CONNECT_CHANNEL = None CREATION_THROTTLE = Throttle(limit=2, timeout=10 * 60) LOGIN_THROTTLE = Throttle(limit=5, timeout=5 * 60) + class AccountSessionHandler(object): """ Manages the session(s) attached to an account. @@ -227,6 +230,8 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): if not _SESSIONS: from evennia.server.sessionhandler import SESSIONS as _SESSIONS _SESSIONS.disconnect(session, reason) + if not self.sessions.all(): + ACCOUNT_LOGOUT.send(sender=self) # puppeting operations @@ -301,6 +306,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): # re-cache locks to make sure superuser bypass is updated obj.locks.cache_lock_bypass(obj) # final hook + OBJECT_PUPPET.send(sender=obj, account=self, session=session) obj.at_post_puppet() def unpuppet_object(self, session): @@ -323,6 +329,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): obj.sessions.remove(session) if not obj.sessions.count(): del obj.account + OBJECT_UNPUPPET.send(sender=obj, session=session, account=self) obj.at_post_unpuppet(self, session=session) # Just to be sure we're always clear. session.puppet = None @@ -736,7 +743,9 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): logger.log_trace() # Update the throttle to indicate a new account was created from this IP - if ip and not guest: CREATION_THROTTLE.update(ip, 'Too many accounts being created.') + if ip and not guest: + CREATION_THROTTLE.update(ip, 'Too many accounts being created.') + ACCOUNT_CREATE.send(sender=account) return account, errors def delete(self, *args, **kwargs): diff --git a/evennia/accounts/models.py b/evennia/accounts/models.py index 412740c875..cf865309a4 100644 --- a/evennia/accounts/models.py +++ b/evennia/accounts/models.py @@ -25,6 +25,7 @@ from django.utils.encoding import smart_str from evennia.accounts.manager import AccountDBManager from evennia.typeclasses.models import TypedObject from evennia.utils.utils import make_iter +from evennia.utils.signals import ACCOUNT_RENAME __all__ = ("AccountDB",) @@ -146,8 +147,10 @@ class AccountDB(TypedObject, AbstractUser): return self.username def __username_set(self, value): + old_name = self.username self.username = value self.save(update_fields=["username"]) + ACCOUNT_RENAME.send(self, old_name=old_name, new_name=value) def __username_del(self): del self.username diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 0a519a225f..cacd515014 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -21,6 +21,7 @@ from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.utils.logger import log_trace from evennia.utils.utils import (variable_from_module, is_iter, to_str, make_iter, delay, callables_from_module) +from evennia.utils.signals import ACCOUNT_LOGIN from evennia.utils.inlinefuncs import parse_inlinefunc from codecs import decode as codecs_decode @@ -483,7 +484,6 @@ class ServerSessionHandler(SessionHandler): faking login without any AMP being actually active. """ - if session.logged_in and not force: # don't log in a session that is already logged in. return @@ -518,6 +518,8 @@ class ServerSessionHandler(SessionHandler): operation=SLOGIN, sessiondata={"logged_in": True, "uid": session.uid}) + if nsess < 2: + ACCOUNT_LOGIN.send(sender=account, session=session) account.at_post_login(session=session) def disconnect(self, session, reason="", sync_portal=True): diff --git a/evennia/utils/signals.py b/evennia/utils/signals.py new file mode 100644 index 0000000000..09a5af5c7c --- /dev/null +++ b/evennia/utils/signals.py @@ -0,0 +1,15 @@ +from django.dispatch import Signal + + +ACCOUNT_CREATE = Signal(providing_args=['session', ]) + +ACCOUNT_RENAME = Signal(providing_args=['old_name', 'new_name']) + +ACCOUNT_LOGIN = Signal(providing_args=['session', ]) + +ACCOUNT_LOGOUT = Signal() + +OBJECT_PUPPET = Signal(providing_args=['session', 'account']) + +OBJECT_UNPUPPET = Signal(providing_args=['session', 'account']) + From 72faf039cfa585691918f7bc3aee56a486009e3c Mon Sep 17 00:00:00 2001 From: Andrew Bastien Date: Sat, 13 Apr 2019 18:05:15 -0400 Subject: [PATCH 2/2] Refactored signals with better names and more of them. Put them after hook calls. --- evennia/accounts/accounts.py | 12 +++---- evennia/accounts/models.py | 4 +-- evennia/server/sessionhandler.py | 16 +++++---- evennia/server/signals.py | 60 ++++++++++++++++++++++++++++++++ evennia/typeclasses/models.py | 2 ++ evennia/utils/signals.py | 15 -------- 6 files changed, 79 insertions(+), 30 deletions(-) create mode 100644 evennia/server/signals.py delete mode 100644 evennia/utils/signals.py diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 610c3bc7e7..019cc7ce01 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -29,8 +29,8 @@ from evennia.utils import class_from_module, create, logger from evennia.utils.utils import (lazy_property, to_str, make_iter, is_iter, variable_from_module) -from evennia.utils.signals import (ACCOUNT_CREATE, OBJECT_PUPPET, - OBJECT_UNPUPPET, ACCOUNT_LOGOUT) +from evennia.server.signals import (SIGNAL_ACCOUNT_POST_CREATE, SIGNAL_OBJECT_POST_PUPPET, + SIGNAL_OBJECT_POST_UNPUPPET) from evennia.typeclasses.attributes import NickHandler from evennia.scripts.scripthandler import ScriptHandler from evennia.commands.cmdsethandler import CmdSetHandler @@ -230,8 +230,6 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): if not _SESSIONS: from evennia.server.sessionhandler import SESSIONS as _SESSIONS _SESSIONS.disconnect(session, reason) - if not self.sessions.all(): - ACCOUNT_LOGOUT.send(sender=self) # puppeting operations @@ -306,8 +304,8 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): # re-cache locks to make sure superuser bypass is updated obj.locks.cache_lock_bypass(obj) # final hook - OBJECT_PUPPET.send(sender=obj, account=self, session=session) obj.at_post_puppet() + SIGNAL_OBJECT_POST_PUPPET.send(sender=obj, account=self, session=session) def unpuppet_object(self, session): """ @@ -329,8 +327,8 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): obj.sessions.remove(session) if not obj.sessions.count(): del obj.account - OBJECT_UNPUPPET.send(sender=obj, session=session, account=self) obj.at_post_unpuppet(self, session=session) + SIGNAL_OBJECT_POST_UNPUPPET.send(sender=obj, session=session, account=self) # Just to be sure we're always clear. session.puppet = None session.puid = None @@ -745,7 +743,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): # Update the throttle to indicate a new account was created from this IP if ip and not guest: CREATION_THROTTLE.update(ip, 'Too many accounts being created.') - ACCOUNT_CREATE.send(sender=account) + SIGNAL_ACCOUNT_POST_CREATE.send(sender=account, ip=ip) return account, errors def delete(self, *args, **kwargs): diff --git a/evennia/accounts/models.py b/evennia/accounts/models.py index cf865309a4..a0283e2aef 100644 --- a/evennia/accounts/models.py +++ b/evennia/accounts/models.py @@ -25,7 +25,7 @@ from django.utils.encoding import smart_str from evennia.accounts.manager import AccountDBManager from evennia.typeclasses.models import TypedObject from evennia.utils.utils import make_iter -from evennia.utils.signals import ACCOUNT_RENAME +from evennia.server.signals import SIGNAL_ACCOUNT_POST_RENAME __all__ = ("AccountDB",) @@ -150,7 +150,7 @@ class AccountDB(TypedObject, AbstractUser): old_name = self.username self.username = value self.save(update_fields=["username"]) - ACCOUNT_RENAME.send(self, old_name=old_name, new_name=value) + SIGNAL_ACCOUNT_POST_RENAME.send(self, old_name=old_name, new_name=value) def __username_del(self): del self.username diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index cacd515014..0a0e7def9a 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -20,13 +20,12 @@ from django.conf import settings from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.utils.logger import log_trace from evennia.utils.utils import (variable_from_module, is_iter, - to_str, make_iter, delay, callables_from_module) -from evennia.utils.signals import ACCOUNT_LOGIN + make_iter, delay, callables_from_module) +from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT +from evennia.server.signals import SIGNAL_ACCOUNT_POST_CONNECT, SIGNAL_ACCOUNT_POST_DISCONNECT from evennia.utils.inlinefuncs import parse_inlinefunc from codecs import decode as codecs_decode -import pickle - _INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED # delayed imports @@ -518,9 +517,10 @@ class ServerSessionHandler(SessionHandler): operation=SLOGIN, sessiondata={"logged_in": True, "uid": session.uid}) - if nsess < 2: - ACCOUNT_LOGIN.send(sender=account, session=session) account.at_post_login(session=session) + if nsess < 2: + SIGNAL_ACCOUNT_POST_LOGIN.send(sender=account, session=session) + SIGNAL_ACCOUNT_POST_CONNECT.send(sender=account, session=session) def disconnect(self, session, reason="", sync_portal=True): """ @@ -547,7 +547,11 @@ class ServerSessionHandler(SessionHandler): string = string.format(reason=sreason, account=session.account, address=session.address, nsessions=nsess) session.log(string) + if nsess == 0: + SIGNAL_ACCOUNT_POST_LOGOUT.send(sender=session.account, session=session) + session.at_disconnect(reason) + SIGNAL_ACCOUNT_POST_DISCONNECT.send(sender=session.account, session=session) sessid = session.sessid if sessid in self and not hasattr(self, "_disconnect_all"): del self[sessid] diff --git a/evennia/server/signals.py b/evennia/server/signals.py new file mode 100644 index 0000000000..d7d0d1fa56 --- /dev/null +++ b/evennia/server/signals.py @@ -0,0 +1,60 @@ +""" +This module brings Django Signals into Evennia. These are events that +can be subscribed to by importing a given Signal and using the +following code. + +THIS_SIGNAL.connect(callback, sender_object) + +When other code calls THIS_SIGNAL.send(sender, **kwargs), the callback +will be triggered. + +Callbacks must be in the following format: + +def my_callback(sender, **kwargs): + ... + +This is used on top of hooks to make certain features easier to +add to contribs without necessitating a full takeover of hooks +that may be in high demand. + +""" +from django.dispatch import Signal + +# The sender is the created Account. This is triggered at the very end of Account.create() +# after the Account is created. +SIGNAL_ACCOUNT_POST_CREATE = Signal(providing_args=['ip', ]) + +# The Sender is the renamed Account. This is triggered by the username setter in AccountDB. +SIGNAL_ACCOUNT_POST_RENAME = Signal(providing_args=['old_name', 'new_name']) + +# The Sender is the connecting Account. This is triggered when an Account connects cold; +# that is, it had no other sessions connected. +SIGNAL_ACCOUNT_POST_LOGIN = Signal(providing_args=['session', ]) + +# The Sender is the Account attempting to authenticate. This is triggered whenever a +# session tries to login to an Account but fails. +SIGNAL_ACCOUNT_POST_LOGIN_FAIL = Signal(providing_args=['session', ]) + +# The sender is the connecting Account. This is triggered whenever a session authenticates +# to an Account regardless of existing sessions. +SIGNAL_ACCOUNT_POST_CONNECT = Signal(providing_args=['session', ]) + +# The sender is the Account. This is triggered when an Account's final session disconnects. +SIGNAL_ACCOUNT_POST_LOGOUT = Signal(providing_args=['session', ]) + +# The sender is the disconnecting Account. This is triggered whenever a session disconnects +# from the account, regardless of how many it started with or remain. +SIGNAL_ACCOUNT_POST_DISCONNECT = Signal(providing_args=['session', ]) + +# The sender is the Object being puppeted. This is triggered after all puppeting hooks have +# been called. The Object has already been puppeted by this point. +SIGNAL_OBJECT_POST_PUPPET = Signal(providing_args=['session', 'account']) + +# The sender is the Object being released. This is triggered after all hooks are called. +# The Object is no longer puppeted by this point. +SIGNAL_OBJECT_POST_UNPUPPET = Signal(providing_args=['session', 'account']) + +# The sender is the Typed Object being released. This isn't necessarily an Object; +# it could be a script. It fires whenever the value of the Typed object's 'key' +# changes. Will need to use isinstance() or other filtering on things that use this. +SIGNAL_TYPED_OBJECT_POST_RENAME = Signal(providing_args=['old_key', 'new_key']) diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index cd8d9a03ed..25f3da4a37 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -42,6 +42,7 @@ from evennia.typeclasses.attributes import Attribute, AttributeHandler, NAttribu from evennia.typeclasses.tags import Tag, TagHandler, AliasHandler, PermissionHandler from evennia.utils.idmapper.models import SharedMemoryModel, SharedMemoryModelBase +from evennia.server.signals import SIGNAL_TYPED_OBJECT_POST_RENAME from evennia.typeclasses import managers from evennia.locks.lockhandler import LockHandler @@ -326,6 +327,7 @@ class TypedObject(SharedMemoryModel): self.db_key = value self.save(update_fields=["db_key"]) self.at_rename(oldname, value) + SIGNAL_TYPED_OBJECT_POST_RENAME.send(sender=self, old_key=oldname, new_key=value) # # diff --git a/evennia/utils/signals.py b/evennia/utils/signals.py deleted file mode 100644 index 09a5af5c7c..0000000000 --- a/evennia/utils/signals.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.dispatch import Signal - - -ACCOUNT_CREATE = Signal(providing_args=['session', ]) - -ACCOUNT_RENAME = Signal(providing_args=['old_name', 'new_name']) - -ACCOUNT_LOGIN = Signal(providing_args=['session', ]) - -ACCOUNT_LOGOUT = Signal() - -OBJECT_PUPPET = Signal(providing_args=['session', 'account']) - -OBJECT_UNPUPPET = Signal(providing_args=['session', 'account']) -