[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="visible">True</property>
<property name="spacing">5</property> <property name="spacing">5</property>
<property name="layout_style">end</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> <child>
<object class="GtkButton" id="uninstall_plugin_button"> <object class="GtkButton" id="uninstall_plugin_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<signal name="clicked" handler="on_uninstall_plugin_button_clicked"/> <signal name="clicked" handler="on_uninstall_plugin_button_clicked"/>
<child> <child>
@ -266,6 +303,7 @@
<child> <child>
<object class="GtkLabel" id="uninstall_plugin_button_label"> <object class="GtkLabel" id="uninstall_plugin_button_label">
<property name="visible">True</property> <property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Uninstall</property> <property name="label" translatable="yes">Uninstall</property>
</object> </object>
<packing> <packing>
@ -278,7 +316,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">0</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -302,6 +340,7 @@
<child> <child>
<object class="GtkLabel" id="configure_plugin_button_label"> <object class="GtkLabel" id="configure_plugin_button_label">
<property name="visible">True</property> <property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Configure</property> <property name="label" translatable="yes">Configure</property>
</object> </object>
<packing> <packing>
@ -314,7 +353,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">1</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
</object> </object>

View file

@ -61,7 +61,7 @@ from common.connection_handlers_events import *
from common import ged from common import ged
from common import nec from common import nec
from common.nec import NetworkEvent from common.nec import NetworkEvent
from plugins import GajimPlugin
if gajim.HAVE_FARSIGHT: if gajim.HAVE_FARSIGHT:
from common.jingle import ConnectionJingle from common.jingle import ConnectionJingle
else: else:

View file

@ -131,3 +131,15 @@ class GajimGeneralException(Exception):
def __str__(self): def __str__(self):
return self.text 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

@ -1468,8 +1468,8 @@ class WarningDialog(HigDialog):
""" """
def __init__(self, pritext, sectext=''): def __init__(self, pritext, sectext=''):
HigDialog.__init__( self, None, HigDialog.__init__(self, None, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK,
gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext) pritext, sectext)
self.set_modal(False) self.set_modal(False)
if hasattr(gajim.interface, 'roster') and gajim.interface.roster: if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
self.set_transient_for(gajim.interface.roster.window) self.set_transient_for(gajim.interface.roster.window)
@ -1508,9 +1508,8 @@ class YesNoDialog(HigDialog):
on_response_no=None): on_response_no=None):
self.user_response_yes = on_response_yes self.user_response_yes = on_response_yes
self.user_response_no = on_response_no self.user_response_no = on_response_no
HigDialog.__init__( self, None, HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext, pritext, sectext, on_response_yes=self.on_response_yes,
on_response_yes=self.on_response_yes,
on_response_no=self.on_response_no) on_response_no=self.on_response_no)
if checktext: if checktext:
@ -4442,6 +4441,52 @@ class AvatarChooserDialog(ImageChooserDialog):
else: else:
self.response_clear(widget) 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: class AddSpecialNotificationDialog:
def __init__(self, jid): def __init__(self, jid):
""" """

View file

