diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 56474550e..c6a489933 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -244,6 +244,7 @@ Session::Session(QObject *parent) , m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers")) , m_globalMaxRatio(BITTORRENT_SESSION_KEY("GlobalMaxRatio"), -1, [](qreal r) { return r < 0 ? -1. : r;}) , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY("AddTorrentPaused"), false) + , m_isCreateTorrentSubfolder(BITTORRENT_SESSION_KEY("CreateTorrentSubfolder"), true) , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY("AddExtensionToIncompleteFiles"), false) , m_refreshInterval(BITTORRENT_SESSION_KEY("RefreshInterval"), 1500) , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY("Preallocation"), false) @@ -1405,7 +1406,9 @@ bool Session::deleteTorrent(const QString &hash, bool deleteLocalFiles) // Remove it from session if (deleteLocalFiles) { - m_savePathsToRemove[torrent->hash()] = torrent->rootPath(true); + QString rootPath = torrent->rootPath(true); + if (!rootPath.isEmpty()) + m_savePathsToRemove[torrent->hash()] = rootPath; m_nativeSession->remove_torrent(torrent->nativeHandle(), libt::session::delete_files); } else { @@ -1652,6 +1655,9 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri p = magnetUri.addTorrentParams(); } else if (torrentInfo.isValid()) { + if (!addData.resumed && !addData.hasRootFolder) + torrentInfo.stripRootFolder(); + // Metadata if (!addData.resumed && !addData.hasSeedStatus) findIncompleteFiles(torrentInfo, savePath); @@ -3248,6 +3254,16 @@ void Session::getPendingAlerts(std::vector &out, ulong time) #endif } +bool Session::isCreateTorrentSubfolder() const +{ + return m_isCreateTorrentSubfolder; +} + +void Session::setCreateTorrentSubfolder(bool value) +{ + m_isCreateTorrentSubfolder = value; +} + // Read alerts sent by the BitTorrent session void Session::readAlerts() { @@ -3646,6 +3662,7 @@ namespace torrentData.name = QString::fromStdString(fast.dict_find_string_value("qBt-name")); torrentData.hasSeedStatus = fast.dict_find_int_value("qBt-seedStatus"); torrentData.disableTempPath = fast.dict_find_int_value("qBt-tempPathDisabled"); + torrentData.hasRootFolder = fast.dict_find_int_value("qBt-hasRootFolder"); magnetUri = MagnetUri(QString::fromStdString(fast.dict_find_string_value("qBt-magnetUri"))); torrentData.addPaused = fast.dict_find_int_value("qBt-paused"); diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index adeae914a..0af2500ca 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -147,6 +147,7 @@ namespace BitTorrent QVector filePriorities; // used if TorrentInfo is set bool ignoreShareRatio = false; bool skipChecking = false; + bool createSubfolder = true; }; struct TorrentStatusReport @@ -221,6 +222,8 @@ namespace BitTorrent void setPeXEnabled(bool enabled); bool isAddTorrentPaused() const; void setAddTorrentPaused(bool value); + bool isCreateTorrentSubfolder() const; + void setCreateTorrentSubfolder(bool value); bool isTrackerEnabled() const; void setTrackerEnabled(bool enabled); bool isAppendExtensionEnabled() const; @@ -538,6 +541,7 @@ namespace BitTorrent CachedSettingValue m_additionalTrackers; CachedSettingValue m_globalMaxRatio; CachedSettingValue m_isAddTorrentPaused; + CachedSettingValue m_isCreateTorrentSubfolder; CachedSettingValue m_isAppendExtensionEnabled; CachedSettingValue m_refreshInterval; CachedSettingValue m_isPreallocationEnabled; diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index 607551d7b..41f3fbf4a 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -89,6 +89,7 @@ AddTorrentData::AddTorrentData(const AddTorrentParams ¶ms) , sequential(params.sequential) , hasSeedStatus(params.skipChecking) // do not react on 'torrent_finished_alert' when skipping , skipChecking(params.skipChecking) + , hasRootFolder(params.createSubfolder) , addForced(params.addForced) , addPaused(params.addPaused) , filePriorities(params.filePriorities) @@ -199,6 +200,7 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle , m_ratioLimit(data.ratioLimit) , m_tempPathDisabled(data.disableTempPath) , m_hasMissingFiles(false) + , m_hasRootFolder(data.hasRootFolder) , m_pauseAfterRecheck(false) , m_needSaveResumeData(false) { @@ -208,8 +210,13 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle updateStatus(); m_hash = InfoHash(m_nativeStatus.info_hash); - if (!data.resumed) + if (!data.resumed) { setSequentialDownload(data.sequential); + if (hasMetadata()) { + if (filesCount() == 1) + m_hasRootFolder = false; + } + } } TorrentHandle::~TorrentHandle() {} @@ -230,6 +237,9 @@ QString TorrentHandle::name() const if (name.isEmpty()) name = QString::fromStdString(m_nativeStatus.name); + if (name.isEmpty() && hasMetadata()) + name = QString::fromStdString(m_torrentInfo.nativeInfo()->orig_files().name()); + if (name.isEmpty()) name = m_hash; @@ -302,6 +312,9 @@ QString TorrentHandle::savePath(bool actual) const QString TorrentHandle::rootPath(bool actual) const { + if ((filesCount() > 1) && !hasRootFolder()) + return QString(); + QString firstFilePath = filePath(0); const int slashIndex = firstFilePath.indexOf("/"); if (slashIndex >= 0) @@ -314,8 +327,10 @@ QString TorrentHandle::contentPath(bool actual) const { if (filesCount() == 1) return QDir(savePath(actual)).absoluteFilePath(filePath(0)); - else + else if (hasRootFolder()) return rootPath(actual); + else + return savePath(actual); } bool TorrentHandle::isAutoTMMEnabled() const @@ -334,6 +349,11 @@ void TorrentHandle::setAutoTMMEnabled(bool enabled) move_impl(m_session->categorySavePath(m_category)); } +bool TorrentHandle::hasRootFolder() const +{ + return m_hasRootFolder; +} + QString TorrentHandle::nativeActualSavePath() const { return QString::fromStdString(m_nativeStatus.save_path); @@ -1489,6 +1509,7 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert resumeData["qBt-seedStatus"] = m_hasSeedStatus; resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled; resumeData["qBt-queuePosition"] = queuePosition(); + resumeData["qBt-hasRootFolder"] = m_hasRootFolder; m_session->handleTorrentResumeDataReady(this, resumeData); } @@ -1590,6 +1611,10 @@ void TorrentHandle::handleMetadataReceivedAlert(libt::metadata_received_alert *p updateStatus(); if (m_session->isAppendExtensionEnabled()) manageIncompleteFiles(); + if (!m_hasRootFolder) + m_torrentInfo.stripRootFolder(); + if (filesCount() == 1) + m_hasRootFolder = false; m_session->handleTorrentMetadataReceived(this); if (isPaused()) { diff --git a/src/base/bittorrent/torrenthandle.h b/src/base/bittorrent/torrenthandle.h index a0837d09f..f8226bcb1 100644 --- a/src/base/bittorrent/torrenthandle.h +++ b/src/base/bittorrent/torrenthandle.h @@ -98,6 +98,7 @@ namespace BitTorrent bool sequential; bool hasSeedStatus; bool skipChecking; + bool hasRootFolder; TriStateBool addForced; TriStateBool addPaused; // for new torrents @@ -206,6 +207,9 @@ namespace BitTorrent // file4 // // + // Torrent A* (Torrent A in "strip root folder" mode) + // + // // Torrent B (singlefile) // // torrentB/ @@ -222,6 +226,7 @@ namespace BitTorrent // | | rootPath | contentPath | // |---|------------------------------|--------------------------------------------| // | A | /home/user/torrents/torrentA | /home/user/torrents/torrentA | + // | A*| | /home/user/torrents | // | B | /home/user/torrents/torrentB | /home/user/torrents/torrentB/subdir1/file1 | // | C | /home/user/torrents/file1 | /home/user/torrents/file1 | @@ -235,6 +240,8 @@ namespace BitTorrent bool belongsToCategory(const QString &category) const; bool setCategory(const QString &category); + bool hasRootFolder() const; + int filesCount() const; int piecesCount() const; int piecesHave() const; @@ -395,7 +402,7 @@ namespace BitTorrent Session *const m_session; libtorrent::torrent_handle m_nativeHandle; libtorrent::torrent_status m_nativeStatus; - TorrentState m_state; + TorrentState m_state; TorrentInfo m_torrentInfo; SpeedMonitor m_speedMonitor; @@ -421,6 +428,7 @@ namespace BitTorrent qreal m_ratioLimit; bool m_tempPathDisabled; bool m_hasMissingFiles; + bool m_hasRootFolder; bool m_pauseAfterRecheck; bool m_needSaveResumeData; diff --git a/src/base/bittorrent/torrentinfo.cpp b/src/base/bittorrent/torrentinfo.cpp index 6cf53124f..357378bd2 100644 --- a/src/base/bittorrent/torrentinfo.cpp +++ b/src/base/bittorrent/torrentinfo.cpp @@ -279,7 +279,7 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(int fileIndex) const void TorrentInfo::renameFile(uint index, const QString &newPath) { if (!isValid()) return; - nativeInfo()->rename_file(index, newPath.toStdString()); + nativeInfo()->rename_file(index, Utils::Fs::toNativePath(newPath).toStdString()); } int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const @@ -293,6 +293,24 @@ int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const return -1; } +void TorrentInfo::stripRootFolder() +{ + if (filesCount() <= 1) return; + + libtorrent::file_storage files = m_nativeInfo->files(); + + // Solution for case of renamed root folder + std::string testName = filePath(0).split('/').value(0).toStdString(); + if (files.name() != testName) { + files.set_name(testName); + for (int i = 0; i < files.num_files(); ++i) + files.rename_file(i, files.file_path(i)); + } + + files.set_name(""); + m_nativeInfo->remap_files(files); +} + TorrentInfo::NativePtr TorrentInfo::nativeInfo() const { return m_nativeInfo; diff --git a/src/base/bittorrent/torrentinfo.h b/src/base/bittorrent/torrentinfo.h index 9a148add0..8f918087b 100644 --- a/src/base/bittorrent/torrentinfo.h +++ b/src/base/bittorrent/torrentinfo.h @@ -99,6 +99,7 @@ namespace BitTorrent PieceRange filePieces(int fileIndex) const; void renameFile(uint index, const QString &newPath); + void stripRootFolder(); NativePtr nativeInfo() const; diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index c773a9cbd..7cecb3ec7 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -35,6 +35,7 @@ #include #include +#include "base/preferences.h" #include "base/settingsstorage.h" #include "base/net/downloadmanager.h" #include "base/net/downloadhandler.h" @@ -95,6 +96,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) connect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int))); connect(ui->browseButton, SIGNAL(clicked()), SLOT(browseButton_clicked())); ui->defaultSavePathCheckBox->setVisible(false); // Default path is selected by default + ui->createSubfolderCheckBox->setChecked(session->isCreateTorrentSubfolder()); ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never); @@ -624,9 +626,8 @@ void AddNewTorrentDialog::accept() BitTorrent::AddTorrentParams params; - if (ui->skip_check_cb->isChecked()) - // TODO: Check if destination actually exists - params.skipChecking = true; + // TODO: Check if destination actually exists + params.skipChecking = ui->skipCheckingCheckBox->isChecked(); // Category params.category = ui->categoryComboBox->currentText(); @@ -639,6 +640,7 @@ void AddNewTorrentDialog::accept() params.filePriorities = m_contentModel->model()->getFilePriorities(); params.addPaused = !ui->startTorrentCheckBox->isChecked(); + params.createSubfolder = ui->createSubfolderCheckBox->isChecked(); QString savePath = ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString(); if (ui->comboTTM->currentIndex() != 1) { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode. diff --git a/src/gui/addnewtorrentdialog.ui b/src/gui/addnewtorrentdialog.ui index e214b2aa6..e010403b0 100644 --- a/src/gui/addnewtorrentdialog.ui +++ b/src/gui/addnewtorrentdialog.ui @@ -170,7 +170,7 @@ - + Skip hash check @@ -192,6 +192,26 @@ + + + + Start torrent + + + true + + + + + + + Create subfolder + + + true + + + @@ -285,8 +305,8 @@ 0 0 - 308 - 74 + 321 + 69 @@ -403,7 +423,7 @@ never_show_cb adv_button startTorrentCheckBox - skip_check_cb + skipCheckingCheckBox categoryComboBox defaultCategoryCheckbox scrollArea diff --git a/src/gui/optionsdlg.cpp b/src/gui/optionsdlg.cpp index d4cb9cb5a..03abb20be 100644 --- a/src/gui/optionsdlg.cpp +++ b/src/gui/optionsdlg.cpp @@ -214,6 +214,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) connect(m_ui->checkAdditionDialog, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(m_ui->checkAdditionDialogFront, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(m_ui->checkStartPaused, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); + connect(m_ui->checkCreateSubfolder, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(m_ui->deleteTorrentBox, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(m_ui->deleteCancelledTorrentBox, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(m_ui->checkExportDir, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); @@ -515,6 +516,7 @@ void OptionsDialog::saveOptions() AddNewTorrentDialog::setEnabled(useAdditionDialog()); AddNewTorrentDialog::setTopLevel(m_ui->checkAdditionDialogFront->isChecked()); session->setAddTorrentPaused(addTorrentsInPause()); + session->setCreateTorrentSubfolder(m_ui->checkCreateSubfolder->isChecked()); ScanFoldersModel::instance()->removeFromFSWatcher(removedScanDirs); ScanFoldersModel::instance()->addToFSWatcher(addedScanDirs); ScanFoldersModel::instance()->makePersistent(); @@ -716,6 +718,7 @@ void OptionsDialog::loadOptions() m_ui->checkAdditionDialog->setChecked(AddNewTorrentDialog::isEnabled()); m_ui->checkAdditionDialogFront->setChecked(AddNewTorrentDialog::isTopLevel()); m_ui->checkStartPaused->setChecked(session->isAddTorrentPaused()); + m_ui->checkCreateSubfolder->setChecked(session->isCreateTorrentSubfolder()); const TorrentFileGuard::AutoDeleteMode autoDeleteMode = TorrentFileGuard::autoDeleteMode(); m_ui->deleteTorrentBox->setChecked(autoDeleteMode != TorrentFileGuard::Never); m_ui->deleteCancelledTorrentBox->setChecked(autoDeleteMode == TorrentFileGuard::Always); diff --git a/src/gui/optionsdlg.ui b/src/gui/optionsdlg.ui index 8b41e7546..82854e831 100644 --- a/src/gui/optionsdlg.ui +++ b/src/gui/optionsdlg.ui @@ -709,6 +709,16 @@ + + + + Create subfolder for torrents with multiple files + + + true + + + diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 9015b2a33..06fccc1b8 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -316,7 +316,8 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent // List files in torrent PropListModel->model()->setupModelData(m_torrent->info()); - filesList->setExpanded(PropListModel->index(0, 0), true); + if ((m_torrent->filesCount() > 1) && (PropListModel->model()->rowCount() == 1)) + filesList->setExpanded(PropListModel->index(0, 0), true); // Load file priorities PropListModel->model()->updateFilesPriorities(m_torrent->filePriorities());