浏览代码

Allow manual renaming of files and folders with spaces. Allow uploading invalid file name via the InvalidFileName dialog.

Signed-off-by: alex-z <blackslayer4@gmail.com>
alex-z 3 年之前
父节点
当前提交
53654b2a50

+ 1 - 3
src/csync/csync_exclude.cpp

@@ -165,9 +165,7 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QString &path, bool exclu
     // as '.' is a separator that is not stored internally, so let's
     // not allow to sync those to avoid file loss/ambiguities (#416)
     if (blen > 1) {
-        if (bname.at(blen - 1) == QLatin1Char(' ')) {
-            return CSYNC_FILE_EXCLUDE_TRAILING_SPACE;
-        } else if (bname.at(blen - 1) == QLatin1Char('.')) {
+        if (bname.at(blen - 1) == QLatin1Char('.')) {
             return CSYNC_FILE_EXCLUDE_INVALID_CHAR;
         }
     }

+ 2 - 0
src/csync/csync_exclude.h

@@ -45,6 +45,8 @@ enum CSYNC_EXCLUDE_TYPE {
   CSYNC_FILE_EXCLUDE_CONFLICT,
   CSYNC_FILE_EXCLUDE_CANNOT_ENCODE,
   CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED,
+  CSYNC_FILE_EXCLUDE_LEADING_SPACE,
+  CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE,
 };
 
 class ExcludedFilesTest;

+ 5 - 0
src/gui/folder.cpp

@@ -1225,6 +1225,11 @@ void Folder::scheduleThisFolderSoon()
     }
 }
 