@ -30,9 +30,10 @@ import pango
import gtk, gobject import gtk, gobject
import gtkgui_helpers import gtkgui_helpers
import dialogs
from common import gajim from common import gajim
from plugins.helpers import log_calls, log from plugins.helpers import log_calls, log
from common.exceptions import PluginsystemError
class PluginsWindow(object): class PluginsWindow(object):
'''Class for Plugins window''' '''Class for Plugins window'''
@ -44,14 +45,10 @@ class PluginsWindow(object):
self.window = self.xml.get_object('plugins_window') self.window = self.xml.get_object('plugins_window')
self.window.set_transient_for(gajim.interface.roster.window) self.window.set_transient_for(gajim.interface.roster.window)
widgets_to_extract = ('plugins_notebook', widgets_to_extract = ('plugins_notebook', 'plugin_name_label',
'plugin_name_label', 'plugin_version_label', 'plugin_authors_label',
'plugin_version_label', 'plugin_homepage_linkbutton', 'plugin_description_textview',
'plugin_authors_label', 'uninstall_plugin_button', 'configure_plugin_button',
'plugin_homepage_linkbutton',
'plugin_description_textview',
'uninstall_plugin_button',
'configure_plugin_button',
'installed_plugins_treeview') 'installed_plugins_treeview')
for widget_name in widgets_to_extract: for widget_name in widgets_to_extract:
@ -62,8 +59,7 @@ class PluginsWindow(object):
self.plugin_name_label.set_attributes(attr_list) self.plugin_name_label.set_attributes(attr_list)
self.installed_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT, self.installed_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT,
gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
gobject.TYPE_BOOLEAN)
self.installed_plugins_treeview.set_model(self.installed_plugins_model) self.installed_plugins_treeview.set_model(self.installed_plugins_model)
renderer = gtk.CellRendererText() renderer = gtk.CellRendererText()
@ -116,7 +112,8 @@ class PluginsWindow(object):
desc_textbuffer = self.plugin_description_textview.get_buffer() desc_textbuffer = self.plugin_description_textview.get_buffer()
desc_textbuffer.set_text(plugin.description) desc_textbuffer.set_text(plugin.description)
self.plugin_description_textview.set_property('sensitive', True) 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: if plugin.config_dialog is None:
self.configure_plugin_button.set_property('sensitive', False) self.configure_plugin_button.set_property('sensitive', False)
else: else:
@ -143,8 +140,7 @@ class PluginsWindow(object):
self.installed_plugins_model.set_sort_column_id(1, gtk.SORT_ASCENDING) self.installed_plugins_model.set_sort_column_id(1, gtk.SORT_ASCENDING)
for plugin in pm.plugins: for plugin in pm.plugins:
self.installed_plugins_model.append([plugin, self.installed_plugins_model.append([plugin, plugin.name,
plugin.name,
plugin.active]) plugin.active])
@log_calls('PluginsWindow') @log_calls('PluginsWindow')
@ -189,14 +185,66 @@ class PluginsWindow(object):
@log_calls('PluginsWindow') @log_calls('PluginsWindow')
def on_uninstall_plugin_button_clicked(self, widget): 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): class GajimPluginConfigDialog(gtk.Dialog):
@log_calls('GajimPluginConfigDialog') @log_calls('GajimPluginConfigDialog')
def __init__(self, plugin, **kwargs): 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.plugin = plugin
self.add_button('gtk-close', gtk.RESPONSE_CLOSE) self.add_button('gtk-close', gtk.RESPONSE_CLOSE)

View file

@ -29,9 +29,12 @@ __all__ = ['PluginManager']
import os import os
import sys import sys
import fnmatch import fnmatch
import zipfile
from shutil import rmtree
from common import gajim from common import gajim
from common import nec from common import nec
from common.exceptions import PluginsystemError
from plugins.helpers import log, log_calls, Singleton from plugins.helpers import log, log_calls, Singleton
from plugins.plugin import GajimPlugin from plugins.plugin import GajimPlugin
@ -370,7 +373,7 @@ class PluginManager(object):
@staticmethod @staticmethod
@log_calls('PluginManager') @log_calls('PluginManager')
def scan_dir_for_plugins(path): def scan_dir_for_plugins(path, scan_dirs=True):
''' '''
Scans given directory for plugin classes. Scans given directory for plugin classes.
@ -414,7 +417,7 @@ class PluginManager(object):
pass pass
#log.debug('Module not imported successfully. ImportError: %s'%(import_error)) #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 module_name = elem_name
file_path += os.path.sep file_path += os.path.sep
#log.debug('Possible package detected.') #log.debug('Possible package detected.')
@ -454,3 +457,70 @@ class PluginManager(object):
#log.debug(module) #log.debug(module)
return plugins_found 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