diff --git a/data/gui/plugins_window.ui b/data/gui/plugins_window.ui
index 4560c2013..89e451a24 100644
--- a/data/gui/plugins_window.ui
+++ b/data/gui/plugins_window.ui
@@ -245,10 +245,47 @@
True
5
end
+
+
+
+ False
+ False
+ 0
+
+
True
- True
+ False
True
@@ -266,6 +303,7 @@
True
+ 0
Uninstall
@@ -278,7 +316,7 @@
False
False
- 0
+ 1
@@ -302,6 +340,7 @@
True
+ 0
Configure
@@ -314,7 +353,7 @@
False
False
- 1
+ 2
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 164e755c4..df2511e73 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -61,7 +61,7 @@ from common.connection_handlers_events import *
from common import ged
from common import nec
from common.nec import NetworkEvent
-from plugins import GajimPlugin
+
if gajim.HAVE_FARSIGHT:
from common.jingle import ConnectionJingle
else:
diff --git a/src/common/exceptions.py b/src/common/exceptions.py
index 122262418..de8c3309d 100644
--- a/src/common/exceptions.py
+++ b/src/common/exceptions.py
@@ -131,3 +131,15 @@ class GajimGeneralException(Exception):
def __str__(self):
return self.text
+
+class PluginsystemError(Exception):
+ """
+ Error in the pluginsystem
+ """
+
+ def __init__(self, text=''):
+ Exception.__init__(self)
+ self.text = text
+
+ def __str__(self):
+ return self.text
diff --git a/src/dialogs.py b/src/dialogs.py
index f08e515f7..e9ca65d96 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -1349,11 +1349,11 @@ class FileChooserDialog(gtk.FileChooserDialog):
Non-blocking FileChooser Dialog around gtk.FileChooserDialog
"""
def __init__(self, title_text, action, buttons, default_response,
- select_multiple = False, current_folder = None, on_response_ok = None,
- on_response_cancel = None):
+ select_multiple=False, current_folder=None, on_response_ok=None,
+ on_response_cancel=None):
gtk.FileChooserDialog.__init__(self, title=title_text, action=action,
- buttons=buttons)
+ buttons=buttons)
self.set_default_response(default_response)
self.set_select_multiple(select_multiple)
@@ -1468,8 +1468,8 @@ class WarningDialog(HigDialog):
"""
def __init__(self, pritext, sectext=''):
- HigDialog.__init__( self, None,
- gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext)
+ HigDialog.__init__(self, None, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK,
+ pritext, sectext)
self.set_modal(False)
if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
self.set_transient_for(gajim.interface.roster.window)
@@ -1505,13 +1505,12 @@ class YesNoDialog(HigDialog):
"""
def __init__(self, pritext, sectext='', checktext='', on_response_yes=None,
- on_response_no=None):
+ on_response_no=None):
self.user_response_yes = on_response_yes
self.user_response_no = on_response_no
- HigDialog.__init__( self, None,
- gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext,
- on_response_yes=self.on_response_yes,
- on_response_no=self.on_response_no)
+ HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
+ pritext, sectext, on_response_yes=self.on_response_yes,
+ on_response_no=self.on_response_no)
if checktext:
self.checkbutton = gtk.CheckButton(checktext)
@@ -4442,6 +4441,52 @@ class AvatarChooserDialog(ImageChooserDialog):
else:
self.response_clear(widget)
+
+class ArchiveChooserDialog(FileChooserDialog):
+ def __init__(self, on_response_ok=None, on_response_cancel=None):
+
+ def on_ok(widget, callback):
+ '''check if file exists and call callback'''
+ path_to_file = self.get_filename()
+ if not path_to_file:
+ return
+ path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
+ (path_to_file,))[0]
+ if os.path.exists(path_to_file):
+ if isinstance(callback, tuple):
+ callback[0](path_to_file, *callback[1:])
+ else:
+ callback(path_to_file)
+ self.destroy()
+
+ path = helpers.get_documents_path()
+
+ FileChooserDialog.__init__(self,
+ title_text=_('Choose Archive'),
+ action=gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN, gtk.RESPONSE_OK),
+ default_response=gtk.RESPONSE_OK,
+ current_folder=path,
+ on_response_ok=(on_ok, on_response_ok),
+ on_response_cancel=on_response_cancel)
+
+ if on_response_cancel:
+ self.connect('destroy', on_response_cancel)
+
+ filter_ = gtk.FileFilter()
+ filter_.set_name(_('All files'))
+ filter_.add_pattern('*')
+ self.add_filter(filter_)
+
+ filter_ = gtk.FileFilter()
+ filter_.set_name(_('Zip files'))
+ filter_.add_pattern('*.zip')
+
+ self.add_filter(filter_)
+ self.set_filter(filter_)
+
+
class AddSpecialNotificationDialog:
def __init__(self, jid):
"""
diff --git a/src/plugins/gui.py b/src/plugins/gui.py
index ab0281e1f..23de1fe84 100644
--- a/src/plugins/gui.py
+++ b/src/plugins/gui.py
@@ -30,9 +30,10 @@ import pango
import gtk, gobject
import gtkgui_helpers
+import dialogs
from common import gajim
-
from plugins.helpers import log_calls, log
+from common.exceptions import PluginsystemError
class PluginsWindow(object):
'''Class for Plugins window'''
@@ -44,15 +45,11 @@ class PluginsWindow(object):
self.window = self.xml.get_object('plugins_window')
self.window.set_transient_for(gajim.interface.roster.window)
- widgets_to_extract = ('plugins_notebook',
- 'plugin_name_label',
- 'plugin_version_label',
- 'plugin_authors_label',
- 'plugin_homepage_linkbutton',
- 'plugin_description_textview',
- 'uninstall_plugin_button',
- 'configure_plugin_button',
- 'installed_plugins_treeview')
+ widgets_to_extract = ('plugins_notebook', 'plugin_name_label',
+ 'plugin_version_label', 'plugin_authors_label',
+ 'plugin_homepage_linkbutton', 'plugin_description_textview',
+ 'uninstall_plugin_button', 'configure_plugin_button',
+ 'installed_plugins_treeview')
for widget_name in widgets_to_extract:
setattr(self, widget_name, self.xml.get_object(widget_name))
@@ -62,8 +59,7 @@ class PluginsWindow(object):
self.plugin_name_label.set_attributes(attr_list)
self.installed_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT,
- gobject.TYPE_STRING,
- gobject.TYPE_BOOLEAN)
+ gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
self.installed_plugins_treeview.set_model(self.installed_plugins_model)
renderer = gtk.CellRendererText()
@@ -79,7 +75,7 @@ class PluginsWindow(object):
# connect signal for selection change
selection = self.installed_plugins_treeview.get_selection()
selection.connect('changed',
- self.installed_plugins_treeview_selection_changed)
+ self.installed_plugins_treeview_selection_changed)
selection.set_mode(gtk.SELECTION_SINGLE)
self._clear_installed_plugin_info()
@@ -116,7 +112,8 @@ class PluginsWindow(object):
desc_textbuffer = self.plugin_description_textview.get_buffer()
desc_textbuffer.set_text(plugin.description)
self.plugin_description_textview.set_property('sensitive', True)
- self.uninstall_plugin_button.set_property('sensitive', True)
+ self.uninstall_plugin_button.set_property('sensitive',
+ gajim.PLUGINS_DIRS[1] in plugin.__path__)
if plugin.config_dialog is None:
self.configure_plugin_button.set_property('sensitive', False)
else:
@@ -143,9 +140,8 @@ class PluginsWindow(object):
self.installed_plugins_model.set_sort_column_id(1, gtk.SORT_ASCENDING)
for plugin in pm.plugins:
- self.installed_plugins_model.append([plugin,
- plugin.name,
- plugin.active])
+ self.installed_plugins_model.append([plugin, plugin.name,
+ plugin.active])
@log_calls('PluginsWindow')
def installed_plugins_toggled_cb(self, cell, path):
@@ -189,14 +185,66 @@ class PluginsWindow(object):
@log_calls('PluginsWindow')
def on_uninstall_plugin_button_clicked(self, widget):
- pass
+ selection = self.installed_plugins_treeview.get_selection()
+ model, iter = selection.get_selected()
+ if iter:
+ plugin = model.get_value(iter, 0)
+ plugin_name = model.get_value(iter, 1).decode('utf-8')
+ is_active = model.get_value(iter, 2)
+ try:
+ gajim.plugin_manager.remove_plugin(plugin)
+ except PluginsystemError, e:
+ dialogs.WarningDialog(_('Unable to properly remove the plugin'),
+ str(e))
+ return
+ model.remove(iter)
+
+ @log_calls('PluginsWindow')
+ def on_install_plugin_button_clicked(self, widget):
+ def _on_plugin_exists(zip_filename):
+ def on_yes(is_checked):
+ plugin = gajim.plugin_manager.install_from_zip(zip_filename,
+ True)
+ model = self.installed_plugins_model
+
+ for row in xrange(len(model)):
+ if plugin == model[row][0]:
+ model.remove(model.get_iter((row, 0)))
+ break
+
+ iter_ = model.append([plugin, plugin.name, False])
+ sel = self.installed_plugins_treeview.get_selection()
+ sel.select_iter(iter_)
+
+ dialogs.YesNoDialog(_('Plugin already exists'),
+ sectext=_('Overwrite?'), on_response_yes=on_yes)
+
+ def _try_install(zip_filename):
+ try:
+ plugin = gajim.plugin_manager.install_from_zip(zip_filename)
+ except PluginsystemError, er_type:
+ error_text = str(er_type)
+ if error_text == _('Plugin already exists'):
+ _on_plugin_exists(zip_filename)
+ return
+
+ dialogs.WarningDialog(error_text, '"%s"' % zip_filename)
+ return
+
+ model = self.installed_plugins_model
+ iter_ = model.append([plugin, plugin.name, False])
+ sel = self.installed_plugins_treeview.get_selection()
+ sel.select_iter(iter_)
+
+ self.dialog = dialogs.ArchiveChooserDialog(on_response_ok=_try_install)
class GajimPluginConfigDialog(gtk.Dialog):
@log_calls('GajimPluginConfigDialog')
def __init__(self, plugin, **kwargs):
- gtk.Dialog.__init__(self, '%s %s'%(plugin.name, _('Configuration')), **kwargs)
+ gtk.Dialog.__init__(self, '%s %s'%(plugin.name, _('Configuration')),
+ **kwargs)
self.plugin = plugin
self.add_button('gtk-close', gtk.RESPONSE_CLOSE)
diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py
index a0708c89c..3560e4985 100644
--- a/src/plugins/pluginmanager.py
+++ b/src/plugins/pluginmanager.py
@@ -29,9 +29,12 @@ __all__ = ['PluginManager']
import os
import sys
import fnmatch
+import zipfile
+from shutil import rmtree
from common import gajim
from common import nec
+from common.exceptions import PluginsystemError
from plugins.helpers import log, log_calls, Singleton
from plugins.plugin import GajimPlugin
@@ -370,7 +373,7 @@ class PluginManager(object):
@staticmethod
@log_calls('PluginManager')
- def scan_dir_for_plugins(path):
+ def scan_dir_for_plugins(path, scan_dirs=True):
'''
Scans given directory for plugin classes.
@@ -414,7 +417,7 @@ class PluginManager(object):
pass
#log.debug('Module not imported successfully. ImportError: %s'%(import_error))
- elif os.path.isdir(file_path):
+ elif os.path.isdir(file_path) and scan_dirs:
module_name = elem_name
file_path += os.path.sep
#log.debug('Possible package detected.')
@@ -454,3 +457,70 @@ class PluginManager(object):
#log.debug(module)
return plugins_found
+
+ def install_from_zip(self, zip_filename, owerwrite=None):
+ '''
+ Install plagin from zip and return plugin
+ '''
+ try:
+ zip_file = zipfile.ZipFile(zip_filename)
+ except zipfile.BadZipfile, e:
+ # it is not zip file
+ raise PluginsystemError(_('Archive corrupted'))
+ except IOError,e:
+ raise PluginsystemError(_('Archive empty'))
+
+ if zip_file.testzip():
+ # CRC error
+ raise PluginsystemError(_('Archive corrupted'))
+
+ dirs = []
+ for filename in zip_file.namelist():
+ if filename.startswith('.') or filename.startswith('/') or \
+ ('/' not in filename):
+ # members not safe
+ raise PluginsystemError(_('Archive is malformed'))
+ if filename.endswith('/') and filename.find('/', 0, -1) < 0:
+ dirs.append(filename)
+
+ if len(dirs) > 1:
+ # several directories in the root of the archive
+ raise PluginsystemError(_('Archive is malformed'))
+
+ base_dir, user_dir = gajim.PLUGINS_DIRS
+ plugin_dir = os.path.join(user_dir, dirs[0])
+
+ if os.path.isdir(plugin_dir):
+ # Plugin already exists
+ if not owerwrite:
+ raise PluginsystemError(_('Plugin already exists'))
+ self.remove_plugin(self.get_plugin_by_path(plugin_dir))
+
+ zip_file.extractall(user_dir)
+ zip_file.close()
+ path = os.path.join(user_dir, dirs[0])
+ self.add_plugin(self.scan_dir_for_plugins(plugin_dir, False)[0])
+ plugin = self.plugins[-1]
+ return plugin
+
+ def remove_plugin(self, plugin):
+ '''
+ Deactivate and remove plugin from `plugins` list
+ '''
+ def on_error(func, path, error):
+ if func == os.path.islink:
+ # if symlink
+ os.unlink(path)
+ return
+ # access is denied or other
+ raise PluginsystemError(error[1])
+
+ if plugin.active:
+ self.deactivate_plugin(plugin)
+ rmtree(plugin.__path__, False, on_error)
+ self.plugins.remove(plugin)
+
+ def get_plugin_by_path(self, plugin_dir):
+ for plugin in self.plugins:
+ if plugin.__path__ in plugin_dir:
+ return plugin