allow lock/unlock of files from files explorer integration

add new commands to the contextual menu provided by our files explorer
plugins to allow locking/unlocking a file

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
This commit is contained in:
Matthieu Gallien 2022-04-13 10:47:23 +02:00 коммит произвёл Matthieu Gallien
Родитель 2ea68d75bd
Коммит b55a099b61
6 изменённых файлов: 262 добавлений и 243 удалений

Просмотреть файл

@ -958,6 +958,32 @@ void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *)
solver.setRemoteVersionFilename(target);
}
void SocketApi::command_LOCK_FILE(const QString &localFile, SocketListener *listener)
{
Q_UNUSED(listener)
setFileLock(localFile, SyncFileItem::LockStatus::LockedItem);
}
void SocketApi::command_UNLOCK_FILE(const QString &localFile, SocketListener *listener)
{
Q_UNUSED(listener)
setFileLock(localFile, SyncFileItem::LockStatus::UnlockedItem);
}
void SocketApi::setFileLock(const QString &localFile, const SyncFileItem::LockStatus lockState) const
{
const auto fileData = FileData::get(localFile);
const auto shareFolder = fileData.folder;
if (!shareFolder || !shareFolder->accountState()->isConnected()) {
return;
}
shareFolder->accountState()->account()->setLockFileState(fileData.serverRelativePath, shareFolder->journalDb(), lockState);
}
void SocketApi::command_V2_LIST_ACCOUNTS(const QSharedPointer<SocketApiJobV2> &job) const
{
QJsonArray out;
@ -1047,6 +1073,39 @@ void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketLi
//listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email …"));
}
void SocketApi::sendLockFileCommandMenuEntries(const QFileInfo &fileInfo,
Folder* const syncFolder,
const FileData &fileData,
const OCC::SocketListener* const listener) const
{
if (!fileInfo.isDir() && syncFolder->accountState()->account()->capabilities().filesLockAvailable()) {
if (syncFolder->accountState()->account()->fileLockStatus(syncFolder->journalDb(), fileData.folderRelativePath) == SyncFileItem::LockStatus::UnlockedItem) {
listener->sendMessage(QLatin1String("MENU_ITEM:LOCK_FILE::") + tr("Lock file"));
} else {
if (syncFolder->accountState()->account()->fileCanBeUnlocked(syncFolder->journalDb(), fileData.folderRelativePath)) {
listener->sendMessage(QLatin1String("MENU_ITEM:UNLOCK_FILE::") + tr("Unlock file"));
}
}
}
}
void SocketApi::sendLockFileInfoMenuEntries(const QFileInfo &fileInfo,
Folder * const syncFolder,
const FileData &fileData,
const SocketListener * const listener,
const SyncJournalFileRecord &record) const
{
static constexpr auto SECONDS_PER_MINUTE = 60;
if (!fileInfo.isDir() && syncFolder->accountState()->account()->capabilities().filesLockAvailable() &&
syncFolder->accountState()->account()->fileLockStatus(syncFolder->journalDb(), fileData.folderRelativePath) == SyncFileItem::LockStatus::LockedItem) {
listener->sendMessage(QLatin1String("MENU_ITEM:LOCKED_FILE_OWNER:d:") + tr("Locked by %1").arg(record._lockstate._lockOwnerDisplayName));
const auto lockExpirationTime = record._lockstate._lockTime + record._lockstate._lockTimeout;
const auto remainingTime = QDateTime::currentDateTime().secsTo(QDateTime::fromSecsSinceEpoch(lockExpirationTime));
const auto remainingTimeInMinute = static_cast<int>(remainingTime > 0 ? remainingTime / SECONDS_PER_MINUTE : 0);
listener->sendMessage(QLatin1String("MENU_ITEM:LOCKED_FILE_DATE:d:") + tr("Expire in %1 minutes", "remaining time before lock expire", remainingTimeInMinute).arg(remainingTimeInMinute));
}
}
SocketApi::FileData SocketApi::FileData::get(const QString &localFile)
{
FileData data;
@ -1133,6 +1192,7 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
auto flagString = isOnTheServer && !isE2eEncryptedPath ? QLatin1String("::") : QLatin1String(":d:");
const QFileInfo fileInfo(fileData.localPath);
sendLockFileInfoMenuEntries(fileInfo, syncFolder, fileData, listener, record);
if (!fileInfo.isDir()) {
listener->sendMessage(QLatin1String("MENU_ITEM:ACTIVITY") + flagString + tr("Activity"));
}
@ -1145,6 +1205,7 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
}
sendLockFileCommandMenuEntries(fileInfo, syncFolder, fileData, listener);
sendSharingContextMenuOptions(fileData, listener, !isE2eEncryptedPath);
// Conflict files get conflict resolution actions

