feat: ask for permission before update check

This commit is contained in:
Nick Bolton 2024-09-21 20:57:20 +01:00
parent 4f0e8cb844
commit b904cba040
13 changed files with 206 additions and 24 deletions

View file

@ -7,28 +7,28 @@
#
# Serial key value to pre-fill the activation dialog
# SYNERGY_TEST_SERIAL_KEY="DEADBEEF"
# DESKFLOW_TEST_SERIAL_KEY="DEADBEEF"
# Shows the test menu in the GUI (on by default in debug mode)
# SYNERGY_TEST_MENU=true
# DESKFLOW_TEST_MENU=true
# Version checker URL to use (useful for testing)
# SYNERGY_VERSION_URL="http://localhost:8787?version=v1"
# DESKFLOW_VERSION_URL="https://api.deskflow.org/version?fake=1.100.0"
# Enable debug logging in the GUI (on by default in debug mode)
# SYNERGY_GUI_DEBUG=true
# DESKFLOW_GUI_DEBUG=true
# Enable verbose logging in the GUI (always off by default)
# SYNERGY_GUI_VERBOSE=true
# DESKFLOW_GUI_VERBOSE=true
# Reset all settings and delete all data on startup
# SYNERGY_RESET_ALL=true
# DESKFLOW_RESET_ALL=true
# Enable the activation dialog (used for testing)
# SYNERGY_ENABLE_ACTIVATION=true
# DESKFLOW_ENABLE_ACTIVATION=true
# Show licensed product menu items, etc (used for testing)
# SYNERGY_LICENSED_PRODUCT=true
# DESKFLOW_LICENSED_PRODUCT=true
#
# Build

8
.vscode/tasks.json vendored
View file

