summaryrefslogtreecommitdiff
path: root/poezio/plugin_manager.py
diff options
context:
space:
mode:
authorEmmanuel Gil Peyrot <linkmauve@linkmauve.fr>2016-03-31 18:54:41 +0100
committerEmmanuel Gil Peyrot <linkmauve@linkmauve.fr>2016-06-11 20:49:43 +0100
commit332a5c2553db41de777473a1e1be9cd1522c9496 (patch)
tree3ee06a59f147ccc4009b35cccfbe2461bcd18310 /poezio/plugin_manager.py
parentcf44cf7cdec9fdb35caa372563d57e7045dc29dd (diff)
downloadpoezio-332a5c2553db41de777473a1e1be9cd1522c9496.tar.gz
poezio-332a5c2553db41de777473a1e1be9cd1522c9496.tar.bz2
poezio-332a5c2553db41de777473a1e1be9cd1522c9496.tar.xz
poezio-332a5c2553db41de777473a1e1be9cd1522c9496.zip
Move the src directory to poezio, for better cython compatibility.
Diffstat (limited to 'poezio/plugin_manager.py')
-rw-r--r--poezio/plugin_manager.py384
1 files changed, 384 insertions, 0 deletions
diff --git a/poezio/plugin_manager.py b/poezio/plugin_manager.py
new file mode 100644
index 00000000..549753a9
--- /dev/null
+++ b/poezio/plugin_manager.py
@@ -0,0 +1,384 @@
+"""
+Plugin manager module.
+Define the PluginManager class, the one that glues all the plugins and
+the API together. Defines also a bunch of variables related to the
+plugin env.
+"""
+
+import os
+from os import path
+import logging
+
+import core
+import tabs
+from plugin import PluginAPI
+from config import config
+
+log = logging.getLogger(__name__)
+
+class PluginManager(object):
+ """
+ Plugin Manager
+ Contains all the references to the plugins
+ And keeps track of everything the plugin has done through the API.
+ """
+ def __init__(self, core):
+ self.core = core
+ # module name -> module object
+ self.modules = {}
+ # module name -> plugin object
+ self.plugins = {}
+ # module name -> dict of commands loaded for the module
+ self.commands = {}
+ # module name -> list of event_name/handler pairs loaded for the module
+ self.event_handlers = {}
+ # module name -> dict of tab types; tab type -> commands
+ # loaded by the module
+ self.tab_commands = {}
+ # module name → dict of keys/handlers loaded for the module
+ self.keys = {}
+ # module name → dict of tab types; tab type → list of keybinds (tuples)
+ self.tab_keys = {}
+ self.roster_elements = {}
+
+ from importlib import machinery
+ self.finder = machinery.PathFinder()
+
+ self.initial_set_plugins_dir()
+ self.initial_set_plugins_conf_dir()
+ self.fill_load_path()
+
+ self.plugin_api = PluginAPI(core, self)
+
+ def disable_plugins(self):
+ for plugin in set(self.plugins.keys()):
+ try:
+ self.unload(plugin, notify=False)
+ except:
+ pass
+
+ def load(self, name, notify=True):
+ """
+ Load a plugin.
+ """
+ if name in self.plugins:
+ self.unload(name)
+
+ try:
+ module = None
+ loader = self.finder.find_module(name, self.load_path)
+ if not loader:
+ self.core.information('Could not find plugin: %s' % name)
+ return
+ module = loader.load_module()
+ except Exception as e:
+ log.debug("Could not load plugin %s", name, exc_info=True)
+ self.core.information("Could not load plugin %s: %s" % (name, e),
+ 'Error')
+ finally:
+ if not module:
+ return
+
+ self.modules[name] = module
+ self.commands[name] = {}
+ self.keys[name] = {}
+ self.tab_keys[name] = {}
+ self.tab_commands[name] = {}
+ self.event_handlers[name] = []
+ try:
+ self.plugins[name] = None
+ self.plugins[name] = module.Plugin(self.plugin_api, self.core,
+ self.plugins_conf_dir)
+ except Exception as e:
+ log.error('Error while loading the plugin %s', name, exc_info=True)
+ if notify:
+ self.core.information('Unable to load the plugin %s: %s' %
+ (name, e),
+ 'Error')
+ self.unload(name, notify=False)
+ else:
+ if notify:
+ self.core.information('Plugin %s loaded' % name, 'Info')
+
+ def unload(self, name, notify=True):
+ if name in self.plugins:
+ try:
+ for command in self.commands[name].keys():
+ del self.core.commands[command]
+ for key in self.keys[name].keys():
+ del self.core.key_func[key]
+ for tab in list(self.tab_commands[name].keys()):
+ for command in self.tab_commands[name][tab][:]:
+ self.del_tab_command(name, getattr(tabs, tab),
+ command[0])
+ del self.tab_commands[name][tab]
+ for tab in list(self.tab_keys[name].keys()):
+ for key in self.tab_keys[name][tab][:]:
+ self.del_tab_key(name, getattr(tabs, tab), key[0])
+ del self.tab_keys[name][tab]
+ for event_name, handler in self.event_handlers[name][:]:
+ self.del_event_handler(name, event_name, handler)
+
+ if self.plugins[name] is not None:
+ self.plugins[name].unload()
+ del self.plugins[name]
+ del self.commands[name]
+ del self.keys[name]
+ del self.tab_commands[name]
+ del self.event_handlers[name]
+ if notify:
+ self.core.information('Plugin %s unloaded' % name, 'Info')
+ except Exception as e:
+ log.debug("Could not unload plugin %s", name, exc_info=True)
+ self.core.information("Could not unload plugin %s: %s" %
+ (name, e),
+ 'Error')
+
+ def add_command(self, module_name, name, handler, help,
+ completion=None, short='', usage=''):
+ """
+ Add a global command.
+ """
+ if name in self.core.commands:
+ raise Exception("Command '%s' already exists" % (name,))
+
+ commands = self.commands[module_name]
+ commands[name] = core.Command(handler, help, completion, short, usage)
+ self.core.commands[name] = commands[name]
+
+ def del_command(self, module_name, name):
+ """
+ Remove a global command added through add_command.
+ """
+ if name in self.commands[module_name]:
+ del self.commands[module_name][name]
+ if name in self.core.commands:
+ del self.core.commands[name]
+
+ def add_tab_command(self, module_name, tab_type, name, handler, help,
+ completion=None, short='', usage=''):
+ """
+ Add a command only for a type of Tab.
+ """
+ commands = self.tab_commands[module_name]
+ t = tab_type.__name__
+ if name in tab_type.plugin_commands:
+ return
+ if not t in commands:
+ commands[t] = []
+ commands[t].append((name, handler, help, completion))
+ tab_type.plugin_commands[name] = core.Command(handler, help,
+ completion, short, usage)
+ for tab in self.core.tabs:
+ if isinstance(tab, tab_type):
+ tab.update_commands()
+
+ def del_tab_command(self, module_name, tab_type, name):
+ """
+ Remove a command added through add_tab_command.
+ """
+ commands = self.tab_commands[module_name]
+ t = tab_type.__name__
+ if not t in commands:
+ return
+ for command in commands[t]:
+ if command[0] == name:
+ commands[t].remove(command)
+ del tab_type.plugin_commands[name]
+ for tab in self.core.tabs:
+ if isinstance(tab, tab_type) and name in tab.commands:
+ del tab.commands[name]
+
+ def add_tab_key(self, module_name, tab_type, key, handler):
+ """
+ Associate a key binding to a handler only for a type of Tab.
+ """
+ keys = self.tab_keys[module_name]
+ t = tab_type.__name__
+ if key in tab_type.plugin_keys:
+ return
+ if not t in keys:
+ keys[t] = []
+ keys[t].append((key, handler))
+ tab_type.plugin_keys[key] = handler
+ for tab in self.core.tabs:
+ if isinstance(tab, tab_type):
+ tab.update_keys()
+
+ def del_tab_key(self, module_name, tab_type, key):
+ """
+ Remove a key binding added through add_tab_key.
+ """
+ keys = self.tab_keys[module_name]
+ t = tab_type.__name__
+ if not t in keys:
+ return
+ for _key in keys[t]:
+ if _key[0] == key:
+ keys[t].remove(_key)
+ del tab_type.plugin_keys[key]
+ for tab in self.core.tabs:
+ if isinstance(tab, tab_type) and key in tab.key_func:
+ del tab.key_func[key]
+
+ def add_key(self, module_name, key, handler):
+ """
+ Associate a global key binding to a handler, except if it
+ already exists.
+ """
+ if key in self.core.key_func:
+ raise Exception("Key '%s' already exists" % (key,))
+ keys = self.keys[module_name]
+ keys[key] = handler
+ self.core.key_func[key] = handler
+
+ def del_key(self, module_name, key):
+ """
+ Remove a global key binding added by a plugin.
+ """
+ if key in self.keys[module_name]:
+ del self.keys[module_name][key]
+ if key in self.core.key_func:
+ del self.core.commands[key]
+
+ def add_event_handler(self, module_name, event_name, handler, position=0):
+ """
+ Add an event handler. If event_name isn’t in the event list, assume
+ it is a slixmpp event.
+ """
+ eh = self.event_handlers[module_name]
+ eh.append((event_name, handler))
+ if event_name in self.core.events.events:
+ self.core.events.add_event_handler(event_name, handler, position)
+ else:
+ self.core.xmpp.add_event_handler(event_name, handler)
+
+ def del_event_handler(self, module_name, event_name, handler):
+ """
+ Remove an event handler if it exists.
+ """
+ if event_name in self.core.events.events:
+ self.core.events.del_event_handler(None, handler)
+ else:
+ self.core.xmpp.del_event_handler(event_name, handler)
+ eh = self.event_handlers[module_name]
+ eh = list(filter(lambda e: e != (event_name, handler), eh))
+
+ def completion_load(self, the_input):
+ """
+ completion function that completes the name of the plugins, from
+ all .py files in plugins_dir
+ """
+ try:
+ names = set()
+ for path in self.load_path:
+ try:
+ add = set(os.listdir(path))
+ names |= add
+ except:
+ pass
+ except OSError as e:
+ self.core.information('Completion failed: %s' % e, 'Error')
+ return
+ plugins_files = [name[:-3] for name in names if name.endswith('.py')
+ and name != '__init__.py' and not name.startswith('.')]
+ plugins_files.sort()
+ position = the_input.get_argument_position(quoted=False)
+ return the_input.new_completion(plugins_files, position, '',
+ quotify=False)
+
+ def completion_unload(self, the_input):
+ """
+ completion function that completes the name of loaded plugins
+ """
+ position = the_input.get_argument_position(quoted=False)
+ return the_input.new_completion(sorted(self.plugins.keys()), position,
+ '', quotify=False)
+
+ def on_plugins_dir_change(self, new_value):
+ self.plugins_dir = new_value
+ self.check_create_plugins_dir()
+ self.fill_load_path()
+
+ def on_plugins_conf_dir_change(self, new_value):
+ self.plugins_conf_dir = new_value
+ self.check_create_plugins_conf_dir()
+
+ def initial_set_plugins_conf_dir(self):
+ """
+ Create the plugins_conf_dir
+ """
+ plugins_conf_dir = config.get('plugins_conf_dir')
+ if not plugins_conf_dir:
+ config_home = os.environ.get('XDG_CONFIG_HOME')
+ if not config_home:
+ config_home = os.path.join(os.environ.get('HOME'), '.config')
+ plugins_conf_dir = os.path.join(config_home, 'poezio', 'plugins')
+ self.plugins_conf_dir = os.path.expanduser(plugins_conf_dir)
+ self.check_create_plugins_conf_dir()
+
+ def check_create_plugins_conf_dir(self):
+ """
+ Create the plugins config directory if it does not exist.
+ Returns True on success, False on failure.
+ """
+ if not os.access(self.plugins_conf_dir, os.R_OK | os.X_OK):
+ try:
+ os.makedirs(self.plugins_conf_dir)
+ except OSError:
+ log.error('Unable to create the plugin conf dir: %s',
+ self.plugins_conf_dir, exc_info=True)
+ return False
+ return True
+
+ def initial_set_plugins_dir(self):
+ """
+ Set the plugins_dir on start
+ """
+ plugins_dir = config.get('plugins_dir')
+ plugins_dir = plugins_dir or\
+ os.path.join(os.environ.get('XDG_DATA_HOME') or\
+ os.path.join(os.environ.get('HOME'),
+ '.local', 'share'),
+ 'poezio', 'plugins')
+ self.plugins_dir = os.path.expanduser(plugins_dir)
+ self.check_create_plugins_dir()
+
+ def check_create_plugins_dir(self):
+ """
+ Create the plugins directory if it does not exist.
+ Returns True on success, False on failure.
+ """
+ if not os.access(self.plugins_dir, os.R_OK | os.X_OK):
+ try:
+ os.makedirs(self.plugins_dir, exist_ok=True)
+ except OSError:
+ log.error('Unable to create the plugins dir: %s',
+ self.plugins_dir, exc_info=True)
+ return False
+ return True
+
+ def fill_load_path(self):
+ """
+ Append the global packages and the source directory if available
+ """
+
+ self.load_path = []
+
+ default_plugin_path = path.join(path.dirname(path.dirname(__file__)),
+ 'plugins')
+
+ if os.access(default_plugin_path, os.R_OK | os.X_OK):
+ self.load_path.insert(0, default_plugin_path)
+
+ if os.access(self.plugins_dir, os.R_OK | os.X_OK):
+ self.load_path.append(self.plugins_dir)
+
+ try:
+ import poezio_plugins
+ except:
+ pass
+ else:
+ if poezio_plugins.__path__:
+ self.load_path.append(list(poezio_plugins.__path__)[0])
+