diff --git a/plugin_installer/DST_Root_CA_X3.pem b/plugin_installer/DST_Root_CA_X3.pem
new file mode 100644
index 0000000..300cd7d
--- /dev/null
+++ b/plugin_installer/DST_Root_CA_X3.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
+PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
+Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
+rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
+OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
+xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
+7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
+aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
+SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
+ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
+AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
+R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
+JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
+Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/plugin_installer/config_dialog.ui b/plugin_installer/config_dialog.ui
index fe7764c..33077dc 100644
--- a/plugin_installer/config_dialog.ui
+++ b/plugin_installer/config_dialog.ui
@@ -1,326 +1,338 @@
-
+
-
-
diff --git a/plugin_installer/manifest.ini b/plugin_installer/manifest.ini
index 4e61f46..70ae7c8 100644
--- a/plugin_installer/manifest.ini
+++ b/plugin_installer/manifest.ini
@@ -1,10 +1,11 @@
[info]
name: Plugin Installer
short_name: plugin_installer
-version: 0.17
+version: 0.18
description: Install and upgrade plugins from ftp
authors: Denis Fomin
Yann Leboulanger
Thilo Molitor
-homepage: http://trac-plugins.gajim.org/wiki/PluginInstallerPlugin
+ Philipp Hörist
+homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/PluginInstallerPlugin
min_gajim_version: 0.16.10
diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py
index e0197fb..7e65850 100644
--- a/plugin_installer/plugin_installer.py
+++ b/plugin_installer/plugin_installer.py
@@ -4,6 +4,7 @@
##
## Copyright (C) 2010-2012 Denis Fomin
## Copyright (C) 2011-2012 Yann Leboulanger
+## Copyright (C) 2017 Philipp Hörist
##
## This file is part of Gajim.
##
@@ -23,311 +24,227 @@ from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import Pango
from gi.repository import GLib
-from gi.repository import GObject
-import ftplib
import io
import threading
import configparser
import os
-import fnmatch
-import sys
-import zipfile
import ssl
import logging
+import posixpath
+import urllib.error
+from zipfile import ZipFile
+from distutils.version import LooseVersion as V
+from urllib.request import urlopen
from common import gajim
from plugins import GajimPlugin
-from plugins.helpers import log_calls, log
from htmltextview import HtmlTextView
from dialogs import WarningDialog, HigDialog, YesNoDialog
from plugins.gui import GajimPluginConfigDialog
+from enum import IntEnum
+from gtkgui_helpers import get_action
log = logging.getLogger('gajim.plugin_system.plugin_installer')
-(
-C_PIXBUF,
-C_DIR,
-C_NAME,
-C_LOCAL_VERSION,
-C_VERSION,
-C_UPGRADE,
-C_DESCRIPTION,
-C_AUTHORS,
-C_HOMEPAGE
-) = range(9)
+PLUGINS_URL = 'https://ftp.gajim.org/plugins_1/'
+MANIFEST_URL = 'https://ftp.gajim.org/plugins_1/manifests.zip'
+MANIFEST_IMAGE_URL = 'https://ftp.gajim.org/plugins_1/manifests_images.zip'
+MANDATORY_FIELDS = ['name', 'version', 'description', 'authors', 'homepage']
+
+
+class Column(IntEnum):
+ PIXBUF = 0
+ DIR = 1
+ NAME = 2
+ LOCAL_VERSION = 3
+ VERSION = 4
+ UPGRADE = 5
+ DESCRIPTION = 6
+ AUTHORS = 7
+ HOMEPAGE = 8
+
+
+def get_local_version(plugin_name):
+ for plugin in gajim.plugin_manager.plugins:
+ if plugin.name == plugin_name:
+ return plugin.version
-def convert_version_to_list(version_str):
- version_list = version_str.split('.')
- l = []
- while len(version_list):
- l.append(int(version_list.pop(0)))
- return l
class PluginInstaller(GajimPlugin):
-
- @log_calls('PluginInstallerPlugin')
def init(self):
- self.description = _('Install and upgrade plugins from ftp')
+ self.description = _('Install and Upgrade Plugins')
self.config_dialog = PluginInstallerPluginConfigDialog(self)
- self.config_default_values = {'ftp_server': ('ftp.gajim.org', ''),
- 'check_update': (True, ''),
- 'check_update_periodically': (True, '')}
+ self.config_default_values = {'check_update': (True, '')}
self.window = None
self.progressbar = None
self.available_plugins_model = None
- self.upgrading = False # True when opened from upgrade popup dialog
self.timeout_id = 0
self.connected_ids = {}
icon = Gtk.Image()
- self.def_icon = icon.render_icon(Gtk.STOCK_PREFERENCES,
- Gtk.IconSize.MENU)
- if gajim.version.startswith('0.15'):
- self.server_folder = 'plugins_0.15'
- elif gajim.version.startswith('0.16.10'):
- self.server_folder = 'plugins_1'
- else:
- self.server_folder = 'plugins_0.16'
+ self.def_icon = icon.render_icon(
+ Gtk.STOCK_PREFERENCES, Gtk.IconSize.MENU)
- @log_calls('PluginInstallerPlugin')
def activate(self):
- self.pl_menuitem = gajim.interface.roster.xml.get_object(
- 'plugins_menuitem')
- self.id_ = self.pl_menuitem.connect_after('activate', self.on_activate)
- if 'plugins' in gajim.interface.instances:
- self.on_activate(None)
if self.config['check_update']:
self.timeout_id = GLib.timeout_add_seconds(30, self.check_update)
+ if 'plugins' in gajim.interface.instances:
+ self.on_activate(gajim.interface.instances['plugins'])
- @log_calls('PluginInstallerPlugin')
def warn_update(self, plugins):
def open_update(dummy):
- self.upgrading = True
- self.pl_menuitem.activate()
- nb = gajim.interface.instances['plugins'].plugins_notebook
- page = nb.page_num(self.hpaned)
- GLib.idle_add(nb.set_current_page, page)
+ get_action('plugins').activate()
+ page = self.notebook.page_num(self.paned)
+ self.notebook.set_current_page(page)
if plugins:
- plugins_str = '\n'.join(plugins)
- YesNoDialog(_('Plugins updates'), _('Some updates are available for'
- ' your installer plugins. Do you want to update those plugins:'
- '\n%s') % plugins_str, on_response_yes=open_update)
-
- def ftp_connect(self):
- if os.name == 'nt':
- ctx = ssl.create_default_context()
- con = ftplib.FTP_TLS(self.config['ftp_server'], context=ctx)
+ plugins_str = '\n' + '\n'.join(plugins)
+ YesNoDialog(
+ _('Plugins updates'),
+ _('Some updates are available for your installer plugins. '
+ 'Do you want to update those plugins:\n%s')
+ % plugins_str, on_response_yes=open_update)
else:
- con = ftplib.FTP_TLS(self.config['ftp_server'])
+ log.info('No updates found')
+ if hasattr(self, 'thread'):
+ del self.thread
- con.login()
- con.prot_p()
- return con
-
- @log_calls('PluginInstallerPlugin')
def check_update(self):
- def _run():
- try:
- to_update = []
- con = self.ftp_connect()
- con.cwd(self.server_folder)
- con.retrbinary('RETR manifests.zip', ftp.handleDownload)
- zip_file = zipfile.ZipFile(ftp.buffer_)
- manifest_list = zip_file.namelist()
- for filename in manifest_list:
- config = configparser.ConfigParser()
- conf_file = zip_file.open(filename)
- config.read_file(io.TextIOWrapper(conf_file, encoding='utf-8'))
- conf_file.close()
- if not config.has_section('info'):
- continue
- opts = config.options('info')
- if 'name' not in opts or 'version' not in opts or \
- 'description' not in opts or 'authors' not in opts or \
- 'homepage' not in opts:
- continue
- local_version = ftp.get_plugin_version(config.get(
- 'info', 'name'))
- if local_version:
- local = convert_version_to_list(local_version)
- remote = convert_version_to_list(config.get('info',
- 'version'))
- if remote > local:
- to_update.append(config.get('info', 'name'))
- con.quit()
- GLib.idle_add(self.warn_update, to_update)
- # check for updates at least once every 24 hours
- if self.config['check_update_periodically']:
- self.timeout_id = GLib.timeout_add_seconds(24*3600, self.check_update)
- except Exception as e:
- log.debug('Ftp error when check updates: %s' % str(e))
- ftp = Ftp(self)
- ftp.run = _run
- ftp.start()
+ if hasattr(self, 'thread'):
+ return
+ log.info('Checking for Updates...')
+ self.start_download(check_update=True)
self.timeout_id = 0
- @log_calls('PluginInstallerPlugin')
def deactivate(self):
- self.pl_menuitem.disconnect(self.id_)
- if hasattr(self, 'page_num'):
- self.notebook.remove_page(self.notebook.page_num(self.hpaned))
+ if hasattr(self, 'available_page'):
+ self.notebook.remove_page(self.notebook.page_num(self.paned))
self.notebook.set_current_page(0)
for id_, widget in list(self.connected_ids.items()):
widget.disconnect(id_)
- del self.page_num
- if hasattr(self, 'ftp'):
- del self.ftp
+ del self.available_page
+ if hasattr(self, 'thread'):
+ del self.thread
if self.timeout_id > 0:
GLib.source_remove(self.timeout_id)
self.timeout_id = 0
- def on_activate(self, widget):
- if 'plugins' not in gajim.interface.instances:
- return
- if hasattr(self, 'page_num'):
+ def on_activate(self, plugin_win):
+ if hasattr(self, 'available_page'):
# 'Available' tab exists
return
- self.installed_plugins_model = gajim.interface.instances[
- 'plugins'].installed_plugins_model
- self.notebook = gajim.interface.instances['plugins'].plugins_notebook
+ if hasattr(self, 'thread'):
+ del self.thread
+ self.installed_plugins_model = plugin_win.installed_plugins_model
+ self.notebook = plugin_win.plugins_notebook
id_ = self.notebook.connect('switch-page', self.on_notebook_switch_page)
self.connected_ids[id_] = self.notebook
- self.window = gajim.interface.instances['plugins'].window
+ self.window = plugin_win.window
id_ = self.window.connect('destroy', self.on_win_destroy)
self.connected_ids[id_] = self.window
self.Gtk_BUILDER_FILE_PATH = self.local_file_path('config_dialog.ui')
self.xml = Gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
- self.xml.add_objects_from_file(self.Gtk_BUILDER_FILE_PATH, ['hpaned2'])
- self.hpaned = self.xml.get_object('hpaned2')
- self.page_num = self.notebook.append_page(self.hpaned,
- Gtk.Label.new(_('Available')))
+ self.xml.add_objects_from_file(self.Gtk_BUILDER_FILE_PATH,
+ ['refresh', 'paned', 'plugin_store'])
widgets_to_extract = (
- 'plugin_name_label', 'available_treeview', 'progressbar',
- 'inslall_upgrade_button', 'plugin_authors_label',
- 'plugin_homepage_linkbutton', 'plugin_version_label')
+ 'name_label', 'available_treeview', 'progressbar', 'paned',
+ 'install_button', 'authors_label', 'homepage_linkbutton',
+ 'version_label', 'scrolled_description_window')
for widget_name in widgets_to_extract:
setattr(self, widget_name, self.xml.get_object(widget_name))
- self.available_plugins_model = Gtk.ListStore(GdkPixbuf.Pixbuf,
- object, str, str, str, bool,object, object, object)
- self.available_treeview.set_model(self.available_plugins_model)
- self.available_treeview.set_rules_hint(True)
- self.available_plugins_model.set_sort_column_id(2, Gtk.SortType.ASCENDING)
+ # Make Link in LinkButton not centered
+ style_provider = Gtk.CssProvider()
+ css = '.link { padding-left: 0px; padding-right: 0px; }'
+ style_provider.load_from_data(css.encode())
+ context = self.homepage_linkbutton.get_style_context()
+ context.add_provider(style_provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_USER)
- self.progressbar.set_property('no-show-all', True)
- renderer = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('Plugin'))
- cell = Gtk.CellRendererPixbuf()
- col.pack_start(cell, False)
- col.add_attribute(cell, 'pixbuf', C_PIXBUF)
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', C_NAME)
- col.set_resizable(True)
- col.set_property('expand', True)
- col.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
- self.available_treeview.append_column(col)
- col = Gtk.TreeViewColumn(_('Installed\nversion'), renderer,
- text=C_LOCAL_VERSION)
- self.available_treeview.append_column(col)
- col = Gtk.TreeViewColumn(_('Available\nversion'), renderer,
- text=C_VERSION)
- col.set_property('expand', False)
- self.available_treeview.append_column(col)
+ self.available_page = self.notebook.append_page(
+ self.paned, Gtk.Label.new(_('Available')))
- renderer = Gtk.CellRendererToggle()
- renderer.set_property('activatable', True)
- renderer.connect('toggled', self.available_plugins_toggled_cb)
- col = Gtk.TreeViewColumn(_('Install /\nUpgrade'), renderer,
- active=C_UPGRADE)
- self.available_treeview.append_column(col)
+ self.available_plugins_model = self.xml.get_object('plugin_store')
+ self.available_plugins_model.set_sort_column_id(
+ 2, Gtk.SortType.ASCENDING)
- if GObject.signal_lookup('error_signal', self.window) is 0:
- GObject.signal_new('error_signal', self.window,
- GObject.SignalFlags.RUN_LAST, GObject.TYPE_STRING,
- (GObject.TYPE_STRING,))
- GObject.signal_new('plugin_downloaded', self.window,
- GObject.SignalFlags.RUN_LAST, GObject.TYPE_STRING,
- (GObject.TYPE_PYOBJECT,))
-
- id_ = self.window.connect('error_signal', self.on_some_ftp_error)
- self.connected_ids[id_] = self.window
- id_ = self.window.connect('plugin_downloaded',
- self.on_plugin_downloaded)
- self.connected_ids[id_] = self.window
-
- selection = self.available_treeview.get_selection()
- selection.connect('changed',
- self.available_plugins_treeview_selection_changed)
- selection.set_mode(Gtk.SelectionMode.SINGLE)
-
- self._clear_available_plugin_info()
-
- self.plugin_description_textview = HtmlTextView()
- self.plugin_description_textview.set_wrap_mode(Gtk.WrapMode.WORD)
- sw = self.xml.get_object('scrolledwindow1')
- sw.add(self.plugin_description_textview)
+ self.description_textview = HtmlTextView()
+ self.description_textview.set_wrap_mode(Gtk.WrapMode.WORD)
+ self.scrolled_description_window.add(self.description_textview)
self.xml.connect_signals(self)
self.window.show_all()
def on_win_destroy(self, widget):
- if hasattr(self, 'ftp'):
- del self.ftp
- if hasattr(self, 'page_num'):
- del self.page_num
+ if hasattr(self, 'thread'):
+ del self.thread
+ if hasattr(self, 'available_page'):
+ del self.available_page
def available_plugins_toggled_cb(self, cell, path):
- is_active = self.available_plugins_model[path][C_UPGRADE]
- self.available_plugins_model[path][C_UPGRADE] = not is_active
+ is_active = self.available_plugins_model[path][Column.UPGRADE]
+ self.available_plugins_model[path][Column.UPGRADE] = not is_active
dir_list = []
for i in range(len(self.available_plugins_model)):
- if self.available_plugins_model[i][C_UPGRADE]:
- dir_list.append(self.available_plugins_model[i][C_DIR])
- if not dir_list:
- self.inslall_upgrade_button.set_property('sensitive', False)
- else:
- self.inslall_upgrade_button.set_property('sensitive', True)
+ if self.available_plugins_model[i][Column.UPGRADE]:
+ dir_list.append(self.available_plugins_model[i][Column.DIR])
+ self.install_button.set_property('sensitive', bool(dir_list))
def on_notebook_switch_page(self, widget, page, page_num):
- tab_label_text = self.notebook.get_tab_label_text(self.hpaned)
+ tab_label_text = self.notebook.get_tab_label_text(page)
if tab_label_text != (_('Available')):
return
- if not hasattr(self, 'ftp'):
+ if not hasattr(self, 'thread'):
self.available_plugins_model.clear()
- self.progressbar.show()
- self.ftp = Ftp(self)
- self.ftp.remote_dirs = None
- self.ftp.upgrading = True
- self.ftp.start()
+ self.start_download(upgrading=True)
- def on_inslall_upgrade_clicked(self, widget):
- self.inslall_upgrade_button.set_property('sensitive', False)
+ def on_install_upgrade_clicked(self, widget):
+ self.install_button.set_property('sensitive', False)
dir_list = []
for i in range(len(self.available_plugins_model)):
- if self.available_plugins_model[i][C_UPGRADE]:
- dir_list.append(self.available_plugins_model[i][C_DIR])
+ if self.available_plugins_model[i][Column.UPGRADE]:
+ dir_list.append(self.available_plugins_model[i][Column.DIR])
- ftp = Ftp(self)
- ftp.remote_dirs = dir_list
- ftp.start()
+ self.start_download(remote_dirs=dir_list)
- def on_some_ftp_error(self, widget, error_text):
- for i in range(len(self.available_plugins_model)):
- self.available_plugins_model[i][C_UPGRADE] = False
- self.progressbar.hide()
- def warn():
- WarningDialog(_('Ftp error'), error_text, self.window)
- GLib.idle_add(warn)
+ def on_error(self, reason):
+ if reason == 'CERTIFICATE_VERIFY_FAILED':
+ YesNoDialog(
+ _('Security error during download'),
+ _('A security error occurred when '
+ 'downloading. The certificate of the '
+ 'plugin archive could not be verified. '
+ 'this might be a security attack. '
+ '\n\nYou can continue at your risk. '
+ 'Do you want to do so? '
+ '(not recommended)'
+ ),
+ on_response_yes=lambda dlg:
+ self.start_download(secure=False, upgrading=True))
+ else:
+ if self.available_plugins_model:
+ for i in range(len(self.available_plugins_model)):
+ self.available_plugins_model[i][Column.UPGRADE] = False
+ self.progressbar.hide()
+ text = GLib.markup_escape_text(reason)
+ WarningDialog(_('Error in download'),
+ _('An error occurred when downloading\n\n'
+ '[%s]' % (str(text))), self.window)
- def on_plugin_downloaded(self, widget, plugin_dirs):
- dialog = HigDialog(None, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
- '', _('All selected plugins downloaded'))
- dialog.set_modal(False)
- dialog.set_transient_for(self.window)
+ def start_download(self, secure=True, remote_dirs=False,
+ upgrading=False, check_update=False):
+ log.info('Start Download...')
+ log.debug(
+ 'secure: %s, remote_dirs: %s, upgrading: %s, check_update: %s',
+ secure, remote_dirs, upgrading, check_update)
+ self.thread = DownloadAsync(
+ self, secure=secure, remote_dirs=remote_dirs,
+ upgrading=upgrading, check_update=check_update)
+ self.thread.start()
+ def on_plugin_downloaded(self, plugin_dirs):
for _dir in plugin_dirs:
is_active = False
plugins = None
@@ -336,8 +253,8 @@ class PluginInstaller(GajimPlugin):
if plugin:
if plugin.active:
is_active = True
- GLib.idle_add(gajim.plugin_manager.deactivate_plugin,
- plugin)
+ log.info('Deactivate Plugin: %s', plugin)
+ gajim.plugin_manager.deactivate_plugin(plugin)
gajim.plugin_manager.plugins.remove(plugin)
model = self.installed_plugins_model
@@ -346,315 +263,257 @@ class PluginInstaller(GajimPlugin):
model.remove(model.get_iter((row, 0)))
break
- plugins = self.scan_dir_for_plugin(plugin_dir)
+ log.info('Load Plugin from: %s', plugin_dir)
+ plugins = gajim.plugin_manager.scan_dir_for_plugins(
+ plugin_dir, package=True)
if not plugins:
+ log.warn('Loading Plugin failed')
continue
gajim.plugin_manager.add_plugin(plugins[0])
plugin = gajim.plugin_manager.plugins[-1]
+ log.info('Loading successful')
for row in range(len(self.available_plugins_model)):
- if plugin.name == self.available_plugins_model[row][C_NAME]:
- self.available_plugins_model[row][C_LOCAL_VERSION] = \
- plugin.version
- self.available_plugins_model[row][C_UPGRADE] = False
+ model_row = self.available_plugins_model[row]
+ if plugin.name == model_row[Column.NAME]:
+ model_row[Column.LOCAL_VERSION] = plugin.version
+ model_row[Column.UPGRADE] = False
if is_active:
- GLib.idle_add(gajim.plugin_manager.activate_plugin, plugin)
+ log.info('Activate Plugin: %s', plugin)
+ gajim.plugin_manager.activate_plugin(plugin)
# get plugin icon
icon_file = os.path.join(plugin.__path__, os.path.split(
plugin.__path__)[1]) + '.png'
icon = self.def_icon
if os.path.isfile(icon_file):
icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16)
- if not hasattr(plugin, 'activatable'):
- # version 0.15
- plugin.activatable = False
row = [plugin, plugin.name, is_active, plugin.activatable, icon]
self.installed_plugins_model.append(row)
+ dialog = HigDialog(
+ self.window, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
+ '', _('All selected plugins downloaded'))
+ dialog.set_modal(False)
dialog.popup()
def available_plugins_treeview_selection_changed(self, treeview_selection):
model, iter = treeview_selection.get_selected()
- self.xml.get_object('scrolledwindow1').get_children()[0].destroy()
- self.plugin_description_textview = HtmlTextView()
- self.plugin_description_textview.set_wrap_mode(Gtk.WrapMode.WORD)
- sw = self.xml.get_object('scrolledwindow1')
- sw.add(self.plugin_description_textview)
- sw.show_all()
- if iter:
- self.plugin_name_label.set_text(model.get_value(iter, C_NAME))
- self.plugin_version_label.set_text(model.get_value(iter, C_VERSION))
- self.plugin_authors_label.set_text(model.get_value(iter, C_AUTHORS))
- self.plugin_homepage_linkbutton.set_uri(model.get_value(iter,
- C_HOMEPAGE))
- self.plugin_homepage_linkbutton.set_label(model.get_value(iter,
- C_HOMEPAGE))
- label = self.plugin_homepage_linkbutton.get_children()[0]
- label.set_ellipsize(Pango.EllipsizeMode.END)
- self.plugin_homepage_linkbutton.set_property('sensitive', True)
- desc = _(model.get_value(iter, C_DESCRIPTION))
- if not desc.startswith('' + \
- desc + ' '
- desc = desc.replace('\n', '
')
- self.plugin_description_textview.display_html(
- desc, self.plugin_description_textview, None)
- self.plugin_description_textview.set_property('sensitive', True)
- else:
- self._clear_available_plugin_info()
-
- def _clear_available_plugin_info(self):
- self.plugin_name_label.set_text('')
- self.plugin_version_label.set_text('')
- self.plugin_authors_label.set_text('')
- self.plugin_homepage_linkbutton.set_uri('')
- self.plugin_homepage_linkbutton.set_label('')
- self.plugin_homepage_linkbutton.set_property('sensitive', False)
-
- def scan_dir_for_plugin(self, path):
- plugins_found = []
- conf = configparser.ConfigParser()
- fields = ('name', 'short_name', 'version', 'description', 'authors',
- 'homepage')
- if not os.path.isdir(path):
- return plugins_found
-
- dir_list = os.listdir(path)
- dir_, mod = os.path.split(path)
- sys.path.insert(0, dir_)
-
- manifest_path = os.path.join(path, 'manifest.ini')
- if not os.path.isfile(manifest_path):
- return plugins_found
-
- for elem_name in dir_list:
- file_path = os.path.join(path, elem_name)
- module = None
-
- if os.path.isfile(file_path) and fnmatch.fnmatch(file_path, '*.py'):
- module_name = os.path.splitext(elem_name)[0]
- if module_name == '__init__':
- continue
- try:
- full_module_name = '%s.%s' % (mod, module_name)
- if full_module_name in sys.modules:
- from imp import reload
- module = reload(sys.modules[full_module_name])
- else:
- module = __import__(full_module_name)
- except ValueError as value_error:
- pass
- except ImportError as import_error:
- pass
- except AttributeError as attribute_error:
- pass
- if module is None:
- continue
-
- for module_attr_name in [attr_name for attr_name in dir(module)
- if not (attr_name.startswith('__') or attr_name.endswith('__'))]:
- module_attr = getattr(module, module_attr_name)
- try:
- if not issubclass(module_attr, GajimPlugin) or \
- module_attr is GajimPlugin:
- continue
- module_attr.__path__ = os.path.abspath(os.path.dirname(
- file_path))
-
- # read metadata from manifest.ini
- with open(manifest_path) as _file:
- conf.read_file(_file)
- for option in fields:
- if conf.get('info', option) is '':
- raise configparser.NoOptionError('field empty')
- setattr(module_attr, option, conf.get('info', option))
- conf.remove_section('info')
- plugins_found.append(module_attr)
-
- except TypeError as type_error:
- pass
- except configparser.NoOptionError as type_error:
- # all fields are required
- pass
- return plugins_found
+ self.description_textview.get_buffer().set_text('')
+ self.name_label.set_text(model.get_value(iter, Column.NAME))
+ self.version_label.set_text(model.get_value(iter, Column.VERSION))
+ self.authors_label.set_text(model.get_value(iter, Column.AUTHORS))
+ self.homepage_linkbutton.set_uri(
+ model.get_value(iter, Column.HOMEPAGE))
+ self.homepage_linkbutton.set_label(
+ model.get_value(iter, Column.HOMEPAGE))
+ link_label = self.homepage_linkbutton.get_children()[0]
+ link_label.set_ellipsize(Pango.EllipsizeMode.END)
+ desc = _(model.get_value(iter, Column.DESCRIPTION))
+ if not desc.startswith(''
+ '%s') % desc
+ desc = desc.replace('\n', '
')
+ self.description_textview.display_html(
+ desc, self.description_textview, None)
def select_root_iter(self):
- if hasattr(self, 'page_num'):
- selection = self.available_treeview.get_selection()
- if selection.count_selected_rows() == 0:
- root_iter = self.available_plugins_model.get_iter_first()
- selection.select_iter(root_iter)
- scr_win = self.xml.get_object('scrolledwindow2')
- vadjustment = scr_win.get_vadjustment()
- if vadjustment:
- vadjustment.set_value(0)
+ selection = self.available_treeview.get_selection()
+ if selection.count_selected_rows() == 0:
+ root_iter = self.available_plugins_model.get_iter_first()
+ path = self.available_plugins_model.get_path(root_iter)
+ selection.select_iter(root_iter)
+ self.name_label.show()
+ self.homepage_linkbutton.show()
+ self.available_treeview.scroll_to_cell(path)
-class Ftp(threading.Thread):
- def __init__(self, plugin):
- super(Ftp, self).__init__()
+class DownloadAsync(threading.Thread):
+ def __init__(self, plugin, secure, remote_dirs, upgrading, check_update):
+ threading.Thread.__init__(self)
self.plugin = plugin
self.window = plugin.window
self.progressbar = plugin.progressbar
self.model = plugin.available_plugins_model
- self.buffer_ = io.BytesIO()
- self.remote_dirs = None
- self.append_to_model = True
- self.upgrading = False
+ self.remote_dirs = remote_dirs
+ self.upgrading = upgrading
+ self.secure = secure
+ self.check_update = check_update
+ self.pulse = None
icon = Gtk.Image()
- self.def_icon = icon.render_icon(Gtk.STOCK_PREFERENCES,
- Gtk.IconSize.MENU)
+ self.def_icon = icon.render_icon(
+ Gtk.STOCK_PREFERENCES, Gtk.IconSize.MENU)
def model_append(self, row):
- self.model.append(row)
+ row_data = [
+ row['icon'], row['remote_dir'], row['name'], row['local_version'],
+ row['version'], row['upgrade'], row['description'], row['authors'],
+ row['homepage']
+ ]
+ self.model.append(row_data)
return False
def progressbar_pulse(self):
self.progressbar.pulse()
return True
- def get_plugin_version(self, plugin_name):
- for plugin in gajim.plugin_manager.plugins:
- if plugin.name == plugin_name:
- return plugin.version
-
def run(self):
try:
- GLib.idle_add(self.progressbar.set_text,
- _('Connecting to server'))
- self.ftp = self.plugin.ftp_connect()
- self.ftp.cwd(self.plugin.server_folder)
- self.progressbar.set_show_text(True)
- if not self.remote_dirs:
- GLib.idle_add(self.progressbar.set_text,
- _('Scan files on the server'))
- self.ftp.retrbinary('RETR manifests_images.zip', self.handleDownload)
- zip_file = zipfile.ZipFile(self.buffer_)
- manifest_list = zip_file.namelist()
- progress_step = 1.0 / len(manifest_list)
- for filename in manifest_list:
- if not filename.endswith('manifest.ini'):
- continue
- dir_ = filename.split('/')[0]
- fract = self.progressbar.get_fraction() + progress_step
- GLib.idle_add(self.progressbar.set_fraction, fract)
- GLib.idle_add(self.progressbar.set_text,
- _('Reading "%s"') % dir_)
+ if self.check_update:
+ self.run_check_update()
+ else:
+ GLib.idle_add(self.progressbar.show)
+ self.pulse = GLib.timeout_add(150, self.progressbar_pulse)
+ self.run_download_plugin_list()
+ except urllib.error.URLError as exc:
+ if isinstance(exc.reason, ssl.SSLError):
+ ssl_reason = exc.reason.reason
+ if ssl_reason == 'CERTIFICATE_VERIFY_FAILED':
+ log.exception('Certificate verify failed')
+ GLib.idle_add(self.plugin.on_error, ssl_reason)
+ except Exception as exc:
+ GLib.idle_add(self.plugin.on_error, str(exc))
+ log.exception('Error fetching plugin list')
+ finally:
+ if self.pulse:
+ GLib.source_remove(self.pulse)
+ GLib.idle_add(self.progressbar.hide)
+ self.pulse = None
- config = configparser.ConfigParser()
- conf_file = zip_file.open(filename)
- config.read_file(io.TextIOWrapper(conf_file, encoding='utf-8'))
- conf_file.close()
- if not config.has_section('info'):
- continue
- opts = config.options('info')
- if 'name' not in opts or 'version' not in opts or \
- 'description' not in opts or 'authors' not in opts or \
- 'homepage' not in opts:
- continue
+ def parse_manifest(self, buf):
+ '''
+ given the buffer of the zipfile, returns the list of plugin manifests
+ '''
+ zip_file = ZipFile(buf)
+ manifest_list = zip_file.namelist()
+ plugins = []
+ for filename in manifest_list:
+ # Parse manifest
+ if not filename.endswith('manifest.ini'):
+ continue
+ config = configparser.ConfigParser()
+ conf_file = zip_file.open(filename)
+ config.read_file(io.TextIOWrapper(conf_file, encoding='utf-8'))
+ conf_file.close()
+ if not config.has_section('info'):
+ log.warn('Plugin is missing INFO section in manifest.ini. '
+ 'Plugin not loaded.')
+ continue
+ opts = config.options('info')
+ if not set(MANDATORY_FIELDS).issubset(opts):
+ log.warn('Plugin is missing mandatory fields in manifest.ini. '
+ 'Plugin not loaded.')
+ continue
+ # Add icon and remote dir
+ icon = None
+ remote_dir = filename.split('/')[0]
+ png_filename = '{0}/{0}.png'.format(remote_dir)
+ icon = self.def_icon
+ if png_filename in manifest_list:
+ data = zip_file.open(png_filename).read()
+ pix = GdkPixbuf.PixbufLoader()
+ pix.set_size(16, 16)
+ pix.write(data)
+ pix.close()
+ icon = pix.get_pixbuf()
- local_version = self.get_plugin_version(
- config.get('info', 'name'))
- upgrade = False
- if self.upgrading and local_version:
- local = convert_version_to_list(local_version)
- remote = convert_version_to_list(config.get('info',
- 'version'))
- if remote > local:
- upgrade = True
- GLib.idle_add(
- self.plugin.inslall_upgrade_button.set_property,
- 'sensitive', True)
- png_filename = dir_ + '/' + dir_ + '.png'
- if png_filename in manifest_list:
- data = zip_file.open(png_filename).read()
- pbl = GdkPixbuf.PixbufLoader()
- pbl.set_size(16, 16)
- pbl.write(data)
- pbl.close()
- def_icon = pbl.get_pixbuf()
- else:
- def_icon = self.def_icon
- if local_version:
- base_dir, user_dir = gajim.PLUGINS_DIRS
- local_dir = os.path.join(user_dir, dir_)
+ # transform to dictonary
+ config_dict = {}
+ for key, value in config.items('info'):
+ config_dict[key] = value
+ config_dict['icon'] = icon
+ config_dict['remote_dir'] = remote_dir
+ config_dict['upgrade'] = False
- GLib.idle_add(self.model_append, [def_icon, dir_,
- config.get('info', 'name'), local_version,
- config.get('info', 'version'), upgrade,
- config.get('info', 'description'),
- config.get('info', 'authors'),
- config.get('info', 'homepage'), ])
- self.ftp.quit()
- GLib.idle_add(self.progressbar.set_fraction, 0)
- if self.remote_dirs:
- self.download_plugin()
- GLib.idle_add(self.progressbar.hide)
+ plugins.append(config_dict)
+ return plugins
+
+ def download_url(self, url):
+ log.info('Fetching %s', url)
+ ssl_args = {}
+ if self.secure:
+ ssl_args['context'] = ssl.create_default_context(
+ cafile=self.plugin.local_file_path('DST_Root_CA_X3.pem'))
+ else:
+ ssl_args['context'] = ssl.create_default_context()
+ ssl_args['context'].check_hostname = False
+ ssl_args['context'].verify_mode = ssl.CERT_NONE
+
+ for flag in ('OP_NO_SSLv2', 'OP_NO_SSLv3',
+ 'OP_NO_TLSv1', 'OP_NO_TLSv1_1',
+ 'OP_NO_COMPRESSION',
+ ):
+ log.debug('SSL Options: +%s' % flag)
+ ssl_args['context'].options |= getattr(ssl, flag)
+ request = urlopen(url, **ssl_args)
+
+ return io.BytesIO(request.read())
+
+ def run_check_update(self):
+ to_update = []
+ zipbuf = self.download_url(MANIFEST_URL)
+ plugin_list = self.parse_manifest(zipbuf)
+ for plugin in plugin_list:
+ local_version = get_local_version(plugin['name'])
+ if local_version:
+ if V(plugin['version']) > V(local_version):
+ to_update.append(plugin['name'])
+ GLib.idle_add(self.plugin.warn_update, to_update)
+
+ def run_download_plugin_list(self):
+ if not self.remote_dirs:
+ log.info('Downloading Pluginlist...')
+ zipbuf = self.download_url(MANIFEST_IMAGE_URL)
+ plugin_list = self.parse_manifest(zipbuf)
+ for plugin in plugin_list:
+ plugin['local_version'] = get_local_version(plugin['name'])
+ if self.upgrading and plugin['local_version']:
+ if V(plugin['version']) > V(plugin['local_version']):
+ plugin['upgrade'] = True
+ GLib.idle_add(
+ self.plugin.install_button.set_property,
+ 'sensitive', True)
+ GLib.idle_add(self.model_append, plugin)
GLib.idle_add(self.plugin.select_root_iter)
- except Exception as e:
- self.window.emit('error_signal', str(e))
-
- def handleDownload(self, block):
- self.buffer_.write(block)
+ else:
+ self.download_plugin()
def download_plugin(self):
- GLib.idle_add(self.progressbar.show)
- self.pulse = GLib.timeout_add(150, self.progressbar_pulse)
- GLib.idle_add(self.progressbar.set_text, _('Creating a list of files'))
for remote_dir in self.remote_dirs:
filename = remote_dir + '.zip'
+ log.info('Download: %s', filename)
base_dir, user_dir = gajim.PLUGINS_DIRS
if not os.path.isdir(user_dir):
os.mkdir(user_dir)
- local_dir = ld = os.path.join(user_dir, remote_dir)
+ local_dir = os.path.join(user_dir, remote_dir)
if not os.path.isdir(local_dir):
os.mkdir(local_dir)
local_dir = os.path.split(user_dir)[0]
# downloading zip file
- GLib.idle_add(self.progressbar.set_text,
- _('Downloading "%s"') % filename)
- full_filename = os.path.join(user_dir, filename)
- self.buffer_ = io.BytesIO()
try:
- self.ftp.retrbinary('RETR %s' % filename, self.handleDownload)
- except ftplib.all_errors as e:
- print (str(e))
-
- with zipfile.ZipFile(self.buffer_) as zip_file:
- zip_file.extractall(os.path.join(user_dir))
-
- self.ftp.quit()
- GLib.idle_add(self.window.emit, 'plugin_downloaded', self.remote_dirs)
- GLib.source_remove(self.pulse)
+ plugin = posixpath.join(PLUGINS_URL, filename)
+ buf = self.download_url(plugin)
+ except:
+ log.exception("Error downloading plugin %s" % filename)
+ continue
+ with ZipFile(buf) as zip_file:
+ zip_file.extractall(os.path.join(local_dir, 'plugins'))
+ GLib.idle_add(self.plugin.on_plugin_downloaded, self.remote_dirs)
class PluginInstallerPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
- self.Gtk_BUILDER_FILE_PATH = self.plugin.local_file_path(
- 'config_dialog.ui')
+ glade_file_path = self.plugin.local_file_path('config_dialog.ui')
self.xml = Gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
- self.xml.add_objects_from_file(self.Gtk_BUILDER_FILE_PATH, ['hbox111'])
- hbox = self.xml.get_object('hbox111')
- self.get_child().pack_start(hbox, True, True, 0)
+ self.xml.add_objects_from_file(glade_file_path, ['config_grid'])
+ grid = self.xml.get_object('config_grid')
+ self.get_child().pack_start(grid, True, True, 0)
self.xml.connect_signals(self)
- self.connect('hide', self.on_hide)
def on_run(self):
- widget = self.xml.get_object('ftp_server')
- widget.set_text(str(self.plugin.config['ftp_server']))
self.xml.get_object('check_update').set_active(
self.plugin.config['check_update'])
- self.xml.get_object('check_update_periodically').set_active(
- self.plugin.config['check_update_periodically'])
-
- def on_hide(self, widget):
- widget = self.xml.get_object('ftp_server')
- self.plugin.config['ftp_server'] = widget.get_text()
def on_check_update_toggled(self, widget):
self.plugin.config['check_update'] = widget.get_active()
-
- def on_check_update_periodically_toggled(self, widget):
- self.plugin.config['check_update_periodically'] = widget.get_active()