From a93b675cb85e79f7a9666aba816a09f69f655941 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Wed, 2 Dec 2020 09:16:11 +0300 Subject: [PATCH] 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);