From 2eaecb6400b08677d0558eac0f251ecb09ffef15 Mon Sep 17 00:00:00 2001 From: alex-z Date: Fri, 15 Dec 2023 12:09:16 +0100 Subject: [PATCH] File locking. Manual lock should never be modified without user intervention. Set token lock instead of user lock when locking office files automatically. Signed-off-by: alex-z --- src/gui/editlocallyjob.cpp | 3 ++- src/gui/folder.cpp | 9 ++++--- src/gui/socketapi/socketapi.cpp | 9 ++++++- src/libsync/account.cpp | 5 ++-- src/libsync/account.h | 3 ++- src/libsync/capabilities.cpp | 5 ++++ src/libsync/capabilities.h | 1 + src/libsync/lockfilejobs.cpp | 15 ++++++++++-- src/libsync/lockfilejobs.h | 2 ++ test/testlockfile.cpp | 42 ++++++++++++++++++++++----------- 10 files changed, 70 insertions(+), 24 deletions(-) diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index a7547b0fddf68..d20a9bb699c57 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -650,7 +650,8 @@ void EditLocallyJob::lockFile() _folderForFile->remotePathTrailingSlash(), _folderForFile->path(), _folderForFile->journalDb(), - SyncFileItem::LockStatus::LockedItem); + SyncFileItem::LockStatus::LockedItem, + SyncFileItem::LockOwnerType::TokenLock); } void EditLocallyJob::disconnectFolderSignals() diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 21f3423460326..ace5524cb3d91 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -649,7 +649,7 @@ void Folder::slotFilesLockReleased(const QSet &files) } const auto canUnlockFile = isFileRecordValid && rec._lockstate._locked - && rec._lockstate._lockOwnerType == static_cast(SyncFileItem::LockOwnerType::UserLock) + && rec._lockstate._lockOwnerType == static_cast(SyncFileItem::LockOwnerType::TokenLock) && rec._lockstate._lockOwnerId == _accountState->account()->davUser(); if (!canUnlockFile) { @@ -668,11 +668,13 @@ void Folder::slotFilesLockReleased(const QSet &files) disconnect(_officeFileLockReleaseUnlockFailure); qCWarning(lcFolder) << "Failed to unlock a file:" << remoteFilePath << message; }); + const auto lockOwnerType = static_cast(rec._lockstate._lockOwnerType); _accountState->account()->setLockFileState(remoteFilePath, remotePathTrailingSlash(), path(), journalDb(), - SyncFileItem::LockStatus::UnlockedItem); + SyncFileItem::LockStatus::UnlockedItem, + lockOwnerType); } } @@ -719,7 +721,8 @@ void Folder::slotLockedFilesFound(const QSet &files) remotePathTrailingSlash(), path(), journalDb(), - SyncFileItem::LockStatus::LockedItem); + SyncFileItem::LockStatus::LockedItem, + SyncFileItem::LockOwnerType::TokenLock); } } diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp index e2e3c1a463fe3..1df11cf555080 100644 --- a/src/gui/socketapi/socketapi.cpp +++ b/src/gui/socketapi/socketapi.cpp @@ -1091,11 +1091,18 @@ void SocketApi::setFileLock(const QString &localFile, const SyncFileItem::LockSt return; } + const auto record = fileData.journalRecord(); + if (static_cast(record._lockstate._lockOwnerType) != SyncFileItem::LockOwnerType::UserLock) { + qCDebug(lcSocketApi) << "Only user lock state or non-locked files can be affected manually!"; + return; + } + shareFolder->accountState()->account()->setLockFileState(fileData.serverRelativePath, shareFolder->remotePathTrailingSlash(), shareFolder->path(), shareFolder->journalDb(), - lockState); + lockState, + SyncFileItem::LockOwnerType::UserLock); shareFolder->journalDb()->schedulePathForRemoteDiscovery(fileData.serverRelativePath); shareFolder->scheduleThisFolderSoon(); diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index bea76c53b9069..15712b11e165f 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -959,7 +959,8 @@ void Account::setLockFileState(const QString &serverRelativePath, const QString &remoteSyncPathWithTrailingSlash, const QString &localSyncPath, SyncJournalDb * const journal, - const SyncFileItem::LockStatus lockStatus) + const SyncFileItem::LockStatus lockStatus, + const SyncFileItem::LockOwnerType lockOwnerType) { auto& lockStatusJobInProgress = _lockStatusChangeInprogress[serverRelativePath]; if (lockStatusJobInProgress.contains(lockStatus)) { @@ -967,7 +968,7 @@ void Account::setLockFileState(const QString &serverRelativePath, return; } lockStatusJobInProgress.push_back(lockStatus); - auto job = std::make_unique(sharedFromThis(), journal, serverRelativePath, remoteSyncPathWithTrailingSlash, localSyncPath, lockStatus); + auto job = std::make_unique(sharedFromThis(), journal, serverRelativePath, remoteSyncPathWithTrailingSlash, localSyncPath, lockStatus, lockOwnerType); connect(job.get(), &LockFileJob::finishedWithoutError, this, [this, serverRelativePath, lockStatus]() { removeLockStatusChangeInprogress(serverRelativePath, lockStatus); Q_EMIT lockFileSuccess(); diff --git a/src/libsync/account.h b/src/libsync/account.h index e118b53fb2f25..d21748f872a1a 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -316,7 +316,8 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject const QString &remoteSyncPathWithTrailingSlash, const QString &localSyncPath, SyncJournalDb * const journal, - const SyncFileItem::LockStatus lockStatus); + const SyncFileItem::LockStatus lockStatus, + const SyncFileItem::LockOwnerType lockOwnerType); SyncFileItem::LockStatus fileLockStatus(SyncJournalDb * const journal, const QString &folderRelativePath) const; diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index 14c51744a228b..0b73b6f7a4652 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -254,6 +254,11 @@ bool Capabilities::filesLockAvailable() const return _capabilities["files"].toMap()["locking"].toByteArray() >= "1.0"; } +bool Capabilities::filesLockTypeAvailable() const +{ + return _capabilities["files"].toMap()["api-feature-lock-type"].toByteArray() >= "1.0"; +} + bool Capabilities::userStatus() const { if (!_capabilities.contains("user_status")) { diff --git a/src/libsync/capabilities.h b/src/libsync/capabilities.h index 9a7f587f00b99..1dd0514a28f69 100644 --- a/src/libsync/capabilities.h +++ b/src/libsync/capabilities.h @@ -66,6 +66,7 @@ class OWNCLOUDSYNC_EXPORT Capabilities [[nodiscard]] bool chunkingNg() const; [[nodiscard]] bool bulkUpload() const; [[nodiscard]] bool filesLockAvailable() const; + [[nodiscard]] bool filesLockTypeAvailable() const; [[nodiscard]] bool userStatus() const; [[nodiscard]] bool userStatusSupportsEmoji() const; [[nodiscard]] QColor serverColor() const; diff --git a/src/libsync/lockfilejobs.cpp b/src/libsync/lockfilejobs.cpp index 47704513938fe..d1d0f98b9b9a4 100644 --- a/src/libsync/lockfilejobs.cpp +++ b/src/libsync/lockfilejobs.cpp @@ -31,10 +31,12 @@ LockFileJob::LockFileJob(const AccountPtr account, const QString &remoteSyncPathWithTrailingSlash, const QString &localSyncPath, const SyncFileItem::LockStatus requestedLockState, + const SyncFileItem::LockOwnerType lockOwnerType, QObject *parent) : AbstractNetworkJob(account, path, parent) , _journal(journal) , _requestedLockState(requestedLockState) + , _requestedLockOwnerType(lockOwnerType) , _remoteSyncPathWithTrailingSlash(remoteSyncPathWithTrailingSlash) , _localSyncPath(localSyncPath) { @@ -48,7 +50,14 @@ void LockFileJob::start() qCInfo(lcLockFileJob()) << "start" << path() << _requestedLockState; QNetworkRequest request; - request.setRawHeader("X-User-Lock", "1"); + request.setRawHeader(QByteArrayLiteral("X-User-Lock"), QByteArrayLiteral("1")); + if (_account->capabilities().filesLockTypeAvailable()) { + if (_requestedLockOwnerType == SyncFileItem::LockOwnerType::UserLock) { + request.setRawHeader(QByteArrayLiteral("X-User-Lock-Type"), ("0")); + } else if (_requestedLockOwnerType == SyncFileItem::LockOwnerType::TokenLock) { + request.setRawHeader(QByteArrayLiteral("X-User-Lock-Type"), ("2")); + } + } QByteArray verb; switch(_requestedLockState) @@ -174,7 +183,7 @@ SyncJournalFileRecord LockFileJob::handleReply() if (_journal->getFileRecord(relativePathInDb, &record) && record.isValid()) { setFileRecordLocked(record); if ((_lockStatus == SyncFileItem::LockStatus::LockedItem) - && (_lockOwnerType != SyncFileItem::LockOwnerType::UserLock || _userId != account()->davUser())) { + && (_lockOwnerType == SyncFileItem::LockOwnerType::AppLock || _userId != account()->davUser())) { FileSystem::setFileReadOnly(_localSyncPath + relativePathInDb, true); } const auto result = _journal->setFileRecord(record); @@ -205,6 +214,8 @@ void LockFileJob::decodeStartElement(const QString &name, const auto convertedValue = valueText.toInt(&isValid); if (isValid) { _lockOwnerType = static_cast(convertedValue); + } else { + _lockOwnerType = SyncFileItem::LockOwnerType::UserLock; } } else if (name == QStringLiteral("lock-owner-displayname")) { _userDisplayName = reader.readElementText(); diff --git a/src/libsync/lockfilejobs.h b/src/libsync/lockfilejobs.h index 50e02016b7d20..8b2e287f6d4c0 100644 --- a/src/libsync/lockfilejobs.h +++ b/src/libsync/lockfilejobs.h @@ -25,6 +25,7 @@ class OWNCLOUDSYNC_EXPORT LockFileJob : public AbstractNetworkJob const QString &remoteSyncPathWithTrailingSlash, const QString &localSyncPath, const SyncFileItem::LockStatus requestedLockState, + const SyncFileItem::LockOwnerType lockOwnerType, QObject *parent = nullptr); void start() override; @@ -48,6 +49,7 @@ class OWNCLOUDSYNC_EXPORT LockFileJob : public AbstractNetworkJob SyncJournalDb* _journal = nullptr; SyncFileItem::LockStatus _requestedLockState = SyncFileItem::LockStatus::LockedItem; + SyncFileItem::LockOwnerType _requestedLockOwnerType = SyncFileItem::LockOwnerType::UserLock; SyncFileItem::LockStatus _lockStatus = SyncFileItem::LockStatus::UnlockedItem; SyncFileItem::LockOwnerType _lockOwnerType = SyncFileItem::LockOwnerType::UserLock; diff --git a/test/testlockfile.cpp b/test/testlockfile.cpp index 7271a56e46abb..174f9c656abea 100644 --- a/test/testlockfile.cpp +++ b/test/testlockfile.cpp @@ -40,7 +40,8 @@ private slots: QStringLiteral("/"), fakeFolder.localPath(), &fakeFolder.syncJournal(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QVERIFY(lockFileSuccessSpy.wait()); QCOMPARE(lockFileErrorSpy.count(), 0); @@ -85,7 +86,8 @@ private slots: QStringLiteral("/"), fakeFolder.localPath(), &fakeFolder.syncJournal(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QVERIFY(lockFileErrorSpy.wait()); QCOMPARE(lockFileSuccessSpy.count(), 0); @@ -109,7 +111,8 @@ private slots: QStringLiteral("/"), fakeFolder.localPath(), &fakeFolder.syncJournal(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QVERIFY(lockFileSuccessSpy.wait()); QCOMPARE(lockFileErrorSpy.count(), 0); @@ -136,7 +139,8 @@ private slots: QStringLiteral("/"), fakeFolder.localPath(), &fakeFolder.syncJournal(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QVERIFY(lockFileSuccessSpy.wait()); QCOMPARE(lockFileErrorSpy.count(), 0); @@ -160,7 +164,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); @@ -198,7 +203,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError); QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError); @@ -215,7 +221,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::UnlockedItem); + OCC::SyncFileItem::LockStatus::UnlockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError); QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError); @@ -274,7 +281,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); @@ -327,7 +335,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); @@ -380,7 +389,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::UnlockedItem); + OCC::SyncFileItem::LockStatus::UnlockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); @@ -431,7 +441,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::UnlockedItem); + OCC::SyncFileItem::LockStatus::UnlockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); @@ -469,7 +480,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncFileItem::LockStatus::LockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError); QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError); @@ -486,7 +498,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::UnlockedItem); + OCC::SyncFileItem::LockStatus::UnlockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError); QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError); @@ -539,7 +552,8 @@ private slots: QStringLiteral("/") + testFileName, QStringLiteral("/"), fakeFolder.localPath(), - OCC::SyncFileItem::LockStatus::UnlockedItem); + OCC::SyncFileItem::LockStatus::UnlockedItem, + OCC::SyncFileItem::LockOwnerType::UserLock); QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);