From d3d8b1c631a05d2ade38f67f6a3abeb869ca6d76 Mon Sep 17 00:00:00 2001 From: Felix Weilbach Date: Mon, 6 Sep 2021 15:09:29 +0200 Subject: [PATCH] Add dialog to resolve invalid filenames Fixes #3751 Signed-off-by: Felix Weilbach --- src/gui/CMakeLists.txt | 2 + src/gui/invalidfilenamedialog.cpp | 170 +++++++++++++++++++++++++++ src/gui/invalidfilenamedialog.h | 47 ++++++++ src/gui/invalidfilenamedialog.ui | 121 +++++++++++++++++++ src/gui/tray/ActivityListModel.cpp | 16 +++ src/gui/tray/ActivityListModel.h | 2 + src/libsync/propagateremotemkdir.cpp | 2 +- src/libsync/propagateremotemove.h | 2 +- 8 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 src/gui/invalidfilenamedialog.cpp create mode 100644 src/gui/invalidfilenamedialog.h create mode 100644 src/gui/invalidfilenamedialog.ui diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ef1d69383..94894046d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -18,6 +18,7 @@ set(theme_dir ${CMAKE_SOURCE_DIR}/theme) set(client_UI_SRCS accountsettings.ui conflictdialog.ui + invalidfilenamedialog.ui foldercreationdialog.ui folderwizardsourcepage.ui folderwizardtargetpage.ui @@ -55,6 +56,7 @@ set(client_SRCS accountmanager.cpp accountsettings.cpp application.cpp + invalidfilenamedialog.cpp conflictdialog.cpp conflictsolver.cpp connectionvalidator.cpp diff --git a/src/gui/invalidfilenamedialog.cpp b/src/gui/invalidfilenamedialog.cpp new file mode 100644 index 000000000..64e0e8f5b --- /dev/null +++ b/src/gui/invalidfilenamedialog.cpp @@ -0,0 +1,170 @@ +#include "invalidfilenamedialog.h" +#include "accountfwd.h" +#include "common/syncjournalfilerecord.h" +#include "propagateremotemove.h" +#include "ui_invalidfilenamedialog.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace { +constexpr std::array illegalCharacters({ '\\', '/', ':', '?', '*', '\"', '<', '>', '|' }); + +QVector getIllegalCharsFromString(const QString &string) +{ + QVector result; + for (const auto &character : string) { + if (std::find(illegalCharacters.begin(), illegalCharacters.end(), character) + != illegalCharacters.end()) { + result.push_back(character); + } + } + return result; +} + +QString illegalCharacterListToString(const QVector &illegalCharacters) +{ + QString illegalCharactersString; + if (illegalCharacters.size() > 0) { + illegalCharactersString += illegalCharacters[0]; + } + + for (int i = 1; i < illegalCharacters.count(); ++i) { + if (illegalCharactersString.contains(illegalCharacters[i])) { + continue; + } + illegalCharactersString += " " + illegalCharacters[i]; + } + return illegalCharactersString; +} +} + +namespace OCC { + +InvalidFilenameDialog::InvalidFilenameDialog(AccountPtr account, Folder *folder, QString filePath, QWidget *parent) + : QDialog(parent) + , _ui(new Ui::InvalidFilenameDialog) + , _account(account) + , _folder(folder) + , _filePath(std::move(filePath)) +{ + Q_ASSERT(_account); + Q_ASSERT(_folder); + + const auto filePathFileInfo = QFileInfo(_filePath); + _relativeFilePath = filePathFileInfo.path() + QStringLiteral("/"); + _relativeFilePath = _relativeFilePath.replace(folder->path(), QStringLiteral("")); + _relativeFilePath = _relativeFilePath.isEmpty() ? QStringLiteral("") : _relativeFilePath + QStringLiteral("/"); + + _originalFileName = _relativeFilePath + filePathFileInfo.fileName(); + + _ui->setupUi(this); + _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 it 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->filenameLineEdit->setText(filePathFileInfo.fileName()); + + connect(_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + connect(_ui->filenameLineEdit, &QLineEdit::textChanged, this, + &InvalidFilenameDialog::onFilenameLineEditTextChanged); + + checkIfAllowedToRename(); +} + +InvalidFilenameDialog::~InvalidFilenameDialog() = default; + +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); + propfindJob->start(); +} + +void InvalidFilenameDialog::onPropfindPermissionSuccess(const QVariantMap &values) +{ + if (!values.contains("permissions")) { + return; + } + const auto remotePermissions = RemotePermissions::fromServerString(values["permissions"].toString()); + if (!remotePermissions.hasPermission(remotePermissions.CanRename) + || !remotePermissions.hasPermission(remotePermissions.CanMove)) { + _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); + } +} + +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); + propfindJob->start(); +} + +void InvalidFilenameDialog::onFilenameLineEditTextChanged(const QString &text) +{ + const auto isNewFileNameDifferent = text != _originalFileName; + const auto illegalContainedCharacters = getIllegalCharsFromString(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->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(isTextValid); +} + +void InvalidFilenameDialog::onMoveJobFinished() +{ + const auto job = qobject_cast(sender()); + const auto error = job->reply()->error(); + + if (error != QNetworkReply::NoError) { + _ui->errorLabel->setText(tr("Could not rename file. Please make sure you are connected to the server.")); + return; + } + + QDialog::accept(); +} + +void InvalidFilenameDialog::onRemoteFileAlreadyExists(const QVariantMap &values) +{ + Q_UNUSED(values); + + _ui->errorLabel->setText(tr("Can not rename file because 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) +{ + Q_UNUSED(reply); + + // File does not exist. We can 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(); +} +} diff --git a/src/gui/invalidfilenamedialog.h b/src/gui/invalidfilenamedialog.h new file mode 100644 index 000000000..2ae23ad92 --- /dev/null +++ b/src/gui/invalidfilenamedialog.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace OCC { + +class Folder; + +namespace Ui { + class InvalidFilenameDialog; +} + + +class InvalidFilenameDialog : public QDialog +{ + Q_OBJECT + +public: + explicit InvalidFilenameDialog(AccountPtr account, Folder *folder, QString filePath, QWidget *parent = nullptr); + + ~InvalidFilenameDialog() override; + + void accept() override; + +private: + std::unique_ptr _ui; + + AccountPtr _account; + Folder *_folder; + QString _filePath; + QString _relativeFilePath; + QString _originalFileName; + QString _newFilename; + + void onFilenameLineEditTextChanged(const QString &text); + void onMoveJobFinished(); + void onRemoteFileAlreadyExists(const QVariantMap &values); + void onRemoteFileDoesNotExist(QNetworkReply *reply); + void checkIfAllowedToRename(); + void onPropfindPermissionSuccess(const QVariantMap &values); +}; +} diff --git a/src/gui/invalidfilenamedialog.ui b/src/gui/invalidfilenamedialog.ui new file mode 100644 index 000000000..68008709d --- /dev/null +++ b/src/gui/invalidfilenamedialog.ui @@ -0,0 +1,121 @@ + + + OCC::InvalidFilenameDialog + + + + 0 + 0 + 411 + 192 + + + + Invalid filename + + + + QLayout::SetDefaultConstraint + + + + + The file could not be synced because it contains characters which are not allowed on this system. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + false + + + + + + + Error + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + false + + + + + + + Please enter a new name for the file: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + false + + + + + + + New filename + + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 1a9a7578a..22ce0ae52 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -28,6 +28,7 @@ #include "accessmanager.h" #include "owncloudgui.h" #include "guiutility.h" +#include "invalidfilenamedialog.h" #include "ActivityData.h" #include "ActivityListModel.h" @@ -446,6 +447,21 @@ void ActivityListModel::triggerDefaultAction(int activityIndex) _currentConflictDialog->open(); ownCloudGui::raiseDialog(_currentConflictDialog); return; + } else if (activity._status == SyncFileItem::FileNameInvalid) { + if (!_currentInvalidFilenameDialog.isNull()) { + _currentInvalidFilenameDialog->close(); + } + + auto folder = FolderMan::instance()->folder(activity._folder); + const auto folderDir = QDir(folder->path()); + _currentInvalidFilenameDialog = new InvalidFilenameDialog(_accountState->account(), folder, + folderDir.filePath(activity._file)); + connect(_currentInvalidFilenameDialog, &InvalidFilenameDialog::accepted, folder, [folder]() { + folder->scheduleThisFolderSoon(); + }); + _currentInvalidFilenameDialog->open(); + ownCloudGui::raiseDialog(_currentInvalidFilenameDialog); + return; } if (path.isValid()) { diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index 554ac320f..6c67af92d 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -27,6 +27,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcActivity) class AccountState; class ConflictDialog; +class InvalidFilenameDialog; /** * @brief The ActivityListModel @@ -115,6 +116,7 @@ private: bool _showMoreActivitiesAvailableEntry = false; QPointer _currentConflictDialog; + QPointer _currentInvalidFilenameDialog; }; } diff --git a/src/libsync/propagateremotemkdir.cpp b/src/libsync/propagateremotemkdir.cpp index 4e27b42a8..af056d5e9 100644 --- a/src/libsync/propagateremotemkdir.cpp +++ b/src/libsync/propagateremotemkdir.cpp @@ -139,7 +139,7 @@ void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, con propagator()->_activeJobList.append(this); auto propfindJob = new PropfindJob(_job->account(), _job->path(), this); - propfindJob->setProperties({"http://owncloud.org/ns:permissions"}); + propfindJob->setProperties({ "oc:permissions" }); connect(propfindJob, &PropfindJob::result, this, [this, jobPath](const QVariantMap &result){ propagator()->_activeJobList.removeOne(this); _item->_remotePerm = RemotePermissions::fromServerString(result.value(QStringLiteral("permissions")).toString()); diff --git a/src/libsync/propagateremotemove.h b/src/libsync/propagateremotemove.h index 567896dd8..64ee0842e 100644 --- a/src/libsync/propagateremotemove.h +++ b/src/libsync/propagateremotemove.h @@ -22,7 +22,7 @@ namespace OCC { * @brief The MoveJob class * @ingroup libsync */ -class MoveJob : public AbstractNetworkJob +class OWNCLOUDSYNC_EXPORT MoveJob : public AbstractNetworkJob { Q_OBJECT const QString _destination; -- 2.30.2