Completed
Push — master ( 3dcd25...20576f )
by Nicolas
01:26
created

Outdated   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 150
rs 9.6
wmc 32

11 Methods

Rating   Name   Duplication   Size   Complexity  
A installed_version() 0 2 1
A load_config() 0 12 3
A _save_cache() 0 9 3
A latest_version() 0 2 1
A refresh_date() 0 2 1
B _cache_path() 0 11 5
A is_outdated() 0 12 3
B _update_pypi_version() 0 25 4
A get_pypi_version() 0 20 4
B _load_cache() 0 18 6
A __init__() 0 17 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2016 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
try:
29
    import requests
30
except ImportError:
31
    outdated_tag = False
32
else:
33
    outdated_tag = True
34
35
from glances import __version__, __appname__
36
from glances.globals import BSD, LINUX, OSX, WINDOWS
37
from glances.logger import logger
38
39
40
class Outdated(object):
41
42
    """
43
    This class aims at providing methods to warn the user when a new Glances
44
    version is available on the Pypi repository (https://pypi.python.org/pypi/Glances/).
45
    """
46
47
    PYPI_API_URL = 'https://pypi.python.org/pypi/Glances/json'
48
    max_refresh_date = timedelta(days=7)
49
50
    def __init__(self, args, config):
51
        """Init the Outdated class"""
52
        self.args = args
53
        self.config = config
54
55
        # Set default value...
56
        self.data = {
57
            u'installed_version': __version__,
58
            u'latest_version': '0.0',
59
            u'refresh_date': datetime.now()
60
        }
61
        # Read the configuration file
62
        self.load_config(config)
63
        logger.debug("Check Glances version up-to-date: {}".format(not self.args.disable_check_update))
64
65
        # And update !
66
        self.get_pypi_version()
67
68
    def load_config(self, config):
69
        """Load outdated parameter in the global section of the configuration file."""
70
71
        global_section = 'global'
72
        if (hasattr(config, 'has_section') and
73
                config.has_section(global_section)):
74
            self.args.disable_check_update = config.get_value(global_section, 'check_update').lower() == 'false'
75
        else:
76
            logger.debug("Cannot find section {} in the configuration file".format(global_section))
77
            return False
78
79
        return True
80
81
    def installed_version(self):
82
        return self.data['installed_version']
83
84
    def latest_version(self):
85
        return self.data['latest_version']
86
87
    def refresh_date(self):
88
        return self.data['refresh_date']
89
90
    def get_pypi_version(self):
91
        """Wrapper to get the latest Pypi version (async)
92
        The data are stored in a cached file
93
        Only update online once a week
94
        """
95
        if not outdated_tag or self.args.disable_check_update:
96
            return
97
98
        # If the cached file exist, read-it
99
        cached_data = self._load_cache()
100
101
        if cached_data == {}:
102
            # Update needed
103
            # Update and save the cache
104
            thread = threading.Thread(target=self._update_pypi_version)
105
            thread.start()
106
        else:
107
            # Update not needed
108
            self.data['latest_version'] = cached_data['latest_version']
109
            logger.debug("Get the Glances version from the cache file")
110
111
    def is_outdated(self):
112
        """Return True if a new version is available"""
113
        if self.args.disable_check_update:
114
            # Check is disabled by configuration
115
            return False
116
117
        if not outdated_tag:
118
            logger.debug("Python Request lib is not installed. Can not get last Glances version on Pypi.")
119
            return False
120
121
        logger.debug("Check Glances version (installed: {} / latest: {})".format(self.installed_version(), self.latest_version()))
122
        return LooseVersion(self.latest_version()) > LooseVersion(self.installed_version())
123
124
    def _load_cache(self):
125
        """Load cache file and return cached data"""
126
        # If the cached file exist, read-it
127
        cached_data = {}
128
        try:
129
            with open(os.path.join(self._cache_path(), 'glances-version.db'), 'rb') as f:
130
                cached_data = pickle.load(f)
131
        except Exception as e:
132
            logger.debug("Cannot read the version cache file ({})".format(e))
133
        else:
134
            logger.debug("Read the version cache file")
135
            if cached_data['installed_version'] != self.installed_version() or \
136
               datetime.now() - cached_data['refresh_date'] > self.max_refresh_date:
137
                # Reset the cache if:
138
                # - the installed version is different
139
                # - the refresh_date is > max_refresh_date
140
                cached_data = {}
141
        return cached_data
142
143
    def _save_cache(self):
144
        """Save data to a file"""
145
        # If the cached file exist, read-it
146
        try:
147
            with open(os.path.join(self._cache_path(), 'glances-version.db'), 'wb') as f:
148
                pickle.dump(self.data, f)
149
        except IOError:
150
            return False
151
        return True
152
153
    def _cache_path(self):
154
        """Return the cached file path"""
155
        if LINUX or BSD:
156
            return os.path.join(os.environ.get('XDG_CONFIG_HOME') or
157
                                os.path.expanduser('~/.config'),
158
                                __appname__)
159
        elif OSX:
160
            return os.path.join(os.path.expanduser('~/Library/Application Support/'),
161
                                __appname__)
162
        elif WINDOWS:
163
            return os.path.join(os.environ.get('APPDATA'), __appname__)
164
165
    def _update_pypi_version(self):
166
        """Get the latest Pypi version (as a string) via the Restful JSON API"""
167
        # Get the Nginx status
168
        logger.debug("Get latest Glances version from the PyPI RESTful API ({})".format(self.PYPI_API_URL))
169
170
        # Update the current time
171
        self.data[u'refresh_date'] = datetime.now()
172
173
        try:
174
            res = requests.get(self.PYPI_API_URL)
175
        except Exception as e:
176
            logger.debug("Cannot get the Glances version from the PyPI RESTful API ({})".format(e))
177
        else:
178
            if res.ok:
179
                # Update data
180
                self.data[u'latest_version'] = json.loads(res.text)['info']['version']
181
                logger.debug("Save Glances version to the cache file")
182
            else:
183
                logger.debug("Cannot get the Glances version from the PyPI RESTful API ({})".format(res.reason))
184
185
        # Save result to the cache file
186
        # Note: also saved if the Glances Pypi version can not be grabed
187
        self._save_cache()
188
189
        return self.data
190