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..c545919aeb261 --- /dev/null +++ b/src/libsync/lockfilejobs.cpp @@ -0,0 +1,196 @@ +/* + * 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() +{ + _lockStatus = SyncFileItem::LockStatus::UnlockedItem; + _lockOwnerType = SyncFileItem::LockOwnerType::UserLock; + _userDisplayName = QString{}; + _editorName = QString{}; + _userId = QString{}; + _lockTime = 0; + + if (reply()->error() != QNetworkReply::NoError) { + qCInfo(lcLockFileJob()) << "finished with error" << reply()->error() << reply()->errorString(); + const auto httpErrorCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (httpErrorCode == LOCKED_HTTP_ERROR_CODE) { + const 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, + const SyncFileItem::LockStatus lockStatus, + const SyncFileItem::LockOwnerType lockOwnerType, + const QString &userDisplayName, + const QString &userId, + const QString &editorName, + const 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); + + 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: + decodeStartElement(name, reader); + break; + } + } + + 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; +} + +void LockFileJob::decodeStartElement(const QString &name, + QXmlStreamReader &reader) +{ + if (name == QStringLiteral("lock")) { + const auto valueText = reader.readElementText(); + if (valueText.isEmpty()) { + _lockStatus = SyncFileItem::LockStatus::UnlockedItem; + } else { + bool success = false; + _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; + } +} + +} diff --git a/src/libsync/lockfilejobs.h b/src/libsync/lockfilejobs.h new file mode 100644 index 0000000000000..94a6a5c5da722 --- /dev/null +++ b/src/libsync/lockfilejobs.h @@ -0,0 +1,63 @@ +#ifndef LOCKFILEJOBS_H +#define LOCKFILEJOBS_H + +#include "abstractnetworkjob.h" + +#include "syncfileitem.h" + +class QXmlStreamReader; + +namespace OCC { + +class SyncJournalDb; + +class OWNCLOUDSYNC_EXPORT LockFileJob : public AbstractNetworkJob +{ + Q_OBJECT + +public: + static constexpr auto LOCKED_HTTP_ERROR_CODE = 423; + + 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, + const SyncFileItem::LockStatus lockStatus, + const SyncFileItem::LockOwnerType lockOwnerType, + const QString &userDisplayName, + const QString &userId, + const QString &editorName, + const qint64 lockTime) const; + + SyncJournalFileRecord handleReply(); + + void decodeStartElement(const QString &name, + QXmlStreamReader &reader); + + SyncJournalDb* _journal = nullptr; + SyncFileItem::LockStatus _requestedLockState = SyncFileItem::LockStatus::LockedItem; + + SyncFileItem::LockStatus _lockStatus = SyncFileItem::LockStatus::UnlockedItem; + SyncFileItem::LockOwnerType _lockOwnerType = SyncFileItem::LockOwnerType::UserLock; + QString _userDisplayName; + QString _editorName; + QString _userId; + qint64 _lockTime = 0; +}; + +} + +#endif // LOCKFILEJOBS_H