From 991c30943ab394ae4ae752e5c10ccfeb4ebb6763 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Mon, 14 Nov 2022 20:45:08 +0300 Subject: [PATCH 1/4] Allow to fetch data asynchronously --- src/base/bittorrent/peerinfo.cpp | 10 +- src/base/bittorrent/peerinfo.h | 7 +- src/base/bittorrent/sessionimpl.h | 2 +- src/base/bittorrent/torrent.h | 22 +- src/base/bittorrent/torrentimpl.cpp | 290 ++++++++++++++++++++--- src/base/bittorrent/torrentimpl.h | 14 +- src/gui/properties/peerlistwidget.cpp | 60 +++-- src/gui/properties/propertieswidget.cpp | 57 +++-- src/gui/properties/trackerlistwidget.cpp | 71 +++--- 9 files changed, 409 insertions(+), 124 deletions(-) diff --git a/src/base/bittorrent/peerinfo.cpp b/src/base/bittorrent/peerinfo.cpp index 45b8a158a..efbdf2288 100644 --- a/src/base/bittorrent/peerinfo.cpp +++ b/src/base/bittorrent/peerinfo.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015-2022 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,16 +31,15 @@ #include #include "base/bittorrent/ltqbitarray.h" -#include "base/bittorrent/torrent.h" #include "base/net/geoipmanager.h" #include "base/unicodestrings.h" #include "peeraddress.h" using namespace BitTorrent; -PeerInfo::PeerInfo(const Torrent *torrent, const lt::peer_info &nativeInfo) +PeerInfo::PeerInfo(const lt::peer_info &nativeInfo, const QBitArray &allPieces) : m_nativeInfo(nativeInfo) - , m_relevance(calcRelevance(torrent)) + , m_relevance(calcRelevance(allPieces)) { determineFlags(); } @@ -246,9 +245,8 @@ QString PeerInfo::connectionType() const : u"Web"_qs; } -qreal PeerInfo::calcRelevance(const Torrent *torrent) const +qreal PeerInfo::calcRelevance(const QBitArray &allPieces) const { - const QBitArray allPieces = torrent->pieces(); const int localMissing = allPieces.count(false); if (localMissing <= 0) return 0; diff --git a/src/base/bittorrent/peerinfo.h b/src/base/bittorrent/peerinfo.h index 6c7cb7b1c..e6db94f89 100644 --- a/src/base/bittorrent/peerinfo.h +++ b/src/base/bittorrent/peerinfo.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015-2022 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -36,7 +36,6 @@ class QBitArray; namespace BitTorrent { - class Torrent; struct PeerAddress; class PeerInfo @@ -45,7 +44,7 @@ namespace BitTorrent public: PeerInfo() = default; - PeerInfo(const Torrent *torrent, const lt::peer_info &nativeInfo); + PeerInfo(const lt::peer_info &nativeInfo, const QBitArray &allPieces); bool fromDHT() const; bool fromPeX() const; @@ -93,7 +92,7 @@ namespace BitTorrent int downloadingPieceIndex() const; private: - qreal calcRelevance(const Torrent *torrent) const; + qreal calcRelevance(const QBitArray &allPieces) const; void determineFlags(); lt::peer_info m_nativeInfo = {}; diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index e8c42a3b8..a75a47b95 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -443,7 +443,7 @@ namespace BitTorrent template void invoke(Func &&func) { - QMetaObject::invokeMethod(this, std::forward(func)); + QMetaObject::invokeMethod(this, std::forward(func), Qt::QueuedConnection); } void invokeAsync(std::function func); diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index 4b90026c6..43937047e 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015-2022 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -106,9 +106,10 @@ namespace BitTorrent uint qHash(TorrentState key, uint seed = 0); #endif - class Torrent : public AbstractFileStorage + class Torrent : public QObject, public AbstractFileStorage { - Q_GADGET + Q_OBJECT + Q_DISABLE_COPY_MOVE(Torrent) public: enum class StopCondition @@ -128,7 +129,7 @@ namespace BitTorrent static const qreal MAX_RATIO; static const int MAX_SEEDING_TIME; - virtual ~Torrent() = default; + using QObject::QObject; virtual InfoHash infoHash() const = 0; virtual QString name() const = 0; @@ -318,6 +319,19 @@ namespace BitTorrent virtual nonstd::expected exportToBuffer() const = 0; virtual nonstd::expected exportToFile(const Path &path) const = 0; + virtual void fetchPeerInfo(std::function)> resultHandler) const = 0; + virtual void fetchURLSeeds(std::function)> resultHandler) const = 0; + virtual void fetchFilesProgress(std::function)> resultHandler) const = 0; + virtual void fetchPieceAvailability(std::function)> resultHandler) const = 0; + virtual void fetchDownloadingPieces(std::function resultHandler) const = 0; + /** + * @brief fraction of file pieces that are available at least from one peer + * + * This is not the same as torrrent availability, it is just a fraction of pieces + * that can be downloaded right now. It varies between 0 to 1. + */ + virtual void fetchAvailableFileFractions(std::function)> resultHandler) const = 0; + TorrentID id() const; bool isResumed() const; qlonglong remainingSize() const; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index a94cd8d62..a8e654d59 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -236,7 +237,7 @@ namespace TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession , const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms) - : QObject(session) + : Torrent(session) , m_session(session) , m_nativeSession(nativeSession) , m_nativeHandle(nativeHandle) @@ -609,50 +610,86 @@ QVector TorrentImpl::urlSeeds() const void TorrentImpl::addUrlSeeds(const QVector &urlSeeds) { - const std::set currentSeeds = m_nativeHandle.url_seeds(); - - QVector addedUrlSeeds; - addedUrlSeeds.reserve(urlSeeds.size()); - - for (const QUrl &url : urlSeeds) + m_session->invokeAsync([urlSeeds, session = m_session + , nativeHandle = m_nativeHandle + , thisTorrent = QPointer(this)] { - const std::string nativeUrl = url.toString().toStdString(); - if (currentSeeds.find(nativeUrl) == currentSeeds.end()) + try { - m_nativeHandle.add_url_seed(nativeUrl); - addedUrlSeeds << url; - } - } + const std::set nativeSeeds = nativeHandle.url_seeds(); + QVector currentSeeds; + currentSeeds.reserve(static_cast(nativeSeeds.size())); + for (const std::string &urlSeed : nativeSeeds) + currentSeeds.append(QString::fromStdString(urlSeed)); - if (!addedUrlSeeds.isEmpty()) - { - m_session->handleTorrentNeedSaveResumeData(this); - m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds); - } + QVector addedUrlSeeds; + addedUrlSeeds.reserve(urlSeeds.size()); + + for (const QUrl &url : urlSeeds) + { + if (!currentSeeds.contains(url)) + { + nativeHandle.add_url_seed(url.toString().toStdString()); + addedUrlSeeds.append(url); + } + } + + session->invoke([session, thisTorrent, addedUrlSeeds] + { + if (!thisTorrent) + return; + + if (!addedUrlSeeds.isEmpty()) + { + session->handleTorrentNeedSaveResumeData(thisTorrent); + session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds); + } + }); + } + catch (const std::exception &) {} + }); } void TorrentImpl::removeUrlSeeds(const QVector &urlSeeds) { - const std::set currentSeeds = m_nativeHandle.url_seeds(); - - QVector removedUrlSeeds; - removedUrlSeeds.reserve(urlSeeds.size()); - - for (const QUrl &url : urlSeeds) + m_session->invokeAsync([urlSeeds, session = m_session + , nativeHandle = m_nativeHandle + , thisTorrent = QPointer(this)] { - const std::string nativeUrl = url.toString().toStdString(); - if (currentSeeds.find(nativeUrl) != currentSeeds.end()) + try { - m_nativeHandle.remove_url_seed(nativeUrl); - removedUrlSeeds << url; - } - } + const std::set nativeSeeds = nativeHandle.url_seeds(); + QVector currentSeeds; + currentSeeds.reserve(static_cast(nativeSeeds.size())); + for (const std::string &urlSeed : nativeSeeds) + currentSeeds.append(QString::fromStdString(urlSeed)); - if (!removedUrlSeeds.isEmpty()) - { - m_session->handleTorrentNeedSaveResumeData(this); - m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds); - } + QVector removedUrlSeeds; + removedUrlSeeds.reserve(urlSeeds.size()); + + for (const QUrl &url : urlSeeds) + { + if (currentSeeds.contains(url)) + { + nativeHandle.remove_url_seed(url.toString().toStdString()); + removedUrlSeeds.append(url); + } + } + + session->invoke([session, thisTorrent, removedUrlSeeds] + { + if (!thisTorrent) + return; + + if (!removedUrlSeeds.isEmpty()) + { + session->handleTorrentNeedSaveResumeData(thisTorrent); + session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds); + } + }); + } + catch (const std::exception &) {} + }); } void TorrentImpl::clearPeers() @@ -1271,7 +1308,7 @@ QVector TorrentImpl::peers() const peers.reserve(static_cast(nativePeers.size())); for (const lt::peer_info &peer : nativePeers) - peers << PeerInfo(this, peer); + peers.append(PeerInfo(peer, pieces())); return peers; } @@ -2408,6 +2445,171 @@ nonstd::expected TorrentImpl::exportToFile(const Path &path) cons return {}; } +void TorrentImpl::fetchPeerInfo(std::function)> resultHandler) const +{ + invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector + { + try + { + std::vector nativePeers; + nativeHandle.get_peer_info(nativePeers); + QVector peers; + peers.reserve(static_cast(nativePeers.size())); + for (const lt::peer_info &peer : nativePeers) + peers.append(PeerInfo(peer, allPieces)); + return peers; + } + catch (const std::exception &) {} + + return {}; + } + , std::move(resultHandler)); +} + +void TorrentImpl::fetchURLSeeds(std::function)> resultHandler) const +{ + invokeAsync([nativeHandle = m_nativeHandle]() -> QVector + { + try + { + const std::set currentSeeds = nativeHandle.url_seeds(); + QVector urlSeeds; + urlSeeds.reserve(static_cast(currentSeeds.size())); + for (const std::string &urlSeed : currentSeeds) + urlSeeds.append(QString::fromStdString(urlSeed)); + return urlSeeds; + } + catch (const std::exception &) {} + + return {}; + } + , std::move(resultHandler)); +} + +void TorrentImpl::fetchFilesProgress(std::function)> resultHandler) const +{ + invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo + , completedFiles = m_completedFiles]() -> QVector + { + if (!torrentInfo.isValid()) + return {}; + + const int filesCount = torrentInfo.filesCount(); + if (completedFiles.count(true) == filesCount) + return QVector(filesCount, 1); + + try + { +#ifdef QBT_USES_LIBTORRENT2 + const std::vector fp = nativeHandle.file_progress(lt::torrent_handle::piece_granularity); +#else + std::vector fp; + nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity); +#endif + + const auto nativeIndexes = torrentInfo.nativeIndexes(); + QVector result; + result.reserve(filesCount); + for (int i = 0; i < filesCount; ++i) + { + const int64_t progress = fp[LT::toUnderlyingType(nativeIndexes[i])]; + const qlonglong size = torrentInfo.fileSize(i); + if ((size <= 0) || (progress == size)) + result.append(1); + else + result.append(progress / static_cast(size)); + } + return result; + } + catch (const std::exception &) {} + + return {}; + } + , std::move(resultHandler)); +} + +void TorrentImpl::fetchPieceAvailability(std::function)> resultHandler) const +{ + invokeAsync([nativeHandle = m_nativeHandle]() -> QVector + { + try + { + std::vector piecesAvailability; + nativeHandle.piece_availability(piecesAvailability); + return QVector(piecesAvailability.cbegin(), piecesAvailability.cend()); + } + catch (const std::exception &) {} + + return {}; + } + , std::move(resultHandler)); +} + +void TorrentImpl::fetchDownloadingPieces(std::function resultHandler) const +{ + invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray + { + try + { +#ifdef QBT_USES_LIBTORRENT2 + const std::vector queue = nativeHandle.get_download_queue(); +#else + std::vector queue; + nativeHandle.get_download_queue(queue); +#endif + QBitArray result; + result.resize(torrentInfo.piecesCount()); + for (const lt::partial_piece_info &info : queue) + result.setBit(LT::toUnderlyingType(info.piece_index)); + return result; + } + catch (const std::exception &) {} + + return {}; + } + , std::move(resultHandler)); +} + +void TorrentImpl::fetchAvailableFileFractions(std::function)> resultHandler) const +{ + invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector + { + if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0)) + return {}; + + try + { + std::vector piecesAvailability; + nativeHandle.piece_availability(piecesAvailability); + const int filesCount = torrentInfo.filesCount(); + // libtorrent returns empty array for seeding only torrents + if (piecesAvailability.empty()) + return QVector(filesCount, -1); + + QVector result; + result.reserve(filesCount); + for (int i = 0; i < filesCount; ++i) + { + const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i); + + int availablePieces = 0; + for (const int piece : filePieces) + availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0; + + const qreal availability = filePieces.isEmpty() + ? 1 // the file has no pieces, so it is available by default + : static_cast(availablePieces) / filePieces.size(); + result.append(availability); + } + return result; + } + catch (const std::exception &) {} + + return {}; + } + , std::move(resultHandler)); +} + void TorrentImpl::prioritizeFiles(const QVector &priorities) { if (!hasMetadata()) return; @@ -2471,3 +2673,19 @@ QVector TorrentImpl::availableFileFractions() const } return res; } + +template +void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const +{ + m_session->invokeAsync([session = m_session + , func = std::move(func) + , resultHandler = std::move(resultHandler) + , thisTorrent = QPointer(this)]() mutable + { + session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)] + { + if (thisTorrent) + resultHandler(result); + }); + }); +} diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index ee70a70a9..9e4fdf331 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -80,10 +80,10 @@ namespace BitTorrent lt::operation_t operation; }; - class TorrentImpl final : public QObject, public Torrent + class TorrentImpl final : public Torrent { + Q_OBJECT Q_DISABLE_COPY_MOVE(TorrentImpl) - Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentImpl) public: TorrentImpl(SessionImpl *session, lt::session *nativeSession @@ -236,6 +236,13 @@ namespace BitTorrent nonstd::expected exportToBuffer() const override; nonstd::expected exportToFile(const Path &path) const override; + void fetchPeerInfo(std::function)> resultHandler) const override; + void fetchURLSeeds(std::function)> resultHandler) const override; + void fetchFilesProgress(std::function)> resultHandler) const override; + void fetchPieceAvailability(std::function)> resultHandler) const override; + void fetchDownloadingPieces(std::function resultHandler) const override; + void fetchAvailableFileFractions(std::function)> resultHandler) const override; + bool needSaveResumeData() const; // Session interface @@ -290,6 +297,9 @@ namespace BitTorrent nonstd::expected exportTorrent() const; + template + void invokeAsync(Func func, Callback resultHandler) const; + SessionImpl *const m_session = nullptr; lt::session *m_nativeSession = nullptr; lt::torrent_handle m_nativeHandle; diff --git a/src/gui/properties/peerlistwidget.cpp b/src/gui/properties/peerlistwidget.cpp index 5b490df9e..4580a26f6 100644 --- a/src/gui/properties/peerlistwidget.cpp +++ b/src/gui/properties/peerlistwidget.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -392,38 +393,47 @@ void PeerListWidget::saveSettings() const void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent) { - if (!torrent) return; - - const QVector peers = torrent->peers(); - QSet existingPeers; - for (auto i = m_peerItems.cbegin(); i != m_peerItems.cend(); ++i) - existingPeers << i.key(); + if (!torrent) + return; - for (const BitTorrent::PeerInfo &peer : peers) + using TorrentPtr = QPointer; + torrent->fetchPeerInfo([this, torrent = TorrentPtr(torrent)](const QVector &peers) { - if (peer.address().ip.isNull()) continue; + if (torrent != m_properties->getCurrentTorrent()) + return; - bool isNewPeer = false; - updatePeer(torrent, peer, isNewPeer); - if (!isNewPeer) + QSet existingPeers; + existingPeers.reserve(m_peerItems.size()); + for (auto i = m_peerItems.cbegin(); i != m_peerItems.cend(); ++i) + existingPeers.insert(i.key()); + + for (const BitTorrent::PeerInfo &peer : peers) { - const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()}; - existingPeers.remove(peerEndpoint); + if (peer.address().ip.isNull()) + continue; + + bool isNewPeer = false; + updatePeer(torrent, peer, isNewPeer); + if (!isNewPeer) + { + const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()}; + existingPeers.remove(peerEndpoint); + } } - } - // Remove peers that are gone - for (const PeerEndpoint &peerEndpoint : asConst(existingPeers)) - { - QStandardItem *item = m_peerItems.take(peerEndpoint); + // Remove peers that are gone + for (const PeerEndpoint &peerEndpoint : asConst(existingPeers)) + { + QStandardItem *item = m_peerItems.take(peerEndpoint); - QSet &items = m_itemsByIP[peerEndpoint.address.ip]; - items.remove(item); - if (items.isEmpty()) - m_itemsByIP.remove(peerEndpoint.address.ip); + QSet &items = m_itemsByIP[peerEndpoint.address.ip]; + items.remove(item); + if (items.isEmpty()) + m_itemsByIP.remove(peerEndpoint.address.ip); - m_listModel->removeRow(item->row()); - } + m_listModel->removeRow(item->row()); + } + }); } void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTorrent::PeerInfo &peer, bool &isNewPeer) @@ -433,7 +443,7 @@ void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTor const Qt::Alignment intDataTextAlignment = Qt::AlignRight | Qt::AlignVCenter; const auto setModelData = - [this] (const int row, const int column, const QString &displayData + [this](const int row, const int column, const QString &displayData , const QVariant &underlyingData, const Qt::Alignment textAlignmentData = {} , const QString &toolTip = {}) { diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 8c63d048f..070f637d9 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -498,13 +499,20 @@ void PropertiesWidget::loadDynamicData() if (m_torrent->hasMetadata()) { + using TorrentPtr = QPointer; + m_ui->labelTotalPiecesVal->setText(tr("%1 x %2 (have %3)", "(torrent pieces) eg 152 x 4MB (have 25)").arg(m_torrent->piecesCount()).arg(Utils::Misc::friendlyUnit(m_torrent->pieceLength())).arg(m_torrent->piecesHave())); if (!m_torrent->isSeed() && !m_torrent->isPaused() && !m_torrent->isQueued() && !m_torrent->isChecking()) { // Pieces availability showPiecesAvailability(true); - m_piecesAvailability->setAvailability(m_torrent->pieceAvailability()); + m_torrent->fetchPieceAvailability([this, torrent = TorrentPtr(m_torrent)](const QVector &pieceAvailability) + { + if (torrent == m_torrent) + m_piecesAvailability->setAvailability(pieceAvailability); + }); + m_ui->labelAverageAvailabilityVal->setText(Utils::String::fromDouble(m_torrent->distributedCopies(), 3)); } else @@ -515,7 +523,12 @@ void PropertiesWidget::loadDynamicData() // Progress qreal progress = m_torrent->progress() * 100.; m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + u'%'); - m_downloadedPieces->setProgress(m_torrent->pieces(), m_torrent->downloadingPieces()); + + m_torrent->fetchDownloadingPieces([this, torrent = TorrentPtr(m_torrent)](const QBitArray &downloadingPieces) + { + if (torrent == m_torrent) + m_downloadedPieces->setProgress(m_torrent->pieces(), downloadingPieces); + }); } else { @@ -538,6 +551,19 @@ void PropertiesWidget::loadDynamicData() qDebug("Updating priorities in files tab"); m_ui->filesList->setUpdatesEnabled(false); + using TorrentPtr = QPointer; + m_torrent->fetchFilesProgress([this, torrent = TorrentPtr(m_torrent)](const QVector &filesProgress) + { + if (torrent == m_torrent) + m_propListModel->model()->updateFilesProgress(filesProgress); + }); + + m_torrent->fetchAvailableFileFractions([this, torrent = TorrentPtr(m_torrent)](const QVector &availableFileFractions) + { + if (torrent == m_torrent) + m_propListModel->model()->updateFilesAvailability(availableFileFractions); + }); + // Load torrent content if not yet done so const bool isContentInitialized = m_propListModel->model()->hasIndex(0, 0); if (!isContentInitialized) @@ -546,9 +572,6 @@ void PropertiesWidget::loadDynamicData() m_propListModel->model()->setupModelData(*m_torrent); // Load file priorities m_propListModel->model()->updateFilesPriorities(m_torrent->filePriorities()); - // Update file progress/availability - m_propListModel->model()->updateFilesProgress(m_torrent->filesProgress()); - m_propListModel->model()->updateFilesAvailability(m_torrent->availableFileFractions()); // Expand single-item folders recursively. // This will trigger sorting and filtering so do it after all relevant data is loaded. @@ -563,8 +586,6 @@ void PropertiesWidget::loadDynamicData() { // Torrent content was loaded already, only make some updates - m_propListModel->model()->updateFilesProgress(m_torrent->filesProgress()); - m_propListModel->model()->updateFilesAvailability(m_torrent->availableFileFractions()); // XXX: We don't update file priorities regularly for performance // reasons. This means that priorities will not be updated if // set from the Web UI. @@ -583,15 +604,21 @@ void PropertiesWidget::loadUrlSeeds() if (!m_torrent) return; - m_ui->listWebSeeds->clear(); - qDebug("Loading URL seeds"); - const QVector hcSeeds = m_torrent->urlSeeds(); - // Add url seeds - for (const QUrl &hcSeed : hcSeeds) + using TorrentPtr = QPointer; + m_torrent->fetchURLSeeds([this, torrent = TorrentPtr(m_torrent)](const QVector &urlSeeds) { - qDebug("Loading URL seed: %s", qUtf8Printable(hcSeed.toString())); - new QListWidgetItem(hcSeed.toString(), m_ui->listWebSeeds); - } + if (torrent != m_torrent) + return; + + m_ui->listWebSeeds->clear(); + qDebug("Loading URL seeds"); + // Add url seeds + for (const QUrl &urlSeed : urlSeeds) + { + qDebug("Loading URL seed: %s", qUtf8Printable(urlSeed.toString())); + new QListWidgetItem(urlSeed.toString(), m_ui->listWebSeeds); + } + }); } Path PropertiesWidget::getFullPath(const QModelIndex &index) const diff --git a/src/gui/properties/trackerlistwidget.cpp b/src/gui/properties/trackerlistwidget.cpp index 791700485..6b1965783 100644 --- a/src/gui/properties/trackerlistwidget.cpp +++ b/src/gui/properties/trackerlistwidget.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -310,42 +311,50 @@ void TrackerListWidget::loadStickyItems(const BitTorrent::Torrent *torrent) m_LSDItem->setText(COL_MSG, privateMsg); } - // XXX: libtorrent should provide this info... - // Count peers from DHT, PeX, LSD - uint seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, peersDHT = 0, peersPeX = 0, peersLSD = 0; - for (const BitTorrent::PeerInfo &peer : asConst(torrent->peers())) + using TorrentPtr = QPointer; + torrent->fetchPeerInfo([this, torrent = TorrentPtr(torrent)](const QVector &peers) { - if (peer.isConnecting()) continue; + if (torrent != m_properties->getCurrentTorrent()) + return; - if (peer.fromDHT()) - { - if (peer.isSeed()) - ++seedsDHT; - else - ++peersDHT; - } - if (peer.fromPeX()) + // XXX: libtorrent should provide this info... + // Count peers from DHT, PeX, LSD + uint seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, peersDHT = 0, peersPeX = 0, peersLSD = 0; + for (const BitTorrent::PeerInfo &peer : peers) { - if (peer.isSeed()) - ++seedsPeX; - else - ++peersPeX; - } - if (peer.fromLSD()) - { - if (peer.isSeed()) - ++seedsLSD; - else - ++peersLSD; + if (peer.isConnecting()) + continue; + + if (peer.fromDHT()) + { + if (peer.isSeed()) + ++seedsDHT; + else + ++peersDHT; + } + if (peer.fromPeX()) + { + if (peer.isSeed()) + ++seedsPeX; + else + ++peersPeX; + } + if (peer.fromLSD()) + { + if (peer.isSeed()) + ++seedsLSD; + else + ++peersLSD; + } } - } - m_DHTItem->setText(COL_SEEDS, QString::number(seedsDHT)); - m_DHTItem->setText(COL_LEECHES, QString::number(peersDHT)); - m_PEXItem->setText(COL_SEEDS, QString::number(seedsPeX)); - m_PEXItem->setText(COL_LEECHES, QString::number(peersPeX)); - m_LSDItem->setText(COL_SEEDS, QString::number(seedsLSD)); - m_LSDItem->setText(COL_LEECHES, QString::number(peersLSD)); + m_DHTItem->setText(COL_SEEDS, QString::number(seedsDHT)); + m_DHTItem->setText(COL_LEECHES, QString::number(peersDHT)); + m_PEXItem->setText(COL_SEEDS, QString::number(seedsPeX)); + m_PEXItem->setText(COL_LEECHES, QString::number(peersPeX)); + m_LSDItem->setText(COL_SEEDS, QString::number(seedsLSD)); + m_LSDItem->setText(COL_LEECHES, QString::number(peersLSD)); + }); } void TrackerListWidget::loadTrackers() From 998b08f5d8482f84705fd5b4d0c272f2ae8a5fbc Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Fri, 18 Nov 2022 09:46:24 +0300 Subject: [PATCH 2/4] Set metadata asynchronously --- src/base/bittorrent/torrent.h | 2 +- src/base/bittorrent/torrentimpl.cpp | 17 ++++++++++++----- src/base/bittorrent/torrentimpl.h | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index 43937047e..71291a1ae 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -310,7 +310,7 @@ namespace BitTorrent virtual void removeUrlSeeds(const QVector &urlSeeds) = 0; virtual bool connectPeer(const PeerAddress &peerAddress) = 0; virtual void clearPeers() = 0; - virtual bool setMetadata(const TorrentInfo &torrentInfo) = 0; + virtual void setMetadata(const TorrentInfo &torrentInfo) = 0; virtual StopCondition stopCondition() const = 0; virtual void setStopCondition(StopCondition stopCondition) = 0; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index a8e654d59..27ea8193d 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -2215,17 +2215,24 @@ lt::torrent_handle TorrentImpl::nativeHandle() const return m_nativeHandle; } -bool TorrentImpl::setMetadata(const TorrentInfo &torrentInfo) +void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo) { if (hasMetadata()) - return false; + return; + m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo] + { + try + { #ifdef QBT_USES_LIBTORRENT2 - return m_nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section()); + nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section()); #else - const std::shared_ptr nativeInfo = torrentInfo.nativeInfo(); - return m_nativeHandle.set_metadata(lt::span(nativeInfo->metadata().get(), nativeInfo->metadata_size())); + const std::shared_ptr nativeInfo = torrentInfo.nativeInfo(); + nativeHandle.set_metadata(lt::span(nativeInfo->metadata().get(), nativeInfo->metadata_size())); #endif + } + catch (const std::exception &) {} + }); } Torrent::StopCondition TorrentImpl::stopCondition() const diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index 9e4fdf331..9cfae0ddf 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -227,7 +227,7 @@ namespace BitTorrent void removeUrlSeeds(const QVector &urlSeeds) override; bool connectPeer(const PeerAddress &peerAddress) override; void clearPeers() override; - bool setMetadata(const TorrentInfo &torrentInfo) override; + void setMetadata(const TorrentInfo &torrentInfo) override; StopCondition stopCondition() const override; void setStopCondition(StopCondition stopCondition) override; From b335114219d225022e9d0808a4a7f3fbe74c7f3c Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Fri, 18 Nov 2022 12:39:54 +0300 Subject: [PATCH 3/4] Use better method to set bit --- src/base/bittorrent/torrentimpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 27ea8193d..505ddf756 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -2055,7 +2055,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p) const int fileIndex = m_indexMap.value(p->index, -1); Q_ASSERT(fileIndex >= 0); - m_completedFiles[fileIndex] = true; + m_completedFiles.setBit(fileIndex); if (m_session->isAppendExtensionEnabled()) { From 40258f6a2fd98241ecf0f4446ddcd0d73d8be3e8 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Sun, 4 Dec 2022 18:45:34 +0300 Subject: [PATCH 4/4] Stop async worker at correct place --- src/base/bittorrent/sessionimpl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 18644a370..b909e67a6 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -627,6 +627,8 @@ SessionImpl::~SessionImpl() // we delete lt::session delete Net::PortForwarder::instance(); + // We must stop "async worker" only after deletion + // of all the components that could potentially use it m_asyncWorker->clear(); m_asyncWorker->waitForDone();