Skip to content

Commit

Permalink
Pause sync when on a metered Internet connection
Browse files Browse the repository at this point in the history
Add an advanced configuration setting to pause synchronization when a
metered internet connection is detected.

When the internet connection is metered, and the user chooses to
force-sync a folder, they will now be asked if they are sure that they
want to do this.

Fixes: #4808
  • Loading branch information
erikjv committed Jan 3, 2024
1 parent dc7b2f2 commit 5e453ff
Show file tree
Hide file tree
Showing 19 changed files with 204 additions and 46 deletions.
6 changes: 6 additions & 0 deletions changelog/unreleased/4808
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: Add option to pause synchronization on metered connections

On platforms that support metered-connection detection, an option is now
available to pause folder synchronization when the network connection
switches to metered. When synchronization is paused, force-syncing can
still be done.
58 changes: 45 additions & 13 deletions src/gui/accountsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include <QIcon>
#include <QKeySequence>
#include <QMessageBox>
#include <QNetworkInformation>
#include <QPropertyAnimation>
#include <QSortFilterProxyModel>
#include <QToolTip>
Expand Down Expand Up @@ -642,19 +643,42 @@ void AccountSettings::slotScheduleCurrentFolderForceFullDiscovery()
void AccountSettings::slotForceSyncCurrentFolder()
{
if (auto selectedFolder = this->selectedFolder()) {
// Terminate and reschedule any running sync
for (auto *folder : FolderMan::instance()->folders()) {
if (folder->isSyncRunning()) {
folder->slotTerminateSync(tr("User triggered force sync"));
FolderMan::instance()->scheduler()->enqueueFolder(folder);
}
if (Utility::internetConnectionIsMetered() && 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"
"<p>Do you really want to force a Synchronization now?"),
QMessageBox::Yes | QMessageBox::No, ocApp()->gui()->settingsDialog());
messageBox->setAttribute(Qt::WA_DeleteOnClose);
connect(messageBox, &QMessageBox::accepted, this, [this, selectedFolder] { doForceSyncCurrentFolder(selectedFolder); });
messageBox->open();
ownCloudGui::raiseDialog(messageBox);
} else {
doForceSyncCurrentFolder(selectedFolder);
}
}
}

selectedFolder->slotWipeErrorBlacklist(); // issue #6757
selectedFolder->slotNextSyncFullLocalDiscovery(); // ensure we don't forget about local errors
// Insert the selected folder at the front of the queue
FolderMan::instance()->scheduler()->enqueueFolder(selectedFolder, SyncScheduler::Priority::High);
void AccountSettings::doForceSyncCurrentFolder(Folder *selectedFolder)
{
// Prevent new sync starts
FolderMan::instance()->scheduler()->stop();

// Terminate and reschedule any running sync
for (auto *folder : FolderMan::instance()->folders()) {
if (folder->isSyncRunning()) {
folder->slotTerminateSync(tr("User triggered force sync"));
FolderMan::instance()->scheduler()->enqueueFolder(folder);
}
}

selectedFolder->slotWipeErrorBlacklist(); // issue #6757
selectedFolder->slotNextSyncFullLocalDiscovery(); // ensure we don't forget about local errors

// Insert the selected folder at the front of the queue
FolderMan::instance()->scheduler()->enqueueFolder(selectedFolder, SyncScheduler::Priority::High);

// Restart scheduler
FolderMan::instance()->scheduler()->start();
}

void AccountSettings::slotAccountStateChanged()
Expand All @@ -670,19 +694,27 @@ void AccountSettings::slotAccountStateChanged()
_model->slotUpdateFolderState(folder);
}

auto acceptOAuthLogin = [this]() {
if (_askForOAuthLoginDialog != nullptr) {
_askForOAuthLoginDialog->accept();
}
};

const QString server = QStringLiteral("<a href=\"%1\">%1</a>")
.arg(Utility::escape(safeUrl.toString()));

