Allow to pause automatically on metered network connections

* Allow to pause all devices on metered network connections (devices will
  be automatically resumed when network is no longer metered)
* Allow to stop Syncthing when it was started via the built-in launcher
  on metered network connections (it will be automatically started again
  when the network connection is no longer metered)
* See https://github.com/Martchus/syncthingtray/issues/231
This commit is contained in:
Martchus 2024-02-15 18:53:54 +01:00
parent 9816b6cf51
commit 30fa37f048
13 changed files with 247 additions and 13 deletions

View File

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

View File

@ -25,6 +25,11 @@
#include <QStringBuilder>
#include <QTimer>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
#include <QNetworkInformation>
#define SYNCTHINGCONNECTION_SUPPORT_METERED
#endif
#include <iostream>
#include <utility>
@ -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<qsizetype>(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;
}

View File

@ -17,6 +17,11 @@
#include <QSslError>
#include <QTimer>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
#include <QNetworkInformation>
#define SYNCTHINGCONNECTION_SUPPORT_METERED
#endif
#include <cstdint>
#include <functional>
#include <limits>
@ -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<QSslError> &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<SyncthingDir> m_dirs;
std::vector<SyncthingDev> 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().
*/

View File

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

View File

@ -53,6 +53,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnectionSettings {
QList<QSslError> expectedSslErrors;
SyncthingStatusComputionFlags statusComputionFlags = SyncthingStatusComputionFlags::Default;
bool autoConnect = false;
bool pauseOnMeteredConnection = false;
static QList<QSslError> compileSslErrors(const QSslCertificate &trustedCert);
bool loadHttpsCert();

View File

@ -1,9 +1,16 @@
#include "./syncthinglauncher.h"
#include <syncthingconnector/syncthingconnection.h>
#include "../settings/settings.h"
#include <QtConcurrentRun>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
#include <QNetworkInformation>
#define SYNCTHINGCONNECTION_SUPPORT_METERED
#endif
#include <algorithm>
#include <functional>
#include <limits>
@ -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<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&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<bool> without value).
*/
void SyncthingLauncher::setNetworkConnectionMetered(std::optional<bool> 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)
{

View File

@ -15,6 +15,8 @@
#include <QFuture>
#include <QUrl>
#include <optional>
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<bool> 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<bool> isNetworkConnectionMetered() const;
void setNetworkConnectionMetered(std::optional<bool> 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<bool> 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<void> m_startFuture;
QFuture<void> 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<bool> 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<bool> without value if it is unknown whether the network connection is metered.
inline std::optional<bool> 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
{

View File

@ -475,7 +475,7 @@
</property>
</widget>
</item>
<item row="21" column="1">
<item row="22" column="1">
<layout class="QVBoxLayout" name="statusComputionFlagsVerticalLayout">
<property name="spacing">
<number>2</number>
@ -520,21 +520,21 @@
</item>
</layout>
</item>
<item row="22" column="0">
<item row="23" column="0">
<widget class="QLabel" name="statusTextLabel">
<property name="text">
<string>Current status</string>
</property>
</widget>
</item>
<item row="22" column="1">
<item row="23" column="1">
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>disconnected</string>
</property>
</widget>
</item>
<item row="25" column="1">
<item row="26" column="1">
<widget class="QPushButton" name="connectPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -643,6 +643,13 @@
</property>
</widget>
</item>
<item row="21" column="1">
<widget class="QCheckBox" name="pauseOnMeteredConnectionCheckBox">
<property name="text">
<string>Pause all devices while the local network connection is metered</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -140,6 +140,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="stopOnMeteredCheckBox">
<property name="text">
<string>Stop automatically when network connection is metered</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="sizePolicy">

View File

@ -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<UnderlyingFlagType>(connectionSettings->statusComputionFlags)));
if (statusComputionFlags.canConvert<UnderlyingFlagType>()) {
@ -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<std::underlying_type_t<Data::SyncthingStatusComputionFlags>>(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();

View File

@ -95,6 +95,7 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher {
QHash<QString, ToolParameter> tools;
bool considerForReconnect = false;
bool showButton = false;
bool stopOnMeteredConnection = false;
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
struct SYNCTHINGWIDGETS_EXPORT LibSyncthing {

View File

@ -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<bool> 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<QWidget *>{
ui()->authLabel, ui()->userNameLabel, ui()->passwordLabel, ui()->timeoutLabel, ui()->longPollingLabel, ui()->pollLabel }) {
for (auto *const widget : std::initializer_list<QWidget *>{ 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<QWidget *>{ 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<QWidget *>{ 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<bool> 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<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
@ -1141,6 +1164,10 @@ QWidget *LauncherOptionPage::setupWidget()
connect(ui()->logLevelComboBox, static_cast<void (QComboBox::*)(int)>(&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 &params = 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<bool> isMetered)
{
ui()->stopOnMeteredCheckBox->setToolTip(meteredToolTip(isMetered));
}
bool LauncherOptionPage::isRunning() const
{
return (m_process && m_process->isRunning()) || (m_launcher && m_launcher->isRunning());

View File

@ -69,6 +69,7 @@ void moveSelectedConfigDown();
void moveSelectedConfigUp();
void setCurrentIndex(int currentIndex);
void toggleAdvancedSettings(bool show);
void handleNetworkConnectionMeteredChanged(std::optional<bool> isMetered);
Data::SyncthingConnection *m_connection;
Data::SyncthingConnectionSettings m_primarySettings;
std::vector<Data::SyncthingConnectionSettings> 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<bool> isMetered);
void launch();
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
void updateLibSyncthingLogLevel();