diff --git a/CMakeLists.txt b/CMakeLists.txt index d048908..13e0ea0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ set(META_VERSION_MAJOR 1) set(META_VERSION_MINOR 5) set(META_VERSION_PATCH 0) set(META_RELEASE_DATE "2024-02-06") -set(META_SOVERSION 12) +set(META_SOVERSION 13) set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON) project(${META_PROJECT_NAME}) diff --git a/syncthingconnector/syncthingconnection.cpp b/syncthingconnector/syncthingconnection.cpp index c52bc6f..c1ae149 100644 --- a/syncthingconnector/syncthingconnection.cpp +++ b/syncthingconnector/syncthingconnection.cpp @@ -25,6 +25,11 @@ #include #include +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) +#include +#define SYNCTHINGCONNECTION_SUPPORT_METERED +#endif + #include #include @@ -108,6 +113,7 @@ SyncthingConnection::SyncthingConnection( , m_dirStatsAltered(false) , m_recordFileChanges(false) , m_useDeprecatedRoutes(true) + , m_pausingOnMeteredConnection(false) { m_trafficPollTimer.setInterval(SyncthingConnectionSettings::defaultTrafficPollInterval); m_trafficPollTimer.setTimerType(Qt::VeryCoarseTimer); @@ -129,6 +135,14 @@ SyncthingConnection::SyncthingConnection( setupTestData(); #endif + // initialize handling of metered connections +#ifdef SYNCTHINGCONNECTION_SUPPORT_METERED + QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Metered); + if (const auto *const networkInformation = QNetworkInformation::instance()) { + QObject::connect(networkInformation, &QNetworkInformation::isMeteredChanged, this, &SyncthingConnection::handleMeteredConnection); + } +#endif + setLoggingFlags(loggingFlags); // allow initializing the default value for m_useDeprecatedRoutes via environment variable @@ -248,6 +262,50 @@ void SyncthingConnection::disablePolling() setErrorsPollInterval(0); } +/*! + * \brief Sets whether to pause all devices on metered connections. + */ +void SyncthingConnection::setPausingOnMeteredConnection(bool pausingOnMeteredConnection) +{ + if (m_pausingOnMeteredConnection != pausingOnMeteredConnection) { + m_pausingOnMeteredConnection = pausingOnMeteredConnection; + if (m_hasConfig) { + handleMeteredConnection(); + } + } +} + +/*! + * \brief Ensures that devices are paused/resumed depending on whether the network connection is metered. + */ +void SyncthingConnection::handleMeteredConnection() +{ +#ifdef SYNCTHINGCONNECTION_SUPPORT_METERED + const auto *const networkInformation = QNetworkInformation::instance(); + if (!networkInformation->supports(QNetworkInformation::Feature::Metered)) { + return; + } + if (networkInformation->isMetered() && m_pausingOnMeteredConnection) { + auto hasDevicesToPause = false; + m_devsPausedDueToMeteredConnection.reserve(static_cast(m_devs.size())); + for (const auto &device : m_devs) { + if (!device.paused && device.status != SyncthingDevStatus::ThisDevice) { + if (!m_devsPausedDueToMeteredConnection.contains(device.id)) { + m_devsPausedDueToMeteredConnection << device.id; + hasDevicesToPause = true; + } + } + } + if (hasDevicesToPause) { + pauseDevice(m_devsPausedDueToMeteredConnection); + } + } else { + resumeDevice(m_devsPausedDueToMeteredConnection); + m_devsPausedDueToMeteredConnection.clear(); + } +#endif +} + /*! * \brief Connects asynchronously to Syncthing. Does nothing if already connected. * @@ -445,6 +503,7 @@ void SyncthingConnection::continueReconnecting() m_hasDiskEvents = false; m_dirs.clear(); m_devs.clear(); + m_devsPausedDueToMeteredConnection.clear(); m_lastConnectionsUpdateEvent = 0; m_lastConnectionsUpdateTime = DateTime(); m_lastFileEvent = 0; @@ -886,6 +945,7 @@ bool SyncthingConnection::applySettings(SyncthingConnectionSettings &connectionS setRequestTimeout(connectionSettings.requestTimeout); setLongPollingTimeout(connectionSettings.longPollingTimeout); setStatusComputionFlags(connectionSettings.statusComputionFlags); + setPausingOnMeteredConnection(connectionSettings.pauseOnMeteredConnection); return reconnectRequired; } diff --git a/syncthingconnector/syncthingconnection.h b/syncthingconnector/syncthingconnection.h index 2622cbb..560249b 100644 --- a/syncthingconnector/syncthingconnection.h +++ b/syncthingconnector/syncthingconnection.h @@ -17,6 +17,11 @@ #include #include +#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) +#include +#define SYNCTHINGCONNECTION_SUPPORT_METERED +#endif + #include #include #include @@ -92,6 +97,7 @@ class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnection : public QObject { Q_PROPERTY(QStringList deviceIds READ deviceIds) Q_PROPERTY(QJsonObject rawConfig READ rawConfig NOTIFY newConfig) Q_PROPERTY(bool useDeprecatedRoutes READ isUsingDeprecatedRoutes WRITE setUseDeprecatedRoutes) + Q_PROPERTY(bool pausingOnMeteredConnection READ isPausingOnMeteredConnection WRITE setPausingOnMeteredConnection) public: explicit SyncthingConnection(const QString &syncthingUrl = QStringLiteral("http://localhost:8080"), const QByteArray &apiKey = QByteArray(), @@ -145,6 +151,8 @@ public: void setRequestTimeout(int requestTimeout); int longPollingTimeout() const; void setLongPollingTimeout(int longPollingTimeout); + bool isPausingOnMeteredConnection() const; + void setPausingOnMeteredConnection(bool pausingOnMeteredConnection); // getter for information retrieved from Syncthing const QString &configDir() const; @@ -340,6 +348,7 @@ private Q_SLOTS: void handleAdditionalRequestCanceled(); void handleSslErrors(const QList &errors); void handleRedirection(const QUrl &url); + void handleMeteredConnection(); void recalculateStatus(); QString configPath() const; QByteArray changeConfigVerb() const; @@ -415,6 +424,7 @@ private: bool m_hasDiskEvents; std::vector m_dirs; std::vector m_devs; + QStringList m_devsPausedDueToMeteredConnection; SyncthingEventId m_lastConnectionsUpdateEvent; CppUtilities::DateTime m_lastConnectionsUpdateTime; SyncthingEventId m_lastFileEvent = 0; @@ -433,6 +443,7 @@ private: bool m_dirStatsAltered; bool m_recordFileChanges; bool m_useDeprecatedRoutes; + bool m_pausingOnMeteredConnection; }; /*! @@ -768,6 +779,14 @@ inline void SyncthingConnection::setLongPollingTimeout(int longPollingTimeout) m_longPollingTimeout = longPollingTimeout; } +/*! + * \brief Returns whether to pause all devices on metered connections. + */ +inline bool SyncthingConnection::isPausingOnMeteredConnection() const +{ + return m_pausingOnMeteredConnection; +} + /*! * \brief Returns what information is considered to compute the overall status returned by status(). */ diff --git a/syncthingconnector/syncthingconnection_requests.cpp b/syncthingconnector/syncthingconnection_requests.cpp index 76034ca..61857de 100644 --- a/syncthingconnector/syncthingconnection_requests.cpp +++ b/syncthingconnector/syncthingconnection_requests.cpp @@ -347,7 +347,7 @@ bool SyncthingConnection::pauseResumeDevice(const QStringList &devIds, bool paus if (devIds.isEmpty()) { return false; } - if (!isConnected()) { + if (!m_hasConfig) { emit error(tr("Unable to pause/resume a devices when not connected"), SyncthingErrorCategory::SpecificRequest, QNetworkReply::NoError); return false; } @@ -794,6 +794,9 @@ void SyncthingConnection::readDevs(const QJsonArray &devs) m_devs.swap(newDevs); emit this->newDevices(m_devs); + if (m_pausingOnMeteredConnection) { + handleMeteredConnection(); + } } // status of Syncthing (own ID, startup time) diff --git a/syncthingconnector/syncthingconnectionsettings.h b/syncthingconnector/syncthingconnectionsettings.h index 7a7a455..20561da 100644 --- a/syncthingconnector/syncthingconnectionsettings.h +++ b/syncthingconnector/syncthingconnectionsettings.h @@ -53,6 +53,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnectionSettings { QList expectedSslErrors; SyncthingStatusComputionFlags statusComputionFlags = SyncthingStatusComputionFlags::Default; bool autoConnect = false; + bool pauseOnMeteredConnection = false; static QList compileSslErrors(const QSslCertificate &trustedCert); bool loadHttpsCert(); diff --git a/syncthingwidgets/misc/syncthinglauncher.cpp b/syncthingwidgets/misc/syncthinglauncher.cpp index f92f6cd..40301c1 100644 --- a/syncthingwidgets/misc/syncthinglauncher.cpp +++ b/syncthingwidgets/misc/syncthinglauncher.cpp @@ -1,9 +1,16 @@ #include "./syncthinglauncher.h" +#include + #include "../settings/settings.h" #include +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) +#include +#define SYNCTHINGCONNECTION_SUPPORT_METERED +#endif + #include #include #include @@ -31,13 +38,17 @@ SyncthingLauncher *SyncthingLauncher::s_mainInstance = nullptr; */ SyncthingLauncher::SyncthingLauncher(QObject *parent) : QObject(parent) + , m_lastLauncherSettings(nullptr) , m_guiListeningUrlSearch("Access the GUI via the following URL: ", "\n\r", std::string_view(), std::bind(&SyncthingLauncher::handleGuiListeningUrlFound, this, std::placeholders::_1, std::placeholders::_2)) #ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING , m_libsyncthingLogLevel(LibSyncthing::LogLevel::Info) #endif , m_manuallyStopped(true) + , m_stoppedMetered(false) , m_emittingOutput(false) + , m_useLibSyncthing(false) + , m_stopOnMeteredConnection(false) { connect(&m_process, &SyncthingProcess::readyRead, this, &SyncthingLauncher::handleProcessReadyRead, Qt::QueuedConnection); connect(&m_process, static_cast(&SyncthingProcess::finished), this, @@ -45,6 +56,15 @@ SyncthingLauncher::SyncthingLauncher(QObject *parent) connect(&m_process, &SyncthingProcess::stateChanged, this, &SyncthingLauncher::handleProcessStateChanged, Qt::QueuedConnection); connect(&m_process, &SyncthingProcess::errorOccurred, this, &SyncthingLauncher::errorOccurred, Qt::QueuedConnection); connect(&m_process, &SyncthingProcess::confirmKill, this, &SyncthingLauncher::confirmKill); + + // initialize handling of metered connections +#ifdef SYNCTHINGCONNECTION_SUPPORT_METERED + QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Metered); + if (const auto *const networkInformation = QNetworkInformation::instance(); networkInformation->supports(QNetworkInformation::Feature::Metered)) { + connect(networkInformation, &QNetworkInformation::isMeteredChanged, this, [this](bool isMetered) { setNetworkConnectionMetered(isMetered); }); + setNetworkConnectionMetered(networkInformation->isMetered()); + } +#endif } /*! @@ -60,6 +80,38 @@ void SyncthingLauncher::setEmittingOutput(bool emittingOutput) emit outputAvailable(std::move(data)); } +/*! + * \brief Sets whether the current network connection is metered and stops/starts Syncthing accordingly as needed. + * \remarks + * - This is detected and monitored automatically. A manually set value will be overridden again on the next change. + * - One may set this manually for testing purposes or in case the automatic detection is not supported (then + * isNetworkConnectionMetered() returns a std::optional without value). + */ +void SyncthingLauncher::setNetworkConnectionMetered(std::optional metered) +{ + if (metered != m_metered) { + m_metered = metered; + if (m_stopOnMeteredConnection) { + if (metered.value_or(false)) { + terminateDueToMeteredConnection(); + } else if (!metered.value_or(true) && m_stoppedMetered && m_lastLauncherSettings) { + launch(*m_lastLauncherSettings); + } + } + emit networkConnectionMeteredChanged(metered); + } +} + +/*! + * \brief Sets whether Syncthing should automatically be stopped as long as the network connection is metered. + */ +void SyncthingLauncher::setStoppingOnMeteredConnection(bool stopOnMeteredConnection) +{ + if ((stopOnMeteredConnection != m_stopOnMeteredConnection) && (m_stopOnMeteredConnection = stopOnMeteredConnection) && m_metered) { + terminateDueToMeteredConnection(); + } +} + /*! * \brief Returns whether the built-in Syncthing library is available. */ @@ -139,6 +191,8 @@ void SyncthingLauncher::launch(const Settings::Launcher &launcherSettings) } else { launch(launcherSettings.syncthingPath, SyncthingProcess::splitArguments(launcherSettings.syncthingArgs)); } + m_stopOnMeteredConnection = launcherSettings.stopOnMeteredConnection; + m_lastLauncherSettings = &launcherSettings; } #ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING @@ -233,6 +287,9 @@ void SyncthingLauncher::handleProcessFinished(int exitCode, QProcess::ExitStatus void SyncthingLauncher::resetState() { m_manuallyStopped = false; + m_stoppedMetered = false; + delete m_relevantConnection; + m_relevantConnection = nullptr; m_guiListeningUrlSearch.reset(); if (!m_guiListeningUrl.isEmpty()) { m_guiListeningUrl.clear(); @@ -281,6 +338,15 @@ void SyncthingLauncher::handleGuiListeningUrlFound(CppUtilities::BufferSearch &, emit guiUrlChanged(m_guiListeningUrl); } +void SyncthingLauncher::terminateDueToMeteredConnection() +{ + if (m_lastLauncherSettings && !m_relevantConnection) { + m_relevantConnection = m_lastLauncherSettings->connectionForLauncher(this); + } + terminate(m_relevantConnection); + m_stoppedMetered = true; +} + #ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING void SyncthingLauncher::runLibSyncthing(const LibSyncthing::RuntimeOptions &runtimeOptions) { diff --git a/syncthingwidgets/misc/syncthinglauncher.h b/syncthingwidgets/misc/syncthinglauncher.h index 275a781..94b361b 100644 --- a/syncthingwidgets/misc/syncthinglauncher.h +++ b/syncthingwidgets/misc/syncthinglauncher.h @@ -15,6 +15,8 @@ #include #include +#include + namespace Settings { struct Launcher; } @@ -29,6 +31,9 @@ class SYNCTHINGWIDGETS_EXPORT SyncthingLauncher : public QObject { Q_PROPERTY(CppUtilities::DateTime activeSince READ activeSince) Q_PROPERTY(bool manuallyStopped READ isManuallyStopped) Q_PROPERTY(bool emittingOutput READ isEmittingOutput WRITE setEmittingOutput) + Q_PROPERTY(std::optional networkConnectionMetered READ isNetworkConnectionMetered WRITE setNetworkConnectionMetered NOTIFY + networkConnectionMeteredChanged) + Q_PROPERTY(bool stoppingOnMeteredConnection READ isStoppingOnMeteredConnection WRITE setStoppingOnMeteredConnection) Q_PROPERTY(QUrl guiUrl READ guiUrl NOTIFY guiUrlChanged) Q_PROPERTY(SyncthingProcess *process READ process) @@ -41,6 +46,10 @@ public: bool isManuallyStopped() const; bool isEmittingOutput() const; void setEmittingOutput(bool emittingOutput); + std::optional isNetworkConnectionMetered() const; + void setNetworkConnectionMetered(std::optional metered); + bool isStoppingOnMeteredConnection() const; + void setStoppingOnMeteredConnection(bool stopOnMeteredConnection); QString errorString() const; QUrl guiUrl() const; SyncthingProcess *process(); @@ -64,6 +73,7 @@ Q_SIGNALS: void exited(int exitCode, QProcess::ExitStatus exitStatus); void errorOccurred(QProcess::ProcessError error); void guiUrlChanged(const QUrl &newUrl); + void networkConnectionMeteredChanged(std::optional isMetered); public Q_SLOTS: void launch(const QString &program, const QStringList &arguments); @@ -90,9 +100,12 @@ private: #endif void handleOutputAvailable(QByteArray &&data); void handleGuiListeningUrlFound(CppUtilities::BufferSearch &bufferSearch, std::string &&searchResult); + void terminateDueToMeteredConnection(); SyncthingProcess m_process; QUrl m_guiListeningUrl; + const Settings::Launcher *m_lastLauncherSettings; + SyncthingConnection *m_relevantConnection; QFuture m_startFuture; QFuture m_stopFuture; QByteArray m_outputBuffer; @@ -102,8 +115,11 @@ private: LibSyncthing::LogLevel m_libsyncthingLogLevel; #endif bool m_manuallyStopped; + bool m_stoppedMetered; bool m_emittingOutput; bool m_useLibSyncthing; + bool m_stopOnMeteredConnection; + std::optional m_metered; static SyncthingLauncher *s_mainInstance; }; @@ -145,6 +161,19 @@ inline bool SyncthingLauncher::isEmittingOutput() const return m_emittingOutput; } +/// \brief Returns whether the current network connection is metered. +/// \remarks Returns an std::optional without value if it is unknown whether the network connection is metered. +inline std::optional SyncthingLauncher::isNetworkConnectionMetered() const +{ + return m_metered; +} + +/// \brief Returns whether Syncthing should automatically be stopped as long as the network connection is metered. +inline bool SyncthingLauncher::isStoppingOnMeteredConnection() const +{ + return m_stopOnMeteredConnection; +} + /// \brief Returns the last error message. inline QString SyncthingLauncher::errorString() const { diff --git a/syncthingwidgets/settings/connectionoptionpage.ui b/syncthingwidgets/settings/connectionoptionpage.ui index 4db0458..915009b 100644 --- a/syncthingwidgets/settings/connectionoptionpage.ui +++ b/syncthingwidgets/settings/connectionoptionpage.ui @@ -475,7 +475,7 @@ - + 2 @@ -520,21 +520,21 @@ - + Current status - + disconnected - + @@ -643,6 +643,13 @@ + + + + Pause all devices while the local network connection is metered + + + diff --git a/syncthingwidgets/settings/launcheroptionpage.ui b/syncthingwidgets/settings/launcheroptionpage.ui index 84bb9c8..e2bcf0b 100644 --- a/syncthingwidgets/settings/launcheroptionpage.ui +++ b/syncthingwidgets/settings/launcheroptionpage.ui @@ -140,6 +140,13 @@ + + + + Stop automatically when network connection is metered + + + diff --git a/syncthingwidgets/settings/settings.cpp b/syncthingwidgets/settings/settings.cpp index bebc219..0c1fcd8 100644 --- a/syncthingwidgets/settings/settings.cpp +++ b/syncthingwidgets/settings/settings.cpp @@ -313,6 +313,8 @@ bool restore() connectionSettings->longPollingTimeout = settings.value(QStringLiteral("longPollingTimeout"), connectionSettings->longPollingTimeout).toInt(); connectionSettings->autoConnect = settings.value(QStringLiteral("autoConnect"), connectionSettings->autoConnect).toBool(); + connectionSettings->pauseOnMeteredConnection + = settings.value(QStringLiteral("pauseOnMetered"), connectionSettings->pauseOnMeteredConnection).toBool(); const auto statusComputionFlags = settings.value(QStringLiteral("statusComputionFlags"), QVariant::fromValue(static_cast(connectionSettings->statusComputionFlags))); if (statusComputionFlags.canConvert()) { @@ -392,6 +394,7 @@ bool restore() launcher.syncthingArgs = settings.value(QStringLiteral("syncthingArgs"), launcher.syncthingArgs).toString(); launcher.considerForReconnect = settings.value(QStringLiteral("considerLauncherForReconnect"), launcher.considerForReconnect).toBool(); launcher.showButton = settings.value(QStringLiteral("showLauncherButton"), launcher.showButton).toBool(); + launcher.stopOnMeteredConnection = settings.value(QStringLiteral("stopOnMetered"), launcher.stopOnMeteredConnection).toBool(); settings.beginGroup(QStringLiteral("tools")); const auto childGroups = settings.childGroups(); for (const QString &tool : childGroups) { @@ -465,6 +468,7 @@ bool save() settings.setValue(QStringLiteral("requestTimeout"), connectionSettings->requestTimeout); settings.setValue(QStringLiteral("longPollingTimeout"), connectionSettings->longPollingTimeout); settings.setValue(QStringLiteral("autoConnect"), connectionSettings->autoConnect); + settings.setValue(QStringLiteral("pauseOnMetered"), connectionSettings->pauseOnMeteredConnection); settings.setValue(QStringLiteral("statusComputionFlags"), QVariant::fromValue(static_cast>(connectionSettings->statusComputionFlags))); settings.setValue(QStringLiteral("httpsCertPath"), connectionSettings->httpsCertPath); @@ -519,6 +523,7 @@ bool save() settings.setValue(QStringLiteral("syncthingArgs"), launcher.syncthingArgs); settings.setValue(QStringLiteral("considerLauncherForReconnect"), launcher.considerForReconnect); settings.setValue(QStringLiteral("showLauncherButton"), launcher.showButton); + settings.setValue(QStringLiteral("stopOnMetered"), launcher.stopOnMeteredConnection); settings.beginGroup(QStringLiteral("tools")); for (auto i = launcher.tools.cbegin(), end = launcher.tools.cend(); i != end; ++i) { const ToolParameter &toolParams = i.value(); diff --git a/syncthingwidgets/settings/settings.h b/syncthingwidgets/settings/settings.h index a75559f..30970d3 100644 --- a/syncthingwidgets/settings/settings.h +++ b/syncthingwidgets/settings/settings.h @@ -95,6 +95,7 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher { QHash tools; bool considerForReconnect = false; bool showButton = false; + bool stopOnMeteredConnection = false; #ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING struct SYNCTHINGWIDGETS_EXPORT LibSyncthing { diff --git a/syncthingwidgets/settings/settingsdialog.cpp b/syncthingwidgets/settings/settingsdialog.cpp index c1ebdb0..3da8ab2 100644 --- a/syncthingwidgets/settings/settingsdialog.cpp +++ b/syncthingwidgets/settings/settingsdialog.cpp @@ -82,6 +82,15 @@ using namespace QtUtilities; namespace QtGui { +/// \brief Returns the tooltip text for the specified \a isMetered value. +static QString meteredToolTip(std::optional isMetered) +{ + return isMetered.has_value() + ? (isMetered.value() ? QCoreApplication::translate("QtGui", "The network connection is currently considered metered.") + : QCoreApplication::translate("QtGui", "The network connection is currently not considered metered.")) + : QCoreApplication::translate("QtGui", "Unable to determine whether the network connection is metered; assuming an unmetered connection."); +} + // ConnectionOptionPage ConnectionOptionPage::ConnectionOptionPage(Data::SyncthingConnection *connection, QWidget *parentWidget) : ConnectionOptionPageBase(parentWidget) @@ -133,6 +142,11 @@ QWidget *ConnectionOptionPage::setupWidget() QObject::connect(ui()->addPushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::addNewConfig, this)); QObject::connect(ui()->removePushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::removeSelectedConfig, this)); QObject::connect(ui()->advancedCheckBox, &QCheckBox::toggled, bind(&ConnectionOptionPage::toggleAdvancedSettings, this, std::placeholders::_1)); + if (const auto *const launcher = SyncthingLauncher::mainInstance()) { + handleNetworkConnectionMeteredChanged(launcher->isNetworkConnectionMetered()); + QObject::connect(launcher, &SyncthingLauncher::networkConnectionMeteredChanged, + bind(&ConnectionOptionPage::handleNetworkConnectionMeteredChanged, this, std::placeholders::_1)); + } #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) ui()->timeoutSpinBox->setEnabled(false); #endif @@ -220,6 +234,7 @@ bool ConnectionOptionPage::showConnectionSettings(int index) ui()->pollErrorsSpinBox->setValue(connectionSettings.errorsPollInterval); ui()->reconnectSpinBox->setValue(connectionSettings.reconnectInterval); ui()->autoConnectCheckBox->setChecked(connectionSettings.autoConnect); + ui()->pauseOnMeteredConnectionCheckBox->setChecked(connectionSettings.pauseOnMeteredConnection); m_statusComputionModel->setStatusComputionFlags(connectionSettings.statusComputionFlags); setCurrentIndex(index); return true; @@ -247,6 +262,7 @@ bool ConnectionOptionPage::cacheCurrentSettings(bool applying) connectionSettings.errorsPollInterval = ui()->pollErrorsSpinBox->value(); connectionSettings.reconnectInterval = ui()->reconnectSpinBox->value(); connectionSettings.autoConnect = ui()->autoConnectCheckBox->isChecked(); + connectionSettings.pauseOnMeteredConnection = ui()->pauseOnMeteredConnectionCheckBox->isChecked(); connectionSettings.statusComputionFlags = m_statusComputionModel->statusComputionFlags(); if (!connectionSettings.loadHttpsCert()) { const QString errorMessage = QCoreApplication::translate("QtGui::ConnectionOptionPage", "Unable to load specified certificate \"%1\".") @@ -366,20 +382,26 @@ void ConnectionOptionPage::toggleAdvancedSettings(bool show) return; } #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) - for (auto *const widget : std::initializer_list{ - ui()->authLabel, ui()->userNameLabel, ui()->passwordLabel, ui()->timeoutLabel, ui()->longPollingLabel, ui()->pollLabel }) { + for (auto *const widget : std::initializer_list{ ui()->authLabel, ui()->userNameLabel, ui()->passwordLabel, ui()->timeoutLabel, + ui()->longPollingLabel, ui()->pollLabel, ui()->pauseOnMeteredConnectionCheckBox }) { ui()->formLayout->setRowVisible(widget, show); } #else - for (auto *const widget : std::initializer_list{ ui()->authLabel, ui()->authCheckBox, ui()->userNameLabel, ui()->userNameLineEdit, - ui()->passwordLabel, ui()->passwordLineEdit, ui()->timeoutLabel, ui()->timeoutSpinBox, ui()->longPollingLabel, ui()->longPollingSpinBox, - ui()->pollLabel, ui()->pollDevStatsLabel, ui()->pollDevStatsSpinBox, ui()->pollErrorsLabel, ui()->pollErrorsSpinBox, - ui()->pollTrafficLabel, ui()->pollTrafficSpinBox, ui()->reconnectLabel, ui()->reconnectSpinBox }) { + for (auto *const widget : + std::initializer_list{ ui()->authLabel, ui()->authCheckBox, ui()->userNameLabel, ui()->userNameLineEdit, ui()->passwordLabel, + ui()->passwordLineEdit, ui()->timeoutLabel, ui()->timeoutSpinBox, ui()->longPollingLabel, ui()->longPollingSpinBox, ui()->pollLabel, + ui()->pollDevStatsLabel, ui()->pollDevStatsSpinBox, ui()->pollErrorsLabel, ui()->pollErrorsSpinBox, ui()->pollTrafficLabel, + ui()->pollTrafficSpinBox, ui()->reconnectLabel, ui()->reconnectSpinBox, ui()->pauseOnMeteredConnectionCheckBox }) { widget->setVisible(show); } #endif } +void ConnectionOptionPage::handleNetworkConnectionMeteredChanged(std::optional isMetered) +{ + ui()->pauseOnMeteredConnectionCheckBox->setToolTip(meteredToolTip(isMetered)); +} + bool ConnectionOptionPage::apply() { if (!cacheCurrentSettings(true)) { @@ -1088,6 +1110,7 @@ QWidget *LauncherOptionPage::setupWidget() // hide "consider for reconnect" and "show start/stop button on tray" checkboxes for tools ui()->considerForReconnectCheckBox->setVisible(false); ui()->showButtonCheckBox->setVisible(false); + ui()->stopOnMeteredCheckBox->setVisible(false); } // hide libsyncthing-controls by default (as the checkbox is unchecked by default) @@ -1126,7 +1149,7 @@ QWidget *LauncherOptionPage::setupWidget() ui()->useBuiltInVersionCheckBox->setToolTip(SyncthingLauncher::libSyncthingVersionInfo()); } - // connect signals & slots + // setup process/launcher if (m_process) { connect(m_process, &SyncthingProcess::readyRead, this, &LauncherOptionPage::handleSyncthingReadyRead, Qt::QueuedConnection); connect(m_process, static_cast(&SyncthingProcess::finished), this, @@ -1141,6 +1164,10 @@ QWidget *LauncherOptionPage::setupWidget() connect(ui()->logLevelComboBox, static_cast(&QComboBox::currentIndexChanged), this, &LauncherOptionPage::updateLibSyncthingLogLevel); #endif + + handleNetworkConnectionMeteredChanged(m_launcher->isNetworkConnectionMetered()); + connect(m_launcher, &SyncthingLauncher::networkConnectionMeteredChanged, this, &LauncherOptionPage::handleNetworkConnectionMeteredChanged); + m_launcher->setEmittingOutput(true); } connect(ui()->launchNowPushButton, &QPushButton::clicked, this, &LauncherOptionPage::launch); @@ -1164,6 +1191,7 @@ bool LauncherOptionPage::apply() settings.syncthingArgs = ui()->argumentsLineEdit->text(); settings.considerForReconnect = ui()->considerForReconnectCheckBox->isChecked(); settings.showButton = ui()->showButtonCheckBox->isChecked(); + settings.stopOnMeteredConnection = ui()->stopOnMeteredCheckBox->isChecked(); } else { ToolParameter ¶ms = settings.tools[m_tool]; params.autostart = ui()->enabledCheckBox->isChecked(); @@ -1189,6 +1217,7 @@ void LauncherOptionPage::reset() ui()->argumentsLineEdit->setText(settings.syncthingArgs); ui()->considerForReconnectCheckBox->setChecked(settings.considerForReconnect); ui()->showButtonCheckBox->setChecked(settings.showButton); + ui()->stopOnMeteredCheckBox->setChecked(settings.stopOnMeteredConnection); } else { const ToolParameter params = settings.tools.value(m_tool); ui()->useBuiltInVersionCheckBox->setChecked(false); @@ -1304,6 +1333,11 @@ void LauncherOptionPage::handleSyncthingError(QProcess::ProcessError error) } } +void LauncherOptionPage::handleNetworkConnectionMeteredChanged(std::optional isMetered) +{ + ui()->stopOnMeteredCheckBox->setToolTip(meteredToolTip(isMetered)); +} + bool LauncherOptionPage::isRunning() const { return (m_process && m_process->isRunning()) || (m_launcher && m_launcher->isRunning()); diff --git a/syncthingwidgets/settings/settingsdialog.h b/syncthingwidgets/settings/settingsdialog.h index 89a3196..d57b7c5 100644 --- a/syncthingwidgets/settings/settingsdialog.h +++ b/syncthingwidgets/settings/settingsdialog.h @@ -69,6 +69,7 @@ void moveSelectedConfigDown(); void moveSelectedConfigUp(); void setCurrentIndex(int currentIndex); void toggleAdvancedSettings(bool show); +void handleNetworkConnectionMeteredChanged(std::optional isMetered); Data::SyncthingConnection *m_connection; Data::SyncthingConnectionSettings m_primarySettings; std::vector m_secondarySettings; @@ -137,6 +138,7 @@ private Q_SLOTS: void handleSyncthingOutputAvailable(const QByteArray &output); void handleSyncthingExited(int exitCode, QProcess::ExitStatus exitStatus); void handleSyncthingError(QProcess::ProcessError error); + void handleNetworkConnectionMeteredChanged(std::optional isMetered); void launch(); #ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING void updateLibSyncthingLogLevel();