From c0b3f36b24da531e6b94505bb73a7ae80859926f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 20 Feb 2017 16:30:19 +0100 Subject: [PATCH 01/19] [plugin_installer] Port recent plugin_installer This inlcudes slight refactoring and switch to https instead of ftps --- plugin_installer/config_dialog.ui | 18 ---- plugin_installer/plugin_installer.py | 156 +++++++++++++-------------- 2 files changed, 74 insertions(+), 100 deletions(-) diff --git a/plugin_installer/config_dialog.ui b/plugin_installer/config_dialog.ui index fe7764c..01a796c 100644 --- a/plugin_installer/config_dialog.ui +++ b/plugin_installer/config_dialog.ui @@ -394,24 +394,6 @@ 1 - - - Check update every 24 hours - False - True - False - False - False - 0.5 - True - - - - False - True - 2 - - diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index e0197fb..bca7a5e 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -25,7 +25,6 @@ from gi.repository import Pango from gi.repository import GLib from gi.repository import GObject -import ftplib import io import threading import configparser @@ -33,9 +32,11 @@ import os import fnmatch import sys import zipfile -import ssl import logging +import posixpath +from urllib.request import urlopen +from urllib.parse import urlparse, urljoin from common import gajim from plugins import GajimPlugin from plugins.helpers import log_calls, log @@ -68,11 +69,11 @@ 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', ''), + self.config_default_values = {'http_server': ('https://ftp.gajim.org', ''), 'check_update': (True, ''), - 'check_update_periodically': (True, '')} + } self.window = None self.progressbar = None self.available_plugins_model = None @@ -82,20 +83,10 @@ class PluginInstaller(GajimPlugin): 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.server_folder = 'plugins_1' @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) @@ -113,34 +104,53 @@ class PluginInstaller(GajimPlugin): ' 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) - else: - con = ftplib.FTP_TLS(self.config['ftp_server']) + def parse_manifest(self, buf): + ''' + given the buffer of the zipfile, returns the list of plugin manifests + ''' + zip_file = zipfile.ZipFile(buf) + manifest_list = zip_file.namelist() + plugins = [] + 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 + plugins.append(config) + return plugins - con.login() - con.prot_p() - return con + def retrieve_path(self, directory, fname): + print('retrive path') + server = self.config['http_server'] + if not server: + server = self.config_default_values['http_server'][0] + if not urlparse(server).scheme: + server = 'https://' + server + if urlparse(server).scheme != 'https': + log.warn('Warning: not using HTTPS is a ' + 'very serious security issue!') + location = posixpath.join(directory, fname) + uri = urljoin(server, location) + log.debug('Fetching {}'.format(uri)) + request = urlopen(uri) + + manifest_buffer = io.BytesIO(request.read()) + + return manifest_buffer + + def retrieve_manifest(self): + return self.retrieve_path(self.server_folder, 'manifests.zip') @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 + zipbuf = self.retrieve_manifest() + plugin_manifests = self.parse_manifest(zipbuf) + for config in plugin_manifests: 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 \ @@ -154,13 +164,9 @@ class PluginInstaller(GajimPlugin): '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)) + log.error('Ftp error when check updates: %s' % str(e), exc_info=True) ftp = Ftp(self) ftp.run = _run ftp.start() @@ -181,18 +187,15 @@ class PluginInstaller(GajimPlugin): GLib.source_remove(self.timeout_id) self.timeout_id = 0 - def on_activate(self, widget): - if 'plugins' not in gajim.interface.instances: - return + def on_activate(self, plugin_win): if hasattr(self, 'page_num'): # 'Available' tab exists return - self.installed_plugins_model = gajim.interface.instances[ - 'plugins'].installed_plugins_model - self.notebook = gajim.interface.instances['plugins'].plugins_notebook + 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') @@ -461,11 +464,10 @@ class PluginInstaller(GajimPlugin): file_path)) # read metadata from manifest.ini - with open(manifest_path) as _file: - conf.read_file(_file) + conf.readfp(open(manifest_path, 'r')) for option in fields: if conf.get('info', option) is '': - raise configparser.NoOptionError('field empty') + raise configparser.NoOptionError setattr(module_attr, option, conf.get('info', option)) conf.remove_section('info') plugins_found.append(module_attr) @@ -496,13 +498,12 @@ class Ftp(threading.Thread): 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 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) @@ -521,14 +522,15 @@ class Ftp(threading.Thread): 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_) + _('Scan files on the server')) + try: + buf = self.plugin.retrieve_path(self.plugin.server_folder, 'manifests_images.zip') + except: + log.exception("Error fetching plugin list") + return + zip_file = zipfile.ZipFile(buf) manifest_list = zip_file.namelist() progress_step = 1.0 / len(manifest_list) for filename in manifest_list: @@ -593,9 +595,6 @@ class Ftp(threading.Thread): except Exception as e: self.window.emit('error_signal', str(e)) - def handleDownload(self, block): - self.buffer_.write(block) - def download_plugin(self): GLib.idle_add(self.progressbar.show) self.pulse = GLib.timeout_add(150, self.progressbar_pulse) @@ -605,24 +604,22 @@ class Ftp(threading.Thread): 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() + _('Downloading "%s"') % filename) 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)) - + buf = self.plugin.retrieve_path(self.plugin.server_folder, + filename) + except: + log.exception("Error downloading plugin %s" % filename) + continue + with zipfile.ZipFile(buf) as zip_file: + zip_file.extractall(os.path.join(local_dir, 'plugins')) self.ftp.quit() GLib.idle_add(self.window.emit, 'plugin_downloaded', self.remote_dirs) GLib.source_remove(self.pulse) @@ -642,12 +639,10 @@ class PluginInstallerPluginConfigDialog(GajimPluginConfigDialog): 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'])) + widget = self.xml.get_object('http_server') + widget.set_text(str(self.plugin.config['http_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') @@ -655,6 +650,3 @@ class PluginInstallerPluginConfigDialog(GajimPluginConfigDialog): 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() From b520fa803a58bd93ae8e224460445b342cfe1b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 20 Feb 2017 16:46:54 +0100 Subject: [PATCH 02/19] [plugin_installer] Use IntEnum --- plugin_installer/plugin_installer.py | 66 ++++++++++++++-------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index bca7a5e..7b9fb71 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -43,20 +43,22 @@ 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 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) + +class Column(IntEnum): + PIXBUF = 0 + DIR = 1 + NAME = 2 + LOCAL_VERSION = 3 + VERSION = 4 + UPGRADE = 5 + DESCRIPTION = 6 + AUTHORS = 7 + HOMEPAGE = 8 + def convert_version_to_list(version_str): version_list = version_str.split('.') @@ -225,18 +227,18 @@ class PluginInstaller(GajimPlugin): col = Gtk.TreeViewColumn(_('Plugin')) cell = Gtk.CellRendererPixbuf() col.pack_start(cell, False) - col.add_attribute(cell, 'pixbuf', C_PIXBUF) + col.add_attribute(cell, 'pixbuf', Column.PIXBUF) col.pack_start(renderer, True) - col.add_attribute(renderer, 'text', C_NAME) + col.add_attribute(renderer, 'text', Column.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) + text=Column.LOCAL_VERSION) self.available_treeview.append_column(col) col = Gtk.TreeViewColumn(_('Available\nversion'), renderer, - text=C_VERSION) + text=Column.VERSION) col.set_property('expand', False) self.available_treeview.append_column(col) @@ -244,7 +246,7 @@ class PluginInstaller(GajimPlugin): renderer.set_property('activatable', True) renderer.connect('toggled', self.available_plugins_toggled_cb) col = Gtk.TreeViewColumn(_('Install /\nUpgrade'), renderer, - active=C_UPGRADE) + active=Column.UPGRADE) self.available_treeview.append_column(col) if GObject.signal_lookup('error_signal', self.window) is 0: @@ -283,12 +285,12 @@ class PluginInstaller(GajimPlugin): del self.page_num 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 self.available_plugins_model[i][Column.UPGRADE]: + dir_list.append(self.available_plugins_model[i][Column.DIR]) if not dir_list: self.inslall_upgrade_button.set_property('sensitive', False) else: @@ -310,8 +312,8 @@ class PluginInstaller(GajimPlugin): self.inslall_upgrade_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 @@ -319,7 +321,7 @@ class PluginInstaller(GajimPlugin): 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.available_plugins_model[i][Column.UPGRADE] = False self.progressbar.hide() def warn(): WarningDialog(_('Ftp error'), error_text, self.window) @@ -355,10 +357,10 @@ class PluginInstaller(GajimPlugin): gajim.plugin_manager.add_plugin(plugins[0]) plugin = gajim.plugin_manager.plugins[-1] 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] = \ + if plugin.name == self.available_plugins_model[row][Column.NAME]: + self.available_plugins_model[row][Column.LOCAL_VERSION] = \ plugin.version - self.available_plugins_model[row][C_UPGRADE] = False + self.available_plugins_model[row][Column.UPGRADE] = False if is_active: GLib.idle_add(gajim.plugin_manager.activate_plugin, plugin) # get plugin icon @@ -384,17 +386,17 @@ class PluginInstaller(GajimPlugin): 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_name_label.set_text(model.get_value(iter, Column.NAME)) + self.plugin_version_label.set_text(model.get_value(iter, Column.VERSION)) + self.plugin_authors_label.set_text(model.get_value(iter, Column.AUTHORS)) self.plugin_homepage_linkbutton.set_uri(model.get_value(iter, - C_HOMEPAGE)) + Column.HOMEPAGE)) self.plugin_homepage_linkbutton.set_label(model.get_value(iter, - C_HOMEPAGE)) + Column.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)) + desc = _(model.get_value(iter, Column.DESCRIPTION)) if not desc.startswith('' + \ desc + ' ' From a41a3e915b2bc03d18a93cbff64d22f10f94d43a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 20 Feb 2017 17:43:18 +0100 Subject: [PATCH 03/19] [plugin_installer] Refactor config window - Remove Server URL entry field - Dont use deprecated widgets anymore --- plugin_installer/config_dialog.ui | 156 +++++++++++++++------------ plugin_installer/plugin_installer.py | 16 +-- 2 files changed, 91 insertions(+), 81 deletions(-) diff --git a/plugin_installer/config_dialog.ui b/plugin_installer/config_dialog.ui index 01a796c..dca05f7 100644 --- a/plugin_installer/config_dialog.ui +++ b/plugin_installer/config_dialog.ui @@ -1,7 +1,35 @@ - + - + + + False + + + True + False + 12 + 12 + 12 + 12 + + + Check update after start + True + False + False + False + True + + + + 0 + 0 + + + + + False @@ -27,7 +55,62 @@ True 1 - + + + + + Plugin + True + + + + 0 + + + + + + 2 + + + + + + + Installed + + + + 3 + + + + + + + Available + + + + 4 + + + + + + + Install / +Upgrade + 0.5 + + + + + + 5 + + + @@ -179,9 +262,9 @@ True True + False True none - False 0 @@ -332,69 +415,4 @@ - - False - - - True - False - 6 - - - True - False - 6 - - - True - False - FTP Server: - 0 - - - False - True - 0 - - - - - True - True - - - - True - True - 1 - - - - - False - True - 0 - - - - - Check update after start - False - True - False - False - False - 0.5 - True - - - - False - True - 1 - - - - - diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index 7b9fb71..ebf9540 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -629,26 +629,18 @@ class Ftp(threading.Thread): 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('http_server') - widget.set_text(str(self.plugin.config['http_server'])) self.xml.get_object('check_update').set_active( self.plugin.config['check_update']) - 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() From 02f2f38bbe02b09b5290fde5c565b65351891350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Tue, 21 Feb 2017 22:15:29 +0100 Subject: [PATCH 04/19] [plugin_installer] Refactor UI - Move UI code to glade file - Remove use of deprecated widgets - Rename some widgets --- plugin_installer/config_dialog.ui | 381 +++++++++++---------------- plugin_installer/plugin_installer.py | 121 ++++----- 2 files changed, 205 insertions(+), 297 deletions(-) diff --git a/plugin_installer/config_dialog.ui b/plugin_installer/config_dialog.ui index dca05f7..5ce735d 100644 --- a/plugin_installer/config_dialog.ui +++ b/plugin_installer/config_dialog.ui @@ -30,29 +30,67 @@ - + + + + + + + + + + + + + + + + + + + + + + + + True + False + gtk-refresh + + False - - 800 + True True - 340 - True - + True False + + + False + end + + + 0 + 1 + + True True + True 6 never True True + True + plugin_store + False 1 @@ -116,294 +154,185 @@ Upgrade - True - True - 0 - - - - - False - end - - - False - True - 1 + 0 + 0 - False + True False - + + 350 True False 6 - vertical - 6 + 3 + 5 - + True False + start &lt;empty&gt; True - 0 - False - True - 0 + 0 + 0 + 2 - + True False - 6 - - - True - False - Version: - - - False - True - 0 - - - - - True - False - <empty> - True - 0 - - - True - True - 1 - - + start + Version: - False - True - 1 + 0 + 1 - + True False - 6 - - - True - False - Authors: - 0 - - - False - True - 0 - - - - - True - False - <empty> - word-char - True - end - 0 - 0 - - - True - True - 1 - - + start + start + Authors: - False - True - 2 + 0 + 2 - + True False - - - True - False - Homepage: - - - False - True - 0 - - - - - True - True - False - True - none - 0 - - - True - True - 1 - - + start + Homepage: - False - True - 3 + 0 + 3 - + True False + start + Description: + + + 0 + 4 + 2 + + + + + True + False + start + False + <empty> + True + + + 1 + 1 + + + + + True + False + start + start + <empty> + word-char + True + end + + + 1 + 2 + + + + + True + True + False + True + start + True + none + + + 1 + 3 + + + + + True + False + end + 2 + 2 vertical + start - - True - False - - - True - False - Description: - - - False - True - 0 - - - - - - - - False - True - 0 - - - - + + Install/Upgrade True True - in - - - + True + start + False + refresh + True + True True - 1 + 0 - True - True - 4 + 0 + 6 + 2 - + True - False + True + True + True + in - - - True - False - end - - - False - True - False - True - True - - - - True - False - - - True - False - gtk-refresh - - - True - True - 0 - - - - - True - False - Install/Upgrade - 0 - - - True - True - 1 - - - - - - - False - False - 0 - - - - - False - False - end - 1 - - - False - False - 5 + 0 + 5 + 2 diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index ebf9540..b2086af 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -203,51 +203,32 @@ class PluginInstaller(GajimPlugin): 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) + # 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.page_num = self.notebook.append_page( + self.paned, Gtk.Label.new(_('Available'))) + + self.available_plugins_model = self.xml.get_object('plugin_store') self.available_plugins_model.set_sort_column_id(2, Gtk.SortType.ASCENDING) 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', Column.PIXBUF) - col.pack_start(renderer, True) - col.add_attribute(renderer, 'text', Column.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=Column.LOCAL_VERSION) - self.available_treeview.append_column(col) - col = Gtk.TreeViewColumn(_('Available\nversion'), renderer, - text=Column.VERSION) - col.set_property('expand', False) - self.available_treeview.append_column(col) - - renderer = Gtk.CellRendererToggle() - renderer.set_property('activatable', True) - renderer.connect('toggled', self.available_plugins_toggled_cb) - col = Gtk.TreeViewColumn(_('Install /\nUpgrade'), renderer, - active=Column.UPGRADE) - self.available_treeview.append_column(col) if GObject.signal_lookup('error_signal', self.window) is 0: GObject.signal_new('error_signal', self.window, @@ -264,16 +245,15 @@ class PluginInstaller(GajimPlugin): self.connected_ids[id_] = self.window selection = self.available_treeview.get_selection() - selection.connect('changed', - self.available_plugins_treeview_selection_changed) + 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() @@ -292,12 +272,12 @@ class PluginInstaller(GajimPlugin): if self.available_plugins_model[i][Column.UPGRADE]: dir_list.append(self.available_plugins_model[i][Column.DIR]) if not dir_list: - self.inslall_upgrade_button.set_property('sensitive', False) + self.install_button.set_property('sensitive', False) else: - self.inslall_upgrade_button.set_property('sensitive', True) + self.install_button.set_property('sensitive', True) 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(self.paned) if tab_label_text != (_('Available')): return if not hasattr(self, 'ftp'): @@ -308,8 +288,8 @@ class PluginInstaller(GajimPlugin): self.ftp.upgrading = True self.ftp.start() - 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][Column.UPGRADE]: @@ -379,41 +359,41 @@ class PluginInstaller(GajimPlugin): 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) + self.xml.get_object('scrolled_description_window').get_children()[0].destroy() + self.description_textview = HtmlTextView() + self.description_textview.set_wrap_mode(Gtk.WrapMode.WORD) + sw = self.xml.get_object('scrolled_description_window') + sw.add(self.description_textview) sw.show_all() if iter: - self.plugin_name_label.set_text(model.get_value(iter, Column.NAME)) - self.plugin_version_label.set_text(model.get_value(iter, Column.VERSION)) - self.plugin_authors_label.set_text(model.get_value(iter, Column.AUTHORS)) - self.plugin_homepage_linkbutton.set_uri(model.get_value(iter, + 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.plugin_homepage_linkbutton.set_label(model.get_value(iter, + self.homepage_linkbutton.set_label(model.get_value(iter, Column.HOMEPAGE)) - label = self.plugin_homepage_linkbutton.get_children()[0] + label = self.homepage_linkbutton.get_children()[0] label.set_ellipsize(Pango.EllipsizeMode.END) - self.plugin_homepage_linkbutton.set_property('sensitive', True) + self.homepage_linkbutton.set_property('sensitive', True) desc = _(model.get_value(iter, Column.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) + self.description_textview.display_html( + desc, self.description_textview, None) + self.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) + self.name_label.set_text('') + self.version_label.set_text('') + self.authors_label.set_text('') + self.homepage_linkbutton.set_uri('') + self.homepage_linkbutton.set_label('') + self.homepage_linkbutton.set_property('sensitive', False) def scan_dir_for_plugin(self, path): plugins_found = [] @@ -487,7 +467,7 @@ class PluginInstaller(GajimPlugin): 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') + scr_win = self.xml.get_object('scrolled_description_window') vadjustment = scr_win.get_vadjustment() if vadjustment: vadjustment.set_value(0) @@ -566,7 +546,7 @@ class Ftp(threading.Thread): if remote > local: upgrade = True GLib.idle_add( - self.plugin.inslall_upgrade_button.set_property, + self.plugin.install_button.set_property, 'sensitive', True) png_filename = dir_ + '/' + dir_ + '.png' if png_filename in manifest_list: @@ -581,7 +561,6 @@ class Ftp(threading.Thread): if local_version: base_dir, user_dir = gajim.PLUGINS_DIRS local_dir = os.path.join(user_dir, dir_) - GLib.idle_add(self.model_append, [def_icon, dir_, config.get('info', 'name'), local_version, config.get('info', 'version'), upgrade, From ef23d76c5dd4b4832b92edf4a0481ec15aaf8e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Tue, 21 Feb 2017 22:18:12 +0100 Subject: [PATCH 05/19] [plugin_installer] Refactor async download --- plugin_installer/plugin_installer.py | 172 +++++++++++++-------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index b2086af..8eaf6e5 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -106,72 +106,9 @@ class PluginInstaller(GajimPlugin): ' your installer plugins. Do you want to update those plugins:' '\n%s') % plugins_str, on_response_yes=open_update) - def parse_manifest(self, buf): - ''' - given the buffer of the zipfile, returns the list of plugin manifests - ''' - zip_file = zipfile.ZipFile(buf) - manifest_list = zip_file.namelist() - plugins = [] - 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 - plugins.append(config) - return plugins - - def retrieve_path(self, directory, fname): - print('retrive path') - server = self.config['http_server'] - if not server: - server = self.config_default_values['http_server'][0] - if not urlparse(server).scheme: - server = 'https://' + server - if urlparse(server).scheme != 'https': - log.warn('Warning: not using HTTPS is a ' - 'very serious security issue!') - location = posixpath.join(directory, fname) - uri = urljoin(server, location) - log.debug('Fetching {}'.format(uri)) - request = urlopen(uri) - - manifest_buffer = io.BytesIO(request.read()) - - return manifest_buffer - - def retrieve_manifest(self): - return self.retrieve_path(self.server_folder, 'manifests.zip') - - @log_calls('PluginInstallerPlugin') def check_update(self): - def _run(): - try: - to_update = [] - zipbuf = self.retrieve_manifest() - plugin_manifests = self.parse_manifest(zipbuf) - for config in plugin_manifests: - 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')) - GLib.idle_add(self.warn_update, to_update) - except Exception as e: - log.error('Ftp error when check updates: %s' % str(e), exc_info=True) - ftp = Ftp(self) - ftp.run = _run - ftp.start() + self.thread = download_async(self, check_update=True) + self.thread.start() self.timeout_id = 0 @log_calls('PluginInstallerPlugin') @@ -183,8 +120,8 @@ class PluginInstaller(GajimPlugin): for id_, widget in list(self.connected_ids.items()): widget.disconnect(id_) del self.page_num - if hasattr(self, 'ftp'): - del self.ftp + if hasattr(self, 'thread'): + del self.thread if self.timeout_id > 0: GLib.source_remove(self.timeout_id) self.timeout_id = 0 @@ -259,8 +196,8 @@ class PluginInstaller(GajimPlugin): self.window.show_all() def on_win_destroy(self, widget): - if hasattr(self, 'ftp'): - del self.ftp + if hasattr(self, 'thread'): + del self.thread if hasattr(self, 'page_num'): del self.page_num @@ -280,13 +217,11 @@ class PluginInstaller(GajimPlugin): tab_label_text = self.notebook.get_tab_label_text(self.paned) 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.thread = download_async(self, upgrading=True) + self.thread.start() def on_install_upgrade_clicked(self, widget): self.install_button.set_property('sensitive', False) @@ -295,9 +230,8 @@ class PluginInstaller(GajimPlugin): 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.thread = download_async(self, remote=dir_list) + self.thread.start() def on_some_ftp_error(self, widget, error_text): for i in range(len(self.available_plugins_model)): @@ -473,16 +407,17 @@ class PluginInstaller(GajimPlugin): vadjustment.set_value(0) -class Ftp(threading.Thread): - def __init__(self, plugin): - super(Ftp, self).__init__() +class download_async(threading.Thread): + def __init__(self, plugin, remote_dirs=None, + upgrading=False, check_update=False): + threading.Thread.__init__(self) self.plugin = plugin self.window = plugin.window self.progressbar = plugin.progressbar self.model = plugin.available_plugins_model - self.remote_dirs = None - self.append_to_model = True - self.upgrading = False + self.remote_dirs = remote_dirs + self.upgrading = upgrading + self.check_update = check_update icon = Gtk.Image() self.def_icon = icon.render_icon( Gtk.STOCK_PREFERENCES, Gtk.IconSize.MENU) @@ -501,6 +436,73 @@ class Ftp(threading.Thread): return plugin.version def run(self): + if self.check_update: + self.run_check_update() + else: + self.run_download_plugin_list() + + def parse_manifest(self, buf): + ''' + given the buffer of the zipfile, returns the list of plugin manifests + ''' + zip_file = zipfile.ZipFile(buf) + manifest_list = zip_file.namelist() + plugins = [] + 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 + plugins.append(config) + return plugins + + def retrieve_path(self, directory, fname): + server = self.plugin.config['http_server'] + if not server: + server = self.plugin.config_default_values['http_server'][0] + if not urlparse(server).scheme: + server = 'https://' + server + if urlparse(server).scheme != 'https': + log.warn('Warning: not using HTTPS is a ' + 'very serious security issue!') + location = posixpath.join(directory, fname) + uri = urljoin(server, location) + log.debug('Fetching {}'.format(uri)) + request = urlopen(uri) + + manifest_buffer = io.BytesIO(request.read()) + + return manifest_buffer + + def retrieve_manifest(self): + return self.retrieve_path(self.plugin.server_folder, 'manifests.zip') + + def run_check_update(self): + try: + to_update = [] + zipbuf = self.retrieve_manifest() + plugin_manifests = self.parse_manifest(zipbuf) + for config in plugin_manifests: + 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 = self.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')) + GLib.idle_add(self.warn_update, to_update) + except Exception as e: + log.error('Ftp error when check updates: %s' % str(e), exc_info=True) + + def run_download_plugin_list(self): try: GLib.idle_add(self.progressbar.set_text, _('Connecting to server')) @@ -508,7 +510,7 @@ class Ftp(threading.Thread): GLib.idle_add(self.progressbar.set_text, _('Scan files on the server')) try: - buf = self.plugin.retrieve_path(self.plugin.server_folder, 'manifests_images.zip') + buf = self.retrieve_path(self.plugin.server_folder, 'manifests_images.zip') except: log.exception("Error fetching plugin list") return @@ -567,7 +569,6 @@ class Ftp(threading.Thread): 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() @@ -601,7 +602,6 @@ class Ftp(threading.Thread): continue with zipfile.ZipFile(buf) as zip_file: zip_file.extractall(os.path.join(local_dir, 'plugins')) - self.ftp.quit() GLib.idle_add(self.window.emit, 'plugin_downloaded', self.remote_dirs) GLib.source_remove(self.pulse) From 9c3df4f32d58026c99ab8f64604a85d53ddd0412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Thu, 23 Feb 2017 22:44:08 +0100 Subject: [PATCH 06/19] [plugin_installer] Further Refactoring - Rename some methods more appropriate - Move some methods to more appropriate places - Dont emit GTK events to signal GTK, use GLib.idle_add - Remove option to set the plugin repo URL, use global vars instead --- plugin_installer/plugin_installer.py | 311 +++++++++++---------------- 1 file changed, 131 insertions(+), 180 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index 8eaf6e5..26758c7 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -23,7 +23,6 @@ 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 io import threading @@ -36,7 +35,6 @@ import logging import posixpath from urllib.request import urlopen -from urllib.parse import urlparse, urljoin from common import gajim from plugins import GajimPlugin from plugins.helpers import log_calls, log @@ -44,9 +42,14 @@ 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') +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' + class Column(IntEnum): PIXBUF = 0 @@ -60,6 +63,12 @@ class Column(IntEnum): HOMEPAGE = 8 +def get_plugin_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 = [] @@ -73,53 +82,53 @@ class PluginInstaller(GajimPlugin): def init(self): self.description = _('Install and Upgrade Plugins') self.config_dialog = PluginInstallerPluginConfigDialog(self) - self.config_default_values = {'http_server': ('https://ftp.gajim.org', ''), - 'check_update': (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) - self.server_folder = 'plugins_1' @log_calls('PluginInstallerPlugin') def activate(self): 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) + 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: + if hasattr(self, 'thread'): + del self.thread def check_update(self): - self.thread = download_async(self, check_update=True) + if hasattr(self, 'thread'): + return + self.thread = DownloadAsync(self, check_update=True) self.thread.start() 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 + del self.available_page if hasattr(self, 'thread'): del self.thread if self.timeout_id > 0: @@ -127,9 +136,11 @@ class PluginInstaller(GajimPlugin): self.timeout_id = 0 def on_activate(self, plugin_win): - if hasattr(self, 'page_num'): + if hasattr(self, 'available_page'): # 'Available' tab exists return + 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) @@ -159,7 +170,7 @@ class PluginInstaller(GajimPlugin): context.add_provider(style_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) - self.page_num = self.notebook.append_page( + self.available_page = self.notebook.append_page( self.paned, Gtk.Label.new(_('Available'))) self.available_plugins_model = self.xml.get_object('plugin_store') @@ -167,20 +178,6 @@ class PluginInstaller(GajimPlugin): self.progressbar.set_property('no-show-all', True) - 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) @@ -198,8 +195,8 @@ class PluginInstaller(GajimPlugin): def on_win_destroy(self, widget): if hasattr(self, 'thread'): del self.thread - if hasattr(self, 'page_num'): - del self.page_num + if hasattr(self, 'available_page'): + del self.available_page def available_plugins_toggled_cb(self, cell, path): is_active = self.available_plugins_model[path][Column.UPGRADE] @@ -208,19 +205,15 @@ class PluginInstaller(GajimPlugin): for i in range(len(self.available_plugins_model)): if self.available_plugins_model[i][Column.UPGRADE]: dir_list.append(self.available_plugins_model[i][Column.DIR]) - if not dir_list: - self.install_button.set_property('sensitive', False) - else: - self.install_button.set_property('sensitive', True) + 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.paned) + tab_label_text = self.notebook.get_tab_label_text(page) if tab_label_text != (_('Available')): return if not hasattr(self, 'thread'): self.available_plugins_model.clear() - self.progressbar.show() - self.thread = download_async(self, upgrading=True) + self.thread = DownloadAsync(self, upgrading=True) self.thread.start() def on_install_upgrade_clicked(self, widget): @@ -230,18 +223,18 @@ class PluginInstaller(GajimPlugin): if self.available_plugins_model[i][Column.UPGRADE]: dir_list.append(self.available_plugins_model[i][Column.DIR]) - self.thread = download_async(self, remote=dir_list) + self.thread = DownloadAsync(self, remote_dirs=dir_list) self.thread.start() - def on_some_ftp_error(self, widget, error_text): - for i in range(len(self.available_plugins_model)): - self.available_plugins_model[i][Column.UPGRADE] = False - self.progressbar.hide() - def warn(): - WarningDialog(_('Ftp error'), error_text, self.window) - GLib.idle_add(warn) + def on_error(self, error_text): + 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(error_text) + WarningDialog(_('Error'), text, self.window) - def on_plugin_downloaded(self, widget, plugin_dirs): + def on_plugin_downloaded(self, plugin_dirs): dialog = HigDialog(None, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, '', _('All selected plugins downloaded')) dialog.set_modal(False) @@ -288,7 +281,6 @@ class PluginInstaller(GajimPlugin): plugin.activatable = False row = [plugin, plugin.name, is_active, plugin.activatable, icon] self.installed_plugins_model.append(row) - dialog.popup() def available_plugins_treeview_selection_changed(self, treeview_selection): @@ -396,7 +388,7 @@ class PluginInstaller(GajimPlugin): return plugins_found def select_root_iter(self): - if hasattr(self, 'page_num'): + if hasattr(self, 'available_page'): selection = self.available_treeview.get_selection() if selection.count_selected_rows() == 0: root_iter = self.available_plugins_model.get_iter_first() @@ -407,7 +399,7 @@ class PluginInstaller(GajimPlugin): vadjustment.set_value(0) -class download_async(threading.Thread): +class DownloadAsync(threading.Thread): def __init__(self, plugin, remote_dirs=None, upgrading=False, check_update=False): threading.Thread.__init__(self) @@ -430,16 +422,15 @@ class download_async(threading.Thread): 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): - if self.check_update: - self.run_check_update() - else: - self.run_download_plugin_list() + try: + if self.check_update: + self.run_check_update() + else: + self.run_download_plugin_list() + except Exception as e: + GLib.idle_add(self.plugin.on_error, str(e)) + log.exception('Error fetching plugin list') def parse_manifest(self, buf): ''' @@ -458,129 +449,92 @@ class download_async(threading.Thread): plugins.append(config) return plugins - def retrieve_path(self, directory, fname): - server = self.plugin.config['http_server'] - if not server: - server = self.plugin.config_default_values['http_server'][0] - if not urlparse(server).scheme: - server = 'https://' + server - if urlparse(server).scheme != 'https': - log.warn('Warning: not using HTTPS is a ' - 'very serious security issue!') - location = posixpath.join(directory, fname) - uri = urljoin(server, location) - log.debug('Fetching {}'.format(uri)) - request = urlopen(uri) - - manifest_buffer = io.BytesIO(request.read()) - - return manifest_buffer - - def retrieve_manifest(self): - return self.retrieve_path(self.plugin.server_folder, 'manifests.zip') + def download_url(self, url): + log.debug('Fetching {}'.format(url)) + request = urlopen(url) + return io.BytesIO(request.read()) def run_check_update(self): - try: - to_update = [] - zipbuf = self.retrieve_manifest() - plugin_manifests = self.parse_manifest(zipbuf) - for config in plugin_manifests: + to_update = [] + zipbuf = self.download_url(MANIFEST_URL) + plugin_manifests = self.parse_manifest(zipbuf) + for config in plugin_manifests: + 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 = 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')) + GLib.idle_add(self.plugin.warn_update, to_update) + + def run_download_plugin_list(self): + GLib.idle_add(self.progressbar.show) + self.pulse = GLib.timeout_add(150, self.progressbar_pulse) + if not self.remote_dirs: + buf = self.download_url(MANIFEST_IMAGE_URL) + zip_file = zipfile.ZipFile(buf) + manifest_list = zip_file.namelist() + for filename in manifest_list: + if not filename.endswith('manifest.ini'): + continue + dir_ = filename.split('/')[0] + 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 = self.get_plugin_version(config.get( - 'info', 'name')) - if local_version: + + local_version = 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: - to_update.append(config.get('info', 'name')) - GLib.idle_add(self.warn_update, to_update) - except Exception as e: - log.error('Ftp error when check updates: %s' % str(e), exc_info=True) - - def run_download_plugin_list(self): - try: - GLib.idle_add(self.progressbar.set_text, - _('Connecting to server')) - if not self.remote_dirs: - GLib.idle_add(self.progressbar.set_text, - _('Scan files on the server')) - try: - buf = self.retrieve_path(self.plugin.server_folder, 'manifests_images.zip') - except: - log.exception("Error fetching plugin list") - return - zip_file = zipfile.ZipFile(buf) - 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_) - - 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 = 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.install_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_) - 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'), ]) - GLib.idle_add(self.progressbar.set_fraction, 0) - if self.remote_dirs: - self.download_plugin() - GLib.idle_add(self.progressbar.hide) - GLib.idle_add(self.plugin.select_root_iter) - except Exception as e: - self.window.emit('error_signal', str(e)) + upgrade = True + GLib.idle_add( + self.plugin.install_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_) + 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'), ]) + else: + self.download_plugin() + GLib.source_remove(self.pulse) + GLib.idle_add(self.progressbar.hide) + GLib.idle_add(self.plugin.select_root_iter) 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' base_dir, user_dir = gajim.PLUGINS_DIRS @@ -592,18 +546,15 @@ class download_async(threading.Thread): local_dir = os.path.split(user_dir)[0] # downloading zip file - GLib.idle_add(self.progressbar.set_text, - _('Downloading "%s"') % filename) try: - buf = self.plugin.retrieve_path(self.plugin.server_folder, - filename) + plugin = posixpath.join(PLUGINS_URL, filename) + buf = self.download_url(plugin) except: log.exception("Error downloading plugin %s" % filename) continue with zipfile.ZipFile(buf) as zip_file: zip_file.extractall(os.path.join(local_dir, 'plugins')) - GLib.idle_add(self.window.emit, 'plugin_downloaded', self.remote_dirs) - GLib.source_remove(self.pulse) + GLib.idle_add(self.plugin.on_plugin_downloaded, self.remote_dirs) class PluginInstallerPluginConfigDialog(GajimPluginConfigDialog): From 5768f07c7a88f7f2810dd30e33340a4bd2370cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Thu, 23 Feb 2017 22:57:41 +0100 Subject: [PATCH 07/19] [plugin_installer] Remove excess use of idle_add --- plugin_installer/plugin_installer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index 26758c7..d6b255f 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -248,8 +248,7 @@ class PluginInstaller(GajimPlugin): if plugin: if plugin.active: is_active = True - GLib.idle_add(gajim.plugin_manager.deactivate_plugin, - plugin) + gajim.plugin_manager.deactivate_plugin(plugin) gajim.plugin_manager.plugins.remove(plugin) model = self.installed_plugins_model @@ -269,7 +268,7 @@ class PluginInstaller(GajimPlugin): plugin.version self.available_plugins_model[row][Column.UPGRADE] = False if is_active: - GLib.idle_add(gajim.plugin_manager.activate_plugin, plugin) + gajim.plugin_manager.activate_plugin(plugin) # get plugin icon icon_file = os.path.join(plugin.__path__, os.path.split( plugin.__path__)[1]) + '.png' From a841c598753409af89bf3eafffb65d31903c68e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Thu, 23 Feb 2017 22:58:23 +0100 Subject: [PATCH 08/19] [plugin_installer] Remove code for older versions --- plugin_installer/plugin_installer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index d6b255f..8bf0860 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -275,9 +275,6 @@ class PluginInstaller(GajimPlugin): 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.popup() From 95c04ebbcefdc4aaddf7405633df1fa4b6c0c73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Thu, 23 Feb 2017 23:00:03 +0100 Subject: [PATCH 09/19] [plugin_installer] Use scan from PluginManager --- plugin_installer/plugin_installer.py | 71 +--------------------------- 1 file changed, 2 insertions(+), 69 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index 8bf0860..6e9cfb1 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -28,8 +28,6 @@ import io import threading import configparser import os -import fnmatch -import sys import zipfile import logging import posixpath @@ -257,7 +255,8 @@ class PluginInstaller(GajimPlugin): model.remove(model.get_iter((row, 0))) break - plugins = self.scan_dir_for_plugin(plugin_dir) + plugins = gajim.plugin_manager.scan_dir_for_plugins( + plugin_dir, package=True) if not plugins: continue gajim.plugin_manager.add_plugin(plugins[0]) @@ -317,72 +316,6 @@ class PluginInstaller(GajimPlugin): self.homepage_linkbutton.set_label('') self.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 - conf.readfp(open(manifest_path, 'r')) - for option in fields: - if conf.get('info', option) is '': - raise configparser.NoOptionError - 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 - def select_root_iter(self): if hasattr(self, 'available_page'): selection = self.available_treeview.get_selection() From 14083c5b3020a325790b60b41cb65ce11a4ce9a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Fri, 24 Feb 2017 00:36:45 +0100 Subject: [PATCH 10/19] [plugin_installer] Add HTTPS Pinning --- plugin_installer/DST_Root_CA_X3.pem | 20 +++++++ plugin_installer/plugin_installer.py | 83 ++++++++++++++++++++++------ 2 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 plugin_installer/DST_Root_CA_X3.pem 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/plugin_installer.py b/plugin_installer/plugin_installer.py index 6e9cfb1..4b37e3a 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -28,9 +28,11 @@ import io import threading import configparser import os +import ssl import zipfile import logging import posixpath +import urllib.error from urllib.request import urlopen from common import gajim @@ -115,8 +117,7 @@ class PluginInstaller(GajimPlugin): def check_update(self): if hasattr(self, 'thread'): return - self.thread = DownloadAsync(self, check_update=True) - self.thread.start() + self.start_download(check_update=True) self.timeout_id = 0 @log_calls('PluginInstallerPlugin') @@ -211,8 +212,7 @@ class PluginInstaller(GajimPlugin): return if not hasattr(self, 'thread'): self.available_plugins_model.clear() - self.thread = DownloadAsync(self, upgrading=True) - self.thread.start() + self.start_download(upgrading=True) def on_install_upgrade_clicked(self, widget): self.install_button.set_property('sensitive', False) @@ -221,16 +221,38 @@ class PluginInstaller(GajimPlugin): if self.available_plugins_model[i][Column.UPGRADE]: dir_list.append(self.available_plugins_model[i][Column.DIR]) - self.thread = DownloadAsync(self, remote_dirs=dir_list) - self.thread.start() + self.start_download(remote_dirs=dir_list) - def on_error(self, error_text): - 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(error_text) - WarningDialog(_('Error'), text, self.window) + 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 start_download(self, secure=True, remote_dirs=None, + upgrading=False, check_update=False): + 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): dialog = HigDialog(None, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, @@ -329,7 +351,7 @@ class PluginInstaller(GajimPlugin): class DownloadAsync(threading.Thread): - def __init__(self, plugin, remote_dirs=None, + def __init__(self, plugin, secure=True, remote_dirs=None, upgrading=False, check_update=False): threading.Thread.__init__(self) self.plugin = plugin @@ -338,6 +360,7 @@ class DownloadAsync(threading.Thread): self.model = plugin.available_plugins_model self.remote_dirs = remote_dirs self.upgrading = upgrading + self.secure = secure self.check_update = check_update icon = Gtk.Image() self.def_icon = icon.render_icon( @@ -357,9 +380,19 @@ class DownloadAsync(threading.Thread): self.run_check_update() else: self.run_download_plugin_list() - except Exception as e: - GLib.idle_add(self.plugin.on_error, str(e)) + 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 'plugins' in gajim.interface.instances: + GLib.source_remove(self.pulse) + GLib.idle_add(self.progressbar.hide) def parse_manifest(self, buf): ''' @@ -380,7 +413,23 @@ class DownloadAsync(threading.Thread): def download_url(self, url): log.debug('Fetching {}'.format(url)) - request = urlopen(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.info('Installer SSL: +%s' % flag) + ssl_args['context'].options |= getattr(ssl, flag) + request = urlopen(url, **ssl_args) + return io.BytesIO(request.read()) def run_check_update(self): From 61a244d292950ac12f2281c0050cfdf86c9b1da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Fri, 24 Feb 2017 20:02:16 +0100 Subject: [PATCH 11/19] [plugin_installer] Add more useful logging output --- plugin_installer/plugin_installer.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index 4b37e3a..d501b70 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -37,7 +37,6 @@ import urllib.error 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 @@ -77,8 +76,6 @@ def convert_version_to_list(version_str): return l class PluginInstaller(GajimPlugin): - - @log_calls('PluginInstallerPlugin') def init(self): self.description = _('Install and Upgrade Plugins') self.config_dialog = PluginInstallerPluginConfigDialog(self) @@ -92,14 +89,12 @@ class PluginInstaller(GajimPlugin): self.def_icon = icon.render_icon(Gtk.STOCK_PREFERENCES, Gtk.IconSize.MENU) - @log_calls('PluginInstallerPlugin') def activate(self): 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): get_action('plugins').activate() @@ -111,16 +106,17 @@ class PluginInstaller(GajimPlugin): ' your installer plugins. Do you want to update those plugins:' '\n%s') % plugins_str, on_response_yes=open_update) else: + log.info('No updates found') if hasattr(self, 'thread'): del self.thread def check_update(self): 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): if hasattr(self, 'available_page'): self.notebook.remove_page(self.notebook.page_num(self.paned)) @@ -247,8 +243,12 @@ class PluginInstaller(GajimPlugin): _('An error occurred when downloading\n\n' '[%s]' % (str(text))), self.window) - def start_download(self, secure=True, remote_dirs=None, + 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) @@ -268,6 +268,7 @@ class PluginInstaller(GajimPlugin): if plugin: if plugin.active: is_active = True + log.info('Deactivate Plugin: %s', plugin) gajim.plugin_manager.deactivate_plugin(plugin) gajim.plugin_manager.plugins.remove(plugin) @@ -277,18 +278,22 @@ class PluginInstaller(GajimPlugin): model.remove(model.get_iter((row, 0))) break + 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][Column.NAME]: self.available_plugins_model[row][Column.LOCAL_VERSION] = \ plugin.version self.available_plugins_model[row][Column.UPGRADE] = False if is_active: + 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( @@ -351,8 +356,7 @@ class PluginInstaller(GajimPlugin): class DownloadAsync(threading.Thread): - def __init__(self, plugin, secure=True, remote_dirs=None, - upgrading=False, check_update=False): + def __init__(self, plugin, secure, remote_dirs, upgrading, check_update): threading.Thread.__init__(self) self.plugin = plugin self.window = plugin.window @@ -412,7 +416,7 @@ class DownloadAsync(threading.Thread): return plugins def download_url(self, url): - log.debug('Fetching {}'.format(url)) + log.info('Fetching %s', url) ssl_args = {} if self.secure: ssl_args['context'] = ssl.create_default_context( @@ -426,7 +430,7 @@ class DownloadAsync(threading.Thread): 'OP_NO_TLSv1', 'OP_NO_TLSv1_1', 'OP_NO_COMPRESSION', ): - log.info('Installer SSL: +%s' % flag) + log.debug('SSL Options: +%s' % flag) ssl_args['context'].options |= getattr(ssl, flag) request = urlopen(url, **ssl_args) @@ -456,6 +460,7 @@ class DownloadAsync(threading.Thread): GLib.idle_add(self.progressbar.show) self.pulse = GLib.timeout_add(150, self.progressbar_pulse) if not self.remote_dirs: + log.info('Downloading Pluginlist...') buf = self.download_url(MANIFEST_IMAGE_URL) zip_file = zipfile.ZipFile(buf) manifest_list = zip_file.namelist() @@ -515,6 +520,7 @@ class DownloadAsync(threading.Thread): def download_plugin(self): 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) From e2b7d50bc35d77816482a371eeaed61f81d82213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Fri, 24 Feb 2017 20:57:56 +0100 Subject: [PATCH 12/19] [plugin_installer] Fix pylint errors --- plugin_installer/plugin_installer.py | 68 ++++++++++++++-------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index d501b70..e3a051b 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -48,6 +48,7 @@ log = logging.getLogger('gajim.plugin_system.plugin_installer') 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): @@ -75,6 +76,7 @@ def convert_version_to_list(version_str): l.append(int(version_list.pop(0))) return l + class PluginInstaller(GajimPlugin): def init(self): self.description = _('Install and Upgrade Plugins') @@ -86,8 +88,8 @@ class PluginInstaller(GajimPlugin): self.timeout_id = 0 self.connected_ids = {} 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 activate(self): if self.config['check_update']: @@ -102,9 +104,11 @@ class PluginInstaller(GajimPlugin): self.notebook.set_current_page(page) if plugins: 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) + 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: log.info('No updates found') if hasattr(self, 'thread'): @@ -151,8 +155,8 @@ class PluginInstaller(GajimPlugin): widgets_to_extract = ( 'name_label', 'available_treeview', 'progressbar', 'paned', - 'install_button', 'authors_label', - 'homepage_linkbutton', 'version_label', 'scrolled_description_window') + '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)) @@ -169,7 +173,8 @@ class PluginInstaller(GajimPlugin): self.paned, Gtk.Label.new(_('Available'))) self.available_plugins_model = self.xml.get_object('plugin_store') - self.available_plugins_model.set_sort_column_id(2, Gtk.SortType.ASCENDING) + self.available_plugins_model.set_sort_column_id( + 2, Gtk.SortType.ASCENDING) self.progressbar.set_property('no-show-all', True) @@ -231,8 +236,8 @@ class PluginInstaller(GajimPlugin): 'Do you want to do so? ' '(not recommended)' ), - on_response_yes=lambda dlg: self.start_download( - secure=False, upgrading=True)) + 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)): @@ -255,11 +260,6 @@ class PluginInstaller(GajimPlugin): self.thread.start() def on_plugin_downloaded(self, 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) - for _dir in plugin_dirs: is_active = False plugins = None @@ -288,10 +288,10 @@ class PluginInstaller(GajimPlugin): 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][Column.NAME]: - self.available_plugins_model[row][Column.LOCAL_VERSION] = \ - plugin.version - self.available_plugins_model[row][Column.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: log.info('Activate Plugin: %s', plugin) gajim.plugin_manager.activate_plugin(plugin) @@ -303,11 +303,17 @@ class PluginInstaller(GajimPlugin): icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16) 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('scrolled_description_window').get_children()[0].destroy() + self.xml.get_object('scrolled_description_window'). \ + get_children()[0].destroy() self.description_textview = HtmlTextView() self.description_textview.set_wrap_mode(Gtk.WrapMode.WORD) sw = self.xml.get_object('scrolled_description_window') @@ -317,17 +323,17 @@ class PluginInstaller(GajimPlugin): 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)) + self.homepage_linkbutton.set_uri( + model.get_value(iter, Column.HOMEPAGE)) + self.homepage_linkbutton.set_label( + model.get_value(iter, Column.HOMEPAGE)) label = self.homepage_linkbutton.get_children()[0] label.set_ellipsize(Pango.EllipsizeMode.END) self.homepage_linkbutton.set_property('sensitive', True) desc = _(model.get_value(iter, Column.DESCRIPTION)) if not desc.startswith('' + \ - desc + ' ' + desc = ('' + '%s') % desc desc = desc.replace('\n', '
') self.description_textview.display_html( desc, self.description_textview, None) @@ -442,9 +448,7 @@ class DownloadAsync(threading.Thread): plugin_manifests = self.parse_manifest(zipbuf) for config in plugin_manifests: 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: + if not set(MANDATORY_FIELDS).issubset(opts): continue local_version = get_plugin_version(config.get( 'info', 'name')) @@ -472,12 +476,8 @@ class DownloadAsync(threading.Thread): 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: + if not set(MANDATORY_FIELDS).issubset(opts): continue local_version = get_plugin_version( From 463f3073de9bc7b8084840e0df00f1b5b108c92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Fri, 24 Feb 2017 21:04:29 +0100 Subject: [PATCH 13/19] [plugin_installer] Use distutils for version comparison --- plugin_installer/plugin_installer.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index e3a051b..f483d7e 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -34,6 +34,7 @@ import logging import posixpath import urllib.error +from distutils.version import LooseVersion as V from urllib.request import urlopen from common import gajim from plugins import GajimPlugin @@ -69,14 +70,6 @@ def get_plugin_version(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): def init(self): self.description = _('Install and Upgrade Plugins') @@ -453,10 +446,7 @@ class DownloadAsync(threading.Thread): local_version = 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: + if V(config.get('info', 'version')) > V(local_version): to_update.append(config.get('info', 'name')) GLib.idle_add(self.plugin.warn_update, to_update) @@ -484,10 +474,7 @@ class DownloadAsync(threading.Thread): 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: + if V(config.get('info', 'version')) > V(local_version): upgrade = True GLib.idle_add( self.plugin.install_button.set_property, From c08aea63ef7f45d1c77d288a2105768110adf98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 25 Feb 2017 00:02:19 +0100 Subject: [PATCH 14/19] [plugin_installer] Improve progressbar --- plugin_installer/plugin_installer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index f483d7e..e30e2e3 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -365,6 +365,7 @@ class DownloadAsync(threading.Thread): 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) @@ -382,6 +383,8 @@ class DownloadAsync(threading.Thread): 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): @@ -393,9 +396,10 @@ class DownloadAsync(threading.Thread): GLib.idle_add(self.plugin.on_error, str(exc)) log.exception('Error fetching plugin list') finally: - if 'plugins' in gajim.interface.instances: + if self.pulse: GLib.source_remove(self.pulse) GLib.idle_add(self.progressbar.hide) + self.pulse = None def parse_manifest(self, buf): ''' @@ -451,8 +455,6 @@ class DownloadAsync(threading.Thread): GLib.idle_add(self.plugin.warn_update, to_update) def run_download_plugin_list(self): - GLib.idle_add(self.progressbar.show) - self.pulse = GLib.timeout_add(150, self.progressbar_pulse) if not self.remote_dirs: log.info('Downloading Pluginlist...') buf = self.download_url(MANIFEST_IMAGE_URL) @@ -500,8 +502,6 @@ class DownloadAsync(threading.Thread): config.get('info', 'homepage'), ]) else: self.download_plugin() - GLib.source_remove(self.pulse) - GLib.idle_add(self.progressbar.hide) GLib.idle_add(self.plugin.select_root_iter) def download_plugin(self): From 71491aa9a1adddeadd6a3b94a64393e3991cf254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 25 Feb 2017 00:06:39 +0100 Subject: [PATCH 15/19] [plugin_installer] Improve Scrolling --- plugin_installer/plugin_installer.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index e30e2e3..7a0c374 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -343,15 +343,12 @@ class PluginInstaller(GajimPlugin): self.homepage_linkbutton.set_property('sensitive', False) def select_root_iter(self): - if hasattr(self, 'available_page'): - 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('scrolled_description_window') - 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.available_treeview.scroll_to_cell(path) class DownloadAsync(threading.Thread): @@ -500,9 +497,9 @@ class DownloadAsync(threading.Thread): config.get('info', 'description'), config.get('info', 'authors'), config.get('info', 'homepage'), ]) + GLib.idle_add(self.plugin.select_root_iter) else: self.download_plugin() - GLib.idle_add(self.plugin.select_root_iter) def download_plugin(self): for remote_dir in self.remote_dirs: From 28b12464aae64d9dda4a1523bcd159021ca520dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 25 Feb 2017 01:17:40 +0100 Subject: [PATCH 16/19] [plugin_installer] Simplify setting description --- plugin_installer/config_dialog.ui | 8 ++-- plugin_installer/plugin_installer.py | 59 +++++++++------------------- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/plugin_installer/config_dialog.ui b/plugin_installer/config_dialog.ui index 5ce735d..790ce49 100644 --- a/plugin_installer/config_dialog.ui +++ b/plugin_installer/config_dialog.ui @@ -70,6 +70,7 @@ False + True end @@ -174,10 +175,9 @@ Upgrade 5 - True False + True start - &lt;empty&gt; True @@ -245,7 +245,6 @@ Upgrade False start False - <empty> True @@ -259,7 +258,6 @@ Upgrade False start start - <empty> word-char True end @@ -271,10 +269,10 @@ Upgrade - True True False True + True start True none diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index 7a0c374..4e8b4db 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -169,15 +169,11 @@ class PluginInstaller(GajimPlugin): self.available_plugins_model.set_sort_column_id( 2, Gtk.SortType.ASCENDING) - self.progressbar.set_property('no-show-all', True) - 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.description_textview = HtmlTextView() self.description_textview.set_wrap_mode(Gtk.WrapMode.WORD) self.scrolled_description_window.add(self.description_textview) @@ -305,42 +301,23 @@ class PluginInstaller(GajimPlugin): def available_plugins_treeview_selection_changed(self, treeview_selection): model, iter = treeview_selection.get_selected() - self.xml.get_object('scrolled_description_window'). \ - get_children()[0].destroy() - self.description_textview = HtmlTextView() - self.description_textview.set_wrap_mode(Gtk.WrapMode.WORD) - sw = self.xml.get_object('scrolled_description_window') - sw.add(self.description_textview) - sw.show_all() - if iter: - 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)) - label = self.homepage_linkbutton.get_children()[0] - label.set_ellipsize(Pango.EllipsizeMode.END) - self.homepage_linkbutton.set_property('sensitive', True) - 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) - self.description_textview.set_property('sensitive', True) - else: - self._clear_available_plugin_info() - - def _clear_available_plugin_info(self): - self.name_label.set_text('') - self.version_label.set_text('') - self.authors_label.set_text('') - self.homepage_linkbutton.set_uri('') - self.homepage_linkbutton.set_label('') - self.homepage_linkbutton.set_property('sensitive', False) + 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): selection = self.available_treeview.get_selection() @@ -348,6 +325,8 @@ class PluginInstaller(GajimPlugin): 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) From ec332db512ca84f8dbaf20192a7ee1bfab4837a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 25 Feb 2017 03:00:59 +0100 Subject: [PATCH 17/19] [plugin_installer] Refactor manifest parsing --- plugin_installer/plugin_installer.py | 110 +++++++++++++-------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index 4e8b4db..b266e93 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -29,11 +29,11 @@ import threading import configparser import os import ssl -import zipfile 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 @@ -64,7 +64,7 @@ class Column(IntEnum): HOMEPAGE = 8 -def get_plugin_version(plugin_name): +def get_local_version(plugin_name): for plugin in gajim.plugin_manager.plugins: if plugin.name == plugin_name: return plugin.version @@ -347,7 +347,12 @@ class DownloadAsync(threading.Thread): 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): @@ -381,17 +386,48 @@ class DownloadAsync(threading.Thread): ''' given the buffer of the zipfile, returns the list of plugin manifests ''' - zip_file = zipfile.ZipFile(buf) + 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 - plugins.append(config) + 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() + + # 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 + + plugins.append(config_dict) return plugins def download_url(self, url): @@ -418,64 +454,28 @@ class DownloadAsync(threading.Thread): def run_check_update(self): to_update = [] zipbuf = self.download_url(MANIFEST_URL) - plugin_manifests = self.parse_manifest(zipbuf) - for config in plugin_manifests: - opts = config.options('info') - if not set(MANDATORY_FIELDS).issubset(opts): - continue - local_version = get_plugin_version(config.get( - 'info', 'name')) + plugin_list = self.parse_manifest(zipbuf) + for plugin in plugin_list: + local_version = get_local_version(plugin['name']) if local_version: - if V(config.get('info', 'version')) > V(local_version): - to_update.append(config.get('info', 'name')) + 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...') - buf = self.download_url(MANIFEST_IMAGE_URL) - zip_file = zipfile.ZipFile(buf) - manifest_list = zip_file.namelist() - for filename in manifest_list: - if not filename.endswith('manifest.ini'): - continue - dir_ = filename.split('/')[0] - config = configparser.ConfigParser() - conf_file = zip_file.open(filename) - config.read_file(io.TextIOWrapper(conf_file, encoding='utf-8')) - conf_file.close() - opts = config.options('info') - if not set(MANDATORY_FIELDS).issubset(opts): - continue - - local_version = get_plugin_version( - config.get('info', 'name')) - upgrade = False - if self.upgrading and local_version: - if V(config.get('info', 'version')) > V(local_version): - upgrade = True + 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) - 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_) - 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'), ]) + GLib.idle_add(self.model_append, plugin) GLib.idle_add(self.plugin.select_root_iter) else: self.download_plugin() @@ -499,7 +499,7 @@ class DownloadAsync(threading.Thread): except: log.exception("Error downloading plugin %s" % filename) continue - with zipfile.ZipFile(buf) as zip_file: + 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) From d0db3ee2940e2a206b31a6a321de084f28f8e406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 25 Feb 2017 03:05:15 +0100 Subject: [PATCH 18/19] [plugin_installer] Move code into UI file --- plugin_installer/config_dialog.ui | 4 +++- plugin_installer/plugin_installer.py | 5 ----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/plugin_installer/config_dialog.ui b/plugin_installer/config_dialog.ui index 790ce49..33077dc 100644 --- a/plugin_installer/config_dialog.ui +++ b/plugin_installer/config_dialog.ui @@ -94,7 +94,9 @@ False 1 - + + + diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index b266e93..c437be7 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -169,11 +169,6 @@ class PluginInstaller(GajimPlugin): self.available_plugins_model.set_sort_column_id( 2, Gtk.SortType.ASCENDING) - selection = self.available_treeview.get_selection() - selection.connect( - 'changed', self.available_plugins_treeview_selection_changed) - selection.set_mode(Gtk.SelectionMode.SINGLE) - self.description_textview = HtmlTextView() self.description_textview.set_wrap_mode(Gtk.WrapMode.WORD) self.scrolled_description_window.add(self.description_textview) From c0b9f26e88f6a02197668229bf46814d7be21d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 25 Feb 2017 15:14:08 +0100 Subject: [PATCH 19/19] [plugin_installer] Update manifest.ini --- plugin_installer/manifest.ini | 5 +++-- plugin_installer/plugin_installer.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) 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 c437be7..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. ##