[Dicson] ability to install from zip / uninstall plugin. Fixes #5906
This commit is contained in:
parent
cfac956598
commit
096b8f3d91
6 changed files with 249 additions and 35 deletions
|
@ -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>
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue