9578053 Jan 22 2022 distfiles.gentoo.org/distfiles/gajim-1.3.3-2.tar.gz

This commit is contained in:
emdee 2022-10-19 18:09:31 +00:00
parent a5b3822651
commit 4c1b226bff
1045 changed files with 753037 additions and 18 deletions

View file

@ -0,0 +1 @@
from .plugin_installer import PluginInstaller

View file

@ -0,0 +1,51 @@
# 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 <http://www.gnu.org/licenses/>.
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

View file

@ -0,0 +1,348 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.14"/>
<object class="GtkWindow" id="ConfigWindow">
<property name="can_focus">False</property>
<child>
<object class="GtkGrid" id="config_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">12</property>
<property name="margin_right">12</property>
<property name="margin_top">12</property>
<property name="margin_bottom">12</property>
<child>
<object class="GtkCheckButton" id="check_update">
<property name="label" translatable="yes">Check update after start</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_check_update_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="plugin_store">
<columns>
<!-- column-name icon -->
<column type="GdkPixbuf"/>
<!-- column-name dir -->
<column type="gchararray"/>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name localversion -->
<column type="gchararray"/>
<!-- column-name version -->
<column type="gchararray"/>
<!-- column-name upgrade -->
<column type="gboolean"/>
<!-- column-name description -->
<column type="gchararray"/>
<!-- column-name authors -->
<column type="gchararray"/>
<!-- column-name homepage -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkWindow" id="AvailablePlugins">
<property name="can_focus">False</property>
<child>
<object class="GtkPaned" id="paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkGrid" id="plugin_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkProgressBar" id="progressbar">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="border_width">6</property>
<property name="hscrollbar_policy">never</property>
<child>
<object class="GtkTreeView" id="available_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vexpand">True</property>
<property name="model">plugin_store</property>
<property name="headers_clickable">False</property>
<property name="search_column">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection">
<signal name="changed" handler="available_plugins_treeview_selection_changed" swapped="no"/>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Plugin</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererPixbuf" id="pixbufrenderer"/>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="plugintextrenderer"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Installed</property>
<child>
<object class="GtkCellRendererText" id="versiontextrenderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Available</property>
<child>
<object class="GtkCellRendererText" id="availabletextrenderer"/>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Install /
Upgrade</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererToggle" id="togglerenderer">
<signal name="toggled" handler="available_plugins_toggled_cb" swapped="no"/>
</object>
<attributes>
<attribute name="active">5</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkButtonBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<property name="layout_style">start</property>
<child>
<object class="GtkButton" id="install_button">
<property name="label" translatable="yes">Install/Upgrade</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">start</property>
<property name="vexpand">False</property>
<property name="image">refresh</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_install_upgrade_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="description_grid">
<property name="width_request">350</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">6</property>
<property name="row_spacing">3</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="name_label">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="halign">start</property>
<property name="selectable">True</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="versionlabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Version:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="authorslabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Authors:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="homepage_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Homepage:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="description_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Description:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="version_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">False</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="authors_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="wrap_mode">word-char</property>
<property name="selectable">True</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="homepage_linkbutton">
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="no_show_all">True</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="relief">none</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_description_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
<property name="width">2</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkImage" id="refresh">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-refresh</property>
</object>
</interface>

View file

@ -0,0 +1,341 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkListStore" id="plugin_store">
<columns>
<!-- column-name icon -->
<column type="GdkPixbuf"/>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name localversion -->
<column type="gchararray"/>
<!-- column-name version -->
<column type="gchararray"/>
<!-- column-name upgrade -->
<column type="gboolean"/>
<!-- column-name plugin -->
<column type="PyObject"/>
</columns>
</object>
<object class="GtkBox" id="available_plugins_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">18</property>
<property name="spacing">18</property>
<child>
<object class="GtkOverlay">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="plugins_box">
<property name="width_request">180</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">out</property>
<child>
<object class="GtkTreeView" id="available_plugins_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">plugin_store</property>
<property name="search_column">2</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection">
<signal name="changed" handler="_on_plugin_selection_changed" swapped="no"/>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="fixed_width">180</property>
<property name="min_width">150</property>
<property name="max_width">300</property>
<property name="title" translatable="yes">Plugin</property>
<property name="clickable">True</property>
<property name="sort_indicator">True</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="pixbufrenderer"/>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="plugintextrenderer">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Installed</property>
<child>
<object class="GtkCellRendererText" id="versiontextrenderer"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Available</property>
<child>
<object class="GtkCellRendererText" id="availabletextrenderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Install</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<property name="sort_indicator">True</property>
<property name="sort_column_id">4</property>
<child>
<object class="GtkCellRendererToggle" id="togglerenderer">
<signal name="toggled" handler="_available_plugin_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">4</attribute>
</attributes>
</child>
</object>
</child>
<style>
<class name="space"/>
</style>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToolbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="toolbar_style">text</property>
<property name="show_arrow">False</property>
<child>
<object class="GtkToolButton" id="install_plugin_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Install / Upda_te</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="_on_install_update_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<style>
<class name="inline-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="index">-1</property>
</packing>
</child>
<child type="overlay">
<object class="GtkSpinner" id="spinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="width_request">400</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<child>
<object class="GtkLabel" id="name_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;Plugin Name&gt;</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
<style>
<class name="large-header"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="description_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;Description&gt;</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Version</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Authors</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Homepage</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="version_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="authors_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="homepage_linkbutton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View file

@ -0,0 +1,13 @@
[info]
name: Plugin Installer
short_name: plugin_installer
version: 1.3.8
description: Install and upgrade plugins for Gajim
authors: Denis Fomin <fominde@gmail.com>
Yann Leboulanger <asterix@lagaule.org>
Thilo Molitor <thilo@eightysoft.de>
Philipp Hörist <philipp@hoerist.com>
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/PluginInstallerPlugin
min_gajim_version: 1.2.91
max_gajim_version: 1.3.90

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

View file

@ -0,0 +1,260 @@
# Copyright (C) 2010-2012 Denis Fomin <fominde AT gmail.com>
# Copyright (C) 2011-2012 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2017-2019 Philipp Hörist <philipp AT hoerist.com>
#
# 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 <http://www.gnu.org/licenses/>.
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'
'<b>%s</b>') % 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

View file

@ -0,0 +1,8 @@
# File which defines all remote URLs
server = 'https://ftp.gajim.org'
directory = 'plugins_1.3_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

View file

@ -0,0 +1,195 @@
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()

View file

@ -0,0 +1,138 @@
# 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 <http://www.gnu.org/licenses/>.
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 = '<a href="%s">%s</a>' % (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)