diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index eb850758917aa..e367f37ddc534 100644 --- a/src/csync/csync_exclude.cpp +++ b/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; } } diff --git a/src/csync/csync_exclude.h b/src/csync/csync_exclude.h index 856ac67eff8ad..558dea30b73de 100644 --- a/src/csync/csync_exclude.h +++ b/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; diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 6da7388db12a6..0ad6e8bad7f13 100644 --- a/src/gui/folder.cpp +++ b/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; diff --git a/src/gui/folder.h b/src/gui/folder.h index e18aecbfa8ddd..e38cc091c8e63 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -256,6 +256,8 @@ class Folder : public QObject */ 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. diff --git a/src/gui/invalidfilenamedialog.cpp b/src/gui/invalidfilenamedialog.cpp index 8b318fed3a902..d71783534cc4b 100644 --- a/src/gui/invalidfilenamedialog.cpp +++ b/src/gui/invalidfilenamedialog.cpp @@ -18,6 +18,7 @@ #include "propagateremotemove.h" #include "ui_invalidfilenamedialog.h" +#include "filesystem.h" #include #include @@ -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(); +} } diff --git a/src/gui/invalidfilenamedialog.h b/src/gui/invalidfilenamedialog.h index 2b6ff0dedd33b..f55b76de38025 100644 --- a/src/gui/invalidfilenamedialog.h +++ b/src/gui/invalidfilenamedialog.h @@ -41,6 +41,9 @@ class InvalidFilenameDialog : public QDialog void accept() override; +signals: + void acceptedInvalidName(const QString &filePath); + private: std::unique_ptr _ui; @@ -53,9 +56,16 @@ class InvalidFilenameDialog : public QDialog 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(); }; } diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp index 6d412f67de4e4..469d2d2385678 100644 --- a/src/gui/tray/activitylistmodel.cpp +++ b/src/gui/tray/activitylistmodel.cpp @@ -687,6 +687,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; diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 7527d06d53a46..43cfaa100d08a 100644 --- a/src/libsync/discovery.cpp +++ b/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 &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; diff --git a/src/libsync/discovery.h b/src/libsync/discovery.h index 97c11d9cf45c5..3e18d81d2c0af 100644 --- a/src/libsync/discovery.h +++ b/src/libsync/discovery.h @@ -150,8 +150,6 @@ class ProcessDirectoryJob : public QObject } }; - bool checkForInvalidFileName(const PathTuple &path, const std::map &entries, Entries &entry); - /** Iterate over entries inside the directory (non-recursively). * * Called once _serverEntries and _localEntries are filled @@ -162,8 +160,7 @@ class ProcessDirectoryJob : public QObject // 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. * diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index 3f5240dd1de31..f4bb42704c11f 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -271,6 +271,7 @@ class DiscoveryPhase : public QObject ExcludedFiles *_excludes; QRegularExpression _invalidFilenameRx; // FIXME: maybe move in ExcludedFiles QStringList _serverBlacklistedFiles; // The blacklist from the capabilities + QStringList _leadingAndTrailingSpacesFilesAllowed; bool _ignoreHiddenFiles = false; std::function _shouldDiscoverLocaly; diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 0306e0f8c9549..ac19cd40cbb55 100644 --- a/src/libsync/syncengine.cpp +++ b/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. diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index 87b0cf4df37c4..cbdb65345e503 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -74,6 +74,8 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject 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 slots: LocalDiscoveryStyle _lastLocalDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly; LocalDiscoveryStyle _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly; std::set _localDiscoveryPaths; + + QStringList _leadingAndTrailingSpacesFilesAllowed; }; } diff --git a/src/libsync/syncresult.cpp b/src/libsync/syncresult.cpp index 2e2c01033de71..c058210a494a1 100644 --- a/src/libsync/syncresult.cpp +++ b/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) { diff --git a/test/testlocaldiscovery.cpp b/test/testlocaldiscovery.cpp index 29c80e9a499c7..5cffb73eb5a88 100644 --- a/test/testlocaldiscovery.cpp +++ b/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); diff --git a/test/testsyncvirtualfiles.cpp b/test/testsyncvirtualfiles.cpp index bff6488e45caa..425305b6597c7 100644 --- a/test/testsyncvirtualfiles.cpp +++ b/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