Browse Source

Merge pull request #13995 from glassez/rename-files

Improve content file/folder names handling
adaptive-webui-19844
Vladimir Golovnev 4 years ago committed by GitHub
parent
commit
348109a1f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/base/CMakeLists.txt
  2. 2
      src/base/base.pri
  3. 140
      src/base/bittorrent/abstractfilestorage.cpp
  4. 53
      src/base/bittorrent/abstractfilestorage.h
  5. 9
      src/base/bittorrent/torrenthandle.h
  6. 11
      src/base/bittorrent/torrenthandleimpl.cpp
  7. 2
      src/base/bittorrent/torrenthandleimpl.h
  8. 13
      src/base/bittorrent/torrentinfo.h
  9. 2
      src/base/utils/fs.cpp
  10. 4
      src/gui/properties/propertieswidget.cpp
  11. 245
      src/gui/torrentcontenttreeview.cpp
  12. 5
      src/gui/torrentcontenttreeview.h
  13. 54
      src/webui/api/torrentscontroller.cpp
  14. 1
      src/webui/api/torrentscontroller.h
  15. 26
      src/webui/www/private/rename_file.html
  16. 1
      src/webui/www/private/scripts/file-tree.js
  17. 2
      src/webui/www/private/scripts/filesystem.js
  18. 28
      src/webui/www/private/scripts/prop-files.js

2
src/base/CMakeLists.txt

@ -2,6 +2,7 @@ add_library(qbt_base STATIC @@ -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 @@ -89,6 +90,7 @@ add_library(qbt_base STATIC
# sources
asyncfilestorage.cpp
bittorrent/abstractfilestorage.cpp
bittorrent/bandwidthscheduler.cpp
bittorrent/customstorage.cpp
bittorrent/downloadpriority.cpp

2
src/base/base.pri

@ -1,6 +1,7 @@ @@ -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 += \ @@ -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 \

140
src/base/bittorrent/abstractfilestorage.cpp

@ -0,0 +1,140 @@ @@ -0,0 +1,140 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
*
* 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 <QDir>
#include <QHash>
#include <QVector>
#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<int> 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);
}
}

53
src/base/bittorrent/abstractfilestorage.h

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
*
* 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 <QtGlobal>
#include <QCoreApplication>
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);
};
}

9
src/base/bittorrent/torrenthandle.h

@ -33,6 +33,8 @@ @@ -33,6 +33,8 @@
#include <QString>
#include <QtContainerFwd>
#include "abstractfilestorage.h"
class QBitArray;
class QDateTime;
class QUrl;
@ -89,7 +91,7 @@ namespace BitTorrent @@ -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 @@ -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 @@ -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<DownloadPriority> filePriorities() const = 0;
@ -273,7 +271,6 @@ namespace BitTorrent @@ -273,7 +271,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<DownloadPriority> &priorities) = 0;
virtual void setRatioLimit(qreal limit) = 0;
virtual void setSeedingTimeLimit(int limit) = 0;

11
src/base/bittorrent/torrenthandleimpl.cpp

@ -1405,11 +1405,12 @@ void TorrentHandleImpl::moveStorage(const QString &newPath, const MoveStorageMod @@ -1405,11 +1405,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)
@ -1826,7 +1827,7 @@ void TorrentHandleImpl::manageIncompleteFiles() @@ -1826,7 +1827,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;
@ -1835,7 +1836,7 @@ void TorrentHandleImpl::manageIncompleteFiles() @@ -1835,7 +1836,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());

2
src/base/bittorrent/torrenthandleimpl.h

@ -220,7 +220,7 @@ namespace BitTorrent @@ -220,7 +220,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<DownloadPriority> &priorities) override;
void setRatioLimit(qreal limit) override;
void setSeedingTimeLimit(int limit) override;

13
src/base/bittorrent/torrentinfo.h

