diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 6100f70fc8..c86efc11a0 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -21,17 +21,19 @@ #include "logger.h" #include "configfile.h" #include "ocsnavigationappsjob.h" +#include "pushnotifications.h" #include #include -#include - +#include #include #include #include #include #include +#include + namespace OCC { Q_LOGGING_CATEGORY(lcAccountState, "nextcloud.gui.account.state", QtInfoMsg) @@ -54,6 +56,8 @@ AccountState::AccountState(AccountPtr account) this, &AccountState::slotCredentialsFetched); connect(account.data(), &Account::credentialsAsked, this, &AccountState::slotCredentialsAsked); + connect(account.data(), &Account::pushNotificationsReady, + this, &AccountState::slotPushNotificationsReady); connect(this, &AccountState::isConnectedChanged, [=]{ // Get the Apps available on the server if we're now connected. @@ -61,6 +65,12 @@ AccountState::AccountState(AccountPtr account) fetchNavigationApps(); } }); + + connect(&_checkConnectionTimer, &QTimer::timeout, this, &AccountState::slotCheckConnection); + _checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingIntervalMsec); + _checkConnectionTimer.start(); + + QTimer::singleShot(0, this, &AccountState::slotCheckConnection); } AccountState::~AccountState() = default; @@ -120,6 +130,10 @@ void AccountState::setState(State state) if (oldState == Connected || _state == Connected) { emit isConnectedChanged(); } + if (_state == Connected) { + _checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingIntervalMsec); + setRetryCount(0); + } } // might not have changed but the underlying _connectionErrors might have @@ -149,6 +163,16 @@ QString AccountState::stateString(State state) return tr("Unknown account state"); } +int AccountState::retryCount() const +{ + return _retryCount; +} + +void AccountState::increaseRetryCount() +{ + ++_retryCount; +} + bool AccountState::isSignedOut() const { return _state == SignedOut; @@ -234,8 +258,15 @@ void AccountState::trySignIn() } } +void AccountState::systemOnlineConfigurationChanged() +{ + QMetaObject::invokeMethod(this, "slotCheckConnection", Qt::QueuedConnection); +} + void AccountState::checkConnectivity() { + qCInfo(lcAccountState()) << "check connectivity"; + if (isSignedOut() || _waitingForNewCredentials) { return; } @@ -293,6 +324,20 @@ void AccountState::checkConnectivity() void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors) { + const auto updateRetryCount = [this]() { + increaseRetryCount(); + qCInfo(lcAccountState()) << "connection retry count" << retryCount(); + _lastCheckConnectionTimer.invalidate(); + _lastCheckConnectionTimer.start(); + }; + + const auto resetRetryCount = [this]() { + qCInfo(lcAccountState) << "reset retry count"; + setRetryCount(0); + _lastCheckConnectionTimer.invalidate(); + _lastCheckConnectionTimer.start(); + }; + if (isSignedOut()) { qCWarning(lcAccountState) << "Signed out, ignoring" << status << _account->url().toString(); return; @@ -329,6 +374,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta case ConnectionValidator::Connected: if (_state != Connected) { setState(Connected); + resetRetryCount(); // Get the Apps available on the server. fetchNavigationApps(); @@ -340,6 +386,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta case ConnectionValidator::Undefined: case ConnectionValidator::NotConfigured: setState(Disconnected); + updateRetryCount(); break; case ConnectionValidator::ServerVersionMismatch: setState(ConfigurationError); @@ -349,6 +396,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta // or because we are having network issues. The latter one is // much more likely, so keep trying to connect. setState(NetworkError); + updateRetryCount(); break; case ConnectionValidator::CredentialsWrong: case ConnectionValidator::CredentialsNotReady: @@ -367,6 +415,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta break; case ConnectionValidator::Timeout: setState(NetworkError); + updateRetryCount(); break; } } @@ -456,6 +505,11 @@ void AccountState::fetchNavigationApps(){ job->getNavigationApps(); } +void AccountState::setRetryCount(int count) +{ + _retryCount = count; +} + void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){ if(statusCode == 200){ qCDebug(lcAccountState) << "New navigation apps ETag Response Header received " << value; @@ -468,6 +522,42 @@ void AccountState::slotOcsError(int statusCode, const QString &message) qCDebug(lcAccountState) << "Error " << statusCode << " while fetching new navigation apps: " << message; } +void AccountState::slotCheckConnection() +{ + if (_lastCheckConnectionTimer.isValid()) { + const auto currentDelay = (retryCount() <= 1 ? ConnectionValidator::DefaultCallingIntervalMsec : + (retryCount() == 2 ? ConnectionValidator::DefaultCallingIntervalMsec * 2 : + (retryCount() == 3 ? ConnectionValidator::DefaultCallingIntervalMsec * 4 : + ConnectionValidator::DefaultCallingIntervalMsec * 8))); + + if (!_lastCheckConnectionTimer.hasExpired(currentDelay - 1)) { + qCInfo(lcAccountState()) << "timer has not expired: do not check now" << _lastCheckConnectionTimer.elapsed() << currentDelay; + return; + } + } + + const auto currentState = state(); + + // Don't check if we're manually signed out or + // when the error is permanent. + const auto pushNotifications = account()->pushNotifications(); + const auto pushNotificationsAvailable = (pushNotifications && pushNotifications->isReady()); + if (currentState != AccountState::SignedOut && currentState != AccountState::ConfigurationError + && currentState != AccountState::AskingCredentials && !pushNotificationsAvailable) { + checkConnectivity(); + } else if (currentState == AccountState::SignedOut && lastConnectionStatus() == AccountState::ConnectionStatus::SslError) { + qCWarning(lcAccountState()) << "Account is signed out due to SSL Handshake error. Going to perform a sign-in attempt..."; + trySignIn(); + } +} + +void AccountState::slotPushNotificationsReady() +{ + if (state() != AccountState::State::Connected) { + setState(AccountState::State::Connected); + } +} + void AccountState::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode) { if(_account){ diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index e2b0a927d0..c9d9acbea1 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -16,11 +16,13 @@ #ifndef ACCOUNTINFO_H #define ACCOUNTINFO_H +#include "connectionvalidator.h" +#include "creds/abstractcredentials.h" + #include #include #include -#include "connectionvalidator.h" -#include "creds/abstractcredentials.h" +#include #include @@ -104,6 +106,9 @@ public: State state() const; static QString stateString(State state); + int retryCount() const; + void increaseRetryCount(); + bool isSignedOut() const; AccountAppList appList() const; @@ -175,6 +180,8 @@ public: void trySignIn(); + void systemOnlineConfigurationChanged(); + public slots: /// Triggers a ping to the server to update state and /// connection status and errors. @@ -184,6 +191,8 @@ private: void setState(State state); void fetchNavigationApps(); + void setRetryCount(int count); + signals: void stateChanged(State state); void isConnectedChanged(); @@ -205,6 +214,11 @@ protected Q_SLOTS: void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode); void slotOcsError(int statusCode, const QString &message); +private Q_SLOTS: + + void slotCheckConnection(); + void slotPushNotificationsReady(); + private: AccountPtr _account; State _state; @@ -241,6 +255,12 @@ private: AccountAppList _apps; bool _isDesktopNotificationsAllowed; + + int _retryCount = 0; + + QTimer _checkConnectionTimer; + QElapsedTimer _lastCheckConnectionTimer; + }; class AccountApp : public QObject diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 9fbd620eba..b1a3561eac 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -470,30 +470,16 @@ void Application::slotCleanup() void Application::slotSystemOnlineConfigurationChanged(QNetworkConfiguration cnf) { if (cnf.state() & QNetworkConfiguration::Active) { - QMetaObject::invokeMethod(this, "slotCheckConnection", Qt::QueuedConnection); + const auto list = AccountManager::instance()->accounts(); + for (const auto &accountState : list) { + accountState->systemOnlineConfigurationChanged(); + } } } void Application::slotCheckConnection() { - const auto list = AccountManager::instance()->accounts(); - for (const auto &accountState : list) { - AccountState::State state = accountState->state(); - - // Don't check if we're manually signed out or - // when the error is permanent. - const auto pushNotifications = accountState->account()->pushNotifications(); - const auto pushNotificationsAvailable = (pushNotifications && pushNotifications->isReady()); - if (state != AccountState::SignedOut && state != AccountState::ConfigurationError - && state != AccountState::AskingCredentials && !pushNotificationsAvailable) { - accountState->checkConnectivity(); - } else if (state == AccountState::SignedOut && accountState->lastConnectionStatus() == AccountState::ConnectionStatus::SslError) { - qCWarning(lcApplication) << "Account is signed out due to SSL Handshake error. Going to perform a sign-in attempt..."; - accountState->trySignIn(); - } - } - - if (list.isEmpty()) { + if (AccountManager::instance()->accounts().isEmpty()) { // let gui open the setup wizard _gui->slotOpenSettingsDialog();