Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable-3.11] Feature/client status reporting #6283

Merged
merged 9 commits into from
Dec 11, 2023
12 changes: 11 additions & 1 deletion src/libsync/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ set(libsync_SRCS
capabilities.cpp
clientproxy.h
clientproxy.cpp
clientstatusreporting.h
clientstatusreporting.cpp
clientstatusreportingcommon.h
clientstatusreportingcommon.cpp
clientstatusreportingdatabase.h
clientstatusreportingdatabase.cpp
clientstatusreportingnetwork.h
clientstatusreportingnetwork.cpp
clientstatusreportingrecord.h
cookiejar.h
cookiejar.cpp
discovery.h
Expand Down Expand Up @@ -171,7 +180,7 @@ IF (NOT APPLE)
)
ENDIF(NOT APPLE)

find_package(Qt5 REQUIRED COMPONENTS WebSockets Xml)
find_package(Qt5 REQUIRED COMPONENTS WebSockets Xml Sql)

add_library(nextcloudsync SHARED ${libsync_SRCS})
add_library(Nextcloud::sync ALIAS nextcloudsync)
Expand All @@ -186,6 +195,7 @@ target_link_libraries(nextcloudsync
Qt5::Network
Qt5::WebSockets
Qt5::Xml
Qt5::Sql
)

if (NOT TOKEN_AUTH_ONLY)
Expand Down
21 changes: 21 additions & 0 deletions src/libsync/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,25 @@ void Account::setPushNotificationsReconnectInterval(int interval)
_pushNotificationsReconnectTimer.setInterval(interval);
}

void Account::trySetupClientStatusReporting()
{
if (!_capabilities.isClientStatusReportingEnabled()) {
_clientStatusReporting.reset();
return;
}

if (!_clientStatusReporting) {
_clientStatusReporting = std::make_unique<ClientStatusReporting>(this);
}
}

void Account::reportClientStatus(const ClientStatusReportingStatus status) const
{
if (_clientStatusReporting) {
_clientStatusReporting->reportClientStatus(status);
}
}

void Account::trySetupPushNotifications()
{
// Stop the timer to prevent parallel setup attempts
Expand Down Expand Up @@ -669,6 +688,8 @@ void Account::setCapabilities(const QVariantMap &caps)

setupUserStatusConnector();
trySetupPushNotifications();

trySetupClientStatusReporting();
}

void Account::setupUserStatusConnector()
Expand Down
7 changes: 7 additions & 0 deletions src/libsync/account.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "capabilities.h"
#include "clientsideencryption.h"
#include "clientstatusreporting.h"
#include "common/utility.h"
#include "syncfileitem.h"

Expand Down Expand Up @@ -305,6 +306,10 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
[[nodiscard]] PushNotifications *pushNotifications() const;
void setPushNotificationsReconnectInterval(int interval);

void trySetupClientStatusReporting();

void reportClientStatus(const ClientStatusReportingStatus status) const;

[[nodiscard]] std::shared_ptr<UserStatusConnector> userStatusConnector() const;

