# -*- 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 .
##
'''
Plug-in management related classes.
:author: Mateusz Biliński
:since: 05/30/2008
:copyright: Copyright (2008) Mateusz Biliński
: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