mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-01-11 07:18:08 +00:00
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.
This commit is contained in:
parent
79048812e9
commit
4453e7fcdd
@ -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
|
||||
|
@ -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 \
|
||||
|
140
src/base/bittorrent/abstractfilestorage.cpp
Normal file
140
src/base/bittorrent/abstractfilestorage.cpp
Normal file
@ -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
Normal file
53
src/base/bittorrent/abstractfilestorage.h
Normal file
@ -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);
|
||||
};
|
||||
}
|
@ -33,6 +33,8 @@
|
||||
#include <QString>
|
||||
#include <QtContainerFwd>
|
||||
|
||||
#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<DownloadPriority> 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<DownloadPriority> &priorities) = 0;
|
||||
virtual void setRatioLimit(qreal limit) = 0;
|
||||
virtual void setSeedingTimeLimit(int limit) = 0;
|
||||
|
@ -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());
|
||||
|
@ -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<DownloadPriority> &priorities) override;
|
||||
void setRatioLimit(qreal limit) override;
|
||||
void setSeedingTimeLimit(int limit) override;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <QtContainerFwd>
|
||||
|
||||
#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<TrackerEntry> trackers() const;
|
||||
QVector<QUrl> 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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 @@
|
||||
#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,138 +104,7 @@ void TorrentContentTreeView::keyPressEvent(QKeyEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentContentTreeView::renameSelectedFile(BitTorrent::TorrentHandle *torrent)
|
||||
{
|
||||
if (!torrent) return;
|
||||
|
||||
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 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
|
||||
{
|
||||
// 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();
|
||||
|
||||
model->setData(modelIndex, newName);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentContentTreeView::renameSelectedFile(BitTorrent::TorrentInfo &torrent)
|
||||
void TorrentContentTreeView::renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage)
|
||||
{
|
||||
const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0);
|
||||
if (selectedIndexes.size() != 1) return;
|
||||
@ -241,99 +123,26 @@ void TorrentContentTreeView::renameSelectedFile(BitTorrent::TorrentInfo &torrent
|
||||
, modelIndex.data().toString(), &ok, isFile).trimmed();
|
||||
if (!ok || !modelIndex.isValid()) return;
|
||||
|
||||
if (!Utils::Fs::isValidFileSystemName(newName))
|
||||
const QString oldName = modelIndex.data().toString();
|
||||
if (newName == oldName)
|
||||
return; // Name did not change
|
||||
|
||||
const QString parentPath = getFullPath(modelIndex.parent());
|
||||
const QString oldPath {parentPath.isEmpty() ? oldName : parentPath + QLatin1Char {'/'} + oldName};
|
||||
const QString newPath {parentPath.isEmpty() ? newName : parentPath + QLatin1Char {'/'} + newName};
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
if (isFile)
|
||||
fileStorage.renameFile(oldPath, newPath);
|
||||
else
|
||||
fileStorage.renameFolder(oldPath, newPath);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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"));
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
// 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->renameFile(oldPath, newPath);
|
||||
}
|
||||
catch (const RuntimeError &error)
|
||||
{
|
||||
throw APIError(APIErrorType::Conflict, error.message());
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentsController::renameFolderAction()
|
||||
{
|
||||
requireParams({"hash", "oldPath", "newPath"});
|
||||
|
||||
const QString hash = params()["hash"];
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
const QString oldPath = params()["oldPath"];
|
||||
const QString newPath = params()["newPath"];
|
||||
|
||||
try
|
||||
{
|
||||
torrent->renameFolder(oldPath, newPath);
|
||||
}
|
||||
catch (const RuntimeError &error)
|
||||
{
|
||||
throw APIError(APIErrorType::Conflict, error.message());
|
||||
}
|
||||
|
||||
torrent->renameFile(fileIndex, newFilePath);
|
||||
}
|
||||
|
@ -84,4 +84,5 @@ private slots:
|
||||
void toggleSequentialDownloadAction();
|
||||
void toggleFirstLastPiecePrioAction();
|
||||
void renameFileAction();
|
||||
void renameFolderAction();
|
||||
};
|
||||
|
@ -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 @@
|
||||
|
||||
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 @@
|
||||
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();
|
||||
|
@ -117,6 +117,7 @@ window.qBittorrent.FileTree = (function() {
|
||||
|
||||
const FileNode = new Class({
|
||||
name: "",
|
||||
path: "",
|
||||
rowId: null,
|
||||
size: 0,
|
||||
checked: TriState.Unchecked,
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
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() {
|
||||
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() {
|
||||
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…
Reference in New Issue
Block a user