Completed
Push — master ( 4dd873...a1acfe )
by Nicolas
02:21 queued 01:12
created

Outdated._update_pypi_version()   A

Complexity

Conditions 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
dl 0
loc 20
rs 9.4285
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2017 Nicolargo <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""Manage Glances update."""
21
22
from datetime import datetime, timedelta
23
from distutils.version import LooseVersion
24
import threading
25
import json
26
import pickle
27
import os
28
29
from glances import __version__
30
from glances.compat import nativestr, urlopen, HTTPError, URLError
31
from glances.config import user_cache_dir
32
from glances.globals import safe_makedirs
33
from glances.logger import logger
34
35
PYPI_API_URL = 'https://pypi.python.org/pypi/Glances/json'
36
37
38
class Outdated(object):
39
40
    """
41
    This class aims at providing methods to warn the user when a new Glances
42
    version is available on the PyPI repository (https://pypi.python.org/pypi/Glances/).
43
    """
44
45
    def __init__(self, args, config):
46
        """Init the Outdated class"""
47
        self.args = args
48
        self.config = config
49
        self.cache_dir = user_cache_dir()
50
        self.cache_file = os.path.join(self.cache_dir, 'glances-version.db')
51
52
        # Set default value...
53
        self.data = {
54
            u'installed_version': __version__,
55
            u'latest_version': '0.0',
56
            u'refresh_date': datetime.now()
57
        }
58
        # Read the configuration file
59
        self.load_config(config)
60
        logger.debug("Check Glances version up-to-date: {}".format(not self.args.disable_check_update))
61
62
        # And update !
63
        self.get_pypi_version()
64
65
    def load_config(self, config):
66
        """Load outdated parameter in the global section of the configuration file."""
67
68
        global_section = 'global'
69
        if (hasattr(config, 'has_section') and
70
                config.has_section(global_section)):
71
            self.args.disable_check_update = config.get_value(global_section, 'check_update').lower() == 'false'
72
        else:
73
            logger.debug("Cannot find section {} in the configuration file".format(global_section))
74
            return False
75
76
        return True
77
78
    def installed_version(self):
79
        return self.data['installed_version']
80
81
    def latest_version(self):
82
        return self.data['latest_version']
83
84
    def refresh_date(self):
85
        return self.data['refresh_date']
86
87
    def get_pypi_version(self):
88
        """Wrapper to get the latest PyPI version (async)
89
        The data are stored in a cached file
90
        Only update online once a week
91
        """
92
        if self.args.disable_check_update:
93
            return
94
95
        # If the cached file exist, read-it
96
        cached_data = self._load_cache()
97
98
        if cached_data == {}:
99
            # Update needed
100
            # Update and save the cache
101
            thread = threading.Thread(target=self._update_pypi_version)
102
            thread.start()
103
        else:
104
            # Update not needed
105
            self.data['latest_version'] = cached_data['latest_version']
106
            logger.debug("Get Glances version from cache file")
107
108
    def is_outdated(self):
109
        """Return True if a new version is available"""
110
        if self.args.disable_check_update:
111
            # Check is disabled by configuration
112
            return False
113
114
        logger.debug("Check Glances version (installed: {} / latest: {})".format(self.installed_version(), self.latest_version()))
115
        return LooseVersion(self.latest_version()) > LooseVersion(self.installed_version())
116
117
    def _load_cache(self):
118
        """Load cache file and return cached data"""
119
        # If the cached file exist, read-it
120
        max_refresh_date = timedelta(days=7)
121
        cached_data = {}
122
        try:
123
            with open(self.cache_file, 'rb') as f:
124
                cached_data = pickle.load(f)
125
        except Exception as e:
126
            logger.debug("Cannot read version from cache file: {}".format(e))
127
        else:
128
            logger.debug("Read version from cache file")
129
            if (cached_data['installed_version'] != self.installed_version() or
130
                    datetime.now() - cached_data['refresh_date'] > max_refresh_date):
131
                # Reset the cache if:
132
                # - the installed version is different
133
                # - the refresh_date is > max_refresh_date
134
                cached_data = {}
135
        return cached_data
136
137
    def _save_cache(self):
138
        """Save data to the cache file."""
139
        # Create the cache directory
140
        safe_makedirs(self.cache_dir)
141
142
        # Create/overwrite the cache file
143
        with open(self.cache_file, 'wb') as f:
144
            pickle.dump(self.data, f)
145
146
    def _update_pypi_version(self):
147
        """Get the latest PyPI version (as a string) via the RESTful JSON API"""
148
        logger.debug("Get latest Glances version from the PyPI RESTful API ({})".format(PYPI_API_URL))
149
150
        # Update the current time
151
        self.data[u'refresh_date'] = datetime.now()
152
153
        try:
154
            res = urlopen(PYPI_API_URL, timeout=3).read()
155
        except (HTTPError, URLError) as e:
156
            logger.debug("Cannot get Glances version from the PyPI RESTful API ({})".format(e))
157
        else:
158
            self.data[u'latest_version'] = json.loads(nativestr(res))['info']['version']
159
            logger.debug("Save Glances version to the cache file")
160
161
        # Save result to the cache file
162
        # Note: also saved if the Glances PyPI version cannot be grabbed
163
        self._save_cache()
164
165
        return self.data
166