[Dicson] ability to install from zip / uninstall plugin. Fixes #5906
This commit is contained in:
parent
cfac956598
commit
096b8f3d91
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue