Example #1
0
class test_commands:

    def __init__(self):
        self.manager = CommandManager()
        self.manager.logger.setLevel(logging.CRITICAL)  # Shut up, logger

        self.factory_manager = Mock(name="factory_manager")
        self.plugin = Mock(name="plugin")

    @nosetools.nottest
    def teardown(self):
        # Clean up
        self.manager.commands = {}
        self.manager.aliases = {}
        self.manager.auth_handler = None
        self.manager.perm_handler = None

        self.plugin.reset_mock()
        self.plugin.handler.reset_mock()

        self.factory_manager.reset_mock()

    @nose.with_setup(teardown=teardown)
    def test_singleton(self):
        """CMNDS | Test Singleton metaclass"""
        nosetools.assert_true(self.manager is CommandManager())

    @nose.with_setup(teardown=teardown)
    def test_set_factory_manager(self):
        """CMNDS | Test setting factory manager"""
        self.manager.set_factory_manager(self.factory_manager)
        nosetools.assert_true(self.factory_manager)

    @nose.with_setup(teardown=teardown)
    def test_add_command(self):
        """CMNDS | Test adding commands"""
        r = self.manager.register_command("test", self.plugin.handler,
                                          self.plugin, "test.test",
                                          ["test2"], True)

        nosetools.assert_true(r)
        nosetools.assert_true("test" in self.manager.commands)

        command = self.manager.commands.get("test", None)

        if command:
            nosetools.assert_true("f" in command)
            nosetools.assert_true(command.get("f") is self.plugin.handler)

            nosetools.assert_true("permission" in command)
            nosetools.assert_true(command.get("permission") == "test.test")

            nosetools.assert_true("owner" in command)
            nosetools.assert_true(command.get("owner") is self.plugin)

            nosetools.assert_true("default" in command)
            nosetools.assert_true(command.get("default"))

        nosetools.assert_true("test2" in self.manager.aliases)

        alias = self.manager.aliases.get("test2", None)

        if alias:
            nosetools.assert_true(alias == "test")

        r = self.manager.register_command("test", self.plugin.handler,
                                          self.plugin, "test.test",
                                          ["test2"], True)

        nosetools.assert_false(r)

    @nose.with_setup(teardown=teardown)
    def test_unregister_commands(self):
        """CMNDS | Test unregistering commands"""
        self.manager.register_command("test1", self.plugin.handler,
                                      self.plugin, aliases=["test11"])
        self.manager.register_command("test2", self.plugin.handler,
                                      self.plugin, aliases=["test22"])
        self.manager.register_command("test3", self.plugin.handler,
                                      self.plugin, aliases=["test33"])

        nosetools.assert_equals(len(self.manager.commands), 3)
        nosetools.assert_equals(len(self.manager.aliases), 3)

        self.manager.unregister_commands_for_owner(self.plugin)

        nosetools.assert_equals(len(self.manager.commands), 0)
        nosetools.assert_equals(len(self.manager.aliases), 0)

    @nose.with_setup(teardown=teardown)
    def test_run_commands_defaults(self):
        """CMNDS | Test running commands directly | Defaults"""

        self.manager.register_command("test4", self.plugin.handler,
                                      self.plugin, aliases=["test5"],
                                      default=True)

        caller = Mock(name="caller")
        source = Mock(name="source")
        protocol = Mock(name="protocol")

        # Testing defaults

        r = self.manager.run_command("test4", caller, source, protocol, "")
        nosetools.assert_equals(r, (CommandState.Success, None))

        r = self.manager.run_command("test5", caller, source, protocol, "")
        nosetools.assert_equals(r, (CommandState.Success, None))

        nosetools.assert_equals(self.plugin.handler.call_count, 2)

    @nose.with_setup(teardown=teardown)
    def test_run_commands_aliases(self):
        """CMNDS | Test running commands directly | Aliases"""

        self.manager.register_command("test4", self.plugin.handler,
                                      self.plugin, aliases=["test5"],
                                      default=True)

        caller = Mock(name="caller")
        source = Mock(name="source")
        protocol = Mock(name="protocol")

        # Testing defaults

        r = self.manager.run_command("test4", caller, source, protocol, "")
        nosetools.assert_equals(r, (CommandState.Success, None))

        self.plugin.handler.assert_called_with(protocol, caller, source,
                                               "test4", "", [])

        # Reset mock
        self.plugin.handler.reset_mock()

        r = self.manager.run_command("test5", caller, source, protocol, "")
        nosetools.assert_equals(r, (CommandState.Success, None))

        self.plugin.handler.assert_called_with(protocol, caller, source,
                                               "test4", "", [])

        # Reset mock
        self.plugin.handler.reset_mock()

        self.manager.register_command("test5", self.plugin.handler,
                                      self.plugin, default=True)

        r = self.manager.run_command("test5", caller, source, protocol, "")
        nosetools.assert_equals(r, (CommandState.Success, None))

        self.plugin.handler.assert_called_with(protocol, caller, source,
                                               "test5", "", [])

    @nose.with_setup(teardown=teardown)
    def test_run_commands_auth(self):
        """CMNDS | Test running commands directly | Auth"""

        caller = Mock(name="caller")
        source = Mock(name="source")
        protocol = Mock(name="protocol")

        auth = Mock(name="auth")
        perms = Mock(name="perms")

        self.manager.set_auth_handler(auth)
        self.manager.set_permissions_handler(perms)

        ### COMMAND WITH PERMISSION ###
        auth.authorized.return_value = True
        perms.check.return_value = True

        self.manager.register_command("test5", self.plugin.handler,
                                      self.plugin, "test.test",
                                      aliases=["test6"])

        r = self.manager.run_command("test5", caller, source, protocol, "")

        nosetools.assert_equals(r, (CommandState.Success, None))
        nosetools.assert_equals(self.plugin.handler.call_count, 1)

        auth.authorized.reset_mock()
        perms.check.reset_mock()
        self.plugin.handler.reset_mock()

        ### ALIAS WITH PERMISSION ###

        r = self.manager.run_command("test6", caller, source, protocol, "")

        nosetools.assert_equals(r, (CommandState.Success, None))
        nosetools.assert_equals(self.plugin.handler.call_count, 1)

        auth.authorized.reset_mock()
        perms.check.reset_mock()

        auth.authorized.return_value = True
        perms.check.return_value = False
        self.plugin.handler.reset_mock()

        ### COMMAND WITHOUT PERMISSION ###

        r = self.manager.run_command("test5", caller, source, protocol, "")

        nosetools.assert_equals(r, (CommandState.NoPermission, None))
        nosetools.assert_equals(self.plugin.handler.call_count, 0)

        perms.check.return_value = True

        auth.authorized.reset_mock()
        perms.check.reset_mock()
        self.plugin.handler.reset_mock()

        ### COMMAND WITH EXCEPTION ###

        self.plugin.handler = Mock(side_effect=Exception('Boom!'))
        self.manager.unregister_commands_for_owner(self.plugin)

        self.manager.register_command("test5", self.plugin.handler,
                                      self.plugin, "test.test")

        r = self.manager.run_command("test5", caller, source, protocol, "")

        nosetools.assert_equals(r[0], CommandState.Error)
        nosetools.assert_true(isinstance(r[1], Exception))
        nosetools.assert_equals(self.plugin.handler.call_count, 1)

        auth.authorized.reset_mock()
        perms.check.reset_mock()
        self.plugin.handler.reset_mock()

        ### UNKNOWN COMMAND ###

        r = self.manager.run_command("test7", caller, source, protocol, "")
        nosetools.assert_equals(r, (CommandState.Unknown, None))
        nosetools.assert_equals(self.plugin.handler.call_count, 0)
