def test_topological_sort(self):
     dep_list = [('myplugin4', {'myplugin2', 'myplugin1'}),
                 ('myplugin2', {'myplugin1'}),
                 ('myplugin3', {}),
                 ('myplugin1', {}),
                 ('myplugin5', {'myplugin1', 'myplugin3', 'myplugin4'})]
     sorted_list = [x for x in functions.topological_sort(dep_list)]
     self.assertListEqual(sorted_list, ['myplugin3', 'myplugin1', 'myplugin2', 'myplugin4', 'myplugin5'])
示例#2
0
 def test_topological_sort(self):
     dep_list = [('myplugin4', {'myplugin2', 'myplugin1'}),
                 ('myplugin2', {'myplugin1'}), ('myplugin3', {}),
                 ('myplugin1', {}),
                 ('myplugin5', {'myplugin1', 'myplugin3', 'myplugin4'})]
     sorted_list = [x for x in functions.topological_sort(dep_list)]
     self.assertListEqual(
         sorted_list,
         ['myplugin3', 'myplugin1', 'myplugin2', 'myplugin4', 'myplugin5'])
 def test_topological_sort(self):
     dep_list = [
         ("myplugin4", {"myplugin2", "myplugin1"}),
         ("myplugin2", {"myplugin1"}),
         ("myplugin3", {}),
         ("myplugin1", {}),
         ("myplugin5", {"myplugin1", "myplugin3", "myplugin4"}),
     ]
     sorted_list = [x for x in functions.topological_sort(dep_list)]
     self.assertListEqual(sorted_list, ["myplugin3", "myplugin1", "myplugin2", "myplugin4", "myplugin5"])
