diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index bad167a31..368059544 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -1944,7 +1944,7 @@ bool Session::deleteTorrent(const QString &hash, bool deleteLocalFiles) Utils::Fs::forceRemove(unwantedFile); const QString parentFolder = Utils::Fs::branchPath(unwantedFile); 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)) { if (!torrent->isValid()) continue; - if (torrent->isChecking() || torrent->isPaused()) continue; + if (!final && !torrent->needSaveResumeData()) continue; - if (torrent->hasMissingFiles() || torrent->hasError()) continue; + if (torrent->isChecking() + || torrent->isPaused() + || torrent->hasError() + || torrent->hasMissingFiles()) + continue; saveTorrentResumeData(torrent); } diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index 93aa75d60..13842b61d 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -1402,6 +1402,7 @@ void TorrentHandle::setTrackerLogin(const QString &username, const QString &pass void TorrentHandle::renameFile(int index, const QString &name) { + m_oldPath[LTFileIndex {index}].push_back(filePath(index)); ++m_renameCount; qDebug() << Q_FUNC_INFO << index << name; 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 // 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); - 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); + } } 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) { -#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. // All we need to do is make sure we have a valid instance of the TorrentInfo object. 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 oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts); + oldPathParts.removeLast(); // drop file name part + QVector 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; while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) 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) { - 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; while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) 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) diff --git a/src/base/bittorrent/torrenthandle.h b/src/base/bittorrent/torrenthandle.h index 425e688d4..c9d9d9a67 100644 --- a/src/base/bittorrent/torrenthandle.h +++ b/src/base/bittorrent/torrenthandle.h @@ -385,6 +385,12 @@ namespace BitTorrent private: typedef boost::function EventTrigger; +#if (LIBTORRENT_VERSION_NUM < 10200) + using LTFileIndex = int; +#else + using LTFileIndex = lt::file_index_t; +#endif + void updateStatus(); void updateStatus(const libtorrent::torrent_status &nativeStatus); void updateState(); @@ -446,6 +452,10 @@ namespace BitTorrent QQueue m_moveFinishedTriggers; 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> m_oldPath; + bool m_useAutoTMM; // Persistent data diff --git a/src/base/utils/string.cpp b/src/base/utils/string.cpp index 85b5b9191..59c07031b 100644 --- a/src/base/utils/string.cpp +++ b/src/base/utils/string.cpp @@ -213,3 +213,14 @@ TriStateBool Utils::String::parseTriStateBool(const QString &string) return TriStateBool::False; return TriStateBool::Undefined; } + +QString Utils::String::join(const QVector &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; +} diff --git a/src/base/utils/string.h b/src/base/utils/string.h index 5a1088622..3f15322da 100644 --- a/src/base/utils/string.h +++ b/src/base/utils/string.h @@ -31,6 +31,7 @@ #define UTILS_STRING_H #include +#include class QByteArray; class QLatin1String; @@ -70,6 +71,8 @@ namespace Utils bool parseBool(const QString &string, const bool defaultValue); TriStateBool parseTriStateBool(const QString &string); + + QString join(const QVector &strings, const QString &separator); } } diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 759a17d1a..87754575e 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -95,6 +95,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP // TODO: set dialog file properties using m_torrentParams.filePriorities m_ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + m_ui->lblMetaLoading->setVisible(false); m_ui->progMetaLoading->setVisible(false); @@ -153,8 +154,10 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP // Signal / slots connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked); QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut); - connect(editHotkey, &QShortcut::activated, this, &AddNewTorrentDialog::renameSelectedFile); - connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked, this, &AddNewTorrentDialog::renameSelectedFile); + connect(editHotkey, &QShortcut::activated + , 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(); } @@ -244,11 +247,7 @@ void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParam ok = dlg->loadTorrent(source); if (ok) -#ifdef Q_OS_MAC - dlg->exec(); -#else - dlg->open(); -#endif + dlg->QDialog::show(); else delete dlg; } @@ -453,107 +452,6 @@ void AddNewTorrentDialog::setSavePath(const QString &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() { m_ui->savePath->clear(); @@ -593,7 +491,7 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &) QAction *act = myFilesLlistMenu.exec(QCursor::pos()); if (act) { if (act == actRename) { - renameSelectedFile(); + m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); } else { BitTorrent::FilePriority prio = BitTorrent::FilePriority::Normal; @@ -615,9 +513,6 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &) void AddNewTorrentDialog::accept() { - if (!m_hasMetadata) - disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo&))); - // TODO: Check if destination actually exists m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked(); @@ -663,7 +558,6 @@ void AddNewTorrentDialog::accept() void AddNewTorrentDialog::reject() { if (!m_hasMetadata) { - disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo))); setMetadataProgressIndicator(false); BitTorrent::Session::instance()->cancelLoadMetadata(m_hash); } @@ -675,7 +569,8 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &info) { 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()) { RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata.")); setMetadataProgressIndicator(false, tr("Invalid metadata")); diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index 38fc894a6..5b23aacdb 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -75,7 +75,6 @@ private slots: void displayContentTreeMenu(const QPoint &); void updateDiskSpaceLabel(); void onSavePathChanged(const QString &newPath); - void renameSelectedFile(); void updateMetadata(const BitTorrent::TorrentInfo &info); void handleDownloadFailed(const QString &url, const QString &reason); void handleRedirectedToMagnet(const QString &url, const QString &magnetUri); diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index f6cdcc45c..7382975f0 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -45,7 +45,7 @@ QListView::IconMode - -1 + 0 diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index c0387a716..e3b4f1767 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -154,7 +154,8 @@ PropertiesWidget::PropertiesWidget(QWidget *parent, MainWindow *mainWindow, Tran connect(m_refreshTimer, &QTimer::timeout, this, &PropertiesWidget::loadDynamicData); m_refreshTimer->start(3000); // 3sec 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); connect(m_editHotkeyWeb, &QShortcut::activated, this, &PropertiesWidget::editWebSeed); connect(m_ui->listWebSeeds, &QListWidget::doubleClicked, this, &PropertiesWidget::editWebSeed); @@ -619,7 +620,7 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &) openFolder(index, true); } else if (act == actRename) { - renameSelectedFile(); + m_ui->filesList->renameSelectedFile(m_torrent); } else { BitTorrent::FilePriority prio = BitTorrent::FilePriority::Normal; @@ -671,125 +672,6 @@ void PropertiesWidget::displayWebSeedListMenu(const QPoint &) 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() { const QModelIndexList selectedIndexes = m_ui->filesList->selectionModel()->selectedRows(0); diff --git a/src/gui/properties/propertieswidget.h b/src/gui/properties/propertieswidget.h index 0da16226d..4f1d56609 100644 --- a/src/gui/properties/propertieswidget.h +++ b/src/gui/properties/propertieswidget.h @@ -103,7 +103,6 @@ protected slots: void filteredFilesChanged(); void showPiecesDownloaded(bool show); void showPiecesAvailability(bool show); - void renameSelectedFile(); void openSelectedFile(); private slots: diff --git a/src/gui/torrentcontenttreeview.cpp b/src/gui/torrentcontenttreeview.cpp index 972bf53a7..285d3f568 100644 --- a/src/gui/torrentcontenttreeview.cpp +++ b/src/gui/torrentcontenttreeview.cpp @@ -28,11 +28,23 @@ #include "torrentcontenttreeview.h" +#include #include #include +#include +#include #include #include +#include +#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" 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(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(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 current = currentIndex(); diff --git a/src/gui/torrentcontenttreeview.h b/src/gui/torrentcontenttreeview.h index 62c1d4471..b44c395af 100644 --- a/src/gui/torrentcontenttreeview.h +++ b/src/gui/torrentcontenttreeview.h @@ -31,6 +31,12 @@ #include +namespace BitTorrent +{ + class TorrentHandle; + class TorrentInfo; +} + class TorrentContentTreeView : public QTreeView { Q_OBJECT @@ -39,6 +45,9 @@ public: explicit TorrentContentTreeView(QWidget *parent = nullptr); void keyPressEvent(QKeyEvent *event) override; + void renameSelectedFile(BitTorrent::TorrentHandle *torrent); + void renameSelectedFile(BitTorrent::TorrentInfo &torrent); + private: QModelIndex currentNameCell(); };