@ -34,6 +34,7 @@ @@ -34,6 +34,7 @@
#include <QtContainerFwd>
#include "base/indexrange.h"
#include "abstractfilestorage.h"
#include "torrentcontentlayout.h"
class QByteArray;
@ -46,7 +47,7 @@ namespace BitTorrent @@ -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 @@ -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<TrackerEntry> trackers() const;
QVector<QUrl> urlSeeds() const;
@ -91,7 +92,7 @@ namespace BitTorrent @@ -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;

2
src/base/utils/fs.cpp

@ -95,7 +95,7 @@ QString Utils::Fs::folderName(const QString &filePath) @@ -95,7 +95,7 @@ QString Utils::Fs::folderName(const QString &filePath)
const QString path = toUniformPath(filePath);
const int slashIndex = path.lastIndexOf('/');
if (slashIndex == -1)
return path;
return {};
return path.left(slashIndex);
}

4
src/gui/properties/propertieswidget.cpp

@ -158,7 +158,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) @@ -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 &) @@ -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();
}

245
src/gui/torrentcontenttreeview.cpp

@ -37,10 +37,12 @@ @@ -37,10 +37,12 @@
#include <QTableView>
#include <QThread>
#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 @@ @@ -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) @@ -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 @@ -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<TorrentContentFilterModel *>(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);
}
}

5
src/gui/torrentcontenttreeview.h

@ -32,6 +32,7 @@ @@ -32,6 +32,7 @@
namespace BitTorrent
{
class AbstractFileStorage;
class TorrentHandle;
class TorrentInfo;
}
@ -39,13 +40,13 @@ namespace BitTorrent @@ -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();

54
src/webui/api/torrentscontroller.cpp

@ -1251,44 +1251,44 @@ void TorrentsController::tagsAction() @@ -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);
}

1
src/webui/api/torrentscontroller.h

@ -84,4 +84,5 @@ private slots: @@ -84,4 +84,5 @@ private slots:
void toggleSequentialDownloadAction();
void toggleFirstLastPiecePrioAction();
void renameFileAction();
void renameFolderAction();
};

