Merge pull request #10772 from Chocobo1/backport

Backport to v4_1_x
This commit is contained in:
Mike Tzou 2019-06-17 01:00:35 +08:00 committed by GitHub
commit e8be3bf939
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 343 additions and 267 deletions

View file

@ -1944,7 +1944,7 @@ bool Session::deleteTorrent(const QString &hash, bool deleteLocalFiles)
Utils::Fs::forceRemove(unwantedFile); Utils::Fs::forceRemove(unwantedFile);
const QString parentFolder = Utils::Fs::branchPath(unwantedFile); const QString parentFolder = Utils::Fs::branchPath(unwantedFile);
qDebug("Attempt to remove parent folder (if empty): %s", qUtf8Printable(parentFolder)); qDebug("Attempt to remove parent folder (if empty): %s", qUtf8Printable(parentFolder));
QDir().rmpath(parentFolder); QDir().rmdir(parentFolder);
} }
} }
@ -2377,9 +2377,13 @@ void Session::generateResumeData(bool final)
{ {
for (TorrentHandle *const torrent : asConst(m_torrents)) { for (TorrentHandle *const torrent : asConst(m_torrents)) {
if (!torrent->isValid()) continue; if (!torrent->isValid()) continue;
if (torrent->isChecking() || torrent->isPaused()) continue;
if (!final && !torrent->needSaveResumeData()) continue; if (!final && !torrent->needSaveResumeData()) continue;
if (torrent->hasMissingFiles() || torrent->hasError()) continue; if (torrent->isChecking()
|| torrent->isPaused()
|| torrent->hasError()
|| torrent->hasMissingFiles())
continue;
saveTorrentResumeData(torrent); saveTorrentResumeData(torrent);
} }

View file

@ -1402,6 +1402,7 @@ void TorrentHandle::setTrackerLogin(const QString &username, const QString &pass
void TorrentHandle::renameFile(int index, const QString &name) void TorrentHandle::renameFile(int index, const QString &name)
{ {
m_oldPath[LTFileIndex {index}].push_back(filePath(index));
++m_renameCount; ++m_renameCount;
qDebug() << Q_FUNC_INFO << index << name; qDebug() << Q_FUNC_INFO << index << name;
m_nativeHandle.rename_file(index, Utils::Fs::toNativePath(name).toStdString()); m_nativeHandle.rename_file(index, Utils::Fs::toNativePath(name).toStdString());
@ -1664,10 +1665,14 @@ void TorrentHandle::handleSaveResumeDataFailedAlert(const libtorrent::save_resum
{ {
// if torrent has no metadata we should save dummy fastresume data // if torrent has no metadata we should save dummy fastresume data
// containing Magnet URI and qBittorrent own resume data only // containing Magnet URI and qBittorrent own resume data only
if (p->error.value() == libt::errors::no_metadata) if (p->error.value() == libt::errors::no_metadata) {
handleSaveResumeDataAlert(nullptr); handleSaveResumeDataAlert(nullptr);
else }
else {
LogMsg(tr("Save resume data failed. Torrent: \"%1\", error: \"%2\"")
.arg(name(), QString::fromStdString(p->error.message())), Log::CRITICAL);
m_session->handleTorrentResumeDataFailed(this); m_session->handleTorrentResumeDataFailed(this);
}
} }
void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p) void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p)
@ -1685,45 +1690,66 @@ void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_r
void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert *p) void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert *p)
{ {
#if LIBTORRENT_VERSION_NUM < 10100
QString newName = Utils::Fs::fromNativePath(QString::fromStdString(p->name));
#else
QString newName = Utils::Fs::fromNativePath(p->new_name());
#endif
// TODO: Check this!
if (filesCount() > 1) {
// Check if folders were renamed
QStringList oldPathParts = m_torrentInfo.origFilePath(p->index).split('/');
oldPathParts.removeLast();
QString oldPath = oldPathParts.join('/');
QStringList newPathParts = newName.split('/');
newPathParts.removeLast();
QString newPath = newPathParts.join('/');
if (!newPathParts.isEmpty() && (oldPath != newPath)) {
qDebug("oldPath(%s) != newPath(%s)", qUtf8Printable(oldPath), qUtf8Printable(newPath));
oldPath = QString("%1/%2").arg(savePath(true), oldPath);
qDebug("Detected folder renaming, attempt to delete old folder: %s", qUtf8Printable(oldPath));
QDir().rmpath(oldPath);
}
}
// We don't really need to call updateStatus() in this place. // We don't really need to call updateStatus() in this place.
// All we need to do is make sure we have a valid instance of the TorrentInfo object. // All we need to do is make sure we have a valid instance of the TorrentInfo object.
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
// remove empty leftover folders
// for example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
// be removed if they are empty
const QString oldFilePath = m_oldPath[LTFileIndex {p->index}].takeFirst();
const QString newFilePath = Utils::Fs::fromNativePath(p->new_name());
if (m_oldPath[LTFileIndex {p->index}].isEmpty())
m_oldPath.remove(LTFileIndex {p->index});
QVector<QStringRef> oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts);
oldPathParts.removeLast(); // drop file name part
QVector<QStringRef> newPathParts = newFilePath.splitRef('/', QString::SkipEmptyParts);
newPathParts.removeLast(); // drop file name part
#if defined(Q_OS_WIN)
const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
#else
const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
#endif
int pathIdx = 0;
while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) {
if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0)
break;
++pathIdx;
}
for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) {
QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/")));
oldPathParts.removeLast();
}
--m_renameCount; --m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()(); m_moveFinishedTriggers.takeFirst()();
if (isPaused() && (m_renameCount == 0))
saveResumeData(); // otherwise the new path will not be saved
} }
void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_failed_alert *p) void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_failed_alert *p)
{ {
Q_UNUSED(p); LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
.arg(name(), filePath(p->index)
, QString::fromStdString(p->error.message())), Log::WARNING);
m_oldPath[LTFileIndex {p->index}].removeFirst();
if (m_oldPath[LTFileIndex {p->index}].isEmpty())
m_oldPath.remove(LTFileIndex {p->index});
--m_renameCount; --m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()(); m_moveFinishedTriggers.takeFirst()();
if (isPaused() && (m_renameCount == 0))
saveResumeData(); // otherwise the new path will not be saved
} }
void TorrentHandle::handleFileCompletedAlert(const libtorrent::file_completed_alert *p) void TorrentHandle::handleFileCompletedAlert(const libtorrent::file_completed_alert *p)

