diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index d807212ea..41a1a0a05 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -1479,10 +1479,9 @@ void TorrentHandle::moveStorage(const QString &newPath, bool overwrite) } } -void TorrentHandle::renameFile(int index, const QString &name) +void TorrentHandle::renameFile(const int index, const QString &name) { ++m_renameCount; - qDebug() << Q_FUNC_INFO << index << name; m_nativeHandle.rename_file(index, Utils::Fs::toNativePath(name).toStdString()); } @@ -1777,6 +1776,10 @@ void TorrentHandle::handleFileRenameFailedAlert(const lt::file_rename_failed_ale { 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_renameCount; while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) m_moveFinishedTriggers.takeFirst()(); diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 3c53eabe4..cb077db9d 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -151,8 +151,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(); } @@ -434,107 +436,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(); @@ -574,7 +475,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::DownloadPriority prio = BitTorrent::DownloadPriority::Normal; diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index eb8f9d598..00ddb9a9d 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -82,7 +82,6 @@ private slots: void displayContentTreeMenu(const QPoint &); void updateDiskSpaceLabel(); void onSavePathChanged(const QString &newPath); - void renameSelectedFile(); void updateMetadata(const BitTorrent::TorrentInfo &info); void handleDownloadFinished(const Net::DownloadResult &result); void TMMChanged(int index); diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 07209d963..2796740f8 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -150,7 +150,8 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) connect(m_tabBar, &PropTabBar::visibilityToggled, this, &PropertiesWidget::saveSettings); 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); @@ -621,7 +622,7 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &) openFolder(index, true); } else if (act == actRename) { - renameSelectedFile(); + m_ui->filesList->renameSelectedFile(m_torrent); } else { BitTorrent::DownloadPriority prio = BitTorrent::DownloadPriority::Normal; @@ -673,125 +674,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 c0c5750d3..7d4265c55 100644 --- a/src/gui/properties/propertieswidget.h +++ b/src/gui/properties/propertieswidget.h @@ -95,7 +95,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 712964a95..1a0761a23 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) @@ -75,6 +87,241 @@ 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(); + + // Remove old folder + const QString oldFullPath = torrent->savePath(true) + oldPath; + int timeout = 10; + while (!QDir().rmpath(oldFullPath) && (timeout > 0)) { + // FIXME: We should not sleep here (freezes the UI for 1 second) + QThread::msleep(100); + --timeout; + } + + 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(); };