From 4453e7fcdd4ba2432d0b3b179b3d7d422a58e4a0 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Thu, 17 Dec 2020 11:57:06 +0300 Subject: [PATCH] Improve content file/folder names handling Move files/folders renaming functions to core classes. Query file/folder for renaming by its current path. Add ability to rename content folders from WebAPI/WebUI. --- src/base/CMakeLists.txt | 2 + src/base/base.pri | 2 + src/base/bittorrent/abstractfilestorage.cpp | 140 +++++++++++ src/base/bittorrent/abstractfilestorage.h | 53 +++++ src/base/bittorrent/torrenthandle.h | 9 +- src/base/bittorrent/torrenthandleimpl.cpp | 11 +- src/base/bittorrent/torrenthandleimpl.h | 2 +- src/base/bittorrent/torrentinfo.h | 13 +- src/gui/properties/propertieswidget.cpp | 4 +- src/gui/torrentcontenttreeview.cpp | 245 +++----------------- src/gui/torrentcontenttreeview.h | 5 +- src/webui/api/torrentscontroller.cpp | 54 ++--- src/webui/api/torrentscontroller.h | 1 + src/webui/www/private/rename_file.html | 26 ++- src/webui/www/private/scripts/file-tree.js | 1 + src/webui/www/private/scripts/prop-files.js | 28 +-- 16 files changed, 303 insertions(+), 293 deletions(-) create mode 100644 src/base/bittorrent/abstractfilestorage.cpp create mode 100644 src/base/bittorrent/abstractfilestorage.h diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 5e2552cbc..84144d82f 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(qbt_base STATIC # headers algorithm.h asyncfilestorage.h + bittorrent/abstractfilestorage.h bittorrent/addtorrentparams.h bittorrent/bandwidthscheduler.h bittorrent/cachestatus.h @@ -89,6 +90,7 @@ add_library(qbt_base STATIC # sources asyncfilestorage.cpp + bittorrent/abstractfilestorage.cpp bittorrent/bandwidthscheduler.cpp bittorrent/customstorage.cpp bittorrent/downloadpriority.cpp diff --git a/src/base/base.pri b/src/base/base.pri index 13ace978a..7b30ce1ad 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -1,6 +1,7 @@ HEADERS += \ $$PWD/algorithm.h \ $$PWD/asyncfilestorage.h \ + $$PWD/bittorrent/abstractfilestorage.h \ $$PWD/bittorrent/addtorrentparams.h \ $$PWD/bittorrent/bandwidthscheduler.h \ $$PWD/bittorrent/cachestatus.h \ @@ -89,6 +90,7 @@ HEADERS += \ SOURCES += \ $$PWD/asyncfilestorage.cpp \ + $$PWD/bittorrent/abstractfilestorage.cpp \ $$PWD/bittorrent/bandwidthscheduler.cpp \ $$PWD/bittorrent/customstorage.cpp \ $$PWD/bittorrent/downloadpriority.cpp \ diff --git a/src/base/bittorrent/abstractfilestorage.cpp b/src/base/bittorrent/abstractfilestorage.cpp new file mode 100644 index 000000000..c43aa7a1e --- /dev/null +++ b/src/base/bittorrent/abstractfilestorage.cpp @@ -0,0 +1,140 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2020 Vladimir Golovnev + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "abstractfilestorage.h" + +#include +#include +#include + +#include "base/bittorrent/common.h" +#include "base/exceptions.h" +#include "base/utils/fs.h" + +#if defined(Q_OS_WIN) +const Qt::CaseSensitivity CASE_SENSITIVITY {Qt::CaseInsensitive}; +#else +const Qt::CaseSensitivity CASE_SENSITIVITY {Qt::CaseSensitive}; +#endif + +namespace +{ + bool areSameFileNames(QString first, QString second) + { + if (first.endsWith(QB_EXT, Qt::CaseInsensitive)) + first.chop(QB_EXT.size()); + if (second.endsWith(QB_EXT, Qt::CaseInsensitive)) + second.chop(QB_EXT.size()); + return QString::compare(first, second, CASE_SENSITIVITY) == 0; + } +} + +void BitTorrent::AbstractFileStorage::renameFile(const QString &oldPath, const QString &newPath) +{ + if (!Utils::Fs::isValidFileSystemName(oldPath, true)) + throw RuntimeError {tr("The old path is invalid: '%1'.").arg(oldPath)}; + if (!Utils::Fs::isValidFileSystemName(newPath, true)) + throw RuntimeError {tr("The new path is invalid: '%1'.").arg(newPath)}; + + const QString oldFilePath = Utils::Fs::toUniformPath(oldPath); + if (oldFilePath.endsWith(QLatin1Char {'/'})) + throw RuntimeError {tr("Invalid file path: '%1'.").arg(oldFilePath)}; + + const QString newFilePath = Utils::Fs::toUniformPath(newPath); + if (newFilePath.endsWith(QLatin1Char {'/'})) + throw RuntimeError {tr("Invalid file path: '%1'.").arg(newFilePath)}; + if (QDir().isAbsolutePath(newFilePath)) + throw RuntimeError {tr("Absolute path isn't allowed: '%1'.").arg(newFilePath)}; + + int renamingFileIndex = -1; + for (int i = 0; i < filesCount(); ++i) + { + const QString path = filePath(i); + + if ((renamingFileIndex < 0) && areSameFileNames(path, oldFilePath)) + renamingFileIndex = i; + + if (areSameFileNames(path, newFilePath)) + throw RuntimeError {tr("The file already exists: '%1'.").arg(newFilePath)}; + } + + if (renamingFileIndex < 0) + throw RuntimeError {tr("No such file: '%1'.").arg(oldFilePath)}; + + const auto extAdjusted = [](const QString &path, const bool needExt) -> QString + { + if (path.endsWith(QB_EXT, Qt::CaseInsensitive) == needExt) + return path; + + return (needExt ? (path + QB_EXT) : (path.left(path.size() - QB_EXT.size()))); + }; + + renameFile(renamingFileIndex, extAdjusted(newFilePath, filePath(renamingFileIndex).endsWith(QB_EXT, Qt::CaseInsensitive))); +} + +void BitTorrent::AbstractFileStorage::renameFolder(const QString &oldPath, const QString &newPath) +{ + if (!Utils::Fs::isValidFileSystemName(oldPath, true)) + throw RuntimeError {tr("The old path is invalid: '%1'.").arg(oldPath)}; + if (!Utils::Fs::isValidFileSystemName(newPath, true)) + throw RuntimeError {tr("The new path is invalid: '%1'.").arg(newPath)}; + + const auto cleanFolderPath = [](const QString &path) -> QString + { + const QString uniformPath = Utils::Fs::toUniformPath(path); + return (uniformPath.endsWith(QLatin1Char {'/'}) ? uniformPath : uniformPath + QLatin1Char {'/'}); + }; + + const QString oldFolderPath = cleanFolderPath(oldPath); + const QString newFolderPath = cleanFolderPath(newPath); + if (QDir().isAbsolutePath(newFolderPath)) + throw RuntimeError {tr("Absolute path isn't allowed: '%1'.").arg(newFolderPath)}; + + QVector renamingFileIndexes; + renamingFileIndexes.reserve(filesCount()); + + for (int i = 0; i < filesCount(); ++i) + { + const QString path = filePath(i); + + if (path.startsWith(oldFolderPath, CASE_SENSITIVITY)) + renamingFileIndexes.append(i); + + if (path.startsWith(newFolderPath, CASE_SENSITIVITY)) + throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)}; + } + + if (renamingFileIndexes.isEmpty()) + throw RuntimeError {tr("No such folder: '%1'.").arg(oldFolderPath)}; + + for (const int index : renamingFileIndexes) + { + const QString newFilePath = newFolderPath + filePath(index).mid(oldFolderPath.size()); + renameFile(index, newFilePath); + } +} diff --git a/src/base/bittorrent/abstractfilestorage.h b/src/base/bittorrent/abstractfilestorage.h new file mode 100644 index 000000000..985beb0ff --- /dev/null +++ b/src/base/bittorrent/abstractfilestorage.h @@ -0,0 +1,53 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2020 Vladimir Golovnev + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include +#include + +class QString; + +namespace BitTorrent +{ + class AbstractFileStorage + { + Q_DECLARE_TR_FUNCTIONS(AbstractFileStorage) + + public: + virtual int filesCount() const = 0; + virtual QString filePath(int index) const = 0; + virtual QString fileName(int index) const = 0; + virtual qlonglong fileSize(int index) const = 0; + + virtual void renameFile(int index, const QString &name) = 0; + + void renameFile(const QString &oldPath, const QString &newPath); + void renameFolder(const QString &oldPath, const QString &newPath); + }; +} diff --git a/src/base/bittorrent/torrenthandle.h b/src/base/bittorrent/torrenthandle.h index 7fea30c55..80826cd35 100644 --- a/src/base/bittorrent/torrenthandle.h +++ b/src/base/bittorrent/torrenthandle.h @@ -33,6 +33,8 @@ #include #include +#include "abstractfilestorage.h" + class QBitArray; class QDateTime; class QUrl; @@ -89,7 +91,7 @@ namespace BitTorrent uint qHash(TorrentState key, uint seed); - class TorrentHandle + class TorrentHandle : public AbstractFileStorage { public: static const qreal USE_GLOBAL_RATIO; @@ -177,7 +179,6 @@ namespace BitTorrent virtual bool removeTag(const QString &tag) = 0; virtual void removeAllTags() = 0; - virtual int filesCount() const = 0; virtual int piecesCount() const = 0; virtual int piecesHave() const = 0; virtual qreal progress() const = 0; @@ -185,9 +186,6 @@ namespace BitTorrent virtual qreal ratioLimit() const = 0; virtual int seedingTimeLimit() const = 0; - virtual QString filePath(int index) const = 0; - virtual QString fileName(int index) const = 0; - virtual qlonglong fileSize(int index) const = 0; virtual QStringList absoluteFilePaths() const = 0; virtual QVector filePriorities() const = 0; @@ -270,7 +268,6 @@ namespace BitTorrent virtual void forceReannounce(int index = -1) = 0; virtual void forceDHTAnnounce() = 0; virtual void forceRecheck() = 0; - virtual void renameFile(int index, const QString &name) = 0; virtual void prioritizeFiles(const QVector &priorities) = 0; virtual void setRatioLimit(qreal limit) = 0; virtual void setSeedingTimeLimit(int limit) = 0; diff --git a/src/base/bittorrent/torrenthandleimpl.cpp b/src/base/bittorrent/torrenthandleimpl.cpp index 2424e5f16..eb0bfc726 100644 --- a/src/base/bittorrent/torrenthandleimpl.cpp +++ b/src/base/bittorrent/torrenthandleimpl.cpp @@ -1390,11 +1390,12 @@ void TorrentHandleImpl::moveStorage(const QString &newPath, const MoveStorageMod } } -void TorrentHandleImpl::renameFile(const int index, const QString &name) +void TorrentHandleImpl::renameFile(const int index, const QString &path) { - m_oldPath[lt::file_index_t {index}].push_back(filePath(index)); + const QString oldPath = filePath(index); + m_oldPath[lt::file_index_t {index}].push_back(oldPath); ++m_renameCount; - m_nativeHandle.rename_file(lt::file_index_t {index}, Utils::Fs::toNativePath(name).toStdString()); + m_nativeHandle.rename_file(lt::file_index_t {index}, Utils::Fs::toNativePath(path).toStdString()); } void TorrentHandleImpl::handleStateUpdate(const lt::torrent_status &nativeStatus) @@ -1811,7 +1812,7 @@ void TorrentHandleImpl::manageIncompleteFiles() QString name = filePath(i); if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1)) { - if (!name.endsWith(QB_EXT)) + if (!name.endsWith(QB_EXT, Qt::CaseInsensitive)) { const QString newName = name + QB_EXT; qDebug() << "Renaming" << name << "to" << newName; @@ -1820,7 +1821,7 @@ void TorrentHandleImpl::manageIncompleteFiles() } else { - if (name.endsWith(QB_EXT)) + if (name.endsWith(QB_EXT, Qt::CaseInsensitive)) { const QString oldName = name; name.chop(QB_EXT.size()); diff --git a/src/base/bittorrent/torrenthandleimpl.h b/src/base/bittorrent/torrenthandleimpl.h index 314dbbd19..f1f927947 100644 --- a/src/base/bittorrent/torrenthandleimpl.h +++ b/src/base/bittorrent/torrenthandleimpl.h @@ -217,7 +217,7 @@ namespace BitTorrent void forceReannounce(int index = -1) override; void forceDHTAnnounce() override; void forceRecheck() override; - void renameFile(int index, const QString &name) override; + void renameFile(int index, const QString &path) override; void prioritizeFiles(const QVector &priorities) override; void setRatioLimit(qreal limit) override; void setSeedingTimeLimit(int limit) override; diff --git a/src/base/bittorrent/torrentinfo.h b/src/base/bittorrent/torrentinfo.h index 8a36ee9e2..dca6b59c7 100644 --- a/src/base/bittorrent/torrentinfo.h +++ b/src/base/bittorrent/torrentinfo.h @@ -34,6 +34,7 @@ #include #include "base/indexrange.h" +#include "abstractfilestorage.h" #include "torrentcontentlayout.h" class QByteArray; @@ -46,7 +47,7 @@ namespace BitTorrent class InfoHash; class TrackerEntry; - class TorrentInfo + class TorrentInfo final : public AbstractFileStorage { Q_DECLARE_TR_FUNCTIONS(TorrentInfo) @@ -68,15 +69,15 @@ namespace BitTorrent QString comment() const; bool isPrivate() const; qlonglong totalSize() const; - int filesCount() const; + int filesCount() const override; int pieceLength() const; int pieceLength(int index) const; int piecesCount() const; - QString filePath(int index) const; + QString filePath(int index) const override; QStringList filePaths() const; - QString fileName(int index) const; + QString fileName(int index) const override; QString origFilePath(int index) const; - qlonglong fileSize(int index) const; + qlonglong fileSize(int index) const override; qlonglong fileOffset(int index) const; QVector trackers() const; QVector urlSeeds() const; @@ -91,7 +92,7 @@ namespace BitTorrent PieceRange filePieces(const QString &file) const; PieceRange filePieces(int fileIndex) const; - void renameFile(int index, const QString &newPath); + void renameFile(int index, const QString &newPath) override; QString rootFolder() const; bool hasRootFolder() const; diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index bbb90f111..ea050b175 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -158,7 +158,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) connect(m_ui->listWebSeeds, &QListWidget::doubleClicked, this, &PropertiesWidget::editWebSeed); const auto *renameFileHotkey = new QShortcut(Qt::Key_F2, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut); - connect(renameFileHotkey, &QShortcut::activated, this, [this]() { m_ui->filesList->renameSelectedFile(m_torrent); }); + connect(renameFileHotkey, &QShortcut::activated, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); }); const auto *openFileHotkeyReturn = new QShortcut(Qt::Key_Return, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut); connect(openFileHotkeyReturn, &QShortcut::activated, this, &PropertiesWidget::openSelectedFile); const auto *openFileHotkeyEnter = new QShortcut(Qt::Key_Enter, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut); @@ -593,7 +593,7 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &) connect(actOpenContainingFolder, &QAction::triggered, this, [this, index]() { openParentFolder(index); }); const QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")); - connect(actRename, &QAction::triggered, this, [this]() { m_ui->filesList->renameSelectedFile(m_torrent); }); + connect(actRename, &QAction::triggered, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); }); menu->addSeparator(); } diff --git a/src/gui/torrentcontenttreeview.cpp b/src/gui/torrentcontenttreeview.cpp index c6341c003..6f4f01a96 100644 --- a/src/gui/torrentcontenttreeview.cpp +++ b/src/gui/torrentcontenttreeview.cpp @@ -37,10 +37,12 @@ #include #include +#include "base/bittorrent/abstractfilestorage.h" #include "base/bittorrent/common.h" #include "base/bittorrent/session.h" #include "base/bittorrent/torrenthandle.h" #include "base/bittorrent/torrentinfo.h" +#include "base/exceptions.h" #include "base/global.h" #include "base/utils/fs.h" #include "autoexpandabledialog.h" @@ -48,6 +50,17 @@ #include "torrentcontentfiltermodel.h" #include "torrentcontentmodelitem.h" +namespace +{ + QString getFullPath(const QModelIndex &idx) + { + QStringList paths; + for (QModelIndex i = idx; i.isValid(); i = i.parent()) + paths.prepend(i.data().toString()); + return paths.join(QLatin1Char {'/'}); + } +} + TorrentContentTreeView::TorrentContentTreeView(QWidget *parent) : QTreeView(parent) { @@ -91,10 +104,8 @@ void TorrentContentTreeView::keyPressEvent(QKeyEvent *event) } } -void TorrentContentTreeView::renameSelectedFile(BitTorrent::TorrentHandle *torrent) +void TorrentContentTreeView::renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage) { - if (!torrent) return; - const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0); if (selectedIndexes.size() != 1) return; @@ -112,228 +123,26 @@ void TorrentContentTreeView::renameSelectedFile(BitTorrent::TorrentHandle *torre , modelIndex.data().toString(), &ok, isFile).trimmed(); if (!ok || !modelIndex.isValid()) return; - if (!Utils::Fs::isValidFileSystemName(newName)) - { - RaisedMessageBox::warning(this, tr("Rename error"), - tr("The name is empty or contains forbidden characters, please choose a different one."), - QMessageBox::Ok); - return; - } + const QString oldName = modelIndex.data().toString(); + if (newName == oldName) + return; // Name did not change - if (isFile) - { - const int fileIndex = model->getFileIndex(modelIndex); + const QString parentPath = getFullPath(modelIndex.parent()); + const QString oldPath {parentPath.isEmpty() ? oldName : parentPath + QLatin1Char {'/'} + oldName}; + const QString newPath {parentPath.isEmpty() ? newName : parentPath + QLatin1Char {'/'} + newName}; - if (newName.endsWith(QB_EXT)) - newName.chop(QB_EXT.size()); - const QString oldFileName = torrent->fileName(fileIndex); - const QString oldFilePath = torrent->filePath(fileIndex); - - const bool useFilenameExt = BitTorrent::Session::instance()->isAppendExtensionEnabled() - && (torrent->filesProgress()[fileIndex] != 1); - const QString newFileName = newName + (useFilenameExt ? QB_EXT : QString()); - const QString newFilePath = oldFilePath.leftRef(oldFilePath.size() - oldFileName.size()) + newFileName; - - if (oldFileName == newFileName) - { - qDebug("Name did not change: %s", qUtf8Printable(oldFileName)); - return; - } - - // check if that name is already used - for (int i = 0; i < torrent->filesCount(); ++i) - { - if (i == fileIndex) continue; - if (Utils::Fs::sameFileNames(torrent->filePath(i), newFilePath)) - { - RaisedMessageBox::warning(this, tr("Rename error"), - tr("This name is already in use in this folder. Please use a different name."), - QMessageBox::Ok); - return; - } - } - - qDebug("Renaming %s to %s", qUtf8Printable(oldFilePath), qUtf8Printable(newFilePath)); - torrent->renameFile(fileIndex, newFilePath); - - model->setData(modelIndex, newName); - } - else + try { - // renaming a folder - - const QString oldName = modelIndex.data().toString(); - if (newName == oldName) - return; // Name did not change - - QString parentPath; - for (QModelIndex idx = model->parent(modelIndex); idx.isValid(); idx = model->parent(idx)) - parentPath.prepend(idx.data().toString() + '/'); - - const QString oldPath {parentPath + oldName + '/'}; - const QString newPath {parentPath + newName + '/'}; - - // Check for overwriting -#if defined(Q_OS_WIN) - const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; -#else - const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; -#endif - - for (int i = 0; i < torrent->filesCount(); ++i) - { - const QString currentPath = torrent->filePath(i); - - if (currentPath.startsWith(oldPath)) - continue; - - if (currentPath.startsWith(newPath, caseSensitivity)) - { - RaisedMessageBox::warning(this, tr("The folder could not be renamed"), - tr("This name is already in use. Please use a different name."), - QMessageBox::Ok); - return; - } - } - - // Replace path in all files - bool needForceRecheck = false; - - for (int i = 0; i < torrent->filesCount(); ++i) - { - const QString currentPath = torrent->filePath(i); - - if (currentPath.startsWith(oldPath)) - { - const QString path {newPath + currentPath.mid(oldPath.length())}; - - if (!needForceRecheck && QFile::exists(path)) - needForceRecheck = true; - - torrent->renameFile(i, path); - } - } - - // Force recheck - if (needForceRecheck) - torrent->forceRecheck(); + if (isFile) + fileStorage.renameFile(oldPath, newPath); + else + fileStorage.renameFolder(oldPath, newPath); model->setData(modelIndex, newName); } -} - -void TorrentContentTreeView::renameSelectedFile(BitTorrent::TorrentInfo &torrent) -{ - const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0); - if (selectedIndexes.size() != 1) return; - - const QPersistentModelIndex modelIndex = selectedIndexes.first(); - if (!modelIndex.isValid()) return; - - auto model = dynamic_cast(TorrentContentTreeView::model()); - if (!model) return; - - const bool isFile = (model->itemType(modelIndex) == TorrentContentModelItem::FileType); - - // Ask for new name - bool ok = false; - QString newName = AutoExpandableDialog::getText(this, tr("Renaming"), tr("New name:"), QLineEdit::Normal - , modelIndex.data().toString(), &ok, isFile).trimmed(); - if (!ok || !modelIndex.isValid()) return; - - if (!Utils::Fs::isValidFileSystemName(newName)) - { - RaisedMessageBox::warning(this, tr("Rename error"), - tr("The name is empty or contains forbidden characters, please choose a different one."), - QMessageBox::Ok); - return; - } - - if (isFile) - { - const int fileIndex = model->getFileIndex(modelIndex); - - if (newName.endsWith(QB_EXT)) - newName.chop(QB_EXT.size()); - const QString oldFileName = torrent.fileName(fileIndex); - const QString oldFilePath = torrent.filePath(fileIndex); - const QString newFilePath = oldFilePath.leftRef(oldFilePath.size() - oldFileName.size()) + newName; - - if (oldFileName == newName) - { - qDebug("Name did not change: %s", qUtf8Printable(oldFileName)); - return; - } - - // check if that name is already used - for (int i = 0; i < torrent.filesCount(); ++i) - { - if (i == fileIndex) continue; - if (Utils::Fs::sameFileNames(torrent.filePath(i), newFilePath)) - { - RaisedMessageBox::warning(this, tr("Rename error"), - tr("This name is already in use in this folder. Please use a different name."), - QMessageBox::Ok); - return; - } - } - - qDebug("Renaming %s to %s", qUtf8Printable(oldFilePath), qUtf8Printable(newFilePath)); - torrent.renameFile(fileIndex, newFilePath); - - model->setData(modelIndex, newName); - } - else + catch (const RuntimeError &error) { - // renaming a folder - - const QString oldName = modelIndex.data().toString(); - if (newName == oldName) - return; // Name did not change - - QString parentPath; - for (QModelIndex idx = model->parent(modelIndex); idx.isValid(); idx = model->parent(idx)) - parentPath.prepend(idx.data().toString() + '/'); - - const QString oldPath {parentPath + oldName + '/'}; - const QString newPath {parentPath + newName + '/'}; - - // Check for overwriting -#if defined(Q_OS_WIN) - const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; -#else - const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; -#endif - - for (int i = 0; i < torrent.filesCount(); ++i) - { - const QString currentPath = torrent.filePath(i); - - if (currentPath.startsWith(oldPath)) - continue; - - if (currentPath.startsWith(newPath, caseSensitivity)) - { - RaisedMessageBox::warning(this, tr("The folder could not be renamed"), - tr("This name is already in use. Please use a different name."), - QMessageBox::Ok); - return; - } - } - - // Replace path in all files - for (int i = 0; i < torrent.filesCount(); ++i) - { - const QString currentPath = torrent.filePath(i); - - if (currentPath.startsWith(oldPath)) - { - const QString path {newPath + currentPath.mid(oldPath.length())}; - torrent.renameFile(i, path); - } - } - - model->setData(modelIndex, newName); + RaisedMessageBox::warning(this, tr("Rename error"), error.message(), QMessageBox::Ok); } } diff --git a/src/gui/torrentcontenttreeview.h b/src/gui/torrentcontenttreeview.h index 969f3dedc..468c1ccdb 100644 --- a/src/gui/torrentcontenttreeview.h +++ b/src/gui/torrentcontenttreeview.h @@ -32,6 +32,7 @@ namespace BitTorrent { + class AbstractFileStorage; class TorrentHandle; class TorrentInfo; } @@ -39,13 +40,13 @@ namespace BitTorrent class TorrentContentTreeView final : public QTreeView { Q_OBJECT + Q_DISABLE_COPY(TorrentContentTreeView) public: explicit TorrentContentTreeView(QWidget *parent = nullptr); void keyPressEvent(QKeyEvent *event) override; - void renameSelectedFile(BitTorrent::TorrentHandle *torrent); - void renameSelectedFile(BitTorrent::TorrentInfo &torrent); + void renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage); private: QModelIndex currentNameCell(); diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 5e93fb4b3..46b4a4b09 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -1251,44 +1251,44 @@ void TorrentsController::tagsAction() void TorrentsController::renameFileAction() { - requireParams({"hash", "id", "name"}); + requireParams({"hash", "oldPath", "newPath"}); const QString hash = params()["hash"]; BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); if (!torrent) throw APIError(APIErrorType::NotFound); - QString newName = params()["name"].trimmed(); - if (newName.isEmpty()) - throw APIError(APIErrorType::BadParams, tr("Name cannot be empty")); - if (!Utils::Fs::isValidFileSystemName(newName)) - throw APIError(APIErrorType::Conflict, tr("Name is not valid")); - if (newName.endsWith(QB_EXT)) - newName.chop(QB_EXT.size()); + const QString oldPath = params()["oldPath"]; + const QString newPath = params()["newPath"]; - bool ok = false; - const int fileIndex = params()["id"].toInt(&ok); - if (!ok || (fileIndex < 0) || (fileIndex >= torrent->filesCount())) - throw APIError(APIErrorType::Conflict, tr("ID is not valid")); + try + { + torrent->renameFile(oldPath, newPath); + } + catch (const RuntimeError &error) + { + throw APIError(APIErrorType::Conflict, error.message()); + } +} - const QString oldFileName = torrent->fileName(fileIndex); - const QString oldFilePath = torrent->filePath(fileIndex); +void TorrentsController::renameFolderAction() +{ + requireParams({"hash", "oldPath", "newPath"}); - const bool useFilenameExt = BitTorrent::Session::instance()->isAppendExtensionEnabled() - && (torrent->filesProgress()[fileIndex] != 1); - const QString newFileName = (newName + (useFilenameExt ? QB_EXT : QString())); - const QString newFilePath = (oldFilePath.leftRef(oldFilePath.size() - oldFileName.size()) + newFileName); + const QString hash = params()["hash"]; + BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + if (!torrent) + throw APIError(APIErrorType::NotFound); - if (oldFileName == newFileName) - return; + const QString oldPath = params()["oldPath"]; + const QString newPath = params()["newPath"]; - // check if new name is already used - for (int i = 0; i < torrent->filesCount(); ++i) + try { - if (i == fileIndex) continue; - if (Utils::Fs::sameFileNames(torrent->filePath(i), newFilePath)) - throw APIError(APIErrorType::Conflict, tr("Name is already in use")); + torrent->renameFolder(oldPath, newPath); + } + catch (const RuntimeError &error) + { + throw APIError(APIErrorType::Conflict, error.message()); } - - torrent->renameFile(fileIndex, newFilePath); } diff --git a/src/webui/api/torrentscontroller.h b/src/webui/api/torrentscontroller.h index e7653d360..5a1db8345 100644 --- a/src/webui/api/torrentscontroller.h +++ b/src/webui/api/torrentscontroller.h @@ -84,4 +84,5 @@ private slots: void toggleSequentialDownloadAction(); void toggleFirstLastPiecePrioAction(); void renameFileAction(); + void renameFolderAction(); }; diff --git a/src/webui/www/private/rename_file.html b/src/webui/www/private/rename_file.html index 8d6a846a2..19b5dcbe6 100644 --- a/src/webui/www/private/rename_file.html +++ b/src/webui/www/private/rename_file.html @@ -8,6 +8,7 @@ +