From 3af25fdff671fd41b4226b2621f2cb81edef3784 Mon Sep 17 00:00:00 2001 From: Bob Swift Date: Tue, 17 Jul 2018 15:36:32 -0600 Subject: [PATCH] PICARD-1045: Add program update checking --- picard/tagger.py | 5 + picard/ui/mainwindow.py | 12 ++ picard/ui/options/general.py | 10 ++ picard/ui/ui_options_general.py | 52 +++++++- picard/util/__init__.py | 39 ++++++ picard/util/checkupdate.py | 152 ++++++++++++++++++++++ test/test_versioncompare.py | 217 ++++++++++++++++++++++++++++++++ ui/options_general.ui | 86 +++++++++++++ 8 files changed, 571 insertions(+), 2 deletions(-) create mode 100644 picard/util/checkupdate.py create mode 100644 test/test_versioncompare.py diff --git a/picard/tagger.py b/picard/tagger.py index 6797781af..500267bab 100644 --- a/picard/tagger.py +++ b/picard/tagger.py @@ -97,6 +97,8 @@ from picard.ui.searchdialog.album import AlbumSearchDialog from picard.ui.searchdialog.artist import ArtistSearchDialog from picard.ui.searchdialog.track import TrackSearchDialog +from picard.util.checkupdate import UpdateCheckManager + # A "fix" for https://bugs.python.org/issue1438480 def _patched_shutil_copystat(src, dst, *, follow_symlinks=True): @@ -244,6 +246,9 @@ class Tagger(QtWidgets.QApplication): self.exit_cleanup = [] self.stopping = False + # Load release version information + self.updatecheckmanager = UpdateCheckManager() + def register_cleanup(self, func): self.exit_cleanup.append(func) diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py index 9cc6169e6..3ba8526bb 100644 --- a/picard/ui/mainwindow.py +++ b/picard/ui/mainwindow.py @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from collections import OrderedDict +import datetime from functools import partial import os.path @@ -174,6 +175,9 @@ class MainWindow(QtWidgets.QMainWindow, PreserveGeometry): def show(self): self.restoreWindowState() super().show() + if config.setting['check_for_updates'] and datetime.date.today().toordinal() >= config.persist['last_update_check'] + config.setting['update_check_days']: + log.debug(_("Initiating start-up check for program updates.")) + self.tagger.updatecheckmanager.check_update(show_always=False, update_level='dev' if config.setting["include_beta_versions"] else 'final') self.metadata_box.restore_state() def closeEvent(self, event): @@ -535,6 +539,9 @@ class MainWindow(QtWidgets.QMainWindow, PreserveGeometry): self.open_folder_action.setEnabled(False) self.open_folder_action.triggered.connect(self.open_folder) + self.check_update_action = QtWidgets.QAction(_("&Check for Update"), self) + self.check_update_action.triggered.connect(self.check_for_update) + def toggle_rename_files(self, checked): config.setting["rename_files"] = checked @@ -608,6 +615,8 @@ class MainWindow(QtWidgets.QMainWindow, PreserveGeometry): menu.addSeparator() menu.addAction(self.view_history_action) menu.addSeparator() + menu.addAction(self.check_update_action) + menu.addSeparator() menu.addAction(self.support_forum_action) menu.addAction(self.report_bug_action) menu.addAction(self.view_log_action) @@ -1087,3 +1096,6 @@ class MainWindow(QtWidgets.QMainWindow, PreserveGeometry): target = selected_objects[0] self.tagger.paste_files(target) self.paste_action.setEnabled(False) + + def check_for_update(self): + self.tagger.updatecheckmanager.check_update(show_always=True, update_level='dev' if config.setting["include_beta_versions"] else 'final') diff --git a/picard/ui/options/general.py b/picard/ui/options/general.py index 9c8c161e3..d3550bde6 100644 --- a/picard/ui/options/general.py +++ b/picard/ui/options/general.py @@ -50,6 +50,10 @@ class GeneralOptionsPage(OptionsPage): config.TextOption("persist", "oauth_access_token", ""), config.IntOption("persist", "oauth_access_token_expires", 0), config.TextOption("persist", "oauth_username", ""), + config.BoolOption("setting", "check_for_updates", False), + config.BoolOption("setting", "include_beta_versions", False), + config.IntOption("setting", "update_check_days", 0), + config.IntOption("persist", "last_update_check", 0), ] def __init__(self, parent=None): @@ -66,12 +70,18 @@ class GeneralOptionsPage(OptionsPage): self.ui.server_port.setValue(config.setting["server_port"]) self.ui.analyze_new_files.setChecked(config.setting["analyze_new_files"]) self.ui.ignore_file_mbids.setChecked(config.setting["ignore_file_mbids"]) + self.ui.check_for_updates.setChecked(config.setting["check_for_updates"]) + self.ui.include_beta_versions.setChecked(config.setting["include_beta_versions"]) + self.ui.update_check_days.setValue(config.setting["update_check_days"]) def save(self): config.setting["server_host"] = self.ui.server_host.currentText().strip() config.setting["server_port"] = self.ui.server_port.value() config.setting["analyze_new_files"] = self.ui.analyze_new_files.isChecked() config.setting["ignore_file_mbids"] = self.ui.ignore_file_mbids.isChecked() + config.setting["check_for_updates"] = self.ui.check_for_updates.isChecked() + config.setting["include_beta_versions"] = self.ui.include_beta_versions.isChecked() + config.setting["update_check_days"] = self.ui.update_check_days.value() def update_login_logout(self): if self.tagger.webservice.oauth_manager.is_logged_in(): diff --git a/picard/ui/ui_options_general.py b/picard/ui/ui_options_general.py index 77aafbb54..4c8fd577f 100644 --- a/picard/ui/ui_options_general.py +++ b/picard/ui/ui_options_general.py @@ -67,8 +67,52 @@ class Ui_GeneralOptionsPage(object): self.ignore_file_mbids.setObjectName("ignore_file_mbids") self.verticalLayout.addWidget(self.ignore_file_mbids) self.vboxlayout.addWidget(self.groupBox_2) - spacerItem1 = QtWidgets.QSpacerItem(181, 21, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.vboxlayout.addItem(spacerItem1) + self.groupBox_3 = QtWidgets.QGroupBox(GeneralOptionsPage) + self.groupBox_3.setEnabled(True) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) + self.groupBox_3.setSizePolicy(sizePolicy) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.gridLayout.setContentsMargins(9, -1, -1, -1) + self.gridLayout.setHorizontalSpacing(6) + self.gridLayout.setObjectName("gridLayout") + self.include_beta_versions = QtWidgets.QCheckBox(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.include_beta_versions.sizePolicy().hasHeightForWidth()) + self.include_beta_versions.setSizePolicy(sizePolicy) + self.include_beta_versions.setObjectName("include_beta_versions") + self.gridLayout.addWidget(self.include_beta_versions, 1, 0, 1, 2) + self.update_check_days = QtWidgets.QSpinBox(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.update_check_days.sizePolicy().hasHeightForWidth()) + self.update_check_days.setSizePolicy(sizePolicy) + self.update_check_days.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.update_check_days.setObjectName("update_check_days") + self.gridLayout.addWidget(self.update_check_days, 2, 1, 1, 1) + self.check_for_updates = QtWidgets.QCheckBox(self.groupBox_3) + self.check_for_updates.setObjectName("check_for_updates") + self.gridLayout.addWidget(self.check_for_updates, 0, 0, 1, 2) + self.label_2 = QtWidgets.QLabel(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) + self.label_2.setSizePolicy(sizePolicy) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem1, 2, 2, 1, 1) + self.vboxlayout.addWidget(self.groupBox_3) + spacerItem2 = QtWidgets.QSpacerItem(181, 21, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.vboxlayout.addItem(spacerItem2) self.retranslateUi(GeneralOptionsPage) QtCore.QMetaObject.connectSlotsByName(GeneralOptionsPage) @@ -85,4 +129,8 @@ class Ui_GeneralOptionsPage(object): self.groupBox_2.setTitle(_("General")) self.analyze_new_files.setText(_("Automatically scan all new files")) self.ignore_file_mbids.setText(_("Ignore MBIDs when loading new files")) + self.groupBox_3.setTitle(_("Update Checking")) + self.include_beta_versions.setText(_("Include beta versions in update checks")) + self.check_for_updates.setText(_("Check for updates during start-up")) + self.label_2.setText(_("Days between update checks:")) diff --git a/picard/util/__init__.py b/picard/util/__init__.py index 35bb83ecc..8bfc1463d 100644 --- a/picard/util/__init__.py +++ b/picard/util/__init__.py @@ -496,3 +496,42 @@ def restore_method(func): return func_wrapper builtins.__dict__['string_'] = convert_to_string + + +def compare_version_tuples(version1, version2): + '''Compare Versions + + Compares two Picard version tuples to determine whether the second tuple + contains a higher version number than the first tuple. + + Args: + version1: The first version tuple to compare. This will be used as + the base for the comparison. + version2: The version tuple to be compared to the base version. + + Returns: + -1 if version2 is lower than version1 + 0 if version2 is the same as version1 + 1 if version2 is higher than version1 + + Raises: + none + ''' + + # Create test copies that can be modified + test1 = list(version1) + test2 = list(version2) + + # Set sort order for release type element + test1[3] = 1 if test1[3] == 'final' else 0 + if test1[3]: + test1[4] = 0 + test2[3] = 1 if test2[3] == 'final' else 0 + if test2[3]: + test2[4] = 0 + + # Compare elements in order + for x in range(0, 5): + if test1[x] != test2[x]: + return 1 if test1[x] < test2[x] else -1 + return 0 diff --git a/picard/util/checkupdate.py b/picard/util/checkupdate.py new file mode 100644 index 000000000..e00d0c917 --- /dev/null +++ b/picard/util/checkupdate.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2018 Bob Swift +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from picard import (PICARD_VERSION, tagger, log, version_from_string, version_to_string, config) +import picard.util.webbrowser2 as wb2 +from PyQt5 import QtCore +from PyQt5.QtWidgets import QMessageBox +from picard.util import load_json, compare_version_tuples +from functools import partial +import re +import datetime + + +# Used to strip leading and trailing text from version string. +_RE_CLEAN_VERSION = re.compile('^[^0-9]*(.*)[^0-9]*$', re.IGNORECASE) + + +# GitHub API information +GITHUB_API = { + 'host': 'api.github.com', + 'port': 443, + 'endpoint': { + 'releases': '/repos/metabrainz/picard/releases', + 'tags': '/repos/metabrainz/picard/git/refs/tags' + } +} + + +class UpdateCheckManager(QtCore.QObject): + + def __init__(self): + super().__init__() + + # Version tuple format: str.key: ( str.version, ( int.major, int.minor, int.micro, str.type, int.development ), str.title, str.url ) + self._available_versions = { + 'stable': ('', (0, 0, 0, 'dev', 0), '', ''), + 'beta': ('', (0, 0, 0, 'dev', 0), '', ''), + 'dev': ('', (0, 0, 0, 'dev', 0), '', ''), + } + self._show_always = False + self._update_level = 'dev' + + def check_update(self, show_always=False, update_level='dev', callback=None): + '''Checks if an update is available. + + Compares the version number of the currently running instance of Picard + and displays a dialog box informing the user if an update is available, + with an option of opening the Picard site in the browser to download the + update. If there is no update available, no dialog will be shown unless + the "show_always" parameter has been set to True. This allows for silent + checking during startup if so configured. + + Args: + show_always: Boolean value indicating whether the results dialog + should be shown even when there is no update available. + update_level: Determines what type of updates to check. If set to + 'final' only stable release versions are checked. If set to + 'dev' both beta and stable releases are checked. + callback: Optional callback function. + + Returns: + none. + + Raises: + none. + ''' + self._show_always = show_always + self._update_level = update_level + + # Gets list of releases from GitHub website api. + self.query_available_updates() + + @property + def available_versions(self): + '''Provide a list of the latest version tuples for each type.''' + return self._available_versions + + def query_available_updates(self, callback=None): + '''Gets list of releases from GitHub website api.''' + output_text = _("Getting release information from GitHub.") + log.debug(output_text) + self.tagger.webservice.get( + GITHUB_API['host'], + GITHUB_API['port'], + GITHUB_API['endpoint']['releases'], + partial(self._releases_json_loaded, callback=callback), + parse_response_type=None, + priority=True, + important=True + ) + + def _releases_json_loaded(self, response, reply, error, callback=None): + '''Processes response from GitHub api query.''' + if error: + tagger.window.set_statusbar_message( + N_("Error loading releases list: %(error)s"), + {'error': reply.errorString()}, + echo=log.error + ) + else: + config.persist['last_update_check'] = datetime.date.today().toordinal() + releases = load_json(response) + + for release in releases: + ver = version_from_string( + _RE_CLEAN_VERSION.findall(release['tag_name'])[0]) + key = 'beta' if release['prerelease'] else 'stable' + if compare_version_tuples(self._available_versions[key][1], ver) > 0: + self._available_versions[key] = (version_to_string( + ver, short=True), ver, release['name'], release['html_url'],) + for key in self._available_versions.keys(): + log.debug("Version key '%s' --> %s" % + (key, self._available_versions[key],)) + + # Display results to user. + msg_title = _("Picard Update") + if (compare_version_tuples(PICARD_VERSION, self._available_versions['stable'][1]) > 0) | ( + (compare_version_tuples(PICARD_VERSION, self._available_versions['beta'][1]) > 0) & (self._update_level == 'dev')): + key = 'beta' if (compare_version_tuples(self._available_versions['stable'][1], + self._available_versions['beta'][1]) > 0) & (self._update_level == 'dev') else 'stable' + if self.available_versions[key][2]: + new_version = "%s (%s)" & (self._available_versions[key][2], self._available_versions[key][0]) + else: + new_version = self.available_versions[key][0] + msg_text = _("A new version of Picard is available.\n\nNew version: %s\n\n" + "Would you like to download the new version?") % (new_version,) + if QMessageBox.information(None, msg_title, msg_text, QMessageBox.Ok | QMessageBox.Cancel, + QMessageBox.Cancel) == QMessageBox.Ok: + wb2.open(self._available_versions[key][3]) + else: + if self._show_always: + msg_text = _("There is no update currently available.") + QMessageBox.information( + None, msg_title, msg_text, QMessageBox.Ok, QMessageBox.Ok) + if callback: + callback() diff --git a/test/test_versioncompare.py b/test/test_versioncompare.py new file mode 100644 index 000000000..ba089df03 --- /dev/null +++ b/test/test_versioncompare.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- + +import unittest +from picard.util import compare_version_tuples + + +class CompareVersionsTest(unittest.TestCase): + '''Unit tests for compare_version_tuples() function.''' + + def test_compare_version_01(self): + a, b, r = (0, 0, 1, 'dev', 1), (0, 0, 1, 'dev', 1), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_02(self): + a, b, r = (0, 1, 0, 'dev', 1), (0, 1, 0, 'dev', 1), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_03(self): + a, b, r = (0, 1, 1, 'dev', 1), (0, 1, 1, 'dev', 1), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_04(self): + a, b, r = (1, 0, 0, 'dev', 1), (1, 0, 0, 'dev', 1), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_05(self): + a, b, r = (1, 0, 1, 'dev', 1), (1, 0, 1, 'dev', 1), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_06(self): + a, b, r = (1, 1, 0, 'dev', 1), (1, 1, 0, 'dev', 1), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_07(self): + a, b, r = (1, 1, 1, 'dev', 1), (1, 1, 1, 'dev', 1), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_08(self): + a, b, r = (0, 0, 1, 'dev', 2), (0, 0, 1, 'dev', 1), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_09(self): + a, b, r = (0, 0, 2, 'dev', 1), (0, 0, 1, 'dev', 2), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_10(self): + a, b, r = (0, 1, 0, 'dev', 1), (0, 0, 1, 'dev', 2), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_11(self): + a, b, r = (1, 0, 0, 'dev', 1), (0, 1, 1, 'dev', 2), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_12(self): + a, b, r = (0, 0, 1, 'dev', 1), (0, 0, 1, 'dev', 2), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_13(self): + a, b, r = (0, 0, 1, 'dev', 2), (0, 0, 2, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_14(self): + a, b, r = (0, 0, 1, 'dev', 2), (0, 1, 0, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_15(self): + a, b, r = (0, 1, 1, 'dev', 2), (1, 0, 0, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_16(self): + a, b, r = (0, 0, 1, 'final', 0), (0, 0, 1, 'final', 0), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_17(self): + a, b, r = (0, 0, 1, 'final', 0), (0, 0, 1, 'final', 1), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_18(self): + a, b, r = (0, 0, 1, 'final', 1), (0, 0, 1, 'final', 0), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_19(self): + a, b, r = (0, 1, 0, 'final', 0), (0, 1, 0, 'final', 0), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_20(self): + a, b, r = (0, 1, 1, 'final', 0), (0, 1, 1, 'final', 0), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_21(self): + a, b, r = (1, 0, 0, 'final', 0), (1, 0, 0, 'final', 0), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_22(self): + a, b, r = (1, 0, 1, 'final', 0), (1, 0, 1, 'final', 0), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_23(self): + a, b, r = (1, 1, 0, 'final', 0), (1, 1, 0, 'final', 0), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_24(self): + a, b, r = (1, 1, 1, 'final', 0), (1, 1, 1, 'final', 0), 0 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_25(self): + a, b, r = (0, 0, 2, 'final', 0), (0, 0, 1, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_26(self): + a, b, r = (0, 1, 0, 'final', 0), (0, 0, 1, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_27(self): + a, b, r = (1, 0, 0, 'final', 0), (0, 1, 1, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_28(self): + a, b, r = (0, 0, 1, 'final', 0), (0, 0, 2, 'final', 0), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_29(self): + a, b, r = (0, 0, 1, 'final', 0), (0, 1, 0, 'final', 0), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_30(self): + a, b, r = (0, 1, 1, 'final', 0), (1, 0, 0, 'final', 0), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_31(self): + a, b, r = (0, 0, 1, 'dev', 1), (0, 0, 1, 'final', 0), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_32(self): + a, b, r = (0, 0, 2, 'dev', 1), (0, 0, 1, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_33(self): + a, b, r = (0, 0, 2, 'dev', 1), (0, 1, 0, 'final', 0), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_34(self): + a, b, r = (0, 1, 1, 'dev', 1), (0, 1, 0, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_35(self): + a, b, r = (0, 2, 0, 'dev', 1), (0, 1, 0, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_36(self): + a, b, r = (0, 2, 0, 'dev', 1), (0, 1, 1, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_37(self): + a, b, r = (0, 2, 0, 'dev', 1), (0, 2, 0, 'final', 0), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_38(self): + a, b, r = (0, 2, 1, 'dev', 1), (0, 2, 0, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_39(self): + a, b, r = (0, 2, 1, 'dev', 1), (0, 2, 1, 'final', 0), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_40(self): + a, b, r = (1, 0, 0, 'dev', 1), (0, 1, 0, 'final', 0), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_41(self): + a, b, r = (1, 0, 0, 'dev', 1), (1, 0, 0, 'final', 0), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_42(self): + a, b, r = (0, 0, 1, 'final', 0), (0, 0, 1, 'dev', 1), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_43(self): + a, b, r = (0, 0, 1, 'final', 0), (0, 0, 2, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_44(self): + a, b, r = (0, 1, 0, 'final', 0), (0, 0, 2, 'dev', 1), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_45(self): + a, b, r = (0, 1, 0, 'final', 0), (0, 1, 1, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_46(self): + a, b, r = (0, 1, 0, 'final', 0), (0, 2, 0, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_47(self): + a, b, r = (0, 1, 1, 'final', 0), (0, 2, 0, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_48(self): + a, b, r = (0, 2, 0, 'final', 0), (0, 2, 0, 'dev', 1), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_49(self): + a, b, r = (0, 2, 0, 'final', 0), (0, 2, 1, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_50(self): + a, b, r = (0, 2, 1, 'final', 0), (0, 2, 1, 'dev', 1), -1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_51(self): + a, b, r = (0, 1, 0, 'final', 0), (1, 0, 0, 'dev', 1), 1 + self.assertEqual(compare_version_tuples(a, b), r) + + def test_compare_version_52(self): + a, b, r = (1, 0, 0, 'final', 0), (1, 0, 0, 'dev', 1), -1 + self.assertEqual(compare_version_tuples(a, b), r) + diff --git a/ui/options_general.ui b/ui/options_general.ui index fd4b40a9a..85cec3b27 100644 --- a/ui/options_general.ui +++ b/ui/options_general.ui @@ -132,6 +132,92 @@ + + + + true + + + + 0 + 0 + + + + Update Checking + + + + QLayout::SetDefaultConstraint + + + 9 + + + 6 + + + + + + 0 + 0 + + + + Include beta versions in update checks + + + + + + + + 0 + 0 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Check for updates during start-up + + + + + + + + 0 + 0 + + + + Days between update checks: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + +