# -*- coding: utf-8 -*- # Copyright © 2005 Lateef Alabi-Oki # # This file is part of Scribes. # # Scribes 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; either version 2 of the License, or # (at your option) any later version. # # Scribes 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 Scribes; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 # USA """ This module documents a class that creates a plug-in manager for the text editor. @author: Lateef Alabi-Oki @organization: The Scribes Project @copyright: Copyright © 2005 Lateef Alabi-Oki @license: GNU GPLv2 or Later @contact: mystilleef@gmail.com """ from gobject import GObject, SIGNAL_RUN_LAST, TYPE_NONE class ScribesPluginManager(GObject): """ This class creates an object that manages plug-ins for the text editor. The core function of the object is to load and unload plug-ins. """ __gsignals__ = { "processed": (SIGNAL_RUN_LAST, TYPE_NONE, ()), } def __init__(self, editor): """ Initialize the plug-in manager. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param editor: Reference to the text editor. @type editor: An Editor object. """ try: from Exceptions import PluginError GObject.__init__(self) self.__init_attributes(editor) from gobject import idle_add, PRIORITY_LOW idle_add(self.__optimize_methods, priority=PRIORITY_LOW) self.signal_id_1 = self.connect("processed", self.__manager_processed_cb) self.signal_id_2 = self.__editor.connect("loading-plugins", self.__manager_loading_plugins_cb) self.signal_id_3 = self.__editor.connect("loaded-plugins", self.__manager_loaded_plugins_cb) self.signal_id_4 = self.__editor.connect("close-document", self.__manager_close_document_cb) self.signal_id_5 = self.__editor.connect("close-document-no-save", self.__manager_close_document_cb) # Check for plug-in folders. if self.__plugin_folders_exist() is False: raise PluginError # Check for plug-in modules. if self.__plugin_modules_exist() is False: raise PluginError self.__set_plugin_search_path() # Load plug-in modules. self.__load_plugins() self.__create_triggers() self.signal_id_6 = self.__unload_plugin_trigger.connect("activate", self.__manager_trigger_cb) self.signal_id_7 = self.__reload_plugin_trigger.connect("activate", self.__manager_trigger_cb) except PluginError: self.__destroy() print "Error: Plug-ins were not loaded" print "Error: Plug-in manager died a horrible death." def __init_attributes(self, editor): """ Initialize the object's data attributes. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param editor: Reference to the text editor. @type editor: An Editor object. """ # Reference to the editor. self.__editor = editor # List of active plug-ins. self.__active_plugins = [] # List of inactive plug-ins. self.__inactive_plugins = [] # Modules with loaded plug-in. self.__active_modules = [] # Modules with unloaded plug-ins. self.__inactive_modules = [] # Modules with plug-ins that failed to load. self.__failed_modules = [] # Third party modules found (~/.gnome2/scribes/plugins). self.__found_home_modules = None # Core modules found (${prefix}/share/scribes/plugins). self.__found_core_modules = None # Total number of modules. self.__number_of_modules = 0 # Number of modules processed. self.__processed_modules = 0 self.__unload_plugin_trigger = None self.__reload_plugin_trigger = None # Status ID. self.__status_id = None self.signal_id_1 = None self.signal_id_2 = None self.signal_id_3 = None self.signal_id_4 = None self.signal_id_5 = None self.signal_id_6 = None self.signal_id_7 = None self.__registration_id = self.__editor.register_termination_id() return def __plugin_folders_exist(self): """ Check if plug-in folders exist. There are two plug-in folders: ~/.gnome2/scribes/plugins and ${prefix}/share/scribes/plugins. Each plug-in folders should contain __init__.py. If __init__.py is not found, it is created when possible. If __init__.py cannot be created, plug-ins will not be loaded. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @return: True if plug-in folders are found. @rtype: A Boolean object. """ from info import home_plugin_folder, core_plugin_folder from os import path try: filename = home_plugin_folder + "/__init__.py" if path.exists(filename) is False: if path.exists(home_plugin_folder) is False: from os import makedirs makedirs(home_plugin_folder) try: fd = open(filename, "w") fd.close() except IOError: return False filename = core_plugin_folder + "/__init__.py" if not path.exists(filename) is False: #raise Exception pass except: return False return True def __plugin_modules_exist(self): """ Account for found plug-in modules. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @return: True if plug-in modules are found. @rtype: A Boolean object. """ from info import home_plugin_folder, core_plugin_folder self.__found_home_modules = self.__get_plugin_modules(home_plugin_folder) self.__found_core_modules = self.__get_plugin_modules(core_plugin_folder) return True def __get_plugin_modules(self, plugin_folder): """ Get all plug-in modules in a folder. Valid plug-in modules are python files that start with "Plugin" and end with ".py". @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param plugin_folder: A plug-in folder. @type plugin_folder: A String object. @return: A list of strings representing plug-in modules. @rtype: A List object. """ from os import listdir from itertools import ifilter, imap get_module_name = lambda filename: filename[:-3] iterator = iter(listdir(plugin_folder)) plugin_files = imap(get_module_name, ifilter(self.__get_valid_plugin_filename, iterator)) return plugin_files def __get_valid_plugin_filename(self, filename): from operator import is_ if is_(filename.startswith("Plugin"), False) or is_(filename.endswith(".py"), False): # Ignore filenames that do not start with "Plugin" or end # end with ".py" return False return True def __set_plugin_search_path(self): """ Add plug-in folders to Python's search path. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. """ from info import home_plugin_folder, core_plugin_folder from sys import path from operator import contains, not_, gt if not_(contains(path, core_plugin_folder)): path.insert(0, core_plugin_folder) if not_(contains(path, home_plugin_folder)): path.insert(0, home_plugin_folder) if gt(path.count("/usr/bin"), 1): path.remove("/usr/bin") return def __load_plugins(self): """ Load found plug-ins. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. """ self.__editor.emit("loading-plugins") from info import home_plugin_folder, core_plugin_folder from gobject import idle_add, PRIORITY_LOW if self.__found_core_modules: idle_add(self.__process_modules, self.__found_core_modules, core_plugin_folder, priority=PRIORITY_LOW) if self.__found_home_modules: idle_add(self.__process_modules, self.__found_home_modules, home_plugin_folder, priority=PRIORITY_LOW) return def __process_modules(self, modules, plugin_folder): """ Process modules. @param modules: A list of modules to be processed. @type modules: A List object. @param plugin_folder: Path to the plug-in to process. @type plugin_folder: A String object. """ from gobject import idle_add, PRIORITY_LOW for module in modules: self.__editor.response() idle_add(self.__initialize_plugin, module, plugin_folder, priority=PRIORITY_LOW) self.__editor.response() return False def __initialize_plugin(self, module_name, plugin_folder): """ Initialize necessary plug-ins. This function determines what plug-ins to activate. Checks are also performed to filter invalid modules or problematic plug-ins. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param module: A plug-in module. @type module: A Module object. @param home_plugin_folder: Third party plug-in folder. @type home_plugin_folder: A String object. """ try: from Exceptions import PluginError from imp import load_source file_path = plugin_folder + "/" + module_name + ".py" module = load_source(module_name, file_path) from operator import is_, truth if is_(hasattr(module, "autoload"), False): raise PluginError if truth(getattr(module, "autoload")): if is_(self.__activate_plugin(module), False): raise PluginError else: self.__inactive_modules.append(module) self.emit("processed") self.__editor.response() except PluginError: print "Error while loading plugin: ", module_name return False def __activate_plugin(self, module): """ Load a plug-in. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param module: Module containing a plug-in initialization class/ @type module: A Module object. @return: True if initialization is successful. @rtype: A Boolean object. """ try: value = False from Exceptions import PluginError if self.__can_activate_plugin(module) is False: raise PluginError version = getattr(module, "version") class_name = getattr(module, "class_name") if self.__duplicate_plugin_found(class_name): if self.__unload_duplicate_plugin(class_name, version) is False: raise PluginError PluginClass = getattr(module, class_name) plugin_object = PluginClass(self.__editor) plugin_object.load() plugin_object.scribes_plugin_module = module plugin_object.scribes_plugin_name = class_name plugin_object.scribes_plugin_version = version self.__active_plugins.append(plugin_object) self.__active_modules.append(module) value = True except PluginError: pass return value def __can_activate_plugin(self, module): """ Check if a plug-in can be initialized. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param module: Modules containing a plug-in initialization class. @type module: A Module object. @return: True if a plug-in can be initialized. @rtype: A Boolean object. """ try: if hasattr(module, "version") is False: raise Exception version = getattr(module, "version") if hasattr(module, "class_name") is False: raise Exception class_name = getattr(module, "class_name") if hasattr(module, class_name) is False: raise Exception PluginClass = getattr(module, class_name) if hasattr(PluginClass, "__init__") is False: raise Exception if hasattr(PluginClass, "load") is False: raise Exception if hasattr(PluginClass, "unload") is False: raise Exception except: return False return True def __duplicate_plugin_found(self, name): """ Check for possible duplicate plug-ins. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param name: Name of a plug-in class. @type name: A String object. @return: True if duplicate plug-ins are found. @rtype: A Boolean object. """ for plugin_object in self.__active_plugins: if name == plugin_object.scribes_plugin_name: return True return False def __unload_duplicate_plugin(self, name, version): """ Try to unload duplicate active plug-in. The plug-in with the newer version number will be loaded. The plug-in with the lower version number will be unloaded. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param version: The version of the module to be loaded. @type version: A String object. @return: True if duplicate active module is successfully unloaded. @rtype: A Boolean object. """ for plugin_object in self.__active_plugins: if name == plugin_object.scribes_plugin_name: if float(version) >= float(plugin_object.scribes_plugin_version): self.__remove_plugin(plugin_object.scribes_plugin_module) self.__editor.response() return True break self.__editor.response() return False def __deactivate_plugin(self, module): """ Unload a plug-in. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param module: Module containing a plug-in initialization class/ @type module: A Module object. @return: True if initialization is successful. @rtype: A Boolean object. """ try: for plugin_object in self.__active_plugins: if plugin_object.scribes_plugin_module == module: plugin_object.unload() self.__active_plugins.remove(plugin_object) self.__active_modules.remove(module) self.__inactive_modules.append(module) self.__inactive_plugins.append(plugin_object) break except: self.__editor.response() return False self.__editor.response() return True def __remove_plugin(self, module): """ Unload a plug-in. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param module: Module containing a plug-in initialization class/ @type module: A Module object. @return: True if initialization is successful. @rtype: A Boolean object. """ try: for plugin_object in self.__active_plugins: if plugin_object.scribes_plugin_module == module: plugin_object.unload() self.__active_plugins.remove(plugin_object) self.__active_modules.remove(module) del plugin_object del module plugin_object = None module = None break self.__editor.response() except: return False return True def __unload_plugins(self): """ Unload all plugins @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. """ self.__number_of_modules = 0 self.__processed_modules = 0 if self.__active_modules: for module in self.__active_modules: del module module = None self.__active_modules = [] if self.__inactive_modules: for module in self.__inactive_modules: del module module = None self.__inactive_modules = [] if self.__active_plugins: for plugin in self.__active_plugins: plugin.unload() del plugin plugin = None self.__editor.response() self.__active_plugins = [] if self.__inactive_plugins: for plugin in self.__inactive_plugins: del plugin plugin = None self.__inactive_plugins = [] if self.__found_home_modules: for module in self.__found_home_modules: del module module = None self.__found_home_modules = None if self.__found_core_modules: for module in self.__found_core_modules: del module module = None self.__found_core_modules = None if self.__failed_modules: for module in self.__failed_modules: del module module = None self.__failed_modules = [] self.__editor.response() # print "Unloaded plugins" return False def __reload_plugins(self): """ Reload all plugins. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. """ self.__unload_plugins() if self.__plugin_folders_exist() and self.__plugin_modules_exist(): self.__load_plugins() # print "Reloaded plugins" return False def __create_triggers(self): """ Create triggers to unload/reload plugins. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. """ # Trigger to unload plugins. from trigger import Trigger self.__unload_plugin_trigger = Trigger("unload_plugins") self.__editor.triggermanager.add_trigger(self.__unload_plugin_trigger, "alt - shift - Return") # Trigger to reload plugins. from trigger import Trigger self.__reload_plugin_trigger = Trigger("reload_plugins") self.__editor.triggermanager.add_trigger(self.__reload_plugin_trigger, "alt - Return") return def __optimize_methods(self): try: from psyco import bind bind(self.__process_modules) bind(self.__initialize_plugin) except ImportError: pass return False ######################################################################## # # Event and Signal Handlers # ######################################################################## def __manager_processed_cb(self, plugin_manager): """ Handles callback when "processed" signal is emitted. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param plugin_manager: Reference to the ScribesPluginManager. @type plugin_manager: A ScribesPluginManager object. """ self.__processed_modules += 1 module_list = [self.__active_modules, self.__inactive_modules, self.__failed_modules] if self.__processed_modules == sum(map(len, module_list)): self.__editor.emit("loaded-plugins") return def __manager_loading_plugins_cb(self, editor): """ Handles callback when the "loading-plugins" signals are emitted. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param editor: Reference to the text editor. @type editor: An Editor object. """ # self.__status_id = self.__editor.feedback.set_modal_message("Loading plug-ins", "run") return def __manager_loaded_plugins_cb(self, editor): """ Handles callback when the "loaded-plugins" signals are emitted. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param editor: Reference to the text editor. @type editor: An Editor object. """ # self.__editor.feedback.unset_modal_message(self.__status_id, False) # self.__editor.feedback.update_status_message("Loaded plug-ins", "info") return def __manager_close_document_cb(self, editor): """ Handles callback when the "close-document" or "close-document-no-save" signals are emitted. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param editor: Reference to the text editor. @type editor: An Editor object. """ self.__destroy() return def __manager_trigger_cb(self, trigger): """ Handles callback when the "activate" signal is called. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. @param trigger: A trigger to unload or reload plugins. @type trigger: A Trigger object. """ if trigger == self.__unload_plugin_trigger: self.__unload_plugins() else: self.__reload_plugins() return def __destroy(self): """ Destroy the plug-in manager. @param self: Reference to the ScribesPluginManager instance. @type self: A ScribesPluginManager object. """ self.__unload_plugins() self.__status_id = None if self.signal_id_1 and self.handler_is_connected(self.signal_id_1): self.disconnect(self.signal_id_1) if self.signal_id_2 and self.__editor.handler_is_connected(self.signal_id_2): self.__editor.disconnect(self.signal_id_2) if self.signal_id_3 and self.__editor.handler_is_connected(self.signal_id_3): self.__editor.disconnect(self.signal_id_3) if self.signal_id_4 and self.__editor.handler_is_connected(self.signal_id_4): self.__editor.disconnect(self.signal_id_4) if self.signal_id_5 and self.__editor.handler_is_connected(self.signal_id_5): self.__editor.disconnect(self.signal_id_5) if self.signal_id_6 and self.__unload_plugin_trigger.handler_is_connected(self.signal_id_6): self.__unload_plugin_trigger.disconnect(self.signal_id_6) if self.signal_id_7 and self.__reload_plugin_trigger.handler_is_connected(self.signal_id_7): self.__reload_plugin_trigger.disconnect(self.signal_id_7) del self.__active_plugins, self.__inactive_plugins del self.__active_modules, self.__inactive_modules del self.__failed_modules, self.__found_home_modules del self.__found_core_modules del self.__number_of_modules, self.__processed_modules del self.signal_id_1, self.signal_id_2, self.signal_id_3 del self.signal_id_4, self.signal_id_5, self.__status_id del self.__unload_plugin_trigger, self.__reload_plugin_trigger del self.signal_id_6, self.signal_id_7 if self.__registration_id: self.__editor.unregister_termination_id(self.__registration_id) self.__registration_id = None del self.__editor, self.__registration_id, self self = None # print "Destroyed Plug-in Manager" return