зеркало из https://github.com/nextcloud/desktop.git
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:
Родитель
2ea68d75bd
Коммит
b55a099b61
|
@ -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"
|
Загрузка…
Ссылка в новой задаче