[Dicson] ability to install from zip / uninstall plugin. Fixes #5906

This commit is contained in:
Yann Leboulanger 2010-09-14 19:31:35 +02:00
parent cfac956598
commit 096b8f3d91
6 changed files with 249 additions and 35 deletions

View File

@ -245,10 +245,47 @@
<property name="visible">True</property>
<property name="spacing">5</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="install_plugin_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_install_plugin_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox13">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="stock">gtk-apply</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="install_plugin_button_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Install</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="uninstall_plugin_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_uninstall_plugin_button_clicked"/>
<child>
@ -266,6 +303,7 @@
<child>
<object class="GtkLabel" id="uninstall_plugin_button_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Uninstall</property>
</object>
<packing>
@ -278,7 +316,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
@ -302,6 +340,7 @@
<child>
<object class="GtkLabel" id="configure_plugin_button_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Configure</property>
</object>
<packing>
@ -314,7 +353,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
</object>

View File

@ -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:

View File

@ -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

View File

@ -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):
"""

View File

@ -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)

View File

@ -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