switch (state) {
case AccountState::PausedDueToMetered:
showConnectionLabel(tr("Sync to %1 is paused due to metered internet connection.").arg(server));
acceptOAuthLogin();
break;
case AccountState::Connected: {
QStringList errors;
if (account->serverSupportLevel() != Account::ServerSupportLevel::Supported) {
errors << tr("The server version %1 is unsupported! Proceed at your own risk.").arg(account->capabilities().status().versionString());
}
showConnectionLabel(tr("Connected to %1.").arg(server), errors);
if (_askForOAuthLoginDialog != nullptr) {
_askForOAuthLoginDialog->accept();
}
acceptOAuthLogin();
break;
}
case AccountState::ServiceUnavailable:
Expand Down
1 change: 1 addition & 0 deletions src/gui/accountsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ protected slots:
QStringList errors = QStringList());
bool event(QEvent *) override;
void createAccountToolbox();
void doForceSyncCurrentFolder(Folder *selectedFolder);

/// Returns the alias of the selected folder, empty string if none
Folder *selectedFolder() const;
Expand Down
17 changes: 16 additions & 1 deletion src/gui/accountstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "application.h"
#include "configfile.h"
#include "fetchserversettings.h"
#include "guiutility.h"

#include "libsync/creds/abstractcredentials.h"
#include "libsync/creds/httpcredentials.h"
Expand Down Expand Up @@ -136,6 +137,18 @@ AccountState::AccountState(AccountPtr account)
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);
}
}
});
}
#endif
// as a fallback and to recover after server issues we also poll
Expand Down Expand Up @@ -231,6 +244,8 @@ void AccountState::setState(State state)
_connectionValidator->deleteLater();
_connectionValidator.clear();
checkConnectivity();
} else if (_state == Connected && Utility::internetConnectionIsMetered() && ConfigFile().pauseSyncWhenMetered()) {
_state = PausedDueToMetered;
}
}

Expand Down Expand Up @@ -290,7 +305,7 @@ void AccountState::signIn()

bool AccountState::isConnected() const
{
return _state == Connected;
return _state == Connected || _state == PausedDueToMetered;
}

void AccountState::tagLastSuccessfullETagRequest(const QDateTime &tp)
Expand Down
5 changes: 5 additions & 0 deletions src/gui/accountstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ class 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)
Expand Down
1 change: 0 additions & 1 deletion src/gui/folder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,6 @@ void Folder::setVirtualFilesEnabled(bool enabled)
};
if (isSyncRunning()) {
connect(this, &Folder::syncFinished, this, finalizeVfsSwitch, Qt::SingleShotConnection);
QString reason;
slotTerminateSync(tr("Switching VFS mode on folder '%1'").arg(displayName()));
} else {
finalizeVfsSwitch();
Expand Down
1 change: 1 addition & 0 deletions src/gui/folder.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ class Folder : public QObject
{
return *_engine;
}

Vfs &vfs()
{
OC_ENFORCE(_vfs);
Expand Down
2 changes: 1 addition & 1 deletion src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void TrayOverallStatusResult::addResult(Folder *f)
lastSyncDone = time;
}

auto status = f->syncPaused() ? SyncResult::Paused : f->syncResult().status();
auto status = f->syncPaused() || f->accountState()->state() == AccountState::PausedDueToMetered ? SyncResult::Paused : f->syncResult().status();
if (status == SyncResult::Undefined) {
status = SyncResult::Problem;
}
Expand Down
2 changes: 1 addition & 1 deletion src/gui/folderstatusmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
auto status = f->syncResult();
if (!accountConnected) {
status.setStatus(SyncResult::Status::Offline);
} else if (f->syncPaused()) {
} else if (f->syncPaused() || f->accountState()->state() == AccountState::PausedDueToMetered) {
status.setStatus(SyncResult::Status::Paused);
}
return Theme::instance()->syncStateIconName(status);
Expand Down
14 changes: 12 additions & 2 deletions src/gui/guiutility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
#include "application.h"
#include "settingsdialog.h"

#include <QClipboard>
#include <QApplication>
#include <QClipboard>
#include <QDesktopServices>
#include <QIcon>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QNetworkInformation>
#include <QUrlQuery>
#include <QIcon>

#include "theme.h"

Expand Down Expand Up @@ -85,3 +86,12 @@ QString Utility::vfsFreeSpaceActionText()
{
return QCoreApplication::translate("utility", "Free up local space");
}

bool Utility::internetConnectionIsMetered()
{
if (auto *qNetInfo = QNetworkInformation::instance()) {
return qNetInfo->isMetered();
}

return false;
}
1 change: 1 addition & 0 deletions src/gui/guiutility.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ namespace Utility {

QString socketApiSocketPath();

bool internetConnectionIsMetered();
} // namespace Utility
} // namespace OCC

