Skip to content

Commit fc8bfdc

Browse files
authored
Merge pull request #4454 from nextcloud/bugfix/allow-manual-rename-files-with-spaces
Bugfix/allow manual rename files with spaces
2 parents 96e4fce + 53654b2 commit fc8bfdc

15 files changed

Lines changed: 385 additions & 245 deletions

src/csync/csync_exclude.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,7 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QString &path, bool exclu
165165
// as '.' is a separator that is not stored internally, so let's
166166
// not allow to sync those to avoid file loss/ambiguities (#416)
167167
if (blen > 1) {
168-
if (bname.at(blen - 1) == QLatin1Char(' ')) {
169-
return CSYNC_FILE_EXCLUDE_TRAILING_SPACE;
170-
} else if (bname.at(blen - 1) == QLatin1Char('.')) {
168+
if (bname.at(blen - 1) == QLatin1Char('.')) {
171169
return CSYNC_FILE_EXCLUDE_INVALID_CHAR;
172170
}
173171
}

src/csync/csync_exclude.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ enum CSYNC_EXCLUDE_TYPE {
4545
CSYNC_FILE_EXCLUDE_CONFLICT,
4646
CSYNC_FILE_EXCLUDE_CANNOT_ENCODE,
4747
CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED,
48+
CSYNC_FILE_EXCLUDE_LEADING_SPACE,
49+
CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE,
4850
};
4951

5052
class ExcludedFilesTest;

src/gui/folder.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,11 @@ void Folder::scheduleThisFolderSoon()
12251225
}
12261226
}
12271227

