Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions src/libsync/discovery.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2018 ownCloud GmbH
Expand Down Expand Up @@ -28,10 +28,10 @@

namespace
{
constexpr const char *editorNamesForDelayedUpload[] = {"PowerPDF"};

Check warning on line 31 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "std::array" or "std::vector" instead of a C-style array.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yL_&open=AZ4EVJ8Lv7_SaclY-yL_&pullRequest=10001
constexpr const char *fileExtensionsToCheckIfOpenForSigning[] = {".pdf"};

Check warning on line 32 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "std::array" or "std::vector" instead of a C-style array.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMA&open=AZ4EVJ8Lv7_SaclY-yMA&pullRequest=10001

Check warning on line 32 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMB&open=AZ4EVJ8Lv7_SaclY-yMB&pullRequest=10001
constexpr auto delayIntervalForSyncRetryForOpenedForSigningFilesSeconds = 60;

Check warning on line 33 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMC&open=AZ4EVJ8Lv7_SaclY-yMC&pullRequest=10001
constexpr auto delayIntervalForSyncRetryForFilesExceedQuotaSeconds = 60;

Check warning on line 34 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMD&open=AZ4EVJ8Lv7_SaclY-yMD&pullRequest=10001
}

namespace OCC {
Expand Down Expand Up @@ -61,7 +61,7 @@
computePinState(parent->_pinState);
}

ProcessDirectoryJob::ProcessDirectoryJob(DiscoveryPhase *data, PinState basePinState, const PathTuple &path, const SyncFileItemPtr &dirItem, const SyncFileItemPtr &parentDirItem, QueryMode queryLocal, qint64 lastSyncTimestamp, QObject *parent)

Check warning on line 64 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This function has 8 parameters, which is greater than the 7 authorized.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yME&open=AZ4EVJ8Lv7_SaclY-yME&pullRequest=10001
: QObject(parent)
, _dirItem(dirItem)
, _dirParentItem(parentDirItem)
Expand Down Expand Up @@ -232,7 +232,7 @@

const auto willBeExcluded = handleExcluded(path._target, e, entries, isHidden, isBlacklisted);

if (willBeExcluded) {

Check warning on line 235 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "willBeExcluded" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMF&open=AZ4EVJ8Lv7_SaclY-yMF&pullRequest=10001
continue;
}

Expand Down Expand Up @@ -277,11 +277,11 @@
|| excluded == CSYNC_FILE_EXCLUDE_TRAILING_SPACE
|| excluded == CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE;

const auto leadingAndTrailingSpacesFilesAllowed = !_discoveryData->_shouldEnforceWindowsFileNameCompatibility || _discoveryData->_leadingAndTrailingSpacesFilesAllowed.contains(_discoveryData->_localDir + path);

