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 @@
-
-
-
-
-
-
-
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 096e098..0000000
Binary files a/plugin_installer/plugin_installer.png and /dev/null differ
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)