diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 6da9d6732f129..23800013b0a1e 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -272,8 +272,7 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent const auto fileName = path.mid(path.lastIndexOf('/') + 1); - const auto osDoesNotSupportsSpaces = Utility::isWindows(); - if (excluded == CSYNC_NOT_EXCLUDED && osDoesNotSupportsSpaces) { + if (excluded == CSYNC_NOT_EXCLUDED) { const auto endsWithSpace = fileName.endsWith(QLatin1Char(' ')); const auto startsWithSpace = fileName.startsWith(QLatin1Char(' ')); if (startsWithSpace && endsWithSpace) { @@ -291,20 +290,30 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent const auto hasLeadingOrTrailingSpaces = excluded == CSYNC_FILE_EXCLUDE_LEADING_SPACE || excluded == CSYNC_FILE_EXCLUDE_TRAILING_SPACE || excluded == CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE; - const auto leadingAndTrailingSpacesFilesAllowed = _discoveryData->_leadingAndTrailingSpacesFilesAllowed.contains(_discoveryData->_localDir + path); - if (hasLeadingOrTrailingSpaces && ((!osDoesNotSupportsSpaces && wasSyncedAlready) || leadingAndTrailingSpacesFilesAllowed)) { + + const auto leadingAndTrailingSpacesFilesAllowed = !_discoveryData->_shouldEnforceWindowsFileNameCompatibility || _discoveryData->_leadingAndTrailingSpacesFilesAllowed.contains(_discoveryData->_localDir + path); +#if defined Q_OS_WINDOWS + if (hasLeadingOrTrailingSpaces && leadingAndTrailingSpacesFilesAllowed) { +#else + if (hasLeadingOrTrailingSpaces && (wasSyncedAlready || leadingAndTrailingSpacesFilesAllowed)) { +#endif excluded = CSYNC_NOT_EXCLUDED; } // FIXME: move to ExcludedFiles 's regexp ? bool isInvalidPattern = false; - if (excluded == CSYNC_NOT_EXCLUDED && !_discoveryData->_invalidFilenameRx.pattern().isEmpty()) { - if (path.contains(_discoveryData->_invalidFilenameRx)) { - excluded = CSYNC_FILE_EXCLUDE_INVALID_CHAR; - isInvalidPattern = true; - } + if (excluded == CSYNC_NOT_EXCLUDED + && !wasSyncedAlready + && !_discoveryData->_invalidFilenameRx.pattern().isEmpty() + && path.contains(_discoveryData->_invalidFilenameRx)) { + + excluded = CSYNC_FILE_EXCLUDE_INVALID_CHAR; + isInvalidPattern = true; } - if (excluded == CSYNC_NOT_EXCLUDED && _discoveryData->_ignoreHiddenFiles && isHidden) { + if (excluded == CSYNC_NOT_EXCLUDED + && !entries.dbEntry.isValid() + && _discoveryData->_ignoreHiddenFiles && isHidden) { + excluded = CSYNC_FILE_EXCLUDE_HIDDEN; } @@ -335,7 +344,8 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent }); if (excluded == CSYNC_NOT_EXCLUDED && !localName.isEmpty() - && (_discoveryData->_serverBlacklistedFiles.contains(localName) + && !wasSyncedAlready + &&(_discoveryData->_serverBlacklistedFiles.contains(localName) || hasForbiddenFilename || hasForbiddenBasename || hasForbiddenExtension @@ -415,14 +425,23 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent case CSYNC_FILE_EXCLUDE_TRAILING_SPACE: item->_errorString = tr("Filename contains trailing spaces."); item->_status = SyncFileItem::FileNameInvalid; + if (!maybeRenameForWindowsCompatibility(_discoveryData->_localDir + item->_file, excluded)) { + item->_errorString += QStringLiteral(" %1").arg(tr("Cannot be renamed or uploaded.")); + } break; case CSYNC_FILE_EXCLUDE_LEADING_SPACE: item->_errorString = tr("Filename contains leading spaces."); item->_status = SyncFileItem::FileNameInvalid; + if (!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: item->_errorString = tr("Filename contains leading and trailing spaces."); item->_status = SyncFileItem::FileNameInvalid; + if (!maybeRenameForWindowsCompatibility(_discoveryData->_localDir + item->_file, excluded)) { + item->_errorString += QStringLiteral(" %1").arg(tr("Cannot be renamed or uploaded.")); + } break; case CSYNC_FILE_EXCLUDE_LONG_FILENAME: item->_errorString = tr("Filename is too long."); @@ -462,6 +481,9 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent } item->_errorString = reasonString.isEmpty() ? errorString : QStringLiteral("%1 %2").arg(errorString, reasonString); item->_status = SyncFileItem::FileNameInvalidOnServer; + if (!maybeRenameForWindowsCompatibility(_discoveryData->_localDir + item->_file, excluded)) { + item->_errorString += QStringLiteral(" %1").arg(tr("Cannot be renamed or uploaded.")); + } break; } } @@ -2269,4 +2291,42 @@ void ProcessDirectoryJob::setupDbPinStateActions(SyncJournalFileRecord &record) } } +bool ProcessDirectoryJob::maybeRenameForWindowsCompatibility(const QString &absoluteFileName, + CSYNC_EXCLUDE_TYPE excludeReason) +{ + auto result = true; + + const auto leadingAndTrailingSpacesFilesAllowed = !_discoveryData->_shouldEnforceWindowsFileNameCompatibility || _discoveryData->_leadingAndTrailingSpacesFilesAllowed.contains(absoluteFileName); + if (leadingAndTrailingSpacesFilesAllowed) { + return result; + } + + const auto fileInfo = QFileInfo{absoluteFileName}; + switch (excludeReason) + { + case CSYNC_NOT_EXCLUDED: + case CSYNC_FILE_EXCLUDE_CASE_CLASH_CONFLICT: + case CSYNC_FILE_EXCLUDE_AND_REMOVE: + case CSYNC_FILE_EXCLUDE_CANNOT_ENCODE: + case CSYNC_FILE_EXCLUDE_INVALID_CHAR: + case CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED: + case CSYNC_FILE_EXCLUDE_HIDDEN: + case CSYNC_FILE_SILENTLY_EXCLUDED: + case CSYNC_FILE_EXCLUDE_STAT_FAILED: + case CSYNC_FILE_EXCLUDE_LONG_FILENAME: + case CSYNC_FILE_EXCLUDE_LIST: + case CSYNC_FILE_EXCLUDE_CONFLICT: + break; + case CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE: + case CSYNC_FILE_EXCLUDE_LEADING_SPACE: + case CSYNC_FILE_EXCLUDE_TRAILING_SPACE: + { + const auto renameTarget = QString{fileInfo.absolutePath() + QStringLiteral("/") + fileInfo.fileName().trimmed()}; + result = FileSystem::rename(absoluteFileName, renameTarget); + break; + } + } + return result; +} + } diff --git a/src/libsync/discovery.h b/src/libsync/discovery.h index 840eb15d61169..a18fc601458a1 100644 --- a/src/libsync/discovery.h +++ b/src/libsync/discovery.h @@ -15,6 +15,7 @@ #pragma once #include +#include "csync_exclude.h" #include "discoveryphase.h" #include "syncfileitem.h" #include "common/asserts.h" @@ -255,6 +256,9 @@ class ProcessDirectoryJob : public QObject */ void setupDbPinStateActions(SyncJournalFileRecord &record); + bool maybeRenameForWindowsCompatibility(const QString &absoluteFileName, + CSYNC_EXCLUDE_TYPE excludeReason); + qint64 _lastSyncTimestamp = 0; QueryMode _queryServer = QueryMode::NormalQuery; diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index f1d1d3136dd65..855758b1f9047 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -326,6 +326,7 @@ class DiscoveryPhase : public QObject QRegularExpression _invalidFilenameRx; // FIXME: maybe move in ExcludedFiles QStringList _serverBlacklistedFiles; // The blacklist from the capabilities QStringList _leadingAndTrailingSpacesFilesAllowed; + bool _shouldEnforceWindowsFileNameCompatibility = false; bool _ignoreHiddenFiles = false; std::function _shouldDiscoverLocaly; @@ -342,6 +343,11 @@ class DiscoveryPhase : public QObject QStringList _listExclusiveFiles; + QStringList _forbiddenFilenames; + QStringList _forbiddenBasenames; + QStringList _forbiddenExtensions; + QStringList _forbiddenChars; + bool _hasUploadErrorItems = false; bool _hasDownloadRemovedItems = false; diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 2ac90bf1e8df3..515417084a78e 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -671,6 +671,28 @@ void SyncEngine::startSync() return; } + const auto accountCaps = _account->capabilities(); + const auto forbiddenFilenames = accountCaps.forbiddenFilenames(); + const auto forbiddenBasenames = accountCaps.forbiddenFilenameBasenames(); + const auto forbiddenExtensions = accountCaps.forbiddenFilenameExtensions(); + const auto forbiddenChars = accountCaps.forbiddenFilenameCharacters(); + + _discoveryPhase->_forbiddenFilenames = forbiddenFilenames; + _discoveryPhase->_forbiddenBasenames = forbiddenBasenames; + _discoveryPhase->_forbiddenExtensions = forbiddenExtensions; + _discoveryPhase->_forbiddenChars = forbiddenChars; + if (!forbiddenFilenames.isEmpty() && + !forbiddenBasenames.isEmpty() && + !forbiddenExtensions.isEmpty() && + !forbiddenChars.isEmpty()) { + _shouldEnforceWindowsFileNameCompatibility = true; + _discoveryPhase->_shouldEnforceWindowsFileNameCompatibility = _shouldEnforceWindowsFileNameCompatibility; + } +#if defined Q_OS_WINDOWS + _shouldEnforceWindowsFileNameCompatibility = true; + _discoveryPhase->_shouldEnforceWindowsFileNameCompatibility = _shouldEnforceWindowsFileNameCompatibility; +#endif + // Check for invalid character in old server version QString invalidFilenamePattern = _account->capabilities().invalidFilenameRegex(); if (invalidFilenamePattern.isNull() @@ -1208,6 +1230,11 @@ void SyncEngine::addAcceptedInvalidFileName(const QString& filePath) _leadingAndTrailingSpacesFilesAllowed.append(filePath); } +void SyncEngine::setLocalDiscoveryEnforceWindowsFileNameCompatibility(bool value) +{ + _shouldEnforceWindowsFileNameCompatibility = value; +} + bool SyncEngine::wasFileTouched(const QString &fn) const { // Start from the end (most recent) and look for our path. Check the time just in case. diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index 2dc5bfa6b4a32..76fc99ce7dc54 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -159,6 +159,7 @@ public slots: */ void setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle style, std::set paths = {}); void addAcceptedInvalidFileName(const QString& filePath); + void setLocalDiscoveryEnforceWindowsFileNameCompatibility(bool value); signals: // During update, before reconcile @@ -409,6 +410,7 @@ private slots: std::set _localDiscoveryPaths; QStringList _leadingAndTrailingSpacesFilesAllowed; + bool _shouldEnforceWindowsFileNameCompatibility = false; // Hash of files we have scheduled for later sync runs, along with a // pointer to the timer which will trigger the sync run for it. diff --git a/test/syncenginetestutils.cpp b/test/syncenginetestutils.cpp index 30c6254e5bfb6..637c9099c415c 100644 --- a/test/syncenginetestutils.cpp +++ b/test/syncenginetestutils.cpp @@ -1207,6 +1207,81 @@ void FakeFolder::switchToVfs(QSharedPointer vfs) vfs->start(vfsParams); } +void FakeFolder::enableEnforceWindowsFileNameCompatibility() +{ + syncEngine().account()->setCapabilities(QVariantMap{ + { + "files", + QVariantMap{ + { + "forbidden_filename_basenames", + QStringList{"con", + "prn", + "aux", + "nul", + "com0", + "com1", + "com2", + "com3", + "com4", + "com5", + "com6", + "com7", + "com8", + "com9", + "com¹", + "com²", + "com³", + "lpt0", + "lpt1", + "lpt2", + "lpt3", + "lpt4", + "lpt5", + "lpt6", + "lpt7", + "lpt8", + "lpt9", + "lpt¹", + "lpt²", + "lpt³" + } + }, + { + "forbidden_filename_characters", + QStringList{"\\", + "/", + "<", + ">", + ":", + "\"", + "|", + "?", + "*", + "\\", + "/" + } + }, + { + "forbidden_filename_extensions", + QStringList{" ", + ".", + ".filepart", + ".part", + ".part" + } + }, + { + "forbidden_filenames", + QStringList{"\\", + ".htaccess" + } + } + } + } + }); +} + FileInfo FakeFolder::currentLocalState() { QDir rootDir { _tempDir.path() }; diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index b188a2d00a8aa..817a138a58a3f 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -559,6 +559,8 @@ class FakeFolder void switchToVfs(QSharedPointer vfs); + void enableEnforceWindowsFileNameCompatibility(); + [[nodiscard]] OCC::AccountPtr account() const { return _account; } [[nodiscard]] OCC::SyncEngine &syncEngine() const { return *_syncEngine; } [[nodiscard]] OCC::SyncJournalDb &syncJournal() const { return *_journalDb; } diff --git a/test/testlocaldiscovery.cpp b/test/testlocaldiscovery.cpp index 95c691b75e364..87bca0cdf1731 100644 --- a/test/testlocaldiscovery.cpp +++ b/test/testlocaldiscovery.cpp @@ -371,7 +371,10 @@ private slots: void testCreateFileWithTrailingSpaces_localAndRemoteTrimmedDoNotExist_renameAndUploadFile() { FakeFolder fakeFolder{FileInfo{}}; + fakeFolder.enableEnforceWindowsFileNameCompatibility(); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + const QString fileWithSpaces1(" foo"); const QString fileWithSpaces2(" bar "); const QString fileWithSpaces3("bla "); @@ -394,7 +397,6 @@ private slots: QVERIFY(fakeFolder.syncOnce()); -#if defined Q_OS_WINDOWS QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::FileNameInvalid); QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::FileNameInvalid); QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::FileNameInvalid); @@ -402,15 +404,6 @@ private slots: QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); QCOMPARE(completeSpy.findItem(extraFileNameWithSpaces)->_status, SyncFileItem::Status::FileNameInvalid); -#else - QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(extraFileNameWithSpaces)->_status, SyncFileItem::Status::Success); -#endif fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces1); fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces2); @@ -425,14 +418,14 @@ private slots: fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("foo"), QStringLiteral("bar"), QStringLiteral("bla"), QStringLiteral("A/foo"), QStringLiteral("A/bar"), QStringLiteral("A/bla")}); QVERIFY(fakeFolder.syncOnce()); -#if defined Q_OS_WINDOWS - QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(extraFileNameWithSpaces)->_status, SyncFileItem::Status::Success); +#if !defined Q_OS_WINDOWS + QCOMPARE(completeSpy.findItem(QStringLiteral("foo"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("bar"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("bla"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/foo"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/bar"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/bla"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("with spaces"))->_status, SyncFileItem::Status::Success); #endif } @@ -455,20 +448,21 @@ private slots: QVERIFY(fakeFolder.syncOnce()); - if (Utility::isWindows()) { +#if defined Q_OS_WINDOWS QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::FileNameInvalid); QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); - } else { +#else QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::Success); QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::Success); QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::Success); - } +#endif } void testCreateLocalPathsWithLeadingAndTrailingSpaces_syncOnSupportingOs() { FakeFolder fakeFolder{FileInfo()}; + fakeFolder.enableEnforceWindowsFileNameCompatibility(); fakeFolder.localModifier().mkdir("A"); QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -490,18 +484,16 @@ private slots: QVERIFY(fakeFolder.syncOnce()); -#if !defined Q_OS_WINDOWS - QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(folderWithSpaces1)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(folderWithSpaces2)->_status, SyncFileItem::Status::Success); - QVERIFY(fakeFolder.remoteModifier().find(fileWithSpaces1)); - QVERIFY(fakeFolder.remoteModifier().find(fileWithSpaces2)); - QVERIFY(fakeFolder.remoteModifier().find(fileWithSpaces3)); - QVERIFY(fakeFolder.remoteModifier().find(folderWithSpaces1)); - QVERIFY(fakeFolder.remoteModifier().find(folderWithSpaces2)); -#endif + QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(folderWithSpaces1)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(folderWithSpaces2)->_status, SyncFileItem::Status::FileNameInvalid); + QVERIFY(!fakeFolder.remoteModifier().find(fileWithSpaces1)); + QVERIFY(!fakeFolder.remoteModifier().find(fileWithSpaces2)); + QVERIFY(!fakeFolder.remoteModifier().find(fileWithSpaces3)); + QVERIFY(!fakeFolder.remoteModifier().find(folderWithSpaces1)); + QVERIFY(!fakeFolder.remoteModifier().find(folderWithSpaces2)); } void testCreateFileWithTrailingSpaces_remoteGetRenamedManually() @@ -525,17 +517,17 @@ private slots: ItemCompletedSpy completeSpy(fakeFolder); completeSpy.clear(); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.syncOnce()); - if (Utility::isWindows()) { +#if defined Q_OS_WINDOWS QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::FileNameInvalid); QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); - } else { +#else QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::Success); QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::Success); QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::Success); - } +#endif fakeFolder.remoteModifier().rename(fileWithSpaces4, fileWithoutSpaces4); fakeFolder.remoteModifier().rename(fileWithSpaces5, fileWithoutSpaces5); @@ -553,6 +545,7 @@ private slots: void testCreateFileWithTrailingSpaces_localTrimmedAlsoCreated_dontRenameAutomaticallyAndDontUploadFile() { FakeFolder fakeFolder{FileInfo{}}; + fakeFolder.enableEnforceWindowsFileNameCompatibility(); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); const QString fileWithSpaces(" foo"); const QString fileTrimmed("foo"); @@ -564,13 +557,8 @@ private slots: QVERIFY(fakeFolder.currentRemoteState().find(fileTrimmed)); -#if defined Q_OS_WINDOWS // no file with spaces on Windows QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces)); -#else - //Linux/Mac OS allows spaces - QVERIFY(fakeFolder.currentRemoteState().find(fileWithSpaces)); -#endif QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces)); QVERIFY(fakeFolder.currentLocalState().find(fileTrimmed)); @@ -617,9 +605,11 @@ private slots: QVERIFY(fakeFolder.syncOnce()); +#if !defined Q_OS_WINDOWS auto expectedState = fakeFolder.currentLocalState(); qDebug() << expectedState; QCOMPARE(fakeFolder.currentRemoteState(), expectedState); +#endif } void testBlockInvalidMtimeSyncRemote() diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index cecd9ae59f65d..02f9668cc7da2 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -2035,6 +2035,80 @@ private slots: QVERIFY(fakeFolder.syncOnce()); } + + void testCreateFileWithTrailingLeadingSpaces_local_automatedRenameBeforeUpload() + { + FakeFolder fakeFolder{FileInfo{}}; + fakeFolder.enableEnforceWindowsFileNameCompatibility(); + + fakeFolder.syncEngine().setLocalDiscoveryEnforceWindowsFileNameCompatibility(true); + + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + const QString fileWithSpaces1(" foo"); + const QString fileWithSpaces2(" bar "); + const QString fileWithSpaces3("bla "); + const QString fileWithSpaces4("A/ foo"); + const QString fileWithSpaces5("A/ bar "); + const QString fileWithSpaces6("A/bla "); + const auto extraFileNameWithSpaces = QStringLiteral(" with spaces "); + const QString fileWithoutSpaces1("foo"); + const QString fileWithoutSpaces2("bar"); + const QString fileWithoutSpaces3("bla"); + const QString fileWithoutSpaces4("A/foo"); + const QString fileWithoutSpaces5("A/bar"); + const QString fileWithoutSpaces6("A/bla"); + const auto extraFileNameWithoutSpaces = QStringLiteral("with spaces"); + + fakeFolder.localModifier().insert(fileWithSpaces1); + fakeFolder.localModifier().insert(fileWithSpaces2); + fakeFolder.localModifier().insert(fileWithSpaces3); + fakeFolder.localModifier().mkdir("A"); + fakeFolder.localModifier().insert(fileWithSpaces4); + fakeFolder.localModifier().insert(fileWithSpaces5); + fakeFolder.localModifier().insert(fileWithSpaces6); + fakeFolder.localModifier().mkdir(extraFileNameWithSpaces); + + ItemCompletedSpy completeSpy(fakeFolder); + completeSpy.clear(); + + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(extraFileNameWithSpaces)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces1)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces2)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces3)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces4)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces5)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces6)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(extraFileNameWithoutSpaces)->_status, SyncFileItem::Status::NoStatus); + + completeSpy.clear(); + + fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("foo"), QStringLiteral("bar"), QStringLiteral("bla"), QStringLiteral("A/foo"), QStringLiteral("A/bar"), QStringLiteral("A/bla")}); + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(extraFileNameWithSpaces)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces1)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces2)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces3)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces4)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces5)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithoutSpaces6)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(extraFileNameWithoutSpaces)->_status, SyncFileItem::Status::Success); + } }; QTEST_GUILESS_MAIN(TestSyncEngine) diff --git a/test/testsyncvirtualfiles.cpp b/test/testsyncvirtualfiles.cpp index 47dabce84f2a2..f6281ab38df71 100644 --- a/test/testsyncvirtualfiles.cpp +++ b/test/testsyncvirtualfiles.cpp @@ -863,22 +863,68 @@ private slots: QVERIFY(fakeFolder.syncOnce()); #if defined Q_OS_WINDOWS - QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::Success); -#else - QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::NoStatus); - QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::NoStatus); - QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::NoStatus); - QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::NoStatus); - QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::NoStatus); - QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::NoStatus); + QCOMPARE(completeSpy.findItem(QStringLiteral("foo"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("bar"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("bla"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/foo"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/bar"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/bla"))->_status, SyncFileItem::Status::Success); #endif } + void testCreateFileWithTrailingSpaces_acceptAndRejectInvalidFileName_enforceWindowsNamingRules() + { + FakeFolder fakeFolder{ FileInfo() }; + fakeFolder.enableEnforceWindowsFileNameCompatibility(); + setupVfs(fakeFolder); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + const QString fileWithSpaces1(" foo"); + const QString fileWithSpaces2(" bar "); + const QString fileWithSpaces3("bla "); + const QString fileWithSpaces4("A/ foo"); + const QString fileWithSpaces5("A/ bar "); + const QString fileWithSpaces6("A/bla "); + + fakeFolder.localModifier().insert(fileWithSpaces1); + fakeFolder.localModifier().insert(fileWithSpaces2); + fakeFolder.localModifier().insert(fileWithSpaces3); + fakeFolder.localModifier().mkdir("A"); + fakeFolder.localModifier().insert(fileWithSpaces4); + fakeFolder.localModifier().insert(fileWithSpaces5); + fakeFolder.localModifier().insert(fileWithSpaces6); + + ItemCompletedSpy completeSpy(fakeFolder); + completeSpy.clear(); + + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(fileWithSpaces1)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces2)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces3)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); + + fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces1); + fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces2); + fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces3); + fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces4); + fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces5); + fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces6); + + completeSpy.clear(); + + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(QStringLiteral("foo"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("bar"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("bla"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/foo"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/bar"))->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/bla"))->_status, SyncFileItem::Status::Success); + } + void testCreateFileWithTrailingSpaces_remoteDontGetRenamedAutomatically() { // On Windows we can't create files/folders with leading/trailing spaces locally. So, we have to fail those items. On other OSs - we just sync them down normally. @@ -903,16 +949,15 @@ private slots: QVERIFY(fakeFolder.syncOnce()); - if (Utility::isWindows()) { - QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::FileNameInvalid); - QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); - QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); - } else { - QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual4)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual5)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual6)->_status, SyncFileItem::Status::Success); - } - +#if defined Q_OS_WINDOWS + QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); +#else + QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual4)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual5)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual6)->_status, SyncFileItem::Status::Success); +#endif } void testCreateFileWithTrailingSpaces_remoteGetRenamedManually() @@ -947,16 +992,16 @@ private slots: QVERIFY(fakeFolder.syncOnce()); - if (Utility::isWindows()) { - QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::FileNameInvalid); - QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); - QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); - } else { - QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual4)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual5)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual6)->_status, SyncFileItem::Status::Success); - } - +#if defined Q_OS_WINDOWS + QCOMPARE(completeSpy.findItem(fileWithSpaces4)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::FileNameInvalid); + QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::FileNameInvalid); +#else + QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual4)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual5)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithSpacesVirtual6)->_status, SyncFileItem::Status::Success); +#endif + fakeFolder.remoteModifier().rename(fileWithSpaces4, fileWithoutSpaces4); fakeFolder.remoteModifier().rename(fileWithSpaces5, fileWithoutSpaces5); fakeFolder.remoteModifier().rename(fileWithSpaces6, fileWithoutSpaces6); @@ -965,15 +1010,9 @@ private slots: QVERIFY(fakeFolder.syncOnce()); - if (Utility::isWindows()) { - QCOMPARE(completeSpy.findItem(fileWithoutSpaces4 + DVSUFFIX)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithoutSpaces5 + DVSUFFIX)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithoutSpaces6 + DVSUFFIX)->_status, SyncFileItem::Status::Success); - } else { - QCOMPARE(completeSpy.findItem(fileWithoutSpacesVirtual4)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithoutSpacesVirtual5)->_status, SyncFileItem::Status::Success); - QCOMPARE(completeSpy.findItem(fileWithoutSpacesVirtual6)->_status, SyncFileItem::Status::Success); - } + QCOMPARE(completeSpy.findItem(fileWithoutSpacesVirtual4)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithoutSpacesVirtual5)->_status, SyncFileItem::Status::Success); + QCOMPARE(completeSpy.findItem(fileWithoutSpacesVirtual6)->_status, SyncFileItem::Status::Success); } // Dehydration via sync works