diff --git a/changelog/unreleased/11533 b/changelog/unreleased/11533
new file mode 100644
index 00000000000..1e0a45aa212
--- /dev/null
+++ b/changelog/unreleased/11533
@@ -0,0 +1,9 @@
+Enhancement: Pause sync when behind a captive portal
+
+When the operating system detects a captive portal, stop all outgoing
+network requests. These requests will fail with an SSL certificate
+mismatch error. When the captive portal is "gone", synchronisation will
+be restarted.
+
+https://github.com/owncloud/client/issues/11533
+https://github.com/owncloud/client/pull/11567
diff --git a/changelog/unreleased/11567 b/changelog/unreleased/11567
new file mode 100644
index 00000000000..3870c61396e
--- /dev/null
+++ b/changelog/unreleased/11567
@@ -0,0 +1,6 @@
+Enhancement: Pause synchronization when a captive portal is detected
+
+An encrypted connection to a captive portal will use an unknown certificate, which in turn will caused a dialog to be shown by the client.
+This is now changed: when a captive portal is detected by the OS, the client now shows in the account status that it cannot connect to the server because of a captive portal.
+
+Supported OSses: Windows, Linux (with some network managers)
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 6155a427320..2aab0dbc8a4 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -42,6 +42,7 @@ set(client_SRCS
ignorelisteditor.cpp
lockwatcher.cpp
logbrowser.cpp
+ networkinformation.cpp
networksettings.cpp
ocssharejob.cpp
openfilemanager.cpp
diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp
index 2b7b451c782..a70d70e97b1 100644
--- a/src/gui/accountsettings.cpp
+++ b/src/gui/accountsettings.cpp
@@ -30,6 +30,7 @@
#include "folderwizard/folderwizard.h"
#include "gui/accountmodalwidget.h"
#include "gui/models/models.h"
+#include "gui/networkinformation.h"
#include "gui/qmlutils.h"
#include "gui/selectivesyncwidget.h"
#include "gui/spaces/spaceimageprovider.h"
@@ -412,7 +413,7 @@ void AccountSettings::slotEnableCurrentFolder(Folder *folder, bool terminate)
void AccountSettings::slotForceSyncCurrentFolder(Folder *folder)
{
- if (Utility::internetConnectionIsMetered() && ConfigFile().pauseSyncWhenMetered()) {
+ if (NetworkInformation::instance()->isMetered() && ConfigFile().pauseSyncWhenMetered()) {
auto messageBox = new QMessageBox(QMessageBox::Question, tr("Internet connection is metered"),
tr("Synchronization is paused because the Internet connection is a metered connection"
"
Do you really want to force a Synchronization now?"),
@@ -457,6 +458,7 @@ void AccountSettings::slotAccountStateChanged()
{
const AccountState::State state = _accountState->state();
const AccountPtr account = _accountState->account();
+ qCDebug(lcAccountSettings) << "Account state changed to" << state << "for account" << account;
// in 2023 there should never be credentials encoded in the url, but we never know...
const auto safeUrl = account->url().adjusted(QUrl::RemoveUserInfo);
@@ -470,9 +472,6 @@ void AccountSettings::slotAccountStateChanged()
.arg(Utility::escape(safeUrl.toString()));
switch (state) {
- case AccountState::PausedDueToMetered:
- showConnectionLabel(tr("Sync to %1 is paused due to metered internet connection.").arg(server));
- break;
case AccountState::Connected: {
QStringList errors;
if (account->serverSupportLevel() != Account::ServerSupportLevel::Supported) {
@@ -495,7 +494,13 @@ void AccountSettings::slotAccountStateChanged()
break;
}
case AccountState::Connecting:
- showConnectionLabel(tr("Connecting to: %1.").arg(server));
+ if (NetworkInformation::instance()->isBehindCaptivePortal()) {
+ showConnectionLabel(tr("Captive portal prevents connections to %1.").arg(server));
+ } else if (NetworkInformation::instance()->isMetered() && ConfigFile().pauseSyncWhenMetered()) {
+ showConnectionLabel(tr("Sync to %1 is paused due to metered internet connection.").arg(server));
+ } else {
+ showConnectionLabel(tr("Connecting to: %1.").arg(server));
+ }
break;
case AccountState::ConfigurationError:
showConnectionLabel(tr("Server configuration error: %1.")
diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp
index f8ab56a2080..69abb0a0b81 100644
--- a/src/gui/accountstate.cpp
+++ b/src/gui/accountstate.cpp
@@ -23,6 +23,7 @@
#include "libsync/creds/abstractcredentials.h"
#include "libsync/creds/httpcredentials.h"
+#include "gui/networkinformation.h"
#include "gui/quotainfo.h"
#include "gui/settingsdialog.h"
#include "gui/spacemigration.h"
@@ -34,7 +35,6 @@
#include "theme.h"
#include
-#include
#include
#include
#include
@@ -113,40 +113,64 @@ AccountState::AccountState(AccountPtr account)
},
Qt::QueuedConnection);
- if (QNetworkInformation *qNetInfo = QNetworkInformation::instance()) {
- connect(qNetInfo, &QNetworkInformation::reachabilityChanged, this, [this](QNetworkInformation::Reachability reachability) {
- switch (reachability) {
- case QNetworkInformation::Reachability::Online:
- [[fallthrough]];
- case QNetworkInformation::Reachability::Site:
- [[fallthrough]];
- case QNetworkInformation::Reachability::Unknown:
- // the connection might not yet be established
- QTimer::singleShot(0, this, [this] { checkConnectivity(false); });
- break;
- case QNetworkInformation::Reachability::Disconnected:
- // explicitly set disconnected, this way a successful checkConnectivity call above will trigger a local discover
- if (state() != State::SignedOut) {
- setState(State::Disconnected);
- }
- [[fallthrough]];
- case QNetworkInformation::Reachability::Local:
- break;
+ connect(NetworkInformation::instance(), &NetworkInformation::reachabilityChanged, this, [this](NetworkInformation::Reachability reachability) {
+ switch (reachability) {
+ case NetworkInformation::Reachability::Online:
+ [[fallthrough]];
+ case NetworkInformation::Reachability::Site:
+ [[fallthrough]];
+ case NetworkInformation::Reachability::Unknown:
+ // the connection might not yet be established
+ QTimer::singleShot(0, this, [this] { checkConnectivity(false); });
+ break;
+ case NetworkInformation::Reachability::Disconnected:
+ // explicitly set disconnected, this way a successful checkConnectivity call above will trigger a local discover
+ if (state() != State::SignedOut) {
+ setState(State::Disconnected);
}
- });
+ [[fallthrough]];
+ case NetworkInformation::Reachability::Local:
+ break;
+ }
+ });
- connect(qNetInfo, &QNetworkInformation::isMeteredChanged, this, [this](bool isMetered) {
- if (ConfigFile().pauseSyncWhenMetered()) {
- if (state() == State::Connected && isMetered) {
- qCInfo(lcAccountState) << "Network switched to a metered connection, setting account state to PausedDueToMetered";
- setState(State::PausedDueToMetered);
- } else if (state() == State::PausedDueToMetered && !isMetered) {
- qCInfo(lcAccountState) << "Network switched to a NON-metered connection, setting account state to Connected";
- setState(State::Connected);
- }
+ connect(NetworkInformation::instance(), &NetworkInformation::isMeteredChanged, this, [this](bool isMetered) {
+ if (ConfigFile().pauseSyncWhenMetered()) {
+ if (state() == State::Connected && isMetered) {
+ qCInfo(lcAccountState) << "Network switched to a metered connection, setting account state to PausedDueToMetered";
+ setState(State::Connecting);
+ } else if (state() == State::Connecting && !isMetered) {
+ qCInfo(lcAccountState) << "Network switched to a NON-metered connection, setting account state to Connected";
+ setState(State::Connected);
}
- });
+ }
+ });
+
+ connect(NetworkInformation::instance(), &NetworkInformation::isBehindCaptivePortalChanged, this, [this](bool onoff) {
+ if (onoff) {
+ // Block jobs from starting: they will fail because of the captive portal.
+ // Note: this includes the `Drives` jobs started periodically by the `SpacesManager`.
+ _queueGuard.block();
+ } else {
+ // Empty the jobs queue before unblocking it. The client might have been behind a captive
+ // portal for hours, so a whole bunch of jobs might have queued up. If we wouldn't
+ // clear the queue, unleashing all those jobs might look like a DoS attack. Most of them
+ // are also not very useful anymore (e.g. `Drives` jobs), and the important ones (PUT jobs)
+ // will be rescheduled by a directory scan.
+ _account->jobQueue()->clear();
+ _queueGuard.unblock();
+ }
+
+ // A direct connect is not possible, because then the state parameter of `isBehindCaptivePortalChanged`
+ // would become the `verifyServerState` argument to `checkConnectivity`.
+ // The call is also made for when we "go behind" a captive portal. That ensures that not
+ // only the status is set to `Connecting`, but also makes the UI show that syncing is paused.
+ QTimer::singleShot(0, this, [this] { checkConnectivity(false); });
+ });
+ if (NetworkInformation::instance()->isBehindCaptivePortal()) {
+ _queueGuard.block();
}
+
// as a fallback and to recover after server issues we also poll
auto timer = new QTimer(this);
timer->setInterval(ConnectionValidator::DefaultCallingInterval);
@@ -240,8 +264,11 @@ void AccountState::setState(State state)
_connectionValidator->deleteLater();
_connectionValidator.clear();
checkConnectivity();
- } else if (_state == Connected && Utility::internetConnectionIsMetered() && ConfigFile().pauseSyncWhenMetered()) {
- _state = PausedDueToMetered;
+ } else if (_state == Connected) {
+ if ((NetworkInformation::instance()->isMetered() && ConfigFile().pauseSyncWhenMetered())
+ || NetworkInformation::instance()->isBehindCaptivePortal()) {
+ _state = Connecting;
+ }
}
}
@@ -302,7 +329,7 @@ void AccountState::signIn()
bool AccountState::isConnected() const
{
- return _state == Connected || _state == PausedDueToMetered;
+ return _state == Connected;
}
void AccountState::tagLastSuccessfullETagRequest(const QDateTime &tp)
@@ -364,6 +391,9 @@ void AccountState::checkConnectivity(bool blockJobs)
this, &AccountState::slotConnectionValidatorResult);
connect(_connectionValidator, &ConnectionValidator::sslErrors, this, [blockJobs, this](const QList &errors) {
+ if (NetworkInformation::instance()->isBehindCaptivePortal()) {
+ return;
+ }
if (!_tlsDialog) {
// ignore errors for already accepted certificates
auto filteredErrors = _account->accessManager()->filterSslErrors(errors);
@@ -479,6 +509,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
setState(Connected);
break;
case ConnectionValidator::Undefined:
+ [[fallthrough]];
case ConnectionValidator::NotConfigured:
setState(Disconnected);
break;
@@ -494,6 +525,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
setState(NetworkError);
break;
case ConnectionValidator::CredentialsWrong:
+ [[fallthrough]];
case ConnectionValidator::CredentialsNotReady:
slotInvalidCredentials();
break;
@@ -511,6 +543,9 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
case ConnectionValidator::Timeout:
setState(NetworkError);
break;
+ case ConnectionValidator::CaptivePortal:
+ setState(Connecting);
+ break;
}
}
diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h
index fcbe0675d00..a3802c1342a 100644
--- a/src/gui/accountstate.h
+++ b/src/gui/accountstate.h
@@ -82,11 +82,6 @@ class OWNCLOUDGUI_EXPORT AccountState : public QObject
/// We are currently asking the user for credentials
AskingCredentials,
- /// We are on a metered internet connection, and the user preference
- /// is to pause syncing in this case. This state is entered from and
- /// left to a `Connected` state.
- PausedDueToMetered,
-
Connecting
};
Q_ENUM(State)
diff --git a/src/gui/application.cpp b/src/gui/application.cpp
index 0d8d726b59d..748fd4b1c7d 100644
--- a/src/gui/application.cpp
+++ b/src/gui/application.cpp
@@ -48,8 +48,6 @@
#include
#include
-#include
-
namespace OCC {
Q_LOGGING_CATEGORY(lcApplication, "gui.application", QtInfoMsg)
diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp
index 3ce3018b44b..f178be3dc87 100644
--- a/src/gui/connectionvalidator.cpp
+++ b/src/gui/connectionvalidator.cpp
@@ -14,6 +14,7 @@
#include "gui/connectionvalidator.h"
#include "gui/clientproxy.h"
#include "gui/fetchserversettings.h"
+#include "gui/networkinformation.h"
#include "gui/tlserrordialog.h"
#include "libsync/account.h"
#include "libsync/cookiejar.h"
@@ -132,7 +133,7 @@ void ConnectionValidator::slotCheckServerAndAuth()
reportResult(Timeout);
return;
case QNetworkReply::SslHandshakeFailedError:
- reportResult(SslError);
+ reportResult(NetworkInformation::instance()->isBehindCaptivePortal() ? CaptivePortal : SslError);
return;
case QNetworkReply::TooManyRedirectsError:
reportResult(MaintenanceMode);
@@ -214,7 +215,7 @@ void ConnectionValidator::slotAuthFailed()
if (job->reply()->error() == QNetworkReply::SslHandshakeFailedError) {
_errors << job->errorStringParsingBody();
- stat = SslError;
+ stat = NetworkInformation::instance()->isBehindCaptivePortal() ? CaptivePortal : SslError;
} else if (job->reply()->error() == QNetworkReply::AuthenticationRequiredError || !_account->credentials()->stillValid(job->reply())) {
qCWarning(lcConnectionValidator) << "******** Password is wrong!" << job->reply()->error() << job;
diff --git a/src/gui/connectionvalidator.h b/src/gui/connectionvalidator.h
index bf421c57969..7c2ca7999bc 100644
--- a/src/gui/connectionvalidator.h
+++ b/src/gui/connectionvalidator.h
@@ -101,9 +101,10 @@ class OWNCLOUDGUI_EXPORT ConnectionValidator : public QObject
ServiceUnavailable, // 503 on authed request
MaintenanceMode, // maintenance enabled in status.php
Timeout, // actually also used for other errors on the authed request
- ClientUnsupported // The server blocks us as an unsupported client
+ ClientUnsupported, // The server blocks us as an unsupported client
+ CaptivePortal, // We're stuck behind a captive portal and (will) get SSL certificate problems
};
- Q_ENUM(Status);
+ Q_ENUM(Status)
// How often should the Application ask this object to check for the connection?
static constexpr auto DefaultCallingInterval = std::chrono::seconds(62);
diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp
index ee8e1dd09ec..2cae66cf940 100644
--- a/src/gui/folderman.cpp
+++ b/src/gui/folderman.cpp
@@ -20,6 +20,7 @@
#include "common/asserts.h"
#include "configfile.h"
#include "folder.h"
+#include "gui/networkinformation.h"
#include "guiutility.h"
#include "libsync/syncengine.h"
#include "lockwatcher.h"
@@ -73,7 +74,9 @@ void TrayOverallStatusResult::addResult(Folder *f)
lastSyncDone = time;
}
- auto status = f->syncPaused() || f->accountState()->state() == AccountState::PausedDueToMetered ? SyncResult::Paused : f->syncResult().status();
+ auto status = f->syncPaused() || NetworkInformation::instance()->isBehindCaptivePortal() || NetworkInformation::instance()->isMetered()
+ ? SyncResult::Paused
+ : f->syncResult().status();
if (status == SyncResult::Undefined) {
status = SyncResult::Problem;
}
diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp
index 0ab1b053f01..50f37dcc7a5 100644
--- a/src/gui/folderstatusmodel.cpp
+++ b/src/gui/folderstatusmodel.cpp
@@ -16,6 +16,7 @@
#include "account.h"
#include "accountstate.h"
#include "folderman.h"
+#include "gui/networkinformation.h"
#include "gui/quotainfo.h"
#include "theme.h"
@@ -49,7 +50,7 @@ namespace {
auto status = f->syncResult();
if (!f->accountState()->isConnected()) {
status.setStatus(SyncResult::Status::Offline);
- } else if (f->syncPaused() || f->accountState()->state() == AccountState::PausedDueToMetered) {
+ } else if (f->syncPaused() || NetworkInformation::instance()->isBehindCaptivePortal() || NetworkInformation::instance()->isMetered()) {
status.setStatus(SyncResult::Status::Paused);
}
return QStringLiteral("states/%1").arg(Theme::instance()->syncStateIconName(status));
diff --git a/src/gui/guiutility.cpp b/src/gui/guiutility.cpp
index 2c9c83a7cb0..e995b68c95e 100644
--- a/src/gui/guiutility.cpp
+++ b/src/gui/guiutility.cpp
@@ -22,7 +22,6 @@
#include
#include
#include
-#include
#include
#include
@@ -96,15 +95,6 @@ QString Utility::vfsFreeSpaceActionText()
return QCoreApplication::translate("utility", "Free up local space");
}
-bool Utility::internetConnectionIsMetered()
-{
- if (auto *qNetInfo = QNetworkInformation::instance()) {
- return qNetInfo->isMetered();
- }
-
- return false;
-}
-
void Utility::markDirectoryAsSyncRoot(const QString &path, const QUuid &accountUuid)
{
Q_ASSERT(getDirectorySyncRootMarkings(path).first.isEmpty());
diff --git a/src/gui/guiutility.h b/src/gui/guiutility.h
index 82d75a9ae88..6638e8e612c 100644
--- a/src/gui/guiutility.h
+++ b/src/gui/guiutility.h
@@ -52,8 +52,6 @@ namespace Utility {
QString socketApiSocketPath();
- bool internetConnectionIsMetered();
-
OWNCLOUDGUI_EXPORT void markDirectoryAsSyncRoot(const QString &path, const QUuid &accountUuid);
std::pair getDirectorySyncRootMarkings(const QString &path);
void unmarkDirectoryAsSyncRoot(const QString &path);
diff --git a/src/gui/main.cpp b/src/gui/main.cpp
index d4a7244fcc7..2066e7f692c 100644
--- a/src/gui/main.cpp
+++ b/src/gui/main.cpp
@@ -19,6 +19,7 @@
#include "common/utility.h"
#include "gui/application.h"
#include "gui/logbrowser.h"
+#include "gui/networkinformation.h"
#include "libsync/configfile.h"
#include "libsync/platform.h"
#include "libsync/theme.h"
@@ -39,7 +40,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -249,28 +249,6 @@ void setupLogging(const CommandLineOptions &options)
qCInfo(lcMain) << "Arguments:" << qApp->arguments();
}
-void loadQNetworkInformationBackend()
-{
- if (!QNetworkInformation::loadDefaultBackend()) {
- qCWarning(lcMain) << "Failed to load default backend of QNetworkInformation.";
- if (!QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Metered)) {
- qCWarning(lcMain) << "Failed to load backend of QNetworkInformation by metered feature.";
- if (!QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Reachability)) {
- qCWarning(lcMain) << "Failed to load backend of QNetworkInformation by reachability feature.";
- qCWarning(lcMain) << "Available backends:" << QNetworkInformation::availableBackends().join(QStringLiteral(", "));
- return;
- }
- }
- }
- qCDebug(lcMain) << "Loaded network information backend:" << QNetworkInformation::instance()->backendName();
- qCDebug(lcMain) << "Supported features:" << QNetworkInformation::instance()->supportedFeatures();
- qCDebug(lcMain) << "Available backends:" << QNetworkInformation::availableBackends().join(QStringLiteral(", "));
- if (auto qni = QNetworkInformation::instance()) {
- QObject::connect(qni, &QNetworkInformation::reachabilityChanged,
- [](QNetworkInformation::Reachability reachability) { qCInfo(lcMain) << "Connection Status changed to:" << reachability; });
- }
-}
-
QString setupTranslations(QApplication *app)
{
const auto trPath = Translations::translationsDirectoryPath();
@@ -480,7 +458,7 @@ int main(int argc, char **argv)
}
setupLogging(options);
- loadQNetworkInformationBackend();
+ NetworkInformation::instance(); //
platform->setApplication(&app);
diff --git a/src/gui/networkinformation.cpp b/src/gui/networkinformation.cpp
new file mode 100644
index 00000000000..b1309fdd877
--- /dev/null
+++ b/src/gui/networkinformation.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) by Erik Verbruggen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ */
+
+#include "networkinformation.h"
+
+#include
+
+using namespace OCC;
+
+Q_LOGGING_CATEGORY(lcNetInfo, "gui.netinfo", QtInfoMsg)
+
+NetworkInformation *NetworkInformation::_instance = nullptr;
+
+namespace {
+
+void loadQNetworkInformationBackend()
+{
+ if (!QNetworkInformation::loadDefaultBackend()) {
+ qCWarning(lcNetInfo) << "Failed to load default backend of QNetworkInformation.";
+ if (!QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Metered)) {
+ qCWarning(lcNetInfo) << "Failed to load backend of QNetworkInformation by metered feature.";
+ if (!QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Reachability)) {
+ qCWarning(lcNetInfo) << "Failed to load backend of QNetworkInformation by reachability feature.";
+ qCWarning(lcNetInfo) << "Available backends:" << QNetworkInformation::availableBackends().join(QStringLiteral(", "));
+ return;
+ }
+ }
+ }
+ qCDebug(lcNetInfo) << "Loaded network information backend:" << QNetworkInformation::instance()->backendName();
+ qCDebug(lcNetInfo) << "Supported features:" << QNetworkInformation::instance()->supportedFeatures();
+ qCDebug(lcNetInfo) << "Available backends:" << QNetworkInformation::availableBackends().join(QStringLiteral(", "));
+
+ if (auto qni = QNetworkInformation::instance()) {
+ QObject::connect(qni, &QNetworkInformation::reachabilityChanged, [qni](QNetworkInformation::Reachability reachability) {
+ qCInfo(lcNetInfo) << "Connection Status changed to:" << reachability << "captive portal status:" << qni->isBehindCaptivePortal();
+ });
+ }
+}
+
+} // anonymous namespace
+
+NetworkInformation::NetworkInformation() { }
+
+NetworkInformation *NetworkInformation::instance()
+{
+ if (!_instance) {
+ _instance = new NetworkInformation;
+
+ loadQNetworkInformationBackend();
+
+ if (auto qni = QNetworkInformation::instance()) {
+ connect(qni, &QNetworkInformation::isMeteredChanged, _instance, &NetworkInformation::isMeteredChanged);
+ connect(qni, &QNetworkInformation::reachabilityChanged, _instance, &NetworkInformation::reachabilityChanged);
+ connect(qni, &QNetworkInformation::isBehindCaptivePortalChanged, _instance, &NetworkInformation::slotIsBehindCaptivePortalChanged);
+ }
+ }
+
+ return _instance;
+}
+
+bool NetworkInformation::isMetered()
+{
+ if (auto *qNetInfo = QNetworkInformation::instance()) {
+ return qNetInfo->isMetered();
+ }
+
+ return false;
+}
+
+bool NetworkInformation::supports(Features features) const
+{
+ if (auto *qNetInfo = QNetworkInformation::instance()) {
+ return qNetInfo->supports(features);
+ }
+
+ return false;
+}
+
+bool NetworkInformation::isForcedCaptivePortal() const
+{
+ return _forcedCaptivePortal;
+}
+
+void NetworkInformation::setForcedCaptivePortal(bool onoff)
+{
+ if (_forcedCaptivePortal != onoff) {
+ _forcedCaptivePortal = onoff;
+ qCDebug(lcNetInfo) << "Switching forced captive portal to" << onoff;
+
+ bool behindCaptivePortal = false;
+
+ if (auto *qNetInfo = QNetworkInformation::instance()) {
+ behindCaptivePortal = qNetInfo->isBehindCaptivePortal();
+ }
+
+ if (behindCaptivePortal != _forcedCaptivePortal) {
+ Q_EMIT isBehindCaptivePortalChanged(_forcedCaptivePortal);
+ }
+ }
+}
+
+bool NetworkInformation::isBehindCaptivePortal() const
+{
+ if (_forcedCaptivePortal) {
+ return true;
+ }
+
+ if (auto *qNetInfo = QNetworkInformation::instance()) {
+ return qNetInfo->isBehindCaptivePortal();
+ }
+
+ return false;
+}
+
+void NetworkInformation::slotIsBehindCaptivePortalChanged(bool state)
+{
+ qCDebug(lcNetInfo) << "OS signals behind captive portal changed to" << state << "forced captive portal flag:" << _forcedCaptivePortal;
+
+ if (!_forcedCaptivePortal) {
+ Q_EMIT isBehindCaptivePortalChanged(state);
+ }
+}
diff --git a/src/gui/networkinformation.h b/src/gui/networkinformation.h
new file mode 100644
index 00000000000..075fad6a784
--- /dev/null
+++ b/src/gui/networkinformation.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) by Erik Verbruggen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ */
+
+#pragma once
+
+#include "gui/owncloudguilib.h"
+
+#include
+
+namespace OCC {
+
+/**
+ * @brief Wrapper class for QNetworkInformation
+ *
+ * This class is used instead of QNetworkInformation so we do not need to check for an instance,
+ * and to facilitate debugging by being able to force certain network states (i.e. captive portal).
+ */
+class OWNCLOUDGUI_EXPORT NetworkInformation : public QObject
+{
+ Q_OBJECT
+
+public:
+ static NetworkInformation *instance();
+
+ bool isMetered();
+
+ using Feature = QNetworkInformation::Feature;
+ using Features = QNetworkInformation::Features;
+ using Reachability = QNetworkInformation::Reachability;
+
+ bool supports(Features features) const;
+
+ bool isForcedCaptivePortal() const;
+ void setForcedCaptivePortal(bool onoff);
+ bool isBehindCaptivePortal() const;
+
+Q_SIGNALS:
+ void isMeteredChanged(bool isMetered);
+ void reachabilityChanged(NetworkInformation::Reachability reachability);
+ void isBehindCaptivePortalChanged(bool state);
+
+private Q_SLOTS:
+ void slotIsBehindCaptivePortalChanged(bool state);
+
+private:
+ NetworkInformation();
+
+ static NetworkInformation *_instance;
+
+ bool _forcedCaptivePortal = false;
+};
+
+}
diff --git a/src/gui/networksettings.cpp b/src/gui/networksettings.cpp
index 6774be81e80..99ccaa85d04 100644
--- a/src/gui/networksettings.cpp
+++ b/src/gui/networksettings.cpp
@@ -13,6 +13,7 @@
*/
#include "networksettings.h"
+#include "networkinformation.h"
#include "ui_networksettings.h"
#include "accountmanager.h"
@@ -22,7 +23,6 @@
#include "theme.h"
#include
-#include
#include
#include
#include
@@ -186,13 +186,11 @@ void NetworkSettings::loadBWLimitSettings()
void NetworkSettings::loadMeteredSettings()
{
- if (QNetworkInformation *qNetInfo = QNetworkInformation::instance()) {
- if (Utility::isWindows() // The backend implements the metered feature, but does not report it as supported.
- // See https://bugreports.qt.io/browse/QTBUG-118741
- || qNetInfo->supports(QNetworkInformation::Feature::Metered)) {
- _ui->pauseSyncWhenMeteredCheckbox->setChecked(ConfigFile().pauseSyncWhenMetered());
- return;
- }
+ if (Utility::isWindows() // The backend implements the metered feature, but does not report it as supported.
+ // See https://bugreports.qt.io/browse/QTBUG-118741
+ || NetworkInformation::instance()->supports(NetworkInformation::Feature::Metered)) {
+ _ui->pauseSyncWhenMeteredCheckbox->setChecked(ConfigFile().pauseSyncWhenMetered());
+ return;
}
_ui->pauseSyncWhenMeteredCheckbox->setVisible(false);
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index 71f5b87edc5..6d8f425695d 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -26,6 +26,7 @@
#include "folderwizard/folderwizard.h"
#include "gui/accountsettings.h"
#include "gui/commonstrings.h"
+#include "gui/networkinformation.h"
#include "guiutility.h"
#include "libsync/theme.h"
#include "logbrowser.h"
@@ -639,17 +640,22 @@ void ownCloudGui::updateContextMenu()
_contextMenu->addSeparator();
if (_app->debugMode()) {
- auto *crashMenu = _contextMenu->addMenu(QStringLiteral("Debug actions"));
- crashMenu->addAction(QStringLiteral("Crash if asserts enabled - OC_ENSURE"), _app, [] {
+ auto *debugMenu = _contextMenu->addMenu(QStringLiteral("Debug actions"));
+ debugMenu->addAction(QStringLiteral("Crash if asserts enabled - OC_ENSURE"), _app, [] {
if (OC_ENSURE(false)) {
Q_UNREACHABLE();
}
});
- crashMenu->addAction(QStringLiteral("Crash if asserts enabled - Q_ASSERT"), _app, [] { Q_ASSERT(false); });
- crashMenu->addAction(QStringLiteral("Crash now - Utility::crash()"), _app, [] { Utility::crash(); });
- crashMenu->addAction(QStringLiteral("Crash now - OC_ENFORCE()"), _app, [] { OC_ENFORCE(false); });
- crashMenu->addAction(QStringLiteral("Crash now - qFatal"), _app, [] { qFatal("la Qt fatale"); });
- crashMenu->addAction(QStringLiteral("Restart now"), _app, [] { RestartManager::requestRestart(); });
+ debugMenu->addAction(QStringLiteral("Crash if asserts enabled - Q_ASSERT"), _app, [] { Q_ASSERT(false); });
+ debugMenu->addAction(QStringLiteral("Crash now - Utility::crash()"), _app, [] { Utility::crash(); });
+ debugMenu->addAction(QStringLiteral("Crash now - OC_ENFORCE()"), _app, [] { OC_ENFORCE(false); });
+ debugMenu->addAction(QStringLiteral("Crash now - qFatal"), _app, [] { qFatal("la Qt fatale"); });
+ debugMenu->addAction(QStringLiteral("Restart now"), _app, [] { RestartManager::requestRestart(); });
+ debugMenu->addSeparator();
+ auto captivePortalCheckbox = debugMenu->addAction(QStringLiteral("Behind Captive Portal"));
+ captivePortalCheckbox->setCheckable(true);
+ captivePortalCheckbox->setChecked(NetworkInformation::instance()->isForcedCaptivePortal());
+ connect(captivePortalCheckbox, &QAction::triggered, [](bool checked) { NetworkInformation::instance()->setForcedCaptivePortal(checked); });
}
_contextMenu->addSeparator();
diff --git a/src/gui/scheduling/syncscheduler.cpp b/src/gui/scheduling/syncscheduler.cpp
index 42cf5b492a9..0b5b219cb7c 100644
--- a/src/gui/scheduling/syncscheduler.cpp
+++ b/src/gui/scheduling/syncscheduler.cpp
@@ -15,14 +15,13 @@
#include "gui/scheduling/syncscheduler.h"
#include "gui/folderman.h"
+#include "gui/networkinformation.h"
#include "gui/scheduling/etagwatcher.h"
#include "libsync/configfile.h"
#include "libsync/syncengine.h"
#include "guiutility.h"
-#include
-
using namespace std::chrono_literals;
using namespace OCC;
@@ -168,7 +167,7 @@ void SyncScheduler::startNext()
auto syncPriority = nextSync.second;
if (!_currentSync.isNull()) {
- if (_pauseSyncWhenMetered && Utility::internetConnectionIsMetered()) {
+ if (_pauseSyncWhenMetered && NetworkInformation::instance()->isMetered()) {
if (syncPriority == Priority::High) {
qCInfo(lcSyncScheduler) << "Scheduler is paused due to metered internet connection, BUT next sync is HIGH priority, so allow sync to start";
} else {
diff --git a/src/libsync/jobqueue.h b/src/libsync/jobqueue.h
index 70a24b65abd..438849c1c14 100644
--- a/src/libsync/jobqueue.h
+++ b/src/libsync/jobqueue.h
@@ -47,14 +47,15 @@ class OWNCLOUDSYNC_EXPORT JobQueue
size_t size() const;
-private:
- void block();
- void unblock();
/**
* Clear the queue and abort all jobs
*/
void clear();
+private:
+ void block();
+ void unblock();
+
Account *_account;
uint _blocked = 0;
std::vector> _jobs;
diff --git a/test/testfoldermigration.cpp b/test/testfoldermigration.cpp
index 6bd36b37330..643ca329fab 100644
--- a/test/testfoldermigration.cpp
+++ b/test/testfoldermigration.cpp
@@ -11,6 +11,7 @@
#include "common/utility.h"
#include "configfile.h"
#include "folderman.h"
+#include "gui/networkinformation.h"
#include "testutils/testutils.h"
using namespace OCC;
diff --git a/test/testutils/testutilsloader.cpp b/test/testutils/testutilsloader.cpp
index f5b137e22c7..016ed935eb2 100644
--- a/test/testutils/testutilsloader.cpp
+++ b/test/testutils/testutilsloader.cpp
@@ -1,4 +1,5 @@
#include "configfile.h"
+#include "gui/networkinformation.h"
#include "logger.h"
#include "resources/loadresources.h"
#include "testutils.h"
@@ -19,6 +20,9 @@ void setUpTests()
OCC::Logger::instance()->setLogDebug(true);
OCC::Account::setCommonCacheDirectory(QStringLiteral("%1/cache").arg(dir.path()));
+
+ // ensure we have an instance of NetworkInformation
+ OCC::NetworkInformation::instance();
}
Q_COREAPP_STARTUP_FUNCTION(setUpTests)
}