From 2aab7db010960022c511518ac0c3f1d7317a9392 Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Fri, 13 May 2016 18:23:42 +0200 Subject: [PATCH] First sprint for the outdated function --- glances/logger.py | 4 ++ glances/outdated.py | 159 ++++++++++++++++++++++++++++++++++++++++++ glances/standalone.py | 9 +++ 3 files changed, 172 insertions(+) create mode 100644 glances/outdated.py diff --git a/glances/logger.py b/glances/logger.py index 98086ba6..64d798c9 100644 --- a/glances/logger.py +++ b/glances/logger.py @@ -71,6 +71,10 @@ LOGGING_CFG = { 'handlers': ['file'], 'level': 'INFO' }, + 'requests': { + 'handlers': ['file', 'console'], + 'level': 'ERROR', + }, 'elasticsearch': { 'handlers': ['file', 'console'], 'level': 'ERROR', diff --git a/glances/outdated.py b/glances/outdated.py new file mode 100644 index 00000000..09ad28fd --- /dev/null +++ b/glances/outdated.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2016 Nicolargo +# +# Glances is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Glances 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +"""Manage Glances update.""" + +from datetime import datetime, timedelta +from distutils.version import LooseVersion +import threading +import json +import pickle +import os +try: + import requests +except ImportError: + outdated_tag = False +else: + outdated_tag = True + +from glances import __version__, __appname__ +from glances.globals import BSD, LINUX, OSX, WINDOWS +from glances.logger import logger + + +class Outdated(object): + + """ + This class aims at providing methods to warn the user when a new Glances + version is available on the Pypi repository (https://pypi.python.org/pypi/Glances/). + """ + + PYPI_API_URL = 'https://pypi.python.org/pypi/Glances/json' + max_refresh_date = timedelta(days=7) + + def __init__(self, args, config): + """Init the Outdated class""" + self.args = args + self.config = config + + # Set default value... + self.data = { + 'installed_version': __version__, + 'latest_version': '0.0', + 'refresh_date': None + } + # ... and update + self.get_pypi_version() + + def installed_version(self): + return self.data['installed_version'] + + def latest_version(self): + return self.data['latest_version'] + + def refresh_date(self): + return self.data['refresh_date'] + + def get_pypi_version(self): + """Wrapper to get the latest Pypi version (async) + The data are stored in a cached file + Only update online once a week + """ + # If the cached file exist, read-it + cached_data = self._load_cache() + + if outdated_tag and cached_data == {}: + # Update needed + # Update and save the cache + thread = threading.Thread(target=self._update_pypi_version) + thread.start() + else: + # Update not needed + self.data['latest_version'] = cached_data['latest_version'] + logger.debug("Get the Glances version from the cache file") + + def _load_cache(self): + """Load cache file and return cached data""" + # If the cached file exist, read-it + cached_data = {} + try: + with open(os.path.join(self._cache_path(), 'glances-version.db'), 'rb') as f: + cached_data = pickle.load(f) + except IOError as e: + logger.debug("Can not read the version cache file ({0})".format(e)) + else: + logger.debug("Read the version cache file") + if cached_data['installed_version'] != self.installed_version() or \ + datetime.now() - cached_data['refresh_date'] > self.max_refresh_date: + # Reset the cache if: + # - the installed version is different + # - the refresh_date is > max_refresh_date + cached_data = {} + return cached_data + + def _save_cache(self): + """Save data to a file""" + # If the cached file exist, read-it + try: + with open(os.path.join(self._cache_path(), 'glances-version.db'), 'wb') as f: + pickle.dump(self.data, f) + except IOError: + return False + return True + + def _cache_path(self): + """Return the cached file path""" + if LINUX or BSD: + return os.path.join(os.environ.get('XDG_CONFIG_HOME') or + os.path.expanduser('~/.config'), + __appname__) + elif OSX: + return os.path.join(os.path.expanduser('~/Library/Application Support/'), + __appname__) + elif WINDOWS: + return os.path.join(os.environ.get('APPDATA'), __appname__) + + def _update_pypi_version(self): + """Get the latest Pypi version (as a string) via the Restful JSON API""" + # Get the Nginx status + logger.debug("Get latest Glances version from the Pypi Restfull API ({0})".format(self.PYPI_API_URL)) + try: + res = requests.get(self.PYPI_API_URL) + except Exception as e: + logger.debug("Can not get the Glances version from the Pypi Restfull API ({0})".format(e)) + else: + if res.ok: + # Update data + self.data['latest_version'] = json.loads(res.text)['info']['version'] + self.data['refresh_date'] = datetime.now() + # Save result to the cache file + self._save_cache() + logger.debug("Save Glances version to the cache file") + else: + logger.debug("Can not get the Glances version from the Pypi Restfull API ({0})".format(res.reason)) + + return self.data + + def is_outdated(self): + """Return True if a new version is available""" + if not outdated_tag: + logger.debug("Python Request lib is not installed. Can not get last Glances version on Pypi.") + return False + logger.debug("Check Glances version (installed: {0} / latest: {1})".format(self.installed_version(), self.latest_version())) + return LooseVersion(self.latest_version()) > LooseVersion(self.installed_version()) diff --git a/glances/standalone.py b/glances/standalone.py index f0cd4e27..ae610811 100644 --- a/glances/standalone.py +++ b/glances/standalone.py @@ -26,6 +26,7 @@ from glances.logger import logger from glances.processes import glances_processes from glances.stats import GlancesStats from glances.outputs.glances_curses import GlancesCursesStandalone +from glances.outdated import Outdated class GlancesStandalone(object): @@ -77,6 +78,9 @@ class GlancesStandalone(object): # Init screen self.screen = GlancesCursesStandalone(config=config, args=args) + # Check the latest Glances version + self.outdated = Outdated(config=config, args=args) + @property def quiet(self): return self._quiet @@ -115,3 +119,8 @@ class GlancesStandalone(object): # Exit from export modules self.stats.end() + + # Check Glances version versus Pypi one + if self.outdated.is_outdated(): + print("You are using Glances version {0}, however version {1} is available.".format(self.outdated.installed_version(), self.outdated.latest_version())) + print("You should consider upgrading using: pip install --upgrade glances")