Add UserInfo class and fetch quota via API instead of PropfindJob
authorMichael Schuster <michael@schuster.ms>
Fri, 14 Feb 2020 01:10:01 +0000 (02:10 +0100)
committerMichael Schuster <michael@schuster.ms>
Fri, 14 Feb 2020 01:10:01 +0000 (02:10 +0100)
The PropfindJob quota includes the size of shares and thus leads to confusion
in regard of the real space available, as shown in the UI.
This commit aims to streamline the behaviour with the Android and iOS apps,
which also utilize the API.

Details:
- Refactor the QuotaInfo class into UserInfo
- Use JsonApiJob (ocs/v1.php/cloud/user) instead of PropfindJob
- Let ConnectionValidator use the new UserInfo class to fetch
  the user and the avatar image (to avoid code duplication)
- Allow updating the avatar image upon AccountSettings visibility,
  using UserInfo's quota fetching

Signed-off-by: Michael Schuster <michael@schuster.ms>
src/gui/CMakeLists.txt
src/gui/accountsettings.cpp
src/gui/accountsettings.h
src/gui/accountstate.cpp
src/gui/connectionvalidator.cpp
src/gui/connectionvalidator.h
src/gui/quotainfo.cpp [deleted file]
src/gui/quotainfo.h [deleted file]
src/gui/userinfo.cpp [new file with mode: 0644]
src/gui/userinfo.h [new file with mode: 0644]

