From abfebcf2917e971a4e8c279d3f2871c79e64583a Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Fri, 14 Feb 2020 02:10:01 +0100 Subject: [PATCH] Add UserInfo class and fetch quota via API instead of PropfindJob 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 --- src/gui/CMakeLists.txt | 2 +- src/gui/accountsettings.cpp | 8 +- src/gui/accountsettings.h | 4 +- src/gui/accountstate.cpp | 2 +- src/gui/connectionvalidator.cpp | 40 +++---- src/gui/connectionvalidator.h | 19 ++-- src/gui/quotainfo.cpp | 121 --------------------- src/gui/userinfo.cpp | 156 ++++++++++++++++++++++++++++ src/gui/{quotainfo.h => userinfo.h} | 61 +++++++---- 9 files changed, 229 insertions(+), 184 deletions(-) delete mode 100644 src/gui/quotainfo.cpp create mode 100644 src/gui/userinfo.cpp rename src/gui/{quotainfo.h => userinfo.h} (54%) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4ccf3abaa..0e5f04bff 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -87,7 +87,7 @@ set(client_SRCS syncrunfilelog.cpp systray.cpp thumbnailjob.cpp - quotainfo.cpp + userinfo.cpp accountstate.cpp addcertificatedialog.cpp authenticationdialog.cpp diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 1c9e3b47d..a8d07e9db 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -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 diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index e620029a5..68ddd01bb 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -22,7 +22,7 @@ #include #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; diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 1491db714..90eec6275 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -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); diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index 97bdf7573..3b9f8f4ca 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -22,6 +22,8 @@ #include "connectionvalidator.h" #include "account.h" +#include "accountstate.h" +#include "userinfo.h" #include "networkjobs.h" #include "clientproxy.h" #include @@ -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); } diff --git a/src/gui/connectionvalidator.h b/src/gui/connectionvalidator.h index 779b6a8a2..079fc36e6 100644 --- a/src/gui/connectionvalidator.h +++ b/src/gui/connectionvalidator.h @@ -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 index 0368eefbe..000000000 --- a/src/gui/quotainfo.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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 - -#include - -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() << "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) - // 1374532061.2 - 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/userinfo.cpp b/src/gui/userinfo.cpp new file mode 100644 index 000000000..4f1446edf --- /dev/null +++ b/src/gui/userinfo.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) by Daniel Molkentin + * Copyright (C) by Michael Schuster + * + * 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 + +#include +#include +#include + +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/quotainfo.h b/src/gui/userinfo.h similarity index 54% rename from src/gui/quotainfo.h rename to src/gui/userinfo.h index 27635fd83..de8c7ec6b 100644 --- a/src/gui/quotainfo.h +++ b/src/gui/userinfo.h @@ -1,5 +1,6 @@ /* * Copyright (C) by Daniel Molkentin + * Copyright (C) by Michael Schuster * * 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 @@ -12,8 +13,8 @@ * for more details. */ -#ifndef QUOTAINFO_H -#define QUOTAINFO_H +#ifndef USERINFO_H +#define USERINFO_H #include #include @@ -23,31 +24,51 @@ namespace OCC { class AccountState; -class PropfindJob; +class JsonApiJob; /** - * @brief handles getting the quota to display in the UI + * @brief handles getting the user info and quota to display in the UI * * It is typically owned by the AccountSetting page. * - * The quota is requested if these 3 conditions are met: + * 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 quota when the UI is visible otherwise this might slow down the server with + * 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 quota job is not finished within 30 seconds, it is cancelled and another one is started + * 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 - */ -class QuotaInfo : public QObject + * + * 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 QuotaInfo(OCC::AccountState *accountState, QObject *parent = nullptr); + explicit UserInfo(OCC::AccountState *accountState, bool allowDisconnectedAccountState, bool fetchAvatarImage, QObject *parent = nullptr); qint64 lastQuotaTotalBytes() const { return _lastQuotaTotalBytes; } qint64 lastQuotaUsedBytes() const { return _lastQuotaUsedBytes; } @@ -60,32 +81,34 @@ public: void setActive(bool active); public Q_SLOTS: - void slotCheckQuota(); + void slotFetchInfo(); private Q_SLOTS: - void slotUpdateLastQuota(const QVariantMap &); + 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 canGetQuota() const; - - /// Returns the folder that quota shall be retrieved for - QString quotaBaseFolder() const; + bool canGetInfo() const; QPointer _accountState; + bool _allowDisconnectedAccountState; + bool _fetchAvatarImage; + qint64 _lastQuotaTotalBytes; qint64 _lastQuotaUsedBytes; QTimer _jobRestartTimer; - QDateTime _lastQuotaRecieved; // the time at which the quota was received last + 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 _job; // the currently running job + QPointer _job; // the currently running job }; } // namespace OCC -#endif //QUOTAINFO_H +#endif //USERINFO_H -- 2.30.2