From ea342ae233d5e8383478551523baf0c906b68aa7 Mon Sep 17 00:00:00 2001 From: Dario Date: Wed, 16 Jan 2019 16:56:53 +0100 Subject: [PATCH 01/13] Initial version_checker refactor --- medusa/__main__.py | 7 +- medusa/config.py | 2 +- medusa/server/api/v1/core.py | 2 +- medusa/server/web/core/error_logs.py | 2 +- medusa/server/web/home/handler.py | 2 +- medusa/updater/__init__.py | 1 + medusa/updater/docker_updater.py | 239 ++++++ medusa/updater/github_updater.py | 399 ++++++++++ medusa/updater/source_updater.py | 259 +++++++ medusa/updater/update_manager.py | 33 + medusa/updater/version_checker.py | 358 +++++++++ medusa/version_checker.py | 1048 -------------------------- 12 files changed, 1297 insertions(+), 1055 deletions(-) create mode 100644 medusa/updater/__init__.py create mode 100644 medusa/updater/docker_updater.py create mode 100644 medusa/updater/github_updater.py create mode 100644 medusa/updater/source_updater.py create mode 100644 medusa/updater/update_manager.py create mode 100644 medusa/updater/version_checker.py delete mode 100644 medusa/version_checker.py diff --git a/medusa/__main__.py b/medusa/__main__.py index ecc268cba9..a6667da0ed 100755 --- a/medusa/__main__.py +++ b/medusa/__main__.py @@ -67,7 +67,7 @@ from medusa import ( app, auto_post_processor, cache, db, event_queue, exception_handler, helpers, logger as app_logger, metadata, name_cache, naming, network_timezones, providers, - scheduler, show_queue, show_updater, subtitles, torrent_checker, trakt_checker, version_checker + scheduler, show_queue, show_updater, subtitles, torrent_checker, trakt_checker ) from medusa.common import SD, SKIPPED, WANTED from medusa.config import ( @@ -90,6 +90,7 @@ from medusa.system.shutdown import Shutdown from medusa.themes import read_themes from medusa.tv import Series +from medusa.updater.version_checker import CheckVersion logger = logging.getLogger(__name__) @@ -987,7 +988,7 @@ def initialize(self, console_logging=True): pass if app.VERSION_NOTIFY: - updater = version_checker.CheckVersion().updater + updater = CheckVersion().updater if updater: app.APP_VERSION = updater.get_cur_version() @@ -1128,7 +1129,7 @@ def initialize(self, console_logging=True): # initialize schedulers # updaters - app.version_check_scheduler = scheduler.Scheduler(version_checker.CheckVersion(), + app.version_check_scheduler = scheduler.Scheduler(CheckVersion(), cycleTime=datetime.timedelta(hours=app.UPDATE_FREQUENCY), threadName='CHECKVERSION', silent=False) diff --git a/medusa/config.py b/medusa/config.py index df815c978d..089e7fea51 100644 --- a/medusa/config.py +++ b/medusa/config.py @@ -32,7 +32,7 @@ from medusa.helper.common import try_int from medusa.helpers.utils import split_and_strip from medusa.logger.adapters.style import BraceAdapter -from medusa.version_checker import CheckVersion +from medusa.updater.version_checker import CheckVersion from requests.compat import urlsplit diff --git a/medusa/server/api/v1/core.py b/medusa/server/api/v1/core.py index 1474e44c9e..ce1572e7f3 100644 --- a/medusa/server/api/v1/core.py +++ b/medusa/server/api/v1/core.py @@ -59,7 +59,7 @@ from medusa.show.show import Show from medusa.system.restart import Restart from medusa.system.shutdown import Shutdown -from medusa.version_checker import CheckVersion +from medusa.updater.version_checker import CheckVersion from requests.compat import unquote_plus diff --git a/medusa/server/web/core/error_logs.py b/medusa/server/web/core/error_logs.py index 75b8b5d635..5fdba4c778 100644 --- a/medusa/server/web/core/error_logs.py +++ b/medusa/server/web/core/error_logs.py @@ -13,7 +13,7 @@ from medusa.issue_submitter import IssueSubmitter from medusa.logger import filter_logline, read_loglines from medusa.server.web.core.base import PageTemplate, WebRoot -from medusa.version_checker import CheckVersion +from medusa.updater.version_checker import CheckVersion from six import text_type diff --git a/medusa/server/web/home/handler.py b/medusa/server/web/home/handler.py index 6f15097943..0dbfeb044c 100644 --- a/medusa/server/web/home/handler.py +++ b/medusa/server/web/home/handler.py @@ -105,7 +105,7 @@ from medusa.system.restart import Restart from medusa.system.shutdown import Shutdown from medusa.tv.series import Series, SeriesIdentifier -from medusa.version_checker import CheckVersion +from medusa.updater.version_checker import CheckVersion from requests.compat import ( quote_plus, diff --git a/medusa/updater/__init__.py b/medusa/updater/__init__.py new file mode 100644 index 0000000000..9bad5790a5 --- /dev/null +++ b/medusa/updater/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/medusa/updater/docker_updater.py b/medusa/updater/docker_updater.py new file mode 100644 index 0000000000..95baffc8c1 --- /dev/null +++ b/medusa/updater/docker_updater.py @@ -0,0 +1,239 @@ +# coding=utf-8 + +from __future__ import unicode_literals + +import logging +import os +import shutil +import stat +import tarfile + +from github import GithubException + +from medusa import app, helpers, notifiers +from medusa.github_client import get_github_repo +from medusa.logger.adapters.style import BraceAdapter +from medusa.session.core import MedusaSafeSession +from medusa.updater.update_manager import UpdateManager + + +log = BraceAdapter(logging.getLogger(__name__)) +log.logger.addHandler(logging.NullHandler()) + + +class DockerUpdateManager(UpdateManager): + def __init__(self): + super(DockerUpdateManager, self).__init__() + self.github_org = self.get_github_org() + self.github_repo = self.get_github_repo() + + self.branch = app.BRANCH + if app.BRANCH == '': + self.branch = self._find_installed_branch() + + self._cur_commit_hash = app.CUR_COMMIT_HASH + self._newest_commit_hash = None + self._num_commits_behind = 0 + self._num_commits_ahead = 0 + + self.session = MedusaSafeSession() + + @staticmethod + def _find_installed_branch(): + return app.CUR_COMMIT_BRANCH if app.CUR_COMMIT_BRANCH else 'master' + + def get_cur_commit_hash(self): + return self._cur_commit_hash + + def get_newest_commit_hash(self): + return self._newest_commit_hash + + @staticmethod + def get_cur_version(): + return '' + + @staticmethod + def get_newest_version(): + return '' + + def get_num_commits_behind(self): + return self._num_commits_behind + + def get_num_commits_ahead(self): + return self._num_commits_ahead + + def need_update(self): + # need this to run first to set self._newest_commit_hash + try: + self._check_github_for_update() + except Exception as error: + log.warning(u"Unable to contact github, can't check for update: {0!r}", error) + return False + + if self.branch != self._find_installed_branch(): + log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) + return True + + if not self._cur_commit_hash or self._num_commits_behind > 0 or self._num_commits_ahead > 0: + return True + + return False + + def can_update(self): + """Whether or not the update can be performed. + + :return: + :rtype: bool + """ + return True + + def _check_github_for_update(self): + """Use pygithub to ask github if there is a newer version.. + + If there is a newer version it sets application's version text. + + commit_hash: hash that we're checking against + """ + + self._num_commits_behind = 0 + self._newest_commit_hash = None + + gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) + # try to get newest commit hash and commits behind directly by comparing branch and current commit + if self._cur_commit_hash: + try: + branch_compared = gh.compare(base=self.branch, head=self._cur_commit_hash) + self._newest_commit_hash = branch_compared.base_commit.sha + self._num_commits_behind = branch_compared.behind_by + self._num_commits_ahead = branch_compared.ahead_by + except Exception: # UnknownObjectException + self._newest_commit_hash = '' + self._num_commits_behind = 0 + self._num_commits_ahead = 0 + self._cur_commit_hash = '' + + # fall back and iterate over last 100 (items per page in gh_api) commits + if not self._newest_commit_hash: + + for curCommit in gh.get_commits(): + if not self._newest_commit_hash: + self._newest_commit_hash = curCommit.sha + if not self._cur_commit_hash: + break + + if curCommit.sha == self._cur_commit_hash: + break + + # when _cur_commit_hash doesn't match anything _num_commits_behind == 100 + self._num_commits_behind += 1 + + log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}', + self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind) + + def set_newest_text(self): + """Set an update text, when running in a docker container.""" + app.NEWEST_VERSION_STRING = None + + if not self._cur_commit_hash or self._num_commits_behind > 0: + log.debug(u'There is an update available, Medusa is running in a docker container, so auto updating is disabled.') + app.NEWEST_VERSION_STRING = 'There is an update available: please pull the latest docker image, ' \ + 'and rebuild your container to update' + + def update(self): + """ + Downloads the latest source tarball from github and installs it over the existing version. + """ + + tar_download_url = 'http://github.com/' + self.github_org + '/' + self.github_repo + '/tarball/' + self.branch + + try: + # prepare the update dir + app_update_dir = os.path.join(app.PROG_DIR, u'sr-update') + + if os.path.isdir(app_update_dir): + log.info(u'Clearing out update folder {0!r} before extracting', app_update_dir) + shutil.rmtree(app_update_dir) + + log.info(u'Clearing update folder {0!r} before extracting', app_update_dir) + os.makedirs(app_update_dir) + + # retrieve file + log.info(u'Downloading update from {0!r}', tar_download_url) + tar_download_path = os.path.join(app_update_dir, u'sr-update.tar') + helpers.download_file(tar_download_url, tar_download_path, session=self.session) + + if not os.path.isfile(tar_download_path): + log.warning(u"Unable to retrieve new version from {0!r}, can't update", tar_download_url) + return False + + if not tarfile.is_tarfile(tar_download_path): + log.warning(u"Retrieved version from {0!r} is corrupt, can't update", tar_download_url) + return False + + # extract to sr-update dir + log.info(u'Extracting file {0}', tar_download_path) + tar = tarfile.open(tar_download_path) + tar.extractall(app_update_dir) + tar.close() + + # delete .tar.gz + log.info(u'Deleting file {0}', tar_download_path) + os.remove(tar_download_path) + + # find update dir name + update_dir_contents = [x for x in os.listdir(app_update_dir) if + os.path.isdir(os.path.join(app_update_dir, x))] + if len(update_dir_contents) != 1: + log.warning(u'Invalid update data, update failed: {0}', update_dir_contents) + return False + content_dir = os.path.join(app_update_dir, update_dir_contents[0]) + + # walk temp folder and move files to main folder + log.info(u'Moving files from {0} to {1}', content_dir, app.PROG_DIR) + for dirname, _, filenames in os.walk(content_dir): # @UnusedVariable + dirname = dirname[len(content_dir) + 1:] + for curfile in filenames: + old_path = os.path.join(content_dir, dirname, curfile) + new_path = os.path.join(app.PROG_DIR, dirname, curfile) + + # Avoid DLL access problem on WIN32/64 + # These files needing to be updated manually + # or find a way to kill the access from memory + extension = os.path.splitext(curfile)[1] + if extension == '.dll': + try: + log.debug(u'Special handling for {0}', curfile) + os.chmod(new_path, stat.S_IWRITE) + os.remove(new_path) + os.renames(old_path, new_path) + except Exception as e: + log.debug(u'Unable to update {0}: {1!r}', new_path, e) + os.remove(old_path) # Trash the updated file without moving in new path + continue + + if os.path.isfile(new_path): + os.remove(new_path) + os.renames(old_path, new_path) + + app.CUR_COMMIT_HASH = self._newest_commit_hash + app.CUR_COMMIT_BRANCH = self.branch + + except Exception as e: + log.exception(u'Error while trying to update: {0}', e) + return False + + # Notify update successful + try: + notifiers.notify_git_update(app.CUR_COMMIT_HASH or '') + except Exception: + log.debug(u'Unable to send update notification. Continuing the update process') + return True + + @staticmethod + def list_remote_branches(): + try: + gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) + return [x.name for x in gh.get_branches() if x] + except GithubException as error: + log.warning(u"Unable to contact github, can't check for update: {0!r}", error) + return [] diff --git a/medusa/updater/github_updater.py b/medusa/updater/github_updater.py new file mode 100644 index 0000000000..9b0f471eac --- /dev/null +++ b/medusa/updater/github_updater.py @@ -0,0 +1,399 @@ +# coding=utf-8 + +from __future__ import unicode_literals + +import logging +import os +import platform +import re +import subprocess +from builtins import str + +from medusa import app, notifiers +from medusa.logger.adapters.style import BraceAdapter +from medusa.updater.update_manager import UpdateManager + + +ERROR_MESSAGE = ('Unable to find your git executable. Set git executable path in Advanced Settings ' + 'OR shutdown application and delete your .git folder and run from source to enable updates.') + +log = BraceAdapter(logging.getLogger(__name__)) +log.logger.addHandler(logging.NullHandler()) + + +class GitUpdateManager(UpdateManager): + def __init__(self): + super(GitUpdateManager, self).__init__() + self._git_path = self._find_working_git() + self.github_org = self.get_github_org() + self.github_repo = self.get_github_repo() + + self.branch = app.BRANCH = self._find_installed_branch() + + self._cur_commit_hash = None + self._newest_commit_hash = None + self._num_commits_behind = 0 + self._num_commits_ahead = 0 + self._cur_version = '' + + def get_cur_commit_hash(self): + return self._cur_commit_hash + + def get_newest_commit_hash(self): + return self._newest_commit_hash + + def get_cur_version(self): + if self._cur_commit_hash or self._find_installed_version(): + self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format(self._cur_commit_hash))[0] + return self._cur_version + + def get_newest_version(self): + if self._newest_commit_hash: + self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 ' + self._newest_commit_hash)[0] + else: + self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 ' + self._cur_commit_hash)[0] + return self._cur_version + + def get_num_commits_behind(self): + return self._num_commits_behind + + def get_num_commits_ahead(self): + return self._num_commits_ahead + + def _find_working_git(self): + test_cmd = 'version' + + if app.GIT_PATH: + main_git = '"' + app.GIT_PATH + '"' + else: + main_git = 'git' + + log.debug(u'Checking if we can use git commands: {0} {1}', main_git, test_cmd) + _, _, exit_status = self._run_git(main_git, test_cmd) + + if exit_status == 0: + log.debug(u'Using: {0}', main_git) + return main_git + else: + log.debug(u'Not using: {0}', main_git) + + # trying alternatives + + alternative_git = [] + + # osx people who start sr from launchd have a broken path, so try a hail-mary attempt for them + if platform.system().lower() == 'darwin': + alternative_git.append('/usr/local/git/bin/git') + + if platform.system().lower() == 'windows': + if main_git != main_git.lower(): + alternative_git.append(main_git.lower()) + + if alternative_git: + log.debug(u'Trying known alternative git locations') + + for cur_git in alternative_git: + log.debug(u'Checking if we can use git commands: {0} {1}', cur_git, test_cmd) + _, _, exit_status = self._run_git(cur_git, test_cmd) + + if exit_status == 0: + log.debug(u'Using: {0}', cur_git) + return cur_git + else: + log.debug(u'Not using: {0}', cur_git) + + # Still haven't found a working git + # Warn user only if he has version check enabled + if app.VERSION_NOTIFY: + app.NEWEST_VERSION_STRING = ERROR_MESSAGE + + return None + + @staticmethod + def _run_git(git_path, args): + + output = err = exit_status = None + + if not git_path: + log.warning(u"No git specified, can't use git commands") + app.NEWEST_VERSION_STRING = ERROR_MESSAGE + exit_status = 1 + return output, err, exit_status + + # If we have a valid git remove the git warning + # String will be updated as soon we check github + app.NEWEST_VERSION_STRING = None + + cmd = git_path + ' ' + args + + try: + log.debug(u'Executing {cmd} with your shell in {dir}', {'cmd': cmd, 'dir': app.PROG_DIR}) + p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + shell=True, cwd=app.PROG_DIR) + output, err = p.communicate() + exit_status = p.returncode + + # Convert bytes to string in python3 + if isinstance(output, (bytes, bytearray)): + output = output.decode('utf-8') + + if output: + output = output.strip() + + except OSError: + log.info(u"Command {cmd} didn't work", {'cmd': cmd}) + exit_status = 1 + + if exit_status == 0: + log.debug(u'{cmd} : returned successful', {'cmd': cmd}) + exit_status = 0 + + elif exit_status == 1: + if output: + if 'stash' in output: + log.warning(u"Enable 'git reset' in settings or stash your changes in local files") + else: + log.warning(u'{cmd} returned : {output}', {'cmd': cmd, 'output': output}) + else: + log.warning(u'{cmd} returned no data', {'cmd': cmd}) + exit_status = 1 + + elif exit_status == 128 or 'fatal:' in output or err: + log.warning(u'{cmd} returned : {output}', {'cmd': cmd, 'output': output}) + exit_status = 128 + + else: + log.warning(u'{cmd} returned : {output}. Treat as error for now', {'cmd': cmd, 'output': output}) + exit_status = 1 + + return output, err, exit_status + + def _find_installed_version(self): + """Attempt to find the currently installed version of the application. + + Uses git show to get commit version. + + :return: True for success or False for failure + """ + output, _, exit_status = self._run_git(self._git_path, 'rev-parse HEAD') # @UnusedVariable + + if exit_status == 0 and output: + cur_commit_hash = output.strip() + if not re.match('^[a-z0-9]+$', cur_commit_hash): + log.warning(u"Output doesn't look like a hash, not using it") + return False + self._cur_commit_hash = cur_commit_hash + app.CUR_COMMIT_HASH = str(cur_commit_hash) + return True + else: + return False + + def _find_installed_branch(self): + branch_info, _, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD') # @UnusedVariable + if exit_status == 0 and branch_info: + branch = branch_info.strip().replace('refs/heads/', '', 1) + if branch: + app.BRANCH = branch + return branch + return '' + + def _check_github_for_update(self): + """ + Uses git commands to check if there is a newer version that the provided + commit hash. If there is a newer version it sets _num_commits_behind. + """ + + self._num_commits_behind = 0 + self._num_commits_ahead = 0 + + # update remote origin url + self.update_remote_origin() + + # get all new info from github + output, _, exit_status = self._run_git(self._git_path, 'fetch --prune %s' % app.GIT_REMOTE) + if not exit_status == 0: + log.warning(u"Unable to contact github, can't check for update") + return + + # get latest commit_hash from remote + output, _, exit_status = self._run_git(self._git_path, 'rev-parse --verify --quiet "@{upstream}"') + + if exit_status == 0 and output: + cur_commit_hash = output.strip() + + if not re.match('^[a-z0-9]+$', cur_commit_hash): + log.debug(u"Output doesn't look like a hash, not using it") + return + + else: + self._newest_commit_hash = cur_commit_hash + else: + log.debug(u"git didn't return newest commit hash") + return + + # get number of commits behind and ahead (option --count not supported git < 1.7.2) + output, _, exit_status = self._run_git(self._git_path, 'rev-list --left-right "@{upstream}"...HEAD') + if exit_status == 0 and output: + + try: + self._num_commits_behind = int(output.count('<')) + self._num_commits_ahead = int(output.count('>')) + + except Exception: + log.debug(u"git didn't return numbers for behind and ahead, not using it") + return + + log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}, num_commits_ahead = {3}', + self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind, self._num_commits_ahead) + + def set_newest_text(self): + # if we're up to date then don't set this + app.NEWEST_VERSION_STRING = None + + if self._num_commits_behind > 0 or self._is_hard_reset_allowed(): + + base_url = 'http://github.com/' + self.github_org + '/' + self.github_repo + if self._newest_commit_hash: + url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash + else: + url = base_url + '/commits/' + + newest_text = 'There is a newer version available ' + newest_text += " (you're " + str(self._num_commits_behind) + ' commit' + if self._num_commits_behind > 1: + newest_text += 's' + newest_text += ' behind' + if self._num_commits_ahead > 0: + newest_text += ' and {ahead} commit{s} ahead'.format(ahead=self._num_commits_ahead, + s='s' if self._num_commits_ahead > 1 else '') + newest_text += ') — Update Now' + + elif self._num_commits_ahead > 0: + newest_text = u'Local branch is ahead of {0}. Automatic update not possible'.format(self.branch) + log.warning(newest_text) + + else: + return + + app.NEWEST_VERSION_STRING = newest_text + + def need_update(self): + + if self.branch != self._find_installed_branch(): + log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) + return True + + self._find_installed_version() + if not self._cur_commit_hash: + return True + else: + try: + self._check_github_for_update() + except Exception as e: + log.warning(u"Unable to contact github, can't check for update: {0!r}", e) + return False + + if self._num_commits_behind > 0 or self._num_commits_ahead > 0: + return True + + return False + + def can_update(self): + """Return whether update can be executed. + + :return: + :rtype: bool + """ + return self._num_commits_ahead <= 0 or self._is_hard_reset_allowed() + + def update(self): + """Call git pull origin in order to update the application. + + Returns a bool depending on the call's success. + """ + # update remote origin url + self.update_remote_origin() + + # remove untracked files and performs a hard reset on git branch to avoid update issues + if self._is_hard_reset_allowed(): + self.reset() + + # Executing git clean before updating + self.clean() + + if self.branch == self._find_installed_branch(): + _, _, exit_status = self._run_git(self._git_path, 'pull -f %s %s' % (app.GIT_REMOTE, self.branch)) # @UnusedVariable + else: + _, _, exit_status = self._run_git(self._git_path, 'checkout -f ' + self.branch) # @UnusedVariable + + # Executing git clean after updating + self.clean() + + if exit_status == 0: + self._find_installed_version() + # Notify update successful + if app.NOTIFY_ON_UPDATE: + try: + notifiers.notify_git_update(app.CUR_COMMIT_HASH or '') + except Exception: + log.debug(u'Unable to send update notification. Continuing the update process') + return True + + else: + return False + + @staticmethod + def _is_hard_reset_allowed(): + """Return whether git hard reset is allowed or not. + + :return: + :rtype: bool + """ + return app.GIT_RESET and (not app.GIT_RESET_BRANCHES or + app.BRANCH in app.GIT_RESET_BRANCHES) + + def clean(self): + """Call git clean to remove all untracked files. + + It only affects source folders and libX and extX folders, + to prevent deleting untracked user data not known by .gitignore + + :return: + :rtype: int + """ + # Fixes: goo.gl/tr8Awf - to be removed in the next release + root_dir = os.path.basename(app.PROG_DIR) + helper_folder = os.path.join(root_dir, 'helper') + helpers_folder = os.path.join(root_dir, 'helpers') + + folders = (app.LIB_FOLDER, app.LIB2_FOLDER, app.LIB3_FOLDER, app.EXT_FOLDER, + app.EXT2_FOLDER, app.EXT3_FOLDER, app.SRC_FOLDER, app.STATIC_FOLDER, + helper_folder, helpers_folder) + app.LEGACY_SRC_FOLDERS + _, _, exit_status = self._run_git(self._git_path, 'clean -d -f -x {0}'.format(' '.join(folders))) + + return exit_status + + def reset(self): + """ + Calls git reset --hard to perform a hard reset. Returns a bool depending + on the call's success. + """ + _, _, exit_status = self._run_git(self._git_path, 'reset --hard {0}/{1}'.format + (app.GIT_REMOTE, app.BRANCH)) # @UnusedVariable + if exit_status == 0: + return True + + def list_remote_branches(self): + # update remote origin url + self.update_remote_origin() + app.BRANCH = self._find_installed_branch() + + branches, _, exit_status = self._run_git(self._git_path, 'ls-remote --heads %s' % app.GIT_REMOTE) # @UnusedVariable + if exit_status == 0 and branches: + if branches: + return re.findall(r'refs/heads/(.*)', branches) + return [] + + def update_remote_origin(self): + self._run_git(self._git_path, 'config remote.%s.url %s' % (app.GIT_REMOTE, app.GIT_REMOTE_URL)) + self._run_git(self._git_path, 'config remote.%s.pushurl %s' % (app.GIT_REMOTE, app.GIT_REMOTE_URL)) diff --git a/medusa/updater/source_updater.py b/medusa/updater/source_updater.py new file mode 100644 index 0000000000..b7b363cb6c --- /dev/null +++ b/medusa/updater/source_updater.py @@ -0,0 +1,259 @@ +# coding=utf-8 + +from __future__ import unicode_literals + +import logging +import os +import shutil +import stat +import tarfile +from builtins import str + +from github import GithubException + +from medusa import app, helpers, notifiers +from medusa.github_client import get_github_repo +from medusa.logger.adapters.style import BraceAdapter +from medusa.session.core import MedusaSafeSession +from medusa.updater.update_manager import UpdateManager + + +log = BraceAdapter(logging.getLogger(__name__)) +log.logger.addHandler(logging.NullHandler()) + + +class SourceUpdateManager(UpdateManager): + def __init__(self): + super(SourceUpdateManager, self).__init__() + self.github_org = self.get_github_org() + self.github_repo = self.get_github_repo() + + self.branch = app.BRANCH + if app.BRANCH == '': + self.branch = self._find_installed_branch() + + self._cur_commit_hash = app.CUR_COMMIT_HASH + self._newest_commit_hash = None + self._num_commits_behind = 0 + self._num_commits_ahead = 0 + + self.session = MedusaSafeSession() + + @staticmethod + def _find_installed_branch(): + return app.CUR_COMMIT_BRANCH if app.CUR_COMMIT_BRANCH else 'master' + + def get_cur_commit_hash(self): + return self._cur_commit_hash + + def get_newest_commit_hash(self): + return self._newest_commit_hash + + @staticmethod + def get_cur_version(): + return '' + + @staticmethod + def get_newest_version(): + return '' + + def get_num_commits_behind(self): + return self._num_commits_behind + + def get_num_commits_ahead(self): + return self._num_commits_ahead + + def need_update(self): + # need this to run first to set self._newest_commit_hash + try: + self._check_github_for_update() + except Exception as error: + log.warning(u"Unable to contact github, can't check for update: {0!r}", error) + return False + + if self.branch != self._find_installed_branch(): + log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) + return True + + if not self._cur_commit_hash or self._num_commits_behind > 0 or self._num_commits_ahead > 0: + return True + + return False + + def can_update(self): + """Whether or not the update can be performed. + + :return: + :rtype: bool + """ + return True + + def _check_github_for_update(self): + """Use pygithub to ask github if there is a newer version.. + + If there is a newer version it sets application's version text. + + commit_hash: hash that we're checking against + """ + + self._num_commits_behind = 0 + self._newest_commit_hash = None + + gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) + # try to get newest commit hash and commits behind directly by comparing branch and current commit + if self._cur_commit_hash: + try: + branch_compared = gh.compare(base=self.branch, head=self._cur_commit_hash) + self._newest_commit_hash = branch_compared.base_commit.sha + self._num_commits_behind = branch_compared.behind_by + self._num_commits_ahead = branch_compared.ahead_by + except Exception: # UnknownObjectException + self._newest_commit_hash = '' + self._num_commits_behind = 0 + self._num_commits_ahead = 0 + self._cur_commit_hash = '' + + # fall back and iterate over last 100 (items per page in gh_api) commits + if not self._newest_commit_hash: + + for curCommit in gh.get_commits(): + if not self._newest_commit_hash: + self._newest_commit_hash = curCommit.sha + if not self._cur_commit_hash: + break + + if curCommit.sha == self._cur_commit_hash: + break + + # when _cur_commit_hash doesn't match anything _num_commits_behind == 100 + self._num_commits_behind += 1 + + log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}', + self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind) + + def set_newest_text(self): + # if we're up to date then don't set this + app.NEWEST_VERSION_STRING = None + + if not self._cur_commit_hash: + log.debug(u"Unknown current version number, don't know if we should update or not") + + newest_text = "Unknown current version number: If you've never used the application " \ + 'upgrade system before then current version is not set. ' \ + '— Update Now' + + elif self._num_commits_behind > 0: + base_url = 'http://github.com/' + self.github_org + '/' + self.github_repo + if self._newest_commit_hash: + url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash + else: + url = base_url + '/commits/' + + newest_text = 'There is a newer version available' + newest_text += " (you're " + str(self._num_commits_behind) + ' commit' + if self._num_commits_behind > 1: + newest_text += 's' + newest_text += ' behind) — Update Now' + else: + return + + app.NEWEST_VERSION_STRING = newest_text + + def update(self): + """ + Downloads the latest source tarball from github and installs it over the existing version. + """ + + tar_download_url = 'http://github.com/' + self.github_org + '/' + self.github_repo + '/tarball/' + self.branch + + try: + # prepare the update dir + app_update_dir = os.path.join(app.PROG_DIR, u'medusa-update') + + if os.path.isdir(app_update_dir): + log.info(u'Clearing out update folder {0!r} before extracting', app_update_dir) + shutil.rmtree(app_update_dir) + + log.info(u'Clearing update folder {0!r} before extracting', app_update_dir) + os.makedirs(app_update_dir) + + # retrieve file + log.info(u'Downloading update from {0!r}', tar_download_url) + tar_download_path = os.path.join(app_update_dir, u'medusa-update.tar') + helpers.download_file(tar_download_url, tar_download_path, session=self.session) + + if not os.path.isfile(tar_download_path): + log.warning(u"Unable to retrieve new version from {0!r}, can't update", tar_download_url) + return False + + if not tarfile.is_tarfile(tar_download_path): + log.warning(u"Retrieved version from {0!r} is corrupt, can't update", tar_download_url) + return False + + # extract to medusa-update dir + log.info(u'Extracting file {0}', tar_download_path) + tar = tarfile.open(tar_download_path) + tar.extractall(app_update_dir) + tar.close() + + # delete .tar.gz + log.info(u'Deleting file {0}', tar_download_path) + os.remove(tar_download_path) + + # find update dir name + update_dir_contents = [x for x in os.listdir(app_update_dir) if + os.path.isdir(os.path.join(app_update_dir, x))] + if len(update_dir_contents) != 1: + log.warning(u'Invalid update data, update failed: {0}', update_dir_contents) + return False + content_dir = os.path.join(app_update_dir, update_dir_contents[0]) + + # walk temp folder and move files to main folder + log.info(u'Moving files from {0} to {1}', content_dir, app.PROG_DIR) + for dirname, _, filenames in os.walk(content_dir): # @UnusedVariable + dirname = dirname[len(content_dir) + 1:] + for curfile in filenames: + old_path = os.path.join(content_dir, dirname, curfile) + new_path = os.path.join(app.PROG_DIR, dirname, curfile) + + # Avoid DLL access problem on WIN32/64 + # These files needing to be updated manually + # or find a way to kill the access from memory + extension = os.path.splitext(curfile)[1] + if extension == '.dll': + try: + log.debug(u'Special handling for {0}', curfile) + os.chmod(new_path, stat.S_IWRITE) + os.remove(new_path) + os.renames(old_path, new_path) + except Exception as e: + log.debug(u'Unable to update {0}: {1!r}', new_path, e) + os.remove(old_path) # Trash the updated file without moving in new path + continue + + if os.path.isfile(new_path): + os.remove(new_path) + os.renames(old_path, new_path) + + app.CUR_COMMIT_HASH = self._newest_commit_hash + app.CUR_COMMIT_BRANCH = self.branch + + except Exception as e: + log.exception(u'Error while trying to update: {0}', e) + return False + + # Notify update successful + try: + notifiers.notify_git_update(app.CUR_COMMIT_HASH or '') + except Exception: + log.debug(u'Unable to send update notification. Continuing the update process') + return True + + @staticmethod + def list_remote_branches(): + try: + gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) + return [x.name for x in gh.get_branches() if x] + except GithubException as error: + log.warning(u"Unable to contact github, can't check for update: {0!r}", error) + return [] diff --git a/medusa/updater/update_manager.py b/medusa/updater/update_manager.py new file mode 100644 index 0000000000..ab0c700e29 --- /dev/null +++ b/medusa/updater/update_manager.py @@ -0,0 +1,33 @@ +# coding=utf-8 + +from __future__ import unicode_literals + +import logging + +from medusa import app +from medusa.logger.adapters.style import BraceAdapter + +from six import text_type + + +log = BraceAdapter(logging.getLogger(__name__)) +log.logger.addHandler(logging.NullHandler()) + + +class UpdateManager(object): + def __init__(self): + """Update manager initialization.""" + # Initialize the app.RUNS_IN_DOCKER variable + # self.runs_in_docker() + + @staticmethod + def get_github_org(): + return app.GIT_ORG + + @staticmethod + def get_github_repo(): + return app.GIT_REPO + + @staticmethod + def get_update_url(): + return app.WEB_ROOT + '/home/update/?pid=' + text_type(app.PID) diff --git a/medusa/updater/version_checker.py b/medusa/updater/version_checker.py new file mode 100644 index 0000000000..06fa540639 --- /dev/null +++ b/medusa/updater/version_checker.py @@ -0,0 +1,358 @@ +# coding=utf-8 + +from __future__ import unicode_literals + +import datetime +import logging +import os +import re +import time +from builtins import object +from builtins import str +from logging import DEBUG, WARNING + +from medusa import app, db, helpers, ui +from medusa.logger.adapters.style import BraceAdapter +from medusa.session.core import MedusaSafeSession +from medusa.updater.docker_updater import DockerUpdateManager +from medusa.updater.github_updater import GitUpdateManager +from medusa.updater.source_updater import SourceUpdateManager + + +log = BraceAdapter(logging.getLogger(__name__)) +log.logger.addHandler(logging.NullHandler()) + + +class CheckVersion(object): + """Version check class meant to run as a thread object with the sr scheduler.""" + + def __init__(self): + self.amActive = False + self.updater = self.find_install_type() + self.session = MedusaSafeSession() + + def run(self, force=False): + + self.amActive = True + + # Update remote branches and store in app.GIT_REMOTE_BRANCHES + self.list_remote_branches() + + if self.updater: + # set current branch version + app.BRANCH = self.get_branch() + + if self.check_for_new_version(force): + if app.AUTO_UPDATE: + log.info(u'New update found, starting auto-updater ...') + ui.notifications.message('New update found, starting auto-updater') + if self.run_backup_if_safe(): + if app.version_check_scheduler.action.update(): + log.info(u'Update was successful!') + ui.notifications.message('Update was successful') + app.events.put(app.events.SystemEvent.RESTART) + else: + log.info(u'Update failed!') + ui.notifications.message('Update failed!') + + self.check_for_new_news(force) + + self.amActive = False + + def run_backup_if_safe(self): + return self.safe_to_update() is True and self._runbackup() is True + + def _runbackup(self): + # Do a system backup before update + log.info(u'Config backup in progress...') + ui.notifications.message('Backup', 'Config backup in progress...') + try: + backupDir = os.path.join(app.DATA_DIR, app.BACKUP_DIR) + if not os.path.isdir(backupDir): + os.mkdir(backupDir) + + if self._keeplatestbackup(backupDir) and self._backup(backupDir): + log.info(u'Config backup successful, updating...') + ui.notifications.message('Backup', 'Config backup successful, updating...') + return True + else: + log.warning(u'Config backup failed, aborting update') + ui.notifications.message('Backup', 'Config backup failed, aborting update') + return False + except Exception as e: + log.error(u'Update: Config backup failed. Error: {0!r}', e) + ui.notifications.message('Backup', 'Config backup failed, aborting update') + return False + + @staticmethod + def _keeplatestbackup(backupDir=None): + if not backupDir: + return False + + import glob + files = glob.glob(os.path.join(backupDir, '*.zip')) + if not files: + return True + + now = time.time() + newest = files[0], now - os.path.getctime(files[0]) + for f in files[1:]: + age = now - os.path.getctime(f) + if age < newest[1]: + newest = f, age + files.remove(newest[0]) + + for f in files: + os.remove(f) + + return True + + # TODO: Merge with backup in helpers + @staticmethod + def _backup(backupDir=None): + if not backupDir: + return False + source = [ + os.path.join(app.DATA_DIR, app.APPLICATION_DB), + app.CONFIG_FILE, + os.path.join(app.DATA_DIR, app.FAILED_DB), + os.path.join(app.DATA_DIR, app.CACHE_DB) + ] + target = os.path.join(backupDir, app.BACKUP_FILENAME.format(timestamp=time.strftime('%Y%m%d%H%M%S'))) + + for (path, dirs, files) in os.walk(app.CACHE_DIR, topdown=True): + for dirname in dirs: + if path == app.CACHE_DIR and dirname not in ['images']: + dirs.remove(dirname) + for filename in files: + source.append(os.path.join(path, filename)) + + return helpers.backup_config_zip(source, target, app.DATA_DIR) + + def safe_to_update(self): + + def db_safe(self): + message = { + 'equal': { + 'type': DEBUG, + 'text': u'We can proceed with the update. New update has same DB version'}, + 'upgrade': { + 'type': WARNING, + 'text': u"We can't proceed with the update. New update has a new DB version. Please manually update"}, + 'downgrade': { + 'type': WARNING, + 'text': u"We can't proceed with the update. New update has a old DB version. It's not possible to downgrade"}, + } + try: + result = self.getDBcompare() + if result in message: + log.log(message[result]['type'], message[result]['text']) # unpack the result message into a log entry + else: + log.warning(u"We can't proceed with the update. Unable to check remote DB version. Error: {0}", result) + return result in ['equal'] # add future True results to the list + except Exception as error: + log.error(u"We can't proceed with the update. Unable to compare DB version. Error: {0!r}", error) + return False + + def postprocessor_safe(): + if not app.auto_post_processor_scheduler.action.amActive: + log.debug(u'We can proceed with the update. Post-Processor is not running') + return True + else: + log.debug(u"We can't proceed with the update. Post-Processor is running") + return False + + def showupdate_safe(): + if not app.show_update_scheduler.action.amActive: + log.debug(u'We can proceed with the update. Shows are not being updated') + return True + else: + log.debug(u"We can't proceed with the update. Shows are being updated") + return False + + db_safe = db_safe(self) + postprocessor_safe = postprocessor_safe() + showupdate_safe = showupdate_safe() + + if db_safe and postprocessor_safe and showupdate_safe: + log.debug(u'Proceeding with auto update') + return True + else: + log.debug(u'Auto update aborted') + return False + + def getDBcompare(self): + """ + Compare the current DB version with the new branch version. + + :return: 'upgrade', 'equal', or 'downgrade' + """ + try: + self.updater.need_update() + cur_hash = str(self.updater.get_newest_commit_hash()) + assert len(cur_hash) == 40, 'Commit hash wrong length: {length} hash: {hash}'.format( + length=len(cur_hash), hash=cur_hash) + + check_url = 'http://rawcdn.githack.com/{org}/{repo}/{commit}/medusa/databases/main_db.py'.format( + org=app.GIT_ORG, repo=app.GIT_REPO, commit=cur_hash) + response = self.session.get(check_url) + + # Get remote DB version + match_max_db = re.search(r'MAX_DB_VERSION\s*=\s*(?P\d{2,3})', response.text) + new_branch_major_db_version = int(match_max_db.group('version')) if match_max_db else None + match_minor_db = re.search(r'CURRENT_MINOR_DB_VERSION\s*=\s*(?P\d{1,2})', response.text) + new_branch_min_db_version = int(match_minor_db.group('version')) if match_minor_db else None + + # Check local DB version + main_db_con = db.DBConnection() + cur_branch_major_db_version, cur_branch_minor_db_version = main_db_con.checkDBVersion() + + if any([cur_branch_major_db_version is None, cur_branch_minor_db_version is None, + new_branch_major_db_version is None, new_branch_min_db_version is None]): + return 'Could not compare database versions, aborting' + + if new_branch_major_db_version > cur_branch_major_db_version: + return 'upgrade' + elif new_branch_major_db_version == cur_branch_major_db_version: + if new_branch_min_db_version < cur_branch_minor_db_version: + return 'downgrade' + elif new_branch_min_db_version > cur_branch_minor_db_version: + return 'upgrade' + return 'equal' + else: + return 'downgrade' + except Exception as e: + return repr(e) + + def find_install_type(self): + """ + Determines how this copy of sr was installed. + + :return: type of installation. Possible values are: + 'docker': any docker build + 'git': running from source using git + 'source': running from source without git + """ + if self.runs_in_docker(): + return DockerUpdateManager() + elif os.path.isdir(os.path.join(app.PROG_DIR, u'.git')): + return GitUpdateManager() + + return SourceUpdateManager() + + def check_for_new_version(self, force=False): + """ + Check the internet for a newer version. + + :force: if true the VERSION_NOTIFY setting will be ignored and a check will be forced + :return: bool, True for new version or False for no new version. + """ + if not self.updater or (not app.VERSION_NOTIFY and not app.AUTO_UPDATE and not force): + log.info(u'Version checking is disabled, not checking for the newest version') + app.NEWEST_VERSION_STRING = None + return False + + # checking for updates + if not app.AUTO_UPDATE: + log.info(u'Checking for updates using {0}', 'Secret!!') + + if not self.updater.need_update(): + app.NEWEST_VERSION_STRING = None + + if force: + ui.notifications.message('No update needed') + log.info(u'No update needed') + + # no updates needed + return False + + # found updates + self.updater.set_newest_text() + return self.updater.can_update() + + def check_for_new_news(self, force=False): + """ + Check GitHub for the latest news. + + :return: unicode, a copy of the news + :force: ignored + """ + # Grab a copy of the news + log.debug(u'Checking GitHub for latest news.') + response = self.session.get(app.NEWS_URL) + if not response or not response.text: + log.debug(u'Could not load news from URL: {0}', app.NEWS_URL) + return + + try: + last_read = datetime.datetime.strptime(app.NEWS_LAST_READ, '%Y-%m-%d') + except ValueError: + log.warning(u'Invalid news last read date: {0}', app.NEWS_LAST_READ) + last_read = 0 + + news = response.text + app.NEWS_UNREAD = 0 + got_latest = False + for match in re.finditer(r'^####\s*(\d{4}-\d{2}-\d{2})\s*####', news, re.M): + if not got_latest: + got_latest = True + app.NEWS_LATEST = match.group(1) + + try: + if datetime.datetime.strptime(match.group(1), '%Y-%m-%d') > last_read: + app.NEWS_UNREAD += 1 + except ValueError: + log.warning(u'Unable to match latest news date. Repository news date: {0}', match.group(1)) + pass + + return news + + def need_update(self): + if self.updater: + return self.updater.need_update() + + def update(self): + if self.updater: + # update branch with current config branch value + self.updater.branch = app.BRANCH + + # check for updates + if self.updater.need_update(): + return self.updater.update() + + def list_remote_branches(self): + if self.updater: + app.GIT_REMOTE_BRANCHES = self.updater.list_remote_branches() + return app.GIT_REMOTE_BRANCHES + + def get_branch(self): + if self.updater: + return self.updater.branch + + @staticmethod + def runs_in_docker(): + """ + Check if Medusa is run in a docker container. + + If run in a container, we don't want to use the auto update feature, but just want to inform the user + there is an update available. The user can update through getting the latest docker tag. + """ + if app.RUNS_IN_DOCKER is not None: + return app.RUNS_IN_DOCKER + + path = '/proc/{pid}/cgroup'.format(pid=os.getpid()) + try: + if not os.path.isfile(path): + return False + + with open(path) as f: + for line in f: + if re.match(r'\d+:[\w=]+:/docker(-[ce]e)?/\w+', line): + log.debug(u'Running in a docker container') + app.RUNS_IN_DOCKER = True + return True + return False + except (EnvironmentError, OSError) as error: + log.info(u'Tried to check the path {path} if we are running in a docker container, ' + u'but an error occurred: {error}', {'path': path, 'error': error}) + return False diff --git a/medusa/version_checker.py b/medusa/version_checker.py deleted file mode 100644 index a53cf8d3d8..0000000000 --- a/medusa/version_checker.py +++ /dev/null @@ -1,1048 +0,0 @@ -# coding=utf-8 -# Author: Nic Wolfe -# -# This file is part of Medusa. -# -# Medusa is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Medusa 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Medusa. If not, see . -from __future__ import unicode_literals - -import datetime -import logging -import os -import platform -import re -import shutil -import stat -import subprocess -import tarfile -import time -from builtins import object -from builtins import str -from logging import DEBUG, WARNING - -from github import GithubException - -from medusa import app, db, helpers, notifiers, ui -from medusa.github_client import get_github_repo -from medusa.logger.adapters.style import BraceAdapter -from medusa.session.core import MedusaSafeSession - - -ERROR_MESSAGE = ('Unable to find your git executable. Set git executable path in Advanced Settings ' - 'OR shutdown application and delete your .git folder and run from source to enable updates.') - -log = BraceAdapter(logging.getLogger(__name__)) -log.logger.addHandler(logging.NullHandler()) - - -class CheckVersion(object): - """Version check class meant to run as a thread object with the sr scheduler.""" - - def __init__(self): - self.updater = None - self.install_type = None - self.amActive = False - self.install_type = self.find_install_type() - if self.install_type == 'git': - self.updater = GitUpdateManager() - elif self.install_type == 'source': - self.updater = SourceUpdateManager() - - self.session = MedusaSafeSession() - - def run(self, force=False): - - self.amActive = True - - # Update remote branches and store in app.GIT_REMOTE_BRANCHES - self.list_remote_branches() - - if self.updater: - # set current branch version - app.BRANCH = self.get_branch() - - if self.check_for_new_version(force): - if app.AUTO_UPDATE: - log.info(u'New update found, starting auto-updater ...') - ui.notifications.message('New update found, starting auto-updater') - if self.run_backup_if_safe(): - if app.version_check_scheduler.action.update(): - log.info(u'Update was successful!') - ui.notifications.message('Update was successful') - app.events.put(app.events.SystemEvent.RESTART) - else: - log.info(u'Update failed!') - ui.notifications.message('Update failed!') - - self.check_for_new_news(force) - - self.amActive = False - - def run_backup_if_safe(self): - return self.safe_to_update() is True and self._runbackup() is True - - def _runbackup(self): - # Do a system backup before update - log.info(u'Config backup in progress...') - ui.notifications.message('Backup', 'Config backup in progress...') - try: - backupDir = os.path.join(app.DATA_DIR, app.BACKUP_DIR) - if not os.path.isdir(backupDir): - os.mkdir(backupDir) - - if self._keeplatestbackup(backupDir) and self._backup(backupDir): - log.info(u'Config backup successful, updating...') - ui.notifications.message('Backup', 'Config backup successful, updating...') - return True - else: - log.warning(u'Config backup failed, aborting update') - ui.notifications.message('Backup', 'Config backup failed, aborting update') - return False - except Exception as e: - log.error(u'Update: Config backup failed. Error: {0!r}', e) - ui.notifications.message('Backup', 'Config backup failed, aborting update') - return False - - @staticmethod - def _keeplatestbackup(backupDir=None): - if not backupDir: - return False - - import glob - files = glob.glob(os.path.join(backupDir, '*.zip')) - if not files: - return True - - now = time.time() - newest = files[0], now - os.path.getctime(files[0]) - for f in files[1:]: - age = now - os.path.getctime(f) - if age < newest[1]: - newest = f, age - files.remove(newest[0]) - - for f in files: - os.remove(f) - - return True - - # TODO: Merge with backup in helpers - @staticmethod - def _backup(backupDir=None): - if not backupDir: - return False - source = [ - os.path.join(app.DATA_DIR, app.APPLICATION_DB), - app.CONFIG_FILE, - os.path.join(app.DATA_DIR, app.FAILED_DB), - os.path.join(app.DATA_DIR, app.CACHE_DB) - ] - target = os.path.join(backupDir, app.BACKUP_FILENAME.format(timestamp=time.strftime('%Y%m%d%H%M%S'))) - - for (path, dirs, files) in os.walk(app.CACHE_DIR, topdown=True): - for dirname in dirs: - if path == app.CACHE_DIR and dirname not in ['images']: - dirs.remove(dirname) - for filename in files: - source.append(os.path.join(path, filename)) - - return helpers.backup_config_zip(source, target, app.DATA_DIR) - - def safe_to_update(self): - - def db_safe(self): - message = { - 'equal': { - 'type': DEBUG, - 'text': u'We can proceed with the update. New update has same DB version'}, - 'upgrade': { - 'type': WARNING, - 'text': u"We can't proceed with the update. New update has a new DB version. Please manually update"}, - 'downgrade': { - 'type': WARNING, - 'text': u"We can't proceed with the update. New update has a old DB version. It's not possible to downgrade"}, - } - try: - result = self.getDBcompare() - if result in message: - log.log(message[result]['type'], message[result]['text']) # unpack the result message into a log entry - else: - log.warning(u"We can't proceed with the update. Unable to check remote DB version. Error: {0}", result) - return result in ['equal'] # add future True results to the list - except Exception as error: - log.error(u"We can't proceed with the update. Unable to compare DB version. Error: {0!r}", error) - return False - - def postprocessor_safe(): - if not app.auto_post_processor_scheduler.action.amActive: - log.debug(u'We can proceed with the update. Post-Processor is not running') - return True - else: - log.debug(u"We can't proceed with the update. Post-Processor is running") - return False - - def showupdate_safe(): - if not app.show_update_scheduler.action.amActive: - log.debug(u'We can proceed with the update. Shows are not being updated') - return True - else: - log.debug(u"We can't proceed with the update. Shows are being updated") - return False - - db_safe = db_safe(self) - postprocessor_safe = postprocessor_safe() - showupdate_safe = showupdate_safe() - - if db_safe and postprocessor_safe and showupdate_safe: - log.debug(u'Proceeding with auto update') - return True - else: - log.debug(u'Auto update aborted') - return False - - def getDBcompare(self): - """ - Compare the current DB version with the new branch version. - - :return: 'upgrade', 'equal', or 'downgrade' - """ - try: - self.updater.need_update() - cur_hash = str(self.updater.get_newest_commit_hash()) - assert len(cur_hash) == 40, 'Commit hash wrong length: {length} hash: {hash}'.format( - length=len(cur_hash), hash=cur_hash) - - check_url = 'http://rawcdn.githack.com/{org}/{repo}/{commit}/medusa/databases/main_db.py'.format( - org=app.GIT_ORG, repo=app.GIT_REPO, commit=cur_hash) - response = self.session.get(check_url) - - # Get remote DB version - match_max_db = re.search(r'MAX_DB_VERSION\s*=\s*(?P\d{2,3})', response.text) - new_branch_major_db_version = int(match_max_db.group('version')) if match_max_db else None - match_minor_db = re.search(r'CURRENT_MINOR_DB_VERSION\s*=\s*(?P\d{1,2})', response.text) - new_branch_min_db_version = int(match_minor_db.group('version')) if match_minor_db else None - - # Check local DB version - main_db_con = db.DBConnection() - cur_branch_major_db_version, cur_branch_minor_db_version = main_db_con.checkDBVersion() - - if any([cur_branch_major_db_version is None, cur_branch_minor_db_version is None, - new_branch_major_db_version is None, new_branch_min_db_version is None]): - return 'Could not compare database versions, aborting' - - if new_branch_major_db_version > cur_branch_major_db_version: - return 'upgrade' - elif new_branch_major_db_version == cur_branch_major_db_version: - if new_branch_min_db_version < cur_branch_minor_db_version: - return 'downgrade' - elif new_branch_min_db_version > cur_branch_minor_db_version: - return 'upgrade' - return 'equal' - else: - return 'downgrade' - except Exception as e: - return repr(e) - - @staticmethod - def find_install_type(): - """ - Determines how this copy of sr was installed. - - :return: type of installation. Possible values are: - 'win': any compiled windows build - 'git': running from source using git - 'source': running from source without git - """ - # check if we're a windows build - if app.BRANCH.startswith('build '): - install_type = 'win' - elif os.path.isdir(os.path.join(app.PROG_DIR, u'.git')): - install_type = 'git' - else: - install_type = 'source' - - return install_type - - def check_for_new_version(self, force=False): - """ - Check the internet for a newer version. - - :force: if true the VERSION_NOTIFY setting will be ignored and a check will be forced - :return: bool, True for new version or False for no new version. - """ - if not self.updater or (not app.VERSION_NOTIFY and not app.AUTO_UPDATE and not force): - log.info(u'Version checking is disabled, not checking for the newest version') - app.NEWEST_VERSION_STRING = None - return False - - # checking for updates - if not app.AUTO_UPDATE: - log.info(u'Checking for updates using {0}', self.install_type.upper()) - - if not self.updater.need_update(): - app.NEWEST_VERSION_STRING = None - - if force: - ui.notifications.message('No update needed') - log.info(u'No update needed') - - # no updates needed - return False - - # found updates - self.updater.set_newest_text() - return self.updater.can_update() - - def check_for_new_news(self, force=False): - """ - Check GitHub for the latest news. - - :return: unicode, a copy of the news - :force: ignored - """ - # Grab a copy of the news - log.debug(u'Checking GitHub for latest news.') - response = self.session.get(app.NEWS_URL) - if not response or not response.text: - log.debug(u'Could not load news from URL: {0}', app.NEWS_URL) - return - - try: - last_read = datetime.datetime.strptime(app.NEWS_LAST_READ, '%Y-%m-%d') - except ValueError: - log.warning(u'Invalid news last read date: {0}', app.NEWS_LAST_READ) - last_read = 0 - - news = response.text - app.NEWS_UNREAD = 0 - got_latest = False - for match in re.finditer(r'^####\s*(\d{4}-\d{2}-\d{2})\s*####', news, re.M): - if not got_latest: - got_latest = True - app.NEWS_LATEST = match.group(1) - - try: - if datetime.datetime.strptime(match.group(1), '%Y-%m-%d') > last_read: - app.NEWS_UNREAD += 1 - except ValueError: - log.warning(u'Unable to match latest news date. Repository news date: {0}', match.group(1)) - pass - - return news - - def need_update(self): - if self.updater: - return self.updater.need_update() - - def update(self): - if self.updater: - # update branch with current config branch value - self.updater.branch = app.BRANCH - - # check for updates - if self.updater.need_update(): - return self.updater.update() - - def list_remote_branches(self): - if self.updater: - app.GIT_REMOTE_BRANCHES = self.updater.list_remote_branches() - return app.GIT_REMOTE_BRANCHES - - def get_branch(self): - if self.updater: - return self.updater.branch - - -class UpdateManager(object): - def __init__(self): - """Update manager initialization.""" - # Initialize the app.RUNS_IN_DOCKER variable - self.runs_in_docker() - - @staticmethod - def get_github_org(): - return app.GIT_ORG - - @staticmethod - def get_github_repo(): - return app.GIT_REPO - - @staticmethod - def get_update_url(): - return app.WEB_ROOT + '/home/update/?pid=' + str(app.PID) - - @staticmethod - def runs_in_docker(): - """ - Check if Medusa is run in a docker container. - - If run in a container, we don't want to use the auto update feature, but just want to inform the user - there is an update available. The user can update through getting the latest docker tag. - """ - if app.RUNS_IN_DOCKER is not None: - return app.RUNS_IN_DOCKER - - path = '/proc/{pid}/cgroup'.format(pid=os.getpid()) - try: - if not os.path.isfile(path): - return False - - with open(path) as f: - for line in f: - if re.match(r'\d+:[\w=]+:/docker(-[ce]e)?/\w+', line): - log.debug(u'Running in a docker container') - app.RUNS_IN_DOCKER = True - return True - return False - except (EnvironmentError, OSError) as error: - log.info(u'Tried to check the path {path} if we are running in a docker container, ' - u'but an error occurred: {error}', {'path': path, 'error': error}) - return False - - def set_newest_text_docker(self): - """ - Set an alternative update text, when running in a docker container. - - This method is used by the GitUpdateMananager and the SourceUpdateManager. Both should not auto update from - within the container. - """ - if app.RUNS_IN_DOCKER and (not self._cur_commit_hash or self._num_commits_behind > 0): - log.debug(u'There is an update available, Medusa is running in a docker container, so auto updating is disabled.') - app.NEWEST_VERSION_STRING = 'There is an update available: please pull the latest docker image, ' \ - 'and rebuild your container to update' - return True - return False - - -class GitUpdateManager(UpdateManager): - def __init__(self): - super(GitUpdateManager, self).__init__() - self._git_path = self._find_working_git() - self.github_org = self.get_github_org() - self.github_repo = self.get_github_repo() - - self.branch = app.BRANCH = self._find_installed_branch() - - self._cur_commit_hash = None - self._newest_commit_hash = None - self._num_commits_behind = 0 - self._num_commits_ahead = 0 - self._cur_version = '' - - def get_cur_commit_hash(self): - return self._cur_commit_hash - - def get_newest_commit_hash(self): - return self._newest_commit_hash - - def get_cur_version(self): - if self._cur_commit_hash or self._find_installed_version(): - self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format(self._cur_commit_hash))[0] - return self._cur_version - - def get_newest_version(self): - if self._newest_commit_hash: - self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 ' + self._newest_commit_hash)[0] - else: - self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 ' + self._cur_commit_hash)[0] - return self._cur_version - - def get_num_commits_behind(self): - return self._num_commits_behind - - def get_num_commits_ahead(self): - return self._num_commits_ahead - - def _find_working_git(self): - test_cmd = 'version' - - if app.GIT_PATH: - main_git = '"' + app.GIT_PATH + '"' - else: - main_git = 'git' - - log.debug(u'Checking if we can use git commands: {0} {1}', main_git, test_cmd) - _, _, exit_status = self._run_git(main_git, test_cmd) - - if exit_status == 0: - log.debug(u'Using: {0}', main_git) - return main_git - else: - log.debug(u'Not using: {0}', main_git) - - # trying alternatives - - alternative_git = [] - - # osx people who start sr from launchd have a broken path, so try a hail-mary attempt for them - if platform.system().lower() == 'darwin': - alternative_git.append('/usr/local/git/bin/git') - - if platform.system().lower() == 'windows': - if main_git != main_git.lower(): - alternative_git.append(main_git.lower()) - - if alternative_git: - log.debug(u'Trying known alternative git locations') - - for cur_git in alternative_git: - log.debug(u'Checking if we can use git commands: {0} {1}', cur_git, test_cmd) - _, _, exit_status = self._run_git(cur_git, test_cmd) - - if exit_status == 0: - log.debug(u'Using: {0}', cur_git) - return cur_git - else: - log.debug(u'Not using: {0}', cur_git) - - # Still haven't found a working git - # Warn user only if he has version check enabled - if app.VERSION_NOTIFY: - app.NEWEST_VERSION_STRING = ERROR_MESSAGE - - return None - - @staticmethod - def _run_git(git_path, args): - - output = err = exit_status = None - - if not git_path: - log.warning(u"No git specified, can't use git commands") - app.NEWEST_VERSION_STRING = ERROR_MESSAGE - exit_status = 1 - return output, err, exit_status - - # If we have a valid git remove the git warning - # String will be updated as soon we check github - app.NEWEST_VERSION_STRING = None - - cmd = git_path + ' ' + args - - try: - log.debug(u'Executing {cmd} with your shell in {dir}', {'cmd': cmd, 'dir': app.PROG_DIR}) - p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - shell=True, cwd=app.PROG_DIR) - output, err = p.communicate() - exit_status = p.returncode - - # Convert bytes to string in python3 - if isinstance(output, (bytes, bytearray)): - output = output.decode('utf-8') - - if output: - output = output.strip() - - except OSError: - log.info(u"Command {cmd} didn't work", {'cmd': cmd}) - exit_status = 1 - - if exit_status == 0: - log.debug(u'{cmd} : returned successful', {'cmd': cmd}) - exit_status = 0 - - elif exit_status == 1: - if output: - if 'stash' in output: - log.warning(u"Enable 'git reset' in settings or stash your changes in local files") - else: - log.warning(u'{cmd} returned : {output}', {'cmd': cmd, 'output': output}) - else: - log.warning(u'{cmd} returned no data', {'cmd': cmd}) - exit_status = 1 - - elif exit_status == 128 or 'fatal:' in output or err: - log.warning(u'{cmd} returned : {output}', {'cmd': cmd, 'output': output}) - exit_status = 128 - - else: - log.warning(u'{cmd} returned : {output}. Treat as error for now', {'cmd': cmd, 'output': output}) - exit_status = 1 - - return output, err, exit_status - - def _find_installed_version(self): - """Attempt to find the currently installed version of the application. - - Uses git show to get commit version. - - :return: True for success or False for failure - """ - output, _, exit_status = self._run_git(self._git_path, 'rev-parse HEAD') # @UnusedVariable - - if exit_status == 0 and output: - cur_commit_hash = output.strip() - if not re.match('^[a-z0-9]+$', cur_commit_hash): - log.warning(u"Output doesn't look like a hash, not using it") - return False - self._cur_commit_hash = cur_commit_hash - app.CUR_COMMIT_HASH = str(cur_commit_hash) - return True - else: - return False - - def _find_installed_branch(self): - branch_info, _, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD') # @UnusedVariable - if exit_status == 0 and branch_info: - branch = branch_info.strip().replace('refs/heads/', '', 1) - if branch: - app.BRANCH = branch - return branch - return '' - - def _check_github_for_update(self): - """ - Uses git commands to check if there is a newer version that the provided - commit hash. If there is a newer version it sets _num_commits_behind. - """ - - self._num_commits_behind = 0 - self._num_commits_ahead = 0 - - # update remote origin url - self.update_remote_origin() - - # get all new info from github - output, _, exit_status = self._run_git(self._git_path, 'fetch --prune %s' % app.GIT_REMOTE) - if not exit_status == 0: - log.warning(u"Unable to contact github, can't check for update") - return - - # get latest commit_hash from remote - output, _, exit_status = self._run_git(self._git_path, 'rev-parse --verify --quiet "@{upstream}"') - - if exit_status == 0 and output: - cur_commit_hash = output.strip() - - if not re.match('^[a-z0-9]+$', cur_commit_hash): - log.debug(u"Output doesn't look like a hash, not using it") - return - - else: - self._newest_commit_hash = cur_commit_hash - else: - log.debug(u"git didn't return newest commit hash") - return - - # get number of commits behind and ahead (option --count not supported git < 1.7.2) - output, _, exit_status = self._run_git(self._git_path, 'rev-list --left-right "@{upstream}"...HEAD') - if exit_status == 0 and output: - - try: - self._num_commits_behind = int(output.count('<')) - self._num_commits_ahead = int(output.count('>')) - - except Exception: - log.debug(u"git didn't return numbers for behind and ahead, not using it") - return - - log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}, num_commits_ahead = {3}', - self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind, self._num_commits_ahead) - - def set_newest_text(self): - - # if we're up to date then don't set this - app.NEWEST_VERSION_STRING = None - - if self.set_newest_text_docker(): - return - - if self._num_commits_behind > 0 or self._is_hard_reset_allowed(): - - base_url = 'http://github.com/' + self.github_org + '/' + self.github_repo - if self._newest_commit_hash: - url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash - else: - url = base_url + '/commits/' - - newest_text = 'There is a newer version available ' - newest_text += " (you're " + str(self._num_commits_behind) + ' commit' - if self._num_commits_behind > 1: - newest_text += 's' - newest_text += ' behind' - if self._num_commits_ahead > 0: - newest_text += ' and {ahead} commit{s} ahead'.format(ahead=self._num_commits_ahead, - s='s' if self._num_commits_ahead > 1 else '') - newest_text += ') — Update Now' - - elif self._num_commits_ahead > 0: - newest_text = u'Local branch is ahead of {0}. Automatic update not possible'.format(self.branch) - log.warning(newest_text) - - else: - return - - app.NEWEST_VERSION_STRING = newest_text - - def need_update(self): - - if self.branch != self._find_installed_branch(): - log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) - return True - - self._find_installed_version() - if not self._cur_commit_hash: - return True - else: - try: - self._check_github_for_update() - except Exception as e: - log.warning(u"Unable to contact github, can't check for update: {0!r}", e) - return False - - if self._num_commits_behind > 0 or self._num_commits_ahead > 0: - return True - - return False - - def can_update(self): - """Return whether update can be executed. - - :return: - :rtype: bool - """ - return self._num_commits_ahead <= 0 or self._is_hard_reset_allowed() - - def update(self): - """Call git pull origin in order to update the application. - - Returns a bool depending on the call's success. - """ - # update remote origin url - self.update_remote_origin() - - # remove untracked files and performs a hard reset on git branch to avoid update issues - if self._is_hard_reset_allowed(): - self.reset() - - # Executing git clean before updating - self.clean() - - if self.branch == self._find_installed_branch(): - _, _, exit_status = self._run_git(self._git_path, 'pull -f %s %s' % (app.GIT_REMOTE, self.branch)) # @UnusedVariable - else: - _, _, exit_status = self._run_git(self._git_path, 'checkout -f ' + self.branch) # @UnusedVariable - - # Executing git clean after updating - self.clean() - - if exit_status == 0: - self._find_installed_version() - # Notify update successful - if app.NOTIFY_ON_UPDATE: - try: - notifiers.notify_git_update(app.CUR_COMMIT_HASH or '') - except Exception: - log.debug(u'Unable to send update notification. Continuing the update process') - return True - - else: - return False - - @staticmethod - def _is_hard_reset_allowed(): - """Return whether git hard reset is allowed or not. - - :return: - :rtype: bool - """ - return app.GIT_RESET and (not app.GIT_RESET_BRANCHES or - app.BRANCH in app.GIT_RESET_BRANCHES) - - def clean(self): - """Call git clean to remove all untracked files. - - It only affects source folders and libX and extX folders, - to prevent deleting untracked user data not known by .gitignore - - :return: - :rtype: int - """ - # Fixes: goo.gl/tr8Awf - to be removed in the next release - root_dir = os.path.basename(app.PROG_DIR) - helper_folder = os.path.join(root_dir, 'helper') - helpers_folder = os.path.join(root_dir, 'helpers') - - folders = (app.LIB_FOLDER, app.LIB2_FOLDER, app.LIB3_FOLDER, app.EXT_FOLDER, - app.EXT2_FOLDER, app.EXT3_FOLDER, app.SRC_FOLDER, app.STATIC_FOLDER, - helper_folder, helpers_folder) + app.LEGACY_SRC_FOLDERS - _, _, exit_status = self._run_git(self._git_path, 'clean -d -f -x {0}'.format(' '.join(folders))) - - return exit_status - - def reset(self): - """ - Calls git reset --hard to perform a hard reset. Returns a bool depending - on the call's success. - """ - _, _, exit_status = self._run_git(self._git_path, 'reset --hard {0}/{1}'.format - (app.GIT_REMOTE, app.BRANCH)) # @UnusedVariable - if exit_status == 0: - return True - - def list_remote_branches(self): - # update remote origin url - self.update_remote_origin() - app.BRANCH = self._find_installed_branch() - - branches, _, exit_status = self._run_git(self._git_path, 'ls-remote --heads %s' % app.GIT_REMOTE) # @UnusedVariable - if exit_status == 0 and branches: - if branches: - return re.findall(r'refs/heads/(.*)', branches) - return [] - - def update_remote_origin(self): - self._run_git(self._git_path, 'config remote.%s.url %s' % (app.GIT_REMOTE, app.GIT_REMOTE_URL)) - self._run_git(self._git_path, 'config remote.%s.pushurl %s' % (app.GIT_REMOTE, app.GIT_REMOTE_URL)) - - -class SourceUpdateManager(UpdateManager): - def __init__(self): - super(SourceUpdateManager, self).__init__() - self.github_org = self.get_github_org() - self.github_repo = self.get_github_repo() - - self.branch = app.BRANCH - if app.BRANCH == '': - self.branch = self._find_installed_branch() - - self._cur_commit_hash = app.CUR_COMMIT_HASH - self._newest_commit_hash = None - self._num_commits_behind = 0 - self._num_commits_ahead = 0 - - self.session = MedusaSafeSession() - - @staticmethod - def _find_installed_branch(): - return app.CUR_COMMIT_BRANCH if app.CUR_COMMIT_BRANCH else 'master' - - def get_cur_commit_hash(self): - return self._cur_commit_hash - - def get_newest_commit_hash(self): - return self._newest_commit_hash - - @staticmethod - def get_cur_version(): - return '' - - @staticmethod - def get_newest_version(): - return '' - - def get_num_commits_behind(self): - return self._num_commits_behind - - def get_num_commits_ahead(self): - return self._num_commits_ahead - - def need_update(self): - # need this to run first to set self._newest_commit_hash - try: - self._check_github_for_update() - except Exception as error: - log.warning(u"Unable to contact github, can't check for update: {0!r}", error) - return False - - if self.branch != self._find_installed_branch(): - log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) - return True - - if not self._cur_commit_hash or self._num_commits_behind > 0 or self._num_commits_ahead > 0: - return True - - return False - - def can_update(self): - """Whether or not the update can be performed. - - :return: - :rtype: bool - """ - return True - - def _check_github_for_update(self): - """Use pygithub to ask github if there is a newer version.. - - If there is a newer version it sets application's version text. - - commit_hash: hash that we're checking against - """ - - self._num_commits_behind = 0 - self._newest_commit_hash = None - - gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) - # try to get newest commit hash and commits behind directly by comparing branch and current commit - if self._cur_commit_hash: - try: - branch_compared = gh.compare(base=self.branch, head=self._cur_commit_hash) - self._newest_commit_hash = branch_compared.base_commit.sha - self._num_commits_behind = branch_compared.behind_by - self._num_commits_ahead = branch_compared.ahead_by - except Exception: # UnknownObjectException - self._newest_commit_hash = '' - self._num_commits_behind = 0 - self._num_commits_ahead = 0 - self._cur_commit_hash = '' - - # fall back and iterate over last 100 (items per page in gh_api) commits - if not self._newest_commit_hash: - - for curCommit in gh.get_commits(): - if not self._newest_commit_hash: - self._newest_commit_hash = curCommit.sha - if not self._cur_commit_hash: - break - - if curCommit.sha == self._cur_commit_hash: - break - - # when _cur_commit_hash doesn't match anything _num_commits_behind == 100 - self._num_commits_behind += 1 - - log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}', - self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind) - - def set_newest_text(self): - - # if we're up to date then don't set this - app.NEWEST_VERSION_STRING = None - - if self.set_newest_text_docker(): - return - - if not self._cur_commit_hash: - log.debug(u"Unknown current version number, don't know if we should update or not") - - newest_text = "Unknown current version number: If you've never used the application " \ - 'upgrade system before then current version is not set. ' \ - '— Update Now' - - elif self._num_commits_behind > 0: - base_url = 'http://github.com/' + self.github_org + '/' + self.github_repo - if self._newest_commit_hash: - url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash - else: - url = base_url + '/commits/' - - newest_text = 'There is a newer version available' - newest_text += " (you're " + str(self._num_commits_behind) + ' commit' - if self._num_commits_behind > 1: - newest_text += 's' - newest_text += ' behind) — Update Now' - else: - return - - app.NEWEST_VERSION_STRING = newest_text - - def update(self): - """ - Downloads the latest source tarball from github and installs it over the existing version. - """ - - tar_download_url = 'http://github.com/' + self.github_org + '/' + self.github_repo + '/tarball/' + self.branch - - try: - # prepare the update dir - app_update_dir = os.path.join(app.PROG_DIR, u'sr-update') - - if os.path.isdir(app_update_dir): - log.info(u'Clearing out update folder {0!r} before extracting', app_update_dir) - shutil.rmtree(app_update_dir) - - log.info(u'Clearing update folder {0!r} before extracting', app_update_dir) - os.makedirs(app_update_dir) - - # retrieve file - log.info(u'Downloading update from {0!r}', tar_download_url) - tar_download_path = os.path.join(app_update_dir, u'sr-update.tar') - helpers.download_file(tar_download_url, tar_download_path, session=self.session) - - if not os.path.isfile(tar_download_path): - log.warning(u"Unable to retrieve new version from {0!r}, can't update", tar_download_url) - return False - - if not tarfile.is_tarfile(tar_download_path): - log.warning(u"Retrieved version from {0!r} is corrupt, can't update", tar_download_url) - return False - - # extract to sr-update dir - log.info(u'Extracting file {0}', tar_download_path) - tar = tarfile.open(tar_download_path) - tar.extractall(app_update_dir) - tar.close() - - # delete .tar.gz - log.info(u'Deleting file {0}', tar_download_path) - os.remove(tar_download_path) - - # find update dir name - update_dir_contents = [x for x in os.listdir(app_update_dir) if - os.path.isdir(os.path.join(app_update_dir, x))] - if len(update_dir_contents) != 1: - log.warning(u'Invalid update data, update failed: {0}', update_dir_contents) - return False - content_dir = os.path.join(app_update_dir, update_dir_contents[0]) - - # walk temp folder and move files to main folder - log.info(u'Moving files from {0} to {1}', content_dir, app.PROG_DIR) - for dirname, _, filenames in os.walk(content_dir): # @UnusedVariable - dirname = dirname[len(content_dir) + 1:] - for curfile in filenames: - old_path = os.path.join(content_dir, dirname, curfile) - new_path = os.path.join(app.PROG_DIR, dirname, curfile) - - # Avoid DLL access problem on WIN32/64 - # These files needing to be updated manually - # or find a way to kill the access from memory - extension = os.path.splitext(curfile)[1] - if extension == '.dll': - try: - log.debug(u'Special handling for {0}', curfile) - os.chmod(new_path, stat.S_IWRITE) - os.remove(new_path) - os.renames(old_path, new_path) - except Exception as e: - log.debug(u'Unable to update {0}: {1!r}', new_path, e) - os.remove(old_path) # Trash the updated file without moving in new path - continue - - if os.path.isfile(new_path): - os.remove(new_path) - os.renames(old_path, new_path) - - app.CUR_COMMIT_HASH = self._newest_commit_hash - app.CUR_COMMIT_BRANCH = self.branch - - except Exception as e: - log.exception(u'Error while trying to update: {0}', e) - return False - - # Notify update successful - try: - notifiers.notify_git_update(app.CUR_COMMIT_HASH or '') - except Exception: - log.debug(u'Unable to send update notification. Continuing the update process') - return True - - @staticmethod - def list_remote_branches(): - try: - gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) - return [x.name for x in gh.get_branches() if x] - except GithubException as error: - log.warning(u"Unable to contact github, can't check for update: {0!r}", error) - return [] From b543866e781b76b66bbaa21637971737ff9296e0 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 18 Jan 2019 20:16:38 +0100 Subject: [PATCH 02/13] Final changes --- medusa/__main__.py | 2 +- medusa/github_client.py | 18 +++ medusa/server/api/v1/core.py | 2 +- medusa/updater/docker_updater.py | 222 ++---------------------------- medusa/updater/github_updater.py | 120 ++++++++-------- medusa/updater/source_updater.py | 78 ++++++----- medusa/updater/update_manager.py | 7 + medusa/updater/version_checker.py | 1 - 8 files changed, 135 insertions(+), 315 deletions(-) diff --git a/medusa/__main__.py b/medusa/__main__.py index a6667da0ed..6a103b2b1e 100755 --- a/medusa/__main__.py +++ b/medusa/__main__.py @@ -990,7 +990,7 @@ def initialize(self, console_logging=True): if app.VERSION_NOTIFY: updater = CheckVersion().updater if updater: - app.APP_VERSION = updater.get_cur_version() + app.APP_VERSION = updater.current_version app.MAJOR_DB_VERSION, app.MINOR_DB_VERSION = db.DBConnection().checkDBVersion() diff --git a/medusa/github_client.py b/medusa/github_client.py index 106aa8d126..0ff05f961a 100644 --- a/medusa/github_client.py +++ b/medusa/github_client.py @@ -102,3 +102,21 @@ def get_github_repo(organization, repo, gh=None): except github.GithubException as e: logger.debug('Unable to contact Github: {ex!r}', ex=e) raise + + +def get_latest_release(organization, repo, gh=None): + """Return the latest release of a repository. + + :param repo: + :type repo: string + :param gh: + :type gh: Github + :return: + :rtype github.GitRelease.GitRelease + """ + try: + gh = gh or github.MainClass.Github(**OPTIONS) + return gh.get_organization(organization).get_repo(repo).get_latest_release() + except github.GithubException as e: + logger.debug('Unable to contact Github: {ex!r}', ex=e) + raise diff --git a/medusa/server/api/v1/core.py b/medusa/server/api/v1/core.py index ce1572e7f3..1f6cc31a47 100644 --- a/medusa/server/api/v1/core.py +++ b/medusa/server/api/v1/core.py @@ -1421,7 +1421,7 @@ def run(self): 'commit': check_version.updater.get_newest_commit_hash(), 'version': check_version.updater.get_newest_version(), }, - 'commits_offset': check_version.updater.get_num_commits_behind(), + 'commits_offset': check_version.updater.commits_behind, 'needs_update': needs_update, } diff --git a/medusa/updater/docker_updater.py b/medusa/updater/docker_updater.py index 95baffc8c1..58c3629de3 100644 --- a/medusa/updater/docker_updater.py +++ b/medusa/updater/docker_updater.py @@ -3,81 +3,19 @@ from __future__ import unicode_literals import logging -import os -import shutil -import stat -import tarfile -from github import GithubException - -from medusa import app, helpers, notifiers -from medusa.github_client import get_github_repo +from medusa import app from medusa.logger.adapters.style import BraceAdapter -from medusa.session.core import MedusaSafeSession -from medusa.updater.update_manager import UpdateManager +from medusa.updater.source_updater import SourceUpdateManager log = BraceAdapter(logging.getLogger(__name__)) log.logger.addHandler(logging.NullHandler()) -class DockerUpdateManager(UpdateManager): +class DockerUpdateManager(SourceUpdateManager): def __init__(self): super(DockerUpdateManager, self).__init__() - self.github_org = self.get_github_org() - self.github_repo = self.get_github_repo() - - self.branch = app.BRANCH - if app.BRANCH == '': - self.branch = self._find_installed_branch() - - self._cur_commit_hash = app.CUR_COMMIT_HASH - self._newest_commit_hash = None - self._num_commits_behind = 0 - self._num_commits_ahead = 0 - - self.session = MedusaSafeSession() - - @staticmethod - def _find_installed_branch(): - return app.CUR_COMMIT_BRANCH if app.CUR_COMMIT_BRANCH else 'master' - - def get_cur_commit_hash(self): - return self._cur_commit_hash - - def get_newest_commit_hash(self): - return self._newest_commit_hash - - @staticmethod - def get_cur_version(): - return '' - - @staticmethod - def get_newest_version(): - return '' - - def get_num_commits_behind(self): - return self._num_commits_behind - - def get_num_commits_ahead(self): - return self._num_commits_ahead - - def need_update(self): - # need this to run first to set self._newest_commit_hash - try: - self._check_github_for_update() - except Exception as error: - log.warning(u"Unable to contact github, can't check for update: {0!r}", error) - return False - - if self.branch != self._find_installed_branch(): - log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) - return True - - if not self._cur_commit_hash or self._num_commits_behind > 0 or self._num_commits_ahead > 0: - return True - - return False def can_update(self): """Whether or not the update can be performed. @@ -85,155 +23,17 @@ def can_update(self): :return: :rtype: bool """ - return True - - def _check_github_for_update(self): - """Use pygithub to ask github if there is a newer version.. - - If there is a newer version it sets application's version text. - - commit_hash: hash that we're checking against - """ - - self._num_commits_behind = 0 - self._newest_commit_hash = None - - gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) - # try to get newest commit hash and commits behind directly by comparing branch and current commit - if self._cur_commit_hash: - try: - branch_compared = gh.compare(base=self.branch, head=self._cur_commit_hash) - self._newest_commit_hash = branch_compared.base_commit.sha - self._num_commits_behind = branch_compared.behind_by - self._num_commits_ahead = branch_compared.ahead_by - except Exception: # UnknownObjectException - self._newest_commit_hash = '' - self._num_commits_behind = 0 - self._num_commits_ahead = 0 - self._cur_commit_hash = '' - - # fall back and iterate over last 100 (items per page in gh_api) commits - if not self._newest_commit_hash: - - for curCommit in gh.get_commits(): - if not self._newest_commit_hash: - self._newest_commit_hash = curCommit.sha - if not self._cur_commit_hash: - break - - if curCommit.sha == self._cur_commit_hash: - break - - # when _cur_commit_hash doesn't match anything _num_commits_behind == 100 - self._num_commits_behind += 1 - - log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}', - self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind) + return False - def set_newest_text(self): + def _set_update_text(self): """Set an update text, when running in a docker container.""" - app.NEWEST_VERSION_STRING = None - - if not self._cur_commit_hash or self._num_commits_behind > 0: - log.debug(u'There is an update available, Medusa is running in a docker container, so auto updating is disabled.') - app.NEWEST_VERSION_STRING = 'There is an update available: please pull the latest docker image, ' \ - 'and rebuild your container to update' + log.debug('There is an update available, Medusa is running in a docker container,' + ' so auto updating is disabled.') + app.NEWEST_VERSION_STRING = 'There is an update available: please pull the latest docker image, ' \ + 'and rebuild your container to update' def update(self): """ - Downloads the latest source tarball from github and installs it over the existing version. + Downloads the latest version. """ - - tar_download_url = 'http://github.com/' + self.github_org + '/' + self.github_repo + '/tarball/' + self.branch - - try: - # prepare the update dir - app_update_dir = os.path.join(app.PROG_DIR, u'sr-update') - - if os.path.isdir(app_update_dir): - log.info(u'Clearing out update folder {0!r} before extracting', app_update_dir) - shutil.rmtree(app_update_dir) - - log.info(u'Clearing update folder {0!r} before extracting', app_update_dir) - os.makedirs(app_update_dir) - - # retrieve file - log.info(u'Downloading update from {0!r}', tar_download_url) - tar_download_path = os.path.join(app_update_dir, u'sr-update.tar') - helpers.download_file(tar_download_url, tar_download_path, session=self.session) - - if not os.path.isfile(tar_download_path): - log.warning(u"Unable to retrieve new version from {0!r}, can't update", tar_download_url) - return False - - if not tarfile.is_tarfile(tar_download_path): - log.warning(u"Retrieved version from {0!r} is corrupt, can't update", tar_download_url) - return False - - # extract to sr-update dir - log.info(u'Extracting file {0}', tar_download_path) - tar = tarfile.open(tar_download_path) - tar.extractall(app_update_dir) - tar.close() - - # delete .tar.gz - log.info(u'Deleting file {0}', tar_download_path) - os.remove(tar_download_path) - - # find update dir name - update_dir_contents = [x for x in os.listdir(app_update_dir) if - os.path.isdir(os.path.join(app_update_dir, x))] - if len(update_dir_contents) != 1: - log.warning(u'Invalid update data, update failed: {0}', update_dir_contents) - return False - content_dir = os.path.join(app_update_dir, update_dir_contents[0]) - - # walk temp folder and move files to main folder - log.info(u'Moving files from {0} to {1}', content_dir, app.PROG_DIR) - for dirname, _, filenames in os.walk(content_dir): # @UnusedVariable - dirname = dirname[len(content_dir) + 1:] - for curfile in filenames: - old_path = os.path.join(content_dir, dirname, curfile) - new_path = os.path.join(app.PROG_DIR, dirname, curfile) - - # Avoid DLL access problem on WIN32/64 - # These files needing to be updated manually - # or find a way to kill the access from memory - extension = os.path.splitext(curfile)[1] - if extension == '.dll': - try: - log.debug(u'Special handling for {0}', curfile) - os.chmod(new_path, stat.S_IWRITE) - os.remove(new_path) - os.renames(old_path, new_path) - except Exception as e: - log.debug(u'Unable to update {0}: {1!r}', new_path, e) - os.remove(old_path) # Trash the updated file without moving in new path - continue - - if os.path.isfile(new_path): - os.remove(new_path) - os.renames(old_path, new_path) - - app.CUR_COMMIT_HASH = self._newest_commit_hash - app.CUR_COMMIT_BRANCH = self.branch - - except Exception as e: - log.exception(u'Error while trying to update: {0}', e) - return False - - # Notify update successful - try: - notifiers.notify_git_update(app.CUR_COMMIT_HASH or '') - except Exception: - log.debug(u'Unable to send update notification. Continuing the update process') - return True - - @staticmethod - def list_remote_branches(): - try: - gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) - return [x.name for x in gh.get_branches() if x] - except GithubException as error: - log.warning(u"Unable to contact github, can't check for update: {0!r}", error) - return [] + return False diff --git a/medusa/updater/github_updater.py b/medusa/updater/github_updater.py index 9b0f471eac..0d71a28a2f 100644 --- a/medusa/updater/github_updater.py +++ b/medusa/updater/github_updater.py @@ -7,12 +7,13 @@ import platform import re import subprocess -from builtins import str from medusa import app, notifiers from medusa.logger.adapters.style import BraceAdapter from medusa.updater.update_manager import UpdateManager +from six import text_type + ERROR_MESSAGE = ('Unable to find your git executable. Set git executable path in Advanced Settings ' 'OR shutdown application and delete your .git folder and run from source to enable updates.') @@ -34,30 +35,35 @@ def __init__(self): self._newest_commit_hash = None self._num_commits_behind = 0 self._num_commits_ahead = 0 - self._cur_version = '' - def get_cur_commit_hash(self): + @property + def current_commit_hash(self): return self._cur_commit_hash - def get_newest_commit_hash(self): + @property + def newest_commit_hash(self): return self._newest_commit_hash - def get_cur_version(self): - if self._cur_commit_hash or self._find_installed_version(): - self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format(self._cur_commit_hash))[0] - return self._cur_version + @property + def current_version(self): + if self._cur_commit_hash or self._set_commit_hash(): + cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format( + self._cur_commit_hash))[0] + return cur_version.lstrip('v') - def get_newest_version(self): + @property + def newest_version(self): if self._newest_commit_hash: - self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 ' + self._newest_commit_hash)[0] - else: - self._cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 ' + self._cur_commit_hash)[0] - return self._cur_version + new_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format( + self._newest_commit_hash))[0] + return new_version.lstrip('v') - def get_num_commits_behind(self): + @property + def commits_behind(self): return self._num_commits_behind - def get_num_commits_ahead(self): + @property + def commits_ahead(self): return self._num_commits_ahead def _find_working_git(self): @@ -78,7 +84,6 @@ def _find_working_git(self): log.debug(u'Not using: {0}', main_git) # trying alternatives - alternative_git = [] # osx people who start sr from launchd have a broken path, so try a hail-mary attempt for them @@ -107,11 +112,8 @@ def _find_working_git(self): if app.VERSION_NOTIFY: app.NEWEST_VERSION_STRING = ERROR_MESSAGE - return None - @staticmethod def _run_git(git_path, args): - output = err = exit_status = None if not git_path: @@ -123,7 +125,6 @@ def _run_git(git_path, args): # If we have a valid git remove the git warning # String will be updated as soon we check github app.NEWEST_VERSION_STRING = None - cmd = git_path + ' ' + args try: @@ -168,28 +169,27 @@ def _run_git(git_path, args): return output, err, exit_status - def _find_installed_version(self): - """Attempt to find the currently installed version of the application. + def _set_commit_hash(self): + """Attempt to set the hash of the currently installed version of the application. - Uses git show to get commit version. - - :return: True for success or False for failure + Uses git to get commit version. """ - output, _, exit_status = self._run_git(self._git_path, 'rev-parse HEAD') # @UnusedVariable + output, _, exit_status = self._run_git(self._git_path, 'rev-parse HEAD') if exit_status == 0 and output: cur_commit_hash = output.strip() if not re.match('^[a-z0-9]+$', cur_commit_hash): log.warning(u"Output doesn't look like a hash, not using it") return False + self._cur_commit_hash = cur_commit_hash - app.CUR_COMMIT_HASH = str(cur_commit_hash) + app.CUR_COMMIT_HASH = cur_commit_hash return True - else: - return False + + return False def _find_installed_branch(self): - branch_info, _, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD') # @UnusedVariable + branch_info, _, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD') if exit_status == 0 and branch_info: branch = branch_info.strip().replace('refs/heads/', '', 1) if branch: @@ -202,7 +202,6 @@ def _check_github_for_update(self): Uses git commands to check if there is a newer version that the provided commit hash. If there is a newer version it sets _num_commits_behind. """ - self._num_commits_behind = 0 self._num_commits_ahead = 0 @@ -246,12 +245,26 @@ def _check_github_for_update(self): log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}, num_commits_ahead = {3}', self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind, self._num_commits_ahead) - def set_newest_text(self): - # if we're up to date then don't set this - app.NEWEST_VERSION_STRING = None + def need_update(self): + if self.branch != self._find_installed_branch(): + log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) + return True - if self._num_commits_behind > 0 or self._is_hard_reset_allowed(): + try: + self._set_commit_hash() + self._check_github_for_update() + except Exception as e: + log.warning(u"Unable to contact github, can't check for update: {0!r}", e) + return False + if self._num_commits_behind > 0 or self._num_commits_ahead > 0: + self._set_update_text() + return True + + return False + + def _set_update_text(self): + if self._num_commits_behind > 0 or self._is_hard_reset_allowed(): base_url = 'http://github.com/' + self.github_org + '/' + self.github_repo if self._newest_commit_hash: url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash @@ -259,7 +272,7 @@ def set_newest_text(self): url = base_url + '/commits/' newest_text = 'There is a newer version available ' - newest_text += " (you're " + str(self._num_commits_behind) + ' commit' + newest_text += " (you're " + text_type(self._num_commits_behind) + ' commit' if self._num_commits_behind > 1: newest_text += 's' newest_text += ' behind' @@ -277,27 +290,6 @@ def set_newest_text(self): app.NEWEST_VERSION_STRING = newest_text - def need_update(self): - - if self.branch != self._find_installed_branch(): - log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) - return True - - self._find_installed_version() - if not self._cur_commit_hash: - return True - else: - try: - self._check_github_for_update() - except Exception as e: - log.warning(u"Unable to contact github, can't check for update: {0!r}", e) - return False - - if self._num_commits_behind > 0 or self._num_commits_ahead > 0: - return True - - return False - def can_update(self): """Return whether update can be executed. @@ -322,15 +314,15 @@ def update(self): self.clean() if self.branch == self._find_installed_branch(): - _, _, exit_status = self._run_git(self._git_path, 'pull -f %s %s' % (app.GIT_REMOTE, self.branch)) # @UnusedVariable + _, _, exit_status = self._run_git(self._git_path, 'pull -f %s %s' % (app.GIT_REMOTE, self.branch)) else: - _, _, exit_status = self._run_git(self._git_path, 'checkout -f ' + self.branch) # @UnusedVariable + _, _, exit_status = self._run_git(self._git_path, 'checkout -f ' + self.branch) # Executing git clean after updating self.clean() if exit_status == 0: - self._find_installed_version() + self._set_commit_hash() # Notify update successful if app.NOTIFY_ON_UPDATE: try: @@ -339,8 +331,7 @@ def update(self): log.debug(u'Unable to send update notification. Continuing the update process') return True - else: - return False + return False @staticmethod def _is_hard_reset_allowed(): @@ -378,8 +369,7 @@ def reset(self): Calls git reset --hard to perform a hard reset. Returns a bool depending on the call's success. """ - _, _, exit_status = self._run_git(self._git_path, 'reset --hard {0}/{1}'.format - (app.GIT_REMOTE, app.BRANCH)) # @UnusedVariable + _, _, exit_status = self._run_git(self._git_path, 'reset --hard {0}/{1}'.format(app.GIT_REMOTE, app.BRANCH)) if exit_status == 0: return True @@ -388,7 +378,7 @@ def list_remote_branches(self): self.update_remote_origin() app.BRANCH = self._find_installed_branch() - branches, _, exit_status = self._run_git(self._git_path, 'ls-remote --heads %s' % app.GIT_REMOTE) # @UnusedVariable + branches, _, exit_status = self._run_git(self._git_path, 'ls-remote --heads %s' % app.GIT_REMOTE) if exit_status == 0 and branches: if branches: return re.findall(r'refs/heads/(.*)', branches) diff --git a/medusa/updater/source_updater.py b/medusa/updater/source_updater.py index b7b363cb6c..ef2a0140ca 100644 --- a/medusa/updater/source_updater.py +++ b/medusa/updater/source_updater.py @@ -7,16 +7,18 @@ import shutil import stat import tarfile -from builtins import str from github import GithubException from medusa import app, helpers, notifiers -from medusa.github_client import get_github_repo +from medusa.common import VERSION +from medusa.github_client import get_github_repo, get_latest_release from medusa.logger.adapters.style import BraceAdapter from medusa.session.core import MedusaSafeSession from medusa.updater.update_manager import UpdateManager +from six import text_type + log = BraceAdapter(logging.getLogger(__name__)) log.logger.addHandler(logging.NullHandler()) @@ -43,27 +45,36 @@ def __init__(self): def _find_installed_branch(): return app.CUR_COMMIT_BRANCH if app.CUR_COMMIT_BRANCH else 'master' - def get_cur_commit_hash(self): + @property + def current_commit_hash(self): return self._cur_commit_hash - def get_newest_commit_hash(self): + @property + def newest_commit_hash(self): return self._newest_commit_hash - @staticmethod - def get_cur_version(): - return '' + @property + def current_version(self): + return VERSION - @staticmethod - def get_newest_version(): - return '' + @property + def newest_version(self): + latest_release = get_latest_release(self.github_org, self.github_repo) + return latest_release.tag_name.lstrip('v') - def get_num_commits_behind(self): + @property + def commits_behind(self): return self._num_commits_behind - def get_num_commits_ahead(self): + @property + def commits_ahead(self): return self._num_commits_ahead def need_update(self): + if self.branch != self._find_installed_branch(): + log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) + return True + # need this to run first to set self._newest_commit_hash try: self._check_github_for_update() @@ -71,11 +82,17 @@ def need_update(self): log.warning(u"Unable to contact github, can't check for update: {0!r}", error) return False - if self.branch != self._find_installed_branch(): - log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) - return True + if not self._cur_commit_hash: + if self.is_latest_version(): + app.CUR_COMMIT_HASH = self.get_newest_commit_hash() + app.CUR_COMMIT_BRANCH = self.branch + return False + else: + self._set_update_text() + return True - if not self._cur_commit_hash or self._num_commits_behind > 0 or self._num_commits_ahead > 0: + elif self._num_commits_behind > 0 or self._num_commits_ahead > 0: + self._set_update_text() return True return False @@ -95,19 +112,18 @@ def _check_github_for_update(self): commit_hash: hash that we're checking against """ - self._num_commits_behind = 0 self._newest_commit_hash = None - gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) - # try to get newest commit hash and commits behind directly by comparing branch and current commit + + # try to get the newest commit hash and commits by comparing branch and current commit if self._cur_commit_hash: try: branch_compared = gh.compare(base=self.branch, head=self._cur_commit_hash) self._newest_commit_hash = branch_compared.base_commit.sha self._num_commits_behind = branch_compared.behind_by self._num_commits_ahead = branch_compared.ahead_by - except Exception: # UnknownObjectException + except Exception: self._newest_commit_hash = '' self._num_commits_behind = 0 self._num_commits_ahead = 0 @@ -115,7 +131,6 @@ def _check_github_for_update(self): # fall back and iterate over last 100 (items per page in gh_api) commits if not self._newest_commit_hash: - for curCommit in gh.get_commits(): if not self._newest_commit_hash: self._newest_commit_hash = curCommit.sha @@ -131,18 +146,8 @@ def _check_github_for_update(self): log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}', self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind) - def set_newest_text(self): - # if we're up to date then don't set this - app.NEWEST_VERSION_STRING = None - - if not self._cur_commit_hash: - log.debug(u"Unknown current version number, don't know if we should update or not") - - newest_text = "Unknown current version number: If you've never used the application " \ - 'upgrade system before then current version is not set. ' \ - '— Update Now' - - elif self._num_commits_behind > 0: + def _set_update_text(self): + if self._num_commits_behind > 0: base_url = 'http://github.com/' + self.github_org + '/' + self.github_repo if self._newest_commit_hash: url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash @@ -150,12 +155,14 @@ def set_newest_text(self): url = base_url + '/commits/' newest_text = 'There is a newer version available' - newest_text += " (you're " + str(self._num_commits_behind) + ' commit' + newest_text += " (you're " + text_type(self._num_commits_behind) + ' commit' if self._num_commits_behind > 1: newest_text += 's' newest_text += ' behind) — Update Now' else: - return + url = 'http://github.com/' + self.github_org + '/' + self.github_repo + '/releases' + newest_text = 'There is a newer version available' + newest_text += ' (' + self.newest_version + ') — Update Now' app.NEWEST_VERSION_STRING = newest_text @@ -163,7 +170,6 @@ def update(self): """ Downloads the latest source tarball from github and installs it over the existing version. """ - tar_download_url = 'http://github.com/' + self.github_org + '/' + self.github_repo + '/tarball/' + self.branch try: diff --git a/medusa/updater/update_manager.py b/medusa/updater/update_manager.py index ab0c700e29..96d56c1e39 100644 --- a/medusa/updater/update_manager.py +++ b/medusa/updater/update_manager.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import logging +from distutils.version import LooseVersion from medusa import app from medusa.logger.adapters.style import BraceAdapter @@ -31,3 +32,9 @@ def get_github_repo(): @staticmethod def get_update_url(): return app.WEB_ROOT + '/home/update/?pid=' + text_type(app.PID) + + def is_latest_version(self): + """Compare the current installed version with the remote version.""" + if LooseVersion(self.newest_version) > LooseVersion(self.current_version): + return False + return True diff --git a/medusa/updater/version_checker.py b/medusa/updater/version_checker.py index 06fa540639..ce331d5144 100644 --- a/medusa/updater/version_checker.py +++ b/medusa/updater/version_checker.py @@ -267,7 +267,6 @@ def check_for_new_version(self, force=False): return False # found updates - self.updater.set_newest_text() return self.updater.can_update() def check_for_new_news(self, force=False): From 2e18dd669784f6254c2cc8c1de880ded15a11a90 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 18 Jan 2019 20:54:12 +0100 Subject: [PATCH 03/13] Implement __str__ --- medusa/updater/docker_updater.py | 3 +++ medusa/updater/github_updater.py | 3 +++ medusa/updater/source_updater.py | 3 +++ medusa/updater/version_checker.py | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/medusa/updater/docker_updater.py b/medusa/updater/docker_updater.py index 58c3629de3..fac351ba68 100644 --- a/medusa/updater/docker_updater.py +++ b/medusa/updater/docker_updater.py @@ -17,6 +17,9 @@ class DockerUpdateManager(SourceUpdateManager): def __init__(self): super(DockerUpdateManager, self).__init__() + def __str__(self): + return 'Docker Updater' + def can_update(self): """Whether or not the update can be performed. diff --git a/medusa/updater/github_updater.py b/medusa/updater/github_updater.py index 0d71a28a2f..c029557fe7 100644 --- a/medusa/updater/github_updater.py +++ b/medusa/updater/github_updater.py @@ -36,6 +36,9 @@ def __init__(self): self._num_commits_behind = 0 self._num_commits_ahead = 0 + def __str__(self): + return 'GitHub Updater' + @property def current_commit_hash(self): return self._cur_commit_hash diff --git a/medusa/updater/source_updater.py b/medusa/updater/source_updater.py index ef2a0140ca..89fc549a8f 100644 --- a/medusa/updater/source_updater.py +++ b/medusa/updater/source_updater.py @@ -41,6 +41,9 @@ def __init__(self): self.session = MedusaSafeSession() + def __str__(self): + return 'Source Updater' + @staticmethod def _find_installed_branch(): return app.CUR_COMMIT_BRANCH if app.CUR_COMMIT_BRANCH else 'master' diff --git a/medusa/updater/version_checker.py b/medusa/updater/version_checker.py index ce331d5144..64e4ccb1d7 100644 --- a/medusa/updater/version_checker.py +++ b/medusa/updater/version_checker.py @@ -254,7 +254,7 @@ def check_for_new_version(self, force=False): # checking for updates if not app.AUTO_UPDATE: - log.info(u'Checking for updates using {0}', 'Secret!!') + log.info(u'Checking for updates using {0}', self.updater) if not self.updater.need_update(): app.NEWEST_VERSION_STRING = None From a1fc8ab0897e5c289e14b4ffa3bba9b94d344974 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 19 Jan 2019 13:48:31 +0100 Subject: [PATCH 04/13] Improve code --- medusa/updater/docker_updater.py | 11 +++++ medusa/updater/github_updater.py | 75 +++++++++++++++----------------- medusa/updater/source_updater.py | 12 +++-- 3 files changed, 51 insertions(+), 47 deletions(-) diff --git a/medusa/updater/docker_updater.py b/medusa/updater/docker_updater.py index fac351ba68..c95ae626ee 100644 --- a/medusa/updater/docker_updater.py +++ b/medusa/updater/docker_updater.py @@ -20,6 +20,17 @@ def __init__(self): def __str__(self): return 'Docker Updater' + def need_update(self): + if self.branch != self._find_installed_branch(): + log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) + return True + + if not self.is_latest_version(): + self._set_update_text() + return True + + return False + def can_update(self): """Whether or not the update can be performed. diff --git a/medusa/updater/github_updater.py b/medusa/updater/github_updater.py index c029557fe7..b9222172c2 100644 --- a/medusa/updater/github_updater.py +++ b/medusa/updater/github_updater.py @@ -28,8 +28,7 @@ def __init__(self): self._git_path = self._find_working_git() self.github_org = self.get_github_org() self.github_repo = self.get_github_repo() - - self.branch = app.BRANCH = self._find_installed_branch() + self.branch = self._find_installed_branch() self._cur_commit_hash = None self._newest_commit_hash = None @@ -49,17 +48,17 @@ def newest_commit_hash(self): @property def current_version(self): - if self._cur_commit_hash or self._set_commit_hash(): - cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format( - self._cur_commit_hash))[0] - return cur_version.lstrip('v') + self.update_commit_hash() + cur_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format( + self._cur_commit_hash))[0] + return cur_version.lstrip('v') @property def newest_version(self): - if self._newest_commit_hash: - new_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format( - self._newest_commit_hash))[0] - return new_version.lstrip('v') + self.update_newest_commit_hash() + new_version = self._run_git(self._git_path, 'describe --tags --abbrev=0 {0}'.format( + self._newest_commit_hash))[0] + return new_version.lstrip('v') @property def commits_behind(self): @@ -172,7 +171,7 @@ def _run_git(git_path, args): return output, err, exit_status - def _set_commit_hash(self): + def update_commit_hash(self): """Attempt to set the hash of the currently installed version of the application. Uses git to get commit version. @@ -191,23 +190,7 @@ def _set_commit_hash(self): return False - def _find_installed_branch(self): - branch_info, _, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD') - if exit_status == 0 and branch_info: - branch = branch_info.strip().replace('refs/heads/', '', 1) - if branch: - app.BRANCH = branch - return branch - return '' - - def _check_github_for_update(self): - """ - Uses git commands to check if there is a newer version that the provided - commit hash. If there is a newer version it sets _num_commits_behind. - """ - self._num_commits_behind = 0 - self._num_commits_ahead = 0 - + def update_newest_commit_hash(self): # update remote origin url self.update_remote_origin() @@ -215,35 +198,49 @@ def _check_github_for_update(self): output, _, exit_status = self._run_git(self._git_path, 'fetch --prune %s' % app.GIT_REMOTE) if not exit_status == 0: log.warning(u"Unable to contact github, can't check for update") - return + return False # get latest commit_hash from remote output, _, exit_status = self._run_git(self._git_path, 'rev-parse --verify --quiet "@{upstream}"') if exit_status == 0 and output: cur_commit_hash = output.strip() - if not re.match('^[a-z0-9]+$', cur_commit_hash): log.debug(u"Output doesn't look like a hash, not using it") - return - + return False else: self._newest_commit_hash = cur_commit_hash + return True else: log.debug(u"git didn't return newest commit hash") - return + return False + + def _find_installed_branch(self): + branch_info, _, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD') + if exit_status == 0 and branch_info: + branch = branch_info.strip().replace('refs/heads/', '', 1) + if branch: + app.BRANCH = branch + return branch + return '' + + def check_for_update(self): + """ + Uses git commands to check if there is a newer version that the provided + commit hash. If there is a newer version it sets _num_commits_behind. + """ + self.update_commit_hash() + self.update_newest_commit_hash() # get number of commits behind and ahead (option --count not supported git < 1.7.2) output, _, exit_status = self._run_git(self._git_path, 'rev-list --left-right "@{upstream}"...HEAD') if exit_status == 0 and output: - try: self._num_commits_behind = int(output.count('<')) self._num_commits_ahead = int(output.count('>')) - except Exception: log.debug(u"git didn't return numbers for behind and ahead, not using it") - return + return False log.debug(u'cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}, num_commits_ahead = {3}', self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind, self._num_commits_ahead) @@ -254,8 +251,7 @@ def need_update(self): return True try: - self._set_commit_hash() - self._check_github_for_update() + self.check_for_update() except Exception as e: log.warning(u"Unable to contact github, can't check for update: {0!r}", e) return False @@ -287,7 +283,6 @@ def _set_update_text(self): elif self._num_commits_ahead > 0: newest_text = u'Local branch is ahead of {0}. Automatic update not possible'.format(self.branch) log.warning(newest_text) - else: return @@ -325,7 +320,7 @@ def update(self): self.clean() if exit_status == 0: - self._set_commit_hash() + self.update_commit_hash() # Notify update successful if app.NOTIFY_ON_UPDATE: try: diff --git a/medusa/updater/source_updater.py b/medusa/updater/source_updater.py index 89fc549a8f..c28f0e6feb 100644 --- a/medusa/updater/source_updater.py +++ b/medusa/updater/source_updater.py @@ -80,14 +80,15 @@ def need_update(self): # need this to run first to set self._newest_commit_hash try: - self._check_github_for_update() + self.check_for_update() except Exception as error: log.warning(u"Unable to contact github, can't check for update: {0!r}", error) return False + # This will be used until the first update if not self._cur_commit_hash: if self.is_latest_version(): - app.CUR_COMMIT_HASH = self.get_newest_commit_hash() + app.CUR_COMMIT_HASH = self._newest_commit_hash app.CUR_COMMIT_BRANCH = self.branch return False else: @@ -108,15 +109,13 @@ def can_update(self): """ return True - def _check_github_for_update(self): + def check_for_update(self): """Use pygithub to ask github if there is a newer version.. If there is a newer version it sets application's version text. commit_hash: hash that we're checking against """ - self._num_commits_behind = 0 - self._newest_commit_hash = None gh = get_github_repo(app.GIT_ORG, app.GIT_REPO) # try to get the newest commit hash and commits by comparing branch and current commit @@ -127,10 +126,9 @@ def _check_github_for_update(self): self._num_commits_behind = branch_compared.behind_by self._num_commits_ahead = branch_compared.ahead_by except Exception: - self._newest_commit_hash = '' + self._newest_commit_hash = None self._num_commits_behind = 0 self._num_commits_ahead = 0 - self._cur_commit_hash = '' # fall back and iterate over last 100 (items per page in gh_api) commits if not self._newest_commit_hash: From 30fdeb307ba52f7799c28d9a9959ee66a7034b10 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 19 Jan 2019 14:31:42 +0100 Subject: [PATCH 05/13] Fix git detection --- medusa/updater/github_updater.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/medusa/updater/github_updater.py b/medusa/updater/github_updater.py index b9222172c2..319227a9c6 100644 --- a/medusa/updater/github_updater.py +++ b/medusa/updater/github_updater.py @@ -109,20 +109,20 @@ def _find_working_git(self): else: log.debug(u'Not using: {0}', cur_git) - # Still haven't found a working git - # Warn user only if he has version check enabled - if app.VERSION_NOTIFY: - app.NEWEST_VERSION_STRING = ERROR_MESSAGE - - @staticmethod - def _run_git(git_path, args): + def _run_git(self, git_path, args): output = err = exit_status = None if not git_path: - log.warning(u"No git specified, can't use git commands") - app.NEWEST_VERSION_STRING = ERROR_MESSAGE - exit_status = 1 - return output, err, exit_status + git_path = self._find_working_git() + if git_path: + self._git_path = git_path + else: + # Warn user only if he has version check enabled + if app.VERSION_NOTIFY: + log.warning(u"No git specified, can't use git commands") + app.NEWEST_VERSION_STRING = ERROR_MESSAGE + exit_status = 1 + return output, err, exit_status # If we have a valid git remove the git warning # String will be updated as soon we check github From b90b8e35d4190873d469d3eb7580677fde92f261 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 19 Jan 2019 14:40:03 +0100 Subject: [PATCH 06/13] Improve docs --- medusa/updater/docker_updater.py | 4 +--- medusa/updater/github_updater.py | 10 ++-------- medusa/updater/source_updater.py | 4 +--- medusa/updater/version_checker.py | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/medusa/updater/docker_updater.py b/medusa/updater/docker_updater.py index c95ae626ee..5508100dc4 100644 --- a/medusa/updater/docker_updater.py +++ b/medusa/updater/docker_updater.py @@ -47,7 +47,5 @@ def _set_update_text(self): 'and rebuild your container to update' def update(self): - """ - Downloads the latest version. - """ + """Download the latest version.""" return False diff --git a/medusa/updater/github_updater.py b/medusa/updater/github_updater.py index 319227a9c6..eafcb03b04 100644 --- a/medusa/updater/github_updater.py +++ b/medusa/updater/github_updater.py @@ -225,10 +225,7 @@ def _find_installed_branch(self): return '' def check_for_update(self): - """ - Uses git commands to check if there is a newer version that the provided - commit hash. If there is a newer version it sets _num_commits_behind. - """ + """Use git commands to check if there is a newer version that the provided commit hash.""" self.update_commit_hash() self.update_newest_commit_hash() @@ -363,10 +360,7 @@ def clean(self): return exit_status def reset(self): - """ - Calls git reset --hard to perform a hard reset. Returns a bool depending - on the call's success. - """ + """Call git reset --hard to perform a hard reset.""" _, _, exit_status = self._run_git(self._git_path, 'reset --hard {0}/{1}'.format(app.GIT_REMOTE, app.BRANCH)) if exit_status == 0: return True diff --git a/medusa/updater/source_updater.py b/medusa/updater/source_updater.py index c28f0e6feb..100d87a504 100644 --- a/medusa/updater/source_updater.py +++ b/medusa/updater/source_updater.py @@ -168,9 +168,7 @@ def _set_update_text(self): app.NEWEST_VERSION_STRING = newest_text def update(self): - """ - Downloads the latest source tarball from github and installs it over the existing version. - """ + """Download the latest source tarball from github and installs it over the existing version.""" tar_download_url = 'http://github.com/' + self.github_org + '/' + self.github_repo + '/tarball/' + self.branch try: diff --git a/medusa/updater/version_checker.py b/medusa/updater/version_checker.py index 64e4ccb1d7..686ab95c39 100644 --- a/medusa/updater/version_checker.py +++ b/medusa/updater/version_checker.py @@ -226,7 +226,7 @@ def getDBcompare(self): def find_install_type(self): """ - Determines how this copy of sr was installed. + Determine how this copy of Medusa was installed. :return: type of installation. Possible values are: 'docker': any docker build From 8cd2a27495773c27d166108c82f95c79534870b9 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 19 Jan 2019 14:50:32 +0100 Subject: [PATCH 07/13] Update Docker update msg --- medusa/updater/docker_updater.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/medusa/updater/docker_updater.py b/medusa/updater/docker_updater.py index 5508100dc4..029863097f 100644 --- a/medusa/updater/docker_updater.py +++ b/medusa/updater/docker_updater.py @@ -40,11 +40,14 @@ def can_update(self): return False def _set_update_text(self): - """Set an update text, when running in a docker container.""" - log.debug('There is an update available, Medusa is running in a docker container,' + """Set an update text, when running in a Docker container.""" + log.debug('There is an update available, Medusa is running in a Docker container,' ' so auto updating is disabled.') - app.NEWEST_VERSION_STRING = 'There is an update available: please pull the latest docker image, ' \ - 'and rebuild your container to update' + + url = 'http://github.com/' + self.github_org + '/' + self.github_repo + '/releases' + newest_text = 'There is a newer version available' + newest_text += ' (' + self.newest_version + ') — Pull the latest Docker image and rebuild your container to update' + app.NEWEST_VERSION_STRING = newest_text def update(self): """Download the latest version.""" From da11b2110fd44f52d0153fd7bd1809c521b18443 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 19 Jan 2019 14:56:48 +0100 Subject: [PATCH 08/13] Update exceptions --- setup.cfg | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9cbeccdc5e..076ba10789 100644 --- a/setup.cfg +++ b/setup.cfg @@ -173,7 +173,12 @@ flake8-ignore = medusa/themes/__init__.py F401 medusa/tv/__init__.py D104 F401 medusa/ui.py D100 D101 D102 D200 D202 D204 D205 D400 D401 E305 N802 N803 N806 - medusa/version_checker.py D100 D101 D102 D200 D202 D205 D400 D401 N802 N803 N806 + medusa/updater/__init__.py D104 + medusa/updater/docker_updater.py D100 D101 D102 D105 + medusa/updater/github_updater.py D100 D101 D102 D105 + medusa/updater/source_updater.py D100 D101 D102 D105 + medusa/updater/update_manager.py D100 D101 D102 + medusa/updater/version_checker.py D100 D101 D102 N802 N803 N806 setup.py D200 D400 tests/__init__.py D104 tests/*.py D101 D102 D103 From f0acdea77f4da3aa42efbd8e7db3143767713083 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 19 Jan 2019 15:04:16 +0100 Subject: [PATCH 09/13] Fix test --- tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4866f7e9dc..d6e41f2fb1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,11 +20,10 @@ from medusa.logger import read_loglines as logger_read_loglines from medusa.providers.generic_provider import GenericProvider from medusa.tv import Episode, Series -from medusa.version_checker import CheckVersion +from medusa.updater.version_checker import CheckVersion from mock.mock import Mock import pytest -import six from six import iteritems, text_type from subliminal.subtitle import Subtitle from subliminal.video import Video From c9820205c5838a012a2bc9c69c437315aeec6af0 Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 21 Jan 2019 21:24:20 +0100 Subject: [PATCH 10/13] Use self.is_latest_version() only if branch is master --- medusa/updater/docker_updater.py | 2 +- medusa/updater/github_updater.py | 4 +++- medusa/updater/source_updater.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/medusa/updater/docker_updater.py b/medusa/updater/docker_updater.py index 029863097f..5aa9f19a0f 100644 --- a/medusa/updater/docker_updater.py +++ b/medusa/updater/docker_updater.py @@ -25,7 +25,7 @@ def need_update(self): log.debug(u'Branch checkout: {0}->{1}', self._find_installed_branch(), self.branch) return True - if not self.is_latest_version(): + if self.branch == 'master' and not self.is_latest_version(): self._set_update_text() return True diff --git a/medusa/updater/github_updater.py b/medusa/updater/github_updater.py index eafcb03b04..e316559f8e 100644 --- a/medusa/updater/github_updater.py +++ b/medusa/updater/github_updater.py @@ -368,7 +368,9 @@ def reset(self): def list_remote_branches(self): # update remote origin url self.update_remote_origin() - app.BRANCH = self._find_installed_branch() + branch = self._find_installed_branch() + self.branch = branch + app.BRANCH = branch branches, _, exit_status = self._run_git(self._git_path, 'ls-remote --heads %s' % app.GIT_REMOTE) if exit_status == 0 and branches: diff --git a/medusa/updater/source_updater.py b/medusa/updater/source_updater.py index 100d87a504..4fd8f39642 100644 --- a/medusa/updater/source_updater.py +++ b/medusa/updater/source_updater.py @@ -86,7 +86,7 @@ def need_update(self): return False # This will be used until the first update - if not self._cur_commit_hash: + if self.branch == 'master' and not self._cur_commit_hash: if self.is_latest_version(): app.CUR_COMMIT_HASH = self._newest_commit_hash app.CUR_COMMIT_BRANCH = self.branch From 904a86d3da1d749a7db06fb6e3324dba86920c79 Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 21 Jan 2019 21:37:56 +0100 Subject: [PATCH 11/13] Revert unnecessary change --- medusa/updater/github_updater.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/medusa/updater/github_updater.py b/medusa/updater/github_updater.py index e316559f8e..eafcb03b04 100644 --- a/medusa/updater/github_updater.py +++ b/medusa/updater/github_updater.py @@ -368,9 +368,7 @@ def reset(self): def list_remote_branches(self): # update remote origin url self.update_remote_origin() - branch = self._find_installed_branch() - self.branch = branch - app.BRANCH = branch + app.BRANCH = self._find_installed_branch() branches, _, exit_status = self._run_git(self._git_path, 'ls-remote --heads %s' % app.GIT_REMOTE) if exit_status == 0 and branches: From 48dcbef75356ddd11faf1a1fd94dc91d258e3f19 Mon Sep 17 00:00:00 2001 From: Dario Date: Thu, 24 Jan 2019 13:25:31 +0100 Subject: [PATCH 12/13] Review --- medusa/updater/update_manager.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/medusa/updater/update_manager.py b/medusa/updater/update_manager.py index 96d56c1e39..787ce2376c 100644 --- a/medusa/updater/update_manager.py +++ b/medusa/updater/update_manager.py @@ -16,10 +16,6 @@ class UpdateManager(object): - def __init__(self): - """Update manager initialization.""" - # Initialize the app.RUNS_IN_DOCKER variable - # self.runs_in_docker() @staticmethod def get_github_org(): @@ -33,6 +29,14 @@ def get_github_repo(): def get_update_url(): return app.WEB_ROOT + '/home/update/?pid=' + text_type(app.PID) + def current_version(self): + """Get the current verion of the app.""" + raise NotImplementedError + + def newest_version(self): + """Get the newest verion of the app.""" + raise NotImplementedError + def is_latest_version(self): """Compare the current installed version with the remote version.""" if LooseVersion(self.newest_version) > LooseVersion(self.current_version): From 3f05fe7168451405155fcd6bdc523c97c89aa1f2 Mon Sep 17 00:00:00 2001 From: Dario Date: Thu, 24 Jan 2019 13:31:34 +0100 Subject: [PATCH 13/13] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9735ea4bf..241ee237ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ #### Improvements #### Fixes -- Fixed saving newznab provider api key ([#5918](https://github.com/pymedusa/Medusa/pull/5918)) +- Fixed saving newznab provider API key ([#5918](https://github.com/pymedusa/Medusa/pull/5918)) +- Fixed permanent Docker update message ([#6018](https://github.com/pymedusa/Medusa/pull/6018)) -----