@ -67,9 +67,9 @@
{
"label": "kill",
"type": "shell",
"command": "killall synergy; killall synergyc; killall synergys || true",
"command": "killall deskflow; killall deskflowc; killall deskflows || true",
"windows": {
"command": "taskkill /F /IM synergy.exe /IM synergyc.exe /IM synergys.exe; $true"
"command": "taskkill /F /IM deskflow.exe /IM deskflowc.exe /IM deskflows.exe; $true"
},
"presentation": {
"reveal": "silent"
@ -83,11 +83,11 @@
{
"label": "gui",
"type": "process",
"command": "${workspaceFolder}/build/bin/synergy",
"command": "${workspaceFolder}/build/bin/deskflow",
"dependsOn": ["build", "kill"],
"problemMatcher": [],
"windows": {
"command": "${workspaceFolder}/build/bin/synergy.exe"
"command": "${workspaceFolder}/build/bin/deskflow.exe"
}
},
{

View file

@ -570,7 +570,15 @@ void MainWindow::open() {
showAndActivate();
}
m_VersionChecker.checkLatest();
if (!m_AppConfig.enableUpdateCheck().has_value()) {
m_AppConfig.setEnableUpdateCheck(messages::showUpdateCheckOption(this));
}
if (m_AppConfig.enableUpdateCheck().value()) {
m_VersionChecker.checkLatest();
} else {
qDebug("update check disabled");
}
if (m_AppConfig.startedBefore()) {
m_CoreProcess.start();

View file

@ -17,6 +17,8 @@
#include "VersionChecker.h"
#include "env_vars.h"
#include <QLocale>
#include <QNetworkAccessManager>
#include <QNetworkReply>
@ -27,17 +29,21 @@
const char *const kVersion = DESKFLOW_VERSION;
VersionChecker::VersionChecker(std::shared_ptr<QNetworkAccessManager> network)
using namespace deskflow::gui;
VersionChecker::VersionChecker(
std::shared_ptr<QNetworkAccessManagerProxy> network)
: m_network(
network ? network : std::make_shared<QNetworkAccessManager>(this)) {
network ? network : std::make_shared<QNetworkAccessManagerProxy>()) {
m_network->init();
connect(
m_network.get(), SIGNAL(finished(QNetworkReply *)), this,
SLOT(replyFinished(QNetworkReply *)));
m_network.get(), &QNetworkAccessManagerProxy::finished, this,
&VersionChecker::replyFinished);
}
void VersionChecker::checkLatest() const {
const QString url =
qEnvironmentVariable("DESKFLOW_VERSION_URL", DESKFLOW_VERSION_URL);
const QString url = env_vars::versionUrl();
qDebug("checking for updates at: %s", qPrintable(url));
auto request = QNetworkRequest(url);
auto userAgent = QString("Deskflow %1 on %2")
.arg(kVersion)
@ -53,7 +59,10 @@ void VersionChecker::replyFinished(QNetworkReply *reply) {
auto newestVersion = QString(reply->readAll());
if (!newestVersion.isEmpty() &&
compareVersions(DESKFLOW_VERSION, newestVersion) > 0) {
qDebug("update found: %s", qPrintable(newestVersion));
emit updateFound(newestVersion);
} else {
qDebug("no updates found");
}
}

View file

@ -17,6 +17,8 @@
#pragma once
#include "proxy/QNetworkAccessManagerProxy.h"
#include <QObject>
#include <QString>
#include <memory>
@ -26,13 +28,16 @@ class QNetworkReply;
class VersionCheckerTests;
class VersionChecker : public QObject {
using QNetworkAccessManagerProxy =
deskflow::gui::proxy::QNetworkAccessManagerProxy;
Q_OBJECT
friend class VersionCheckerTests;
public:
explicit VersionChecker(
std::shared_ptr<QNetworkAccessManager> network = nullptr);
std::shared_ptr<QNetworkAccessManagerProxy> network = nullptr);
void checkLatest() const;
public slots:
void replyFinished(QNetworkReply *reply);
@ -50,5 +55,5 @@ private:
*/
static int getStageVersion(QString stage);
std::shared_ptr<QNetworkAccessManager> m_network;
std::shared_ptr<QNetworkAccessManagerProxy> m_network;
};

View file

@ -94,6 +94,7 @@ const char *const AppConfig::m_SettingsName[] = {
"mainWindowPosition",
"showDevThanks",
"showCloseReminder",
"enableUpdateCheck",
};
AppConfig::AppConfig(
@ -175,6 +176,8 @@ void AppConfig::recallFromCurrentScope() {
getFromCurrentScope(kShowDevThanks, m_ShowDevThanks).toBool();
m_ShowCloseReminder =
getFromCurrentScope(kShowCloseReminder, m_ShowCloseReminder).toBool();
m_EnableUpdateCheck = getFromCurrentScope<bool>(
kEnableUpdateCheck, [](const QVariant &v) { return v.toBool(); });
}
void AppConfig::recallScreenName() {
@ -231,11 +234,11 @@ void AppConfig::commit() {
setInCurrentScope(kInvertConnection, m_InvertConnection);
setInCurrentScope(kEnableService, m_EnableService);
setInCurrentScope(kCloseToTray, m_CloseToTray);
setInCurrentScope(kShowDevThanks, m_ShowDevThanks);
setInCurrentScope(kShowCloseReminder, m_ShowCloseReminder);
setInCurrentScope(kMainWindowSize, m_MainWindowSize);
setInCurrentScope(kMainWindowPosition, m_MainWindowPosition);
setInCurrentScope(kShowDevThanks, m_ShowDevThanks);
setInCurrentScope(kShowCloseReminder, m_ShowCloseReminder);
setInCurrentScope(kEnableUpdateCheck, m_EnableUpdateCheck);
}
if (m_TlsChanged) {
@ -535,6 +538,10 @@ bool AppConfig::showDevThanks() const { return m_ShowDevThanks; }
bool AppConfig::showCloseReminder() const { return m_ShowCloseReminder; }
std::optional<bool> AppConfig::enableUpdateCheck() const {
return m_EnableUpdateCheck;
}
///////////////////////////////////////////////////////////////////////////////
// End getters
///////////////////////////////////////////////////////////////////////////////
@ -661,6 +668,10 @@ void AppConfig::setShowCloseReminder(bool value) {
m_ShowCloseReminder = value;
}
void AppConfig::setEnableUpdateCheck(bool value) {
m_EnableUpdateCheck = value;
}
///////////////////////////////////////////////////////////////////////////////
// End setters
///////////////////////////////////////////////////////////////////////////////

View file

@ -106,6 +106,7 @@ private:
kMainWindowPosition = 40,
kShowDevThanks = 41,
kShowCloseReminder = 42,
kEnableUpdateCheck = 43,
};
public:
@ -180,6 +181,7 @@ public:
std::optional<QPoint> mainWindowPosition() const;
bool showDevThanks() const;
bool showCloseReminder() const;
std::optional<bool> enableUpdateCheck() const;
//
// Setters (overrides)
@ -224,6 +226,7 @@ public:
void setMainWindowPosition(const QPoint &position);
void setShowDevThanks(bool show);
void setShowCloseReminder(bool show);
void setEnableUpdateCheck(bool value);
/// @brief Sets the user preference to load from SystemScope.
/// @param [in] value
@ -332,6 +335,7 @@ private:
bool m_ShowDevThanks = !deskflow::gui::license::isLicensedProduct();
bool m_LoadFromSystemScope = false;
bool m_ShowCloseReminder = true;
std::optional<bool> m_EnableUpdateCheck;
/**
* @brief Flag is set when any TLS is setting is changed, and is reset

29
src/lib/gui/env_vars.h Normal file
View file

@ -0,0 +1,29 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package 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, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <QtCore>
namespace deskflow::gui::env_vars {
inline QString versionUrl() {
return qEnvironmentVariable("DESKFLOW_VERSION_URL", DESKFLOW_VERSION_URL);
}
} // namespace deskflow::gui::env_vars

View file

@ -18,8 +18,10 @@
#include "messages.h"
#include "Logger.h"
#include "common/constants.h"
#include "common/version.h"
#include "constants.h"
#include "env_vars.h"
#include "gui/license/license_utils.h"
#include "styles.h"
@ -314,4 +316,19 @@ void showWaylandLibraryError(QWidget *parent) {
.arg(kUrlBugReport, kColorSecondary));
}
bool showUpdateCheckOption(QWidget *parent) {
QMessageBox message(parent);
message.addButton(QObject::tr("Close"), QMessageBox::RejectRole);
const auto checkButton = message.addButton(
QObject::tr("Check for updates"), QMessageBox::AcceptRole);
message.setText(
QString("<p>Would you like to check for updates when %1 starts?</p>"
"<p>Checking for updates requires an Internet connection.</p>"
"<p>URL: <pre>%2</pre></p>")
.arg(kAppName, env_vars::versionUrl()));
message.exec();
return message.clickedButton() == checkButton;
}
} // namespace deskflow::gui::messages

View file

@ -56,4 +56,6 @@ void showWaylandExperimental(QWidget *parent);
void showWaylandLibraryError(QWidget *parent);
bool showUpdateCheckOption(QWidget *parent);
} // namespace deskflow::gui::messages

View file

@ -0,0 +1,37 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package 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, see <http://www.gnu.org/licenses/>.
*/
#include "QNetworkAccessManagerProxy.h"
#include <QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
namespace deskflow::gui::proxy {
void QNetworkAccessManagerProxy::init() {
m_network = std::make_shared<QNetworkAccessManager>();
connect(
m_network.get(), &QNetworkAccessManager::finished, this,
[this](QNetworkReply *reply) { emit finished(reply); });
}
void QNetworkAccessManagerProxy::get(const QNetworkRequest &request) const {
m_network->get(request);
}
} // namespace deskflow::gui::proxy

View file

@ -0,0 +1,39 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package 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, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QNetworkAccessManager>
#include <QObject>
namespace deskflow::gui::proxy {
class QNetworkAccessManagerProxy : public QObject {
Q_OBJECT
public:
virtual void init();
virtual void get(const QNetworkRequest &request) const;
signals:
void finished(QNetworkReply *reply);
private:
std::shared_ptr<QNetworkAccessManager> m_network;
};
} // namespace deskflow::gui::proxy

View file

@ -16,9 +16,14 @@
*/
#include "gui/VersionChecker.h"
#include "shared/gui/TestQtCoreApp.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace deskflow::gui::proxy;
using namespace testing;
class VersionCheckerTests : public ::testing::Test {
protected:
int compareVersions(const QString &left, const QString &right) {
@ -26,6 +31,22 @@ protected:
}
};
class MockNetworkAccessManager : public QNetworkAccessManagerProxy {
public:
MOCK_METHOD(void, init, (), (override));
MOCK_METHOD(void, get, (const QNetworkRequest &request), (const, override));
};
TEST_F(VersionCheckerTests, checkLatest_callsNetworkGet) {
TestQtCoreApp app;
const auto network = std::make_shared<NiceMock<MockNetworkAccessManager>>();
const VersionChecker checker(network);
EXPECT_CALL(*network, get(testing::_)).Times(1);
checker.checkLatest();
}
TEST_F(VersionCheckerTests, compareVersions_major_isValid) {
EXPECT_EQ(compareVersions("1.0.0", "2.0.0"), 1);
EXPECT_EQ(compareVersions("2.0.0", "1.0.0"), -1);