示例#4
0
    def do_load(self, client, name=None):
        """
        Load a new plugin
        :param client: The client who launched the command
        :param name: The name of the plugin to load
        """
        def _get_plugin_config(p_name, p_clazz):
            """
            Helper that load and return a configuration file for the given Plugin
            :param p_name: The plugin name
            :param p_clazz: The class implementing the plugin
            """
            def _search_config_file(match):
                """
                Helper that returns a list of available configuration file paths for the given plugin.
                :param match: The plugin name
                """
                # first look in the built-in plugins directory
                search = '%s%s*%s*' % (b3.getAbsolutePath('@b3\\conf'),
                                       os.path.sep, match)
                self.debug('searching for configuration file(s) matching: %s' %
                           search)
                collection = glob.glob(search)
                if len(collection) > 0:
                    return collection
                # if none is found, then search in the extplugins directory
                extplugins_dir = self.console.config.get_external_plugins_dir()
                search = '%s%s*%s*' % (os.path.join(
                    b3.getAbsolutePath(extplugins_dir), match,
                    'conf'), os.path.sep, match)
                self.debug('searching for configuration file(s) matching: %s' %
                           search)
                collection = glob.glob(search)
                return collection

            search_path = _search_config_file(p_name)
            if len(search_path) == 0:
                if p_clazz.requiresConfigFile:
                    raise b3.config.ConfigFileNotFound(
                        'could not find any configuration file')
                self.debug(
                    'no configuration file found for plugin %s: is not required either...'
                    % p_name)
                return None
            if len(search_path) > 1:
                self.warning(
                    'multiple configuration files found for plugin %s: %s',
                    p_name, ', '.join(search_path))

            self.debug('using %s as configuration file for plugin %s',
                       search_path[0], p_name)
            self.bot('loading configuration file %s for plugin %s',
                     search_path[0], p_name)
            return b3.config.load(search_path[0])

        name = name.lower()
        if name in self._protected:
            client.message('^7Plugin ^1%s ^7is protected' % name)
            return

        plugin = self.console.getPlugin(name)
        if plugin:
            client.message('^7Plugin ^2%s ^7is already loaded' % name)
            return

        try:
            mod = self.console.pluginImport(name)
            clz = getattr(mod, '%sPlugin' % name.title())
            cfg = _get_plugin_config(p_name=name, p_clazz=clz)
            plugin_data = PluginData(name=name,
                                     module=mod,
                                     clazz=clz,
                                     conf=cfg)
        except ImportError:
            client.message('^7Missing ^1%s ^7plugin python module' % name)
            client.message(
                '^7Please put the plugin module in ^3@b3/extplugins/')
        except AttributeError:
            client.message(
                '^7Plugin ^1%s ^7has an invalid structure: can\'t load' % name)
            client.message(
                '^7Please inspect your b3 log file for more information')
            self.error('could not create plugin %s instance: %s' %
                       (name, extract_tb(sys.exc_info()[2])))
        except b3.config.ConfigFileNotFound:
            client.message('^7Missing ^1%s ^7plugin configuration file' % name)
            client.message(
                '^7Please put the plugin configuration file in ^3@b3/conf ^7or ^3@b3/extplugins/%s/conf'
                % name)
        except b3.config.ConfigFileNotValid:
            client.message(
                '^7invalid configuration file found for plugin ^1%s' % name)
            client.message(
                '^7Please inspect your b3 log file for more information')
            self.error(
                'plugin %s has an invalid configuration file and can\'t be loaded: %s'
                % (name, extract_tb(sys.exc_info()[2])))
        else:

            plugin_required = []

            def _get_plugin_data(p_data):
                """
                Return a list of PluginData of plugins needed by the current one
                :param p_data: A PluginData containing plugin information
                :return: list[PluginData] a list of PluginData of plugins needed by the current one
                """
                # check for correct B3 version
                if p_data.clazz.requiresVersion and B3version(
                        p_data.clazz.requiresVersion) > B3version(
                            currentVersion):
                    raise MissingRequirement(
                        'plugin %s requires B3 version %s (you have version %s) : please update your '
                        'B3 if you want to run this plugin' %
                        (p_data.name, p_data.clazz.requiresVersion,
                         currentVersion))

                # check if the current game support this plugin (this may actually exclude more than one plugin
                # in case a plugin is built on top of an incompatible one, due to plugin dependencies)
                if p_data.clazz.requiresParsers and self.console.gameName not in p_data.clazz.requiresParsers:
                    raise MissingRequirement(
                        'plugin %s is not compatible with %s parser : supported games are : %s'
                        % (p_data.name, self.console.gameName, ', '.join(
                            p_data.clazz.requiresParsers)))

                # check if the plugin needs a particular storage protocol to work
                if p_data.clazz.requiresStorage and self.console.storage.protocol not in p_data.clazz.requiresStorage:
                    raise MissingRequirement(
                        'plugin %s is not compatible with the storage protocol being used (%s) : '
                        'supported protocols are : %s' %
                        (p_data.name, self.console.storage.protocol, ', '.join(
                            p_data.clazz.requiresStorage)))

                if p_data.clazz.requiresPlugins:
                    collection = []
                    for r in p_data.requiresPlugins:
                        if r not in self.console._plugins and r not in plugin_required:
                            try:
                                # missing requirement, try to load it
                                self.warning(
                                    'plugin %s has unmet dependency : %s : trying to load plugin %s...'
                                    % (p_data.name, r, r))
                                collection += _get_plugin_data(
                                    PluginData(name=r))
                                self.debug(
                                    'plugin %s dependency satisfied: %s' % r)
                            except Exception as err:
                                raise MissingRequirement(
                                    'missing required plugin: %s' % r, err)

                    return collection

                # plugin has not been loaded manually nor a previous automatic load attempt has been done
                if p_data.name not in self.console._plugins and p_data.name not in plugin_required:
                    # we are at the bottom step where we load a new requirement by importing the
                    # plugin module, class and configuration file. If the following generate an exception, recursion
                    # will catch it here above and raise it back so we can exclude the first plugin in the list from load
                    self.debug(
                        'looking for plugin %s module and configuration file...'
                        % p_data.name)
                    p_data.module = self.console.pluginImport(p_data.name)
                    p_data.clazz = getattr(p_data.module,
                                           '%sPlugin' % p_data.name.title())
                    p_data.conf = _get_plugin_config(p_data.name, p_data.clazz)
                    plugin_required.append(p_data.name)  # load just once

                return [p_data]

            rollback = []

            try:

                plugin_list = _get_plugin_data(
                    plugin_data
                )  # generate a list of PluginData (also requirements)
                plugin_dict = {x.name: x
                               for x in plugin_list}  # dict(str, PluginData)
                sorted_list = [
                    y for y in topological_sort([(x.name,
                                                  set(x.clazz.requiresPlugins))
                                                 for x in plugin_list])
                ]

                if len(sorted_list) > 1:
                    client.message(
                        '^7Plugin ^3%s ^7relies on other plugins to work: they will be automatically loaded'
                        % name)

                for s in sorted_list:
                    p = plugin_dict[s]
                    self.bot('loading plugin %s [%s]', p.name,
                             '--' if p.conf is None else p.conf.fileName)
                    plugin_instance = p.clazz(self.console, p.conf)
                    plugin_instance.onLoadConfig()
                    plugin_instance.onStartup()
                    self.console._plugins[p.name] = plugin_instance
                    v = getattr(p.module, '__version__', 'unknown')
                    a = getattr(p.module, '__author__', 'unknown')
                    self.bot('plugin %s (%s - %s) loaded', p.name, v, a)
                    client.message(
                        '^7Plugin ^2%s ^7(^3%s ^7- ^3%s^7) loaded' % p.name, v,
                        a)
                    # queue an event so other plugins may react on this change (for example if 2 plugins provide the
                    # same functionalities, one of them can be disabled not to do duplicated work)
                    self.console.queueEvent(
                        self.console.getEvent('EVT_PLUGIN_LOADED',
                                              data=p.name))
                    # track down all the plugins that we are enabling so we can rollback
                    # changes if a plugin in the dependency tree fails to load/start
                    rollback.append(p.name)

            except b3.exceptions.MissingRequirement:
                # here we do not have to rollback
                client.message(
                    '^7Plugin ^1%s can\'t be loaded due to unmet dependencies'
                    % name)
                client.message(
                    '^7Please inspect your b3 log file for more information')
            except Exception as e:
                # here we rollback all the plugins loaded which are not needed anymore
                client.message('^7Could not load plugin ^1%s^7: %s' %
                               (name, e))
                client.message(
                    '^7Please inspect your b3 log file for more information')
                self.error('plugin %s could not be loaded: %s' %
                           (name, extract_tb(sys.exc_info()[2])))
                if rollback:
                    for name in rollback:
                        self.do_unload(client=client, name=name)
