231 lines
7.0 KiB
Python
231 lines
7.0 KiB
Python
# -*- 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/>.
|
|
##
|
|
|
|
'''
|
|
Plug-in management related classes.
|
|
|
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
|
:since: 05/30/2008
|
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
|
:license: GPL
|
|
'''
|
|
|
|
__all__ = ['PluginManager']
|
|
|
|
import os
|
|
import sys
|
|
import fnmatch
|
|
|
|
import common.gajim as gajim
|
|
|
|
from plugins.helpers import log, log_calls, Singleton
|
|
from plugins.plugin import GajimPlugin
|
|
|
|
class PluginManager(object):
|
|
'''
|
|
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
|
|
after GUI object creation)
|
|
:todo: implement mechanism to dynamically deactive plugins (call plugin's
|
|
deactivation handler)
|
|
'''
|
|
|
|
__metaclass__ = Singleton
|
|
|
|
@log_calls('PluginManager')
|
|
def __init__(self):
|
|
self.plugins = []
|
|
'''
|
|
Detected plugin classes.
|
|
|
|
Each class object in list is `GajimPlugin` subclass.
|
|
|
|
:type: [] of class objects
|
|
'''
|
|
self.active_plugins = []
|
|
'''
|
|
Objectsof active plugins.
|
|
|
|
These are object instances of classes held `plugins`, but only those
|
|
that were activated.
|
|
|
|
:type: [] of `GajimPlugin` based objects
|
|
'''
|
|
self.gui_extension_points = {}
|
|
'''
|
|
Registered GUI extension points.
|
|
'''
|
|
|
|
self.gui_extension_points_handlers = {}
|
|
'''
|
|
Registered handlers of GUI extension points.
|
|
'''
|
|
|
|
for path in gajim.PLUGINS_DIRS:
|
|
self.plugins.extend(PluginManager.scan_dir_for_plugins(path))
|
|
|
|
log.debug('plugins: %s'%(self.plugins))
|
|
|
|
#self._activate_all_plugins()
|
|
|
|
log.debug('active: %s'%(self.active_plugins))
|
|
|
|
@log_calls('PluginManager')
|
|
def gui_extension_point(self, gui_extpoint_name, *args):
|
|
'''
|
|
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.
|
|
|
|
'''
|
|
|
|
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]:
|
|
handlers[0](*args)
|
|
|
|
@log_calls('PluginManager')
|
|
def _activate_plugin(self, plugin):
|
|
'''
|
|
:param plugin: plugin to be activated
|
|
:type plugin: class object of `GajimPlugin` subclass
|
|
'''
|
|
|
|
plugin_object = plugin()
|
|
|
|
success = True
|
|
|
|
self._add_gui_extension_points_handlers_from_plugin(plugin_object)
|
|
self._handle_all_gui_extension_points_with_plugin(plugin_object)
|
|
|
|
if success:
|
|
self.active_plugins.append(plugin_object)
|
|
|
|
return success
|
|
|
|
@log_calls('PluginManager')
|
|
def _add_gui_extension_points_handlers_from_plugin(self, plugin_object):
|
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
|
plugin_object.gui_extension_points.iteritems():
|
|
self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append(
|
|
gui_extpoint_handlers)
|
|
|
|
@log_calls('PluginManager')
|
|
def _handle_all_gui_extension_points_with_plugin(self, plugin_object):
|
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
|
plugin_object.gui_extension_points.iteritems():
|
|
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)
|
|
|
|
@log_calls('PluginManager')
|
|
def _activate_all_plugins(self):
|
|
'''
|
|
Activates all plugins in `plugins`.
|
|
|
|
Activated plugins are appended to `active_plugins` list.
|
|
'''
|
|
self.active_plugins = []
|
|
for plugin in self.plugins:
|
|
self._activate_plugin(plugin)
|
|
|
|
@staticmethod
|
|
@log_calls('PluginManager')
|
|
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
|
|
'''
|
|
plugins_found = []
|
|
if os.path.isdir(path):
|
|
dir_list = os.listdir(path)
|
|
log.debug(dir_list)
|
|
|
|
sys.path.insert(0, path)
|
|
log.debug(sys.path)
|
|
|
|
for file_name in fnmatch.filter(dir_list, '*.py'):
|
|
log.debug('- "%s"'%(file_name))
|
|
file_path = os.path.join(path, file_name)
|
|
log.debug(' "%s"'%(file_path))
|
|
if os.path.isfile(file_path):
|
|
module_name = os.path.splitext(file_name)[0]
|
|
module = __import__(module_name)
|
|
for module_attr_name in [f_name for f_name in dir(module)
|
|
if not (f_name.startswith('__') or
|
|
f_name.endswith('__'))]:
|
|
module_attr = getattr(module, module_attr_name)
|
|
log.debug('%s : %s'%(module_attr_name, module_attr))
|
|
|
|
try:
|
|
if issubclass(module_attr, GajimPlugin) and \
|
|
not module_attr is GajimPlugin:
|
|
log.debug('is subclass of GajimPlugin')
|
|
plugins_found.append(module_attr)
|
|
except TypeError, type_error:
|
|
log.debug('module_attr: %s, error : %s'%(
|
|
module_name+'.'+module_attr_name,
|
|
type_error))
|
|
|
|
log.debug(module)
|
|
|
|
return plugins_found |