Check warning on line 280 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMJ&open=AZ4EVJ8Lv7_SaclY-yMJ&pullRequest=10001
#if defined Q_OS_WINDOWS
if (hasLeadingOrTrailingSpaces && leadingAndTrailingSpacesFilesAllowed) {
#else
if (hasLeadingOrTrailingSpaces && (wasSyncedAlready || leadingAndTrailingSpacesFilesAllowed)) {

Check warning on line 284 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "leadingAndTrailingSpacesFilesAllowed" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMG&open=AZ4EVJ8Lv7_SaclY-yMG&pullRequest=10001
#endif
excluded = CSYNC_NOT_EXCLUDED;
}
Expand Down Expand Up @@ -404,7 +404,7 @@
} else {
char invalid = '\0';
constexpr QByteArrayView invalidChars("\\:?*\"<>|");
for (const auto x : invalidChars) {

Check failure on line 407 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest more than 3 if|for|do|while|switch statements.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMI&open=AZ4EVJ8Lv7_SaclY-yMI&pullRequest=10001
if (item->_file.contains(x)) {
invalid = x;
break;
Expand All @@ -426,21 +426,21 @@
}
item->_status = SyncFileItem::FileNameInvalid;
break;
case CSYNC_FILE_EXCLUDE_TRAILING_SPACE:

Check warning on line 429 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Reduce this switch case number of lines from 6 to at most 5, for example by extracting code into methods.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMO&open=AZ4EVJ8Lv7_SaclY-yMO&pullRequest=10001
item->_errorString = tr("Filename contains trailing spaces.");
item->_status = SyncFileItem::FileNameInvalid;
if (isLocal && !maybeRenameForWindowsCompatibility(_discoveryData->_localDir + item->_file, excluded)) {
item->_errorString += QStringLiteral(" %1").arg(tr("Cannot be renamed or uploaded."));
}
break;
case CSYNC_FILE_EXCLUDE_LEADING_SPACE:

Check warning on line 436 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Reduce this switch case number of lines from 6 to at most 5, for example by extracting code into methods.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMN&open=AZ4EVJ8Lv7_SaclY-yMN&pullRequest=10001
item->_errorString = tr("Filename contains leading spaces.");
item->_status = SyncFileItem::FileNameInvalid;
if (isLocal && !maybeRenameForWindowsCompatibility(_discoveryData->_localDir + item->_file, excluded)) {
item->_errorString += QStringLiteral(" %1").arg(tr("Cannot be renamed or uploaded."));
}
break;
case CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE:

Check warning on line 443 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Reduce this switch case number of lines from 6 to at most 5, for example by extracting code into methods.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMM&open=AZ4EVJ8Lv7_SaclY-yMM&pullRequest=10001
item->_errorString = tr("Filename contains leading and trailing spaces.");
item->_status = SyncFileItem::FileNameInvalid;
if (isLocal && !maybeRenameForWindowsCompatibility(_discoveryData->_localDir + item->_file, excluded)) {
Expand Down Expand Up @@ -468,7 +468,7 @@
case CSYNC_FILE_EXCLUDE_CANNOT_ENCODE:
item->_errorString = tr("The filename cannot be encoded on your file system.");
break;
case CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED:

Check warning on line 471 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Reduce this switch case number of lines from 20 to at most 5, for example by extracting code into methods.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yML&open=AZ4EVJ8Lv7_SaclY-yML&pullRequest=10001
const auto errorString = tr("The filename is blacklisted on the server.");
QString reasonString;
if (hasForbiddenFilename) {
Expand Down Expand Up @@ -513,7 +513,7 @@
const auto conflictRecord = _discoveryData->_statedb->caseConflictRecordByPath(path.toUtf8());
const auto originalBaseFileName = QFileInfo(QString(_discoveryData->_localDir + "/" + conflictRecord.initialBasePath)).fileName();

if (allEntries.find(originalBaseFileName) == allEntries.end()) {

Check warning on line 516 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "contains" member function.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMP&open=AZ4EVJ8Lv7_SaclY-yMP&pullRequest=10001
// original entry is no longer on the server, remove conflicted copy
qCDebug(lcDisco) << "original entry:" << originalBaseFileName << "is no longer on the server, remove conflicted copy:" << path;
return true;
Expand Down Expand Up @@ -694,7 +694,7 @@
_pendingAsyncJobs++;
_discoveryData->checkSelectiveSyncNewFolder(path._server,
serverEntry.remotePerm,
[=, this](bool result) {

Check warning on line 697 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "result" of type "_Bool" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMS&open=AZ4EVJ8Lv7_SaclY-yMS&pullRequest=10001

Check failure on line 697 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Explicitly capture the required scope variables.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMQ&open=AZ4EVJ8Lv7_SaclY-yMQ&pullRequest=10001

Check failure on line 697 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Explicitly capture all local variables required in this lambda.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMR&open=AZ4EVJ8Lv7_SaclY-yMR&pullRequest=10001
--_pendingAsyncJobs;
if (!result) {
processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer);
Expand Down Expand Up @@ -754,7 +754,7 @@
if (item->_e2eEncryptionStatusRemote != SyncFileItem::EncryptionStatus::NotEncrypted) {
Q_ASSERT(item->_e2eEncryptionStatus != SyncFileItem::EncryptionStatus::NotEncrypted);
}
item->_encryptedFileName = [=, this] {

Check warning on line 757 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Capture variables by reference, it is safe in this context.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMT&open=AZ4EVJ8Lv7_SaclY-yMT&pullRequest=10001
if (serverEntry.e2eMangledName.isEmpty()) {
return QString();
}
Expand Down Expand Up @@ -1061,7 +1061,7 @@
// we need to make a request to the server to know that the original file is deleted on the server
_pendingAsyncJobs++;
const auto job = new RequestEtagJob(_discoveryData->_account, _discoveryData->_remoteFolder + originalPath, this);
connect(job, &RequestEtagJob::finishedWithResult, this, [=, this](const HttpResult<QByteArray> &etag) mutable {

Check failure on line 1064 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Explicitly capture the required scope variables.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMU&open=AZ4EVJ8Lv7_SaclY-yMU&pullRequest=10001

Check failure on line 1064 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Explicitly capture all local variables required in this lambda.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMV&open=AZ4EVJ8Lv7_SaclY-yMV&pullRequest=10001
_pendingAsyncJobs--;
QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
if (etag || etag.error().code != 404 ||
Expand Down Expand Up @@ -1138,7 +1138,7 @@
qCDebug(lcDisco) << "_dirItem->_folderQuota.bytesAvailable:" << _dirItem->_folderQuota.bytesAvailable;

SyncJournalFileRecord dirItemDbRecord;
if (_discoveryData->_statedb->getFileRecord(_dirItem->_file, &dirItemDbRecord) && dirItemDbRecord.isValid()) {

Check warning on line 1141 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "dirItemDbRecord" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMW&open=AZ4EVJ8Lv7_SaclY-yMW&pullRequest=10001
const auto dirDbBytesAvailable = dirItemDbRecord._folderQuota.bytesAvailable;
qCDebug(lcDisco) << "Returning for item quota db value dirItemDbRecord._folderQuota.bytesAvailable" << dirDbBytesAvailable;
return dirDbBytesAvailable;
Expand Down Expand Up @@ -1190,7 +1190,7 @@

_childModified |= serverModified;

auto finalize = [&] {

Check warning on line 1193 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This lambda has 71 lines, which is greater than the 20 lines authorized. Split it into several lambdas or functions, or make it a named function.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMZ&open=AZ4EVJ8Lv7_SaclY-yMZ&pullRequest=10001
bool recurse = item->isDirectory() || localEntry.isDirectory || serverEntry.isDirectory;
// Even if we have a local directory: If the remote is a file that's propagated as a
// conflict we don't need to recurse into it. (local c1.owncloud, c1/ ; remote: c1)
Expand Down Expand Up @@ -1232,6 +1232,11 @@
}

item->_status = SyncFileItem::Status::NormalError;
// Mark as a quota error so blacklistUpdate writes an InsufficientRemoteStorage entry.
// Without this, _httpErrorCode would be 0 and blacklistUpdate would not create an
// entry, leaving the file unprotected by checkNewDeleteConflict if the remote parent
// folder is deleted before the quota situation is resolved.
item->_httpErrorCode = 507;
_discoveryData->_anotherSyncNeeded = true;
_discoveryData->_filesNeedingScheduledSync.insert(path._original, delayIntervalForSyncRetryForFilesExceedQuotaSeconds);
}
Expand Down Expand Up @@ -1478,7 +1483,7 @@
item->_checksumHeader.clear();
item->_size = localEntry.size;
item->_modtime = localEntry.modtime;
item->_type = localEntry.isDirectory && !localEntry.isVirtualFile ? ItemTypeDirectory : localEntry.isDirectory ? ItemTypeVirtualDirectory : localEntry.isVirtualFile ? ItemTypeVirtualFile : ItemTypeFile;

Check warning on line 1486 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional operator into an independent statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMb&open=AZ4EVJ8Lv7_SaclY-yMb&pullRequest=10001

Check warning on line 1486 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional operator into an independent statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMa&open=AZ4EVJ8Lv7_SaclY-yMa&pullRequest=10001
_childModified = true;

if (!localEntry.caseClashConflictingName.isEmpty()) {
Expand Down Expand Up @@ -1594,7 +1599,7 @@
item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_discoveryData->_account->capabilities().clientSideEncryptionVersion());
if (item->_e2eEncryptionStatus != item->_e2eEncryptionServerCapability) {
item->_e2eEncryptionStatus = item->_e2eEncryptionServerCapability;
if (base._e2eEncryptionStatus != SyncJournalFileRecord::EncryptionStatus::NotEncrypted) {

Check failure on line 1602 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest more than 3 if|for|do|while|switch statements.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMY&open=AZ4EVJ8Lv7_SaclY-yMY&pullRequest=10001
Q_ASSERT(item->_e2eEncryptionStatus != SyncFileItem::EncryptionStatus::NotEncrypted);
}
}
Expand All @@ -1610,7 +1615,7 @@
const auto serverHasMountRootProperty = _discoveryData->_account->serverHasMountRootProperty();
const auto isExternalStorage = base._remotePerm.hasPermission(RemotePermissions::IsMounted) && base.isDirectory();
const auto movePerms = checkMovePermissions(base._remotePerm, originalPath, item->isDirectory());
if (!movePerms.sourceOk || !movePerms.destinationOk || (serverHasMountRootProperty && isExternalStorage) || isE2eeMoveOnlineOnlyItemWithCfApi) {

Check warning on line 1618 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "movePerms" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMX&open=AZ4EVJ8Lv7_SaclY-yMX&pullRequest=10001
qCInfo(lcDisco) << "Move without permission to rename base file, "
<< "source:" << movePerms.sourceOk
<< ", target:" << movePerms.destinationOk
Expand All @@ -1634,7 +1639,7 @@

// Here we know the new location can't be uploaded: must prevent the source delete.
// Two cases: either the source item was already processed or not.
auto wasDeletedOnClient = _discoveryData->findAndCancelDeletedJob(originalPath);

Check warning on line 1642 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this declaration by a structured binding declaration.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMd&open=AZ4EVJ8Lv7_SaclY-yMd&pullRequest=10001
if (wasDeletedOnClient.first) {
// More complicated. The REMOVE is canceled. Restore will happen next sync.
qCInfo(lcDisco) << "Undid remove instruction on source" << originalPath;
Expand All @@ -1651,7 +1656,7 @@
return;
}

auto wasDeletedOnClient = _discoveryData->findAndCancelDeletedJob(originalPath);

Check warning on line 1659 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this declaration by a structured binding declaration.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMe&open=AZ4EVJ8Lv7_SaclY-yMe&pullRequest=10001

auto processRename = [item, originalPath, base, this](PathTuple &path) {
auto adjustedOriginalPath = _discoveryData->adjustRenamedPath(originalPath, SyncFileItem::Down);
Expand Down Expand Up @@ -1695,7 +1700,7 @@
if (base.isVirtualFile() && isVfsWithSuffix())
chopVirtualFileSuffix(serverOriginalPath);
auto job = new RequestEtagJob(_discoveryData->_account, serverOriginalPath, this);
connect(job, &RequestEtagJob::finishedWithResult, this, [=, this](const HttpResult<QByteArray> &etag) mutable {

Check failure on line 1703 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Explicitly capture all local variables required in this lambda.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMg&open=AZ4EVJ8Lv7_SaclY-yMg&pullRequest=10001

Check failure on line 1703 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Explicitly capture the required scope variables.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMf&open=AZ4EVJ8Lv7_SaclY-yMf&pullRequest=10001


if (!etag || (etag.get() != base._etag && !item->isDirectory()) || _discoveryData->isRenamed(originalPath)
Expand Down Expand Up @@ -1741,7 +1746,7 @@
// If there's no content hash, use heuristics
if (serverEntry.checksumHeader.isEmpty()) {
// If the size or mtime is different, it's definitely a conflict.
bool isConflict = (serverEntry.size != localEntry.size) || (serverEntry.modtime != localEntry.modtime) || (dbEntry.isValid() && dbEntry._modtime != localEntry.modtime && serverEntry.modtime == localEntry.modtime);

Check warning on line 1749 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "isConflict" of type "_Bool" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMh&open=AZ4EVJ8Lv7_SaclY-yMh&pullRequest=10001

// It could be a conflict even if size and mtime match!
//
Expand Down Expand Up @@ -1897,7 +1902,7 @@
item->_direction = _dirItem->_direction;
}

{

Check warning on line 1905 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested code block into a separate function.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMi&open=AZ4EVJ8Lv7_SaclY-yMi&pullRequest=10001
const auto isImportantInstruction = item->_instruction != CSYNC_INSTRUCTION_NONE;
if (isImportantInstruction) {
qCInfo(lcDisco).noquote() << item->_discoveryResult;
Expand Down Expand Up @@ -2117,12 +2122,12 @@
return matchingEditorsKeepingFileBusy;
}

const auto isMatchingFileExtension = std::find_if(std::cbegin(fileExtensionsToCheckIfOpenForSigning), std::cend(fileExtensionsToCheckIfOpenForSigning),
[path](const auto &matchingExtension) {

Check warning on line 2126 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Capture variables by reference, it is safe in this context.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMm&open=AZ4EVJ8Lv7_SaclY-yMm&pullRequest=10001
return path._local.endsWith(matchingExtension, Qt::CaseInsensitive);
}) != std::cend(fileExtensionsToCheckIfOpenForSigning);

Check warning on line 2128 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace with the version of "std::ranges::find_if" that takes a range.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMl&open=AZ4EVJ8Lv7_SaclY-yMl&pullRequest=10001

if (!isMatchingFileExtension) {

Check warning on line 2130 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "isMatchingFileExtension" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMk&open=AZ4EVJ8Lv7_SaclY-yMk&pullRequest=10001
return matchingEditorsKeepingFileBusy;
}

Expand All @@ -2130,8 +2135,8 @@
const auto editorsKeepingFileBusy = Utility::queryProcessInfosKeepingFileOpen(fullLocalPath);

for (const auto &detectedEditorName : editorsKeepingFileBusy) {
const auto isMatchingEditorFound = std::find_if(std::cbegin(editorNamesForDelayedUpload), std::cend(editorNamesForDelayedUpload),
[detectedEditorName](const auto &matchingEditorName) {

Check warning on line 2139 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Capture variables by reference, it is safe in this context.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMo&open=AZ4EVJ8Lv7_SaclY-yMo&pullRequest=10001
return detectedEditorName.processName.startsWith(matchingEditorName, Qt::CaseInsensitive);
}) != std::cend(editorNamesForDelayedUpload);

Check warning on line 2141 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace with the version of "std::ranges::find_if" that takes a range.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMn&open=AZ4EVJ8Lv7_SaclY-yMn&pullRequest=10001
if (isMatchingEditorFound) {
Expand Down Expand Up @@ -2270,7 +2275,7 @@
}
}

DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery()

Check failure on line 2278 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 27 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMp&open=AZ4EVJ8Lv7_SaclY-yMp&pullRequest=10001
{
if (_dirItem && _dirItem->isEncrypted() && _dirItem->_encryptedFileName.isEmpty()) {
_discoveryData->_topLevelE2eeFolderPaths.insert(_discoveryData->_remoteFolder + _dirItem->_file);
Expand Down Expand Up @@ -2365,7 +2370,7 @@
void ProcessDirectoryJob::startAsyncLocalQuery()
{
QString localPath = _discoveryData->_localDir + _currentFolder._local;
auto localJob = new DiscoverySingleLocalDirectoryJob(_discoveryData->_account, localPath, _discoveryData->_syncOptions._vfs.data(), _discoveryData->_fileSystemReliablePermissions);

Check warning on line 2373 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "localJob" of type "class OCC::DiscoverySingleLocalDirectoryJob *" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMq&open=AZ4EVJ8Lv7_SaclY-yMq&pullRequest=10001

_discoveryData->_currentlyActiveJobs++;
_pendingAsyncJobs++;
Expand Down Expand Up @@ -2453,18 +2458,18 @@
}
}

bool ProcessDirectoryJob::maybeRenameForWindowsCompatibility(const QString &absoluteFileName,

Check warning on line 2461 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This function should be declared "const".

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMt&open=AZ4EVJ8Lv7_SaclY-yMt&pullRequest=10001

Check warning on line 2461 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMs&open=AZ4EVJ8Lv7_SaclY-yMs&pullRequest=10001
CSYNC_EXCLUDE_TYPE excludeReason)
{
auto result = true;

const auto leadingAndTrailingSpacesFilesAllowed = !_discoveryData->_shouldEnforceWindowsFileNameCompatibility || _discoveryData->_leadingAndTrailingSpacesFilesAllowed.contains(absoluteFileName);

Check warning on line 2466 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMx&open=AZ4EVJ8Lv7_SaclY-yMx&pullRequest=10001
if (leadingAndTrailingSpacesFilesAllowed) {

Check warning on line 2467 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "leadingAndTrailingSpacesFilesAllowed" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMr&open=AZ4EVJ8Lv7_SaclY-yMr&pullRequest=10001
return result;
}

const auto fileInfo = QFileInfo{absoluteFileName};
switch (excludeReason)

Check failure on line 2472 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a "default" case to this switch statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMw&open=AZ4EVJ8Lv7_SaclY-yMw&pullRequest=10001
{
case CSYNC_NOT_EXCLUDED:
case CSYNC_FILE_LOCKED_SILENTLY_EXCLUDED:
Expand All @@ -2482,10 +2487,10 @@
break;
case CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE:
case CSYNC_FILE_EXCLUDE_LEADING_SPACE:
case CSYNC_FILE_EXCLUDE_TRAILING_SPACE:

Check warning on line 2490 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Reduce this switch case number of lines from 14 to at most 5, for example by extracting code into methods.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMv&open=AZ4EVJ8Lv7_SaclY-yMv&pullRequest=10001
{
const auto removeTrailingSpaces = [] (QString string) -> QString {

Check warning on line 2492 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the redundant return type of this lambda.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yMy&open=AZ4EVJ8Lv7_SaclY-yMy&pullRequest=10001
for (int n = string.size() - 1; n >= 0; -- n) {

Check warning on line 2493 in src/libsync/discovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

implicit conversion loses integer precision: 'qsizetype' (aka 'long long') to 'int'

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVJ8Lv7_SaclY-yL-&open=AZ4EVJ8Lv7_SaclY-yL-&pullRequest=10001
if (!string.at(n).isSpace()) {
string.truncate(n + 1);
break;
Expand All @@ -2505,18 +2510,42 @@
return result;
}

bool ProcessDirectoryJob::checkNewDeleteConflict(const SyncFileItemPtr &item) const
bool ProcessDirectoryJob::checkNewDeleteConflict(const SyncFileItemPtr &item)
{
if (_discoveryData->recursiveCheckForDeletedParents(item->_file)) {
qCWarning(lcDisco) << "Removing local file inside a remotely deleted folder" << item->_file;
item->_instruction = CSYNC_INSTRUCTION_REMOVE;
item->_direction = SyncFileItem::Down;
item->_wantsSpecificActions = SyncFileItem::SynchronizationOptions::MoveToClientTrashBin;
if (!_discoveryData->recursiveCheckForDeletedParents(item->_file)) {
return false;
}

// If the file was blocked from uploading by a remote storage quota error it was never on the
// server, so deleting the local copy would be permanent data loss. Protect it instead and let
// the user resolve the situation (fix quota, then sync or move the file).
if (const auto blacklistEntry = _discoveryData->_statedb->errorBlacklistEntry(item->_file);
blacklistEntry.isValid()
&& blacklistEntry._errorCategory == SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage) {
qCWarning(lcDisco) << "Not removing local file inside a remotely deleted folder: "
"file was never uploaded due to storage quota —"
<< item->_file;
item->_instruction = CSYNC_INSTRUCTION_ERROR;
item->_status = SyncFileItem::SoftError;
// Keep _httpErrorCode at 507 so blacklistUpdate re-creates (rather than wipes) the
// InsufficientRemoteStorage entry if the current ignore-duration later expires.
item->_httpErrorCode = 507;
item->_errorString = tr("\"%1\" was not deleted because its latest changes were not synced "
"and your server quota was exceeded. "
"Please manage your storage and try syncing again.")
.arg(item->_file);
// Prevent the parent directory from being deleted while a quota-blocked child exists.
_childIgnored = true;
emit _discoveryData->itemDiscovered(item);
return true;
}

return false;
qCWarning(lcDisco) << "Removing local file inside a remotely deleted folder" << item->_file;
item->_instruction = CSYNC_INSTRUCTION_REMOVE;
item->_direction = SyncFileItem::Down;
item->_wantsSpecificActions = SyncFileItem::SynchronizationOptions::MoveToClientTrashBin;
emit _discoveryData->itemDiscovered(item);
return true;
}

}
2 changes: 1 addition & 1 deletion src/libsync/discovery.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#pragma once

#include <QObject>

Check failure on line 9 in src/libsync/discovery.h

View workflow job for this annotation

GitHub Actions / build

src/libsync/discovery.h:9:10 [clang-diagnostic-error]

'QObject' file not found
#include <cstdint>
#include "csync_exclude.h"
#include "discoveryphase.h"
Expand Down Expand Up @@ -39,7 +39,7 @@
*
* Results are fed outwards via the DiscoveryPhase::itemDiscovered() signal.
*/
class ProcessDirectoryJob : public QObject

Check warning on line 42 in src/libsync/discovery.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this structure so it has no more than 20 fields, rather than the 21 it currently has.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVKAPv7_SaclY-yMz&open=AZ4EVKAPv7_SaclY-yMz&pullRequest=10001
{
Q_OBJECT

Expand Down Expand Up @@ -249,10 +249,10 @@
*/
void setupDbPinStateActions(SyncJournalFileRecord &record);

bool maybeRenameForWindowsCompatibility(const QString &absoluteFileName,

Check warning on line 252 in src/libsync/discovery.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ4EVKAPv7_SaclY-yM0&open=AZ4EVKAPv7_SaclY-yM0&pullRequest=10001
CSYNC_EXCLUDE_TYPE excludeReason);

[[nodiscard]] bool checkNewDeleteConflict(const SyncFileItemPtr &item) const;
[[nodiscard]] bool checkNewDeleteConflict(const SyncFileItemPtr &item);

qint64 _lastSyncTimestamp = 0;

Expand Down
74 changes: 74 additions & 0 deletions test/testsyncdelete.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2018 ownCloud, Inc.
Expand All @@ -8,7 +8,7 @@
* any purpose.
*/

#include <QtTest>

Check failure on line 11 in test/testsyncdelete.cpp

View workflow job for this annotation

GitHub Actions / build

test/testsyncdelete.cpp:11:10 [clang-diagnostic-error]

'QtTest' file not found
#include "syncenginetestutils.h"
#include <syncengine.h>

Expand Down Expand Up @@ -52,6 +52,80 @@
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}

// Verify that a file blocked from uploading by a remote storage quota error is
// protected from local deletion when the remote parent folder is subsequently deleted.
void testQuotaBlockedFileProtectedFromParentFolderDeletion()
{
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};

// Establish an initial clean sync: a1 and a2 exist on both sides.
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());

// Add a new local file that will fail to upload due to remote quota (HTTP 507).
fakeFolder.localModifier().insert(QStringLiteral("A/quota_blocked.txt"), 100);
fakeFolder.serverErrorPaths().append(QStringLiteral("A/quota_blocked.txt"), 507);

// Sync: upload fails → file is blacklisted as InsufficientRemoteStorage.
QVERIFY(!fakeFolder.syncOnce());
{
auto entry = fakeFolder.syncJournal().errorBlacklistEntry(QStringLiteral("A/quota_blocked.txt"));
QVERIFY(entry.isValid());
QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage);
}

// The file was never uploaded.
QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("A/quota_blocked.txt")));

// Remove the server error so further requests to that path don't return 507.
fakeFolder.serverErrorPaths().clear();

// Server-side: folder A is moved/deleted (e.g., via the web interface).
fakeFolder.remoteModifier().remove(QStringLiteral("A"));

ItemCompletedSpy completeSpy(fakeFolder);
fakeFolder.syncOnce();

// *** Core assertion: quota-blocked file must NOT have been deleted locally. ***
QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("A/quota_blocked.txt")));

// Parent folder A must also survive because it still contains the protected file.
QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("A")));

// Previously-synced siblings must have been deleted locally (trust the server).
QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("A/a1")));
QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("A/a2")));

// The quota-blocked file must still not exist on the server.
QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("A/quota_blocked.txt")));

// The protected file must be reported as an error (or blacklisted-error) item, not silently ignored.
{
auto item = completeSpy.findItem(QStringLiteral("A/quota_blocked.txt"));
QVERIFY(item);
const bool isErrorItem = item->_instruction == CSYNC_INSTRUCTION_ERROR
|| item->_instruction == CSYNC_INSTRUCTION_IGNORE; // BlacklistedError reuses IGNORE
QVERIFY(isErrorItem);
const bool isErrorStatus = item->_status == SyncFileItem::SoftError
|| item->_status == SyncFileItem::BlacklistedError;
QVERIFY(isErrorStatus);
}
}

// Regression: non-quota new files in a server-deleted folder must still be deleted locally
void testDeleteDirectoryWithNewFileNoQuotaError()
{
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().remove(QStringLiteral("A"));
fakeFolder.localModifier().insert(QStringLiteral("A/newfile.txt"), 100);

QVERIFY(fakeFolder.syncOnce());

// New local file with no quota blacklist entry must be removed when parent is deleted on server.
QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("A/newfile.txt")));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}

void issue1329()
{
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
Expand Down
Loading