2008-06-02 01:33:51 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
## This file is part of Gajim.
|
|
|
|
##
|
|
|
|
## Gajim is free software; you can redistribute it and/or modify
|
|
|
|
## it under the terms of the GNU General Public License as published
|
|
|
|
## by the Free Software Foundation; version 3 only.
|
|
|
|
##
|
|
|
|
## Gajim is distributed in the hope that it will be useful,
|
|
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
## GNU General Public License for more details.
|
|
|
|
##
|
|
|
|
## You should have received a copy of the GNU General Public License
|
|
|
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
##
|
|
|
|
|
|
|
|
'''
|
2008-06-03 01:15:08 +02:00
|
|
|
Plug-in management related classes.
|
2008-06-02 01:33:51 +02:00
|
|
|
|
|
|
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
2008-06-18 22:45:22 +02:00
|
|
|
:since: 30th May 2008
|
2008-06-02 01:33:51 +02:00
|
|
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
|
|
|
:license: GPL
|
|
|
|
'''
|
|
|
|
|
|
|
|
__all__ = ['PluginManager']
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import fnmatch
|
|
|
|
|
|
|
|
import common.gajim as gajim
|
|
|
|
|
2008-06-03 01:15:08 +02:00
|
|
|
from plugins.helpers import log, log_calls, Singleton
|
|
|
|
from plugins.plugin import GajimPlugin
|
2008-06-02 01:33:51 +02:00
|
|
|
|
|
|
|
class PluginManager(object):
|
2008-06-03 01:15:08 +02:00
|
|
|
'''
|
|
|
|
Main plug-in management class.
|
|
|
|
|
|
|
|
Currently:
|
|
|
|
- scans for plugins
|
|
|
|
- activates them
|
|
|
|
- handles GUI extension points, when called by GUI objects after plugin
|
|
|
|
is activated (by dispatching info about call to handlers in plugins)
|
|
|
|
|
|
|
|
:todo: add more info about how GUI extension points work
|
|
|
|
:todo: add list of available GUI extension points
|
|
|
|
:todo: implement mechanism to dynamically load plugins where GUI extension
|
|
|
|
points have been already called (i.e. when plugin is activated
|
2008-06-03 15:40:27 +02:00
|
|
|
after GUI object creation). [DONE?]
|
2008-06-03 01:15:08 +02:00
|
|
|
:todo: implement mechanism to dynamically deactive plugins (call plugin's
|
2008-06-03 15:40:27 +02:00
|
|
|
deactivation handler) [DONE?]
|
|
|
|
:todo: when plug-in is deactivated all GUI extension points are removed
|
|
|
|
from `PluginManager.gui_extension_points_handlers`. But when
|
|
|
|
object that invoked GUI extension point is abandoned by Gajim, eg.
|
|
|
|
closed ChatControl object, the reference to called GUI extension
|
|
|
|
points is still in `PluginManager.gui_extension_points`. These
|
|
|
|
should be removed, so that object can be destroyed by Python.
|
|
|
|
Possible solution: add call to clean up method in classes
|
|
|
|
'destructors' (classes that register GUI extension points)
|
2008-06-03 01:15:08 +02:00
|
|
|
'''
|
|
|
|
|
2008-06-02 01:33:51 +02:00
|
|
|
__metaclass__ = Singleton
|
|
|
|
|
2008-06-03 15:40:27 +02:00
|
|
|
#@log_calls('PluginManager')
|
2008-06-02 01:33:51 +02:00
|
|
|
def __init__(self):
|
|
|
|
self.plugins = []
|
2008-06-03 01:15:08 +02:00
|
|
|
'''
|
|
|
|
Detected plugin classes.
|
|
|
|
|
|
|
|
Each class object in list is `GajimPlugin` subclass.
|
|
|
|
|
|
|
|
:type: [] of class objects
|
|
|
|
'''
|
|
|
|
self.active_plugins = []
|
|
|
|
'''
|
2008-06-03 15:40:27 +02:00
|
|
|
Instance objects of active plugins.
|
2008-06-03 01:15:08 +02:00
|
|
|
|
|
|
|
These are object instances of classes held `plugins`, but only those
|
|
|
|
that were activated.
|
|
|
|
|
|
|
|
:type: [] of `GajimPlugin` based objects
|
|
|
|
'''
|
2008-06-02 01:33:51 +02:00
|
|
|
self.gui_extension_points = {}
|
2008-06-03 01:15:08 +02:00
|
|
|
'''
|
|
|
|
Registered GUI extension points.
|
|
|
|
'''
|
2008-06-03 10:25:16 +02:00
|
|
|
|
|
|
|
self.gui_extension_points_handlers = {}
|
|
|
|
'''
|
|
|
|
Registered handlers of GUI extension points.
|
|
|
|
'''
|
2008-06-02 01:33:51 +02:00
|
|
|
|
|
|
|
for path in gajim.PLUGINS_DIRS:
|
2008-06-18 22:45:22 +02:00
|
|
|
self.add_plugins(PluginManager.scan_dir_for_plugins(path))
|
2008-06-02 01:33:51 +02:00
|
|
|
|
|
|
|
log.debug('plugins: %s'%(self.plugins))
|
2008-06-03 01:15:08 +02:00
|
|
|
|
2008-06-18 22:45:22 +02:00
|
|
|
self._activate_all_plugins_from_global_config()
|
2008-06-03 01:15:08 +02:00
|
|
|
|
|
|
|
log.debug('active: %s'%(self.active_plugins))
|
|
|
|
|
2008-06-18 22:45:22 +02:00
|
|
|
@log_calls('PluginManager')
|
|
|
|
def _plugin_has_entry_in_global_config(self, plugin):
|
|
|
|
if gajim.config.get_per('plugins', plugin.short_name) is None:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
@log_calls('PluginManager')
|
|
|
|
def _create_plugin_entry_in_global_config(self, plugin):
|
|
|
|
gajim.config.add_per('plugins', plugin.short_name)
|
2008-06-07 19:28:34 +02:00
|
|
|
|
|
|
|
@log_calls('PluginManager')
|
2008-06-18 22:45:22 +02:00
|
|
|
def add_plugin(self, plugin_class):
|
2008-06-07 19:28:34 +02:00
|
|
|
'''
|
|
|
|
:todo: what about adding plug-ins that are already added? Module reload
|
|
|
|
and adding class from reloaded module or ignoring adding plug-in?
|
|
|
|
'''
|
2008-06-18 22:45:22 +02:00
|
|
|
plugin = plugin_class()
|
|
|
|
if not self._plugin_has_entry_in_global_config(plugin):
|
|
|
|
self._create_plugin_entry_in_global_config(plugin)
|
|
|
|
|
|
|
|
self.plugins.append(plugin)
|
|
|
|
plugin.active = False
|
2008-06-07 19:28:34 +02:00
|
|
|
|
|
|
|
@log_calls('PluginManager')
|
2008-06-18 22:45:22 +02:00
|
|
|
def add_plugins(self, plugin_classes):
|
2008-06-07 19:28:34 +02:00
|
|
|
for plugin_class in plugin_classes:
|
2008-06-18 22:45:22 +02:00
|
|
|
self.add_plugin(plugin_class)
|
2008-06-07 19:28:34 +02:00
|
|
|
|
2008-06-02 01:33:51 +02:00
|
|
|
@log_calls('PluginManager')
|
|
|
|
def gui_extension_point(self, gui_extpoint_name, *args):
|
2008-06-03 01:15:08 +02:00
|
|
|
'''
|
|
|
|
Invokes all handlers (from plugins) for particular GUI extension point.
|
|
|
|
|
|
|
|
:param gui_extpoint_name: name of GUI extension point.
|
|
|
|
:type gui_extpoint_name: unicode
|
|
|
|
:param args: parameters to be passed to extension point handlers
|
|
|
|
(typically and object that invokes `gui_extension_point`; however,
|
|
|
|
this can be practically anything)
|
|
|
|
:type args: tuple
|
|
|
|
|
|
|
|
:todo: GUI extension points must be documented well - names with
|
|
|
|
parameters that will be passed to handlers (in plugins). Such
|
|
|
|
documentation must be obeyed both in core and in plugins. This
|
|
|
|
is a loosely coupled approach and is pretty natural in Python.
|
|
|
|
|
|
|
|
:bug: what if only some handlers are successfully connected? we should
|
|
|
|
revert all those connections that where successfully made. Maybe
|
|
|
|
call 'self._deactivate_plugin()' or sth similar.
|
|
|
|
Looking closer - we only rewrite tuples here. Real check should be
|
|
|
|
made in method that invokes gui_extpoints handlers.
|
|
|
|
'''
|
|
|
|
|
2008-06-03 10:25:16 +02:00
|
|
|
self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args)
|
|
|
|
self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, *args)
|
|
|
|
|
|
|
|
@log_calls('PluginManager')
|
|
|
|
def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args):
|
|
|
|
self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args)
|
|
|
|
|
|
|
|
@log_calls('PluginManager')
|
|
|
|
def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, *args):
|
|
|
|
if gui_extpoint_name in self.gui_extension_points_handlers:
|
|
|
|
for handlers in self.gui_extension_points_handlers[gui_extpoint_name]:
|
2008-06-02 01:33:51 +02:00
|
|
|
handlers[0](*args)
|
|
|
|
|
|
|
|
@log_calls('PluginManager')
|
2008-06-18 22:45:22 +02:00
|
|
|
def activate_plugin(self, plugin):
|
2008-06-02 01:33:51 +02:00
|
|
|
'''
|
2008-06-03 01:15:08 +02:00
|
|
|
:param plugin: plugin to be activated
|
|
|
|
:type plugin: class object of `GajimPlugin` subclass
|
2008-06-02 01:33:51 +02:00
|
|
|
|
2008-06-18 22:45:22 +02:00
|
|
|
:todo: success checks should be implemented using exceptions. Such
|
|
|
|
control should also be implemented in deactivation.
|
|
|
|
'''
|
|
|
|
success = False
|
|
|
|
if not plugin.active:
|
|
|
|
|
|
|
|
self._add_gui_extension_points_handlers_from_plugin(plugin)
|
|
|
|
self._handle_all_gui_extension_points_with_plugin(plugin)
|
|
|
|
|
|
|
|
success = True
|
|
|
|
|
|
|
|
if success:
|
|
|
|
self.active_plugins.append(plugin)
|
|
|
|
plugin.activate()
|
|
|
|
self._set_plugin_active_in_global_config(plugin)
|
|
|
|
plugin.active = True
|
2008-06-03 01:15:08 +02:00
|
|
|
|
2008-06-02 01:33:51 +02:00
|
|
|
return success
|
2008-06-03 10:25:16 +02:00
|
|
|
|
2008-06-18 22:45:22 +02:00
|
|
|
def deactivate_plugin(self, plugin):
|
2008-06-03 15:40:27 +02:00
|
|
|
# detaching plug-in from handler GUI extension points (calling
|
|
|
|
# cleaning up method that must be provided by plug-in developer
|
|
|
|
# for each handled GUI extension point)
|
|
|
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
2008-06-18 22:45:22 +02:00
|
|
|
plugin.gui_extension_points.iteritems():
|
2008-06-07 19:28:34 +02:00
|
|
|
if gui_extpoint_name in self.gui_extension_points:
|
|
|
|
for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]:
|
|
|
|
gui_extpoint_handlers[1](*gui_extension_point_args)
|
2008-06-03 15:40:27 +02:00
|
|
|
|
|
|
|
# remove GUI extension points handlers (provided by plug-in) from
|
|
|
|
# handlers list
|
|
|
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
2008-06-18 22:45:22 +02:00
|
|
|
plugin.gui_extension_points.iteritems():
|
2008-06-03 15:40:27 +02:00
|
|
|
self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers)
|
|
|
|
|
|
|
|
# removing plug-in from active plug-ins list
|
2008-06-18 22:45:22 +02:00
|
|
|
plugin.deactivate()
|
|
|
|
self.active_plugins.remove(plugin)
|
|
|
|
self._set_plugin_active_in_global_config(plugin, False)
|
|
|
|
plugin.active = False
|
2008-06-03 15:40:27 +02:00
|
|
|
|
2008-06-18 22:45:22 +02:00
|
|
|
def _deactivate_all_plugins(self):
|
2008-06-03 15:40:27 +02:00
|
|
|
for plugin_object in self.active_plugins:
|
|
|
|
self.deactivate_plugin(plugin_object)
|
|
|
|
|
2008-06-03 10:25:16 +02:00
|
|
|
@log_calls('PluginManager')
|
2008-06-18 22:45:22 +02:00
|
|
|
def _add_gui_extension_points_handlers_from_plugin(self, plugin):
|
2008-06-03 10:25:16 +02:00
|
|
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
2008-06-18 22:45:22 +02:00
|
|
|
plugin.gui_extension_points.iteritems():
|
2008-06-03 10:25:16 +02:00
|
|
|
self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append(
|
|
|
|
gui_extpoint_handlers)
|
|
|
|
|
|
|
|
@log_calls('PluginManager')
|
2008-06-18 22:45:22 +02:00
|
|
|
def _handle_all_gui_extension_points_with_plugin(self, plugin):
|
2008-06-03 10:25:16 +02:00
|
|
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
2008-06-18 22:45:22 +02:00
|
|
|
plugin.gui_extension_points.iteritems():
|
2008-06-03 10:25:16 +02:00
|
|
|
if gui_extpoint_name in self.gui_extension_points:
|
|
|
|
for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]:
|
|
|
|
gui_extpoint_handlers[0](*gui_extension_point_args)
|
2008-06-03 01:15:08 +02:00
|
|
|
|
2008-06-02 01:33:51 +02:00
|
|
|
@log_calls('PluginManager')
|
2008-06-18 22:45:22 +02:00
|
|
|
def _activate_all_plugins(self):
|
2008-06-03 01:15:08 +02:00
|
|
|
'''
|
|
|
|
Activates all plugins in `plugins`.
|
|
|
|
|
|
|
|
Activated plugins are appended to `active_plugins` list.
|
|
|
|
'''
|
2008-06-18 22:45:22 +02:00
|
|
|
#self.active_plugins = []
|
2008-06-02 01:33:51 +02:00
|
|
|
for plugin in self.plugins:
|
2008-06-03 15:40:27 +02:00
|
|
|
self.activate_plugin(plugin)
|
2008-06-18 22:45:22 +02:00
|
|
|
|
|
|
|
def _activate_all_plugins_from_global_config(self):
|
|
|
|
for plugin in self.plugins:
|
|
|
|
if self._plugin_is_active_in_global_config(plugin):
|
|
|
|
self.activate_plugin(plugin)
|
|
|
|
|
|
|
|
def _plugin_is_active_in_global_config(self, plugin):
|
|
|
|
return gajim.config.get_per('plugins', plugin.short_name, 'active')
|
|
|
|
|
|
|
|
def _set_plugin_active_in_global_config(self, plugin, active=True):
|
|
|
|
gajim.config.set_per('plugins', plugin.short_name, 'active', active)
|
2008-06-02 01:33:51 +02:00
|
|
|
|
2008-06-03 01:15:08 +02:00
|
|
|
@staticmethod
|
2008-06-02 01:33:51 +02:00
|
|
|
@log_calls('PluginManager')
|
2008-06-03 01:15:08 +02:00
|
|
|
def scan_dir_for_plugins(path):
|
|
|
|
'''
|
|
|
|
Scans given directory for plugin classes.
|
|
|
|
|
|
|
|
:param path: directory to scan for plugins
|
|
|
|
:type path: unicode
|
|
|
|
|
|
|
|
:return: list of found plugin classes (subclasses of `GajimPlugin`
|
|
|
|
:rtype: [] of class objects
|
|
|
|
|
|
|
|
:note: currently it only searches for plugin classes in '\*.py' files
|
|
|
|
present in given direcotory `path` (no recursion here)
|
|
|
|
|
|
|
|
:todo: add scanning packages
|
|
|
|
:todo: add scanning zipped modules
|
|
|
|
'''
|
2008-06-02 01:33:51 +02:00
|
|
|
plugins_found = []
|
|
|
|
if os.path.isdir(path):
|
|
|
|
dir_list = os.listdir(path)
|
2008-06-14 20:20:24 +02:00
|
|
|
#log.debug(dir_list)
|
2008-06-02 01:33:51 +02:00
|
|
|
|
|
|
|
sys.path.insert(0, path)
|
2008-06-14 20:20:24 +02:00
|
|
|
#log.debug(sys.path)
|
2008-06-02 01:33:51 +02:00
|
|
|
|
2008-06-14 20:20:24 +02:00
|
|
|
for elem_name in dir_list:
|
|
|
|
log.debug('- "%s"'%(elem_name))
|
|
|
|
file_path = os.path.join(path, elem_name)
|
2008-06-02 01:33:51 +02:00
|
|
|
log.debug(' "%s"'%(file_path))
|
2008-06-14 20:20:24 +02:00
|
|
|
|
|
|
|
module = None
|
|
|
|
|
|
|
|
if os.path.isfile(file_path) and fnmatch.fnmatch(file_path,'*.py'):
|
|
|
|
module_name = os.path.splitext(elem_name)[0]
|
|
|
|
log.debug('Possible module detected.')
|
|
|
|
try:
|
|
|
|
module = __import__(module_name)
|
|
|
|
log.debug('Module imported.')
|
|
|
|
except ValueError, value_error:
|
|
|
|
log.debug('Module not imported successfully. ValueError: %s'%(value_error))
|
|
|
|
except ImportError, import_error:
|
|
|
|
log.debug('Module not imported successfully. ImportError: %s'%(import_error))
|
|
|
|
|
|
|
|
elif os.path.isdir(file_path):
|
|
|
|
module_name = elem_name
|
|
|
|
file_path += os.path.sep
|
|
|
|
log.debug('Possible package detected.')
|
|
|
|
try:
|
|
|
|
module = __import__(module_name)
|
|
|
|
log.debug('Package imported.')
|
|
|
|
except ValueError, value_error:
|
|
|
|
log.debug('Package not imported successfully. ValueError: %s'%(value_error))
|
|
|
|
except ImportError, import_error:
|
|
|
|
log.debug('Package not imported successfully. ImportError: %s'%(import_error))
|
|
|
|
|
|
|
|
|
|
|
|
if module:
|
|
|
|
log.debug('Attributes processing started')
|
|
|
|
for module_attr_name in [attr_name for attr_name in dir(module)
|
|
|
|
if not (attr_name.startswith('__') or
|
|
|
|
attr_name.endswith('__'))]:
|
2008-06-02 01:33:51 +02:00
|
|
|
module_attr = getattr(module, module_attr_name)
|
|
|
|
log.debug('%s : %s'%(module_attr_name, module_attr))
|
2008-06-14 20:20:24 +02:00
|
|
|
|
2008-06-02 01:33:51 +02:00
|
|
|
try:
|
|
|
|
if issubclass(module_attr, GajimPlugin) and \
|
2008-06-03 01:15:08 +02:00
|
|
|
not module_attr is GajimPlugin:
|
2008-06-02 01:33:51 +02:00
|
|
|
log.debug('is subclass of GajimPlugin')
|
2008-06-14 20:20:24 +02:00
|
|
|
#log.debug('file_path: %s\nabspath: %s\ndirname: %s'%(file_path, os.path.abspath(file_path), os.path.dirname(os.path.abspath(file_path))))
|
|
|
|
#log.debug('file_path: %s\ndirname: %s\nabspath: %s'%(file_path, os.path.dirname(file_path), os.path.abspath(os.path.dirname(file_path))))
|
|
|
|
module_attr.__path__ = os.path.abspath(os.path.dirname(file_path))
|
2008-06-02 01:33:51 +02:00
|
|
|
plugins_found.append(module_attr)
|
2008-06-03 01:15:08 +02:00
|
|
|
except TypeError, type_error:
|
2008-06-02 01:33:51 +02:00
|
|
|
log.debug('module_attr: %s, error : %s'%(
|
2008-06-03 01:15:08 +02:00
|
|
|
module_name+'.'+module_attr_name,
|
|
|
|
type_error))
|
2008-06-02 01:33:51 +02:00
|
|
|
|
|
|
|
log.debug(module)
|
|
|
|
|
|
|
|
return plugins_found
|