You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gentoo-overlay/app-admin/keepassxc/files/keepassxc-2.4.0-update_chec...

446 lines
18 KiB

From 28994fef0d2c04690490f34b5da87c42e60a6b5e Mon Sep 17 00:00:00 2001
From: Jonathan White <support@dmapps.us>
Date: Tue, 9 Apr 2019 21:06:13 -0400
Subject: [PATCH] Enhance update checker
* Reduce initial update check notification to 500 ms to prevent inappropriately stealing focus from user
* Add build flag WITH_XC_UPDATECHECK which defaults to ON
* Update checks are resolved every 7 days instead of every time the application is started
* Better checks for beta builds; ignore snapshots
* Increase test cases
---
CMakeLists.txt | 5 ++
src/CMakeLists.txt | 1 +
src/config-keepassx.h.cmake | 1 +
src/core/Clock.cpp | 1 +
src/gui/ApplicationSettingsWidget.cpp | 25 ++++++-
src/gui/ApplicationSettingsWidget.h | 2 +
src/gui/ApplicationSettingsWidgetGeneral.ui | 39 +++++++---
src/gui/MainWindow.cpp | 12 ++--
src/updatecheck/UpdateChecker.cpp | 79 +++++++++++++--------
src/updatecheck/UpdateChecker.h | 2 +-
tests/TestUpdateCheck.cpp | 35 ++++++---
11 files changed, 147 insertions(+), 55 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 658548f7..4f8a419c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,6 +49,7 @@ option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
+option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
if(APPLE)
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
endif()
@@ -76,6 +77,10 @@ else()
set(WITH_XC_CRYPTO_SSH OFF)
endif()
+if(WITH_XC_UPDATECHECK)
+ set(WITH_XC_NETWORKING ON)
+endif()
+
set(KEEPASSXC_VERSION_MAJOR "2")
set(KEEPASSXC_VERSION_MINOR "4")
set(KEEPASSXC_VERSION_PATCH "0")
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 110dc606..31f29033 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -195,6 +195,7 @@ add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible wit
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
+add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
if(APPLE)
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
endif()
diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake
index 7d701886..2acff446 100644
--- a/src/config-keepassx.h.cmake
+++ b/src/config-keepassx.h.cmake
@@ -20,6 +20,7 @@
#cmakedefine WITH_XC_KEESHARE
#cmakedefine WITH_XC_KEESHARE_INSECURE
#cmakedefine WITH_XC_KEESHARE_SECURE
+#cmakedefine WITH_XC_UPDATECHECK
#cmakedefine WITH_XC_TOUCHID
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
diff --git a/src/core/Clock.cpp b/src/core/Clock.cpp
index 88ac4fb7..be9e91dc 100644
--- a/src/core/Clock.cpp
+++ b/src/core/Clock.cpp
@@ -30,6 +30,7 @@ QDateTime Clock::currentDateTime()
uint Clock::currentSecondsSinceEpoch()
{
+ // TODO: change to toSecsSinceEpoch() when min Qt >= 5.8
return instance().currentDateTimeImpl().toTime_t();
}
diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp
index 90b851bd..e6a7068d 100644
--- a/src/gui/ApplicationSettingsWidget.cpp
+++ b/src/gui/ApplicationSettingsWidget.cpp
@@ -91,8 +91,15 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
m_secUi->touchIDResetSpinBox, SLOT(setEnabled(bool)));
// clang-format on
-#ifndef WITH_XC_NETWORKING
+#ifdef WITH_XC_UPDATECHECK
+ connect(m_generalUi->checkForUpdatesOnStartupCheckBox, SIGNAL(toggled(bool)), SLOT(checkUpdatesToggled(bool)));
+#else
m_generalUi->checkForUpdatesOnStartupCheckBox->setVisible(false);
+ m_generalUi->checkForUpdatesIncludeBetasCheckBox->setVisible(false);
+ m_generalUi->checkUpdatesSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed);
+#endif
+
+#ifndef WITH_XC_NETWORKING
m_secUi->privacy->setVisible(false);
#endif
@@ -336,3 +343,19 @@ void ApplicationSettingsWidget::enableToolbarSettings(bool checked)
m_generalUi->toolButtonStyleComboBox->setEnabled(!checked);
m_generalUi->toolButtonStyleLabel->setEnabled(!checked);
}
+
+void ApplicationSettingsWidget::rememberDatabasesToggled(bool checked)
+{
+ if (!checked) {
+ m_generalUi->rememberLastKeyFilesCheckBox->setChecked(false);
+ m_generalUi->openPreviousDatabasesOnStartupCheckBox->setChecked(false);
+ }
+
+ m_generalUi->rememberLastKeyFilesCheckBox->setEnabled(checked);
+ m_generalUi->openPreviousDatabasesOnStartupCheckBox->setEnabled(checked);
+}
+
+void ApplicationSettingsWidget::checkUpdatesToggled(bool checked)
+{
+ m_generalUi->checkForUpdatesIncludeBetasCheckBox->setEnabled(checked);
+}
diff --git a/src/gui/ApplicationSettingsWidget.h b/src/gui/ApplicationSettingsWidget.h
index ffcfea2b..86688b70 100644
--- a/src/gui/ApplicationSettingsWidget.h
+++ b/src/gui/ApplicationSettingsWidget.h
@@ -56,6 +56,8 @@ private slots:
void autoSaveToggled(bool checked);
void systrayToggled(bool checked);
void enableToolbarSettings(bool checked);
+ void rememberDatabasesToggled(bool checked);
+ void checkUpdatesToggled(bool checked);
private:
QWidget* const m_secWidget;
diff --git a/src/gui/ApplicationSettingsWidgetGeneral.ui b/src/gui/ApplicationSettingsWidgetGeneral.ui
index 798971bf..678b55fa 100644
--- a/src/gui/ApplicationSettingsWidgetGeneral.ui
+++ b/src/gui/ApplicationSettingsWidgetGeneral.ui
@@ -86,10 +86,40 @@
<item>
<widget class="QCheckBox" name="checkForUpdatesOnStartupCheckBox">
<property name="text">
- <string>Check for updates at application startup</string>
+ <string>Check for updates at application startup once per week</string>
</property>
</widget>
</item>
+ <item>
+ <layout class="QHBoxLayout" name="checkUpdatesSubLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="checkUpdatesSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkForUpdatesIncludeBetasCheckBox">
+ <property name="text">
+ <string>Include beta releases when checking for updates</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
@@ -186,13 +216,6 @@
<string>General</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
- <item>
- <widget class="QCheckBox" name="checkForUpdatesIncludeBetasCheckBox">
- <property name="text">
- <string>Include pre-releases when checking for updates</string>
- </property>
- </widget>
- </item>
<item>
<widget class="QCheckBox" name="toolbarHideCheckBox">
<property name="text">
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 6e3c96af..6776a59d 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -41,7 +41,7 @@
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
-#ifdef WITH_XC_NETWORKING
+#ifdef WITH_XC_UPDATECHECK
#include "gui/MessageBox.h"
#include "gui/UpdateCheckDialog.h"
#include "updatecheck/UpdateChecker.h"
@@ -372,12 +372,12 @@ MainWindow::MainWindow()
setUnifiedTitleAndToolBarOnMac(true);
#endif
-#ifdef WITH_XC_NETWORKING
+#ifdef WITH_XC_UPDATECHECK
connect(m_ui->actionCheckForUpdates, SIGNAL(triggered()), SLOT(showUpdateCheckDialog()));
connect(UpdateChecker::instance(),
SIGNAL(updateCheckFinished(bool, QString, bool)),
SLOT(hasUpdateAvailable(bool, QString, bool)));
- QTimer::singleShot(3000, this, SLOT(showUpdateCheckStartup()));
+ QTimer::singleShot(500, this, SLOT(showUpdateCheckStartup()));
#else
m_ui->actionCheckForUpdates->setVisible(false);
#endif
@@ -687,7 +687,7 @@ void MainWindow::showAboutDialog()
void MainWindow::showUpdateCheckStartup()
{
-#ifdef WITH_XC_NETWORKING
+#ifdef WITH_XC_UPDATECHECK
if (!config()->get("UpdateCheckMessageShown", false).toBool()) {
auto result =
MessageBox::question(this,
@@ -710,7 +710,7 @@ void MainWindow::showUpdateCheckStartup()
void MainWindow::hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested)
{
-#ifdef WITH_XC_NETWORKING
+#ifdef WITH_XC_UPDATECHECK
if (hasUpdate && !isManuallyRequested) {
auto* updateCheckDialog = new UpdateCheckDialog(this);
updateCheckDialog->showUpdateCheckResponse(hasUpdate, version);
@@ -725,7 +725,7 @@ void MainWindow::hasUpdateAvailable(bool hasUpdate, const QString& version, bool
void MainWindow::showUpdateCheckDialog()
{
-#ifdef WITH_XC_NETWORKING
+#ifdef WITH_XC_UPDATECHECK
updateCheck()->checkForUpdates(true);
auto* updateCheckDialog = new UpdateCheckDialog(this);
updateCheckDialog->show();
diff --git a/src/updatecheck/UpdateChecker.cpp b/src/updatecheck/UpdateChecker.cpp
index 4272410b..14531290 100644
--- a/src/updatecheck/UpdateChecker.cpp
+++ b/src/updatecheck/UpdateChecker.cpp
@@ -17,6 +17,7 @@
#include "UpdateChecker.h"
#include "config-keepassx.h"
+#include "core/Clock.h"
#include "core/Config.h"
#include <QJsonObject>
#include <QNetworkAccessManager>
@@ -38,24 +39,28 @@ UpdateChecker::~UpdateChecker()
void UpdateChecker::checkForUpdates(bool manuallyRequested)
{
+ auto nextCheck = config()->get("GUI/CheckForUpdatesNextCheck", 0).toULongLong();
m_isManuallyRequested = manuallyRequested;
- m_bytesReceived.clear();
- QString apiUrlStr = QString("https://api.github.com/repos/keepassxreboot/keepassxc/releases");
+ if (m_isManuallyRequested || Clock::currentSecondsSinceEpoch() >= nextCheck) {
+ m_bytesReceived.clear();
- if (!config()->get("GUI/CheckForUpdatesIncludeBetas", false).toBool()) {
- apiUrlStr += "/latest";
- }
+ QString apiUrlStr = QString("https://api.github.com/repos/keepassxreboot/keepassxc/releases");
+
+ if (!config()->get("GUI/CheckForUpdatesIncludeBetas", false).toBool()) {
+ apiUrlStr += "/latest";
+ }
- QUrl apiUrl = QUrl(apiUrlStr);
+ QUrl apiUrl = QUrl(apiUrlStr);
- QNetworkRequest request(apiUrl);
- request.setRawHeader("Accept", "application/json");
+ QNetworkRequest request(apiUrl);
+ request.setRawHeader("Accept", "application/json");
- m_reply = m_netMgr->get(request);
+ m_reply = m_netMgr->get(request);
- connect(m_reply, &QNetworkReply::finished, this, &UpdateChecker::fetchFinished);
- connect(m_reply, &QIODevice::readyRead, this, &UpdateChecker::fetchReadyRead);
+ connect(m_reply, &QNetworkReply::finished, this, &UpdateChecker::fetchFinished);
+ connect(m_reply, &QIODevice::readyRead, this, &UpdateChecker::fetchReadyRead);
+ }
}
void UpdateChecker::fetchReadyRead()
@@ -84,8 +89,12 @@ void UpdateChecker::fetchFinished()
if (!jsonObject.value("tag_name").isUndefined()) {
version = jsonObject.value("tag_name").toString();
- hasNewVersion = compareVersions(version, QString(KEEPASSXC_VERSION));
+ hasNewVersion = compareVersions(QString(KEEPASSXC_VERSION), version);
}
+
+ // Check again in 7 days
+ // TODO: change to toSecsSinceEpoch() when min Qt >= 5.8
+ config()->set("GUI/CheckForUpdatesNextCheck", Clock::currentDateTime().addDays(7).toTime_t());
} else {
version = "error";
}
@@ -93,38 +102,46 @@ void UpdateChecker::fetchFinished()
emit updateCheckFinished(hasNewVersion, version, m_isManuallyRequested);
}
-bool UpdateChecker::compareVersions(const QString& remoteVersion, const QString& localVersion)
+bool UpdateChecker::compareVersions(const QString& localVersion, const QString& remoteVersion)
{
+ // Quick full-string equivalence check
if (localVersion == remoteVersion) {
- return false; // Currently using updated version
+ return false;
}
- QRegularExpression verRegex("^(\\d+(\\.\\d+){0,2})(-\\w+)?$", QRegularExpression::CaseInsensitiveOption);
+ QRegularExpression verRegex(R"(^((?:\d+\.){2}\d+)(?:-(\w+?)(\d+)?)?$)");
- QRegularExpressionMatch lmatch = verRegex.match(localVersion);
- QRegularExpressionMatch rmatch = verRegex.match(remoteVersion);
+ auto lmatch = verRegex.match(localVersion);
+ auto rmatch = verRegex.match(remoteVersion);
- if (!lmatch.captured(1).isNull() && !rmatch.captured(1).isNull()) {
- if (lmatch.captured(1) == rmatch.captured(1) && !lmatch.captured(3).isNull()) {
- // Same version, but installed version has snapshot/beta suffix and should be updated to stable
- return true;
- }
+ auto lVersion = lmatch.captured(1).split(".");
+ auto lSuffix = lmatch.captured(2);
+ auto lBetaNum = lmatch.captured(3);
- QStringList lparts = lmatch.captured(1).split(".");
- QStringList rparts = rmatch.captured(1).split(".");
+ auto rVersion = rmatch.captured(1).split(".");
+ auto rSuffix = rmatch.captured(2);
+ auto rBetaNum = rmatch.captured(3);
- if (lparts.length() < 3)
- lparts << "0";
+ if (!lVersion.isEmpty() && !rVersion.isEmpty()) {
+ if (lSuffix.compare("snapshot", Qt::CaseInsensitive) == 0) {
+ // Snapshots are not checked for version updates
+ return false;
+ }
- if (rparts.length() < 3)
- rparts << "0";
+ // Check "-beta[X]" versions
+ if (lVersion == rVersion && !lSuffix.isEmpty()) {
+ // Check if stable version has been released or new beta is available
+ // otherwise the version numbers are equal
+ return rSuffix.isEmpty() || lBetaNum.toInt() < rBetaNum.toInt();
+ }
for (int i = 0; i < 3; i++) {
- int l = lparts[i].toInt();
- int r = rparts[i].toInt();
+ int l = lVersion[i].toInt();
+ int r = rVersion[i].toInt();
- if (l == r)
+ if (l == r) {
continue;
+ }
if (l > r) {
return false; // Installed version is newer than release
diff --git a/src/updatecheck/UpdateChecker.h b/src/updatecheck/UpdateChecker.h
index ac6471d6..64430bda 100644
--- a/src/updatecheck/UpdateChecker.h
+++ b/src/updatecheck/UpdateChecker.h
@@ -31,7 +31,7 @@ public:
~UpdateChecker() override;
void checkForUpdates(bool manuallyRequested);
- static bool compareVersions(const QString& remoteVersion, const QString& localVersion);
+ static bool compareVersions(const QString& localVersion, const QString& remoteVersion);
static UpdateChecker* instance();
signals:
diff --git a/tests/TestUpdateCheck.cpp b/tests/TestUpdateCheck.cpp
index 8cba43b1..ff709cd5 100644
--- a/tests/TestUpdateCheck.cpp
+++ b/tests/TestUpdateCheck.cpp
@@ -29,13 +29,32 @@ void TestUpdateCheck::initTestCase()
void TestUpdateCheck::testCompareVersion()
{
- // Remote Version , Installed Version
- QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("2.3.4")), true);
- QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.4.0")), false);
+ // No upgrade
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.0")), false);
- QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.0-beta1")), true);
- QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta2"), QString("2.3.0-beta1")), true);
- QCOMPARE(UpdateChecker::compareVersions(QString("2.3.4"), QString("2.4.0-snapshot")), false);
- QCOMPARE(UpdateChecker::compareVersions(QString("invalid"), QString("2.4.0")), false);
- QCOMPARE(UpdateChecker::compareVersions(QString(""), QString("2.4.0")), false);
+
+ // First digit upgrade
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("3.0.0")), true);
+ QCOMPARE(UpdateChecker::compareVersions(QString("3.0.0"), QString("2.4.0")), false);
+
+ // Second digit upgrade
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.4"), QString("2.4.0")), true);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("2.3.4")), false);
+
+ // Third digit upgrade
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.1")), true);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.1"), QString("2.3.0")), false);
+
+ // Beta builds
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.0-beta1")), false);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.1-beta1")), true);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta1"), QString("2.3.0")), true);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta"), QString("2.3.0-beta1")), true);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta1"), QString("2.3.0-beta")), false);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta1"), QString("2.3.0-beta2")), true);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta2"), QString("2.3.0-beta1")), false);
+
+ // Snapshot and invalid data
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.3.4-snapshot"), QString("2.4.0")), false);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("invalid")), false);
+ QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("")), false);
}
--
2.21.0