increase time between connection tries
authorMatthieu Gallien <matthieu.gallien@nextcloud.com>
Tue, 12 Apr 2022 21:43:19 +0000 (23:43 +0200)
committerMatthieu Gallien <matthieu.gallien@nextcloud.com>
Fri, 29 Apr 2022 10:15:15 +0000 (12:15 +0200)
each time we do a failed attempt to connect increase the time until the
next try

use an elapsed timer to ensure we do properly wait between each attempt

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
src/gui/accountstate.cpp
src/gui/accountstate.h
src/gui/application.cpp

index 6100f70fc8b3c3af45cad7b15253ed1d30136fea..c86efc11a08048337102965eac775eae2ee7d608 100644 (file)
 #include "logger.h"
 #include "configfile.h"
 #include "ocsnavigationappsjob.h"
+#include "pushnotifications.h"
 
 #include <QSettings>
 #include <QTimer>
-#include <qfontmetrics.h>
-
+#include <QFontMetrics>
 #include <QJsonDocument>
 #include <QJsonObject>
 #include <QJsonArray>
 #include <QNetworkRequest>
 #include <QBuffer>
 
+#include <cmath>
+
 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){
index e2b0a927d0eeed3d3cf21a811843c0e6d00d5011..c9d9acbea15e4b60a4fc9e6afd341f659fb801bd 100644 (file)
 #ifndef ACCOUNTINFO_H
 #define ACCOUNTINFO_H
 
+#include "connectionvalidator.h"
+#include "creds/abstractcredentials.h"
+
 #include <QByteArray>
 #include <QElapsedTimer>
 #include <QPointer>
-#include "connectionvalidator.h"
-#include "creds/abstractcredentials.h"
+#include <QTimer>
 
 #include <memory>
 
@@ -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
index 9fbd620eba9c98a9d32676c42ce4ee387c546cf2..b1a3561eacda65e65d899cb39fb7ef2d09305242 100644 (file)
@@ -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();