void setLockFileState(const QString &serverRelativePath,
Expand Down Expand Up @@ -439,6 +444,8 @@ private slots:

PushNotifications *_pushNotifications = nullptr;

std::unique_ptr<ClientStatusReporting> _clientStatusReporting;

std::shared_ptr<UserStatusConnector> _userStatusConnector;

QHash<QString, QVector<SyncFileItem::LockStatus>> _lockStatusChangeInprogress;
Expand Down
3 changes: 3 additions & 0 deletions src/libsync/bulkpropagatorjob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@ void BulkPropagatorJob::slotPutFinishedOneFile(const BulkUploadItem &singleFile,
singleFile._item->_requestId = job->requestId();
if (singleFile._item->_httpErrorCode != 200) {
commonErrorHandling(singleFile._item, fileReply[QStringLiteral("message")].toString());
const auto exceptionParsed = getExceptionFromReply(job->reply());
singleFile._item->_errorExceptionName = exceptionParsed.first;
singleFile._item->_errorExceptionMessage = exceptionParsed.second;
return;
}

Expand Down
9 changes: 9 additions & 0 deletions src/libsync/capabilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,15 @@ bool Capabilities::hasActivities() const
return _capabilities.contains("activity");
}

bool Capabilities::isClientStatusReportingEnabled() const
{
if (!_capabilities.contains(QStringLiteral("security_guard"))) {
return false;
}
const auto securityGuardCaps = _capabilities[QStringLiteral("security_guard")].toMap();
return securityGuardCaps.contains(QStringLiteral("diagnostics")) && securityGuardCaps[QStringLiteral("diagnostics")].toBool();
}

QList<QByteArray> Capabilities::supportedChecksumTypes() const
{
QList<QByteArray> list;
Expand Down
2 changes: 2 additions & 0 deletions src/libsync/capabilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ class OWNCLOUDSYNC_EXPORT Capabilities
/// return true if the activity app is enabled
[[nodiscard]] bool hasActivities() const;

[[nodiscard]] bool isClientStatusReportingEnabled() const;

/**
* Returns the checksum types the server understands.
*
Expand Down
9 changes: 9 additions & 0 deletions src/libsync/clientsideencryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,7 @@ bool ClientSideEncryption::sensitiveDataRemaining() const
void ClientSideEncryption::failedToInitialize(const AccountPtr &account)
{
forgetSensitiveData(account);
account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
Q_EMIT initializationFinished();
}

Expand Down Expand Up @@ -1775,6 +1776,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)

if (metadataKeys.isEmpty()) {
qCDebug(lcCse()) << "Could not migrate. No metadata keys found!";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}

Expand All @@ -1787,6 +1789,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)

if (_metadataKey.isEmpty()) {
qCDebug(lcCse()) << "Could not setup existing metadata with missing metadataKeys!";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}

Expand Down Expand Up @@ -1861,6 +1864,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)
} else {
_metadataKey.clear();
_files.clear();
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}
}
Expand Down Expand Up @@ -1899,6 +1903,7 @@ QByteArray FolderMetadata::decryptData(const QByteArray &data) const
if (decryptResult.isEmpty())
{
qCDebug(lcCse()) << "ERROR. Could not decrypt the metadata key";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return {};
}
return QByteArray::fromBase64(decryptResult);
Expand All @@ -1916,6 +1921,7 @@ QByteArray FolderMetadata::decryptDataUsingKey(const QByteArray &data,
if (decryptResult.isEmpty())
{
qCDebug(lcCse()) << "ERROR. Could not decrypt";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return {};
}

Expand Down Expand Up @@ -1979,6 +1985,7 @@ QByteArray FolderMetadata::encryptedMetadata() const {

if (_metadataKey.isEmpty()) {
qCDebug(lcCse) << "Metadata generation failed! Empty metadata key!";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return {};
}
const auto version = _account->capabilities().clientSideEncryptionVersion();
Expand All @@ -2000,6 +2007,7 @@ QByteArray FolderMetadata::encryptedMetadata() const {

QString encryptedEncrypted = encryptJsonObject(encryptedDoc.toJson(QJsonDocument::Compact), _metadataKey);
if (encryptedEncrypted.isEmpty()) {
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
qCDebug(lcCse) << "Metadata generation failed!";
}
QJsonObject file;
Expand Down Expand Up @@ -2087,6 +2095,7 @@ bool FolderMetadata::moveFromFileDropToFiles()

if (decryptedKey.isEmpty() || decryptedAuthenticationTag.isEmpty() || decryptedInitializationVector.isEmpty()) {
qCDebug(lcCseMetadata) << "failed to decrypt filedrop entry" << it.key();
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
continue;
}

Expand Down
72 changes: 72 additions & 0 deletions src/libsync/clientstatusreporting.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2023 by Oleksandr Zolotov <[email protected]>
*
* 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 "clientstatusreporting.h"

#include "account.h"
#include "clientstatusreportingdatabase.h"
#include "clientstatusreportingnetwork.h"
#include "clientstatusreportingrecord.h"

namespace OCC
{
Q_LOGGING_CATEGORY(lcClientStatusReporting, "nextcloud.sync.clientstatusreporting", QtInfoMsg)

ClientStatusReporting::ClientStatusReporting(Account *account)
{
for (int i = 0; i < static_cast<int>(ClientStatusReportingStatus::Count); ++i) {
const auto statusString = clientStatusstatusStringFromNumber(static_cast<ClientStatusReportingStatus>(i));
_statusStrings[i] = statusString;
}

if (_statusStrings.size() < static_cast<int>(ClientStatusReportingStatus::Count)) {
return;
}

_database = QSharedPointer<ClientStatusReportingDatabase>::create(account);
if (!_database->isInitialized()) {
return;
}

_reporter = std::make_unique<ClientStatusReportingNetwork>(account, _database);
if (!_reporter->isInitialized()) {
return;
}

_isInitialized = true;
}

ClientStatusReporting::~ClientStatusReporting() = default;

void ClientStatusReporting::reportClientStatus(const ClientStatusReportingStatus status) const
{
if (!_isInitialized) {
return;
}

Q_ASSERT(static_cast<int>(status) >= 0 && static_cast<int>(status) < static_cast<int>(ClientStatusReportingStatus::Count));
if (static_cast<int>(status) < 0 || static_cast<int>(status) >= static_cast<int>(ClientStatusReportingStatus::Count)) {
qCDebug(lcClientStatusReporting) << "Trying to report invalid status:" << static_cast<int>(status);
return;
}

ClientStatusReportingRecord record;
record._name = _statusStrings[static_cast<int>(status)];
record._status = static_cast<int>(status);
record._lastOccurence = QDateTime::currentDateTimeUtc().toMSecsSinceEpoch();
const auto result = _database->setClientStatusReportingRecord(record);
if (!result.isValid()) {
qCDebug(lcClientStatusReporting) << "Could not report client status:" << result.error();
}
}
}
53 changes: 53 additions & 0 deletions src/libsync/clientstatusreporting.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*

Check notice on line 1 in src/libsync/clientstatusreporting.h

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/libsync/clientstatusreporting.h

File src/libsync/clientstatusreporting.h does not conform to Custom style guidelines. (lines 16, 26)
* Copyright (C) 2023 by Oleksandr Zolotov <[email protected]>
*
* 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.
*/
#pragma once

#include "owncloudlib.h"

Check failure on line 16 in src/libsync/clientstatusreporting.h

View workflow job for this annotation

GitHub Actions / build

/src/libsync/clientstatusreporting.h:16:10 [clang-diagnostic-error]

'owncloudlib.h' file not found

Check failure on line 16 in src/libsync/clientstatusreporting.h

View workflow job for this annotation

GitHub Actions / build

/src/libsync/clientstatusreporting.h:16:10 [clang-diagnostic-error]

'owncloudlib.h' file not found
#include <common/result.h>
#include "clientstatusreportingcommon.h"

#include <memory>

#include <QByteArray>
#include <QHash>
#include <QSharedPointer>

namespace OCC {

class Account;
class ClientStatusReportingDatabase;
class ClientStatusReportingNetwork;
struct ClientStatusReportingRecord;

class OWNCLOUDSYNC_EXPORT ClientStatusReporting
{
public:
explicit ClientStatusReporting(Account *account);
~ClientStatusReporting();

private:
// reporting must happen via Account
void reportClientStatus(const ClientStatusReportingStatus status) const;

bool _isInitialized = false;

QHash<int, QByteArray> _statusStrings;

QSharedPointer<ClientStatusReportingDatabase> _database;

std::unique_ptr<ClientStatusReportingNetwork> _reporter;

friend class Account;
};
}
63 changes: 63 additions & 0 deletions src/libsync/clientstatusreportingcommon.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*

Check notice on line 1 in src/libsync/clientstatusreportingcommon.cpp

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/libsync/clientstatusreportingcommon.cpp

File src/libsync/clientstatusreportingcommon.cpp does not conform to Custom style guidelines. (lines 18)
* Copyright (C) 2023 by Oleksandr Zolotov <[email protected]>
*
* 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 "clientstatusreportingcommon.h"
#include <QLoggingCategory>

namespace OCC {
Q_LOGGING_CATEGORY(lcClientStatusReportingCommon, "nextcloud.sync.clientstatusreportingcommon", QtInfoMsg)

Check warning on line 19 in src/libsync/clientstatusreportingcommon.cpp

View workflow job for this annotation

GitHub Actions / build

/src/libsync/clientstatusreportingcommon.cpp:19:1 [cppcoreguidelines-avoid-non-const-global-variables]

variable 'Q_LOGGING_CATEGORY' is non-const and globally accessible, consider making it const

QByteArray clientStatusstatusStringFromNumber(const ClientStatusReportingStatus status)
{
Q_ASSERT(static_cast<int>(status) >= 0 && static_cast<int>(status) < static_cast<int>(ClientStatusReportingStatus::Count));
if (static_cast<int>(status) < 0 || static_cast<int>(status) >= static_cast<int>(ClientStatusReportingStatus::Count)) {
qCDebug(lcClientStatusReportingCommon) << "Invalid status:" << static_cast<int>(status);
return {};
}

switch (status) {
case ClientStatusReportingStatus::DownloadError_Cannot_Create_File:
return QByteArrayLiteral("DownloadResult.CANNOT_CREATE_FILE");
case ClientStatusReportingStatus::DownloadError_Conflict:
return QByteArrayLiteral("DownloadResult.CONFLICT");
case ClientStatusReportingStatus::DownloadError_ConflictCaseClash:
return QByteArrayLiteral("DownloadResult.CONFLICT_CASECLASH");
case ClientStatusReportingStatus::DownloadError_ConflictInvalidCharacters:
return QByteArrayLiteral("DownloadResult.CONFLICT_INVALID_CHARACTERS");
case ClientStatusReportingStatus::DownloadError_No_Free_Space:
return QByteArrayLiteral("DownloadResult.NO_FREE_SPACE");
case ClientStatusReportingStatus::DownloadError_ServerError:
return QByteArrayLiteral("DownloadResult.SERVER_ERROR");
case ClientStatusReportingStatus::DownloadError_Virtual_File_Hydration_Failure:
return QByteArrayLiteral("DownloadResult.VIRTUAL_FILE_HYDRATION_FAILURE");
case ClientStatusReportingStatus::E2EeError_GeneralError:
return QByteArrayLiteral("E2EeError.General");
case ClientStatusReportingStatus::UploadError_Conflict:
return QByteArrayLiteral("UploadResult.CONFLICT_CASECLASH");
case ClientStatusReportingStatus::UploadError_ConflictInvalidCharacters:
return QByteArrayLiteral("UploadResult.CONFLICT_INVALID_CHARACTERS");
case ClientStatusReportingStatus::UploadError_No_Free_Space:
return QByteArrayLiteral("UploadResult.NO_FREE_SPACE");
case ClientStatusReportingStatus::UploadError_No_Write_Permissions:
return QByteArrayLiteral("UploadResult.NO_WRITE_PERMISSIONS");
case ClientStatusReportingStatus::UploadError_ServerError:
return QByteArrayLiteral("UploadResult.SERVER_ERROR");
case ClientStatusReportingStatus::UploadError_Virus_Detected:
return QByteArrayLiteral("UploadResult.VIRUS_DETECTED");
case ClientStatusReportingStatus::Count:
return {};
};
return {};
}
}
Loading
Loading