View file

@ -385,6 +385,12 @@ namespace BitTorrent
private: private:
typedef boost::function<void ()> EventTrigger; typedef boost::function<void ()> EventTrigger;
#if (LIBTORRENT_VERSION_NUM < 10200)
using LTFileIndex = int;
#else
using LTFileIndex = lt::file_index_t;
#endif
void updateStatus(); void updateStatus();
void updateStatus(const libtorrent::torrent_status &nativeStatus); void updateStatus(const libtorrent::torrent_status &nativeStatus);
void updateState(); void updateState();
@ -446,6 +452,10 @@ namespace BitTorrent
QQueue<EventTrigger> m_moveFinishedTriggers; QQueue<EventTrigger> m_moveFinishedTriggers;
int m_renameCount; int m_renameCount;
// Until libtorrent provide an "old_name" field in `file_renamed_alert`
// we will rely on this workaround to remove empty leftover folders
QHash<LTFileIndex, QVector<QString>> m_oldPath;
bool m_useAutoTMM; bool m_useAutoTMM;
// Persistent data // Persistent data

View file

@ -213,3 +213,14 @@ TriStateBool Utils::String::parseTriStateBool(const QString &string)
return TriStateBool::False; return TriStateBool::False;
return TriStateBool::Undefined; return TriStateBool::Undefined;
} }
QString Utils::String::join(const QVector<QStringRef> &strings, const QString &separator)
{
if (strings.empty())
return {};
QString ret = strings[0].toString();
for (int i = 1; i < strings.count(); ++i)
ret += (separator + strings[i]);
return ret;
}

View file

@ -31,6 +31,7 @@
#define UTILS_STRING_H #define UTILS_STRING_H
#include <QString> #include <QString>
#include <QVector>
class QByteArray; class QByteArray;
class QLatin1String; class QLatin1String;
@ -70,6 +71,8 @@ namespace Utils
bool parseBool(const QString &string, const bool defaultValue); bool parseBool(const QString &string, const bool defaultValue);
TriStateBool parseTriStateBool(const QString &string); TriStateBool parseTriStateBool(const QString &string);
QString join(const QVector<QStringRef> &strings, const QString &separator);
} }
} }

View file

@ -95,6 +95,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
// TODO: set dialog file properties using m_torrentParams.filePriorities // TODO: set dialog file properties using m_torrentParams.filePriorities
m_ui->setupUi(this); m_ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
m_ui->lblMetaLoading->setVisible(false); m_ui->lblMetaLoading->setVisible(false);
m_ui->progMetaLoading->setVisible(false); m_ui->progMetaLoading->setVisible(false);
@ -153,8 +154,10 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
// Signal / slots // Signal / slots
connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked); connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut); QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut);
connect(editHotkey, &QShortcut::activated, this, &AddNewTorrentDialog::renameSelectedFile); connect(editHotkey, &QShortcut::activated
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked, this, &AddNewTorrentDialog::renameSelectedFile); , this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
} }
@ -244,11 +247,7 @@ void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParam
ok = dlg->loadTorrent(source); ok = dlg->loadTorrent(source);
if (ok) if (ok)
#ifdef Q_OS_MAC dlg->QDialog::show();
dlg->exec();
#else
dlg->open();
#endif
else else
delete dlg; delete dlg;
} }
@ -453,107 +452,6 @@ void AddNewTorrentDialog::setSavePath(const QString &newPath)
onSavePathChanged(newPath); onSavePathChanged(newPath);
} }
void AddNewTorrentDialog::renameSelectedFile()
{
const QModelIndexList selectedIndexes = m_ui->contentTreeView->selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1) return;
const QModelIndex modelIndex = selectedIndexes.first();
if (!modelIndex.isValid()) return;
// Ask for new name
bool ok = false;
const bool isFile = (m_contentModel->itemType(modelIndex) == TorrentContentModelItem::FileType);
QString newName = AutoExpandableDialog::getText(this, tr("Renaming"), tr("New name:"), QLineEdit::Normal
, modelIndex.data().toString(), &ok, isFile).trimmed();
if (!ok) return;
if (newName.isEmpty() || !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 = m_contentModel->getFileIndex(modelIndex);
if (newName.endsWith(QB_EXT))
newName.chop(QB_EXT.size());
const QString oldFileName = m_torrentInfo.fileName(fileIndex);
const QString oldFilePath = m_torrentInfo.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 < m_torrentInfo.filesCount(); ++i) {
if (i == fileIndex) continue;
if (Utils::Fs::sameFileNames(m_torrentInfo.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));
m_torrentInfo.renameFile(fileIndex, newFilePath);
m_contentModel->setData(modelIndex, newName);
}
else {
// renaming a folder
QStringList pathItems;
pathItems << modelIndex.data().toString();
QModelIndex parent = m_contentModel->parent(modelIndex);
while (parent.isValid()) {
pathItems.prepend(parent.data().toString());
parent = m_contentModel->parent(parent);
}
const QString oldPath = pathItems.join('/');
pathItems.removeLast();
pathItems << newName;
QString newPath = pathItems.join('/');
if (Utils::Fs::sameFileNames(oldPath, newPath)) {
qDebug("Name did not change");
return;
}
if (!newPath.endsWith('/')) newPath += '/';
// Check for overwriting
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
const QString currentName = m_torrentInfo.filePath(i);
#if defined(Q_OS_UNIX) || defined(Q_WS_QWS)
if (currentName.startsWith(newPath, Qt::CaseSensitive)) {
#else
if (currentName.startsWith(newPath, Qt::CaseInsensitive)) {
#endif
RaisedMessageBox::warning(this, tr("The folder could not be renamed"),
tr("This name is already in use in this folder. Please use a different name."),
QMessageBox::Ok);
return;
}
}
// Replace path in all files
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
const QString currentName = m_torrentInfo.filePath(i);
if (currentName.startsWith(oldPath)) {
QString newName = currentName;
newName.replace(0, oldPath.length(), newPath);
newName = Utils::Fs::expandPath(newName);
qDebug("Rename %s to %s", qUtf8Printable(currentName), qUtf8Printable(newName));
m_torrentInfo.renameFile(i, newName);
}
}
// Rename folder in torrent files model too
m_contentModel->setData(modelIndex, newName);
}
}
void AddNewTorrentDialog::populateSavePathComboBox() void AddNewTorrentDialog::populateSavePathComboBox()
{ {
m_ui->savePath->clear(); m_ui->savePath->clear();
@ -593,7 +491,7 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
QAction *act = myFilesLlistMenu.exec(QCursor::pos()); QAction *act = myFilesLlistMenu.exec(QCursor::pos());
if (act) { if (act) {
if (act == actRename) { if (act == actRename) {
renameSelectedFile(); m_ui->contentTreeView->renameSelectedFile(m_torrentInfo);
} }
else { else {
BitTorrent::FilePriority prio = BitTorrent::FilePriority::Normal; BitTorrent::FilePriority prio = BitTorrent::FilePriority::Normal;
@ -615,9 +513,6 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
void AddNewTorrentDialog::accept() void AddNewTorrentDialog::accept()
{ {
if (!m_hasMetadata)
disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo&)));
// TODO: Check if destination actually exists // TODO: Check if destination actually exists
m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked(); m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
@ -663,7 +558,6 @@ void AddNewTorrentDialog::accept()
void AddNewTorrentDialog::reject() void AddNewTorrentDialog::reject()
{ {
if (!m_hasMetadata) { if (!m_hasMetadata) {
disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo)));
setMetadataProgressIndicator(false); setMetadataProgressIndicator(false);
BitTorrent::Session::instance()->cancelLoadMetadata(m_hash); BitTorrent::Session::instance()->cancelLoadMetadata(m_hash);
} }
@ -675,7 +569,8 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &info)
{ {
if (info.hash() != m_hash) return; if (info.hash() != m_hash) return;
disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo))); disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataLoaded, this, &AddNewTorrentDialog::updateMetadata);
if (!info.isValid()) { if (!info.isValid()) {
RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata.")); RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata."));
setMetadataProgressIndicator(false, tr("Invalid metadata")); setMetadataProgressIndicator(false, tr("Invalid metadata"));

View file

@ -75,7 +75,6 @@ private slots:
void displayContentTreeMenu(const QPoint &); void displayContentTreeMenu(const QPoint &);
void updateDiskSpaceLabel(); void updateDiskSpaceLabel();
void onSavePathChanged(const QString &newPath); void onSavePathChanged(const QString &newPath);
void renameSelectedFile();
void updateMetadata(const BitTorrent::TorrentInfo &info); void updateMetadata(const BitTorrent::TorrentInfo &info);
void handleDownloadFailed(const QString &url, const QString &reason); void handleDownloadFailed(const QString &url, const QString &reason);
void handleRedirectedToMagnet(const QString &url, const QString &magnetUri); void handleRedirectedToMagnet(const QString &url, const QString &magnetUri);

View file

@ -45,7 +45,7 @@
<enum>QListView::IconMode</enum> <enum>QListView::IconMode</enum>
</property> </property>
<property name="currentRow"> <property name="currentRow">
<number>-1</number> <number>0</number>
</property> </property>
<item> <item>
<property name="text"> <property name="text">

View file

@ -154,7 +154,8 @@ PropertiesWidget::PropertiesWidget(QWidget *parent, MainWindow *mainWindow, Tran
connect(m_refreshTimer, &QTimer::timeout, this, &PropertiesWidget::loadDynamicData); connect(m_refreshTimer, &QTimer::timeout, this, &PropertiesWidget::loadDynamicData);
m_refreshTimer->start(3000); // 3sec m_refreshTimer->start(3000); // 3sec
m_editHotkeyFile = new QShortcut(Qt::Key_F2, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut); m_editHotkeyFile = new QShortcut(Qt::Key_F2, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut);
connect(m_editHotkeyFile, &QShortcut::activated, this, &PropertiesWidget::renameSelectedFile); connect(m_editHotkeyFile, &QShortcut::activated
, this, [this]() { m_ui->filesList->renameSelectedFile(m_torrent); });
m_editHotkeyWeb = new QShortcut(Qt::Key_F2, m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut); m_editHotkeyWeb = new QShortcut(Qt::Key_F2, m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut);
connect(m_editHotkeyWeb, &QShortcut::activated, this, &PropertiesWidget::editWebSeed); connect(m_editHotkeyWeb, &QShortcut::activated, this, &PropertiesWidget::editWebSeed);
connect(m_ui->listWebSeeds, &QListWidget::doubleClicked, this, &PropertiesWidget::editWebSeed); connect(m_ui->listWebSeeds, &QListWidget::doubleClicked, this, &PropertiesWidget::editWebSeed);
@ -619,7 +620,7 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
openFolder(index, true); openFolder(index, true);
} }
else if (act == actRename) { else if (act == actRename) {
renameSelectedFile(); m_ui->filesList->renameSelectedFile(m_torrent);
} }
else { else {
BitTorrent::FilePriority prio = BitTorrent::FilePriority::Normal; BitTorrent::FilePriority prio = BitTorrent::FilePriority::Normal;
@ -671,125 +672,6 @@ void PropertiesWidget::displayWebSeedListMenu(const QPoint &)
editWebSeed(); editWebSeed();
} }
void PropertiesWidget::renameSelectedFile()
{
if (!m_torrent) return;
const QModelIndexList selectedIndexes = m_ui->filesList->selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1) return;
const QModelIndex modelIndex = selectedIndexes.first();
if (!modelIndex.isValid()) return;
// Ask for new name
bool ok = false;
const bool isFile = (m_propListModel->itemType(modelIndex) == TorrentContentModelItem::FileType);
QString newName = AutoExpandableDialog::getText(this, tr("Renaming"), tr("New name:"), QLineEdit::Normal
, modelIndex.data().toString(), &ok, isFile).trimmed();
if (!ok) return;
if (newName.isEmpty() || !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 = m_propListModel->getFileIndex(modelIndex);
if (newName.endsWith(QB_EXT))
newName.chop(QB_EXT.size());
const QString oldFileName = m_torrent->fileName(fileIndex);
const QString oldFilePath = m_torrent->filePath(fileIndex);
const bool useFilenameExt = BitTorrent::Session::instance()->isAppendExtensionEnabled()
&& (m_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 < m_torrent->filesCount(); ++i) {
if (i == fileIndex) continue;
if (Utils::Fs::sameFileNames(m_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));
m_torrent->renameFile(fileIndex, newFilePath);
m_propListModel->setData(modelIndex, newName);
}
else {
// renaming a folder
QStringList pathItems;
pathItems << modelIndex.data().toString();
QModelIndex parent = m_propListModel->parent(modelIndex);
while (parent.isValid()) {
pathItems.prepend(parent.data().toString());
parent = m_propListModel->parent(parent);
}
const QString oldPath = pathItems.join('/');
pathItems.removeLast();
pathItems << newName;
QString newPath = pathItems.join('/');
if (Utils::Fs::sameFileNames(oldPath, newPath)) {
qDebug("Name did not change");
return;
}
if (!newPath.endsWith('/')) newPath += '/';
// Check for overwriting
for (int i = 0; i < m_torrent->filesCount(); ++i) {
const QString currentName = m_torrent->filePath(i);
#if defined(Q_OS_UNIX) || defined(Q_WS_QWS)
if (currentName.startsWith(newPath, Qt::CaseSensitive)) {
#else
if (currentName.startsWith(newPath, Qt::CaseInsensitive)) {
#endif
QMessageBox::warning(this, tr("The folder could not be renamed"),
tr("This name is already in use in this folder. Please use a different name."),
QMessageBox::Ok);
return;
}
}
bool forceRecheck = false;
// Replace path in all files
for (int i = 0; i < m_torrent->filesCount(); ++i) {
const QString currentName = m_torrent->filePath(i);
if (currentName.startsWith(oldPath)) {
QString newName = currentName;
newName.replace(0, oldPath.length(), newPath);
if (!forceRecheck && QDir(m_torrent->savePath(true)).exists(newName))
forceRecheck = true;
newName = Utils::Fs::expandPath(newName);
qDebug("Rename %s to %s", qUtf8Printable(currentName), qUtf8Printable(newName));
m_torrent->renameFile(i, newName);
}
}
// Force recheck
if (forceRecheck) m_torrent->forceRecheck();
// Rename folder in torrent files model too
m_propListModel->setData(modelIndex, newName);
// Remove old folder
const QDir oldFolder(m_torrent->savePath(true) + '/' + oldPath);
int timeout = 10;
while (!QDir().rmpath(oldFolder.absolutePath()) && (timeout > 0)) {
// FIXME: We should not sleep here (freezes the UI for 1 second)
QThread::msleep(100);
--timeout;
}
}
}
void PropertiesWidget::openSelectedFile() void PropertiesWidget::openSelectedFile()
{ {
const QModelIndexList selectedIndexes = m_ui->filesList->selectionModel()->selectedRows(0); const QModelIndexList selectedIndexes = m_ui->filesList->selectionModel()->selectedRows(0);

View file

@ -103,7 +103,6 @@ protected slots:
void filteredFilesChanged(); void filteredFilesChanged();
void showPiecesDownloaded(bool show); void showPiecesDownloaded(bool show);
void showPiecesAvailability(bool show); void showPiecesAvailability(bool show);
void renameSelectedFile();
void openSelectedFile(); void openSelectedFile();
private slots: private slots:

View file

@ -28,11 +28,23 @@
#include "torrentcontenttreeview.h" #include "torrentcontenttreeview.h"
#include <QDir>
#include <QHeaderView> #include <QHeaderView>
#include <QKeyEvent> #include <QKeyEvent>
#include <QLineEdit>
#include <QMessageBox>
#include <QModelIndexList> #include <QModelIndexList>
#include <QTableView> #include <QTableView>
#include <QThread>
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrenthandle.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/global.h"
#include "base/utils/fs.h"
#include "autoexpandabledialog.h"
#include "raisedmessagebox.h"
#include "torrentcontentfiltermodel.h"
#include "torrentcontentmodelitem.h" #include "torrentcontentmodelitem.h"
TorrentContentTreeView::TorrentContentTreeView(QWidget *parent) TorrentContentTreeView::TorrentContentTreeView(QWidget *parent)
@ -76,6 +88,232 @@ 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 QModelIndex 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) return;
if (newName.isEmpty() || !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)
{
const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1) return;
const QModelIndex 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) return;
if (newName.isEmpty() || !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 {
// 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);
}
}
QModelIndex TorrentContentTreeView::currentNameCell() QModelIndex TorrentContentTreeView::currentNameCell()
{ {
QModelIndex current = currentIndex(); QModelIndex current = currentIndex();

View file

@ -31,6 +31,12 @@
#include <QTreeView> #include <QTreeView>
namespace BitTorrent
{
class TorrentHandle;
class TorrentInfo;
}
class TorrentContentTreeView : public QTreeView class TorrentContentTreeView : public QTreeView
{ {
Q_OBJECT Q_OBJECT
@ -39,6 +45,9 @@ public:
explicit TorrentContentTreeView(QWidget *parent = nullptr); explicit TorrentContentTreeView(QWidget *parent = nullptr);
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
void renameSelectedFile(BitTorrent::TorrentHandle *torrent);
void renameSelectedFile(BitTorrent::TorrentInfo &torrent);
private: private:
QModelIndex currentNameCell(); QModelIndex currentNameCell();
}; };