26
src/webui/www/private/rename_file.html

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
<script src="scripts/lib/mootools-1.2-more.js"></script>
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
<script src="scripts/filesystem.js?v=${CACHEID}"></script>
<script>
'use strict';
@ -31,14 +32,15 @@ @@ -31,14 +32,15 @@
window.addEvent('domready', function() {
const hash = new URI().getData('hash');
const name = new URI().getData('name');
const id = new URI().getData('id');
if (!hash || !name || !id) return;
const path = new URI().getData('path');
const isFolder = ((new URI().getData('isFolder')) === 'true');
const decodedName = decodeURIComponent(name);
$('rename').value = decodedName;
const oldPath = decodeURIComponent(path);
const oldName = window.qBittorrent.Filesystem.fileName(oldPath);
$('rename').value = oldName;
$('rename').focus();
$('rename').setSelectionRange(0, decodedName.lastIndexOf('.'));
if (!isFolder)
$('rename').setSelectionRange(0, oldName.lastIndexOf('.'));
$('renameButton').addEvent('click', function(e) {
new Event(e).stop();
@ -49,20 +51,24 @@ @@ -49,20 +51,24 @@
return;
}
if (newName === name) {
if (newName === oldName) {
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
return;
}
$('renameButton').disabled = true;
const parentPath = window.qBittorrent.Filesystem.folderName(oldPath)
const newPath = parentPath
? parentPath + window.qBittorrent.Filesystem.PathSeparator + newName
: newName;
new Request({
url: 'api/v2/torrents/renameFile',
url: isFolder ? 'api/v2/torrents/renameFolder' : 'api/v2/torrents/renameFile',
method: 'post',
data: {
hash: hash,
id: id,
name: newName
oldPath: oldPath,
newPath: newPath
},
onSuccess: function() {
window.parent.closeWindows();

1
src/webui/www/private/scripts/file-tree.js

@ -117,6 +117,7 @@ window.qBittorrent.FileTree = (function() { @@ -117,6 +117,7 @@ window.qBittorrent.FileTree = (function() {
const FileNode = new Class({
name: "",
path: "",
rowId: null,
size: 0,
checked: TriState.Unchecked,

2
src/webui/www/private/scripts/filesystem.js

@ -70,7 +70,7 @@ window.qBittorrent.Filesystem = (function() { @@ -70,7 +70,7 @@ window.qBittorrent.Filesystem = (function() {
const folderName = function(filepath) {
const slashIndex = filepath.lastIndexOf(PathSeparator);
if (slashIndex === -1)
return filepath;
return '';
return filepath.substring(0, slashIndex);
};

28
src/webui/www/private/scripts/prop-files.js

@ -423,9 +423,9 @@ window.qBittorrent.PropFiles = (function() { @@ -423,9 +423,9 @@ window.qBittorrent.PropFiles = (function() {
rows.forEach(function(row) {
let parent = rootNode;
const pathFolders = row.fileName.split(window.qBittorrent.Filesystem.PathSeparator);
pathFolders.pop();
pathFolders.forEach(function(folderName) {
let folderPath = window.qBittorrent.Filesystem.folderName(row.fileName);
while (folderPath) {
const folderName = window.qBittorrent.Filesystem.fileName(folderPath);
if (folderName === '.unwanted')
return;
@ -439,8 +439,10 @@ window.qBittorrent.PropFiles = (function() { @@ -439,8 +439,10 @@ window.qBittorrent.PropFiles = (function() {
}
}
}
if (parentNode === null) {
parentNode = new window.qBittorrent.FileTree.FolderNode();
parentNode.path = folderPath;
parentNode.name = folderName;
parentNode.rowId = rowId;
parentNode.root = parent;
@ -450,12 +452,14 @@ window.qBittorrent.PropFiles = (function() { @@ -450,12 +452,14 @@ window.qBittorrent.PropFiles = (function() {
}
parent = parentNode;
});
folderPath = window.qBittorrent.Filesystem.folderName(folderPath);
}
const isChecked = row.checked ? TriState.Checked : TriState.Unchecked;
const remaining = (row.priority === FilePriority.Ignored) ? 0 : row.remaining;
const childNode = new window.qBittorrent.FileTree.FileNode();
childNode.name = row.name;
childNode.path = row.fileName;
childNode.rowId = rowId;
childNode.size = row.size;
childNode.checked = isChecked;
@ -527,17 +531,16 @@ window.qBittorrent.PropFiles = (function() { @@ -527,17 +531,16 @@ window.qBittorrent.PropFiles = (function() {
if (rowId === undefined) return;
const row = torrentFilesTable.rows[rowId];
if (!row) return;
const node = torrentFilesTable.getNode(rowId);
if (node.isFolder) return;
const name = row.full_data.name;
const fileId = row.full_data.fileId;
const node = torrentFilesTable.getNode(rowId);
const path = node.path;
new MochaUI.Window({
id: 'renamePage',
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]",
loadMethod: 'iframe',
contentURL: 'rename_file.html?hash=' + hash + '&id=' + fileId + '&name=' + encodeURIComponent(name),
contentURL: 'rename_file.html?hash=' + hash + '&isFolder=' + node.isFolder
+ '&path=' + encodeURIComponent(path),
scrollbars: false,
resizable: false,
maximizable: false,
@ -570,13 +573,6 @@ window.qBittorrent.PropFiles = (function() { @@ -570,13 +573,6 @@ window.qBittorrent.PropFiles = (function() {
this.hideItem('FilePrio');
else
this.showItem('FilePrio');
const rowId = torrentFilesTable.selectedRowsIds()[0];
const node = torrentFilesTable.getNode(rowId);
if (node.isFolder)
this.hideItem('Rename');
else
this.showItem('Rename');
}
});

Loading…
Cancel
Save