From 3db0e1fea21d8b4f8ec8d697138036fa9309d5a8 Mon Sep 17 00:00:00 2001 From: lovetox Date: Fri, 29 Apr 2022 18:59:28 +0200 Subject: [PATCH] [plugin_installer] Remove plugin This is integrated into Gajim now --- plugin_installer/__init__.py | 1 - plugin_installer/config_dialog.py | 51 ---- plugin_installer/installer.ui | 351 -------------------------- plugin_installer/manifest.ini | 13 - plugin_installer/plugin_installer.png | Bin 541 -> 0 bytes plugin_installer/plugin_installer.py | 260 ------------------- plugin_installer/remote.py | 8 - plugin_installer/utils.py | 195 -------------- plugin_installer/widget.py | 138 ---------- 9 files changed, 1017 deletions(-) delete mode 100644 plugin_installer/__init__.py delete mode 100644 plugin_installer/config_dialog.py delete mode 100644 plugin_installer/installer.ui delete mode 100644 plugin_installer/manifest.ini delete mode 100644 plugin_installer/plugin_installer.png delete mode 100644 plugin_installer/plugin_installer.py delete mode 100644 plugin_installer/remote.py delete mode 100644 plugin_installer/utils.py delete mode 100644 plugin_installer/widget.py diff --git a/plugin_installer/__init__.py b/plugin_installer/__init__.py deleted file mode 100644 index 77aa5e2..0000000 --- a/plugin_installer/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .plugin_installer import PluginInstaller diff --git a/plugin_installer/config_dialog.py b/plugin_installer/config_dialog.py deleted file mode 100644 index 046c6bd..0000000 --- a/plugin_installer/config_dialog.py +++ /dev/null @@ -1,51 +0,0 @@ -# This file is part of Gajim. -# -# Gajim is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see . - -from gi.repository import Gtk - -from gajim.gui.settings import SettingsDialog -from gajim.gui.const import Setting -from gajim.gui.const import SettingKind -from gajim.gui.const import SettingType - -from gajim.plugins.plugins_i18n import _ - - -class PluginInstallerConfigDialog(SettingsDialog): - def __init__(self, plugin, parent): - - self.plugin = plugin - settings = [ - Setting(SettingKind.SWITCH, _('Check for updates'), - SettingType.VALUE, self.plugin.config['check_update'], - desc=_('Check for updates after start'), - callback=self.on_setting, data='check_update'), - - Setting(SettingKind.SWITCH, _('Update automatically'), - SettingType.VALUE, self.plugin.config['auto_update'], - desc=_('Update plugins automatically'), - callback=self.on_setting, data='auto_update'), - - Setting(SettingKind.SWITCH, _('Notify after update'), - SettingType.VALUE, self.plugin.config['auto_update_feedback'], - desc=_('Show message when automatic update was successful'), - callback=self.on_setting, data='auto_update_feedback'), - ] - - SettingsDialog.__init__(self, parent, _('Plugin Installer Configuration'), - Gtk.DialogFlags.MODAL, settings, None) - - def on_setting(self, value, data): - self.plugin.config[data] = value diff --git a/plugin_installer/installer.ui b/plugin_installer/installer.ui deleted file mode 100644 index f9ab99d..0000000 --- a/plugin_installer/installer.ui +++ /dev/null @@ -1,351 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - True - False - 18 - 18 - - - True - False - - - 180 - True - False - True - vertical - - - True - True - never - out - - - True - True - plugin_store - 2 - - - - - - - - True - fixed - 180 - 150 - 300 - Plugin - True - True - 1 - - - - 0 - - - - - end - - - 1 - - - - - - - Installed - - - - 2 - - - - - - - Available - - - - 3 - - - - - - - Install - True - 0.5 - True - 4 - - - - - - 4 - - - - - - - - - - True - True - 0 - - - - - True - False - text - False - - - True - False - False - Install / Upda_te - True - - - - False - False - - - - - - False - True - 1 - - - - - -1 - - - - - True - False - True - - - - - False - True - 0 - - - - - 400 - True - False - True - vertical - 18 - - - True - False - True - start - <Plugin Name> - True - 0 - - - - False - True - 0 - - - - - True - False - start - <Description> - True - word-char - True - 0 - - - False - True - 1 - - - - - - True - False - 6 - 12 - - - True - False - end - start - Version - - - - 0 - 0 - - - - - True - False - end - start - Authors - - - - 0 - 1 - - - - - True - False - end - start - Homepage - - - - 0 - 2 - - - - - True - False - start - <empty> - True - word-char - True - 0 - - - 1 - 0 - - - - - True - False - start - <empty> - True - word-char - True - 0 - - - 1 - 1 - - - - - True - False - start - <empty> - True - word-char - 0 - - - 1 - 2 - - - - - - - - - - - - - - False - True - 2 - - - - - False - True - 1 - - - - diff --git a/plugin_installer/manifest.ini b/plugin_installer/manifest.ini deleted file mode 100644 index cf1efe2..0000000 --- a/plugin_installer/manifest.ini +++ /dev/null @@ -1,13 +0,0 @@ -[info] -name: Plugin Installer -short_name: plugin_installer -version: 1.4.0 -description: Install and upgrade plugins for Gajim -authors: Denis Fomin - Yann Leboulanger - Thilo Molitor - Philipp Hörist -homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/PluginInstallerPlugin -min_gajim_version: 1.4.0-dev1 -max_gajim_version: 1.4.90 - diff --git a/plugin_installer/plugin_installer.png b/plugin_installer/plugin_installer.png deleted file mode 100644 index 096e098b4bf4d21769a01f6202076de6ca52e64d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 541 zcmV+&0^ONui8Pj3^qfoxg`11})%NSb4A$MbYag?-@S4{Q_nS2=g$A$_S#w zLS^m*u&MTX;gD$j`RhAe0m$Z8Pv3z85v=Rg(_im-McCmk!AJ+dl*sVw&o4wk{%2zN z_3IZX5I(@t)}OzBS^xk02jKwK|An%_2K@c==kU#wkC2luNb@cv{;nIR9=HK%RwTb8 fSx8}a1_&?!W$xIpcs8(|00000NkvXXu0mjfoW}qv diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py deleted file mode 100644 index 0802371..0000000 --- a/plugin_installer/plugin_installer.py +++ /dev/null @@ -1,260 +0,0 @@ -# Copyright (C) 2010-2012 Denis Fomin -# Copyright (C) 2011-2012 Yann Leboulanger -# Copyright (C) 2017-2019 Philipp Hörist -# -# This file is part of Gajim. -# -# Gajim is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see . - -import logging -from functools import partial -from io import BytesIO -from zipfile import ZipFile - -from gi.repository import GLib -from gi.repository import Soup - -from gajim.common import app - -from gajim.plugins import GajimPlugin -from gajim.plugins.plugins_i18n import _ - -from gajim.gui.dialogs import DialogButton -from gajim.gui.dialogs import InformationDialog -from gajim.gui.dialogs import ConfirmationCheckDialog - -from plugin_installer.config_dialog import PluginInstallerConfigDialog -from plugin_installer.widget import AvailablePage -from plugin_installer.utils import parse_manifests_zip -from plugin_installer.remote import MANIFEST_URL -from plugin_installer.remote import MANIFEST_IMAGE_URL - - -log = logging.getLogger('gajim.p.installer') - - -class PluginInstaller(GajimPlugin): - def init(self): - # pylint: disable=attribute-defined-outside-init - self.description = _('Install and upgrade plugins for Gajim') - self.config_dialog = partial(PluginInstallerConfigDialog, self) - self.config_default_values = {'check_update': (True, ''), - 'auto_update': (False, ''), - 'auto_update_feedback': (True, '')} - self.gui_extension_points = { - 'plugin_window': (self._on_connect_plugin_window, - self._on_disconnect_plugin_window)} - - self._check_update_id = None - self._available_page = None - - self._update_in_progress = False - self._download_in_progress = False - self._download_queue = 0 - self._needs_restart = False - - self._session = Soup.Session() - - @property - def download_in_progress(self): - return self._download_in_progress - - def activate(self): - if self.config['check_update']: - # Check for updates X seconds after Gajim was started - self._check_update_id = GLib.timeout_add_seconds( - 10, self._check_for_updates) - - def deactivate(self): - if self._check_update_id is not None: - GLib.source_remove(self._check_update_id) - self._check_update_id = None - - def _set_download_in_progress(self, state): - self._download_in_progress = state - if self._available_page is not None: - self._available_page.set_download_in_progress(state) - - def _check_for_updates(self): - if self._download_in_progress: - log.info('Abort checking for updates because ' - 'other downloads are in progress') - return - log.info('Checking for Updates...') - message = Soup.Message.new('GET', MANIFEST_URL) - self._session.queue_message(message, - self._on_check_for_updates_finished) - - def _on_check_for_updates_finished(self, _session, message): - if message.status_code != Soup.Status.OK: - log.warning('Download failed: %s', MANIFEST_URL) - log.warning(Soup.Status.get_phrase(message.status_code)) - return - - data = message.props.response_body_data.get_data() - if data is None: - return - - plugin_list = parse_manifests_zip(data) - for plugin in list(plugin_list): - if plugin.needs_update(): - log.info('Update available for: %s - %s', - plugin.name, plugin.version) - else: - plugin_list.remove(plugin) - - if not plugin_list: - log.info('No updates available') - return - - if self.config['auto_update']: - self._update_in_progress = True - self._download_plugins(plugin_list) - else: - self._notify_about_update(plugin_list) - - def _notify_about_update(self, plugins): - def _open_update(is_checked): - if is_checked: - self.config['auto_update'] = True - self._download_plugins(plugins) - - plugins_str = '\n' + '\n'.join([plugin.name for plugin in plugins]) - ConfirmationCheckDialog( - _('Plugin Updates'), - _('Plugin Updates Available'), - _('There are updates for your plugins:\n' - '%s') % plugins_str, - _('Update plugins automatically next time'), - [DialogButton.make('Cancel'), - DialogButton.make('Accept', - text=_('_Update'), - is_default=True, - callback=_open_update)]).show() - - def _download_plugin_list(self): - log.info('Download plugin list...') - message = Soup.Message.new('GET', MANIFEST_IMAGE_URL) - self._session.queue_message(message, - self._on_download_plugin_list_finished) - - def _on_download_plugin_list_finished(self, _session, message): - if message.status_code != Soup.Status.OK: - log.warning('Download failed: %s', MANIFEST_IMAGE_URL) - log.warning(Soup.Status.get_phrase(message.status_code)) - return - - data = message.props.response_body_data.get_data() - if data is None: - return - - plugin_list = parse_manifests_zip(data) - if not plugin_list: - log.warning('No plugins found in zip') - - if self._available_page is None: - return - self._available_page.append_plugins(plugin_list) - log.info('Downloading plugin list finished') - - def _on_download_plugins(self, _available_page, _signal_name, plugin_list): - self._download_plugins(plugin_list) - - def _download_plugins(self, plugin_list): - if self._download_in_progress: - log.warning('Download started while other download in progress') - return - - self._set_download_in_progress(True) - self._download_queue = len(plugin_list) - for plugin in plugin_list: - self._download_plugin(plugin) - - def _download_plugin(self, plugin): - log.info('Download plugin %s', plugin.name) - message = Soup.Message.new('GET', plugin.remote_uri) - self._session.queue_message(message, - self._on_download_plugin_finished, - plugin) - - def _on_download_plugin_finished(self, _session, message, plugin): - self._download_queue -= 1 - if message.status_code != Soup.Status.OK: - log.warning('Download failed: %s', plugin.remote_uri) - log.warning(Soup.Status.get_phrase(message.status_code)) - return - - data = message.props.response_body_data.get_data() - if data is None: - return - - log.info('Finished downloading %s', plugin.name) - - if not plugin.download_path.exists(): - plugin.download_path.mkdir(mode=0o700) - - with ZipFile(BytesIO(data)) as zip_file: - zip_file.extractall(str(plugin.download_path)) - - activated = app.plugin_manager.update_plugins( - replace=False, activate=True, plugin_name=plugin.short_name) - if activated: - if self._available_page is not None: - self._available_page.update_plugin(plugin) - - else: - self._needs_restart = True - log.info('Plugin %s needs restart', plugin.name) - - if self._download_queue == 0: - self._set_download_in_progress(False) - self._notify_about_download_finished() - self._update_in_progress = False - self._needs_restart = False - - def _notify_about_download_finished(self): - if not self._update_in_progress: - if self._needs_restart: - InformationDialog( - _('Plugins Downloaded'), - _('Updates will be installed next time Gajim is ' - 'started.')) - else: - InformationDialog(_('Plugins Downloaded')) - - elif self.config['auto_update_feedback']: - def _on_ok(is_checked): - if is_checked: - self.config['auto_update_feedback'] = False - ConfirmationCheckDialog( - _('Plugins Updated'), - _('Plugins Updated'), - _('Plugin updates have successfully been downloaded.\n' - 'Updates will be installed next time Gajim is started.'), - _('Do not show this message again'), - [DialogButton.make('OK', - callback=_on_ok)]).show() - - def _on_connect_plugin_window(self, plugin_window): - self._available_page = AvailablePage( - self.local_file_path('installer.ui'), plugin_window.get_notebook()) - self._available_page.set_download_in_progress( - self._download_in_progress) - self._available_page.connect('download-plugins', - self._on_download_plugins) - self._download_plugin_list() - - def _on_disconnect_plugin_window(self, _plugin_window): - self._session.abort() - self._available_page.destroy() - self._available_page = None diff --git a/plugin_installer/remote.py b/plugin_installer/remote.py deleted file mode 100644 index 9bebeb6..0000000 --- a/plugin_installer/remote.py +++ /dev/null @@ -1,8 +0,0 @@ -# File which defines all remote URLs - -server = 'https://ftp.gajim.org' -directory = 'plugins_master_zip' - -PLUGINS_DIR_URL = '%s/%s' % (server, directory) -MANIFEST_URL = '%s/manifests.zip' % PLUGINS_DIR_URL -MANIFEST_IMAGE_URL = '%s/manifests_images.zip' % PLUGINS_DIR_URL diff --git a/plugin_installer/utils.py b/plugin_installer/utils.py deleted file mode 100644 index a199738..0000000 --- a/plugin_installer/utils.py +++ /dev/null @@ -1,195 +0,0 @@ -import logging -from io import BytesIO -from pathlib import Path -from zipfile import ZipFile -import configparser -from configparser import ConfigParser -from packaging.version import Version as V - -from gi.repository import Gtk -from gi.repository import GdkPixbuf - -import gajim -from gajim.common import app -from gajim.common import configpaths - -from plugin_installer.remote import PLUGINS_DIR_URL - -log = logging.getLogger('gajim.p.installer.utils') - -MANDATORY_FIELDS = {'name', 'short_name', 'version', - 'description', 'authors', 'homepage'} -FALLBACK_ICON = Gtk.IconTheme.get_default().load_icon( - 'preferences-system', Gtk.IconSize.MENU, 0) - - -class PluginInfo: - def __init__(self, config, icon): - self.icon = icon - self.name = config.get('info', 'name') - self.short_name = config.get('info', 'short_name') - self.version = V(config.get('info', 'version')) - self._installed_version = None - self.min_gajim_version = V(config.get('info', 'min_gajim_version')) - self.max_gajim_version = V(config.get('info', 'max_gajim_version')) - self.description = config.get('info', 'description') - self.authors = config.get('info', 'authors') - self.homepage = config.get('info', 'homepage') - - - @classmethod - def from_zip_file(cls, zip_file, manifest_path): - config = ConfigParser() - # ZipFile can only handle posix paths - with zip_file.open(manifest_path.as_posix()) as manifest_file: - try: - config.read_string(manifest_file.read().decode()) - except configparser.Error as error: - log.warning(error) - raise ValueError('Invalid manifest: %s' % manifest_path) - - if not is_manifest_valid(config): - raise ValueError('Invalid manifest: %s' % manifest_path) - - short_name = config.get('info', 'short_name') - png_filename = '%s.png' % short_name - png_path = manifest_path.parent / png_filename - icon = load_icon_from_zip(zip_file, png_path) or FALLBACK_ICON - - return cls(config, icon) - - @classmethod - def from_path(cls, manifest_path): - config = ConfigParser() - with open(manifest_path, encoding='utf-8') as conf_file: - try: - config.read_file(conf_file) - except configparser.Error as error: - log.warning(error) - raise ValueError('Invalid manifest: %s' % manifest_path) - - if not is_manifest_valid(config): - raise ValueError('Invalid manifest: %s' % manifest_path) - - return cls(config, None) - - @property - def remote_uri(self): - return '%s/%s.zip' % (PLUGINS_DIR_URL, self.short_name) - - @property - def download_path(self): - return Path(configpaths.get('PLUGINS_DOWNLOAD')) - - @property - def installed_version(self): - if self._installed_version is None: - self._installed_version = self._get_installed_version() - return self._installed_version - - def has_valid_version(self): - gajim_version = V(gajim.__version__) - return self.min_gajim_version <= gajim_version <= self.max_gajim_version - - def _get_installed_version(self): - for plugin in app.plugin_manager.plugins: - if plugin.name == self.name: - return V(plugin.version) - - # Fallback: - # If the plugin has errors and is not loaded by the - # PluginManager. Look in the Gajim config if the plugin is - # known and active, if yes load the manifest from the Plugin - # dir and parse the version - plugin_settings = app.settings.get_plugins() - if self.short_name not in plugin_settings: - return None - - active = app.settings.get_plugin_setting(self.short_name, 'active') - if not active: - return None - - manifest_path = (Path(configpaths.get('PLUGINS_USER')) / - self.short_name / - 'manifest.ini') - if not manifest_path.exists(): - return None - try: - return PluginInfo.from_path(manifest_path).version - except Exception as error: - log.warning(error) - return None - - def needs_update(self): - if self.installed_version is None: - return False - return self.installed_version < self.version - - @property - def fields(self): - return [self.icon, - self.name, - str(self.installed_version or ''), - str(self.version), - self.needs_update(), - self] - - -def parse_manifests_zip(bytes_): - plugins = [] - with ZipFile(BytesIO(bytes_)) as zip_file: - files = list(map(Path, zip_file.namelist())) - for manifest_path in filter(is_manifest, files): - try: - plugin = PluginInfo.from_zip_file(zip_file, manifest_path) - except Exception as error: - log.warning(error) - continue - - if not plugin.has_valid_version(): - continue - plugins.append(plugin) - - return plugins - - -def is_manifest(path): - if path.name == 'manifest.ini': - return True - return False - - -def is_manifest_valid(config): - if not config.has_section('info'): - log.warning('Manifest is missing INFO section') - return False - - opts = config.options('info') - if not MANDATORY_FIELDS.issubset(opts): - log.warning('Manifest is missing mandatory fields %s.', - MANDATORY_FIELDS.difference(opts)) - return False - return True - - -def load_icon_from_zip(zip_file, icon_path): - # ZipFile can only handle posix paths - try: - zip_file.getinfo(icon_path.as_posix()) - except KeyError: - return None - - with zip_file.open(icon_path.as_posix()) as png_file: - data = png_file.read() - - pixbuf = GdkPixbuf.PixbufLoader() - pixbuf.set_size(16, 16) - try: - pixbuf.write(data) - except Exception: - log.exception('Can\'t load icon: %s', icon_path) - pixbuf.close() - return None - - pixbuf.close() - return pixbuf.get_pixbuf() diff --git a/plugin_installer/widget.py b/plugin_installer/widget.py deleted file mode 100644 index 0018b45..0000000 --- a/plugin_installer/widget.py +++ /dev/null @@ -1,138 +0,0 @@ -# This file is part of Gajim. -# -# Gajim is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see . - -from enum import IntEnum - -from gi.repository import Gtk - -from gajim.common.helpers import Observable - -from gajim.plugins.plugins_i18n import _ -from gajim.plugins.helpers import get_builder - - -class Column(IntEnum): - PIXBUF = 0 - NAME = 1 - INSTALLED_VERSION = 2 - VERSION = 3 - INSTALL = 4 - PLUGIN = 5 - - -class AvailablePage(Observable): - def __init__(self, builder_path, notebook): - Observable.__init__(self) - self._ui = get_builder(builder_path) - - self._notebook = notebook - self._page_num = self._notebook.append_page( - self._ui.available_plugins_box, - Gtk.Label.new(_('Available'))) - - self._ui.plugin_store.set_sort_column_id(1, Gtk.SortType.ASCENDING) - self._ui.connect_signals(self) - - def destroy(self): - self._notebook.remove_page(self._page_num) - self._notebook = None - self._ui.plugin_store.clear() - self._ui.available_plugins_box.destroy() - self._ui = None - self._plugin = None - self.disconnect_signals() - - def show_page(self): - self._notebook.set_current_page(self._page_num) - - def append_plugins(self, plugins): - for plugin in plugins: - self._ui.plugin_store.append(plugin.fields) - - if plugins: - self._select_first_plugin() - - self._update_install_button() - self._ui.spinner.stop() - self._ui.spinner.hide() - - def update_plugin(self, plugin): - for row in self._ui.plugin_store: - if row[Column.NAME] == plugin.name: - row[Column.INSTALLED_VERSION] = str(plugin.version) - row[Column.INSTALL] = False - break - - def set_download_in_progress(self, state): - self._download_in_progress = state - self._update_install_button() - - def _available_plugin_toggled(self, _cell, path): - is_active = self._ui.plugin_store[path][Column.INSTALL] - self._ui.plugin_store[path][Column.INSTALL] = not is_active - self._update_install_button() - - def _update_install_button(self): - if self._download_in_progress: - self._ui.install_plugin_button.set_sensitive(False) - return - - sensitive = False - for row in self._ui.plugin_store: - if row[Column.INSTALL]: - sensitive = True - break - self._ui.install_plugin_button.set_sensitive(sensitive) - - def _on_install_update_clicked(self, _button): - self._ui.install_plugin_button.set_sensitive(False) - - plugins = [] - for row in self._ui.plugin_store: - if row[Column.INSTALL]: - plugins.append(row[Column.PLUGIN]) - - self.notify('download-plugins', plugins) - - def _on_plugin_selection_changed(self, selection): - model, iter_ = selection.get_selected() - if not iter_: - self._clear_plugin_info() - else: - self._set_plugin_info(model, iter_) - - def _clear_plugin_info(self): - self._ui.name_label.set_text('') - self._ui.description_label.set_text('') - self._ui.version_label.set_text('') - self._ui.authors_label.set_text('') - self._ui.homepage_linkbutton.set_text('') - self._ui.install_plugin_button.set_sensitive(False) - - def _set_plugin_info(self, model, iter_): - plugin = model[iter_][Column.PLUGIN] - self._ui.name_label.set_text(plugin.name) - self._ui.version_label.set_text(str(plugin.version)) - self._ui.authors_label.set_text(plugin.authors) - homepage = '%s' % (plugin.homepage, plugin.homepage) - self._ui.homepage_linkbutton.set_markup(homepage) - self._ui.description_label.set_text(plugin.description) - - def _select_first_plugin(self): - selection = self._ui.available_plugins_treeview.get_selection() - iter_ = self._ui.plugin_store.get_iter_first() - if iter_ is not None: - selection.select_iter(iter_) - path = self._ui.plugin_store.get_path(iter_) - self._ui.available_plugins_treeview.scroll_to_cell(path)