Welcome to mirror list, hosted at ThFree Co, Russian Federation.

utils.py « plugin_installer - dev.gajim.org/gajim/gajim-plugins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 83a565f82948a6f4670566cea8f693d07451fdc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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

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(app.config.get('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 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
        active = app.config.get_per('plugins', 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()