示例#5
0
    def do_load(self, client, name=None):
        """
        Load a new plugin
        :param client: The client who launched the command
        :param name: The name of the plugin to load
        """
        def _get_plugin_config(p_name, p_clazz):
            """
            Helper that load and return a configuration file for the given Plugin
            :param p_name: The plugin name
            :param p_clazz: The class implementing the plugin
            """
            def _search_config_file(match):
                """
                Helper that returns a list of available configuration file paths for the given plugin.
                :param match: The plugin name
                """
                # first look in the built-in plugins directory
                search = '%s%s*%s*' % (b3.getAbsolutePath('@b3\\conf'), os.path.sep, match)
                self.debug('searching for configuration file(s) matching: %s' % search)
                collection = glob.glob(search)
                if len(collection) > 0:
                    return collection
                # if none is found, then search in the extplugins directory
                extplugins_dir = self.console.config.get_external_plugins_dir()
                search = '%s%s*%s*' % (os.path.join(b3.getAbsolutePath(extplugins_dir), match, 'conf'), os.path.sep, match)
                self.debug('searching for configuration file(s) matching: %s' % search)
                collection = glob.glob(search)
                return collection

            search_path = _search_config_file(p_name)
            if len(search_path) == 0:
                if p_clazz.requiresConfigFile:
                    raise b3.config.ConfigFileNotFound('could not find any configuration file')
                self.debug('no configuration file found for plugin %s: is not required either...' % p_name)
                return None
            if len(search_path) > 1:
                self.warning('multiple configuration files found for plugin %s: %s', p_name, ', '.join(search_path))

            self.debug('using %s as configuration file for plugin %s', search_path[0], p_name)
            self.bot('loading configuration file %s for plugin %s', search_path[0], p_name)
            return b3.config.load(search_path[0])

        name = name.lower()
        if name in self._protected:
            client.message('^7Plugin ^1%s ^7is protected' % name)
            return

        plugin = self.console.getPlugin(name)
        if plugin:
            client.message('^7Plugin ^2%s ^7is already loaded' % name)
            return

        try:
            mod = self.console.pluginImport(name)
            clz = getattr(mod, '%sPlugin' % name.title())
            cfg = _get_plugin_config(p_name=name, p_clazz=clz)
            plugin_data = PluginData(name=name, module=mod, clazz=clz, conf=cfg)
        except ImportError:
            client.message('^7Missing ^1%s ^7plugin python module' % name)
            client.message('^7Please put the plugin module in ^3@b3/extplugins/')
        except AttributeError:
            client.message('^7Plugin ^1%s ^7has an invalid structure: can\'t load' % name)
            client.message('^7Please inspect your b3 log file for more information')
            self.error('could not create plugin %s instance: %s' % (name, extract_tb(sys.exc_info()[2])))
        except b3.config.ConfigFileNotFound:
            client.message('^7Missing ^1%s ^7plugin configuration file' % name)
            client.message('^7Please put the plugin configuration file in ^3@b3/conf ^7or ^3@b3/extplugins/%s/conf' % name)
        except b3.config.ConfigFileNotValid:
            client.message('^7invalid configuration file found for plugin ^1%s' % name)
            client.message('^7Please inspect your b3 log file for more information')
            self.error('plugin %s has an invalid configuration file and can\'t be loaded: %s' % (name, extract_tb(sys.exc_info()[2])))
        else:

            plugin_required = []

            def _get_plugin_data(p_data):
                """
                Return a list of PluginData of plugins needed by the current one
                :param p_data: A PluginData containing plugin information
                :return: list[PluginData] a list of PluginData of plugins needed by the current one
                """
                # check for correct B3 version
                if p_data.clazz.requiresVersion and B3version(p_data.clazz.requiresVersion) > B3version(currentVersion):
                    raise MissingRequirement('plugin %s requires B3 version %s (you have version %s) : please update your '
                                             'B3 if you want to run this plugin' % (p_data.name, p_data.clazz.requiresVersion, currentVersion))

                # check if the current game support this plugin (this may actually exclude more than one plugin
                # in case a plugin is built on top of an incompatible one, due to plugin dependencies)
                if p_data.clazz.requiresParsers and self.console.gameName not in p_data.clazz.requiresParsers:
                    raise MissingRequirement('plugin %s is not compatible with %s parser : supported games are : %s' % (
                                             p_data.name, self.console.gameName, ', '.join(p_data.clazz.requiresParsers)))

                # check if the plugin needs a particular storage protocol to work
                if p_data.clazz.requiresStorage and self.console.storage.protocol not in p_data.clazz.requiresStorage:
                    raise MissingRequirement('plugin %s is not compatible with the storage protocol being used (%s) : '
                                             'supported protocols are : %s' % (p_data.name, self.console.storage.protocol,
                                                                               ', '.join(p_data.clazz.requiresStorage)))

                if p_data.clazz.requiresPlugins:
                    collection = []
                    for r in p_data.requiresPlugins:
                        if r not in self.console._plugins and r not in plugin_required:
                            try:
                                # missing requirement, try to load it
                                self.warning('plugin %s has unmet dependency : %s : trying to load plugin %s...' % (p_data.name, r, r))
                                collection += _get_plugin_data(PluginData(name=r))
                                self.debug('plugin %s dependency satisfied: %s' % r)
                            except Exception, err:
                                raise MissingRequirement('missing required plugin: %s' % r, err)

                    return collection

                # plugin has not been loaded manually nor a previous automatic load attempt has been done
                if p_data.name not in self.console._plugins and p_data.name not in plugin_required:
                    # we are at the bottom step where we load a new requirement by importing the
                    # plugin module, class and configuration file. If the following generate an exception, recursion
                    # will catch it here above and raise it back so we can exclude the first plugin in the list from load
                    self.debug('looking for plugin %s module and configuration file...' % p_data.name)
                    p_data.module = self.console.pluginImport(p_data.name)
                    p_data.clazz = getattr(p_data.module, '%sPlugin' % p_data.name.title())
                    p_data.conf = _get_plugin_config(p_data.name, p_data.clazz)
                    plugin_required.append(p_data.name) # load just once

                return [p_data]

            rollback = []

            try:

                plugin_list = _get_plugin_data(plugin_data)     # generate a list of PluginData (also requirements)
                plugin_dict = {x.name: x for x in plugin_list}  # dict(str, PluginData)
                sorted_list = [y for y in topological_sort([(x.name, set(x.clazz.requiresPlugins)) for x in plugin_list])]

                if len(sorted_list) > 1:
                    client.message('^7Plugin ^3%s ^7relies on other plugins to work: they will be automatically loaded' % name)

                for s in sorted_list:
                    p = plugin_dict[s]
                    self.bot('loading plugin %s [%s]', p.name, '--' if p.conf is None else p.conf.fileName)
                    plugin_instance = p.clazz(self.console, p.conf)
                    plugin_instance.onLoadConfig()
                    plugin_instance.onStartup()
                    self.console._plugins[p.name] = plugin_instance
                    v = getattr(p.module, '__version__', 'unknown')
                    a = getattr(p.module, '__author__', 'unknown')
                    self.bot('plugin %s (%s - %s) loaded', p.name, v, a)
                    client.message('^7Plugin ^2%s ^7(^3%s ^7- ^3%s^7) loaded' % p.name, v, a)
                    # queue an event so other plugins may react on this change (for example if 2 plugins provide the
                    # same functionalities, one of them can be disabled not to do duplicated work)
                    self.console.queueEvent(self.console.getEvent('EVT_PLUGIN_LOADED', data=p.name))
                    # track down all the plugins that we are enabling so we can rollback
                    # changes if a plugin in the dependency tree fails to load/start
                    rollback.append(p.name)

            except b3.exceptions.MissingRequirement:
                # here we do not have to rollback
                client.message('^7Plugin ^1%s can\'t be loaded due to unmet dependencies' % name)
                client.message('^7Please inspect your b3 log file for more information')
            except Exception, e:
                # here we rollback all the plugins loaded which are not needed anymore
                client.message('^7Could not load plugin ^1%s^7: %s' % (name, e))
                client.message('^7Please inspect your b3 log file for more information')
                self.error('plugin %s could not be loaded: %s' % (name, extract_tb(sys.exc_info()[2])))
                if rollback:
                    for name in rollback:
                        self.do_unload(client=client, name=name)