From 128648a7354286f89a9818eb2ab4e1921ebb3512 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Thu, 17 Aug 2023 18:14:41 +0800 Subject: [PATCH 1/8] Modernise FolderWatcher::changeDetected Signed-off-by: Claudio Cambra --- src/gui/folderwatcher.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 44b56d391c..868d85370c 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -160,7 +160,7 @@ void FolderWatcher::changeDetected(const QStringList &paths) // - why do we skip the file altogether instead of e.g. reducing the upload frequency? // Check if the same path was reported within the last second. - QSet pathsSet = paths.toSet(); + const auto pathsSet = paths.toSet(); if (pathsSet == _lastPaths && _timer.elapsed() < 1000) { // the same path was reported within the last second. Skip. return; @@ -172,8 +172,7 @@ void FolderWatcher::changeDetected(const QStringList &paths) QSet unlockedFiles; QSet lockedFiles; - for (int i = 0; i < paths.size(); ++i) { - QString path = paths[i]; + for (const auto &path : paths) { if (!_testNotificationPath.isEmpty() && Utility::fileNamesEqual(path, _testNotificationPath)) { _testNotificationPath.clear(); @@ -194,7 +193,7 @@ void FolderWatcher::changeDetected(const QStringList &paths) qCDebug(lcFolderWatcher) << "Locked files:" << lockedFiles.values(); - // ------- handle ignores: + // ------- handle ignores: if (pathIsIgnored(path)) { continue; } @@ -215,7 +214,7 @@ void FolderWatcher::changeDetected(const QStringList &paths) } qCInfo(lcFolderWatcher) << "Detected changes in paths:" << changedPaths; - foreach (const QString &path, changedPaths) { + for (const auto &path : changedPaths) { emit pathChanged(path); } } From 5d0d7a3c9f3bc7277526feceafcc45036af754ad Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 18 Aug 2023 16:43:50 +0800 Subject: [PATCH 2/8] Split off lock file pat pattern matching into separate function Signed-off-by: Claudio Cambra --- src/gui/folderwatcher.cpp | 54 ++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 868d85370c..51c878ea95 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -42,6 +42,31 @@ namespace { const char *lockFilePatterns[] = {".~lock.", "~$"}; + +QString filePathLockFilePatternMatch(const QString &path) +{ + qCDebug(OCC::lcFolderWatcher) << "Checking if it is a lock file:" << path; + + const auto pathSplit = path.split(QLatin1Char('/'), Qt::SkipEmptyParts); + if (pathSplit.isEmpty()) { + return {}; + } + QString lockFilePatternFound; + for (const auto &lockFilePattern : lockFilePatterns) { + if (pathSplit.last().startsWith(lockFilePattern)) { + lockFilePatternFound = lockFilePattern; + break; + } + } + + if (lockFilePatternFound.isEmpty()) { + return {}; + } + + qCDebug(OCC::lcFolderWatcher) << "Found a lock file with prefix:" << lockFilePatternFound << "in path:" << path; + return lockFilePatternFound; +} + } namespace OCC { @@ -226,29 +251,15 @@ void FolderWatcher::folderAccountCapabilitiesChanged() FolderWatcher::FileLockingInfo FolderWatcher::checkIfFileIsLockOrUnlock(const QString &path) const { - qCDebug(lcFolderWatcher) << "Checking if it is a lock file:" << path; - FileLockingInfo result; - const auto pathSplit = path.split(QLatin1Char('/'), Qt::SkipEmptyParts); - if (pathSplit.isEmpty()) { - return result; - } - QString lockFilePatternFound; - for (const auto &lockFilePattern : lockFilePatterns) { - if (pathSplit.last().startsWith(lockFilePattern)) { - lockFilePatternFound = lockFilePattern; - break; - } - } + const auto lockFilePatternFound = filePathLockFilePatternMatch(path); if (lockFilePatternFound.isEmpty()) { return result; } - qCDebug(lcFolderWatcher) << "Found a lock file with prefix:" << lockFilePatternFound << "in path:" << path; - - const auto lockFilePathWitoutPrefix = QString(path).replace(lockFilePatternFound, QStringLiteral("")); - auto lockFilePathWithoutPrefixSplit = lockFilePathWitoutPrefix.split(QLatin1Char('.')); + const auto lockFilePathWithoutPrefix = QString(path).replace(lockFilePatternFound, QStringLiteral("")); + auto lockFilePathWithoutPrefixSplit = lockFilePathWithoutPrefix.split(QLatin1Char('.')); if (lockFilePathWithoutPrefixSplit.size() < 2) { return result; @@ -264,10 +275,10 @@ FolderWatcher::FileLockingInfo FolderWatcher::checkIfFileIsLockOrUnlock(const QS ); lockFilePathWithoutPrefixSplit.push_back(QString::fromStdString(extensionSanitized)); - const auto lockFilePathWithoutPrefix = lockFilePathWithoutPrefixSplit.join(QLatin1Char('.')); + const auto lockFilePathWithoutPrefixNew = lockFilePathWithoutPrefixSplit.join(QLatin1Char('.')); - qCDebug(lcFolderWatcher) << "Assumed locked/unlocked file path" << lockFilePathWithoutPrefix << "Going to try to find matching file"; - auto splitFilePath = lockFilePathWithoutPrefix.split(QLatin1Char('/')); + qCDebug(lcFolderWatcher) << "Assumed locked/unlocked file path" << lockFilePathWithoutPrefixNew << "Going to try to find matching file"; + auto splitFilePath = lockFilePathWithoutPrefixNew.split(QLatin1Char('/')); if (splitFilePath.size() > 1) { const auto lockFileNameWithoutPrefix = splitFilePath.takeLast(); // some software will modify lock file name such that it does not correspond to original file (removing some symbols from the name, so we will search @@ -287,7 +298,8 @@ QString FolderWatcher::findMatchingUnlockedFileInDir(const QString &dirPath, con { QString foundFilePath; const QDir dir(dirPath); - for (const auto &candidateUnlockedFileInfo : dir.entryInfoList(QDir::Files)) { + const auto entryList = dir.entryInfoList(QDir::Files); + for (const auto &candidateUnlockedFileInfo : entryList) { if (candidateUnlockedFileInfo.fileName().contains(lockFileName)) { foundFilePath = candidateUnlockedFileInfo.absoluteFilePath(); break; From da62e6615dfcc1ac3cd0dfcd696ea23eec93c552 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 18 Aug 2023 16:58:28 +0800 Subject: [PATCH 3/8] Define private lock file patterns as const std::array Signed-off-by: Claudio Cambra --- src/gui/folderwatcher.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 51c878ea95..74f716a7cf 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -19,15 +19,6 @@ #include "account.h" #include "capabilities.h" -#include - -#include -#include -#include -#include -#include -#include - #if defined(Q_OS_WIN) #include "folderwatcher_win.h" #elif defined(Q_OS_MAC) @@ -39,9 +30,19 @@ #include "folder.h" #include "filesystem.h" +#include +#include +#include +#include +#include +#include + +#include +#include + namespace { -const char *lockFilePatterns[] = {".~lock.", "~$"}; +const std::array lockFilePatterns = {".~lock.", "~$"}; QString filePathLockFilePatternMatch(const QString &path) { From f5d15d8a8251f1ecf37972ba3bebd56b74060b3f Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 18 Aug 2023 17:02:16 +0800 Subject: [PATCH 4/8] Do not search for matching file pattern in possiblyAddUnlockedFilePath Signed-off-by: Claudio Cambra --- src/gui/folderwatcher.cpp | 15 +++++++++------ src/gui/folderwatcher.h | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 74f716a7cf..3e93fa6741 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -203,9 +203,13 @@ void FolderWatcher::changeDetected(const QStringList &paths) && Utility::fileNamesEqual(path, _testNotificationPath)) { _testNotificationPath.clear(); } - - const auto checkResult = checkIfFileIsLockOrUnlock(path); + + const auto lockFileNamePattern = filePathLockFilePatternMatch(path); + const auto checkResult = checkIfFileIsLockOrUnlock(path,lockFileNamePattern); if (_shouldWatchForFileUnlocking) { + const auto lockFileNamePattern = filePathLockFilePatternMatch(path); + const auto unlockedFilePath = checkIfFileIsLockOrUnlock(path, lockFileNamePattern); + if (checkResult.type == FileLockingInfo::Type::Unlocked && !checkResult.path.isEmpty()) { unlockedFiles.insert(checkResult.path); } @@ -250,16 +254,15 @@ void FolderWatcher::folderAccountCapabilitiesChanged() _shouldWatchForFileUnlocking = _folder->accountState()->account()->capabilities().filesLockAvailable(); } -FolderWatcher::FileLockingInfo FolderWatcher::checkIfFileIsLockOrUnlock(const QString &path) const +FolderWatcher::FileLockingInfo FolderWatcher::checkIfFileIsLockOrUnlock(const QString &path, const QString &lockFileNamePattern) const { FileLockingInfo result; - const auto lockFilePatternFound = filePathLockFilePatternMatch(path); - if (lockFilePatternFound.isEmpty()) { + if (lockFileNamePattern.isEmpty()) { return result; } - const auto lockFilePathWithoutPrefix = QString(path).replace(lockFilePatternFound, QStringLiteral("")); + const auto lockFilePathWithoutPrefix = QString(path).replace(lockFileNamePattern, QStringLiteral("")); auto lockFilePathWithoutPrefixSplit = lockFilePathWithoutPrefix.split(QLatin1Char('.')); if (lockFilePathWithoutPrefixSplit.size() < 2) { diff --git a/src/gui/folderwatcher.h b/src/gui/folderwatcher.h index f2d59f703d..41fd86ee1d 100644 --- a/src/gui/folderwatcher.h +++ b/src/gui/folderwatcher.h @@ -141,7 +141,7 @@ private: void appendSubPaths(QDir dir, QStringList& subPaths); - [[nodiscard]] FileLockingInfo checkIfFileIsLockOrUnlock(const QString &path) const; + [[nodiscard]] FileLockingInfo checkIfFileIsLockOrUnlock(const QString &path, const QString &lockFileNamePattern) const; [[nodiscard]] QString findMatchingUnlockedFileInDir(const QString &dirPath, const QString &lockFileName) const; /* Check if the path should be igored by the FolderWatcher. */ From 859bf078733fc7d4688b437ab2c25dd59bb73a41 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 18 Aug 2023 17:04:11 +0800 Subject: [PATCH 5/8] Notify when new lock files are found in folderwatcher Signed-off-by: Claudio Cambra --- src/gui/folderwatcher.cpp | 15 ++++++++++----- src/gui/folderwatcher.h | 2 ++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 3e93fa6741..f971b7139a 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -197,6 +197,7 @@ void FolderWatcher::changeDetected(const QStringList &paths) QSet changedPaths; QSet unlockedFiles; QSet lockedFiles; + QSet lockFiles; for (const auto &path : paths) { if (!_testNotificationPath.isEmpty() @@ -207,14 +208,11 @@ void FolderWatcher::changeDetected(const QStringList &paths) const auto lockFileNamePattern = filePathLockFilePatternMatch(path); const auto checkResult = checkIfFileIsLockOrUnlock(path,lockFileNamePattern); if (_shouldWatchForFileUnlocking) { - const auto lockFileNamePattern = filePathLockFilePatternMatch(path); - const auto unlockedFilePath = checkIfFileIsLockOrUnlock(path, lockFileNamePattern); - if (checkResult.type == FileLockingInfo::Type::Unlocked && !checkResult.path.isEmpty()) { unlockedFiles.insert(checkResult.path); + } else if (!lockFileNamePattern.isEmpty()) { + lockFiles.insert(path); } - - qCDebug(lcFolderWatcher) << "Unlocked files:" << unlockedFiles.values(); } if (checkResult.type == FileLockingInfo::Type::Locked && !checkResult.path.isEmpty()) { @@ -231,6 +229,9 @@ void FolderWatcher::changeDetected(const QStringList &paths) changedPaths.insert(path); } + qCDebug(lcFolderWatcher) << "Unlocked files:" << unlockedFiles.values(); + qCDebug(lcFolderWatcher) << "Lock files:" << lockFiles; + if (!unlockedFiles.isEmpty()) { emit filesLockReleased(unlockedFiles); } @@ -239,6 +240,10 @@ void FolderWatcher::changeDetected(const QStringList &paths) emit filesLockImposed(lockedFiles); } + if (!lockFiles.isEmpty()) { + emit lockFilesFound(lockFiles); + } + if (changedPaths.isEmpty()) { return; } diff --git a/src/gui/folderwatcher.h b/src/gui/folderwatcher.h index 41fd86ee1d..0cfd0bd804 100644 --- a/src/gui/folderwatcher.h +++ b/src/gui/folderwatcher.h @@ -102,6 +102,8 @@ signals: */ void filesLockImposed(const QSet &files); + void lockFilesFound(const QSet &files); + /** * Emitted if some notifications were lost. * From 65569e5486cf2e366328b0bad909444653deb2b6 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 18 Aug 2023 18:35:04 +0800 Subject: [PATCH 6/8] Emit lock file target file paths in lock file discovery from folder watcher Signed-off-by: Claudio Cambra --- src/gui/folderwatcher.cpp | 18 +++++++++--------- src/gui/folderwatcher.h | 6 +++++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index f971b7139a..c56d004417 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -197,7 +197,6 @@ void FolderWatcher::changeDetected(const QStringList &paths) QSet changedPaths; QSet unlockedFiles; QSet lockedFiles; - QSet lockFiles; for (const auto &path : paths) { if (!_testNotificationPath.isEmpty() @@ -206,12 +205,13 @@ void FolderWatcher::changeDetected(const QStringList &paths) } const auto lockFileNamePattern = filePathLockFilePatternMatch(path); - const auto checkResult = checkIfFileIsLockOrUnlock(path,lockFileNamePattern); + const auto checkResult = lockFileTargetFilePath(path,lockFileNamePattern); if (_shouldWatchForFileUnlocking) { - if (checkResult.type == FileLockingInfo::Type::Unlocked && !checkResult.path.isEmpty()) { + // Lock file has been deleted, file now unlocked + if (checkResult.type == FileLockingInfo::Type::Unlocked && !checkResult.path.isEmpty() && !QFile::exists(path)) { unlockedFiles.insert(checkResult.path); - } else if (!lockFileNamePattern.isEmpty()) { - lockFiles.insert(path); + } else if (!checkResult.path.isEmpty() && QFile::exists(path)) { // Lock file found + lockedFiles.insert(checkResult.path); } } @@ -230,7 +230,7 @@ void FolderWatcher::changeDetected(const QStringList &paths) } qCDebug(lcFolderWatcher) << "Unlocked files:" << unlockedFiles.values(); - qCDebug(lcFolderWatcher) << "Lock files:" << lockFiles; + qCDebug(lcFolderWatcher) << "Locked files:" << lockedFiles; if (!unlockedFiles.isEmpty()) { emit filesLockReleased(unlockedFiles); @@ -240,8 +240,8 @@ void FolderWatcher::changeDetected(const QStringList &paths) emit filesLockImposed(lockedFiles); } - if (!lockFiles.isEmpty()) { - emit lockFilesFound(lockFiles); + if (!lockedFiles.isEmpty()) { + emit lockedFilesFound(lockedFiles); } if (changedPaths.isEmpty()) { @@ -259,7 +259,7 @@ void FolderWatcher::folderAccountCapabilitiesChanged() _shouldWatchForFileUnlocking = _folder->accountState()->account()->capabilities().filesLockAvailable(); } -FolderWatcher::FileLockingInfo FolderWatcher::checkIfFileIsLockOrUnlock(const QString &path, const QString &lockFileNamePattern) const +FolderWatcher::FileLockingInfo FolderWatcher::lockFileTargetFilePath(const QString &path, const QString &lockFileNamePattern) const { FileLockingInfo result; diff --git a/src/gui/folderwatcher.h b/src/gui/folderwatcher.h index 0cfd0bd804..4e2c569b98 100644 --- a/src/gui/folderwatcher.h +++ b/src/gui/folderwatcher.h @@ -104,6 +104,8 @@ signals: void lockFilesFound(const QSet &files); + void lockedFilesFound(const QSet &files); + /** * Emitted if some notifications were lost. * @@ -143,9 +145,11 @@ private: void appendSubPaths(QDir dir, QStringList& subPaths); - [[nodiscard]] FileLockingInfo checkIfFileIsLockOrUnlock(const QString &path, const QString &lockFileNamePattern) const; + [[nodiscard]] FileLockingInfo lockFileTargetFilePath(const QString &path, const QString &lockFileNamePattern) const; [[nodiscard]] QString findMatchingUnlockedFileInDir(const QString &dirPath, const QString &lockFileName) const; + QString findMatchingUnlockedFileInDir(const QString &dirPath, const QString &lockFileName); + /* Check if the path should be igored by the FolderWatcher. */ [[nodiscard]] bool pathIsIgnored(const QString &path) const; From 2503ded168cd12a100bed7d3d39b579df37ce60b Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 18 Aug 2023 19:12:16 +0800 Subject: [PATCH 7/8] Lock files when relevant lock files found by folder watcher Signed-off-by: Claudio Cambra --- src/gui/folder.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/gui/folder.h | 4 ++++ 2 files changed, 40 insertions(+) diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 92b05d269b..29a9f46bcc 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -682,6 +682,40 @@ void Folder::slotFilesLockImposed(const QSet &files) } } +void Folder::slotLockedFilesFound(const QSet &files) +{ + qCDebug(lcFolder) << "Found new lock files" << files; + + for (const auto &file : files) { + const auto fileRecordPath = fileFromLocalPath(file); + SyncJournalFileRecord rec; + + const auto canLockFile = journalDb()->getFileRecord(fileRecordPath, &rec) && rec.isValid() + && (!rec._lockstate._locked + || (rec._lockstate._lockOwnerType == static_cast(SyncFileItem::LockOwnerType::UserLock) + && rec._lockstate._lockOwnerId == _accountState->account()->davUser())); + + if (!canLockFile) { + qCDebug(lcFolder) << "Skipping locking file" << file << "with rec.isValid():" << rec.isValid() + << "and rec._lockstate._lockOwnerId:" << rec._lockstate._lockOwnerId << "and davUser:" << _accountState->account()->davUser(); + continue; + } + + const QString remoteFilePath = remotePathTrailingSlash() + rec.path(); + qCDebug(lcFolder) << "Automatically locking file on server" << remoteFilePath; + _fileLockSuccess = connect(_accountState->account().data(), &Account::lockFileSuccess, this, [this, remoteFilePath] { + disconnect(_fileLockSuccess); + qCDebug(lcFolder) << "Locking file succeeded" << remoteFilePath; + startSync(); + }); + _fileLockFailure = connect(_accountState->account().data(), &Account::lockFileError, this, [this, remoteFilePath](const QString &message) { + disconnect(_fileLockFailure); + qCWarning(lcFolder) << "Failed to lock a file:" << remoteFilePath << message; + }); + _accountState->account()->setLockFileState(remoteFilePath, journalDb(), SyncFileItem::LockStatus::LockedItem); + } +} + void Folder::implicitlyHydrateFile(const QString &relativepath) { qCInfo(lcFolder) << "Implicitly hydrate virtual file:" << relativepath; @@ -1475,6 +1509,7 @@ void Folder::slotCapabilitiesChanged() { if (_accountState->account()->capabilities().filesLockAvailable()) { connect(_folderWatcher.data(), &FolderWatcher::filesLockReleased, this, &Folder::slotFilesLockReleased, Qt::UniqueConnection); + connect(_folderWatcher.data(), &FolderWatcher::lockedFilesFound, this, &Folder::slotLockedFilesFound, Qt::UniqueConnection); } } @@ -1520,6 +1555,7 @@ void Folder::registerFolderWatcher() this, &Folder::slotWatcherUnreliable); if (_accountState->account()->capabilities().filesLockAvailable()) { connect(_folderWatcher.data(), &FolderWatcher::filesLockReleased, this, &Folder::slotFilesLockReleased); + connect(_folderWatcher.data(), &FolderWatcher::lockedFilesFound, this, &Folder::slotLockedFilesFound); } connect(_folderWatcher.data(), &FolderWatcher::filesLockImposed, this, &Folder::slotFilesLockImposed, Qt::UniqueConnection); _folderWatcher->init(path()); diff --git a/src/gui/folder.h b/src/gui/folder.h index edc8484b4c..f0622f48c1 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -359,6 +359,8 @@ public slots: */ void slotFilesLockImposed(const QSet &files); + void slotLockedFilesFound(const QSet &files); + /** * Mark a virtual file as being requested for download, and start a sync. * @@ -562,6 +564,8 @@ private: QMetaObject::Connection _officeFileLockReleaseUnlockSuccess; QMetaObject::Connection _officeFileLockReleaseUnlockFailure; + QMetaObject::Connection _fileLockSuccess; + QMetaObject::Connection _fileLockFailure; }; } From 2b6580a5a9ab638a4d27faafd0e8226385bfd27d Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 30 Aug 2023 23:39:49 +0800 Subject: [PATCH 8/8] Fix declaration of lockfilepatterns Signed-off-by: Claudio Cambra --- src/gui/folderwatcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index c56d004417..0dd183f00c 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -42,7 +42,7 @@ namespace { -const std::array lockFilePatterns = {".~lock.", "~$"}; +const std::array lockFilePatterns = {{".~lock.", "~$"}}; QString filePathLockFilePatternMatch(const QString &path) {