+void Folder::acceptInvalidFileName(const QString &filePath)
+{
+    _engine->addAcceptedInvalidFileName(filePath);
+}
+
 void Folder::setSaveBackwardsCompatible(bool save)
 {
     _saveBackwardsCompatible = save;

+ 2 - 0
src/gui/folder.h

@@ -256,6 +256,8 @@ public:
       */
     void scheduleThisFolderSoon();
 
+    void acceptInvalidFileName(const QString &filePath);
+
     /**
       * Migration: When this flag is true, this folder will save to
       * the backwards-compatible 'Folders' section in the config file.

+ 110 - 21
src/gui/invalidfilenamedialog.cpp

@@ -18,6 +18,7 @@
 #include "propagateremotemove.h"
 #include "ui_invalidfilenamedialog.h"
 
+#include "filesystem.h"
 #include <folder.h>
 
 #include <QPushButton>
@@ -84,13 +85,18 @@ InvalidFilenameDialog::InvalidFilenameDialog(AccountPtr account, Folder *folder,
     _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
     _ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Rename file"));
 
-    _ui->descriptionLabel->setText(tr("The file %1 could not be synced because the name contains characters which are not allowed on this system.").arg(_originalFileName));
-    _ui->explanationLabel->setText(tr("The following characters are not allowed on the system: * \" | & ? , ; : \\ / ~ < >"));
+    _ui->descriptionLabel->setText(tr("The file \"%1\" could not be synced because the name contains characters which are not allowed on this system.").arg(_originalFileName));
+    _ui->explanationLabel->setText(tr("The following characters are not allowed on the system: * \" | & ? , ; : \\ / ~ < > leading/trailing spaces"));
     _ui->filenameLineEdit->setText(filePathFileInfo.fileName());
 
     connect(_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
     connect(_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
 
+    _ui->errorLabel->setText(
+        tr("Checking rename permissions..."));
+    _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+    _ui->filenameLineEdit->setEnabled(false);
+
     connect(_ui->filenameLineEdit, &QLineEdit::textChanged, this,
         &InvalidFilenameDialog::onFilenameLineEditTextChanged);
 
@@ -104,30 +110,88 @@ void InvalidFilenameDialog::checkIfAllowedToRename()
     const auto propfindJob = new PropfindJob(_account, QDir::cleanPath(_folder->remotePath() + _originalFileName));
     propfindJob->setProperties({ "http://owncloud.org/ns:permissions" });
     connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onPropfindPermissionSuccess);
+    connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onPropfindPermissionError);
     propfindJob->start();
 }
 
-void InvalidFilenameDialog::onPropfindPermissionSuccess(const QVariantMap &values)
+void InvalidFilenameDialog::onCheckIfAllowedToRenameComplete(const QVariantMap &values, QNetworkReply *reply)
 {
-    if (!values.contains("permissions")) {
-        return;
-    }
-    const auto remotePermissions = RemotePermissions::fromServerString(values["permissions"].toString());
-    if (!remotePermissions.hasPermission(remotePermissions.CanRename)
-        || !remotePermissions.hasPermission(remotePermissions.CanMove)) {
+    const auto isAllowedToRename = [](const RemotePermissions remotePermissions) {
+        return remotePermissions.hasPermission(remotePermissions.CanRename)
+            && remotePermissions.hasPermission(remotePermissions.CanMove);
+    };
+
+    if (values.contains("permissions") && !isAllowedToRename(RemotePermissions::fromServerString(values["permissions"].toString()))) {
         _ui->errorLabel->setText(
             tr("You don't have the permission to rename this file. Please ask the author of the file to rename it."));
-        _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
-        _ui->filenameLineEdit->setEnabled(false);
+        return;
+    } else if (reply) {
+        if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 404) {
+            _ui->errorLabel->setText(
+                tr("Failed to fetch permissions with error %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()));
+            return;
+        }
     }
+
+    _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
+    _ui->filenameLineEdit->setEnabled(true);
+    _ui->filenameLineEdit->selectAll();
+
+    const auto filePathFileInfo = QFileInfo(_filePath);
+    const auto fileName = filePathFileInfo.fileName();
+    processLeadingOrTrailingSpacesError(fileName);
+}
+
+bool InvalidFilenameDialog::processLeadingOrTrailingSpacesError(const QString &fileName)
+{
+    const auto hasLeadingSpaces = fileName.startsWith(QLatin1Char(' '));
+    const auto hasTrailingSpaces = fileName.endsWith(QLatin1Char(' '));
+
+    _ui->buttonBox->setStandardButtons(_ui->buttonBox->standardButtons() &~ QDialogButtonBox::No);
+
+    if (hasLeadingSpaces || hasTrailingSpaces) {
+        if (hasLeadingSpaces && hasTrailingSpaces) {
+            _ui->errorLabel->setText(tr("Filename contains leading and trailing spaces."));
+        }
+        else if (hasLeadingSpaces) {
+            _ui->errorLabel->setText(tr("Filename contains leading spaces."));
+        } else if (hasTrailingSpaces) {
+            _ui->errorLabel->setText(tr("Filename contains trailing spaces."));
+        }
+
+        if (!Utility::isWindows()) {
+            _ui->buttonBox->setStandardButtons(_ui->buttonBox->standardButtons() | QDialogButtonBox::No);
+            _ui->buttonBox->button(QDialogButtonBox::No)->setText(tr("Use invalid name"));
+            connect(_ui->buttonBox->button(QDialogButtonBox::No), &QPushButton::clicked, this, &InvalidFilenameDialog::useInvalidName);
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+void InvalidFilenameDialog::onPropfindPermissionSuccess(const QVariantMap &values)
+{
+    onCheckIfAllowedToRenameComplete(values);
+}
+
+void InvalidFilenameDialog::onPropfindPermissionError(QNetworkReply *reply)
+{
+    onCheckIfAllowedToRenameComplete({}, reply);
+}
+
+void InvalidFilenameDialog::useInvalidName()
+{
+    emit acceptedInvalidName(_filePath);
 }
 
 void InvalidFilenameDialog::accept()
 {
     _newFilename = _relativeFilePath + _ui->filenameLineEdit->text().trimmed();
     const auto propfindJob = new PropfindJob(_account, QDir::cleanPath(_folder->remotePath() + _newFilename));
-    connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onRemoteFileAlreadyExists);
-    connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onRemoteFileDoesNotExist);
+    connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onRemoteDestinationFileAlreadyExists);
+    connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onRemoteDestinationFileDoesNotExist);
     propfindJob->start();
 }
 
@@ -138,11 +202,10 @@ void InvalidFilenameDialog::onFilenameLineEditTextChanged(const QString &text)
     const auto containsIllegalChars = !illegalContainedCharacters.empty() || text.endsWith(QLatin1Char('.'));
     const auto isTextValid = isNewFileNameDifferent && !containsIllegalChars;
 
-    if (isTextValid) {
-        _ui->errorLabel->setText("");
-    } else {
-        _ui->errorLabel->setText(tr("Filename contains illegal characters: %1")
-                                     .arg(illegalCharacterListToString(illegalContainedCharacters)));
+    _ui->errorLabel->setText("");
+
+    if (!processLeadingOrTrailingSpacesError(text) && !isTextValid){
+        _ui->errorLabel->setText(tr("Filename contains illegal characters: %1").arg(illegalCharacterListToString(illegalContainedCharacters)));
     }
 
     _ui->buttonBox->button(QDialogButtonBox::Ok)
@@ -162,7 +225,7 @@ void InvalidFilenameDialog::onMoveJobFinished()
     QDialog::accept();
 }
 
-void InvalidFilenameDialog::onRemoteFileAlreadyExists(const QVariantMap &values)
+void InvalidFilenameDialog::onRemoteDestinationFileAlreadyExists(const QVariantMap &values)
 {
     Q_UNUSED(values);
 
@@ -170,15 +233,41 @@ void InvalidFilenameDialog::onRemoteFileAlreadyExists(const QVariantMap &values)
     _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
 }
 
-void InvalidFilenameDialog::onRemoteFileDoesNotExist(QNetworkReply *reply)
+void InvalidFilenameDialog::onRemoteDestinationFileDoesNotExist(QNetworkReply *reply)
 {
     Q_UNUSED(reply);
 
-    // File does not exist. We can rename it.
+    const auto propfindJob = new PropfindJob(_account, QDir::cleanPath(_folder->remotePath() + _originalFileName));
+    connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onRemoteSourceFileAlreadyExists);
+    connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onRemoteSourceFileDoesNotExist);
+    propfindJob->start();
+}
+
+void InvalidFilenameDialog::onRemoteSourceFileAlreadyExists(const QVariantMap &values)
+{
+    Q_UNUSED(values);
+
+    // Remote source file exists. We need to start MoveJob to rename it
     const auto remoteSource = QDir::cleanPath(_folder->remotePath() + _originalFileName);
     const auto remoteDestionation = QDir::cleanPath(_account->davUrl().path() + _folder->remotePath() + _newFilename);
     const auto moveJob = new MoveJob(_account, remoteSource, remoteDestionation, this);
     connect(moveJob, &MoveJob::finishedSignal, this, &InvalidFilenameDialog::onMoveJobFinished);
     moveJob->start();
 }
+
+void InvalidFilenameDialog::onRemoteSourceFileDoesNotExist(QNetworkReply *reply)
+{
+    Q_UNUSED(reply);
+
+    // It's a new file we've just created locally. We will attempt to rename it locally.
+    const auto localSource = QDir::cleanPath(_folder->path() + _originalFileName);
+    const auto localDestionation = QDir::cleanPath(_folder->path()+ _newFilename);
+
+    QString error;
+    if (!FileSystem::rename(localSource, localDestionation, &error)) {
+        _ui->errorLabel->setText(tr("Could not rename local file. %1").arg(error));
+        return;
+    }
+    QDialog::accept();
+}
 }

+ 12 - 2
src/gui/invalidfilenamedialog.h

@@ -41,6 +41,9 @@ public:
 
     void accept() override;
 
+signals:
+    void acceptedInvalidName(const QString &filePath);
+
 private:
     std::unique_ptr<Ui::InvalidFilenameDialog> _ui;
 
@@ -53,9 +56,16 @@ private:
 
     void onFilenameLineEditTextChanged(const QString &text);
     void onMoveJobFinished();
-    void onRemoteFileAlreadyExists(const QVariantMap &values);
-    void onRemoteFileDoesNotExist(QNetworkReply *reply);
+    void onRemoteDestinationFileAlreadyExists(const QVariantMap &values);
+    void onRemoteDestinationFileDoesNotExist(QNetworkReply *reply);
+    void onRemoteSourceFileAlreadyExists(const QVariantMap &values);
+    void onRemoteSourceFileDoesNotExist(QNetworkReply *reply);
     void checkIfAllowedToRename();
+    void onCheckIfAllowedToRenameComplete(const QVariantMap &values, QNetworkReply *reply = nullptr);
+    bool processLeadingOrTrailingSpacesError(const QString &fileName);
     void onPropfindPermissionSuccess(const QVariantMap &values);
+    void onPropfindPermissionError(QNetworkReply *reply = nullptr);
+private slots:
+    void useInvalidName();
 };
 }

+ 4 - 0
src/gui/tray/activitylistmodel.cpp

@@ -688,6 +688,10 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
         connect(_currentInvalidFilenameDialog, &InvalidFilenameDialog::accepted, folder, [folder]() {
             folder->scheduleThisFolderSoon();
         });
+        connect(_currentInvalidFilenameDialog, &InvalidFilenameDialog::acceptedInvalidName, folder, [folder](const QString& filePath) {
+            folder->acceptInvalidFileName(filePath);
+            folder->scheduleThisFolderSoon();
+        });
         _currentInvalidFilenameDialog->open();
         ownCloudGui::raiseDialog(_currentInvalidFilenameDialog);
         return;

+ 47 - 61
src/libsync/discovery.cpp

@@ -37,54 +37,6 @@ namespace OCC {
 
 Q_LOGGING_CATEGORY(lcDisco, "sync.discovery", QtInfoMsg)
 
-
-bool ProcessDirectoryJob::checkForInvalidFileName(const PathTuple &path,
-    const std::map<QString, Entries> &entries, Entries &entry)
-{
-    const auto originalFileName = entry.localEntry.isValid() ? entry.localEntry.name : entry.serverEntry.name;
-    const auto newFileName = originalFileName.trimmed();
-
-    if (originalFileName == newFileName) {
-        return true;
-    }
-
-    const auto entriesIter = entries.find(newFileName);
-    if (entriesIter != entries.end()) {
-        QString errorMessage;
-        const auto newFileNameEntry = entriesIter->second;
-        if (entry.serverEntry.isValid() && newFileNameEntry.serverEntry.isValid()) {
-            errorMessage = tr("File contains trailing spaces and could not be renamed, because a file with the same name already exists on the server.");
-        }
-        if (entry.localEntry.isValid() && newFileNameEntry.localEntry.isValid()) {
-            errorMessage = tr("File contains trailing spaces and could not be renamed, because a file with the same name already exists locally.");
-        }
-
-        if (!errorMessage.isEmpty()) {
-            auto item = SyncFileItemPtr::create();
-            if ((entry.localEntry.isValid() && entry.localEntry.isDirectory) || (entry.serverEntry.isValid() && entry.serverEntry.isDirectory)) {
-                item->_type = CSyncEnums::ItemTypeDirectory;
-            } else {
-                item->_type = CSyncEnums::ItemTypeFile;
-            }
-            item->_file = path._target;
-            item->_originalFile = path._target;
-            item->_instruction = CSYNC_INSTRUCTION_ERROR;
-            item->_status = SyncFileItem::NormalError;
-            item->_errorString = errorMessage;
-            processFileFinalize(item, path, false, ParentNotChanged, ParentNotChanged);
-            return false;
-        }
-    }
-
-    if (entry.localEntry.isValid()) {
-        entry.localEntry.renameName = newFileName;
-    } else {
-        entry.serverEntry.renameName = newFileName;
-    }
-
-    return true;
-}
-
 void ProcessDirectoryJob::start()
 {
     qCInfo(lcDisco) << "STARTING" << _currentFolder._server << _queryServer << _currentFolder._local << _queryLocal;
@@ -222,33 +174,46 @@ void ProcessDirectoryJob::process()
         // local stat function.
         // Recall file shall not be ignored (#4420)
         bool isHidden = e.localEntry.isHidden || (!f.first.isEmpty() && f.first[0] == '.' && f.first != QLatin1String(".sys.admin#recall#"));
-#ifdef Q_OS_WIN
-        // exclude ".lnk" files as they are not essential, but, causing troubles when enabling the VFS due to QFileInfo::isDir() and other methods are freezing, which causes the ".lnk" files to start hydrating and freezing the app eventually.
-        const bool isServerEntryWindowsShortcut = !e.localEntry.isValid() && e.serverEntry.isValid() && !e.serverEntry.isDirectory && FileSystem::isLnkFile(e.serverEntry.name);
-#else
-        const bool isServerEntryWindowsShortcut = false;
-#endif
-        if (handleExcluded(path._target, e.localEntry.name,
-                e.localEntry.isDirectory || e.serverEntry.isDirectory, isHidden,
-                e.localEntry.isSymLink || isServerEntryWindowsShortcut))
+        if (handleExcluded(path._target, e, isHidden))
             continue;
 
         if (_queryServer == InBlackList || _discoveryData->isInSelectiveSyncBlackList(path._original)) {
             processBlacklisted(path, e.localEntry, e.dbEntry);
             continue;
         }
-        if (!checkForInvalidFileName(path, entries, e)) {
-            continue;
-        }
         processFile(std::move(path), e.localEntry, e.serverEntry, e.dbEntry);
     }
     QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
 }
 
-bool ProcessDirectoryJob::handleExcluded(const QString &path, const QString &localName, bool isDirectory, bool isHidden, bool isSymlink)
+bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &entries, bool isHidden)
 {
+    const auto isDirectory = entries.localEntry.isDirectory || entries.serverEntry.isDirectory;
+
     auto excluded = _discoveryData->_excludes->traversalPatternMatch(path, isDirectory ? ItemTypeDirectory : ItemTypeFile);
 
+    const auto fileName = path.mid(path.lastIndexOf('/') + 1);
+
+    if (excluded == CSYNC_NOT_EXCLUDED) {
+        const auto endsWithSpace = fileName.endsWith(QLatin1Char(' '));
+        const auto startsWithSpace = fileName.startsWith(QLatin1Char(' '));
+        if (startsWithSpace && endsWithSpace) {
+            excluded = CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE;
+        } else if (endsWithSpace) {
+            excluded = CSYNC_FILE_EXCLUDE_TRAILING_SPACE;
+        } else if (startsWithSpace) {
+            excluded = CSYNC_FILE_EXCLUDE_LEADING_SPACE;
+        }
+    }
+
+    // we don't need to trigger a warning if trailing/leading space file is already on the server or has already been synced down
+    // only if the OS supports trailing/leading spaces
+    const auto wasSyncedAlreadyAndOsSupportsSpaces = !Utility::isWindows() && (entries.serverEntry.isValid() || entries.dbEntry.isValid());
+    if ((excluded == CSYNC_FILE_EXCLUDE_LEADING_SPACE || excluded == CSYNC_FILE_EXCLUDE_TRAILING_SPACE || excluded == CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE)
+            && (wasSyncedAlreadyAndOsSupportsSpaces || _discoveryData->_leadingAndTrailingSpacesFilesAllowed.contains(_discoveryData->_localDir + path))) {
+        excluded = CSYNC_NOT_EXCLUDED;
+    }
+
     // FIXME: move to ExcludedFiles 's regexp ?
     bool isInvalidPattern = false;
     if (excluded == CSYNC_NOT_EXCLUDED && !_discoveryData->_invalidFilenameRx.pattern().isEmpty()) {
@@ -260,6 +225,8 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const QString &loc
     if (excluded == CSYNC_NOT_EXCLUDED && _discoveryData->_ignoreHiddenFiles && isHidden) {
         excluded = CSYNC_FILE_EXCLUDE_HIDDEN;
     }
+
+    const auto &localName = entries.localEntry.name;
     if (excluded == CSYNC_NOT_EXCLUDED && !localName.isEmpty()
             && _discoveryData->_serverBlacklistedFiles.contains(localName)) {
         excluded = CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED;
@@ -280,6 +247,17 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const QString &loc
         }
     }
 
+#ifdef Q_OS_WIN
+    // exclude ".lnk" files as they are not essential, but, causing troubles when enabling the VFS due to
+    // QFileInfo::isDir() and other methods are freezing, which causes the ".lnk" files to start hydrating and freezing
+    // the app eventually.
+    const bool isServerEntryWindowsShortcut = !entries.localEntry.isValid() && entries.serverEntry.isValid()
+        && !entries.serverEntry.isDirectory && FileSystem::isLnkFile(entries.serverEntry.name);
+#else
+    const bool isServerEntryWindowsShortcut = false;
+#endif
+    const auto isSymlink = entries.localEntry.isSymLink || isServerEntryWindowsShortcut;
+
     if (excluded == CSYNC_NOT_EXCLUDED && !isSymlink) {
         return false;
     } else if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED || excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) {
@@ -329,6 +307,14 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const QString &loc
             item->_errorString = tr("Filename contains trailing spaces.");
             item->_status = SyncFileItem::FileNameInvalid;
             break;
+        case CSYNC_FILE_EXCLUDE_LEADING_SPACE:
+            item->_errorString = tr("Filename contains leading spaces.");
+            item->_status = SyncFileItem::FileNameInvalid;
+            break;
+        case CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE:
+            item->_errorString = tr("Filename contains leading and trailing spaces.");
+            item->_status = SyncFileItem::FileNameInvalid;
+            break;
         case CSYNC_FILE_EXCLUDE_LONG_FILENAME:
             item->_errorString = tr("Filename is too long.");
             item->_status = SyncFileItem::FileNameInvalid;

+ 1 - 4
src/libsync/discovery.h

@@ -150,8 +150,6 @@ private:
         }
     };
 
-    bool checkForInvalidFileName(const PathTuple &path, const std::map<QString, Entries> &entries, Entries &entry);
-
     /** Iterate over entries inside the directory (non-recursively).
      *
      * Called once _serverEntries and _localEntries are filled
@@ -162,8 +160,7 @@ private:
 
     // return true if the file is excluded.
     // path is the full relative path of the file. localName is the base name of the local entry.
-    bool handleExcluded(const QString &path, const QString &localName, bool isDirectory,
-        bool isHidden, bool isSymlink);
+    bool handleExcluded(const QString &path, const Entries &entries, bool isHidden);
 
     /** Reconcile local/remote/db information for a single item.
      *

+ 1 - 0
src/libsync/discoveryphase.h

@@ -271,6 +271,7 @@ public:
     ExcludedFiles *_excludes;
     QRegularExpression _invalidFilenameRx; // FIXME: maybe move in ExcludedFiles
     QStringList _serverBlacklistedFiles; // The blacklist from the capabilities
+    QStringList _leadingAndTrailingSpacesFilesAllowed;
     bool _ignoreHiddenFiles = false;
     std::function<bool(const QString &)> _shouldDiscoverLocaly;
 

+ 7 - 0
src/libsync/syncengine.cpp

@@ -548,6 +548,7 @@ void SyncEngine::startSync()
     emit transmissionProgress(*_progressInfo);
 
     _discoveryPhase.reset(new DiscoveryPhase);
+    _discoveryPhase->_leadingAndTrailingSpacesFilesAllowed = _leadingAndTrailingSpacesFilesAllowed;
     _discoveryPhase->_account = _account;
     _discoveryPhase->_excludes = _excludedFiles.data();
     const QString excludeFilePath = _localPath + QStringLiteral(".sync-exclude.lst");
@@ -852,6 +853,7 @@ void SyncEngine::finalize(bool success)
     _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
 
     _clearTouchedFilesTimer.start();
+    _leadingAndTrailingSpacesFilesAllowed.clear();
 }
 
 void SyncEngine::slotProgress(const SyncFileItem &item, qint64 current)
@@ -924,6 +926,11 @@ void SyncEngine::slotClearTouchedFiles()
     _touchedFiles.clear();
 }
 
+void SyncEngine::addAcceptedInvalidFileName(const QString& filePath)
+{
+    _leadingAndTrailingSpacesFilesAllowed.append(filePath);
+}
+
 bool SyncEngine::wasFileTouched(const QString &fn) const
 {
     // Start from the end (most recent) and look for our path. Check the time just in case.

+ 4 - 0
src/libsync/syncengine.h

@@ -74,6 +74,8 @@ public:
     bool ignoreHiddenFiles() const { return _ignore_hidden_files; }
     void setIgnoreHiddenFiles(bool ignore) { _ignore_hidden_files = ignore; }
 
+    void addAcceptedInvalidFileName(const QString& filePath);
+
     ExcludedFiles &excludedFiles() { return *_excludedFiles; }
     Utility::StopWatch &stopWatch() { return _stopWatch; }
     SyncFileStatusTracker &syncFileStatusTracker() { return *_syncFileStatusTracker; }
@@ -295,6 +297,8 @@ private:
     LocalDiscoveryStyle _lastLocalDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
     LocalDiscoveryStyle _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
     std::set<QString> _localDiscoveryPaths;
+
+    QStringList _leadingAndTrailingSpacesFilesAllowed;
 };
 }
 

+ 1 - 1
src/libsync/syncresult.cpp

@@ -141,7 +141,7 @@ void SyncResult::processCompletedItem(const SyncFileItemPtr &item)
         if (!_firstItemError) {
             _firstItemError = item;
         }
-    } else if (item->_status == SyncFileItem::Conflict) {
+    } else if (item->_status == SyncFileItem::Conflict || item->_status == SyncFileItem::FileNameInvalid) {
         if (item->_instruction == CSYNC_INSTRUCTION_CONFLICT) {
             _numNewConflictItems++;
             if (!_firstNewConflictItem) {

+ 91 - 88
test/testlocaldiscovery.cpp

@@ -210,10 +210,10 @@ private slots:
         FakeFolder fakeFolder{FileInfo{}};
         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
         const QString fileWithSpaces1(" foo");
-        const QString fileWithSpaces2(" bar  ");
+        const QString fileWithSpaces2(" bar ");
         const QString fileWithSpaces3("bla ");
         const QString fileWithSpaces4("A/ foo");
-        const QString fileWithSpaces5("A/ bar  ");
+        const QString fileWithSpaces5("A/ bar ");
         const QString fileWithSpaces6("A/bla ");
 
         fakeFolder.localModifier().insert(fileWithSpaces1);
@@ -223,76 +223,50 @@ private slots:
         fakeFolder.localModifier().insert(fileWithSpaces4);
         fakeFolder.localModifier().insert(fileWithSpaces5);
         fakeFolder.localModifier().insert(fileWithSpaces6);
-        fakeFolder.localModifier().mkdir(QStringLiteral("  with spaces  "));
+        fakeFolder.localModifier().mkdir(QStringLiteral(" with spaces "));
 
-        QVERIFY(fakeFolder.syncOnce());
-
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces1.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces1));
-
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces2.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces2));
-
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces3.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces3));
+        ItemCompletedSpy completeSpy(fakeFolder);
+        completeSpy.clear();
 
-        QVERIFY(fakeFolder.currentLocalState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces4));
+        QVERIFY(fakeFolder.syncOnce());
 
-        QVERIFY(fakeFolder.currentLocalState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces5));
+        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(QStringLiteral(" with spaces "))->_status, SyncFileItem::Status::FileNameInvalid);
 
-        QVERIFY(fakeFolder.currentLocalState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces6));
+        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);
+        fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + QStringLiteral(" with spaces "));
 
-        QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("with spaces")));
-        QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("  with spaces  ")));
+        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());
 
-        QVERIFY(fakeFolder.currentRemoteState().find(fileWithSpaces1.trimmed()));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces1));
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces1.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces1));
-
-        QVERIFY(fakeFolder.currentRemoteState().find(fileWithSpaces2.trimmed()));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces2));
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces2.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces2));
-
-        QVERIFY(fakeFolder.currentRemoteState().find(fileWithSpaces3.trimmed()));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces3));
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces3.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces3));
-
-        QVERIFY(fakeFolder.currentRemoteState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces4));
-        QVERIFY(fakeFolder.currentLocalState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces4));
-
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces5));
-        QVERIFY(fakeFolder.currentLocalState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces5));
-
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces6));
-        QVERIFY(fakeFolder.currentLocalState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces6));
-
-        QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("with spaces")));
-        QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("  with spaces  ")));
-        QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("with spaces")));
-        QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("  with spaces  ")));
+        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(QStringLiteral(" with spaces "))->_status, SyncFileItem::Status::Success);
     }
 
-    void testCreateFileWithTrailingSpaces_localAndRemoteTrimmedDoNotExist_renameFile()
+    void testCreateFileWithTrailingSpaces_remoteDontGetRenamedAutomatically()
     {
-        FakeFolder fakeFolder{FileInfo{}};
+        // 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.
+        FakeFolder fakeFolder{FileInfo()};
         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
         const QString fileWithSpaces4("A/ foo");
-        const QString fileWithSpaces5("A/ bar  ");
+        const QString fileWithSpaces5("A/ bar ");
         const QString fileWithSpaces6("A/bla ");
 
         fakeFolder.remoteModifier().mkdir("A");
@@ -300,43 +274,69 @@ private slots:
         fakeFolder.remoteModifier().insert(fileWithSpaces5);
         fakeFolder.remoteModifier().insert(fileWithSpaces6);
 
-        qDebug() << fakeFolder.currentRemoteState();
+        ItemCompletedSpy completeSpy(fakeFolder);
+        completeSpy.clear();
 
         QVERIFY(fakeFolder.syncOnce());
 
-        qDebug() << fakeFolder.currentRemoteState();
+        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(fileWithSpaces4)->_status, SyncFileItem::Status::Success);
+            QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::Success);
+            QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::Success);
+        }
+    }
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces4));
+    void testCreateFileWithTrailingSpaces_remoteGetRenamedManually()
+    {
+        // 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.
+        FakeFolder fakeFolder{FileInfo()};
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        const QString fileWithSpaces4("A/ foo");
+        const QString fileWithSpaces5("A/ bar ");
+        const QString fileWithSpaces6("A/bla ");
+
+        const QString fileWithoutSpaces4("A/foo");
+        const QString fileWithoutSpaces5("A/bar");
+        const QString fileWithoutSpaces6("A/bla");
+
+        fakeFolder.remoteModifier().mkdir("A");
+        fakeFolder.remoteModifier().insert(fileWithSpaces4);
+        fakeFolder.remoteModifier().insert(fileWithSpaces5);
+        fakeFolder.remoteModifier().insert(fileWithSpaces6);
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces5));
+        ItemCompletedSpy completeSpy(fakeFolder);
+        completeSpy.clear();
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces6));
+        QVERIFY(fakeFolder.syncOnce());        
 
-        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(fileWithSpaces4)->_status, SyncFileItem::Status::Success);
+            QCOMPARE(completeSpy.findItem(fileWithSpaces5)->_status, SyncFileItem::Status::Success);
+            QCOMPARE(completeSpy.findItem(fileWithSpaces6)->_status, SyncFileItem::Status::Success);
+        }
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces4));
-        QVERIFY(fakeFolder.currentLocalState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces4));
+        fakeFolder.remoteModifier().rename(fileWithSpaces4, fileWithoutSpaces4);
+        fakeFolder.remoteModifier().rename(fileWithSpaces5, fileWithoutSpaces5);
+        fakeFolder.remoteModifier().rename(fileWithSpaces6, fileWithoutSpaces6);
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces5));
-        QVERIFY(fakeFolder.currentLocalState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces5));
+        completeSpy.clear();
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces6));
-        QVERIFY(fakeFolder.currentLocalState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces6));
+        QVERIFY(fakeFolder.syncOnce());
 
-        auto expectedState = fakeFolder.currentLocalState();
-        QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
+        QCOMPARE(completeSpy.findItem(fileWithoutSpaces4)->_status, SyncFileItem::Status::Success);
+        QCOMPARE(completeSpy.findItem(fileWithoutSpaces5)->_status, SyncFileItem::Status::Success);
+        QCOMPARE(completeSpy.findItem(fileWithoutSpaces6)->_status, SyncFileItem::Status::Success);
     }
 
-    void testCreateFileWithTrailingSpaces_localTrimmedDoesExist_dontRenameAndUploadFile()
+    void testCreateFileWithTrailingSpaces_localTrimmedAlsoCreated_dontRenameAutomaticallyAndDontUploadFile()
     {
         FakeFolder fakeFolder{FileInfo{}};
         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
@@ -344,9 +344,9 @@ private slots:
         const QString fileTrimmed("foo");
 
         fakeFolder.localModifier().insert(fileTrimmed);
-        QVERIFY(fakeFolder.syncOnce());
         fakeFolder.localModifier().insert(fileWithSpaces);
-        QVERIFY(!fakeFolder.syncOnce());
+
+        QVERIFY(fakeFolder.syncOnce());
 
         QVERIFY(fakeFolder.currentRemoteState().find(fileTrimmed));
         QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces));
@@ -354,7 +354,7 @@ private slots:
         QVERIFY(fakeFolder.currentLocalState().find(fileTrimmed));
     }
 
-    void testCreateFileWithTrailingSpaces_localTrimmedAlsoCreated_dontRenameAndUploadFile()
+    void testCreateFileWithTrailingSpaces_localTrimmedAlsoCreated_dontRenameAutomaticallyAndUploadBothFiles()
     {
         FakeFolder fakeFolder{FileInfo{}};
         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
@@ -363,10 +363,13 @@ private slots:
 
         fakeFolder.localModifier().insert(fileTrimmed);
         fakeFolder.localModifier().insert(fileWithSpaces);
-        QVERIFY(!fakeFolder.syncOnce());
+
+        fakeFolder.syncEngine().addAcceptedInvalidFileName(fakeFolder.localPath() + fileWithSpaces);
+
+        QVERIFY(fakeFolder.syncOnce());
 
         QVERIFY(fakeFolder.currentRemoteState().find(fileTrimmed));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces));
+        QVERIFY(fakeFolder.currentRemoteState().find(fileWithSpaces));
         QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces));
         QVERIFY(fakeFolder.currentLocalState().find(fileTrimmed));
     }
@@ -376,7 +379,7 @@ private slots:
         FakeFolder fakeFolder{FileInfo{}};
         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
         const QString fileWithSpaces1(" foo");
-        const QString fileWithSpaces2(" bar  ");
+        const QString fileWithSpaces2(" bar ");
         const QString fileWithSpaces3("bla ");
 
         fakeFolder.localModifier().insert(fileWithSpaces1);

+ 97 - 65
test/testsyncvirtualfiles.cpp

@@ -763,17 +763,17 @@ private slots:
         QVERIFY(dbRecord(fakeFolder, "case6-rename")._type == ItemTypeFile);
     }
 
-    void testCreateFileWithTrailingSpaces_localAndRemoteTrimmedDoNotExist_renameAndUploadFile()
+    void testCreateFileWithTrailingSpaces_acceptAndRejectInvalidFileName()
     {
         FakeFolder fakeFolder{ FileInfo() };
         setupVfs(fakeFolder);
         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
 
         const QString fileWithSpaces1(" foo");
-        const QString fileWithSpaces2(" bar  ");
+        const QString fileWithSpaces2(" bar ");
         const QString fileWithSpaces3("bla ");
         const QString fileWithSpaces4("A/ foo");
-        const QString fileWithSpaces5("A/ bar  ");
+        const QString fileWithSpaces5("A/ bar ");
         const QString fileWithSpaces6("A/bla ");
 
         fakeFolder.localModifier().insert(fileWithSpaces1);
@@ -783,101 +783,133 @@ private slots:
         fakeFolder.localModifier().insert(fileWithSpaces4);
         fakeFolder.localModifier().insert(fileWithSpaces5);
         fakeFolder.localModifier().insert(fileWithSpaces6);
+        
+        ItemCompletedSpy completeSpy(fakeFolder);
+        completeSpy.clear();
 
         QVERIFY(fakeFolder.syncOnce());
 
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces1.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces1));
-
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces2.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces2));
+        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);
 
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces3.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces3));
+        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);
 
-        QVERIFY(fakeFolder.currentLocalState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces4));
+        completeSpy.clear();
 
-        QVERIFY(fakeFolder.currentLocalState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces5));
+        QVERIFY(fakeFolder.syncOnce());
 
-        QVERIFY(fakeFolder.currentLocalState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces6));
+        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);
+    }
 
-        QVERIFY(fakeFolder.syncOnce());
+    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.
+        FakeFolder fakeFolder{ FileInfo() };
+        setupVfs(fakeFolder);
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        const QString fileWithSpaces4("A/ foo");
+        const QString fileWithSpaces5("A/ bar ");
+        const QString fileWithSpaces6("A/bla ");
 
-        QVERIFY(fakeFolder.currentRemoteState().find(fileWithSpaces1.trimmed()));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces1));
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces1.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces1));
+        const QString fileWithSpacesVirtual4(fileWithSpaces4 + DVSUFFIX);
+        const QString fileWithSpacesVirtual5(fileWithSpaces5 + DVSUFFIX);
+        const QString fileWithSpacesVirtual6(fileWithSpaces6 + DVSUFFIX);
 
-        QVERIFY(fakeFolder.currentRemoteState().find(fileWithSpaces2.trimmed()));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces2));
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces2.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces2));
+        fakeFolder.remoteModifier().mkdir("A");
+        fakeFolder.remoteModifier().insert(fileWithSpaces4);
+        fakeFolder.remoteModifier().insert(fileWithSpaces5);
+        fakeFolder.remoteModifier().insert(fileWithSpaces6);
 
-        QVERIFY(fakeFolder.currentRemoteState().find(fileWithSpaces3.trimmed()));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces3));
-        QVERIFY(fakeFolder.currentLocalState().find(fileWithSpaces3.trimmed()));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces3));
+        ItemCompletedSpy completeSpy(fakeFolder);
+        completeSpy.clear();
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces4));
-        QVERIFY(fakeFolder.currentLocalState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces4));
+        QVERIFY(fakeFolder.syncOnce());
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces5));
-        QVERIFY(fakeFolder.currentLocalState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces5));
+        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);
+        }
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces6));
-        QVERIFY(fakeFolder.currentLocalState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentLocalState().find(fileWithSpaces6));
     }
 
-    void testCreateFileWithTrailingSpaces_localAndRemoteTrimmedDoNotExist_renameFile()
+    void testCreateFileWithTrailingSpaces_remoteGetRenamedManually()
     {
-        FakeFolder fakeFolder{ FileInfo() };
+        // 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.
+        FakeFolder fakeFolder{FileInfo()};
         setupVfs(fakeFolder);
         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
         const QString fileWithSpaces4("A/ foo");
-        const QString fileWithSpaces5("A/ bar  ");
+        const QString fileWithSpaces5("A/ bar ");
         const QString fileWithSpaces6("A/bla ");
 
+        const QString fileWithSpacesVirtual4(fileWithSpaces4 + DVSUFFIX);
+        const QString fileWithSpacesVirtual5(fileWithSpaces5 + DVSUFFIX);
+        const QString fileWithSpacesVirtual6(fileWithSpaces6 + DVSUFFIX);
+
+        const QString fileWithoutSpaces4("A/foo");
+        const QString fileWithoutSpaces5("A/bar");
+        const QString fileWithoutSpaces6("A/bla");
+
+        const QString fileWithoutSpacesVirtual4(fileWithoutSpaces4 + DVSUFFIX);
+        const QString fileWithoutSpacesVirtual5(fileWithoutSpaces5 + DVSUFFIX);
+        const QString fileWithoutSpacesVirtual6(fileWithoutSpaces6 + DVSUFFIX);
+
         fakeFolder.remoteModifier().mkdir("A");
         fakeFolder.remoteModifier().insert(fileWithSpaces4);
         fakeFolder.remoteModifier().insert(fileWithSpaces5);
         fakeFolder.remoteModifier().insert(fileWithSpaces6);
 
-        QVERIFY(fakeFolder.syncOnce());
+        ItemCompletedSpy completeSpy(fakeFolder);
+        completeSpy.clear();
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces4));
+        QVERIFY(fakeFolder.syncOnce());
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces5));
+        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);
+        }
+        
+        fakeFolder.remoteModifier().rename(fileWithSpaces4, fileWithoutSpaces4);
+        fakeFolder.remoteModifier().rename(fileWithSpaces5, fileWithoutSpaces5);
+        fakeFolder.remoteModifier().rename(fileWithSpaces6, fileWithoutSpaces6);
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces6));
+        completeSpy.clear();
 
         QVERIFY(fakeFolder.syncOnce());
 
-        QVERIFY(fakeFolder.currentRemoteState().find("A/foo"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces4));
-        QVERIFY(fakeFolder.currentLocalState().find("A/foo" DVSUFFIX));
-        QVERIFY(!fakeFolder.currentLocalState().find(QString{fileWithSpaces4 + DVSUFFIX}));
-
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bar"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces5));
-        QVERIFY(fakeFolder.currentLocalState().find("A/bar" DVSUFFIX));
-        QVERIFY(!fakeFolder.currentLocalState().find(QString{fileWithSpaces5 + DVSUFFIX}));
-
-        QVERIFY(fakeFolder.currentRemoteState().find("A/bla"));
-        QVERIFY(!fakeFolder.currentRemoteState().find(fileWithSpaces6));
-        QVERIFY(fakeFolder.currentLocalState().find("A/bla" DVSUFFIX));
-        QVERIFY(!fakeFolder.currentLocalState().find(QString{fileWithSpaces6 + DVSUFFIX}));
+        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);
+        }
     }
 
     // Dehydration via sync works