From e5e37d41616544029caae2c39de6d8ccfdca38d0 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 7 Apr 2022 10:44:03 +0200 Subject: [PATCH] introduce new jobs to handle lock/unlock of files close #4382 Signed-off-by: Matthieu Gallien --- src/gui/CMakeLists.txt | 2 + src/gui/ocslockfilejob.cpp | 43 ++++++++ src/gui/ocslockfilejob.h | 48 +++++++++ src/libsync/CMakeLists.txt | 2 + src/libsync/lockfilejobs.cpp | 193 +++++++++++++++++++++++++++++++++++ src/libsync/lockfilejobs.h | 49 +++++++++ 6 files changed, 337 insertions(+) create mode 100644 src/gui/ocslockfilejob.cpp create mode 100644 src/gui/ocslockfilejob.h create mode 100644 src/libsync/lockfilejobs.cpp create mode 100644 src/libsync/lockfilejobs.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8b47d33ff5110..1625e599bc5c6 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -120,6 +120,8 @@ set(client_SRCS ocssharejob.cpp ocsshareejob.h ocsshareejob.cpp + ocslockfilejob.h + ocslockfilejob.cpp openfilemanager.h openfilemanager.cpp owncloudgui.h diff --git a/src/gui/ocslockfilejob.cpp b/src/gui/ocslockfilejob.cpp new file mode 100644 index 0000000000000..d1a8b44e644a5 --- /dev/null +++ b/src/gui/ocslockfilejob.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) by Matthieu Gallien + * + * 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 "ocslockfilejob.h" + +#include "account.h" + +namespace OCC { + +OcsLockFileJob::OcsLockFileJob(const AccountPtr account) + : OcsJob(account) +{ + setPath("ocs/v2.php/apps/files_lock/lock/"); +} + +void OCC::OcsLockFileJob::acquireLock(const int fileId) +{ + appendPath(QString::number(fileId)); + setVerb("PUT"); + + start(); +} + +void OcsLockFileJob::releaseLock(const int fileId) +{ + appendPath(QString::number(fileId)); + setVerb("DELETE"); + + start(); +} + +} diff --git a/src/gui/ocslockfilejob.h b/src/gui/ocslockfilejob.h new file mode 100644 index 0000000000000..36f7ff5ac7846 --- /dev/null +++ b/src/gui/ocslockfilejob.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) by Matthieu Gallien + * + * 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 OCSLOCKFILEJOB_H +#define OCSLOCKFILEJOB_H + +#include "ocsjob.h" + +#include "accountfwd.h" + +#include + +class QJsonDocument; + +namespace OCC { + +/** + * @brief The OcsShareJob class + * @ingroup gui + * + * Handle talking to the OCS Share API. + * For creation, deletion and modification of shares. + */ +class OcsLockFileJob : public OcsJob +{ + Q_OBJECT +public: + explicit OcsLockFileJob(const AccountPtr account); + + void acquireLock(const int fileId); + + void releaseLock(const int fileId); +}; + +} + +#endif // OCSLOCKFILEJOB_H diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 0ee2016bbe20a..4d1aacd7b1d85 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -110,6 +110,8 @@ set(libsync_SRCS userstatusconnector.cpp ocsprofileconnector.h ocsprofileconnector.cpp + lockfilejobs.h + lockfilejobs.cpp creds/dummycredentials.h creds/dummycredentials.cpp creds/abstractcredentials.h diff --git a/src/libsync/lockfilejobs.cpp b/src/libsync/lockfilejobs.cpp new file mode 100644 index 0000000000000..7109a25a25545 --- /dev/null +++ b/src/libsync/lockfilejobs.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) by Matthieu Gallien + * + * 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 "lockfilejobs.h" + +#include "account.h" +#include "common/syncjournaldb.h" + +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcLockFileJob, "nextcloud.sync.networkjob.lockfile", QtInfoMsg) + +LockFileJob::LockFileJob(const AccountPtr account, + SyncJournalDb* const journal, + const QString &path, + const SyncFileItem::LockStatus requestedLockState, + QObject *parent) + : AbstractNetworkJob(account, path, parent) + , _journal(journal) + , _requestedLockState(requestedLockState) +{ +} + +void LockFileJob::start() +{ + qCInfo(lcLockFileJob()) << "start" << path() << _requestedLockState; + + QNetworkRequest request; + request.setRawHeader("X-User-Lock", "1"); + + QByteArray verb; + switch(_requestedLockState) + { + case SyncFileItem::LockStatus::LockedItem: + verb = "LOCK"; + break; + case SyncFileItem::LockStatus::UnlockedItem: + verb = "UNLOCK"; + break; + } + sendRequest(verb, makeDavUrl(path()), request); + + AbstractNetworkJob::start(); +} + +bool LockFileJob::finished() +{ + if (reply()->error() != QNetworkReply::NoError) { + qCInfo(lcLockFileJob()) << "finished with error" << reply()->error() << reply()->errorString(); + const auto httpErrorCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (httpErrorCode == 423) { + auto record = handleReply(); + if (static_cast(record._lockOwnerType) == SyncFileItem::LockOwnerType::UserLock) { + Q_EMIT finishedWithError(httpErrorCode, {}, record._lockOwnerDisplayName); + } else { + Q_EMIT finishedWithError(httpErrorCode, {}, record._lockEditorApp); + } + } else { + Q_EMIT finishedWithError(httpErrorCode, reply()->errorString(), {}); + } + } else { + qCInfo(lcLockFileJob()) << "success" << path(); + handleReply(); + Q_EMIT finishedWithoutError(); + } + return true; +} + +void LockFileJob::setFileRecordLocked(SyncJournalFileRecord &record, + SyncFileItem::LockStatus lockStatus, + SyncFileItem::LockOwnerType lockOwnerType, + const QString &userDisplayName, + const QString &userId, + const QString &editorName, + qint64 lockTime) const +{ + record._locked = (lockStatus == SyncFileItem::LockStatus::LockedItem); + record._lockOwnerType = static_cast(lockOwnerType); + record._lockOwnerDisplayName = userDisplayName; + record._lockOwnerId = userId; + record._lockEditorApp = editorName; + record._lockTime = lockTime; +} + +SyncJournalFileRecord LockFileJob::handleReply() +{ + const auto xml = reply()->readAll(); + + QXmlStreamReader reader(xml); + + SyncFileItem::LockStatus lockStatus = SyncFileItem::LockStatus::UnlockedItem; + SyncFileItem::LockOwnerType lockOwnerType = SyncFileItem::LockOwnerType::UserLock; + QString userDisplayName; + QString editorName; + QString userId; + qint64 lockTime = 0; + + while (!reader.atEnd()) { + const auto type = reader.readNext(); + const auto name = reader.name().toString(); + + switch (type) { + case QXmlStreamReader::TokenType::NoToken: + case QXmlStreamReader::TokenType::Invalid: + case QXmlStreamReader::TokenType::DTD: + case QXmlStreamReader::TokenType::EntityReference: + case QXmlStreamReader::TokenType::ProcessingInstruction: + case QXmlStreamReader::TokenType::Comment: + case QXmlStreamReader::TokenType::StartDocument: + case QXmlStreamReader::TokenType::Characters: + case QXmlStreamReader::TokenType::EndDocument: + case QXmlStreamReader::TokenType::EndElement: + break; + case QXmlStreamReader::TokenType::StartElement: + if (name == QStringLiteral("lock")) { + const auto valueText = reader.readElementText(); + if (valueText.isEmpty()) { + lockStatus = SyncFileItem::LockStatus::UnlockedItem; + } else { + bool success; + lockStatus = static_cast(valueText.toInt(&success)); + if (!success) { + lockStatus = SyncFileItem::LockStatus::UnlockedItem; + } + } + } else if (name == QStringLiteral("lock-owner-type")) { + const auto valueText = reader.readElementText(); + lockOwnerType = static_cast(valueText.toInt()); + } else if (name == QStringLiteral("lock-owner-displayname")) { + const auto valueText = reader.readElementText(); + userDisplayName = valueText; + } else if (name == QStringLiteral("lock-owner")) { + const auto valueText = reader.readElementText(); + userId = valueText; + } else if (name == QStringLiteral("lock-time")) { + const auto valueText = reader.readElementText(); + lockTime = valueText.toLongLong(); + } else if (name == QStringLiteral("lock-owner-editor")) { + const auto valueText = reader.readElementText(); + editorName = valueText; + } + + break; + } + } + + qCInfo(lcLockFileJob()) << xml << lockStatus << lockOwnerType << userDisplayName << userId << editorName << lockTime; + + SyncJournalFileRecord record; + + if (lockStatus == SyncFileItem::LockStatus::LockedItem) { + if (lockOwnerType == SyncFileItem::LockOwnerType::UserLock && userDisplayName.isEmpty()) { + return record; + } + + if (lockOwnerType == SyncFileItem::LockOwnerType::AppLock && editorName.isEmpty()) { + return record; + } + + if (userId.isEmpty()) { + return record; + } + + if (lockTime <= 0) { + return record; + } + } + + const auto relativePath = path().mid(1); + if (_journal->getFileRecord(relativePath, &record) && record.isValid()) { + setFileRecordLocked(record, lockStatus, lockOwnerType, userDisplayName, userId, editorName, lockTime); + _journal->setFileRecord(record); + _journal->commit("lock file job"); + } + + return record; +} + +} diff --git a/src/libsync/lockfilejobs.h b/src/libsync/lockfilejobs.h new file mode 100644 index 0000000000000..acaf5e096a508 --- /dev/null +++ b/src/libsync/lockfilejobs.h @@ -0,0 +1,49 @@ +#ifndef LOCKFILEJOBS_H +#define LOCKFILEJOBS_H + +#include "abstractnetworkjob.h" + +#include "syncfileitem.h" + +namespace OCC { + +class SyncJournalDb; + +class OWNCLOUDSYNC_EXPORT LockFileJob : public AbstractNetworkJob +{ + Q_OBJECT + +public: + explicit LockFileJob(const AccountPtr account, + SyncJournalDb* const journal, + const QString &path, + const SyncFileItem::LockStatus requestedLockState, + QObject *parent = nullptr); + void start() override; + +signals: + void finishedWithError(int httpErrorCode, + const QString &errorString, + const QString &lockOwnerName); + void finishedWithoutError(); + +private: + bool finished() override; + + void setFileRecordLocked(SyncJournalFileRecord &record, + SyncFileItem::LockStatus lockStatus, + SyncFileItem::LockOwnerType lockOwnerType, + const QString &userDisplayName, + const QString &userId, + const QString &editorName, + qint64 lockTime) const; + + SyncJournalFileRecord handleReply(); + + SyncJournalDb* _journal = nullptr; + SyncFileItem::LockStatus _requestedLockState = SyncFileItem::LockStatus::LockedItem; +}; + +} + +#endif // LOCKFILEJOBS_H