mirror of
https://github.com/nextcloud/desktop.git
synced 2024-12-20 21:02:02 +03:00
07c292eaf5
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
284 lines
11 KiB
C++
284 lines
11 KiB
C++
/*
|
|
* Copyright 2021 (c) Felix Weilbach <felix.weilbach@nextcloud.com>
|
|
* Copyright 2022 (c) Matthieu Gallien <matthieu.gallien@nextcloud.com>
|
|
* Copyright 2022 (c) Claudio Cambra <claudio.cambra@nextcloud.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include "caseclashfilenamedialog.h"
|
|
#include "ui_caseclashfilenamedialog.h"
|
|
|
|
#include "account.h"
|
|
#include "folder.h"
|
|
|
|
#include <QPushButton>
|
|
#include <QDir>
|
|
#include <QAbstractButton>
|
|
#include <QDialogButtonBox>
|
|
#include <QFileInfo>
|
|
#include <QPushButton>
|
|
#include <QDirIterator>
|
|
#include <QDesktopServices>
|
|
#include <QLoggingCategory>
|
|
|
|
#include <array>
|
|
|
|
namespace {
|
|
constexpr std::array<QChar, 9> caseClashIllegalCharacters({ '\\', '/', ':', '?', '*', '\"', '<', '>', '|' });
|
|
|
|
QVector<QChar> getCaseClashIllegalCharsFromString(const QString &string)
|
|
{
|
|
QVector<QChar> result;
|
|
for (const auto &character : string) {
|
|
if (std::find(caseClashIllegalCharacters.begin(), caseClashIllegalCharacters.end(), character)
|
|
!= caseClashIllegalCharacters.end()) {
|
|
result.push_back(character);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QString caseClashIllegalCharacterListToString(const QVector<QChar> &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 {
|
|
|
|
Q_LOGGING_CATEGORY(lcCaseClashConflictFialog, "nextcloud.sync.caseclash.dialog", QtInfoMsg)
|
|
|
|
CaseClashFilenameDialog::CaseClashFilenameDialog(AccountPtr account,
|
|
Folder *folder,
|
|
const QString &conflictFilePath,
|
|
const QString &conflictTaggedPath,
|
|
QWidget *parent)
|
|
: QDialog(parent)
|
|
, _ui(std::make_unique<Ui::CaseClashFilenameDialog>())
|
|
, _conflictSolver(conflictFilePath, conflictTaggedPath, folder->remotePath(), folder->path(), account, folder->journalDb())
|
|
, _account(account)
|
|
, _folder(folder)
|
|
, _filePath(std::move(conflictFilePath))
|
|
{
|
|
Q_ASSERT(_account);
|
|
Q_ASSERT(_folder);
|
|
|
|
const auto filePathFileInfo = QFileInfo(_filePath);
|
|
const auto conflictFileName = filePathFileInfo.fileName();
|
|
|
|
_relativeFilePath = filePathFileInfo.path() + QStringLiteral("/");
|
|
_relativeFilePath = _relativeFilePath.replace(folder->path(), QLatin1String());
|
|
_relativeFilePath = _relativeFilePath.isEmpty() ? QString() : _relativeFilePath + QStringLiteral("/");
|
|
|
|
_originalFileName = _relativeFilePath + conflictFileName;
|
|
|
|
_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 of a case clash conflict with an existing file on this system.").arg(_originalFileName));
|
|
_ui->explanationLabel->setText(tr("%1 does not support equal file names with only letter casing differences.").arg(QSysInfo::prettyProductName()));
|
|
_ui->filenameLineEdit->setText(conflictFileName);
|
|
|
|
const auto preexistingConflictingFile = caseClashConflictFile(_filePath);
|
|
updateFileWidgetGroup(preexistingConflictingFile,
|
|
tr("Open existing file"),
|
|
_ui->localVersionFilename,
|
|
_ui->localVersionLink,
|
|
_ui->localVersionMtime,
|
|
_ui->localVersionSize,
|
|
_ui->localVersionButton);
|
|
|
|
updateFileWidgetGroup(conflictTaggedPath,
|
|
tr("Open clashing file"),
|
|
_ui->remoteVersionFilename,
|
|
_ui->remoteVersionLink,
|
|
_ui->remoteVersionMtime,
|
|
_ui->remoteVersionSize,
|
|
_ui->remoteVersionButton);
|
|
// Display incoming conflict filename, not conflict-tagged filename
|
|
_ui->remoteVersionFilename->setText(filePathFileInfo.fileName());
|
|
|
|
adjustSize();
|
|
|
|
connect(_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
|
connect(_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
connect(_ui->localVersionButton, &QToolButton::clicked, this, [preexistingConflictingFile] {
|
|
QDesktopServices::openUrl(QUrl::fromLocalFile(preexistingConflictingFile));
|
|
});
|
|
connect(_ui->remoteVersionButton, &QToolButton::clicked, this, [conflictTaggedPath] {
|
|
QDesktopServices::openUrl(QUrl::fromLocalFile(conflictTaggedPath));
|
|
});
|
|
|
|
_ui->errorLabel->setText({}/*
|
|
tr("Checking rename permissions …")*/);
|
|
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
|
_ui->filenameLineEdit->setEnabled(false);
|
|
|
|
connect(_ui->filenameLineEdit, &QLineEdit::textChanged, this,
|
|
&CaseClashFilenameDialog::onFilenameLineEditTextChanged);
|
|
|
|
connect(&_conflictSolver, &CaseClashConflictSolver::errorStringChanged, this, [this] () {
|
|
_ui->errorLabel->setText(_conflictSolver.errorString());
|
|
});
|
|
|
|
connect(&_conflictSolver, &CaseClashConflictSolver::allowedToRenameChanged, this, [this] () {
|
|
_ui->buttonBox->setStandardButtons(_ui->buttonBox->standardButtons() &~ QDialogButtonBox::No);
|
|
if (_conflictSolver.allowedToRename()) {
|
|
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
|
_ui->filenameLineEdit->setEnabled(true);
|
|
_ui->filenameLineEdit->selectAll();
|
|
} else {
|
|
_ui->buttonBox->setStandardButtons(_ui->buttonBox->standardButtons() | QDialogButtonBox::No);
|
|
}
|
|
});
|
|
|
|
connect(&_conflictSolver, &CaseClashConflictSolver::failed, this, [this] () {
|
|
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
|
});
|
|
|
|
connect(&_conflictSolver, &CaseClashConflictSolver::done, this, [this] () {
|
|
Q_EMIT successfulRename(_folder->remotePath() + _newFilename);
|
|
QDialog::accept();
|
|
});
|
|
|
|
checkIfAllowedToRename();
|
|
}
|
|
|
|
CaseClashFilenameDialog::~CaseClashFilenameDialog() = default;
|
|
|
|
QString CaseClashFilenameDialog::caseClashConflictFile(const QString &conflictFilePath)
|
|
{
|
|
const auto filePathFileInfo = QFileInfo(conflictFilePath);
|
|
const auto conflictFileName = filePathFileInfo.fileName();
|
|
|
|
QDirIterator it(filePathFileInfo.path(), QDirIterator::Subdirectories);
|
|
|
|
while(it.hasNext()) {
|
|
const auto filePath = it.next();
|
|
qCDebug(lcCaseClashConflictFialog) << filePath;
|
|
QFileInfo fileInfo(filePath);
|
|
|
|
if(fileInfo.isDir()) {
|
|
continue;
|
|
}
|
|
|
|
const auto currentFileName = fileInfo.fileName();
|
|
if (currentFileName.compare(conflictFileName, Qt::CaseInsensitive) == 0 &&
|
|
currentFileName != conflictFileName) {
|
|
|
|
return filePath;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void CaseClashFilenameDialog::updateFileWidgetGroup(const QString &filePath,
|
|
const QString &linkText,
|
|
QLabel *filenameLabel,
|
|
QLabel *linkLabel,
|
|
QLabel *mtimeLabel,
|
|
QLabel *sizeLabel,
|
|
QToolButton *button) const
|
|
{
|
|
const auto filePathFileInfo = QFileInfo(filePath);
|
|
const auto filename = filePathFileInfo.fileName();
|
|
const auto lastModifiedString = filePathFileInfo.lastModified().toString();
|
|
const auto fileSizeString = locale().formattedDataSize(filePathFileInfo.size());
|
|
const auto fileUrl = QUrl::fromLocalFile(filePath).toString();
|
|
const auto linkString = QStringLiteral("<a href='%1'>%2</a>").arg(fileUrl, linkText);
|
|
const auto mime = QMimeDatabase().mimeTypeForFile(_filePath);
|
|
QIcon fileTypeIcon;
|
|
|
|
qCDebug(lcCaseClashConflictFialog) << filePath << filePathFileInfo.exists() << filename << lastModifiedString << fileSizeString << fileUrl << linkString << mime;
|
|
|
|
if (QIcon::hasThemeIcon(mime.iconName())) {
|
|
fileTypeIcon = QIcon::fromTheme(mime.iconName());
|
|
} else {
|
|
fileTypeIcon = QIcon(":/qt-project.org/styles/commonstyle/images/file-128.png");
|
|
}
|
|
|
|
filenameLabel->setText(filename);
|
|
mtimeLabel->setText(lastModifiedString);
|
|
sizeLabel->setText(fileSizeString);
|
|
linkLabel->setText(linkString);
|
|
button->setIcon(fileTypeIcon);
|
|
}
|
|
|
|
void CaseClashFilenameDialog::checkIfAllowedToRename()
|
|
{
|
|
_conflictSolver.checkIfAllowedToRename();
|
|
}
|
|
|
|
bool CaseClashFilenameDialog::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"));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CaseClashFilenameDialog::accept()
|
|
{
|
|
_newFilename = _relativeFilePath + _ui->filenameLineEdit->text().trimmed();
|
|
_conflictSolver.solveConflict(_newFilename);
|
|
}
|
|
|
|
void CaseClashFilenameDialog::onFilenameLineEditTextChanged(const QString &text)
|
|
{
|
|
const auto isNewFileNameDifferent = text != _originalFileName;
|
|
const auto illegalContainedCharacters = getCaseClashIllegalCharsFromString(text);
|
|
const auto containsIllegalChars = !illegalContainedCharacters.empty() || text.endsWith(QLatin1Char('.'));
|
|
const auto isTextValid = isNewFileNameDifferent && !containsIllegalChars;
|
|
|
|
_ui->errorLabel->setText("");
|
|
|
|
if (!processLeadingOrTrailingSpacesError(text) && !isTextValid){
|
|
_ui->errorLabel->setText(tr("Filename contains illegal characters: %1").arg(caseClashIllegalCharacterListToString(illegalContainedCharacters)));
|
|
}
|
|
|
|
_ui->buttonBox->button(QDialogButtonBox::Ok)
|
|
->setEnabled(isTextValid);
|
|
}
|
|
}
|