index 4ccf3abaaee9b497a1ee0510f2be6cef246dba11..0e5f04bffdb67faa0b77ee487c936555a6050df2 100644 (file)
@@ -87,7 +87,7 @@ set(client_SRCS
     syncrunfilelog.cpp
     systray.cpp
     thumbnailjob.cpp
-    quotainfo.cpp
+    userinfo.cpp
     accountstate.cpp
     addcertificatedialog.cpp
     authenticationdialog.cpp
index 1c9e3b47d6cafb2243524d6efdc4058c9e7126a6..a8d07e9dbd6a839aa49255175a95e3d089c398f3 100644 (file)
@@ -26,7 +26,7 @@
 #include "configfile.h"
 #include "account.h"
 #include "accountstate.h"
-#include "quotainfo.h"
+#include "userinfo.h"
 #include "accountmanager.h"
 #include "owncloudsetupwizard.h"
 #include "creds/abstractcredentials.h"
@@ -112,7 +112,7 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
     , _ui(new Ui::AccountSettings)
     , _wasDisabledBefore(false)
     , _accountState(accountState)
-    , _quotaInfo(accountState)
+    , _userInfo(accountState, false, true)
     , _menuShown(false)
 {
     _ui->setupUi(this);
@@ -189,7 +189,7 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
     connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged);
     slotAccountStateChanged();
 
-    connect(&_quotaInfo, &QuotaInfo::quotaUpdated,
+    connect(&_userInfo, &UserInfo::quotaUpdated,
         this, &AccountSettings::slotUpdateQuota);
 
     // Connect E2E stuff
@@ -1173,7 +1173,7 @@ void AccountSettings::slotDeleteAccount()
 bool AccountSettings::event(QEvent *e)
 {
     if (e->type() == QEvent::Hide || e->type() == QEvent::Show) {
-        _quotaInfo.setActive(isVisible());
+        _userInfo.setActive(isVisible());
     }
     if (e->type() == QEvent::Show) {
         // Expand the folder automatically only if there's only one, see #4283
index e620029a5c305ae9ae2e8b1faed6087d6a2494c2..68ddd01bb5eb453b47a5942c17fcdff8fe96d12a 100644 (file)
@@ -22,7 +22,7 @@
 #include <QTimer>
 
 #include "folder.h"
-#include "quotainfo.h"
+#include "userinfo.h"
 #include "progressdispatcher.h"
 #include "owncloudgui.h"
 #include "folderstatusmodel.h"
@@ -139,7 +139,7 @@ private:
     QUrl _OCUrl;
     bool _wasDisabledBefore;
     AccountState *_accountState;
-    QuotaInfo _quotaInfo;
+    UserInfo _userInfo;
     QAction *_toggleSignInOutAction;
     QAction *_addAccountAction;
 
index 1491db714eed209a5fc9d2e3d966d3aeebd9eaff..90eec6275081d16372cb742b86d3ee08d3260e9e 100644 (file)
@@ -236,7 +236,7 @@ void AccountState::checkConnectivity()
         return;
     }
 
-    ConnectionValidator *conValidator = new ConnectionValidator(account());
+    ConnectionValidator *conValidator = new ConnectionValidator(AccountStatePtr(this));
     _connectionValidator = conValidator;
     connect(conValidator, &ConnectionValidator::connectionResult,
         this, &AccountState::slotConnectionValidatorResult);
index 97bdf7573fed99acf60bf38291774b28c8e5b2df..3b9f8f4cadbc37efc2eb62ef92e2e147aa8637ee 100644 (file)
@@ -22,6 +22,8 @@
 
 #include "connectionvalidator.h"
 #include "account.h"
+#include "accountstate.h"
+#include "userinfo.h"
 #include "networkjobs.h"
 #include "clientproxy.h"
 #include <creds/abstractcredentials.h>
@@ -34,9 +36,10 @@ Q_LOGGING_CATEGORY(lcConnectionValidator, "nextcloud.sync.connectionvalidator",
 // This makes sure we get tried often enough without "ConnectionValidator already running"
 static qint64 timeoutToUseMsec = qMax(1000, ConnectionValidator::DefaultCallingIntervalMsec - 5 * 1000);
 
-ConnectionValidator::ConnectionValidator(AccountPtr account, QObject *parent)
+ConnectionValidator::ConnectionValidator(AccountStatePtr accountState, QObject *parent)
     : QObject(parent)
-    , _account(account)
+    , _accountState(accountState)
+    , _account(accountState->account())
     , _isCheckingServerAndAuth(false)
 {
 }
@@ -270,10 +273,9 @@ void ConnectionValidator::ocsConfigReceived(const QJsonDocument &json, AccountPt
 
 void ConnectionValidator::fetchUser()
 {
-    JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/user"), this);
-    job->setTimeout(timeoutToUseMsec);
-    QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotUserFetched);
-    job->start();
+    UserInfo *userInfo = new UserInfo(_accountState.data(), true, true, this);
+    QObject::connect(userInfo, &UserInfo::fetchedLastInfo, this, &ConnectionValidator::slotUserFetched);
+    userInfo->setActive(true);
 }
 
 bool ConnectionValidator::setAndCheckServerVersion(const QString &version)
@@ -305,34 +307,22 @@ bool ConnectionValidator::setAndCheckServerVersion(const QString &version)
     return true;
 }
 
-void ConnectionValidator::slotUserFetched(const QJsonDocument &json)
+void ConnectionValidator::slotUserFetched(UserInfo *userInfo)
 {
-    QString user = json.object().value("ocs").toObject().value("data").toObject().value("id").toString();
-    if (!user.isEmpty()) {
-        _account->setDavUser(user);
-    }
-    QString displayName = json.object().value("ocs").toObject().value("data").toObject().value("display-name").toString();
-    if (!displayName.isEmpty()) {
-        _account->setDavDisplayName(displayName);
+    if(userInfo) {
+        userInfo->setActive(false);
+        userInfo->deleteLater();
     }
+
 #ifndef TOKEN_AUTH_ONLY
-    AvatarJob *job = new AvatarJob(_account, _account->davUser(), 128, this);
-    job->setTimeout(20 * 1000);
-    QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage);
-    job->start();
+    connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
+    _account->e2e()->initialize();
 #else
     reportResult(Connected);
 #endif
 }
 
 #ifndef TOKEN_AUTH_ONLY
-void ConnectionValidator::slotAvatarImage(const QImage &img)
-{
-    _account->setAvatar(img);
-    connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
-    _account->e2e()->initialize();
-}
-
 void ConnectionValidator::reportConnected() {
     reportResult(Connected);
 }
index 779b6a8a263e82cdec2034fccd8244ee52651f4d..079fc36e6cde5a175e92715d21fc057b46171f3c 100644 (file)
@@ -67,23 +67,20 @@ namespace OCC {
     +---------------------------------+
     |
   fetchUser
-        PropfindJob
-        |
-        +-> slotUserFetched
-              AvatarJob
-              |
-              +-> slotAvatarImage -->
+        Utilizes the UserInfo class to fetch the user and avatar image
   +-----------------------------------+
   |
   +-> Client Side Encryption Checks --+ --reportResult()
     \endcode
  */
 
+class UserInfo;
+
 class ConnectionValidator : public QObject
 {
     Q_OBJECT
 public:
-    explicit ConnectionValidator(AccountPtr account, QObject *parent = nullptr);
+    explicit ConnectionValidator(AccountStatePtr accountState, QObject *parent = nullptr);
 
     enum Status {
         Undefined,
@@ -125,13 +122,12 @@ protected slots:
     void slotAuthSuccess();
 
     void slotCapabilitiesRecieved(const QJsonDocument &);
-    void slotUserFetched(const QJsonDocument &);
-#ifndef TOKEN_AUTH_ONLY
-    void slotAvatarImage(const QImage &img);
-#endif
+    void slotUserFetched(UserInfo *userInfo);
 
 private:
+#ifndef TOKEN_AUTH_ONLY
     void reportConnected();
+#endif
     void reportResult(Status status);
     void checkServerCapabilities();
     void fetchUser();
@@ -144,6 +140,7 @@ private:
     bool setAndCheckServerVersion(const QString &version);
 
     QStringList _errors;
+    AccountStatePtr _accountState;
     AccountPtr _account;
     bool _isCheckingServerAndAuth;
 };
diff --git a/src/gui/quotainfo.cpp b/src/gui/quotainfo.cpp
deleted file mode 100644 (file)
index 0368eef..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
- *
- * 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 "quotainfo.h"
-#include "account.h"
-#include "accountstate.h"
-#include "networkjobs.h"
-#include "folderman.h"
-#include "creds/abstractcredentials.h"
-#include <theme.h>
-
-#include <QTimer>
-
-namespace OCC {
-
-namespace {
-    static const int defaultIntervalT = 30 * 1000;
-    static const int failIntervalT = 5 * 1000;
-}
-
-QuotaInfo::QuotaInfo(AccountState *accountState, QObject *parent)
-    : QObject(parent)
-    , _accountState(accountState)
-    , _lastQuotaTotalBytes(0)
-    , _lastQuotaUsedBytes(0)
-    , _active(false)
-{
-    connect(accountState, &AccountState::stateChanged,
-        this, &QuotaInfo::slotAccountStateChanged);
-    connect(&_jobRestartTimer, &QTimer::timeout, this, &QuotaInfo::slotCheckQuota);
-    _jobRestartTimer.setSingleShot(true);
-}
-
-void QuotaInfo::setActive(bool active)
-{
-    _active = active;
-    slotAccountStateChanged();
-}
-
-
-void QuotaInfo::slotAccountStateChanged()
-{
-    if (canGetQuota()) {
-        auto elapsed = _lastQuotaRecieved.msecsTo(QDateTime::currentDateTime());
-        if (_lastQuotaRecieved.isNull() || elapsed >= defaultIntervalT) {
-            slotCheckQuota();
-        } else {
-            _jobRestartTimer.start(defaultIntervalT - elapsed);
-        }
-    } else {
-        _jobRestartTimer.stop();
-    }
-}
-
-void QuotaInfo::slotRequestFailed()
-{
-    _lastQuotaTotalBytes = 0;
-    _lastQuotaUsedBytes = 0;
-    _jobRestartTimer.start(failIntervalT);
-}
-
-bool QuotaInfo::canGetQuota() const
-{
-    if (!_accountState || !_active) {
-        return false;
-    }
-    AccountPtr account = _accountState->account();
-    return _accountState->isConnected()
-        && account->credentials()
-        && account->credentials()->ready();
-}
-
-QString QuotaInfo::quotaBaseFolder() const
-{
-    return Theme::instance()->quotaBaseFolder();
-}
-
-void QuotaInfo::slotCheckQuota()
-{
-    if (!canGetQuota()) {
-        return;
-    }
-
-    if (_job) {
-        // The previous job was not finished?  Then we cancel it!
-        _job->deleteLater();
-    }
-
-    AccountPtr account = _accountState->account();
-    _job = new PropfindJob(account, quotaBaseFolder(), this);
-    _job->setProperties(QList<QByteArray>() << "quota-available-bytes"
-                                            << "quota-used-bytes");
-    connect(_job.data(), &PropfindJob::result, this, &QuotaInfo::slotUpdateLastQuota);
-    connect(_job.data(), &AbstractNetworkJob::networkError, this, &QuotaInfo::slotRequestFailed);
-    _job->start();
-}
-
-void QuotaInfo::slotUpdateLastQuota(const QVariantMap &result)
-{
-    // The server can return fractional bytes (#1374)
-    // <d:quota-available-bytes>1374532061.2</d:quota-available-bytes>
-    qint64 avail = result["quota-available-bytes"].toDouble();
-    _lastQuotaUsedBytes = result["quota-used-bytes"].toDouble();
-    // negative value of the available quota have special meaning (#3940)
-    _lastQuotaTotalBytes = avail >= 0 ? _lastQuotaUsedBytes + avail : avail;
-    emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes);
-    _jobRestartTimer.start(defaultIntervalT);
-    _lastQuotaRecieved = QDateTime::currentDateTime();
-}
-}
diff --git a/src/gui/quotainfo.h b/src/gui/quotainfo.h
deleted file mode 100644 (file)
index 27635fd..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
- *
- * 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.
- */
-
-#ifndef QUOTAINFO_H
-#define QUOTAINFO_H
-
-#include <QObject>
-#include <QPointer>
-#include <QVariant>
-#include <QTimer>
-#include <QDateTime>
-
-namespace OCC {
-class AccountState;
-class PropfindJob;
-
-/**
- * @brief handles getting the quota to display in the UI
- *
- * It is typically owned by the AccountSetting page.
- *
- * The quota is requested if these 3 conditions are met:
- *  - This object is active via setActive() (typically if the settings page is visible.)
- *  - The account is connected.
- *  - Every 30 seconds (defaultIntervalT) or 5 seconds in case of failure (failIntervalT)
- *
- * We only request the quota when the UI is visible otherwise this might slow down the server with
- * too many requests. But we still need to do it every 30 seconds otherwise user complains that the
- * quota is not updated fast enough when changed on the server.
- *
- * If the quota job is not finished within 30 seconds, it is cancelled and another one is started
- *
- * @ingroup gui
- */
-class QuotaInfo : public QObject
-{
-    Q_OBJECT
-public:
-    explicit QuotaInfo(OCC::AccountState *accountState, QObject *parent = nullptr);
-
-    qint64 lastQuotaTotalBytes() const { return _lastQuotaTotalBytes; }
-    qint64 lastQuotaUsedBytes() const { return _lastQuotaUsedBytes; }
-
-    /**
-     * When the quotainfo is active, it requests the quota at regular interval.
-     * When setting it to active it will request the quota immediately if the last time
-     * the quota was requested was more than the interval
-     */
-    void setActive(bool active);
-
-public Q_SLOTS:
-    void slotCheckQuota();
-
-private Q_SLOTS:
-    void slotUpdateLastQuota(const QVariantMap &);
-    void slotAccountStateChanged();
-    void slotRequestFailed();
-
-Q_SIGNALS:
-    void quotaUpdated(qint64 total, qint64 used);
-
-private:
-    bool canGetQuota() const;
-
-    /// Returns the folder that quota shall be retrieved for
-    QString quotaBaseFolder() const;
-
-    QPointer<AccountState> _accountState;
-    qint64 _lastQuotaTotalBytes;
-    qint64 _lastQuotaUsedBytes;
-    QTimer _jobRestartTimer;
-    QDateTime _lastQuotaRecieved; // the time at which the quota was received last
-    bool _active; // if we should check at regular interval (when the UI is visible)
-    QPointer<PropfindJob> _job; // the currently running job
-};
-
-
-} // namespace OCC
-
-#endif //QUOTAINFO_H
diff --git a/src/gui/userinfo.cpp b/src/gui/userinfo.cpp
new file mode 100644 (file)
index 0000000..4f1446e
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
+ * Copyright (C) by Michael Schuster <michael@nextcloud.com>
+ *
+ * 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 "userinfo.h"
+#include "account.h"
+#include "accountstate.h"
+#include "networkjobs.h"
+#include "folderman.h"
+#include "creds/abstractcredentials.h"
+#include <theme.h>
+
+#include <QTimer>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+namespace OCC {
+
+namespace {
+    static const int defaultIntervalT = 30 * 1000;
+    static const int failIntervalT = 5 * 1000;
+}
+
+UserInfo::UserInfo(AccountState *accountState, bool allowDisconnectedAccountState, bool fetchAvatarImage, QObject *parent)
+    : QObject(parent)
+    , _accountState(accountState)
+    , _allowDisconnectedAccountState(allowDisconnectedAccountState)
+    , _fetchAvatarImage(fetchAvatarImage)
+    , _lastQuotaTotalBytes(0)
+    , _lastQuotaUsedBytes(0)
+    , _active(false)
+{
+    connect(accountState, &AccountState::stateChanged,
+        this, &UserInfo::slotAccountStateChanged);
+    connect(&_jobRestartTimer, &QTimer::timeout, this, &UserInfo::slotFetchInfo);
+    _jobRestartTimer.setSingleShot(true);
+}
+
+void UserInfo::setActive(bool active)
+{
+    _active = active;
+    slotAccountStateChanged();
+}
+
+
+void UserInfo::slotAccountStateChanged()
+{
+    if (canGetInfo()) {
+        auto elapsed = _lastInfoReceived.msecsTo(QDateTime::currentDateTime());
+        if (_lastInfoReceived.isNull() || elapsed >= defaultIntervalT) {
+            slotFetchInfo();
+        } else {
+            _jobRestartTimer.start(defaultIntervalT - elapsed);
+        }
+    } else {
+        _jobRestartTimer.stop();
+    }
+}
+
+void UserInfo::slotRequestFailed()
+{
+    _lastQuotaTotalBytes = 0;
+    _lastQuotaUsedBytes = 0;
+    _jobRestartTimer.start(failIntervalT);
+}
+
+bool UserInfo::canGetInfo() const
+{
+    if (!_accountState || !_active) {
+        return false;
+    }
+    AccountPtr account = _accountState->account();
+    return (_accountState->isConnected() || _allowDisconnectedAccountState)
+        && account->credentials()
+        && account->credentials()->ready();
+}
+
+void UserInfo::slotFetchInfo()
+{
+    if (!canGetInfo()) {
+        return;
+    }
+
+    if (_job) {
+        // The previous job was not finished?  Then we cancel it!
+        _job->deleteLater();
+    }
+
+    AccountPtr account = _accountState->account();
+    _job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/user"), this);
+    _job->setTimeout(20 * 1000);
+    connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserInfo::slotUpdateLastInfo);
+    connect(_job.data(), &AbstractNetworkJob::networkError, this, &UserInfo::slotRequestFailed);
+    _job->start();
+}
+
+void UserInfo::slotUpdateLastInfo(const QJsonDocument &json)
+{
+    auto objData = json.object().value("ocs").toObject().value("data").toObject();
+
+    AccountPtr account = _accountState->account();
+
+    // User Info
+    QString user = objData.value("id").toString();
+    if (!user.isEmpty()) {
+        account->setDavUser(user);
+    }
+    QString displayName = objData.value("display-name").toString();
+    if (!displayName.isEmpty()) {
+        account->setDavDisplayName(displayName);
+    }
+
+    // Quota
+    auto objQuota = objData.value("quota").toObject();
+    qint64 used = objQuota.value("used").toDouble();
+    qint64 total = objQuota.value("total").toDouble();
+
+    if(_lastInfoReceived.isNull() || _lastQuotaUsedBytes != used || _lastQuotaTotalBytes != total) {
+        _lastQuotaUsedBytes = used;
+        _lastQuotaTotalBytes = total;
+        emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes);
+    }
+
+    _jobRestartTimer.start(defaultIntervalT);
+    _lastInfoReceived = QDateTime::currentDateTime();
+
+    // Avatar Image
+    if(_fetchAvatarImage) {
+        AvatarJob *job = new AvatarJob(account, account->davUser(), 128, this);
+        job->setTimeout(20 * 1000);
+        QObject::connect(job, &AvatarJob::avatarPixmap, this, &UserInfo::slotAvatarImage);
+        job->start();
+    }
+    else
+        emit fetchedLastInfo(this);
+}
+
+void UserInfo::slotAvatarImage(const QImage &img)
+{
+    _accountState->account()->setAvatar(img);
+
+    emit fetchedLastInfo(this);
+}
+
+} // namespace OCC
diff --git a/src/gui/userinfo.h b/src/gui/userinfo.h
new file mode 100644 (file)
index 0000000..de8c7ec
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
+ * Copyright (C) by Michael Schuster <michael@nextcloud.com>
+ *
+ * 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.
+ */
+
+#ifndef USERINFO_H
+#define USERINFO_H
+
+#include <QObject>
+#include <QPointer>
+#include <QVariant>
+#include <QTimer>
+#include <QDateTime>
+
+namespace OCC {
+class AccountState;
+class JsonApiJob;
+
+/**
+ * @brief handles getting the user info and quota to display in the UI
+ *
+ * It is typically owned by the AccountSetting page.
+ *
+ * The user info and quota is requested if these 3 conditions are met:
+ *  - This object is active via setActive() (typically if the settings page is visible.)
+ *  - The account is connected.
+ *  - Every 30 seconds (defaultIntervalT) or 5 seconds in case of failure (failIntervalT)
+ *
+ * We only request the info when the UI is visible otherwise this might slow down the server with
+ * too many requests. But we still need to do it every 30 seconds otherwise user complains that the
+ * quota is not updated fast enough when changed on the server.
+ *
+ * If the fetch job is not finished within 30 seconds, it is cancelled and another one is started
+ *
+ * Constructor notes:
+ *  - allowDisconnectedAccountState: set to true if you want to ignore AccountState's isConnected() state,
+ *    this is used by ConnectionValidator (prior having a valid AccountState).
+ *  - fetchAvatarImage: set to false if you don't want to fetch the avatar image
+ *
+ * @ingroup gui
+ *
+ * Here follows the state machine
+
+ \code{.unparsed}
+ *---> slotFetchInfo
+         JsonApiJob (ocs/v1.php/cloud/user)
+         |
+         +-> slotUpdateLastInfo
+               AvatarJob (if _fetchAvatarImage is true)
+               |
+               +-> slotAvatarImage -->
+   +-----------------------------------+
+   |
+   +-> Client Side Encryption Checks --+ --reportResult()
+     \endcode
+  */
+class UserInfo : public QObject
+{
+    Q_OBJECT
+public:
+    explicit UserInfo(OCC::AccountState *accountState, bool allowDisconnectedAccountState, bool fetchAvatarImage, QObject *parent = nullptr);
+
+    qint64 lastQuotaTotalBytes() const { return _lastQuotaTotalBytes; }
+    qint64 lastQuotaUsedBytes() const { return _lastQuotaUsedBytes; }
+
+    /**
+     * When the quotainfo is active, it requests the quota at regular interval.
+     * When setting it to active it will request the quota immediately if the last time
+     * the quota was requested was more than the interval
+     */
+    void setActive(bool active);
+
+public Q_SLOTS:
+    void slotFetchInfo();
+
+private Q_SLOTS:
+    void slotUpdateLastInfo(const QJsonDocument &json);
+    void slotAccountStateChanged();
+    void slotRequestFailed();
+    void slotAvatarImage(const QImage &img);
+
+Q_SIGNALS:
+    void quotaUpdated(qint64 total, qint64 used);
+    void fetchedLastInfo(UserInfo *userInfo);
+
+private:
+    bool canGetInfo() const;
+
+    QPointer<AccountState> _accountState;
+    bool _allowDisconnectedAccountState;
+    bool _fetchAvatarImage;
+
+    qint64 _lastQuotaTotalBytes;
+    qint64 _lastQuotaUsedBytes;
+    QTimer _jobRestartTimer;
+    QDateTime _lastInfoReceived; // the time at which the user info and quota was received last
+    bool _active; // if we should check at regular interval (when the UI is visible)
+    QPointer<JsonApiJob> _job; // the currently running job
+};
+
+
+} // namespace OCC
+
+#endif //USERINFO_H