2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
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.
|
|
|
|
|
"""
|
|
|
|
|
|
2012-11-22 19:16:16 +00:00
|
|
|
|
import imp
|
2011-09-24 20:26:31 +00:00
|
|
|
|
import os
|
2012-11-18 17:08:03 +00:00
|
|
|
|
from os import path
|
2011-09-24 20:26:31 +00:00
|
|
|
|
import sys
|
2012-03-28 21:08:31 +00:00
|
|
|
|
import logging
|
2011-09-24 20:26:31 +00:00
|
|
|
|
from gettext import gettext as _
|
2013-04-16 16:49:03 +00:00
|
|
|
|
from sys import version_info
|
2011-09-24 20:26:31 +00:00
|
|
|
|
|
2013-01-18 22:08:40 +00:00
|
|
|
|
import core
|
2012-05-21 00:14:25 +00:00
|
|
|
|
import tabs
|
2013-03-06 21:57:41 +00:00
|
|
|
|
from plugin import PluginAPI
|
2012-05-21 00:14:25 +00:00
|
|
|
|
from config import config
|
|
|
|
|
|
2012-03-28 21:08:31 +00:00
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
2013-05-26 18:07:12 +00:00
|
|
|
|
load_path = []
|
|
|
|
|
|
2011-09-24 20:26:31 +00:00
|
|
|
|
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')
|
2011-11-16 08:46:09 +00:00
|
|
|
|
plugins_dir = os.path.expanduser(plugins_dir)
|
2011-09-25 00:39:00 +00:00
|
|
|
|
|
2013-03-02 23:24:18 +00:00
|
|
|
|
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')
|
|
|
|
|
plugins_conf_dir = os.path.expanduser(plugins_conf_dir)
|
2011-09-25 00:39:00 +00:00
|
|
|
|
|
2011-09-24 20:26:31 +00:00
|
|
|
|
try:
|
2013-06-09 01:45:10 +00:00
|
|
|
|
os.makedirs(plugins_dir, exist_ok=True)
|
2011-09-24 20:26:31 +00:00
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
2013-05-26 18:07:12 +00:00
|
|
|
|
else:
|
|
|
|
|
load_path.append(plugins_dir)
|
2011-09-24 20:26:31 +00:00
|
|
|
|
|
2011-09-25 00:39:00 +00:00
|
|
|
|
try:
|
2013-06-09 01:45:10 +00:00
|
|
|
|
os.makedirs(plugins_conf_dir, exist_ok=True)
|
2011-09-25 00:39:00 +00:00
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
2013-03-02 23:24:18 +00:00
|
|
|
|
|
2012-11-18 17:08:03 +00:00
|
|
|
|
default_plugin_path = path.join(path.dirname(path.dirname(__file__)), 'plugins')
|
2011-09-25 00:39:00 +00:00
|
|
|
|
|
2013-05-26 18:07:12 +00:00
|
|
|
|
if os.path.exists(default_plugin_path):
|
|
|
|
|
load_path.append(default_plugin_path)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
import poezio_plugins
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
if poezio_plugins.__path__:
|
|
|
|
|
load_path.append(poezio_plugins.__path__[0])
|
|
|
|
|
|
|
|
|
|
sys.path.extend(load_path)
|
2012-11-21 22:30:59 +00:00
|
|
|
|
|
2013-04-16 16:49:03 +00:00
|
|
|
|
if version_info[1] >= 3: # 3.3 & >
|
|
|
|
|
from importlib import machinery
|
|
|
|
|
finder = machinery.PathFinder()
|
|
|
|
|
|
2011-09-23 15:43:01 +00:00
|
|
|
|
class PluginManager(object):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Plugin Manager
|
|
|
|
|
Contains all the references to the plugins
|
|
|
|
|
And keeps track of everything the plugin has done through the API.
|
|
|
|
|
"""
|
2011-09-23 15:43:01 +00:00
|
|
|
|
def __init__(self, core):
|
|
|
|
|
self.core = core
|
2011-09-24 20:26:31 +00:00
|
|
|
|
self.modules = {} # module name -> module object
|
|
|
|
|
self.plugins = {} # module name -> plugin object
|
|
|
|
|
self.commands = {} # module name -> dict of commands loaded for the module
|
|
|
|
|
self.event_handlers = {} # module name -> list of event_name/handler pairs loaded for the module
|
2011-11-10 13:39:19 +00:00
|
|
|
|
self.tab_commands = {} #module name -> dict of tab types; tab type -> commands loaded by the module
|
2011-11-13 18:43:31 +00:00
|
|
|
|
self.keys = {} # module name → dict of keys/handlers loaded for the module
|
2011-11-13 19:17:33 +00:00
|
|
|
|
self.tab_keys = {} #module name → dict of tab types; tab type → list of keybinds (tuples)
|
2013-03-06 21:57:41 +00:00
|
|
|
|
self.roster_elements = {}
|
|
|
|
|
self.plugin_api = PluginAPI(core, self)
|
2011-09-23 15:43:01 +00:00
|
|
|
|
|
2013-03-10 22:58:20 +00:00
|
|
|
|
def disable_plugins(self):
|
|
|
|
|
for plugin in set(self.plugins.keys()):
|
|
|
|
|
try:
|
|
|
|
|
self.unload(plugin)
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
2011-11-10 16:06:28 +00:00
|
|
|
|
def load(self, name, notify=True):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Load a plugin.
|
|
|
|
|
"""
|
2011-09-23 15:43:01 +00:00
|
|
|
|
if name in self.plugins:
|
2011-09-27 17:14:18 +00:00
|
|
|
|
self.unload(name)
|
2011-09-23 15:43:01 +00:00
|
|
|
|
|
|
|
|
|
try:
|
2013-04-16 16:49:03 +00:00
|
|
|
|
module = None
|
|
|
|
|
if version_info[1] < 3: # < 3.3
|
|
|
|
|
if name in self.modules:
|
|
|
|
|
imp.acquire_lock()
|
|
|
|
|
module = imp.reload(self.modules[name])
|
|
|
|
|
else:
|
2013-05-26 18:07:12 +00:00
|
|
|
|
file, filename, info = imp.find_module(name, load_path)
|
2013-04-16 16:49:03 +00:00
|
|
|
|
imp.acquire_lock()
|
|
|
|
|
module = imp.load_module(name, file, filename, info)
|
|
|
|
|
else: # 3.3 & >
|
|
|
|
|
loader = finder.find_module(name)
|
|
|
|
|
if not loader:
|
2013-05-24 20:13:56 +00:00
|
|
|
|
self.core.information('Could not find plugin')
|
2013-04-16 16:49:03 +00:00
|
|
|
|
return
|
|
|
|
|
module = loader.load_module()
|
|
|
|
|
|
2011-09-23 15:43:01 +00:00
|
|
|
|
except Exception as e:
|
2011-09-24 20:26:31 +00:00
|
|
|
|
import traceback
|
2012-03-28 21:08:31 +00:00
|
|
|
|
log.debug("Could not load plugin: \n%s", traceback.format_exc())
|
|
|
|
|
self.core.information("Could not load plugin: %s" % e, 'Error')
|
2012-11-22 19:16:16 +00:00
|
|
|
|
finally:
|
2013-04-16 16:49:03 +00:00
|
|
|
|
if version_info[1] < 3 and imp.lock_held():
|
2012-11-22 19:16:16 +00:00
|
|
|
|
imp.release_lock()
|
2013-04-16 16:49:03 +00:00
|
|
|
|
if not module:
|
|
|
|
|
return
|
2011-09-24 20:26:31 +00:00
|
|
|
|
|
|
|
|
|
self.modules[name] = module
|
|
|
|
|
self.commands[name] = {}
|
2011-11-13 18:43:31 +00:00
|
|
|
|
self.keys[name] = {}
|
2011-11-13 19:17:33 +00:00
|
|
|
|
self.tab_keys[name] = {}
|
2011-11-10 13:39:19 +00:00
|
|
|
|
self.tab_commands[name] = {}
|
2011-09-24 20:26:31 +00:00
|
|
|
|
self.event_handlers[name] = []
|
2013-03-06 21:57:41 +00:00
|
|
|
|
self.plugins[name] = module.Plugin(self.plugin_api, self.core, plugins_conf_dir)
|
2011-11-10 16:06:28 +00:00
|
|
|
|
if notify:
|
|
|
|
|
self.core.information('Plugin %s loaded' % name, 'Info')
|
2011-09-23 15:43:01 +00:00
|
|
|
|
|
2011-11-10 16:06:28 +00:00
|
|
|
|
def unload(self, name, notify=True):
|
2011-09-23 15:43:01 +00:00
|
|
|
|
if name in self.plugins:
|
|
|
|
|
try:
|
2011-09-24 20:26:31 +00:00
|
|
|
|
for command in self.commands[name].keys():
|
|
|
|
|
del self.core.commands[command]
|
2011-11-13 18:43:31 +00:00
|
|
|
|
for key in self.keys[name].keys():
|
|
|
|
|
del self.core.key_func[key]
|
2011-11-10 13:39:19 +00:00
|
|
|
|
for tab in list(self.tab_commands[name].keys()):
|
2012-03-09 22:24:29 +00:00
|
|
|
|
for command in self.tab_commands[name][tab][:]:
|
2011-11-10 13:39:19 +00:00
|
|
|
|
self.del_tab_command(name, getattr(tabs, tab), command[0])
|
|
|
|
|
del self.tab_commands[name][tab]
|
2011-11-13 19:17:33 +00:00
|
|
|
|
for tab in list(self.tab_keys[name].keys()):
|
2012-03-09 22:24:29 +00:00
|
|
|
|
for key in self.tab_keys[name][tab][:]:
|
2011-11-13 19:17:33 +00:00
|
|
|
|
self.del_tab_key(name, getattr(tabs, tab), key[0])
|
|
|
|
|
del self.tab_keys[name][tab]
|
2012-03-09 22:24:29 +00:00
|
|
|
|
for event_name, handler in self.event_handlers[name][:]:
|
2011-11-09 13:02:17 +00:00
|
|
|
|
self.del_event_handler(name, event_name, handler)
|
2011-09-24 20:26:31 +00:00
|
|
|
|
|
2011-09-23 15:43:01 +00:00
|
|
|
|
self.plugins[name].unload()
|
|
|
|
|
del self.plugins[name]
|
2011-09-24 20:26:31 +00:00
|
|
|
|
del self.commands[name]
|
2011-11-13 18:43:31 +00:00
|
|
|
|
del self.keys[name]
|
2011-11-10 13:39:19 +00:00
|
|
|
|
del self.tab_commands[name]
|
2011-09-24 20:26:31 +00:00
|
|
|
|
del self.event_handlers[name]
|
2011-11-10 16:06:28 +00:00
|
|
|
|
if notify:
|
|
|
|
|
self.core.information('Plugin %s unloaded' % name, 'Info')
|
2011-09-23 15:43:01 +00:00
|
|
|
|
except Exception as e:
|
2011-09-24 20:26:31 +00:00
|
|
|
|
import traceback
|
2012-03-30 23:28:11 +00:00
|
|
|
|
log.debug("Could not unload plugin: \n%s", traceback.format_exc())
|
|
|
|
|
self.core.information("Could not unload plugin: %s" % e, 'Error')
|
2011-09-24 20:26:31 +00:00
|
|
|
|
|
2013-01-18 22:08:40 +00:00
|
|
|
|
def add_command(self, module_name, name, handler, help, completion=None, short='', usage=''):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Add a global command.
|
|
|
|
|
"""
|
|
|
|
|
if name in self.core.commands:
|
|
|
|
|
raise Exception(_("Command '%s' already exists") % (name,))
|
|
|
|
|
|
|
|
|
|
commands = self.commands[module_name]
|
2013-01-18 22:08:40 +00:00
|
|
|
|
commands[name] = core.Command(handler, help, completion, short, usage)
|
|
|
|
|
self.core.commands[name] = commands[name]
|
2012-05-21 00:14:25 +00:00
|
|
|
|
|
2011-10-02 11:21:51 +00:00
|
|
|
|
def del_command(self, module_name, name):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Remove a global command added through add_command.
|
|
|
|
|
"""
|
2011-10-02 11:21:51 +00:00
|
|
|
|
if name in self.commands[module_name]:
|
|
|
|
|
del self.commands[module_name][name]
|
|
|
|
|
if name in self.core.commands:
|
|
|
|
|
del self.core.commands[name]
|
|
|
|
|
|
2013-01-18 22:08:40 +00:00
|
|
|
|
def add_tab_command(self, module_name, tab_type, name, handler, help, completion=None, short='', usage=''):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Add a command only for a type of Tab.
|
|
|
|
|
"""
|
2011-11-10 13:39:19 +00:00
|
|
|
|
commands = self.tab_commands[module_name]
|
|
|
|
|
t = tab_type.__name__
|
2011-11-12 23:25:30 +00:00
|
|
|
|
if name in tab_type.plugin_commands:
|
|
|
|
|
return
|
2011-11-10 13:39:19 +00:00
|
|
|
|
if not t in commands:
|
|
|
|
|
commands[t] = []
|
|
|
|
|
commands[t].append((name, handler, help, completion))
|
2013-01-18 22:08:40 +00:00
|
|
|
|
tab_type.plugin_commands[name] = core.Command(handler, help, completion, short, usage)
|
2011-11-10 13:39:19 +00:00
|
|
|
|
for tab in self.core.tabs:
|
|
|
|
|
if isinstance(tab, tab_type):
|
2011-11-12 23:25:30 +00:00
|
|
|
|
tab.update_commands()
|
2011-11-10 13:39:19 +00:00
|
|
|
|
|
|
|
|
|
def del_tab_command(self, module_name, tab_type, name):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Remove a command added through add_tab_command.
|
|
|
|
|
"""
|
2011-11-10 13:39:19 +00:00
|
|
|
|
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]
|
|
|
|
|
|
2011-11-13 19:17:33 +00:00
|
|
|
|
def add_tab_key(self, module_name, tab_type, key, handler):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Associate a key binding to a handler only for a type of Tab.
|
|
|
|
|
"""
|
2011-11-13 19:17:33 +00:00
|
|
|
|
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):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Remove a key binding added through add_tab_key.
|
|
|
|
|
"""
|
2011-11-13 19:17:33 +00:00
|
|
|
|
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]
|
|
|
|
|
|
2011-11-13 18:43:31 +00:00
|
|
|
|
def add_key(self, module_name, key, handler):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Associate a global key binding to a handler, except if it
|
|
|
|
|
already exists.
|
|
|
|
|
"""
|
2011-11-13 18:43:31 +00:00
|
|
|
|
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):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Remove a global key binding added by a plugin.
|
|
|
|
|
"""
|
2011-11-13 18:43:31 +00:00
|
|
|
|
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]
|
|
|
|
|
|
2011-11-09 13:02:17 +00:00
|
|
|
|
def add_event_handler(self, module_name, event_name, handler, position=0):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Add an event handler. If event_name isn’t in the event list, assume
|
|
|
|
|
it is a sleekxmpp event.
|
|
|
|
|
"""
|
2011-09-24 20:26:31 +00:00
|
|
|
|
eh = self.event_handlers[module_name]
|
|
|
|
|
eh.append((event_name, handler))
|
2011-11-09 13:02:17 +00:00
|
|
|
|
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)
|
2011-09-24 20:26:31 +00:00
|
|
|
|
|
|
|
|
|
def del_event_handler(self, module_name, event_name, handler):
|
2012-05-21 00:14:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Remove an event handler if it exists.
|
|
|
|
|
"""
|
2011-11-09 13:02:17 +00:00
|
|
|
|
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)
|
2011-09-24 20:26:31 +00:00
|
|
|
|
eh = self.event_handlers[module_name]
|
|
|
|
|
eh = list(filter(lambda e : e != (event_name, handler), eh))
|
2011-09-24 21:10:55 +00:00
|
|
|
|
|
|
|
|
|
def completion_load(self, the_input):
|
|
|
|
|
"""
|
|
|
|
|
completion function that completes the name of the plugins, from
|
|
|
|
|
all .py files in plugins_dir
|
|
|
|
|
"""
|
|
|
|
|
try:
|
2013-05-26 18:07:12 +00:00
|
|
|
|
names = set()
|
|
|
|
|
for path in load_path:
|
|
|
|
|
try:
|
|
|
|
|
add = set(os.listdir(path))
|
|
|
|
|
names |= add
|
|
|
|
|
except:
|
|
|
|
|
pass
|
2011-09-24 21:10:55 +00:00
|
|
|
|
except OSError as e:
|
|
|
|
|
self.core.information(_('Completion failed: %s' % e), 'Error')
|
|
|
|
|
return
|
2013-05-26 18:07:12 +00:00
|
|
|
|
plugins_files = [name[:-3] for name in names if name.endswith('.py')
|
|
|
|
|
and name != '__init__.py' and not name.startswith('.')]
|
|
|
|
|
plugins_files.sort()
|
2012-04-19 11:08:35 +00:00
|
|
|
|
return the_input.auto_completion(plugins_files, '', quotify=False)
|
2011-09-24 21:10:55 +00:00
|
|
|
|
|
|
|
|
|
def completion_unload(self, the_input):
|
|
|
|
|
"""
|
|
|
|
|
completion function that completes the name of the plugins that are loaded
|
|
|
|
|
"""
|
2012-04-19 11:08:35 +00:00
|
|
|
|
return the_input.auto_completion(list(self.plugins.keys()), '', quotify=False)
|
2012-11-17 22:06:21 +00:00
|
|
|
|
|
|
|
|
|
def on_plugins_dir_change(self, new_value):
|
|
|
|
|
global plugins_dir
|
|
|
|
|
if plugins_dir in sys.path:
|
|
|
|
|
sys.path.remove(plugins_dir)
|
|
|
|
|
sys.path.append(new_value)
|
|
|
|
|
plugins_dir = new_value
|
2013-03-02 23:24:18 +00:00
|
|
|
|
|
|
|
|
|
def on_plugins_conf_dir_change(self, new_value):
|
|
|
|
|
global plugins_conf_dir
|
|
|
|
|
plugins_conf_dir = new_value
|