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
+
+
+
+
+
+
+
-