Example #2
0
class Manager(object):
    """
    Manager for keeping track of multiple factories - one per protocol.

    This is so that the bot can connect to multiple services at once, and have
    them communicate with each other.
    """

    __metaclass__ = Singleton

    #: Instance of the storage manager
    storage = None

    #: Storage for all of our factories.
    factories = {}

    #: Storage for all of the protocol configs.
    configs = {}

    #: The main configuration is stored here.
    main_config = None

    #: Whether the manager is already running or not
    running = False

    def __init__(self):
        self.commands = CommandManager()
        self.event_manager = EventManager()
        self.logger = getLogger("Manager")
        self.plugman = PluginManager(self)
        self.yapsy_logger = getLogger("yapsy")

        self.metrics = None

    @property
    def all_plugins(self):
        return self.plugman.info_objects

    @property
    def loaded_plugins(self):
        return self.plugman.plugin_objects

    def setup(self):
        signal.signal(signal.SIGINT, self.signal_callback)

        self.yapsy_logger.debug_ = self.yapsy_logger.debug
        self.yapsy_logger.debug = self.yapsy_logger.trace

        self.storage = StorageManager()
        self.main_config = self.storage.get_file(self, "config", YAML,
                                                 "settings.yml")

        self.commands.set_factory_manager(self)

        self.load_config()  # Load the configuration

        try:
            self.metrics = Metrics(self.main_config, self)
        except Exception:
            self.logger.exception(_("Error setting up metrics."))

        self.plugman.scan()
        self.load_plugins()  # Load the configured plugins
        self.load_protocols()  # Load and set up the protocols

        if not len(self.factories):
            self.logger.info(_("It seems like no protocols are loaded. "
                               "Shutting down.."))
            return

    def run(self):
        if not self.running:
            event = ReactorStartedEvent(self)

            reactor.callLater(0, self.event_manager.run_callback,
                              "ReactorStarted", event)

            self.running = True
            reactor.run()
        else:
            raise RuntimeError(_("Manager is already running!"))

    def signal_callback(self, signum, frame):
        try:
            try:
                self.unload()
            except Exception:
                self.logger.exception(_("Error while unloading!"))
                try:
                    reactor.stop()
                except Exception:
                    try:
                        reactor.crash()
                    except Exception:
                        pass
        except Exception:
            exit(0)

    # Load stuff

    def load_config(self):
        """
        Load the main configuration file.

        :return: Whether the config was loaded or not
        :rtype: bool
        """

        try:
            self.logger.info(_("Loading global configuration.."))
            if not self.main_config.exists:
                self.logger.error(_(
                    "Main configuration not found! Please correct this and try"
                    " again."))
                return False
        except IOError:
            self.logger.error(_(
                "Unable to load main configuration at config/settings.yml"))
            self.logger.error(_("Please check that this file exists."))
            return False
        except Exception:
            self.logger.exception(_(
                "Unable to load main configuration at config/settings.yml"))
            return False
        return True

    def load_plugins(self):
        """
        Attempt to load all of the plugins.
        """

        self.logger.info(_("Loading plugins.."))

        self.logger.trace(_("Configured plugins: %s")
                          % ", ".join(self.main_config["plugins"]))

        self.plugman.load_plugins(self.main_config.get("plugins", []))

        event = PluginsLoadedEvent(self, self.plugman.plugin_objects)
        self.event_manager.run_callback("PluginsLoaded", event)

    @deprecated("Use the plugin manager directly")
    def load_plugin(self, name, unload=False):
        """
        Load a single plugin by name.

        This will return one of the system.enums.PluginState values.

        :param name: The plugin to load.
        :type name: str

        :param unload: Whether to unload the plugin, if it's already loaded.
        :type unload: bool
        """

        result = self.plugman.load_plugin(name)

        if result is PluginState.AlreadyLoaded:
            if unload:
                result_two = self.plugman.unload_plugin(name)

                if result_two is not PluginState.Unloaded:
                    return result_two

                result = self.plugman.load_plugin(name)

        return result

    @deprecated("Use the plugin manager directly")
    def collect_plugins(self):
        """
        Collect all possible plugin candidates.
        """

        self.plugman.scan()

    def load_protocols(self):
        """
        Load and set up all of the configured protocols.
        """

        self.logger.info(_("Setting up protocols.."))

        for protocol in self.main_config["protocols"]:
            if protocol.lower().startswith("plugin-"):
                self.logger.error("Invalid protocol name: %s" % protocol)
                self.logger.error(
                    "Protocol names beginning with \"plugin-\" are reserved "
                    "for plugin use."
                )
                continue

            self.logger.info(_("Setting up protocol: %s") % protocol)
            conf_location = "protocols/%s.yml" % protocol
            result = self.load_protocol(protocol, conf_location)

            if result is not PROTOCOL_LOADED:
                if result is PROTOCOL_ALREADY_LOADED:
                    self.logger.warn(_("Protocol is already loaded."))
                elif result is PROTOCOL_CONFIG_NOT_EXISTS:
                    self.logger.warn(_("Unable to find protocol "
                                       "configuration."))
                elif result is PROTOCOL_LOAD_ERROR:
                    self.logger.warn(_("Error detected while loading "
                                       "protocol."))
                elif result is PROTOCOL_SETUP_ERROR:
                    self.logger.warn(_("Error detected while setting up "
                                       "protocol."))

    def load_protocol(self, name, conf_location):
        """
        Attempt to load a protocol by name. This can return one of the
        following, from system.constants:

        * PROTOCOL_ALREADY_LOADED
        * PROTOCOL_CONFIG_NOT_EXISTS
        * PROTOCOL_LOAD_ERROR
        * PROTOCOL_LOADED
        * PROTOCOL_SETUP_ERROR

        :param name: The name of the protocol
        :type name: str

        :param conf_location: The location of the config file, relative
            to the config/ directory, or a Config object
        :type conf_location: str, Config
        """

        if name in self.factories:
            return PROTOCOL_ALREADY_LOADED

        config = conf_location
        if not isinstance(conf_location, Config):
            # TODO: Prevent upward directory traversal properly
            conf_location = conf_location.replace("..", "")
            try:
                config = self.storage.get_file(self, "config", YAML,
                                               conf_location)
                if not config.exists:
                    return PROTOCOL_CONFIG_NOT_EXISTS
            except Exception:
                self.logger.exception(
                    _("Unable to load configuration for the '%s' protocol.")
                    % name)
                return PROTOCOL_LOAD_ERROR
        try:
            self.factories[name] = Factory(name, config, self)
            self.factories[name].setup()
            return PROTOCOL_LOADED
        except Exception:
            if name in self.factories:
                del self.factories[name]
            self.logger.exception(
                _("Unable to create factory for the '%s' protocol!")
                % name)
            return PROTOCOL_SETUP_ERROR

    # Reload stuff

    @deprecated("Use the plugin manager directly")
    def reload_plugin(self, name):
        """
        Attempt to reload a plugin by name.

        This will return one of the system.enums.PluginState values.

        :param name: The name of the plugin
        :type name: str
        """
        return self.plugman.reload_plugin(name)

    def reload_protocol(self, name):
        factory = self.get_factory(name)

        if name is not None:
            factory.shutdown()
            factory.setup()
            return True

    # Unload stuff

    @deprecated("Use the plugin manager directly")
    def unload_plugin(self, name):
        """
        Attempt to unload a plugin by name.

        This will return one of the system.enums.PluginState values.

        :param name: The name of the plugin
        :type name: str
        """

        return self.plugman.unload_plugin(name)

    def unload_protocol(self, name):  # Removes with a shutdown
        """
        Attempt to unload a protocol by name. This will also shut it down.

        :param name: The name of the protocol
        :type name: str

        :return: Whether the protocol was unloaded
        :rtype: bool
        """

        if name in self.factories:
            proto = self.factories[name]
            try:
                proto.shutdown()
            except Exception:
                self.logger.exception(_("Error shutting down protocol %s")
                                      % name)
            finally:
                try:
                    self.storage.release_file(self, "config",
                                              "protocols/%s.yml" % name)
                    self.storage.release_files(proto)
                    self.storage.release_files(proto.protocol)
                except Exception:
                    self.logger.exception("Error releasing files for protocol "
                                          "%s" % name)
            del self.factories[name]
            return True
        return False

    def unload(self):
        """
        Shut down and unload everything.
        """

        # Shut down!
        for name in self.factories.keys():
            self.logger.info(_("Unloading protocol: %s") % name)
            self.unload_protocol(name)

        self.plugman.unload_plugins()

        if reactor.running:
            try:
                reactor.stop()
            except Exception:
                self.logger.exception("Error stopping reactor")

    # Grab stuff

    def get_protocol(self, name):
        """
        Get the instance of a protocol, by name.

        :param name: The name of the protocol
        :type name: str

        :return: The protocol, or None if it doesn't exist.
        """

        if name in self.factories:
            return self.factories[name].protocol
        return None

    def get_factory(self, name):
        """
        Get the instance of a protocol's factory, by name.

        :param name: The name of the protocol
        :type name: str

        :return: The factory, or None if it doesn't exist.
        """

        if name in self.factories:
            return self.factories[name]
        return None

    @deprecated("Use the plugin manager directly")
    def get_plugin(self, name):
        """
        Get the insatnce of a plugin, by name.
        :param name: The name of the plugin
        :type name: str

        :return: The plugin, or None if it isn't loaded.
        """

        return self.plugman.get_plugin(name)

    def remove_protocol(self, protocol):  # Removes without shutdown
        """
        Remove a protocol without shutting it down. You shouldn't use this.

        :param protocol: The name of the protocol
        :type protocol: str

        :return: Whether the protocol was removed.
        :rtype: bool
        """

        if protocol in self.factories:
            del self.factories[protocol]
            return True
        return False
Example #3
0
class Manager(object):
    """
    Manager for keeping track of multiple factories - one per protocol.

    This is so that the bot can connect to multiple services at once, and have
    them communicate with each other.
    """

    __metaclass__ = Singleton

    #: Instance of the storage manager
    storage = None

    #: Storage for all of our factories.
    factories = {}

    #: Storage for all of the protocol configs.
    configs = {}

    #: The main configuration is stored here.
    main_config = None

    #: Whether the manager is already running or not
    running = False

    #: Console handler
    console_magic = None

    def __init__(self):
        self.commands = CommandManager()
        self.event_manager = EventManager()
        self.logger = getLogger("Manager")
        self.plugman = PluginManager(self)
        self.yapsy_logger = getLogger("yapsy")

        self.metrics = None

    @property
    def all_plugins(self):
        return self.plugman.info_objects

    @property
    def loaded_plugins(self):
        return self.plugman.plugin_objects

    def setup(self):
        signal.signal(signal.SIGINT, self.signal_callback)

        self.yapsy_logger.debug_ = self.yapsy_logger.debug
        self.yapsy_logger.debug = self.yapsy_logger.trace

        self.storage = StorageManager()
        self.main_config = self.storage.get_file(self, "config", YAML,
                                                 "settings.yml")

        self.commands.set_factory_manager(self)

        self.load_config()  # Load the configuration

        try:
            self.metrics = Metrics(self.main_config, self)
        except Exception:
            self.logger.exception(_("Error setting up metrics."))

        self.plugman.scan()
        self.load_plugins()  # Load the configured plugins
        self.load_protocols()  # Load and set up the protocols

        if not len(self.factories):
            self.logger.info(
                _("It seems like no protocols are loaded. "
                  "Shutting down.."))
            return

        self.console_magic = ConsoleMagic()

    def run(self):
        if not self.running:
            event = ReactorStartedEvent(self)

            reactor.callLater(0, self.event_manager.run_callback,
                              "ReactorStarted", event)

            self.running = True
            reactor.run()
        else:
            raise RuntimeError(_("Manager is already running!"))

    def signal_callback(self, signum, frame):
        try:
            try:
                self.unload()
                self.console_magic.unwrap()
            except Exception:
                self.logger.exception(_("Error while unloading!"))
                try:
                    reactor.stop()
                except Exception:
                    try:
                        reactor.crash()
                    except Exception:
                        pass
        except Exception:
            exit(0)

    # Load stuff

    def load_config(self):
        """
        Load the main configuration file.

        :return: Whether the config was loaded or not
        :rtype: bool
        """

        try:
            self.logger.info(_("Loading global configuration.."))
            if not self.main_config.exists:
                self.logger.error(
                    _("Main configuration not found! Please correct this and try"
                      " again."))
                return False
        except IOError:
            self.logger.error(
                _("Unable to load main configuration at config/settings.yml"))
            self.logger.error(_("Please check that this file exists."))
            return False
        except Exception:
            self.logger.exception(
                _("Unable to load main configuration at config/settings.yml"))
            return False
        return True

    def load_plugins(self):
        """
        Attempt to load all of the plugins.
        """

        self.logger.info(_("Loading plugins.."))

        self.logger.trace(
            _("Configured plugins: %s") %
            ", ".join(self.main_config["plugins"]))

        self.plugman.load_plugins(self.main_config.get("plugins", []))

        event = PluginsLoadedEvent(self, self.plugman.plugin_objects)
        self.event_manager.run_callback("PluginsLoaded", event)

    @deprecated("Use the plugin manager directly")
    def load_plugin(self, name, unload=False):
        """
        Load a single plugin by name.

        This will return one of the system.enums.PluginState values.

        :param name: The plugin to load.
        :type name: str

        :param unload: Whether to unload the plugin, if it's already loaded.
        :type unload: bool
        """

        result = self.plugman.load_plugin(name)

        if result is PluginState.AlreadyLoaded:
            if unload:
                result_two = self.plugman.unload_plugin(name)

                if result_two is not PluginState.Unloaded:
                    return result_two

                result = self.plugman.load_plugin(name)

        return result

    @deprecated("Use the plugin manager directly")
    def collect_plugins(self):
        """
        Collect all possible plugin candidates.
        """

        self.plugman.scan()

    def load_protocols(self):
        """
        Load and set up all of the configured protocols.
        """

        self.logger.info(_("Setting up protocols.."))

        for protocol in self.main_config["protocols"]:
            if protocol.lower().startswith("plugin-"):
                self.logger.error("Invalid protocol name: %s" % protocol)
                self.logger.error(
                    "Protocol names beginning with \"plugin-\" are reserved "
                    "for plugin use.")
                continue

            self.logger.info(_("Setting up protocol: %s") % protocol)
            conf_location = "protocols/%s.yml" % protocol
            result = self.load_protocol(protocol, conf_location)

            if result is not PROTOCOL_LOADED:
                if result is PROTOCOL_ALREADY_LOADED:
                    self.logger.warn(_("Protocol is already loaded."))
                elif result is PROTOCOL_CONFIG_NOT_EXISTS:
                    self.logger.warn(
                        _("Unable to find protocol "
                          "configuration."))
                elif result is PROTOCOL_LOAD_ERROR:
                    self.logger.warn(
                        _("Error detected while loading "
                          "protocol."))
                elif result is PROTOCOL_SETUP_ERROR:
                    self.logger.warn(
                        _("Error detected while setting up "
                          "protocol."))

    def load_protocol(self, name, conf_location):
        """
        Attempt to load a protocol by name. This can return one of the
        following, from system.constants:

        * PROTOCOL_ALREADY_LOADED
        * PROTOCOL_CONFIG_NOT_EXISTS
        * PROTOCOL_LOAD_ERROR
        * PROTOCOL_LOADED
        * PROTOCOL_SETUP_ERROR

        :param name: The name of the protocol
        :type name: str

        :param conf_location: The location of the config file, relative
            to the config/ directory, or a Config object
        :type conf_location: str, Config
        """

        if name in self.factories:
            return PROTOCOL_ALREADY_LOADED

        config = conf_location
        if not isinstance(conf_location, Config):
            # TODO: Prevent upward directory traversal properly
            conf_location = conf_location.replace("..", "")
            try:
                config = self.storage.get_file(self, "config", YAML,
                                               conf_location)
                if not config.exists:
                    return PROTOCOL_CONFIG_NOT_EXISTS
            except Exception:
                self.logger.exception(
                    _("Unable to load configuration for the '%s' protocol.") %
                    name)
                return PROTOCOL_LOAD_ERROR
        try:
            self.factories[name] = Factory(name, config, self)
            self.factories[name].setup()
            return PROTOCOL_LOADED
        except Exception:
            if name in self.factories:
                del self.factories[name]
            self.logger.exception(
                _("Unable to create factory for the '%s' protocol!") % name)
            return PROTOCOL_SETUP_ERROR

    # Reload stuff

    @deprecated("Use the plugin manager directly")
    def reload_plugin(self, name):
        """
        Attempt to reload a plugin by name.

        This will return one of the system.enums.PluginState values.

        :param name: The name of the plugin
        :type name: str
        """
        return self.plugman.reload_plugin(name)

    def reload_protocol(self, name):
        factory = self.get_factory(name)

        if name is not None:
            factory.shutdown()
            factory.setup()
            return True

    # Unload stuff

    @deprecated("Use the plugin manager directly")
    def unload_plugin(self, name):
        """
        Attempt to unload a plugin by name.

        This will return one of the system.enums.PluginState values.

        :param name: The name of the plugin
        :type name: str
        """

        return self.plugman.unload_plugin(name)

    def unload_protocol(self, name):  # Removes with a shutdown
        """
        Attempt to unload a protocol by name. This will also shut it down.

        :param name: The name of the protocol
        :type name: str

        :return: Whether the protocol was unloaded
        :rtype: bool
        """

        if name in self.factories:
            proto = self.factories[name]
            try:
                proto.shutdown()
            except Exception:
                self.logger.exception(
                    _("Error shutting down protocol %s") % name)
            finally:
                try:
                    self.storage.release_file(self, "config",
                                              "protocols/%s.yml" % name)
                    self.storage.release_files(proto)
                    self.storage.release_files(proto.protocol)
                except Exception:
                    self.logger.exception("Error releasing files for protocol "
                                          "%s" % name)
            del self.factories[name]
            return True
        return False

    def unload(self):
        """
        Shut down and unload everything.
        """

        self.console_magic.unwrap()

        # Shut down!
        for name in self.factories.keys():
            self.logger.info(_("Unloading protocol: %s") % name)
            self.unload_protocol(name)

        self.plugman.unload_plugins()

        if reactor.running:
            try:
                reactor.stop()
            except Exception:
                self.logger.exception("Error stopping reactor")

    # Grab stuff

    def get_protocol(self, name):
        """
        Get the instance of a protocol, by name.

        :param name: The name of the protocol
        :type name: str

        :return: The protocol, or None if it doesn't exist.
        """

        if name in self.factories:
            return self.factories[name].protocol
        return None

    def get_factory(self, name):
        """
        Get the instance of a protocol's factory, by name.

        :param name: The name of the protocol
        :type name: str

        :return: The factory, or None if it doesn't exist.
        """

        if name in self.factories:
            return self.factories[name]
        return None

    @deprecated("Use the plugin manager directly")
    def get_plugin(self, name):
        """
        Get the insatnce of a plugin, by name.
        :param name: The name of the plugin
        :type name: str

        :return: The plugin, or None if it isn't loaded.
        """

        return self.plugman.get_plugin(name)

    def remove_protocol(self, protocol):  # Removes without shutdown
        """
        Remove a protocol without shutting it down. You shouldn't use this.

        :param protocol: The name of the protocol
        :type protocol: str

        :return: Whether the protocol was removed.
        :rtype: bool
        """

        if protocol in self.factories:
            del self.factories[protocol]
            return True
        return False