зеркало из https://github.com/nextcloud/desktop.git
Generate client status report records when errors happen.
Signed-off-by: alex-z <blackslayer4@gmail.com>
This commit is contained in:
Родитель
5b54b13a97
Коммит
8e38739d94
|
@ -22,10 +22,6 @@
|
|||
namespace
|
||||
{
|
||||
constexpr auto lastSentReportTimestamp = "lastClientStatusReportSentTime";
|
||||
//constexpr auto repordSendIntervalMs = 24 * 60 * 60 * 1000;
|
||||
//constexpr int clientStatusReportingSendTimerInterval = 1000 * 60 * 2;
|
||||
constexpr auto repordSendIntervalMs = 2000;
|
||||
constexpr int clientStatusReportingSendTimerInterval = 5000;
|
||||
}
|
||||
|
||||
namespace OCC
|
||||
|
@ -39,6 +35,13 @@ ClientStatusReporting::ClientStatusReporting(Account *account, QObject *parent)
|
|||
init();
|
||||
}
|
||||
|
||||
ClientStatusReporting::~ClientStatusReporting()
|
||||
{
|
||||
if (_database.isOpen()) {
|
||||
_database.close();
|
||||
}
|
||||
}
|
||||
|
||||
void ClientStatusReporting::init()
|
||||
{
|
||||
if (_isInitialized) {
|
||||
|
@ -51,10 +54,7 @@ void ClientStatusReporting::init()
|
|||
_statusNamesAndHashes[i] = {statusString, SyncJournalDb::getPHash(statusString)};
|
||||
}
|
||||
|
||||
const auto databaseId = QStringLiteral("%1@%2").arg(_account->davUser(), _account->url().toString());
|
||||
const auto databaseIdHash = QCryptographicHash::hash(databaseId.toUtf8(), QCryptographicHash::Md5);
|
||||
|
||||
const QString dbPath = ConfigFile().configPath() + QStringLiteral(".userdata_%1.db").arg(QString::fromLatin1(databaseIdHash.left(6).toHex()));
|
||||
const auto dbPath = makeDbPath();
|
||||
|
||||
_database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"));
|
||||
_database.setDatabaseName(dbPath);
|
||||
|
@ -82,25 +82,11 @@ void ClientStatusReporting::init()
|
|||
return;
|
||||
}
|
||||
|
||||
_clientStatusReportingSendTimer.setInterval(clientStatusReportingSendTimerInterval);
|
||||
_clientStatusReportingSendTimer.setInterval(clientStatusReportingTrySendTimerInterval);
|
||||
connect(&_clientStatusReportingSendTimer, &QTimer::timeout, this, &ClientStatusReporting::sendReportToServer);
|
||||
_clientStatusReportingSendTimer.start();
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
reportClientStatus(Status::DownloadError_ConflictCaseClash);
|
||||
reportClientStatus(Status::DownloadError_ConflictInvalidCharacters);
|
||||
reportClientStatus(Status::UploadError_ServerError);
|
||||
reportClientStatus(Status::UploadError_ServerError);
|
||||
setLastSentReportTimestamp(QDateTime::currentDateTime().toMSecsSinceEpoch());
|
||||
|
||||
auto records = getClientStatusReportingRecords();
|
||||
|
||||
// auto resDelete = deleteClientStatusReportingRecords();
|
||||
|
||||
records = getClientStatusReportingRecords();
|
||||
|
||||
auto res = getLastSentReportTimestamp();
|
||||
}
|
||||
|
||||
QVector<ClientStatusReportingRecord> ClientStatusReporting::getClientStatusReportingRecords() const
|
||||
|
@ -147,7 +133,7 @@ Result<void, QString> ClientStatusReporting::setClientStatusReportingRecord(cons
|
|||
{
|
||||
Q_ASSERT(record.isValid());
|
||||
if (!record.isValid()) {
|
||||
qCWarning(lcClientStatusReporting) << "Failed to set ClientStatusReportingRecord";
|
||||
qCDebug(lcClientStatusReporting) << "Failed to set ClientStatusReportingRecord";
|
||||
return {QStringLiteral("Invalid parameter")};
|
||||
}
|
||||
|
||||
|
@ -183,7 +169,7 @@ void ClientStatusReporting::reportClientStatus(const Status status)
|
|||
}
|
||||
Q_ASSERT(status >= 0 && status < Count);
|
||||
if (status < 0 || status >= Status::Count) {
|
||||
qCWarning(lcClientStatusReporting) << "Trying to report invalid status:" << status;
|
||||
qCDebug(lcClientStatusReporting) << "Trying to report invalid status:" << status;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -194,7 +180,7 @@ void ClientStatusReporting::reportClientStatus(const Status status)
|
|||
record._lastOccurence = QDateTime::currentDateTimeUtc().toMSecsSinceEpoch();
|
||||
const auto result = setClientStatusReportingRecord(record);
|
||||
if (!result.isValid()) {
|
||||
qCWarning(lcClientStatusReporting) << "Could not report client status:" << result.error();
|
||||
qCDebug(lcClientStatusReporting) << "Could not report client status:" << result.error();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,17 +196,79 @@ void ClientStatusReporting::sendReportToServer()
|
|||
return;
|
||||
}
|
||||
|
||||
const auto records = getClientStatusReportingRecords();
|
||||
if (records.isEmpty()) {
|
||||
const auto report = prepareReport();
|
||||
if (report.isEmpty()) {
|
||||
qCDebug(lcClientStatusReporting) << "Failed to generate report. Report is empty.";
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap report;
|
||||
const auto clientStatusReportingJob = new JsonApiJob(_account->sharedFromThis(), QStringLiteral("ocs/v2.php/apps/security_guard/diagnostics"));
|
||||
clientStatusReportingJob->setBody(QJsonDocument::fromVariant(report));
|
||||
clientStatusReportingJob->setVerb(SimpleApiJob::Verb::Put);
|
||||
connect(clientStatusReportingJob, &JsonApiJob::jsonReceived, [this](const QJsonDocument &json, int statusCode) {
|
||||
if (statusCode == 0 || statusCode == 200 || statusCode == 201 || statusCode == 204) {
|
||||
const auto metaFromJson = json.object().value("ocs").toObject().value("meta").toObject();
|
||||
const auto codeFromJson = metaFromJson.value("statuscode").toInt();
|
||||
if (codeFromJson == 0 || codeFromJson == 200 || codeFromJson == 201 || codeFromJson == 204) {
|
||||
reportToServerSentSuccessfully();
|
||||
return;
|
||||
}
|
||||
qCDebug(lcClientStatusReporting) << "Received error when sending client report statusCode:" << statusCode << "codeFromJson:" << codeFromJson;
|
||||
}
|
||||
});
|
||||
clientStatusReportingJob->start();
|
||||
}
|
||||
|
||||
void ClientStatusReporting::reportToServerSentSuccessfully()
|
||||
{
|
||||
if (!deleteClientStatusReportingRecords()) {
|
||||
qCDebug(lcClientStatusReporting) << "Error deleting client status report.";
|
||||
}
|
||||
setLastSentReportTimestamp(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch());
|
||||
}
|
||||
|
||||
QString ClientStatusReporting::makeDbPath() const
|
||||
{
|
||||
if (!dbPathForTesting.isEmpty()) {
|
||||
return dbPathForTesting;
|
||||
}
|
||||
const auto databaseId = QStringLiteral("%1@%2").arg(_account->davUser(), _account->url().toString());
|
||||
const auto databaseIdHash = QCryptographicHash::hash(databaseId.toUtf8(), QCryptographicHash::Md5);
|
||||
|
||||
return ConfigFile().configPath() + QStringLiteral(".userdata_%1.db").arg(QString::fromLatin1(databaseIdHash.left(6).toHex()));
|
||||
}
|
||||
|
||||
qulonglong ClientStatusReporting::getLastSentReportTimestamp() const
|
||||
{
|
||||
QMutexLocker locker(&_mutex);
|
||||
QSqlQuery query;
|
||||
const auto prepareResult = query.prepare("SELECT value FROM keyvalue WHERE key = (:key)");
|
||||
query.bindValue(":key", lastSentReportTimestamp);
|
||||
if (!prepareResult || !query.exec()) {
|
||||
qCDebug(lcClientStatusReporting) << "Could not get last sent report timestamp from keyvalue table. No such record:" << lastSentReportTimestamp;
|
||||
return 0;
|
||||
}
|
||||
if (!query.next()) {
|
||||
qCDebug(lcClientStatusReporting) << "Could not get last sent report timestamp from keyvalue table:" << query.lastError().text();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int valueIndex = query.record().indexOf("value");
|
||||
return query.value(valueIndex).toULongLong();
|
||||
}
|
||||
|
||||
QVariantMap ClientStatusReporting::prepareReport() const
|
||||
{
|
||||
const auto records = getClientStatusReportingRecords();
|
||||
if (records.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariantMap report;
|
||||
report[QStringLiteral("sync_conflicts")] = QVariantMap{};
|
||||
report[QStringLiteral("problems")] = QVariantMap{};
|
||||
report[QStringLiteral("virus_detected")] = QVariantMap{};
|
||||
report[QStringLiteral ("e2e_errors")] = QVariantMap{};
|
||||
report[QStringLiteral("e2e_errors")] = QVariantMap{};
|
||||
|
||||
QVariantMap syncConflicts;
|
||||
QVariantMap problems;
|
||||
|
@ -243,53 +291,10 @@ void ClientStatusReporting::sendReportToServer()
|
|||
report[categoryKey] = problems;
|
||||
}
|
||||
}
|
||||
|
||||
if (report.isEmpty()) {
|
||||
qCDebug(lcClientStatusReporting) << "Failed to generate report. Report is empty.";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto clientStatusReportingJob = new JsonApiJob(_account->sharedFromThis(), QStringLiteral("ocs/v2.php/apps/security_guard/diagnostics"));
|
||||
clientStatusReportingJob->setBody(QJsonDocument::fromVariant(report));
|
||||
clientStatusReportingJob->setVerb(SimpleApiJob::Verb::Put);
|
||||
connect(clientStatusReportingJob, &JsonApiJob::jsonReceived, [this](const QJsonDocument &json, int statusCode) {
|
||||
if (statusCode == 200 || statusCode == 204) {
|
||||
const auto data = json.object().value("ocs").toObject().value("data").toObject();
|
||||
const auto dataMap = data.toVariantMap();
|
||||
slotSendReportToserverFinished();
|
||||
}
|
||||
});
|
||||
clientStatusReportingJob->start();
|
||||
return report;
|
||||
}
|
||||
|
||||
void ClientStatusReporting::slotSendReportToserverFinished()
|
||||
{
|
||||
if (!deleteClientStatusReportingRecords()) {
|
||||
qCWarning(lcClientStatusReporting) << "Error deleting client status report.";
|
||||
}
|
||||
setLastSentReportTimestamp(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch());
|
||||
}
|
||||
|
||||
qulonglong ClientStatusReporting::getLastSentReportTimestamp() const
|
||||
{
|
||||
QMutexLocker locker(&_mutex);
|
||||
QSqlQuery query;
|
||||
const auto prepareResult = query.prepare("SELECT value FROM keyvalue WHERE key = (:key)");
|
||||
query.bindValue(":key", lastSentReportTimestamp);
|
||||
if (!prepareResult || !query.exec()) {
|
||||
qCDebug(lcClientStatusReporting) << "Could not get last sent report timestamp from keyvalue table. No such record:" << lastSentReportTimestamp;
|
||||
return 0;
|
||||
}
|
||||
if (!query.next()) {
|
||||
qCDebug(lcClientStatusReporting) << "Could not get last sent report timestamp from keyvalue table:" << query.lastError().text();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int valueIndex = query.record().indexOf("value");
|
||||
return query.value(valueIndex).toULongLong();
|
||||
}
|
||||
|
||||
bool ClientStatusReporting::setLastSentReportTimestamp(const qulonglong timestamp)
|
||||
void ClientStatusReporting::setLastSentReportTimestamp(const qulonglong timestamp)
|
||||
{
|
||||
QMutexLocker locker(&_mutex);
|
||||
QSqlQuery query;
|
||||
|
@ -298,17 +303,15 @@ bool ClientStatusReporting::setLastSentReportTimestamp(const qulonglong timestam
|
|||
query.bindValue(":value", timestamp);
|
||||
if (!prepareResult || !query.exec()) {
|
||||
qCDebug(lcClientStatusReporting) << "Could not set last sent report timestamp from keyvalue table. No such record:" << lastSentReportTimestamp;
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray ClientStatusReporting::statusStringFromNumber(const Status status)
|
||||
{
|
||||
Q_ASSERT(status >= 0 && status < Count);
|
||||
if (status < 0 || status >= Status::Count) {
|
||||
qCWarning(lcClientStatusReporting) << "Invalid status:" << status;
|
||||
qCDebug(lcClientStatusReporting) << "Invalid status:" << status;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -328,8 +331,6 @@ QByteArray ClientStatusReporting::statusStringFromNumber(const Status status)
|
|||
case DownloadError_Virtual_File_Hydration_Failure:
|
||||
return QByteArrayLiteral("DownloadError.VIRTUAL_FILE_HYDRATION_FAILURE ");
|
||||
case UploadError_Conflict:
|
||||
return QByteArrayLiteral("UploadError.CONFLICT");
|
||||
case UploadError_ConflictCaseClash:
|
||||
return QByteArrayLiteral("UploadError.CONFLICT_CASECLASH");
|
||||
case UploadError_ConflictInvalidCharacters:
|
||||
return QByteArrayLiteral("UploadError.CONFLICT_INVALID_CHARACTERS");
|
||||
|
@ -349,7 +350,7 @@ QString ClientStatusReporting::classifyStatus(const Status status)
|
|||
{
|
||||
Q_ASSERT(status >= 0 && status < Count);
|
||||
if (status < 0 || status >= Status::Count) {
|
||||
qCWarning(lcClientStatusReporting) << "Invalid status:" << status;
|
||||
qCDebug(lcClientStatusReporting) << "Invalid status:" << status;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -358,7 +359,6 @@ QString ClientStatusReporting::classifyStatus(const Status status)
|
|||
case DownloadError_ConflictCaseClash:
|
||||
case DownloadError_ConflictInvalidCharacters:
|
||||
case UploadError_Conflict:
|
||||
case UploadError_ConflictCaseClash:
|
||||
case UploadError_ConflictInvalidCharacters:
|
||||
return QStringLiteral("sync_conflicts");
|
||||
case DownloadError_Cannot_Create_File:
|
||||
|
@ -374,4 +374,7 @@ QString ClientStatusReporting::classifyStatus(const Status status)
|
|||
};
|
||||
return {};
|
||||
}
|
||||
int ClientStatusReporting::clientStatusReportingTrySendTimerInterval = 1000 * 60 * 2; // check if the time has come, every 2 minutes
|
||||
int ClientStatusReporting::repordSendIntervalMs = 24 * 60 * 60 * 1000; // once every 24 hours
|
||||
QString ClientStatusReporting::dbPathForTesting;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
#include <QPair>
|
||||
#include <QRecursiveMutex>
|
||||
|
||||
class TestClientStatusReporting;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class Account;
|
||||
|
@ -36,24 +38,23 @@ class OWNCLOUDSYNC_EXPORT ClientStatusReporting : public QObject
|
|||
Q_OBJECT
|
||||
public:
|
||||
enum Status {
|
||||
DownloadError_Cannot_Create_File = 0,
|
||||
DownloadError_Conflict,
|
||||
DownloadError_ConflictCaseClash,
|
||||
DownloadError_ConflictInvalidCharacters,
|
||||
DownloadError_No_Free_Space,
|
||||
DownloadError_ServerError,
|
||||
DownloadError_Virtual_File_Hydration_Failure,
|
||||
UploadError_Conflict,
|
||||
UploadError_ConflictCaseClash,
|
||||
UploadError_ConflictInvalidCharacters,
|
||||
UploadError_No_Free_Space,
|
||||
UploadError_No_Write_Permissions,
|
||||
UploadError_ServerError,
|
||||
Count,
|
||||
DownloadError_Cannot_Create_File = 100,
|
||||
DownloadError_Conflict = 101,
|
||||
DownloadError_ConflictCaseClash = 102,
|
||||
DownloadError_ConflictInvalidCharacters = 103,
|
||||
DownloadError_No_Free_Space = 104,
|
||||
DownloadError_ServerError = 105,
|
||||
DownloadError_Virtual_File_Hydration_Failure = 106,
|
||||
UploadError_Conflict = 107,
|
||||
UploadError_ConflictInvalidCharacters = 108,
|
||||
UploadError_No_Free_Space = 109,
|
||||
UploadError_No_Write_Permissions = 110,
|
||||
UploadError_ServerError = 111,
|
||||
Count = UploadError_ServerError + 1,
|
||||
};
|
||||
|
||||
explicit ClientStatusReporting(Account *account, QObject *parent = nullptr);
|
||||
~ClientStatusReporting() = default;
|
||||
~ClientStatusReporting();
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
@ -62,22 +63,32 @@ private:
|
|||
[[nodiscard]] Result<void, QString> setClientStatusReportingRecord(const ClientStatusReportingRecord &record);
|
||||
[[nodiscard]] QVector<ClientStatusReportingRecord> getClientStatusReportingRecords() const;
|
||||
[[nodiscard]] bool deleteClientStatusReportingRecords();
|
||||
[[nodiscard]] bool setLastSentReportTimestamp(const qulonglong timestamp);
|
||||
void setLastSentReportTimestamp(const qulonglong timestamp);
|
||||
[[nodiscard]] qulonglong getLastSentReportTimestamp() const;
|
||||
[[nodiscard]] QVariantMap prepareReport() const;
|
||||
void reportToServerSentSuccessfully();
|
||||
[[nodiscard]] QString makeDbPath() const;
|
||||
|
||||
private slots:
|
||||
void sendReportToServer();
|
||||
void slotSendReportToserverFinished();
|
||||
|
||||
private:
|
||||
static QByteArray statusStringFromNumber(const Status status);
|
||||
static QString classifyStatus(const Status status);
|
||||
|
||||
static int clientStatusReportingTrySendTimerInterval;
|
||||
static int repordSendIntervalMs;
|
||||
|
||||
static QString dbPathForTesting;
|
||||
|
||||
Account *_account = nullptr;
|
||||
QHash<int, QPair<QByteArray, qint64>> _statusNamesAndHashes;
|
||||
QSqlDatabase _database;
|
||||
bool _isInitialized = false;
|
||||
QTimer _clientStatusReportingSendTimer;
|
||||
mutable QRecursiveMutex _mutex;
|
||||
|
||||
friend class Account;
|
||||
friend class TestClientStatusReporting;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1713,11 +1713,13 @@ bool ProcessDirectoryJob::checkPermissions(const OCC::SyncFileItemPtr &item)
|
|||
// No permissions set
|
||||
return true;
|
||||
} else if (item->isDirectory() && !perms.hasPermission(RemotePermissions::CanAddSubDirectories)) {
|
||||
_discoveryData->_account->reportClientStatus(ClientStatusReporting::Status::UploadError_No_Write_Permissions);
|
||||
qCWarning(lcDisco) << "checkForPermission: ERROR" << item->_file;
|
||||
item->_instruction = CSYNC_INSTRUCTION_ERROR;
|
||||
item->_errorString = tr("Not allowed because you don't have permission to add subfolders to that folder");
|
||||
return false;
|
||||
} else if (!item->isDirectory() && !perms.hasPermission(RemotePermissions::CanAddFile)) {
|
||||
_discoveryData->_account->reportClientStatus(ClientStatusReporting::Status::UploadError_No_Write_Permissions);
|
||||
qCWarning(lcDisco) << "checkForPermission: ERROR" << item->_file;
|
||||
item->_instruction = CSYNC_INSTRUCTION_ERROR;
|
||||
item->_errorString = tr("Not allowed because you don't have permission to add files in that folder");
|
||||
|
|
|
@ -229,29 +229,7 @@ void PropagateItemJob::done(const SyncFileItem::Status statusArg, const QString
|
|||
|
||||
_item->_status = statusArg;
|
||||
|
||||
if (_item->_status == SyncFileItem::Status::Conflict) {
|
||||
if (_item->_direction == SyncFileItem::Direction::Up) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_Conflict);
|
||||
} else {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_Conflict);
|
||||
}
|
||||
} else if (_item->_status == SyncFileItem::Status::FileNameClash) {
|
||||
if (_item->_direction == SyncFileItem::Direction::Up) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_ConflictInvalidCharacters);
|
||||
} else {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_ConflictInvalidCharacters);
|
||||
}
|
||||
} else if (_item->_status == SyncFileItem::Status::FileNameInvalidOnServer) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_ConflictInvalidCharacters);
|
||||
} else if (_item->_status == SyncFileItem::Status::FileNameInvalid) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_ConflictInvalidCharacters);
|
||||
} else if (_item->_httpErrorCode != 0 && _item->_httpErrorCode != 200) {
|
||||
if (_item->_direction == SyncFileItem::Up) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_ServerError);
|
||||
} else {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_ServerError);
|
||||
}
|
||||
}
|
||||
reportClientStatuses();
|
||||
|
||||
if (_item->_isRestoration) {
|
||||
if (_item->_status == SyncFileItem::Success
|
||||
|
@ -360,6 +338,33 @@ bool PropagateItemJob::hasEncryptedAncestor() const
|
|||
return false;
|
||||
}
|
||||
|
||||
void PropagateItemJob::reportClientStatuses()
|
||||
{
|
||||
if (_item->_status == SyncFileItem::Status::Conflict) {
|
||||
if (_item->_direction == SyncFileItem::Direction::Up) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_Conflict);
|
||||
} else {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_Conflict);
|
||||
}
|
||||
} else if (_item->_status == SyncFileItem::Status::FileNameClash) {
|
||||
if (_item->_direction == SyncFileItem::Direction::Up) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_ConflictInvalidCharacters);
|
||||
} else {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_ConflictInvalidCharacters);
|
||||
}
|
||||
} else if (_item->_status == SyncFileItem::Status::FileNameInvalidOnServer) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_ConflictInvalidCharacters);
|
||||
} else if (_item->_status == SyncFileItem::Status::FileNameInvalid) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_ConflictInvalidCharacters);
|
||||
} else if (_item->_httpErrorCode != 0 && _item->_httpErrorCode != 200 && _item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) {
|
||||
if (_item->_direction == SyncFileItem::Up) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_ServerError);
|
||||
} else {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_ServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
|
||||
PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item)
|
||||
|
|
|
@ -194,6 +194,8 @@ protected slots:
|
|||
void slotRestoreJobFinished(SyncFileItem::Status status);
|
||||
|
||||
private:
|
||||
void reportClientStatuses();
|
||||
|
||||
QScopedPointer<PropagateItemJob> _restoreJob;
|
||||
JobParallelism _parallelism = FullParallelism;
|
||||
|
||||
|
|
|
@ -675,6 +675,7 @@ void PropagateDownloadFile::startDownload()
|
|||
if (_tmpFile.exists())
|
||||
FileSystem::setFileReadOnly(_tmpFile.fileName(), false);
|
||||
if (!_tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_Cannot_Create_File);
|
||||
qCWarning(lcPropagateDownload) << "could not open temporary file" << _tmpFile.fileName();
|
||||
done(SyncFileItem::NormalError, _tmpFile.errorString(), ErrorCategory::GenericError);
|
||||
return;
|
||||
|
@ -1259,6 +1260,7 @@ void PropagateDownloadFile::downloadFinished()
|
|||
emit propagator()->touchedFile(filename);
|
||||
// The fileChanged() check is done above to generate better error messages.
|
||||
if (!FileSystem::uncheckedRenameReplace(_tmpFile.fileName(), filename, &error)) {
|
||||
propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_Cannot_Create_File);
|
||||
qCWarning(lcPropagateDownload) << QString("Rename failed: %1 => %2").arg(_tmpFile.fileName()).arg(filename);
|
||||
// If the file is locked, we want to retry this sync when it
|
||||
// becomes available again, otherwise try again directly
|
||||
|
|
|
@ -552,7 +552,6 @@ void SyncEngine::startSync()
|
|||
.arg(
|
||||
Utility::octetsToString(freeBytes),
|
||||
Utility::octetsToString(minFree)), ErrorCategory::GenericError);
|
||||
account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_No_Free_Space);
|
||||
finalize(false);
|
||||
return;
|
||||
} else {
|
||||
|
@ -1263,6 +1262,7 @@ void SyncEngine::slotSummaryError(const QString &message)
|
|||
|
||||
void SyncEngine::slotInsufficientLocalStorage()
|
||||
{
|
||||
account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_No_Free_Space);
|
||||
slotSummaryError(
|
||||
tr("Disk space is low: Downloads that would reduce free space "
|
||||
"below %1 were skipped.")
|
||||
|
@ -1271,8 +1271,8 @@ void SyncEngine::slotInsufficientLocalStorage()
|
|||
|
||||
void SyncEngine::slotInsufficientRemoteStorage()
|
||||
{
|
||||
auto msg = tr("There is insufficient space available on the server for some uploads.");
|
||||
account()->reportClientStatus(ClientStatusReporting::Status::UploadError_No_Free_Space);
|
||||
auto msg = tr("There is insufficient space available on the server for some uploads.");
|
||||
if (_uniqueErrors.contains(msg))
|
||||
return;
|
||||
|
||||
|
|
|
@ -464,6 +464,7 @@ void VfsCfApi::onHydrationJobFinished(HydrationJob *job)
|
|||
qCInfo(lcCfApi) << "Hydration job finished" << job->requestId() << job->folderPath() << job->status();
|
||||
emit hydrationRequestFinished(job->requestId());
|
||||
if (!job->errorString().isEmpty()) {
|
||||
params().account->reportClientStatus(ClientStatusReporting::Status::DownloadError_Virtual_File_Hydration_Failure);
|
||||
emit failureHydrating(job->errorCode(), job->statusCode(), job->errorString(), job->folderPath());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ nextcloud_add_test(SecureFileDrop)
|
|||
nextcloud_add_test(FileTagModel)
|
||||
nextcloud_add_test(SyncConflictsModel)
|
||||
nextcloud_add_test(DateFieldBackend)
|
||||
nextcloud_add_test(ClientStatusReporting)
|
||||
|
||||
target_link_libraries(SecureFileDropTest PRIVATE Nextcloud::sync)
|
||||
configure_file(fake2eelocksucceeded.json "${PROJECT_BINARY_DIR}/bin/fake2eelocksucceeded.json" COPYONLY)
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (C) by Oleksandr Zolotov <alex@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 "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "clientstatusreporting.h"
|
||||
#include "syncenginetestutils.h"
|
||||
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
namespace {
|
||||
static QByteArray fake200Response = R"({"ocs":{"meta":{"status":"success","statuscode":200},"data":[]}})";
|
||||
}
|
||||
|
||||
class TestClientStatusReporting : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TestClientStatusReporting() = default;
|
||||
|
||||
QScopedPointer<FakeQNAM> fakeQnam;
|
||||
OCC::Account *account;
|
||||
QScopedPointer<OCC::AccountState> accountState;
|
||||
QString dbFilePath;
|
||||
QVariantMap bodyReceivedAndParsed;
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
OCC::ClientStatusReporting::clientStatusReportingTrySendTimerInterval = 1000;
|
||||
OCC::ClientStatusReporting::repordSendIntervalMs = 2000;
|
||||
|
||||
fakeQnam.reset(new FakeQNAM({}));
|
||||
account = OCC::Account::create().get();
|
||||
account->setCredentials(new FakeCredentials{fakeQnam.data()});
|
||||
account->setUrl(QUrl(("http://example.de")));
|
||||
|
||||
accountState.reset(new OCC::AccountState(account->sharedFromThis()));
|
||||
|
||||
const auto databaseId = QStringLiteral("%1@%2").arg(account->davUser(), account->url().toString());
|
||||
const auto databaseIdHash = QCryptographicHash::hash(databaseId.toUtf8(), QCryptographicHash::Md5);
|
||||
dbFilePath = QDir::tempPath() + QStringLiteral("/.tests_userdata_%1.db").arg(QString::fromLatin1(databaseIdHash.left(6).toHex()));
|
||||
QFile(dbFilePath).remove();
|
||||
OCC::ClientStatusReporting::dbPathForTesting = dbFilePath;
|
||||
|
||||
QVariantMap capabilities;
|
||||
capabilities[QStringLiteral("security_guard")] = QVariantMap{
|
||||
{ QStringLiteral("diagnostics"), true }
|
||||
};
|
||||
account->setCapabilities(capabilities);
|
||||
|
||||
fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
|
||||
QNetworkReply *reply = nullptr;
|
||||
const auto reqBody = device->readAll();
|
||||
bodyReceivedAndParsed = QJsonDocument::fromJson(reqBody).toVariant().toMap();
|
||||
reply = new FakePayloadReply(op, req, fake200Response, fakeQnam.data());
|
||||
return reply;
|
||||
});
|
||||
}
|
||||
|
||||
void testReportAndSendStatuses()
|
||||
{
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// 5 conflicts
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::UploadError_Conflict);
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::UploadError_ConflictInvalidCharacters);
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::DownloadError_Conflict);
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::DownloadError_ConflictInvalidCharacters);
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::DownloadError_ConflictCaseClash);
|
||||
|
||||
// 4 problems
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::UploadError_ServerError);
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::DownloadError_ServerError);
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::DownloadError_Virtual_File_Hydration_Failure);
|
||||
// 3 occurances of UploadError_No_Write_Permissions
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::UploadError_No_Write_Permissions);
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::UploadError_No_Write_Permissions);
|
||||
account->reportClientStatus(OCC::ClientStatusReporting::Status::UploadError_No_Write_Permissions);
|
||||
QTest::qWait(OCC::ClientStatusReporting::clientStatusReportingTrySendTimerInterval + OCC::ClientStatusReporting::repordSendIntervalMs);
|
||||
|
||||
QVERIFY(!bodyReceivedAndParsed.isEmpty());
|
||||
|
||||
// we must have "virus_detected" and "e2e_errors" keys present (as required by server)
|
||||
QVERIFY(bodyReceivedAndParsed.contains("virus_detected"));
|
||||
QVERIFY(bodyReceivedAndParsed.contains("e2e_errors"));
|
||||
|
||||
// we must have 5 conflicts
|
||||
const auto conflictsReceived = bodyReceivedAndParsed.value("sync_conflicts").toMap();
|
||||
QVERIFY(!conflictsReceived.isEmpty());
|
||||
QCOMPARE(conflictsReceived.value("count"), 5);
|
||||
|
||||
// we must have 4 problems
|
||||
const auto problemsReceived = bodyReceivedAndParsed.value("problems").toMap();
|
||||
QVERIFY(!problemsReceived.isEmpty());
|
||||
QCOMPARE(problemsReceived.size(), 4);
|
||||
const auto problemsNoWritePermissions = problemsReceived.value(OCC::ClientStatusReporting::statusStringFromNumber(OCC::ClientStatusReporting::Status::UploadError_No_Write_Permissions)).toMap();
|
||||
// among those, 3 occurances of UploadError_No_Write_Permissions
|
||||
QCOMPARE(problemsNoWritePermissions.value("count"), 3);
|
||||
|
||||
bodyReceivedAndParsed.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void testNothingReportedAndNothingSent()
|
||||
{
|
||||
QTest::qWait(OCC::ClientStatusReporting::clientStatusReportingTrySendTimerInterval + OCC::ClientStatusReporting::repordSendIntervalMs);
|
||||
QVERIFY(bodyReceivedAndParsed.isEmpty());
|
||||
}
|
||||
|
||||
void cleanupTestCase()
|
||||
{
|
||||
accountState.reset(nullptr);
|
||||
delete account;
|
||||
QFile(dbFilePath).remove();
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestClientStatusReporting)
|
||||
#include "testclientstatusreporting.moc"
|
Загрузка…
Ссылка в новой задаче