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