Expand Down
8 changes: 6 additions & 2 deletions src/gui/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,16 +449,20 @@ int main(int argc, char **argv)
return -1;
}

setupLogging(options);

#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
qCDebug(lcMain) << QNetworkInformation::availableBackends().join(QStringLiteral(", "));
if (!QNetworkInformation::loadDefaultBackend()) {
qCWarning(lcMain) << "Failed to load QNetworkInformation";
} else {
qCDebug(lcMain) << "Loaded network information backend:" << QNetworkInformation::instance()->backendName()
<< "supported features:" << QNetworkInformation::instance()->supportedFeatures();
}
#else
qCWarning(lcMain) << "QNetworkInformation is not available";
#endif

setupLogging(options);

platform->setApplication(&app);

auto folderManager = FolderMan::createInstance();
Expand Down
26 changes: 26 additions & 0 deletions src/gui/networksettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "theme.h"

#include <QList>
#include <QNetworkInformation>
#include <QNetworkProxy>
#include <QString>
#include <QtGui/QtEvents>
Expand Down Expand Up @@ -69,6 +70,7 @@ NetworkSettings::NetworkSettings(QWidget *parent)

loadProxySettings();
loadBWLimitSettings();
loadMeteredSettings();

// proxy
connect(_ui->typeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &NetworkSettings::saveProxySettings);
Expand All @@ -92,6 +94,8 @@ NetworkSettings::NetworkSettings(QWidget *parent)
connect(_ui->hostLineEdit, &QLineEdit::textChanged, this, &NetworkSettings::checkEmptyProxyHost);
checkEmptyProxyHost();
checkAccountLocalhost();

connect(_ui->pauseSyncWhenMeteredCheckbox, &QAbstractButton::clicked, this, &NetworkSettings::saveMeteredSettings);
}

NetworkSettings::~NetworkSettings()
Expand Down Expand Up @@ -177,6 +181,21 @@ void NetworkSettings::loadBWLimitSettings()
_ui->uploadSpinBox->setValue(cfgFile.uploadLimit());
}

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

_ui->pauseSyncWhenMeteredCheckbox->setEnabled(false);
_ui->pauseSyncWhenMeteredCheckbox->setToolTip(tr("Querying metered connection status is not supported on this platform"));
}

void NetworkSettings::saveProxySettings()
{
ConfigFile cfgFile;
Expand Down Expand Up @@ -231,6 +250,13 @@ void NetworkSettings::saveBWLimitSettings()
FolderMan::instance()->setDirtyNetworkLimits();
}

void NetworkSettings::saveMeteredSettings()
{
bool pauseSyncWhenMetered = _ui->pauseSyncWhenMeteredCheckbox->isChecked();
ConfigFile().setPauseSyncWhenMetered(pauseSyncWhenMetered);
FolderMan::instance()->scheduler()->setPauseSyncWhenMetered(pauseSyncWhenMetered);
}

void NetworkSettings::checkEmptyProxyHost()
{
if (_ui->hostLineEdit->isEnabled() && _ui->hostLineEdit->text().isEmpty()) {
Expand Down
2 changes: 2 additions & 0 deletions src/gui/networksettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class NetworkSettings : public QWidget
private slots:
void saveProxySettings();
void saveBWLimitSettings();
void saveMeteredSettings();

/// Red marking of host field if empty and enabled
void checkEmptyProxyHost();
Expand All @@ -53,6 +54,7 @@ private slots:
private:
void loadProxySettings();
void loadBWLimitSettings();
void loadMeteredSettings();
CredentialManager *_credentialManager;


Expand Down
7 changes: 7 additions & 0 deletions src/gui/networksettings.ui
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="pauseSyncWhenMeteredCheckbox">
<property name="text">
<string>Pause synchronization when the Internet connection is metered</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="proxyGroupBox">
<property name="enabled">
Expand Down
Loading

0 comments on commit 5e453ff

Please sign in to comment.