Просмотреть файл

@ -27,6 +27,7 @@
class QUrl;
class QLocalSocket;
class QStringList;
class QFileInfo;
namespace OCC {
@ -124,6 +125,10 @@ private:
Q_INVOKABLE void command_RESOLVE_CONFLICT(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_DELETE_ITEM(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_MOVE_ITEM(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_LOCK_FILE(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_UNLOCK_FILE(const QString &localFile, SocketListener *listener);
void setFileLock(const QString &localFile, const SyncFileItem::LockStatus lockState) const;
// Windows Shell / Explorer pinning fallbacks, see issue: https://github.com/nextcloud/desktop/issues/1599
#ifdef Q_OS_WIN
@ -145,6 +150,17 @@ private:
// Sends the context menu options relating to sharing to listener
void sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener, bool enabled);
void sendLockFileCommandMenuEntries(const QFileInfo &fileInfo,
Folder * const syncFolder,
const FileData &fileData,
const SocketListener * const listener) const;
void sendLockFileInfoMenuEntries(const QFileInfo &fileInfo,
Folder * const syncFolder,
const FileData &fileData,
const SocketListener * const listener,
const SyncJournalFileRecord &record) const;
/** Send the list of menu item. (added in version 1.1)
* argument is a list of files for which the menu should be shown, separated by '\x1e'
* Reply with GET_MENU_ITEMS:BEGIN

Просмотреть файл

@ -25,8 +25,10 @@
#include "pushnotifications.h"
#include "version.h"
#include <deletejob.h>
#include "deletejob.h"
#include "lockfilejobs.h"
#include "common/syncjournaldb.h"
#include "common/asserts.h"
#include "clientsideencryption.h"
#include "ocsuserstatusconnector.h"
@ -113,6 +115,11 @@ AccountPtr Account::sharedFromThis()
return _sharedThis.toStrongRef();
}
AccountPtr Account::sharedFromThis() const
{
return _sharedThis.toStrongRef();
}
QString Account::davUser() const
{
return _davUser.isEmpty() && _credentials ? _credentials->user() : _davUser;
@ -850,4 +857,58 @@ std::shared_ptr<UserStatusConnector> Account::userStatusConnector() const
return _userStatusConnector;
}
void Account::setLockFileState(const QString &serverRelativePath,
SyncJournalDb * const journal,
const SyncFileItem::LockStatus lockStatus)
{
auto job = std::make_unique<LockFileJob>(sharedFromThis(), journal, serverRelativePath, lockStatus);
connect(job.get(), &LockFileJob::finishedWithoutError, this, [this]() {
Q_EMIT lockFileSuccess();
});
connect(job.get(), &LockFileJob::finishedWithError, this, [lockStatus, serverRelativePath, this](const int httpErrorCode, const QString &errorString, const QString &lockOwnerName) {
auto errorMessage = QString{};
const auto filePath = serverRelativePath.mid(1);
if (httpErrorCode == LockFileJob::LOCKED_HTTP_ERROR_CODE) {
errorMessage = tr("File %1 is already locked by %2.").arg(filePath, lockOwnerName);
} else if (lockStatus == SyncFileItem::LockStatus::LockedItem) {
errorMessage = tr("Lock operation on %1 failed with error %2").arg(filePath, errorString);
} else if (lockStatus == SyncFileItem::LockStatus::UnlockedItem) {
errorMessage = tr("Unlock operation on %1 failed with error %2").arg(filePath, errorString);
}
Q_EMIT lockFileError(errorMessage);
});
job->start();
static_cast<void>(job.release());
}
SyncFileItem::LockStatus Account::fileLockStatus(SyncJournalDb * const journal,
const QString &folderRelativePath) const
{
SyncJournalFileRecord record;
if (journal->getFileRecord(folderRelativePath, &record)) {
return record._lockstate._locked ? SyncFileItem::LockStatus::LockedItem : SyncFileItem::LockStatus::UnlockedItem;
}
return SyncFileItem::LockStatus::UnlockedItem;
}
bool Account::fileCanBeUnlocked(SyncJournalDb * const journal,
const QString &folderRelativePath) const
{
SyncJournalFileRecord record;
if (journal->getFileRecord(folderRelativePath, &record)) {
if (record._lockstate._lockOwnerType != static_cast<int>(SyncFileItem::LockOwnerType::UserLock)) {
return false;
}
if (record._lockstate._lockOwnerId != sharedFromThis()->davUser()) {
return false;
}
return true;
}
return false;
}
} // namespace OCC

Просмотреть файл

@ -35,6 +35,7 @@
#include <memory>
#include "capabilities.h"
#include "clientsideencryption.h"
#include "syncfileitem.h"
class QSettings;
class QNetworkReply;
@ -56,6 +57,7 @@ class AccessManager;
class SimpleNetworkJob;
class PushNotifications;
class UserStatusConnector;
class SyncJournalDb;
/**
* @brief Reimplement this to handle SSL errors from libsync
@ -89,6 +91,8 @@ public:
AccountPtr sharedFromThis();
AccountPtr sharedFromThis() const;
/**
* The user that can be used in dav url.
*
@ -275,6 +279,15 @@ public:
std::shared_ptr<UserStatusConnector> userStatusConnector() const;
void setLockFileState(const QString &serverRelativePath,
SyncJournalDb * const journal,
const SyncFileItem::LockStatus lockStatus);
SyncFileItem::LockStatus fileLockStatus(SyncJournalDb * const journal,
const QString &folderRelativePath) const;
bool fileCanBeUnlocked(SyncJournalDb * const journal, const QString &folderRelativePath) const;
public slots:
/// Used when forgetting credentials
void clearQNAMCache();
@ -311,6 +324,9 @@ signals:
void capabilitiesChanged();
void lockFileSuccess();
void lockFileError(const QString&);
protected Q_SLOTS:
void slotCredentialsFetched();
void slotCredentialsAsked();

Просмотреть файл

@ -21,6 +21,113 @@ private slots:
{
}
void testLockFile_lockFile_lockSuccess()
{
const auto testFileName = QStringLiteral("file.txt");
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
fakeFolder.localModifier().insert(testFileName);
QVERIFY(fakeFolder.syncOnce());
fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
QVERIFY(lockFileSuccessSpy.wait());
QCOMPARE(lockFileErrorSpy.count(), 0);
}
void testLockFile_lockFile_lockError()
{
const auto testFileName = QStringLiteral("file.txt");
static constexpr auto LockedHttpErrorCode = 423;
const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
"<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
" <nc:lock/>\n"
" <nc:lock-owner-type>0</nc:lock-owner-type>\n"
" <nc:lock-owner>john</nc:lock-owner>\n"
" <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
" <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
" <nc:lock-time>1650619678</nc:lock-time>\n"
" <nc:lock-timeout>300</nc:lock-timeout>\n"
" <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
"</d:prop>\n");
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
QNetworkReply *reply = nullptr;
if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) {
reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData);
}
return reply;
});
QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
fakeFolder.localModifier().insert(testFileName);
QVERIFY(fakeFolder.syncOnce());
fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
QVERIFY(lockFileErrorSpy.wait());
QCOMPARE(lockFileSuccessSpy.count(), 0);
}
void testLockFile_fileLockStatus_queryLockStatus()
{
const auto testFileName = QStringLiteral("file.txt");
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
fakeFolder.localModifier().insert(testFileName);
QVERIFY(fakeFolder.syncOnce());
fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
QVERIFY(lockFileSuccessSpy.wait());
QCOMPARE(lockFileErrorSpy.count(), 0);
auto lockStatus = fakeFolder.account()->fileLockStatus(&fakeFolder.syncJournal(), testFileName);
QCOMPARE(lockStatus, OCC::SyncFileItem::LockStatus::LockedItem);
}
void testLockFile_fileCanBeUnlocked_canUnlock()
{
const auto testFileName = QStringLiteral("file.txt");
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
fakeFolder.localModifier().insert(testFileName);
QVERIFY(fakeFolder.syncOnce());
fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
QVERIFY(lockFileSuccessSpy.wait());
QCOMPARE(lockFileErrorSpy.count(), 0);
auto lockStatus = fakeFolder.account()->fileCanBeUnlocked(&fakeFolder.syncJournal(), testFileName);
QCOMPARE(lockStatus, true);
}
void testLockFile_lockFile_jobSuccess()
{
const auto testFileName = QStringLiteral("file.txt");

Просмотреть файл

@ -1,242 +0,0 @@
#include "lockfilejobs.h"
#include "account.h"
#include "accountstate.h"
#include "common/syncjournaldb.h"
#include "common/syncjournalfilerecord.h"
#include "syncenginetestutils.h"
#include <QTest>
#include <QSignalSpy>
class TestLockFileJobs : public QObject
{
Q_OBJECT
public:
TestLockFileJobs() = default;
private slots:
void initTestCase()
{
}
void testLockFileJob_lockFile_jobSuccess()
{
const auto testFileName = QStringLiteral("file.txt");
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
fakeFolder.localModifier().insert(testFileName);
QVERIFY(fakeFolder.syncOnce());
auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
job->start();
QVERIFY(jobSuccess.wait());
QCOMPARE(jobFailure.count(), 0);
auto fileRecord = OCC::SyncJournalFileRecord{};
QVERIFY(fakeFolder.syncJournal().getFileRecord(testFileName, &fileRecord));
QCOMPARE(fileRecord._locked, true);
QCOMPARE(fileRecord._lockEditorApp, QString{});
QCOMPARE(fileRecord._lockOwnerDisplayName, QStringLiteral("John Doe"));
QCOMPARE(fileRecord._lockOwnerId, QStringLiteral("john"));
QCOMPARE(fileRecord._lockOwnerType, static_cast<qint64>(OCC::SyncFileItem::LockOwnerType::UserLock));
QCOMPARE(fileRecord._lockTime, 1234560);
QCOMPARE(fileRecord._lockTimeout, 1800);
QVERIFY(fakeFolder.syncOnce());
}
void testLockFileJob_lockFile_unlockFile_jobSuccess()
{
const auto testFileName = QStringLiteral("file.txt");
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
fakeFolder.localModifier().insert(testFileName);
QVERIFY(fakeFolder.syncOnce());
auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError);
lockFileJob->start();
QVERIFY(lockFileJobSuccess.wait());
QCOMPARE(lockFileJobFailure.count(), 0);
QVERIFY(fakeFolder.syncOnce());
auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError);
unlockFileJob->start();
QVERIFY(unlockFileJobSuccess.wait());
QCOMPARE(unlockFileJobFailure.count(), 0);
auto fileRecord = OCC::SyncJournalFileRecord{};
QVERIFY(fakeFolder.syncJournal().getFileRecord(testFileName, &fileRecord));
QCOMPARE(fileRecord._locked, false);
QVERIFY(fakeFolder.syncOnce());
}
void testLockFileJob_lockFile_alreadyLocked()
{
static constexpr auto LockedHttpErrorCode = 423;
static constexpr auto PreconditionFailedHttpErrorCode = 412;
const auto testFileName = QStringLiteral("file.txt");
const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
"<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
" <nc:lock>1</nc:lock>\n"
" <nc:lock-owner-type>0</nc:lock-owner-type>\n"
" <nc:lock-owner>john</nc:lock-owner>\n"
" <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
" <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
" <nc:lock-time>1650619678</nc:lock-time>\n"
" <nc:lock-timeout>300</nc:lock-timeout>\n"
" <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
"</d:prop>\n");
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
QNetworkReply *reply = nullptr;
if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) {
reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData);
} else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) {
reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData);
}
return reply;
});
fakeFolder.localModifier().insert(testFileName);
QVERIFY(fakeFolder.syncOnce());
auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
job->start();
QVERIFY(jobFailure.wait());
QCOMPARE(jobSuccess.count(), 0);
}
void testLockFileJob_unlockFile_alreadyUnlocked()
{
static constexpr auto LockedHttpErrorCode = 423;
static constexpr auto PreconditionFailedHttpErrorCode = 412;
const auto testFileName = QStringLiteral("file.txt");
const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
"<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
" <nc:lock/>\n"
" <nc:lock-owner-type>0</nc:lock-owner-type>\n"
" <nc:lock-owner>john</nc:lock-owner>\n"
" <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
" <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
" <nc:lock-time>1650619678</nc:lock-time>\n"
" <nc:lock-timeout>300</nc:lock-timeout>\n"
" <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
"</d:prop>\n");
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
QNetworkReply *reply = nullptr;
if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) {
reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData);
} else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) {
reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData);
}
return reply;
});
fakeFolder.localModifier().insert(testFileName);
QVERIFY(fakeFolder.syncOnce());
auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
job->start();
QVERIFY(jobFailure.wait());
QCOMPARE(jobSuccess.count(), 0);
}
void testLockFileJob_lockFile_jobError()
{
const auto testFileName = QStringLiteral("file.txt");
static constexpr auto InternalServerErrorHttpErrorCode = 500;
FakeFolder fakeFolder{FileInfo{}};
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
fakeFolder.setServerOverride([] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
QNetworkReply *reply = nullptr;
if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) {
reply = new FakeErrorReply(op, request, nullptr, InternalServerErrorHttpErrorCode, {});
} else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) {
reply = new FakeErrorReply(op, request, nullptr, InternalServerErrorHttpErrorCode, {});
}
return reply;
});
fakeFolder.localModifier().insert(QStringLiteral("file.txt"));
QVERIFY(fakeFolder.syncOnce());
auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError);
lockFileJob->start();
QVERIFY(lockFileJobFailure.wait());
QCOMPARE(lockFileJobSuccess.count(), 0);
QVERIFY(fakeFolder.syncOnce());
auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError);
unlockFileJob->start();
QVERIFY(unlockFileJobFailure.wait());
QCOMPARE(unlockFileJobSuccess.count(), 0);
QVERIFY(fakeFolder.syncOnce());
}
};
QTEST_MAIN(TestLockFileJobs)
#include "testlockfilejobs.moc"