1228+
void Folder::acceptInvalidFileName(const QString &filePath)
1229+
{
1230+
_engine->addAcceptedInvalidFileName(filePath);
1231+
}
1232+
12281233
void Folder::setSaveBackwardsCompatible(bool save)
12291234
{
12301235
_saveBackwardsCompatible = save;

src/gui/folder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ class Folder : public QObject
256256
*/
257257
void scheduleThisFolderSoon();
258258

259+
void acceptInvalidFileName(const QString &filePath);
260+
259261
/**
260262
* Migration: When this flag is true, this folder will save to
261263
* the backwards-compatible 'Folders' section in the config file.

src/gui/invalidfilenamedialog.cpp

Lines changed: 110 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "propagateremotemove.h"
1919
#include "ui_invalidfilenamedialog.h"
2020

21+
#include "filesystem.h"
2122
#include <folder.h>
2223

2324
#include <QPushButton>
@@ -84,13 +85,18 @@ InvalidFilenameDialog::InvalidFilenameDialog(AccountPtr account, Folder *folder,
8485
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
8586
_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Rename file"));
8687

87-
_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));
88-
_ui->explanationLabel->setText(tr("The following characters are not allowed on the system: * \" | & ? , ; : \\ / ~ < >"));
88+
_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));
89+
_ui->explanationLabel->setText(tr("The following characters are not allowed on the system: * \" | & ? , ; : \\ / ~ < > leading/trailing spaces"));
8990
_ui->filenameLineEdit->setText(filePathFileInfo.fileName());
9091

9192
connect(_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
9293
connect(_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
9394

95+
_ui->errorLabel->setText(
96+
tr("Checking rename permissions..."));
97+
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
98+
_ui->filenameLineEdit->setEnabled(false);
99+
94100
connect(_ui->filenameLineEdit, &QLineEdit::textChanged, this,
95101
&InvalidFilenameDialog::onFilenameLineEditTextChanged);
96102

@@ -104,30 +110,88 @@ void InvalidFilenameDialog::checkIfAllowedToRename()
104110
const auto propfindJob = new PropfindJob(_account, QDir::cleanPath(_folder->remotePath() + _originalFileName));
105111
propfindJob->setProperties({ "http://owncloud.org/ns:permissions" });
106112
connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onPropfindPermissionSuccess);
113+
connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onPropfindPermissionError);
107114
propfindJob->start();
108115
}
109116

110-
void InvalidFilenameDialog::onPropfindPermissionSuccess(const QVariantMap &values)
117+
void InvalidFilenameDialog::onCheckIfAllowedToRenameComplete(const QVariantMap &values, QNetworkReply *reply)
111118
{
112-
if (!values.contains("permissions")) {
113-
return;
114-
}
115-
const auto remotePermissions = RemotePermissions::fromServerString(values["permissions"].toString());
116-
if (!remotePermissions.hasPermission(remotePermissions.CanRename)
117-
|| !remotePermissions.hasPermission(remotePermissions.CanMove)) {
119+
const auto isAllowedToRename = [](const RemotePermissions remotePermissions) {
120+
return remotePermissions.hasPermission(remotePermissions.CanRename)
121+
&& remotePermissions.hasPermission(remotePermissions.CanMove);
122+
};
123+
124+
if (values.contains("permissions") && !isAllowedToRename(RemotePermissions::fromServerString(values["permissions"].toString()))) {
118125
_ui->errorLabel->setText(
119126
tr("You don't have the permission to rename this file. Please ask the author of the file to rename it."));
120-
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
121-
_ui->filenameLineEdit->setEnabled(false);
127+
return;
128+
} else if (reply) {
129+
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 404) {
130+
_ui->errorLabel->setText(
131+
tr("Failed to fetch permissions with error %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()));
132+
return;
133+
}
122134
}
135+
136+
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
137+
_ui->filenameLineEdit->setEnabled(true);
138+
_ui->filenameLineEdit->selectAll();
139+
140+
const auto filePathFileInfo = QFileInfo(_filePath);
141+
const auto fileName = filePathFileInfo.fileName();
142+
processLeadingOrTrailingSpacesError(fileName);
143+
}
144+
145+
bool InvalidFilenameDialog::processLeadingOrTrailingSpacesError(const QString &fileName)
146+
{
147+
const auto hasLeadingSpaces = fileName.startsWith(QLatin1Char(' '));
148+
const auto hasTrailingSpaces = fileName.endsWith(QLatin1Char(' '));
149+
150+
_ui->buttonBox->setStandardButtons(_ui->buttonBox->standardButtons() &~ QDialogButtonBox::No);
151+
152+
if (hasLeadingSpaces || hasTrailingSpaces) {
153+
if (hasLeadingSpaces && hasTrailingSpaces) {
154+
_ui->errorLabel->setText(tr("Filename contains leading and trailing spaces."));
155+
}
156+
else if (hasLeadingSpaces) {
157+
_ui->errorLabel->setText(tr("Filename contains leading spaces."));
158+
} else if (hasTrailingSpaces) {
159+
_ui->errorLabel->setText(tr("Filename contains trailing spaces."));
160+
}
161+
162+
if (!Utility::isWindows()) {
163+
_ui->buttonBox->setStandardButtons(_ui->buttonBox->standardButtons() | QDialogButtonBox::No);
164+
_ui->buttonBox->button(QDialogButtonBox::No)->setText(tr("Use invalid name"));
165+
connect(_ui->buttonBox->button(QDialogButtonBox::No), &QPushButton::clicked, this, &InvalidFilenameDialog::useInvalidName);
166+
}
167+
168+
return true;
169+
}
170+
171+
return false;
172+
}
173+
174+
void InvalidFilenameDialog::onPropfindPermissionSuccess(const QVariantMap &values)
175+
{
176+
onCheckIfAllowedToRenameComplete(values);
177+
}
178+
179+
void InvalidFilenameDialog::onPropfindPermissionError(QNetworkReply *reply)
180+
{
181+
onCheckIfAllowedToRenameComplete({}, reply);
182+
}
183+
184+
void InvalidFilenameDialog::useInvalidName()
185+
{
186+
emit acceptedInvalidName(_filePath);
123187
}
124188

125189
void InvalidFilenameDialog::accept()
126190
{
127191
_newFilename = _relativeFilePath + _ui->filenameLineEdit->text().trimmed();
128192
const auto propfindJob = new PropfindJob(_account, QDir::cleanPath(_folder->remotePath() + _newFilename));
129-
connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onRemoteFileAlreadyExists);
130-
connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onRemoteFileDoesNotExist);
193+
connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onRemoteDestinationFileAlreadyExists);
194+
connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onRemoteDestinationFileDoesNotExist);
131195
propfindJob->start();
132196
}
133197

@@ -138,11 +202,10 @@ void InvalidFilenameDialog::onFilenameLineEditTextChanged(const QString &text)
138202
const auto containsIllegalChars = !illegalContainedCharacters.empty() || text.endsWith(QLatin1Char('.'));
139203
const auto isTextValid = isNewFileNameDifferent && !containsIllegalChars;
140204

141-
if (isTextValid) {
142-
_ui->errorLabel->setText("");
143-
} else {
144-
_ui->errorLabel->setText(tr("Filename contains illegal characters: %1")
145-
.arg(illegalCharacterListToString(illegalContainedCharacters)));
205+
_ui->errorLabel->setText("");
206+
207+
if (!processLeadingOrTrailingSpacesError(text) && !isTextValid){
208+
_ui->errorLabel->setText(tr("Filename contains illegal characters: %1").arg(illegalCharacterListToString(illegalContainedCharacters)));
146209
}
147210

148211
_ui->buttonBox->button(QDialogButtonBox::Ok)
@@ -162,23 +225,49 @@ void InvalidFilenameDialog::onMoveJobFinished()
162225
QDialog::accept();
163226
}
164227

165-
void InvalidFilenameDialog::onRemoteFileAlreadyExists(const QVariantMap &values)
228+
void InvalidFilenameDialog::onRemoteDestinationFileAlreadyExists(const QVariantMap &values)
166229
{
167230
Q_UNUSED(values);
168231

169232
_ui->errorLabel->setText(tr("Cannot rename file because a file with the same name does already exist on the server. Please pick another name."));
170233
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
171234
}
172235

173-
void InvalidFilenameDialog::onRemoteFileDoesNotExist(QNetworkReply *reply)
236+
void InvalidFilenameDialog::onRemoteDestinationFileDoesNotExist(QNetworkReply *reply)
174237
{
175238
Q_UNUSED(reply);
176239

177-
// File does not exist. We can rename it.
240+
const auto propfindJob = new PropfindJob(_account, QDir::cleanPath(_folder->remotePath() + _originalFileName));
241+
connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onRemoteSourceFileAlreadyExists);
242+
connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onRemoteSourceFileDoesNotExist);
243+
propfindJob->start();
244+
}
245+
246+
void InvalidFilenameDialog::onRemoteSourceFileAlreadyExists(const QVariantMap &values)
247+
{
248+
Q_UNUSED(values);
249+
250+
// Remote source file exists. We need to start MoveJob to rename it
178251
const auto remoteSource = QDir::cleanPath(_folder->remotePath() + _originalFileName);
179252
const auto remoteDestionation = QDir::cleanPath(_account->davUrl().path() + _folder->remotePath() + _newFilename);
180253
const auto moveJob = new MoveJob(_account, remoteSource, remoteDestionation, this);
181254
connect(moveJob, &MoveJob::finishedSignal, this, &InvalidFilenameDialog::onMoveJobFinished);
182255
moveJob->start();
183256
}
257+
258+
void InvalidFilenameDialog::onRemoteSourceFileDoesNotExist(QNetworkReply *reply)
259+
{
260+
Q_UNUSED(reply);
261+
262+
// It's a new file we've just created locally. We will attempt to rename it locally.
263+
const auto localSource = QDir::cleanPath(_folder->path() + _originalFileName);
264+
const auto localDestionation = QDir::cleanPath(_folder->path()+ _newFilename);
265+
266+
QString error;
267+
if (!FileSystem::rename(localSource, localDestionation, &error)) {
268+
_ui->errorLabel->setText(tr("Could not rename local file. %1").arg(error));
269+
return;
270+
}
271+
QDialog::accept();
272+
}
184273
}

src/gui/invalidfilenamedialog.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class InvalidFilenameDialog : public QDialog
4141

4242
void accept() override;
4343

44+
signals:
45+
void acceptedInvalidName(const QString &filePath);
46+
4447
private:
4548
std::unique_ptr<Ui::InvalidFilenameDialog> _ui;
4649

@@ -53,9 +56,16 @@ class InvalidFilenameDialog : public QDialog
5356

5457
void onFilenameLineEditTextChanged(const QString &text);
5558
void onMoveJobFinished();
56-
void onRemoteFileAlreadyExists(const QVariantMap &values);
57-
void onRemoteFileDoesNotExist(QNetworkReply *reply);
59+
void onRemoteDestinationFileAlreadyExists(const QVariantMap &values);
60+
void onRemoteDestinationFileDoesNotExist(QNetworkReply *reply);
61+
void onRemoteSourceFileAlreadyExists(const QVariantMap &values);
62+
void onRemoteSourceFileDoesNotExist(QNetworkReply *reply);
5863
void checkIfAllowedToRename();
64+
void onCheckIfAllowedToRenameComplete(const QVariantMap &values, QNetworkReply *reply = nullptr);
65+
bool processLeadingOrTrailingSpacesError(const QString &fileName);
5966
void onPropfindPermissionSuccess(const QVariantMap &values);
67+
void onPropfindPermissionError(QNetworkReply *reply = nullptr);
68+
private slots:
69+
void useInvalidName();
6070
};
6171
}

src/gui/tray/activitylistmodel.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,10 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
688688
connect(_currentInvalidFilenameDialog, &InvalidFilenameDialog::accepted, folder, [folder]() {
689689
folder->scheduleThisFolderSoon();
690690
});
691+
connect(_currentInvalidFilenameDialog, &InvalidFilenameDialog::acceptedInvalidName, folder, [folder](const QString& filePath) {
692+
folder->acceptInvalidFileName(filePath);
693+
folder->scheduleThisFolderSoon();
694+
});
691695
_currentInvalidFilenameDialog->open();
692696
ownCloudGui::raiseDialog(_currentInvalidFilenameDialog);
693697
return;

0 commit comments

Comments
 (0)