You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
6.0 KiB

4 years ago
  1. # -*- coding: utf-8 -*-
  2. #
  3. # This file is part of Glances.
  4. #
  5. # Copyright (C) 2018 Nicolargo <nicolas@nicolargo.com>
  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. """Manage Glances update."""
  20. from datetime import datetime, timedelta
  21. from distutils.version import LooseVersion
  22. import threading
  23. import json
  24. import pickle
  25. import os
  26. from glances import __version__
  27. from glances.compat import nativestr, urlopen, HTTPError, URLError
  28. from glances.config import user_cache_dir
  29. from glances.globals import safe_makedirs
  30. from glances.logger import logger
  31. PYPI_API_URL = 'https://pypi.python.org/pypi/Glances/json'
  32. class Outdated(object):
  33. """
  34. This class aims at providing methods to warn the user when a new Glances
  35. version is available on the PyPI repository (https://pypi.python.org/pypi/Glances/).
  36. """
  37. def __init__(self, args, config):
  38. """Init the Outdated class"""
  39. self.args = args
  40. self.config = config
  41. self.cache_dir = user_cache_dir()
  42. self.cache_file = os.path.join(self.cache_dir, 'glances-version.db')
  43. # Set default value...
  44. self.data = {
  45. u'installed_version': __version__,
  46. u'latest_version': '0.0',
  47. u'refresh_date': datetime.now()
  48. }
  49. # Read the configuration file
  50. self.load_config(config)
  51. logger.debug("Check Glances version up-to-date: {}".format(not self.args.disable_check_update))
  52. # And update !
  53. self.get_pypi_version()
  54. def load_config(self, config):
  55. """Load outdated parameter in the global section of the configuration file."""
  56. global_section = 'global'
  57. if (hasattr(config, 'has_section') and
  58. config.has_section(global_section)):
  59. self.args.disable_check_update = config.get_value(global_section, 'check_update').lower() == 'false'
  60. else:
  61. logger.debug("Cannot find section {} in the configuration file".format(global_section))
  62. return False
  63. return True
  64. def installed_version(self):
  65. return self.data['installed_version']
  66. def latest_version(self):
  67. return self.data['latest_version']
  68. def refresh_date(self):
  69. return self.data['refresh_date']
  70. def get_pypi_version(self):
  71. """Wrapper to get the latest PyPI version (async)
  72. The data are stored in a cached file
  73. Only update online once a week
  74. """
  75. if self.args.disable_check_update:
  76. return
  77. # If the cached file exist, read-it
  78. cached_data = self._load_cache()
  79. if cached_data == {}:
  80. # Update needed
  81. # Update and save the cache
  82. thread = threading.Thread(target=self._update_pypi_version)
  83. thread.start()
  84. else:
  85. # Update not needed
  86. self.data['latest_version'] = cached_data['latest_version']
  87. logger.debug("Get Glances version from cache file")
  88. def is_outdated(self):
  89. """Return True if a new version is available"""
  90. if self.args.disable_check_update:
  91. # Check is disabled by configuration
  92. return False
  93. logger.debug("Check Glances version (installed: {} / latest: {})".format(self.installed_version(), self.latest_version()))
  94. return LooseVersion(self.latest_version()) > LooseVersion(self.installed_version())
  95. def _load_cache(self):
  96. """Load cache file and return cached data"""
  97. # If the cached file exist, read-it
  98. max_refresh_date = timedelta(days=7)
  99. cached_data = {}
  100. try:
  101. with open(self.cache_file, 'rb') as f:
  102. cached_data = pickle.load(f)
  103. except Exception as e:
  104. logger.debug("Cannot read version from cache file: {} ({})".format(self.cache_file, e))
  105. else:
  106. logger.debug("Read version from cache file")
  107. if (cached_data['installed_version'] != self.installed_version() or
  108. datetime.now() - cached_data['refresh_date'] > max_refresh_date):
  109. # Reset the cache if:
  110. # - the installed version is different
  111. # - the refresh_date is > max_refresh_date
  112. cached_data = {}
  113. return cached_data
  114. def _save_cache(self):
  115. """Save data to the cache file."""
  116. # Create the cache directory
  117. safe_makedirs(self.cache_dir)
  118. # Create/overwrite the cache file
  119. try:
  120. with open(self.cache_file, 'wb') as f:
  121. pickle.dump(self.data, f)
  122. except Exception as e:
  123. logger.error("Cannot write version to cache file {} ({})".format(self.cache_file, e))
  124. def _update_pypi_version(self):
  125. """Get the latest PyPI version (as a string) via the RESTful JSON API"""
  126. logger.debug("Get latest Glances version from the PyPI RESTful API ({})".format(PYPI_API_URL))
  127. # Update the current time
  128. self.data[u'refresh_date'] = datetime.now()
  129. try:
  130. res = urlopen(PYPI_API_URL, timeout=3).read()
  131. except (HTTPError, URLError) as e:
  132. logger.debug("Cannot get Glances version from the PyPI RESTful API ({})".format(e))
  133. else:
  134. self.data[u'latest_version'] = json.loads(nativestr(res))['info']['version']
  135. logger.debug("Save Glances version to the cache file")
  136. # Save result to the cache file
  137. # Note: also saved if the Glances PyPI version cannot be grabbed
  138. self._save_cache()
  139. return self.data