Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/allow manual rename files with spaces #4454

Merged
merged 1 commit into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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