From 0e8feed2f2da08d155c5dcfc28c50508e209efcc Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Sat, 21 Nov 2020 15:16:21 +0300 Subject: [PATCH 1/4] Clean up metadata downloading code --- src/base/bittorrent/session.cpp | 59 ++++++++++++++------------------- src/base/bittorrent/session.h | 8 ++--- src/gui/addnewtorrentdialog.cpp | 8 ++--- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 5f129ab98..9633b0ace 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -1892,12 +1892,12 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio return true; } -bool Session::cancelLoadMetadata(const InfoHash &hash) +bool Session::cancelDownloadMetadata(const InfoHash &hash) { - const auto loadedMetadataIter = m_loadedMetadata.find(hash); - if (loadedMetadataIter == m_loadedMetadata.end()) return false; + const auto downloadedMetadataIter = m_downloadedMetadata.find(hash); + if (downloadedMetadataIter == m_downloadedMetadata.end()) return false; - m_loadedMetadata.erase(loadedMetadataIter); + m_downloadedMetadata.erase(downloadedMetadataIter); --m_extraLimit; adjustLimits(); m_nativeSession->remove_torrent(m_nativeSession->find_torrent(hash), lt::session::delete_files); @@ -1951,7 +1951,7 @@ void Session::decreaseTorrentsQueuePos(const QVector &hashes) torrentQueue.pop(); } - for (auto i = m_loadedMetadata.cbegin(); i != m_loadedMetadata.cend(); ++i) + for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i) torrentQueuePositionBottom(m_nativeSession->find_torrent(*i)); saveTorrentsQueue(); @@ -2004,7 +2004,7 @@ void Session::bottomTorrentsQueuePos(const QVector &hashes) torrentQueue.pop(); } - for (auto i = m_loadedMetadata.cbegin(); i != m_loadedMetadata.cend(); ++i) + for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i) torrentQueuePositionBottom(m_nativeSession->find_torrent(*i)); saveTorrentsQueue(); @@ -2058,21 +2058,6 @@ bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &par { if (!magnetUri.isValid()) return false; - const InfoHash hash = magnetUri.hash(); - - const auto it = m_loadedMetadata.constFind(hash); - if (it != m_loadedMetadata.constEnd()) - { - // It looks illogical that we don't just use an existing handle, - // but as previous experience has shown, it actually creates unnecessary - // problems and unwanted behavior due to the fact that it was originally - // added with parameters other than those provided by the user. - m_loadedMetadata.erase(it); - --m_extraLimit; - adjustLimits(); - m_nativeSession->remove_torrent(m_nativeSession->find_torrent(hash), lt::session::delete_files); - } - return addTorrent_impl(params, magnetUri); } @@ -2113,7 +2098,7 @@ LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorr const QString category = addTorrentParams.category; if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category)) - loadTorrentParams.category = ""; + loadTorrentParams.category = ""; else loadTorrentParams.category = addTorrentParams.category; @@ -2126,9 +2111,15 @@ bool Session::addTorrent_impl(const AddTorrentParams &addTorrentParams, const Ma const bool hasMetadata = metadata.isValid(); const InfoHash hash = (hasMetadata ? metadata.hash() : magnetUri.hash()); + // It looks illogical that we don't just use an existing handle, + // but as previous experience has shown, it actually creates unnecessary + // problems and unwanted behavior due to the fact that it was originally + // added with parameters other than those provided by the user. + cancelDownloadMetadata(hash); + // We should not add the torrent if it is already // processed or is pending to add to session - if (m_loadingTorrents.contains(hash) || m_loadedMetadata.contains(hash)) + if (m_loadingTorrents.contains(hash)) return false; TorrentHandleImpl *const torrent = m_torrents.value(hash); @@ -2276,9 +2267,9 @@ bool Session::findIncompleteFiles(TorrentInfo &torrentInfo, QString &savePath) c return found; } -// Add a torrent to the BitTorrent session in hidden mode -// and force it to load its metadata -bool Session::loadMetadata(const MagnetUri &magnetUri) +// Add a torrent to libtorrent session in hidden mode +// and force it to download its metadata +bool Session::downloadMetadata(const MagnetUri &magnetUri) { if (!magnetUri.isValid()) return false; @@ -2289,7 +2280,7 @@ bool Session::loadMetadata(const MagnetUri &magnetUri) // processed or adding to session if (m_torrents.contains(hash)) return false; if (m_loadingTorrents.contains(hash)) return false; - if (m_loadedMetadata.contains(hash)) return false; + if (m_downloadedMetadata.contains(hash)) return false; qDebug("Adding torrent to preload metadata..."); qDebug(" -> Hash: %s", qUtf8Printable(hash)); @@ -2322,13 +2313,13 @@ bool Session::loadMetadata(const MagnetUri &magnetUri) p.storage = customStorageConstructor; #endif - // Adding torrent to BitTorrent session + // Adding torrent to libtorrent session lt::error_code ec; lt::torrent_handle h = m_nativeSession->add_torrent(p, ec); if (ec) return false; // waiting for metadata... - m_loadedMetadata.insert(h.info_hash()); + m_downloadedMetadata.insert(h.info_hash()); ++m_extraLimit; adjustLimits(); @@ -3775,7 +3766,7 @@ bool Session::isKnownTorrent(const InfoHash &hash) const { return (m_torrents.contains(hash) || m_loadingTorrents.contains(hash) - || m_loadedMetadata.contains(hash)); + || m_downloadedMetadata.contains(hash)); } void Session::updateSeedingLimitTimer() @@ -4724,18 +4715,18 @@ void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_ale void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) { const InfoHash hash {p->handle.info_hash()}; - const auto loadedMetadataIter = m_loadedMetadata.find(hash); + const auto downloadedMetadataIter = m_downloadedMetadata.find(hash); - if (loadedMetadataIter != m_loadedMetadata.end()) + if (downloadedMetadataIter != m_downloadedMetadata.end()) { TorrentInfo metadata {p->handle.torrent_file()}; - m_loadedMetadata.erase(loadedMetadataIter); + m_downloadedMetadata.erase(downloadedMetadataIter); --m_extraLimit; adjustLimits(); m_nativeSession->remove_torrent(p->handle, lt::session::delete_files); - emit metadataLoaded(metadata); + emit metadataDownloaded(metadata); } } diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index ffabfc2de..5baf05673 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -453,8 +453,8 @@ namespace BitTorrent bool addTorrent(const MagnetUri &magnetUri, const AddTorrentParams ¶ms = AddTorrentParams()); bool addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams ¶ms = AddTorrentParams()); bool deleteTorrent(const InfoHash &hash, DeleteOption deleteOption = Torrent); - bool loadMetadata(const MagnetUri &magnetUri); - bool cancelLoadMetadata(const InfoHash &hash); + bool downloadMetadata(const MagnetUri &magnetUri); + bool cancelDownloadMetadata(const InfoHash &hash); void recursiveTorrentDownload(const InfoHash &hash); void increaseTorrentsQueuePos(const QVector &hashes); @@ -497,7 +497,7 @@ namespace BitTorrent void fullDiskError(TorrentHandle *torrent, const QString &msg); void IPFilterParsed(bool error, int ruleCount); void loadTorrentFailed(const QString &error); - void metadataLoaded(const TorrentInfo &info); + void metadataDownloaded(const TorrentInfo &info); void recursiveTorrentDownloadPossible(TorrentHandle *torrent); void speedLimitModeChanged(bool alternative); void statsUpdated(); @@ -764,7 +764,7 @@ namespace BitTorrent QThread *m_ioThread = nullptr; ResumeDataSavingManager *m_resumeDataSavingManager = nullptr; - QSet m_loadedMetadata; + QSet m_downloadedMetadata; QHash m_torrents; QHash m_loadingTorrents; diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index ae61834eb..d95190c8f 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -347,7 +347,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri) return false; } - connect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataLoaded, this, &AddNewTorrentDialog::updateMetadata); + connect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata); // Set dialog title const QString torrentName = magnetUri.name(); @@ -356,7 +356,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri) setupTreeview(); TMMChanged(m_ui->comboTTM->currentIndex()); - BitTorrent::Session::instance()->loadMetadata(magnetUri); + BitTorrent::Session::instance()->downloadMetadata(magnetUri); setMetadataProgressIndicator(true, tr("Retrieving metadata...")); m_ui->labelHashData->setText(infoHash); @@ -613,7 +613,7 @@ void AddNewTorrentDialog::reject() if (!m_hasMetadata) { setMetadataProgressIndicator(false); - BitTorrent::Session::instance()->cancelLoadMetadata(m_magnetURI.hash()); + BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.hash()); } QDialog::reject(); @@ -623,7 +623,7 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata { if (metadata.hash() != m_magnetURI.hash()) return; - disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataLoaded, this, &AddNewTorrentDialog::updateMetadata); + disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata); if (!metadata.isValid()) { From acab62e345a6af64307aaa2a39cd44a2064f23ac Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Sat, 21 Nov 2020 15:29:36 +0300 Subject: [PATCH 2/4] Properly handle "Append extension" option changing --- src/base/bittorrent/session.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 9633b0ace..55791b2a9 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -593,11 +593,11 @@ void Session::setAppendExtensionEnabled(const bool enabled) { if (isAppendExtensionEnabled() != enabled) { + m_isAppendExtensionEnabled = enabled; + // append or remove .!qB extension for incomplete files for (TorrentHandleImpl *const torrent : asConst(m_torrents)) torrent->handleAppendExtensionToggled(); - - m_isAppendExtensionEnabled = enabled; } } From 9497300a4aab322f8fce0c80f6895ad1c001a200 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Sun, 29 Nov 2020 18:40:49 +0300 Subject: [PATCH 3/4] Don't rewrite TorrentInfo instance if it's valid --- src/base/bittorrent/torrenthandleimpl.cpp | 30 +++++++---------------- src/base/bittorrent/torrenthandleimpl.h | 1 - 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/base/bittorrent/torrenthandleimpl.cpp b/src/base/bittorrent/torrenthandleimpl.cpp index f9928584a..792a5a72a 100644 --- a/src/base/bittorrent/torrenthandleimpl.cpp +++ b/src/base/bittorrent/torrenthandleimpl.cpp @@ -123,8 +123,10 @@ TorrentHandleImpl::TorrentHandleImpl(Session *session, const lt::torrent_handle if (m_useAutoTMM) m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category)); + m_hash = InfoHash {m_nativeHandle.info_hash()}; + m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; + updateStatus(); - m_hash = InfoHash(m_nativeStatus.info_hash); if (hasMetadata()) { @@ -792,7 +794,7 @@ void TorrentHandleImpl::updateState() bool TorrentHandleImpl::hasMetadata() const { - return m_nativeStatus.has_metadata; + return m_torrentInfo.isValid(); } bool TorrentHandleImpl::hasMissingFiles() const @@ -1560,12 +1562,8 @@ void TorrentHandleImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejec void TorrentHandleImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p) { - // 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 + // 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[p->index].takeFirst(); const QString newFilePath = Utils::Fs::toUniformPath(p->new_name()); @@ -1626,10 +1624,6 @@ void TorrentHandleImpl::handleFileRenameFailedAlert(const lt::file_rename_failed void TorrentHandleImpl::handleFileCompletedAlert(const lt::file_completed_alert *p) { - // 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()}; - qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name())); if (m_session->isAppendExtensionEnabled()) { @@ -1647,8 +1641,10 @@ void TorrentHandleImpl::handleFileCompletedAlert(const lt::file_completed_alert void TorrentHandleImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) { Q_UNUSED(p); + qDebug("Metadata received for torrent %s.", qUtf8Printable(name())); - updateStatus(); + m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; + if (m_session->isAppendExtensionEnabled()) manageIncompleteFiles(); if (!m_hasRootFolder) @@ -1814,13 +1810,6 @@ lt::torrent_handle TorrentHandleImpl::nativeHandle() const return m_nativeHandle; } -void TorrentHandleImpl::updateTorrentInfo() -{ - if (!hasMetadata()) return; - - m_torrentInfo = TorrentInfo(m_nativeStatus.torrent_file.lock()); -} - bool TorrentHandleImpl::isMoveInProgress() const { return m_storageIsMoving; @@ -1841,7 +1830,6 @@ void TorrentHandleImpl::updateStatus(const lt::torrent_status &nativeStatus) m_nativeStatus = nativeStatus; updateState(); - updateTorrentInfo(); // NOTE: Don't change the order of these conditionals! // Otherwise it will not work properly since torrent can be CheckingDownloading. diff --git a/src/base/bittorrent/torrenthandleimpl.h b/src/base/bittorrent/torrenthandleimpl.h index d0cfb7d31..1c813a1a4 100644 --- a/src/base/bittorrent/torrenthandleimpl.h +++ b/src/base/bittorrent/torrenthandleimpl.h @@ -250,7 +250,6 @@ namespace BitTorrent void updateStatus(); void updateStatus(const lt::torrent_status &nativeStatus); void updateState(); - void updateTorrentInfo(); void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p); void handleFileCompletedAlert(const lt::file_completed_alert *p); From a93b675cb85e79f7a9666aba816a09f69f655941 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Wed, 2 Dec 2020 09:16:11 +0300 Subject: [PATCH 4/4] Search for existing files in separate thread --- src/base/CMakeLists.txt | 2 + src/base/base.pri | 2 + src/base/bittorrent/filesearcher.cpp | 69 +++++++++++++++++++ src/base/bittorrent/filesearcher.h | 52 ++++++++++++++ src/base/bittorrent/infohash.cpp | 2 + src/base/bittorrent/infohash.h | 6 +- src/base/bittorrent/session.cpp | 90 ++++++++++++++----------- src/base/bittorrent/session.h | 8 ++- src/gui/properties/propertieswidget.cpp | 4 +- src/gui/transferlistmodel.cpp | 2 +- 10 files changed, 191 insertions(+), 46 deletions(-) create mode 100644 src/base/bittorrent/filesearcher.cpp create mode 100644 src/base/bittorrent/filesearcher.h diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index a9c27a861..1c9222263 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(qbt_base STATIC bittorrent/common.h bittorrent/customstorage.h bittorrent/downloadpriority.h + bittorrent/filesearcher.h bittorrent/filterparserthread.h bittorrent/infohash.h bittorrent/ltqhash.h @@ -90,6 +91,7 @@ add_library(qbt_base STATIC bittorrent/bandwidthscheduler.cpp bittorrent/customstorage.cpp bittorrent/downloadpriority.cpp + bittorrent/filesearcher.cpp bittorrent/filterparserthread.cpp bittorrent/infohash.cpp bittorrent/magneturi.cpp diff --git a/src/base/base.pri b/src/base/base.pri index b8bbb3628..4fa643eb3 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -7,6 +7,7 @@ HEADERS += \ $$PWD/bittorrent/common.h \ $$PWD/bittorrent/customstorage.h \ $$PWD/bittorrent/downloadpriority.h \ + $$PWD/bittorrent/filesearcher.h \ $$PWD/bittorrent/filterparserthread.h \ $$PWD/bittorrent/infohash.h \ $$PWD/bittorrent/ltqhash.h \ @@ -90,6 +91,7 @@ SOURCES += \ $$PWD/bittorrent/bandwidthscheduler.cpp \ $$PWD/bittorrent/customstorage.cpp \ $$PWD/bittorrent/downloadpriority.cpp \ + $$PWD/bittorrent/filesearcher.cpp \ $$PWD/bittorrent/filterparserthread.cpp \ $$PWD/bittorrent/infohash.cpp \ $$PWD/bittorrent/magneturi.cpp \ diff --git a/src/base/bittorrent/filesearcher.cpp b/src/base/bittorrent/filesearcher.cpp new file mode 100644 index 000000000..563cfbddb --- /dev/null +++ b/src/base/bittorrent/filesearcher.cpp @@ -0,0 +1,69 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2020 Vladimir Golovnev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "filesearcher.h" + +#include + +#include "base/bittorrent/common.h" +#include "base/bittorrent/infohash.h" + +void FileSearcher::search(const BitTorrent::InfoHash &id, const QStringList &originalFileNames + , const QString &completeSavePath, const QString &incompleteSavePath) +{ + const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool + { + const QDir dir {dirPath}; + bool found = false; + for (QString &fileName : fileNames) + { + if (dir.exists(fileName)) + { + found = true; + } + else if (dir.exists(fileName + QB_EXT)) + { + found = true; + fileName += QB_EXT; + } + } + + return found; + }; + + QString savePath = completeSavePath; + QStringList adjustedFileNames = originalFileNames; + const bool found = findInDir(savePath, adjustedFileNames); + if (!found && !incompleteSavePath.isEmpty()) + { + savePath = incompleteSavePath; + findInDir(savePath, adjustedFileNames); + } + + emit searchFinished(id, savePath, adjustedFileNames); +} diff --git a/src/base/bittorrent/filesearcher.h b/src/base/bittorrent/filesearcher.h new file mode 100644 index 000000000..7084f2084 --- /dev/null +++ b/src/base/bittorrent/filesearcher.h @@ -0,0 +1,52 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2020 Vladimir Golovnev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include + +namespace BitTorrent +{ + class InfoHash; +} + +class FileSearcher final : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(FileSearcher) + +public: + FileSearcher() = default; + +public slots: + void search(const BitTorrent::InfoHash &id, const QStringList &originalFileNames + , const QString &completeSavePath, const QString &incompleteSavePath); + +signals: + void searchFinished(const BitTorrent::InfoHash &id, const QString &savePath, const QStringList &fileNames); +}; diff --git a/src/base/bittorrent/infohash.cpp b/src/base/bittorrent/infohash.cpp index d60233998..29f7c0cda 100644 --- a/src/base/bittorrent/infohash.cpp +++ b/src/base/bittorrent/infohash.cpp @@ -33,6 +33,8 @@ using namespace BitTorrent; +const int InfoHashTypeId = qRegisterMetaType(); + InfoHash::InfoHash() : m_valid(false) { diff --git a/src/base/bittorrent/infohash.h b/src/base/bittorrent/infohash.h index bc1eb21ac..f015f3841 100644 --- a/src/base/bittorrent/infohash.h +++ b/src/base/bittorrent/infohash.h @@ -26,11 +26,11 @@ * exception statement from your version. */ -#ifndef BITTORRENT_INFOHASH_H -#define BITTORRENT_INFOHASH_H +#pragma once #include +#include #include namespace BitTorrent @@ -64,4 +64,4 @@ namespace BitTorrent uint qHash(const InfoHash &key, uint seed); } -#endif // BITTORRENT_INFOHASH_H +Q_DECLARE_METATYPE(BitTorrent::InfoHash) diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 55791b2a9..39b536102 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -88,6 +88,7 @@ #include "bandwidthscheduler.h" #include "common.h" #include "customstorage.h" +#include "filesearcher.h" #include "filterparserthread.h" #include "ltunderlyingtype.h" #include "magneturi.h" @@ -509,6 +510,12 @@ Session::Session(QObject *parent) m_resumeDataSavingManager = new ResumeDataSavingManager {m_resumeFolderPath}; m_resumeDataSavingManager->moveToThread(m_ioThread); connect(m_ioThread, &QThread::finished, m_resumeDataSavingManager, &QObject::deleteLater); + + m_fileSearcher = new FileSearcher; + m_fileSearcher->moveToThread(m_ioThread); + connect(m_ioThread, &QThread::finished, m_fileSearcher, &QObject::deleteLater); + connect(m_fileSearcher, &FileSearcher::searchFinished, this, &Session::fileSearchFinished); + m_ioThread->start(); // Regular saving of fastresume data @@ -1765,6 +1772,24 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result) } } +void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames) +{ + const auto loadingTorrentsIter = m_loadingTorrents.find(id); + if (loadingTorrentsIter != m_loadingTorrents.end()) + { + LoadTorrentParams params = loadingTorrentsIter.value(); + m_loadingTorrents.erase(loadingTorrentsIter); + + lt::add_torrent_params &p = params.ltAddTorrentParams; + + p.save_path = Utils::Fs::toNativePath(savePath).toStdString(); + for (int i = 0; i < fileNames.size(); ++i) + p.renamed_files[lt::file_index_t {i}] = fileNames[i].toStdString(); + + loadTorrent(params); + } +} + // Return the torrent handle, given its hash TorrentHandle *Session::findTorrent(const InfoHash &hash) const { @@ -2137,15 +2162,20 @@ bool Session::addTorrent_impl(const AddTorrentParams &addTorrentParams, const Ma LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams); lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams; + bool isFindingIncompleteFiles = false; + // If empty then Automatic mode, otherwise Manual mode - QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath; + const QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath; if (hasMetadata) { if (!loadTorrentParams.hasRootFolder) metadata.stripRootFolder(); if (!loadTorrentParams.hasSeedStatus) - findIncompleteFiles(metadata, actualSavePath); // if needed points savePath to incomplete folder too + { + findIncompleteFiles(metadata, actualSavePath); + isFindingIncompleteFiles = true; + } // if torrent name wasn't explicitly set we handle the case of // initial renaming of torrent content and rename torrent accordingly @@ -2175,9 +2205,6 @@ bool Session::addTorrent_impl(const AddTorrentParams &addTorrentParams, const Ma if (loadTorrentParams.name.isEmpty() && !p.name.empty()) loadTorrentParams.name = QString::fromStdString(p.name); - - if (isTempPathEnabled()) - actualSavePath = tempPath(); } p.save_path = Utils::Fs::toNativePath(actualSavePath).toStdString(); @@ -2209,7 +2236,11 @@ bool Session::addTorrent_impl(const AddTorrentParams &addTorrentParams, const Ma else p.flags |= lt::torrent_flags::auto_managed; - return loadTorrent(loadTorrentParams); + if (!isFindingIncompleteFiles) + return loadTorrent(loadTorrentParams); + + m_loadingTorrents.insert(hash, loadTorrentParams); + return true; } // Add a torrent to the BitTorrent session @@ -2234,37 +2265,22 @@ bool Session::loadTorrent(LoadTorrentParams params) return true; } -bool Session::findIncompleteFiles(TorrentInfo &torrentInfo, QString &savePath) const +void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const { - auto findInDir = [](const QString &dirPath, TorrentInfo &torrentInfo) -> bool + const InfoHash searchId = torrentInfo.hash(); + const QStringList originalFileNames = torrentInfo.filePaths(); + const QString completeSavePath = savePath; + const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {}); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + QMetaObject::invokeMethod(m_fileSearcher, [=]() { - const QDir dir(dirPath); - bool found = false; - for (int i = 0; i < torrentInfo.filesCount(); ++i) - { - const QString filePath = torrentInfo.filePath(i); - if (dir.exists(filePath)) - { - found = true; - } - else if (dir.exists(filePath + QB_EXT)) - { - found = true; - torrentInfo.renameFile(i, filePath + QB_EXT); - } - } - - return found; - }; - - bool found = findInDir(savePath, torrentInfo); - if (!found && isTempPathEnabled()) - { - savePath = torrentTempPath(torrentInfo); - found = findInDir(savePath, torrentInfo); - } - - return found; + m_fileSearcher->search(searchId, originalFileNames, completeSavePath, incompleteSavePath); + }); +#else + QMetaObject::invokeMethod(m_fileSearcher, "search" + , Q_ARG(InfoHash, searchId), Q_ARG(QStringList, originalFileNames) + , Q_ARG(QString, completeSavePath), Q_ARG(QString, incompleteSavePath)); +#endif } // Add a torrent to libtorrent session in hidden mode @@ -3870,8 +3886,6 @@ void Session::handleTorrentUrlSeedsRemoved(TorrentHandleImpl *const torrent, con void Session::handleTorrentMetadataReceived(TorrentHandleImpl *const torrent) { - torrent->saveResumeData(); - // Save metadata const QDir resumeDataDir {m_resumeFolderPath}; const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->hash())}; @@ -3888,7 +3902,7 @@ void Session::handleTorrentMetadataReceived(TorrentHandleImpl *const torrent) .arg(torrentFileName, err.message()), Log::CRITICAL); } - emit torrentMetadataLoaded(torrent); + emit torrentMetadataReceived(torrent); } void Session::handleTorrentPaused(TorrentHandleImpl *const torrent) diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 5baf05673..aefbe1df5 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -64,6 +64,7 @@ class QTimer; class QUrl; class BandwidthScheduler; +class FileSearcher; class FilterParserThread; class ResumeDataSavingManager; class Statistics; @@ -488,6 +489,8 @@ namespace BitTorrent bool addMoveTorrentStorageJob(TorrentHandleImpl *torrent, const QString &newPath, MoveStorageMode mode); + void findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const; + signals: void allTorrentsFinished(); void categoryAdded(const QString &categoryName); @@ -510,7 +513,7 @@ namespace BitTorrent void torrentFinished(TorrentHandle *torrent); void torrentFinishedChecking(TorrentHandle *torrent); void torrentLoaded(TorrentHandle *torrent); - void torrentMetadataLoaded(TorrentHandle *torrent); + void torrentMetadataReceived(TorrentHandle *torrent); void torrentPaused(TorrentHandle *torrent); void torrentResumed(TorrentHandle *torrent); void torrentSavePathChanged(TorrentHandle *torrent); @@ -537,6 +540,7 @@ namespace BitTorrent void handleIPFilterParsed(int ruleCount); void handleIPFilterError(); void handleDownloadFinished(const Net::DownloadResult &result); + void fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames); // Session reconfiguration triggers void networkOnlineStateChanged(bool online); @@ -593,7 +597,6 @@ namespace BitTorrent bool loadTorrent(LoadTorrentParams params); LoadTorrentParams initLoadTorrentParams(const AddTorrentParams &addTorrentParams); bool addTorrent_impl(const AddTorrentParams &addTorrentParams, const MagnetUri &magnetUri, TorrentInfo torrentInfo = TorrentInfo()); - bool findIncompleteFiles(TorrentInfo &torrentInfo, QString &savePath) const; void updateSeedingLimitTimer(); void exportTorrentFile(const TorrentHandle *torrent, TorrentExportFolder folder = TorrentExportFolder::Regular); @@ -763,6 +766,7 @@ namespace BitTorrent // fastresume data writing thread QThread *m_ioThread = nullptr; ResumeDataSavingManager *m_resumeDataSavingManager = nullptr; + FileSearcher *m_fileSearcher = nullptr; QSet m_downloadedMetadata; diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 7b8c41d7e..bbb90f111 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -104,7 +104,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) connect(m_propListDelegate, &PropListDelegate::filteredFilesChanged, this, &PropertiesWidget::filteredFilesChanged); connect(m_ui->stackedProperties, &QStackedWidget::currentChanged, this, &PropertiesWidget::loadDynamicData); connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentSavePathChanged, this, &PropertiesWidget::updateSavePath); - connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentMetadataLoaded, this, &PropertiesWidget::updateTorrentInfos); + connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentMetadataReceived, this, &PropertiesWidget::updateTorrentInfos); connect(m_ui->filesList, &QAbstractItemView::clicked , m_ui->filesList, qOverload(&QAbstractItemView::edit)); connect(m_ui->filesList, &QWidget::customContextMenuRequested, this, &PropertiesWidget::displayFilesListMenu); @@ -404,7 +404,7 @@ void PropertiesWidget::loadDynamicData() switch (m_ui->stackedProperties->currentIndex()) { case PropTabBar::MainTab: - { + { m_ui->labelWastedVal->setText(Utils::Misc::friendlyUnit(m_torrent->wastedSize())); m_ui->labelUpTotalVal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalUpload()) diff --git a/src/gui/transferlistmodel.cpp b/src/gui/transferlistmodel.cpp index 5f70411d9..479ec0d3c 100644 --- a/src/gui/transferlistmodel.cpp +++ b/src/gui/transferlistmodel.cpp @@ -142,7 +142,7 @@ TransferListModel::TransferListModel(QObject *parent) connect(Session::instance(), &Session::torrentsUpdated, this, &TransferListModel::handleTorrentsUpdated); connect(Session::instance(), &Session::torrentFinished, this, &TransferListModel::handleTorrentStatusUpdated); - connect(Session::instance(), &Session::torrentMetadataLoaded, this, &TransferListModel::handleTorrentStatusUpdated); + connect(Session::instance(), &Session::torrentMetadataReceived, this, &TransferListModel::handleTorrentStatusUpdated); connect(Session::instance(), &Session::torrentResumed, this, &TransferListModel::handleTorrentStatusUpdated); connect(Session::instance(), &Session::torrentPaused, this, &TransferListModel::handleTorrentStatusUpdated); connect(Session::instance(), &Session::torrentFinishedChecking, this, &TransferListModel::handleTorrentStatusUpdated);