diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index b92cfa08e43ca..99a4fa75ab892 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -27,6 +27,9 @@ #include #include #include +#include + +Q_LOGGING_CATEGORY(lcCsyncExclude, "nextcloud.csync.exclude", QtInfoMsg) /** Expands C-like escape sequences (in place) */ @@ -163,10 +166,16 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QString &path, bool exclu // not allow to sync those to avoid file loss/ambiguities (#416) if (blen > 1) { if (bname.at(blen - 1) == QLatin1Char('.')) { + return CSYNC_FILE_EXCLUDE_INVALID_CHAR; } } + if (OCC::FileSystem::isFileLocked(path, OCC::FileSystem::LockMode::SharedRead)) { + qCWarning(lcCsyncExclude) << path << "is locked" << "exluding it from sync"; + return CSYNC_FILE_SILENTLY_EXCLUDED; + } + if (csync_is_windows_reserved_word(bname)) { return CSYNC_FILE_SILENTLY_EXCLUDED; } diff --git a/src/csync/csync_exclude.h b/src/csync/csync_exclude.h index 5e4c9358d28a4..355fb74fd83d5 100644 --- a/src/csync/csync_exclude.h +++ b/src/csync/csync_exclude.h @@ -37,6 +37,7 @@ enum CSYNC_EXCLUDE_TYPE { CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED, CSYNC_FILE_EXCLUDE_LEADING_SPACE, CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE, + CSYNC_FILE_LOCKED_SILENTLY_EXCLUDED, }; class ExcludedFilesTest; diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 55e217a0c47f3..074c87ee42895 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -1001,6 +1001,11 @@ bool Folder::pathIsIgnored(const QString &path) const return true; } + if (OCC::FileSystem::isFileLocked(path, OCC::FileSystem::LockMode::SharedRead)) { + qCDebug(lcFolder) << path << "is locked" << "skip syncing it"; + return true; + } + #ifndef OWNCLOUD_TEST if (isFileExcludedAbsolute(path) && !Utility::isConflictFile(path)) { qCDebug(lcFolder) << "* Ignoring file" << path; diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 103518e8a7f68..1982e3d7adec6 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -354,6 +354,14 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent } } + if (excluded == CSYNC_NOT_EXCLUDED && OCC::FileSystem::isFileLocked(_discoveryData->_localDir + path, OCC::FileSystem::LockMode::SharedRead) && + (!entries.dbEntry.isValid() || !entries.serverEntry.isValid() || entries.serverEntry.etag == entries.dbEntry._etag)) { + qCInfo(lcDisco) << _discoveryData->_localDir + path << "is locked" << "exluding it from sync"; + excluded = CSYNC_FILE_LOCKED_SILENTLY_EXCLUDED; + + emit _discoveryData->seenLockedFile(_discoveryData->_localDir + path); + } + if (excluded == CSYNC_NOT_EXCLUDED && !entries.localEntry.isSymLink) { return false; } else if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED || excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) { @@ -384,6 +392,9 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent case CSYNC_FILE_EXCLUDE_AND_REMOVE: qCFatal(lcDisco) << "These were handled earlier"; break; + case CSYNC_FILE_LOCKED_SILENTLY_EXCLUDED: + item->_errorString = tr("File is locked exluding it from sync."); + break; case CSYNC_FILE_EXCLUDE_LIST: item->_errorString = tr("File is listed on the ignore list."); break; @@ -2456,6 +2467,7 @@ bool ProcessDirectoryJob::maybeRenameForWindowsCompatibility(const QString &abso switch (excludeReason) { case CSYNC_NOT_EXCLUDED: + case CSYNC_FILE_LOCKED_SILENTLY_EXCLUDED: case CSYNC_FILE_EXCLUDE_CASE_CLASH_CONFLICT: case CSYNC_FILE_EXCLUDE_AND_REMOVE: case CSYNC_FILE_EXCLUDE_CANNOT_ENCODE: diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index e54cecf566a68..5c076afd54aed 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -345,6 +345,9 @@ class DiscoveryPhase : public QObject void addErrorToGui(const OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject, const OCC::ErrorCategory category); void remnantReadOnlyFolderDiscovered(const OCC::SyncFileItemPtr &item); + + /** Emitted when propagation would have problems with a locked file. */ + void seenLockedFile(const QString &fileName); private slots: void slotItemDiscovered(const OCC::SyncFileItemPtr &item); }; diff --git a/src/libsync/localdiscoverytracker.cpp b/src/libsync/localdiscoverytracker.cpp index ba8fca0ec7565..c7abfe4277d92 100644 --- a/src/libsync/localdiscoverytracker.cpp +++ b/src/libsync/localdiscoverytracker.cpp @@ -55,7 +55,6 @@ void LocalDiscoveryTracker::slotItemCompleted(const SyncFileItemPtr &item) // For failures, we want to add the file to the list so the next sync // will be able to retry it. if (item->_status == SyncFileItem::Success - || item->_status == SyncFileItem::FileIgnored || item->_status == SyncFileItem::Restoration || item->_status == SyncFileItem::Conflict || (item->_status == SyncFileItem::NoStatus diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp index 261b6e5dd3820..0582611616cf6 100644 --- a/src/libsync/propagateuploadng.cpp +++ b/src/libsync/propagateuploadng.cpp @@ -355,15 +355,19 @@ void PropagateUploadFileNG::startNextChunk() } const auto fileName = _fileToUpload._path; + if (FileSystem::isFileLocked(fileName, FileSystem::LockMode::SharedRead)) { + // If the file is currently locked, we want to retry the sync + // when it becomes available again. + emit propagator()->seenLockedFile(fileName); + + // Soft error because this is likely caused by the user modifying his files while syncing + abortWithError(SyncFileItem::FileLocked, tr("File is locked preventing syncing it", "Generic warning message when a locked file cannot be synced")); + return; + } auto device = std::make_unique(fileName, _sent, _currentChunkSize, &propagator()->_bandwidthManager); - if (auto isLocked = FileSystem::isFileLocked(fileName, FileSystem::LockMode::SharedRead); isLocked || !device->open(QIODevice::ReadOnly)) { + if (!device->open(QIODevice::ReadOnly)) { qCWarning(lcPropagateUploadNG) << "Could not prepare upload device: " << device->errorString(); - // If the file is currently locked, we want to retry the sync - // when it becomes available again. - if (isLocked) { - emit propagator()->seenLockedFile(fileName); - } // Soft error because this is likely caused by the user modifying his files while syncing abortWithError(SyncFileItem::SoftError, device->errorString()); return; diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp index c6ac24d995e8d..ac6b97426bf7c 100644 --- a/src/libsync/propagateuploadv1.cpp +++ b/src/libsync/propagateuploadv1.cpp @@ -134,16 +134,19 @@ void PropagateUploadFileV1::startNextChunk() } const QString fileName = _fileToUpload._path; + if (FileSystem::isFileLocked(fileName, FileSystem::LockMode::SharedRead)) { + emit propagator()->seenLockedFile(fileName); + + // Soft error because this is likely caused by the user modifying his files while syncing + abortWithError(SyncFileItem::FileLocked, tr("File is locked preventing syncing it", "Generic warning message when a locked file cannot be synced")); + return; + } + auto device = std::make_unique( fileName, chunkStart, currentChunkSize, &propagator()->_bandwidthManager); - if (auto isLocked = FileSystem::isFileLocked(fileName, FileSystem::LockMode::SharedRead); isLocked || !device->open(QIODevice::ReadOnly)) { + if (!device->open(QIODevice::ReadOnly)) { qCWarning(lcPropagateUploadV1) << "Could not prepare upload device: " << device->errorString(); - // If the file is currently locked, we want to retry the sync - // when it becomes available again. - if (isLocked) { - emit propagator()->seenLockedFile(fileName); - } // Soft error because this is likely caused by the user modifying his files while syncing abortWithError(SyncFileItem::SoftError, device->errorString()); return; diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 1afd7cff4031b..8c754d3eac718 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -708,6 +708,7 @@ void SyncEngine::startSync() connect(_discoveryPhase.get(), &DiscoveryPhase::silentlyExcluded, _syncFileStatusTracker.data(), &SyncFileStatusTracker::slotAddSilentlyExcluded); connect(_discoveryPhase.get(), &DiscoveryPhase::remnantReadOnlyFolderDiscovered, this, &SyncEngine::remnantReadOnlyFolderDiscovered); + connect(_discoveryPhase.get(), &DiscoveryPhase::seenLockedFile, this, &SyncEngine::seenLockedFile); ProcessDirectoryJob *discoveryJob = nullptr; diff --git a/test/testlockedfiles.cpp b/test/testlockedfiles.cpp index 2bd9cae1c3348..71e485695e1e6 100644 --- a/test/testlockedfiles.cpp +++ b/test/testlockedfiles.cpp @@ -137,7 +137,7 @@ private slots: fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(fakeFolder.syncOnce()); QVERIFY(seenLockedFiles.contains(fakeFolder.localPath() + "A/a1")); QVERIFY(seenLockedFiles.size() == 1);