Skip to content

Commit

Permalink
Merge pull request #4454 from nextcloud/bugfix/allow-manual-rename-fi…
Browse files Browse the repository at this point in the history
…les-with-spaces

Bugfix/allow manual rename files with spaces
  • Loading branch information
allexzander authored May 16, 2022
2 parents 96e4fce + 53654b2 commit fc8bfdc
Show file tree
Hide file tree
Showing 15 changed files with 385 additions and 245 deletions.
4 changes: 1 addition & 3 deletions src/csync/csync_exclude.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/csync/csync_exclude.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/gui/folder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,11 @@ void Folder::scheduleThisFolderSoon()
}
}

void Folder::acceptInvalidFileName(const QString &filePath)
{
_engine->addAcceptedInvalidFileName(filePath);
}

void Folder::setSaveBackwardsCompatible(bool save)
{
_saveBackwardsCompatible = save;
Expand Down
2 changes: 2 additions & 0 deletions src/gui/folder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
131 changes: 110 additions & 21 deletions src/gui/invalidfilenamedialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "propagateremotemove.h"
#include "ui_invalidfilenamedialog.h"

#include "filesystem.h"
#include <folder.h>

#include <QPushButton>
Expand Down Expand Up @@ -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);

Expand All @@ -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();
}

Expand All @@ -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)
Expand All @@ -162,23 +225,49 @@ void InvalidFilenameDialog::onMoveJobFinished()
QDialog::accept();
}

void InvalidFilenameDialog::onRemoteFileAlreadyExists(const QVariantMap &values)
void InvalidFilenameDialog::onRemoteDestinationFileAlreadyExists(const QVariantMap &values)
{
Q_UNUSED(values);

_ui->errorLabel->setText(tr("Cannot rename file because a file with the same name does already exist on the server. Please pick another name."));
_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();
}
}
14 changes: 12 additions & 2 deletions src/gui/invalidfilenamedialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class InvalidFilenameDialog : public QDialog

void accept() override;

signals:
void acceptedInvalidName(const QString &filePath);

private:
std::unique_ptr<Ui::InvalidFilenameDialog> _ui;

Expand All @@ -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();
};
}
4 changes: 4 additions & 0 deletions src/gui/tray/activitylistmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit fc8bfdc

Please sign in to comment.