Skip to content

Commit

Permalink
WIP: Allow to pause automatically on metered network connections
Browse files Browse the repository at this point in the history
See #231
  • Loading branch information
Martchus committed Feb 15, 2024
1 parent 9816b6c commit 133f81a
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
1 change: 1 addition & 0 deletions syncthingconnector/syncthingconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ SyncthingConnection::SyncthingConnection(
, m_dirStatsAltered(false)
, m_recordFileChanges(false)
, m_useDeprecatedRoutes(true)
, m_pauseOnMeteredConnection(false)
{
m_trafficPollTimer.setInterval(SyncthingConnectionSettings::defaultTrafficPollInterval);
m_trafficPollTimer.setTimerType(Qt::VeryCoarseTimer);
Expand Down
18 changes: 18 additions & 0 deletions syncthingconnector/syncthingconnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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 pauseOnMeteredConnection READ isPausingOnMeteredConnection WRITE setPauseOnMeteredConnection)

public:
explicit SyncthingConnection(const QString &syncthingUrl = QStringLiteral("http://localhost:8080"), const QByteArray &apiKey = QByteArray(),
Expand Down Expand Up @@ -145,6 +151,8 @@ class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnection : public QObject {
void setRequestTimeout(int requestTimeout);
int longPollingTimeout() const;
void setLongPollingTimeout(int longPollingTimeout);
bool isPausingOnMeteredConnection() const;
void setPauseOnMeteredConnection(bool pauseOnMeteredConnection);

// getter for information retrieved from Syncthing
const QString &configDir() const;
Expand Down Expand Up @@ -433,6 +441,8 @@ private Q_SLOTS:
bool m_dirStatsAltered;
bool m_recordFileChanges;
bool m_useDeprecatedRoutes;
bool m_pauseOnMeteredConnection;
QNetworkInformation m_networkInformation;
};

/*!
Expand Down Expand Up @@ -768,6 +778,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_pauseOnMeteredConnection;
}

/*!
* \brief Returns what information is considered to compute the overall status returned by status().
*/
Expand Down
68 changes: 68 additions & 0 deletions syncthingwidgets/misc/syncthinglauncher.cpp
Original file line number Diff line number Diff line change
@@ -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>
Expand Down Expand Up @@ -33,18 +40,33 @@ SyncthingLauncher::SyncthingLauncher(QObject *parent)
: QObject(parent)
, 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))
, m_lastLauncherSettings(nullptr)
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
, m_libsyncthingLogLevel(LibSyncthing::LogLevel::Info)
#endif
, m_manuallyStopped(true)
, m_stoppedMetered(false)
, m_emittingOutput(false)
, m_useLibSyncthing(false)
, m_stopIfMetered(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,
&SyncthingLauncher::handleProcessFinished, Qt::QueuedConnection);
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);

#ifdef SYNCTHINGCONNECTION_SUPPORT_METERED
QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Metered);
const auto *const networkInformation = QNetworkInformation::instance();
if (networkInformation->supports(QNetworkInformation::Feature::Metered)) {
setNetworkConnectionMetered(networkInformation->isMetered());
connect(networkInformation, &QNetworkInformation::isMeteredChanged, this, [this] (bool isMetered) {
setNetworkConnectionMetered(isMetered);
});
}
#endif
}

/*!
Expand All @@ -60,6 +82,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_stopIfMetered) {
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::setStoppingIfMetered(bool stopIfMetered)
{
if ((stopIfMetered != m_stopIfMetered) && (m_stopIfMetered = stopIfMetered) && m_metered) {
terminateDueToMeteredConnection();
}
}

/*!
* \brief Returns whether the built-in Syncthing library is available.
*/
Expand Down Expand Up @@ -139,6 +193,8 @@ void SyncthingLauncher::launch(const Settings::Launcher &launcherSettings)
} else {
launch(launcherSettings.syncthingPath, SyncthingProcess::splitArguments(launcherSettings.syncthingArgs));
}
m_stopIfMetered = launcherSettings.stopIfMetered;
m_lastLauncherSettings = &launcherSettings;
}

#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
Expand Down Expand Up @@ -233,6 +289,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();
Expand Down Expand Up @@ -281,6 +340,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)
{
Expand Down
28 changes: 28 additions & 0 deletions syncthingwidgets/misc/syncthinglauncher.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <QFuture>
#include <QUrl>

#include <optional>

namespace Settings {
struct Launcher;
}
Expand All @@ -29,6 +31,8 @@ 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 isStoppingIfMetered READ isStoppingIfMetered WRITE setStoppingIfMetered)
Q_PROPERTY(QUrl guiUrl READ guiUrl NOTIFY guiUrlChanged)
Q_PROPERTY(SyncthingProcess *process READ process)

Expand All @@ -41,6 +45,10 @@ class SYNCTHINGWIDGETS_EXPORT SyncthingLauncher : public QObject {
bool isManuallyStopped() const;
bool isEmittingOutput() const;
void setEmittingOutput(bool emittingOutput);
std::optional<bool> isNetworkConnectionMetered() const;
void setNetworkConnectionMetered(std::optional<bool> metered);
bool isStoppingIfMetered() const;
void setStoppingIfMetered(bool stopIfMetered);
QString errorString() const;
QUrl guiUrl() const;
SyncthingProcess *process();
Expand All @@ -64,6 +72,7 @@ class SYNCTHINGWIDGETS_EXPORT SyncthingLauncher : public QObject {
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);
Expand All @@ -90,9 +99,12 @@ private Q_SLOTS:
#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;
Expand All @@ -102,8 +114,11 @@ private Q_SLOTS:
LibSyncthing::LogLevel m_libsyncthingLogLevel;
#endif
bool m_manuallyStopped;
bool m_stoppedMetered;
bool m_emittingOutput;
bool m_useLibSyncthing;
bool m_stopIfMetered;
std::optional<bool> m_metered;
static SyncthingLauncher *s_mainInstance;
};

Expand Down Expand Up @@ -145,6 +160,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::isStoppingIfMetered() const
{
return m_stopIfMetered;
}

/// \brief Returns the last error message.
inline QString SyncthingLauncher::errorString() const
{
Expand Down
2 changes: 2 additions & 0 deletions syncthingwidgets/settings/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,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.stopIfMetered = settings.value(QStringLiteral("stopIfMetered"), launcher.stopIfMetered).toBool();
settings.beginGroup(QStringLiteral("tools"));
const auto childGroups = settings.childGroups();
for (const QString &tool : childGroups) {
Expand Down Expand Up @@ -519,6 +520,7 @@ bool save()
settings.setValue(QStringLiteral("syncthingArgs"), launcher.syncthingArgs);
settings.setValue(QStringLiteral("considerLauncherForReconnect"), launcher.considerForReconnect);
settings.setValue(QStringLiteral("showLauncherButton"), launcher.showButton);
settings.setValue(QStringLiteral("stopIfMetered"), launcher.stopIfMetered);
settings.beginGroup(QStringLiteral("tools"));
for (auto i = launcher.tools.cbegin(), end = launcher.tools.cend(); i != end; ++i) {
const ToolParameter &toolParams = i.value();
Expand Down
1 change: 1 addition & 0 deletions syncthingwidgets/settings/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher {
QHash<QString, ToolParameter> tools;
bool considerForReconnect = false;
bool showButton = false;
bool stopIfMetered = false;

#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
struct SYNCTHINGWIDGETS_EXPORT LibSyncthing {
Expand Down

0 comments on commit 133f81a

Please sign in to comment.