2019-09-10 00:54:28 +00:00
|
|
|
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
|
|
# which should be included with this package. The terms are also available at
|
2019-09-10 00:54:28 +00:00
|
|
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
|
|
|
|
"""Very simple inter-object notification system.
|
|
|
|
|
|
|
|
This module is a brain-dead simple notification system involving a :class:`Broadcaster` and a
|
|
|
|
:class:`Listener`. A listener can only listen to one broadcaster. A broadcaster can have multiple
|
|
|
|
listeners. If the listener is connected, whenever the broadcaster calls :meth:`~Broadcaster.notify`,
|
|
|
|
the method with the same name as the broadcasted message is called on the listener.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from collections import defaultdict
|
2022-05-10 04:36:39 +00:00
|
|
|
from typing import Callable, DefaultDict, List
|
2019-09-10 00:54:28 +00:00
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
class Broadcaster:
|
2021-08-15 09:10:18 +00:00
|
|
|
"""Broadcasts messages that are received by all listeners."""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __init__(self):
|
|
|
|
self.listeners = set()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def add_listener(self, listener: "Listener") -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
self.listeners.add(listener)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def notify(self, msg: str) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
"""Notify all connected listeners of ``msg``.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
That means that each listeners will have their method with the same name as ``msg`` called.
|
|
|
|
"""
|
2020-01-01 02:16:27 +00:00
|
|
|
for listener in self.listeners.copy(): # listeners can change during iteration
|
|
|
|
if listener in self.listeners: # disconnected during notification
|
2019-09-10 00:54:28 +00:00
|
|
|
listener.dispatch(msg)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def remove_listener(self, listener: "Listener") -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
self.listeners.discard(listener)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
|
|
|
|
class Listener:
|
2021-08-15 09:10:18 +00:00
|
|
|
"""A listener is initialized with the broadcaster it's going to listen to. Initially, it is not connected."""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def __init__(self, broadcaster: Broadcaster) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
self.broadcaster = broadcaster
|
2022-05-10 04:36:39 +00:00
|
|
|
self._bound_notifications: DefaultDict[str, List[Callable]] = defaultdict(list)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def bind_messages(self, messages: str, func: Callable) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
"""Binds multiple message to the same function.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Often, we perform the same thing on multiple messages. Instead of having the same function
|
|
|
|
repeated again and agin in our class, we can use this method to bind multiple messages to
|
|
|
|
the same function.
|
|
|
|
"""
|
|
|
|
for message in messages:
|
|
|
|
self._bound_notifications[message].append(func)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def connect(self) -> None:
|
2021-08-15 09:10:18 +00:00
|
|
|
"""Connects the listener to its broadcaster."""
|
2019-09-10 00:54:28 +00:00
|
|
|
self.broadcaster.add_listener(self)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def disconnect(self) -> None:
|
2021-08-15 09:10:18 +00:00
|
|
|
"""Disconnects the listener from its broadcaster."""
|
2019-09-10 00:54:28 +00:00
|
|
|
self.broadcaster.remove_listener(self)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def dispatch(self, msg: str) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
if msg in self._bound_notifications:
|
|
|
|
for func in self._bound_notifications[msg]:
|
|
|
|
func()
|
|
|
|
if hasattr(self, msg):
|
|
|
|
method = getattr(self, msg)
|
|
|
|
method()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
|
|
|
|
class Repeater(Broadcaster, Listener):
|
|
|
|
REPEATED_NOTIFICATIONS = None
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def __init__(self, broadcaster: Broadcaster) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
Broadcaster.__init__(self)
|
|
|
|
Listener.__init__(self, broadcaster)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def _repeat_message(self, msg: str) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
if not self.REPEATED_NOTIFICATIONS or msg in self.REPEATED_NOTIFICATIONS:
|
|
|
|
self.notify(msg)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-10 04:36:39 +00:00
|
|
|
def dispatch(self, msg: str) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
Listener.dispatch(self, msg)
|
|
|
|
self._repeat_message(msg)
|