PICARD-1045: Add program update checking

This commit is contained in:
Bob Swift
2018-07-17 15:36:32 -06:00
committed by Laurent Monin
parent 5c4853b71c
commit 3af25fdff6
8 changed files with 571 additions and 2 deletions

View File

@@ -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)

View File

@@ -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')

View File

@@ -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():

View File

@@ -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:"))

View File

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

152
picard/util/checkupdate.py Normal file
View File

@@ -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()

217
test/test_versioncompare.py Normal file
View File

@@ -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)

View File

@@ -132,6 +132,92 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Update Checking</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="horizontalSpacing">
<number>6</number>
</property>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="include_beta_versions">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Include beta versions in update checks</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="update_check_days">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="check_for_updates">
<property name="text">
<string>Check for updates during start-up</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Days between update checks:</string>
</property>
</widget>
</item>
<item row="2" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">