diff --git a/houdini/__init__.py b/houdini/__init__.py index 7db50d4..0d7e380 100644 --- a/houdini/__init__.py +++ b/houdini/__init__.py @@ -5,7 +5,8 @@ from abc import abstractmethod import asyncio import enum import logging -import copy +import importlib +import pkgutil class StatusField(enum.IntEnum): @@ -19,7 +20,7 @@ class StatusField(enum.IntEnum): HasWalkedPuffleFirstTime = 65536 HasWalkedPuffleSecondTime = 131072 - + package_modules = package_modules + sub_package_modules class ConflictResolution(enum.Enum): Silent = 0 Append = 1 @@ -45,24 +46,15 @@ class _AbstractManager(dict): self.server = server self.logger = logging.getLogger('houdini') - self.__backup = None super().__init__() @abstractmethod - def load(self, module): - """Loads entries from module""" + async def setup(self, module): + """Setup manager class""" @abstractmethod - def remove(self, module): - """Removes all entries by module""" - - def backup(self): - self.__backup = copy.copy(self) - - def restore(self): - if self.__backup is not None: - self.update(self.__backup) - self.__backup = None + async def load(self, module): + """Loads entries from module""" class PenguinStringCompiler(OrderedDict): diff --git a/houdini/commands.py b/houdini/commands.py index 6d65752..5718ea0 100644 --- a/houdini/commands.py +++ b/houdini/commands.py @@ -64,6 +64,7 @@ player_in_room = handlers.player_in_room class CommandManager(_AbstractManager): def load(self, module): + async def load(self, module): command_objects = inspect.getmembers(module, is_command) if not isinstance(module, plugins.IPlugin): raise TypeError('Commands can only be loaded from plugins') @@ -94,12 +95,6 @@ class CommandManager(_AbstractManager): else: parent_commands[name] = [command_object] - def remove(self, module): - for command_name, command_handlers in self.items(): - for command_handler in command_handlers: - if module.__name__ == command_handler.callback.__module__: - command_handlers.remove(command_handler) - def is_command(command_object): return issubclass(type(command_object), _Command) diff --git a/houdini/events/__init__.py b/houdini/events/__init__.py deleted file mode 100644 index 33c3bee..0000000 --- a/houdini/events/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -import os - - -def evaluate_listener_file_event(listener_file_event): - # Ignore all directory events - if listener_file_event.is_directory: - return False - - listener_module_path = listener_file_event.src_path[2:] - - # Ignore non-Python files - if listener_module_path[-3:] != ".py": - return False - - # Ignore package index files - if '__init__.py' in listener_module_path: - return False - - listener_module = listener_module_path.replace(os.path.sep, ".")[:-3] - - return listener_module_path, listener_module - - -def evaluate_plugin_file_event(plugin_file_event): - # Ignore all directory events - if plugin_file_event.is_directory: - return False - - plugin_module_path = plugin_file_event.src_path[2:] - - # Ignore non-Python files - if plugin_module_path[-3:] != ".py": - return False - - # Remove file extension and replace path separator with dots. Then make like a banana.. and split. - plugin_module_tokens = plugin_module_path.replace(os.path.sep, ".")[:-3].split(".") - - if plugin_module_tokens.pop() == "__init__": - return plugin_module_path, ".".join(plugin_module_tokens) - - return False diff --git a/houdini/events/listener_file_event.py b/houdini/events/listener_file_event.py deleted file mode 100644 index 8d87fbc..0000000 --- a/houdini/events/listener_file_event.py +++ /dev/null @@ -1,88 +0,0 @@ -import sys -import importlib -import logging -from watchdog.events import FileSystemEventHandler - -from houdini.events import evaluate_listener_file_event - - -class ListenerFileEventHandler(FileSystemEventHandler): - - def __init__(self, server): - self.server = server - self.logger = logging.getLogger('houdini') - - def on_created(self, event): - listener_module_details = evaluate_listener_file_event(event) - - if not listener_module_details: - return - - listener_module_path, listener_module = listener_module_details - - self.logger.debug('New listener module detected {}'.format(listener_module)) - - try: - module = importlib.import_module(listener_module) - self.server.xt_listeners.load(module) - self.server.xml_listeners.load(module) - - self.logger.info('New listener module loaded {}'.format(listener_module)) - except Exception as import_error: - self.logger.error('{} detected in {}, not importing'.format( - import_error.__class__.__name__, listener_module)) - - def on_deleted(self, event): - listener_module_details = evaluate_listener_file_event(event) - - if not listener_module_details: - return - - listener_module_path, listener_module = listener_module_details - - if listener_module not in sys.modules: - return - - listener_module_object = sys.modules[listener_module] - - self.logger.debug('Deleting listener module {}'.format(listener_module)) - - self.server.xt_listeners.remove(listener_module_object) - self.server.xml_listeners.remove(listener_module_object) - del sys.modules[listener_module] - - self.logger.info('Deleted listener module {}'.format(listener_module)) - - def on_modified(self, event): - listener_module_details = evaluate_listener_file_event(event) - - if not listener_module_details: - return - - listener_module_path, listener_module = listener_module_details - - if listener_module not in sys.modules: - return False - - self.logger.info('Reloading listener module {}'.format(listener_module)) - - self.server.xt_listeners.backup() - self.server.xml_listeners.backup() - - listener_module_object = sys.modules[listener_module] - - self.server.xt_listeners.remove(listener_module_object) - self.server.xml_listeners.remove(listener_module_object) - - try: - module = importlib.reload(listener_module_object) - self.server.xt_listeners.load(module) - self.server.xml_listeners.load(module) - - self.logger.info('Successfully reloaded listener module {}!'.format(listener_module)) - except Exception as rebuild_error: - self.logger.error('{} detected in {}, not reloading.'.format( - rebuild_error.__class__.__name__, listener_module)) - - self.server.xt_listeners.restore() - self.server.xml_listeners.restore() diff --git a/houdini/events/plugin_file_event.py b/houdini/events/plugin_file_event.py deleted file mode 100644 index 3034338..0000000 --- a/houdini/events/plugin_file_event.py +++ /dev/null @@ -1,87 +0,0 @@ -import sys -import importlib -import os.path -import logging -from watchdog.events import FileSystemEventHandler - -from houdini.events import evaluate_plugin_file_event - - -class PluginFileEventHandler(FileSystemEventHandler): - - def __init__(self, server): - self.server = server - self.logger = logging.getLogger('houdini') - - def on_created(self, event): - plugin_module_details = evaluate_plugin_file_event(event) - - if not plugin_module_details: - return - - plugin_module_path, plugin_module = plugin_module_details - - self.logger.debug('New plugin detected {}'.format(plugin_module)) - - try: - plugin_module_object = importlib.import_module(plugin_module) - - self.server.plugins.load(plugin_module_object) - - self.logger.info('New plugin {} has been loaded'.format(plugin_module)) - except Exception as import_error: - self.logger.error('{} detected in {}, not importing'.format( - import_error.__class__.__name__, plugin_module)) - - def on_deleted(self, event): - plugin_module_path = event.src_path[2:] - - plugin_module = plugin_module_path.replace(os.path.sep, ".") - - if plugin_module not in sys.modules: - return - - self.logger.debug('Deleting plugin {}'.format(plugin_module)) - - plugin_module_object = sys.modules[plugin_module] - - self.server.plugins.remove(plugin_module_object) - del sys.modules[plugin_module] - - self.logger.info('Plugin {} has been removed'.format(plugin_module)) - - def on_modified(self, event): - plugin_module_details = evaluate_plugin_file_event(event) - if not plugin_module_details: - return - - plugin_module_path, plugin_module = plugin_module_details - - if plugin_module not in sys.modules: - return - - self.logger.info('Reloading plugin {}'.format(plugin_module)) - - plugin_module_object = sys.modules[plugin_module] - - self.server.xt_listeners.backup() - self.server.xml_listeners.backup() - self.server.commands.backup() - - self.server.plugins.remove(plugin_module_object) - - try: - new_plugin_module = importlib.reload(plugin_module_object) - - self.server.plugins.load(new_plugin_module) - - self.logger.info('Successfully reloaded plugin {}'.format(plugin_module)) - except LookupError as lookup_error: - self.logger.warning('Did not reload plugin \'{}\': {}'.format(plugin_module, lookup_error)) - except Exception as rebuild_error: - self.logger.error('{} detected in {}, not reloading'.format( - rebuild_error.__class__.__name__, plugin_module)) - - self.server.xt_listeners.restore() - self.server.xml_listeners.restore() - self.server.commands.restore() diff --git a/houdini/handlers/__init__.py b/houdini/handlers/__init__.py index 2c0813b..4ce805a 100644 --- a/houdini/handlers/__init__.py +++ b/houdini/handlers/__init__.py @@ -118,16 +118,12 @@ class _ListenerManager(_AbstractManager): super().__init__(server) - def setup(self, module, strict_load=None, exclude_load=None): + async def setup(self, module, strict_load=None, exclude_load=None): self.strict_load, self.exclude_load = strict_load, exclude_load - for handler_module in self.server.get_package_modules(module): - module = sys.modules[handler_module] if handler_module in sys.modules.keys() \ - else importlib.import_module(handler_module) - self.load(module) + for handler_module in get_package_modules(module): + await self.load(handler_module) - self.logger.debug('Loaded {} listeners'.format(len(self))) - - def load(self, module): + async def load(self, module): module_name = module.__module__ if isinstance(module, plugins.IPlugin) else module.__name__ if not (self.strict_load and module_name not in self.strict_load or self.exclude_load and module_name in self.exclude_load): @@ -159,12 +155,6 @@ class _ListenerManager(_AbstractManager): for override in listener_object.overrides: self[override.packet].remove(override) - def remove(self, module): - for handler_id, handler_listeners in self.items(): - for handler_listener in handler_listeners: - if module.__name__ == handler_listener.callback.__module__: - handler_listeners.remove(handler_listener) - @classmethod def is_listener(cls, listener): return issubclass(type(listener), _Listener) diff --git a/houdini/houdini.py b/houdini/houdini.py index fecb0ef..6b6f6e3 100644 --- a/houdini/houdini.py +++ b/houdini/houdini.py @@ -1,8 +1,6 @@ import asyncio import os import sys -import pkgutil -import importlib import copy from houdini.spheniscidae import Spheniscidae @@ -15,7 +13,6 @@ from logging.handlers import RotatingFileHandler import aioredis from aiocache import SimpleMemoryCache, caches -from watchdog.observers import Observer from houdini.data import db from houdini.data.item import ItemCrumbsCollection @@ -37,8 +34,6 @@ except ImportError: import houdini.handlers import houdini.plugins -from houdini.events.listener_file_event import ListenerFileEventHandler -from houdini.events.plugin_file_event import PluginFileEventHandler from houdini.handlers import XTListenerManager, XMLListenerManager from houdini.plugins import PluginManager @@ -170,11 +165,12 @@ class Houdini: PenguinStringCompiler.setup_default_builder(self.penguin_string_compiler) PenguinStringCompiler.setup_anonymous_default_builder(self.anonymous_penguin_string_compiler) - self.xml_listeners.setup(houdini.handlers, exclude_load='houdini.handlers.login.login') - self.xt_listeners.setup(houdini.handlers) + await self.xml_listeners.setup(houdini.handlers, exclude_load='houdini.handlers.login.login') + await self.xt_listeners.setup(houdini.handlers) + await self.dummy_event_listeners.setup(houdini.handlers) self.logger.info('World server started') else: - self.xml_listeners.setup(houdini.handlers, 'houdini.handlers.login.login') + await self.xml_listeners.setup(houdini.handlers, 'houdini.handlers.login.login') self.logger.info('Login server started') self.items = await ItemCrumbsCollection.get_collection() @@ -218,11 +214,6 @@ class Houdini: self.permissions = await PermissionCrumbsCollection.get_collection() - handlers_path = os.path.join(os.path.dirname(__file__), 'handlers') - plugins_path = os.path.join(os.path.dirname(__file__), 'plugins') - self.configure_observers([handlers_path, ListenerFileEventHandler], - [plugins_path, PluginFileEventHandler]) - self.logger.info('Multi-client support is {}'.format( 'enabled' if self.config.client['MultiClientSupport'] else 'disabled')) self.logger.info('Listening on {}:{}'.format(self.server_config['Address'], self.server_config['Port'])) @@ -231,7 +222,7 @@ class Houdini: self.logger.warning('The static key has been changed from the default, ' 'this may cause authentication issues!') - self.plugins.setup(houdini.plugins) + await self.plugins.setup(houdini.plugins) async with self.server: await self.server.serve_forever() @@ -239,31 +230,3 @@ class Houdini: async def client_connected(self, reader, writer): client_object = self.client_class(self, reader, writer) await client_object.run() - - def get_package_modules(self, package): - package_modules = [] - - for importer, module_name, is_package in pkgutil.iter_modules(package.__path__): - full_module_name = '{0}.{1}'.format(package.__name__, module_name) - - if is_package: - subpackage_object = importlib.import_module(full_module_name, package=package.__path__) - subpackage_object_directory = dir(subpackage_object) - - if houdini.plugins.IPlugin.__name__ in subpackage_object_directory: - package_modules.append(subpackage_object) - continue - - sub_package_modules = self.get_package_modules(subpackage_object) - - package_modules = package_modules + sub_package_modules - else: - package_modules.append(full_module_name) - - return package_modules - - def configure_observers(self, *observer_settings): - for observer_path, observer_class in observer_settings: - event_observer = Observer() - event_observer.schedule(observer_class(self), observer_path, recursive=True) - event_observer.start() diff --git a/houdini/plugins/__init__.py b/houdini/plugins/__init__.py index 08b3f13..55a4be5 100644 --- a/houdini/plugins/__init__.py +++ b/houdini/plugins/__init__.py @@ -2,9 +2,8 @@ from abc import ABC from abc import abstractmethod import inspect -import asyncio -from houdini import _AbstractManager +from houdini import _AbstractManager, get_package_modules class IPlugin(ABC): @@ -37,11 +36,11 @@ class IPlugin(ABC): class PluginManager(_AbstractManager): - def setup(self, module): - for plugin_package in self.server.get_package_modules(module): - self.load(plugin_package) + async def setup(self, module): + for plugin_package in get_package_modules(module): + await self.load(plugin_package) - def load(self, module): + async def load(self, module): plugin_class, plugin_type = inspect.getmembers(module, is_plugin).pop() if self.server.server_config['Plugins'] is not True and \ @@ -51,19 +50,12 @@ class PluginManager(_AbstractManager): plugin_object = plugin_type(self.server) self[module.__name__] = plugin_object - self.server.commands.load(plugin_object) - self.server.xt_listeners.load(plugin_object) - self.server.xml_listeners.load(plugin_object) + await self.server.commands.load(plugin_object) + await self.server.xt_listeners.load(plugin_object) + await self.server.xml_listeners.load(plugin_object) + await self.server.dummy_event_listeners.load(plugin_object) - asyncio.run_coroutine_threadsafe(plugin_object.ready(), self.server.server.get_loop()) - - def remove(self, module): - if module.__name__ in self: - del self[module.__name__] - - self.server.commands.remove(module) - self.server.xt_listeners.remove(module) - self.server.xml_listeners.remove(module) + await plugin_object.ready() def is_plugin(plugin_class):