Provide asynchronous results via QFuture

This commit is contained in:
Vladimir Golovnev (Glassez) 2025-04-21 10:16:04 +03:00
commit 6104242022
No known key found for this signature in database
GPG key ID: 52A2C7DEE2DFA6F7
15 changed files with 139 additions and 219 deletions

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2020-2025 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -28,12 +28,10 @@
#include "filesearcher.h" #include "filesearcher.h"
#include "base/bittorrent/common.h" #include "base/bittorrent/common.h"
#include "base/bittorrent/infohash.h"
void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &originalFileNames namespace
, const Path &savePath, const Path &downloadPath, const bool forceAppendExt)
{ {
const auto findInDir = [](const Path &dirPath, PathList &fileNames, const bool forceAppendExt) -> bool bool findInDir(const Path &dirPath, PathList &fileNames, const bool forceAppendExt)
{ {
bool found = false; bool found = false;
for (Path &fileName : fileNames) for (Path &fileName : fileNames)
@ -58,7 +56,13 @@ void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &origi
} }
return found; return found;
}; }
}
void FileSearcher::search(const PathList &originalFileNames, const Path &savePath
, const Path &downloadPath, const bool forceAppendExt, QPromise<FileSearchResult> promise)
{
promise.start();
Path usedPath = savePath; Path usedPath = savePath;
PathList adjustedFileNames = originalFileNames; PathList adjustedFileNames = originalFileNames;
@ -69,5 +73,6 @@ void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &origi
findInDir(usedPath, adjustedFileNames, forceAppendExt); findInDir(usedPath, adjustedFileNames, forceAppendExt);
} }
emit searchFinished(id, usedPath, adjustedFileNames); promise.addResult(FileSearchResult {usedPath, adjustedFileNames});
promise.finish();
} }

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2020-2025 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -29,26 +29,23 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <QPromise>
#include "base/path.h" #include "base/path.h"
namespace BitTorrent struct FileSearchResult
{ {
class TorrentID; Path savePath;
} PathList fileNames;
};
class FileSearcher final : public QObject class FileSearcher final : public QObject
{ {
Q_OBJECT
Q_DISABLE_COPY_MOVE(FileSearcher) Q_DISABLE_COPY_MOVE(FileSearcher)
public: public:
FileSearcher() = default; using QObject::QObject;
public slots: void search(const PathList &originalFileNames, const Path &savePath
void search(const BitTorrent::TorrentID &id, const PathList &originalFileNames , const Path &downloadPath, bool forceAppendExt, QPromise<FileSearchResult> promise);
, const Path &savePath, const Path &downloadPath, bool forceAppendExt);
signals:
void searchFinished(const BitTorrent::TorrentID &id, const Path &savePath, const PathList &fileNames);
}; };

View file

@ -97,7 +97,6 @@
#include "dbresumedatastorage.h" #include "dbresumedatastorage.h"
#include "downloadpriority.h" #include "downloadpriority.h"
#include "extensiondata.h" #include "extensiondata.h"
#include "filesearcher.h"
#include "filterparserthread.h" #include "filterparserthread.h"
#include "loadtorrentparams.h" #include "loadtorrentparams.h"
#include "lttypecast.h" #include "lttypecast.h"
@ -622,7 +621,6 @@ SessionImpl::SessionImpl(QObject *parent)
m_fileSearcher = new FileSearcher; m_fileSearcher = new FileSearcher;
m_fileSearcher->moveToThread(m_ioThread.get()); m_fileSearcher->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater); connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
m_torrentContentRemover = new TorrentContentRemover; m_torrentContentRemover = new TorrentContentRemover;
m_torrentContentRemover->moveToThread(m_ioThread.get()); m_torrentContentRemover->moveToThread(m_ioThread.get());
@ -2380,31 +2378,6 @@ void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
} }
} }
void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
{
TorrentImpl *torrent = m_torrents.value(id);
if (torrent)
{
torrent->fileSearchFinished(savePath, fileNames);
return;
}
const auto loadingTorrentsIter = m_loadingTorrents.find(id);
if (loadingTorrentsIter != m_loadingTorrents.end())
{
LoadTorrentParams &params = loadingTorrentsIter.value();
lt::add_torrent_params &p = params.ltAddTorrentParams;
p.save_path = savePath.toString().toStdString();
const TorrentInfo torrentInfo {*p.ti};
const auto nativeIndexes = torrentInfo.nativeIndexes();
for (int i = 0; i < fileNames.size(); ++i)
p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString();
m_nativeSession->async_add_torrent(p);
}
}
void SessionImpl::torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage) void SessionImpl::torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage)
{ {
if (errorMessage.isEmpty()) if (errorMessage.isEmpty())
@ -2832,11 +2805,12 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams; lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
p = source.ltAddTorrentParams(); p = source.ltAddTorrentParams();
bool isFindingIncompleteFiles = false;
const bool useAutoTMM = loadTorrentParams.useAutoTMM; const bool useAutoTMM = loadTorrentParams.useAutoTMM;
const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath; const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
bool needFindIncompleteFiles = false;
PathList filePaths;
if (hasMetadata) if (hasMetadata)
{ {
// Torrent that is being added with metadata is considered to be added as stopped // Torrent that is being added with metadata is considered to be added as stopped
@ -2851,7 +2825,7 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount())); Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
PathList filePaths = addTorrentParams.filePaths; filePaths = addTorrentParams.filePaths;
if (filePaths.isEmpty()) if (filePaths.isEmpty())
{ {
filePaths = torrentInfo.filePaths(); filePaths = torrentInfo.filePaths();
@ -2897,13 +2871,9 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
if (!loadTorrentParams.hasFinishedStatus) if (!loadTorrentParams.hasFinishedStatus)
{ {
const Path actualDownloadPath = useAutoTMM needFindIncompleteFiles = true;
? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
isFindingIncompleteFiles = true;
} }
else
if (!isFindingIncompleteFiles)
{ {
for (int index = 0; index < filePaths.size(); ++index) for (int index = 0; index < filePaths.size(); ++index)
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString(); p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
@ -3002,23 +2972,49 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
m_loadingTorrents.insert(id, loadTorrentParams); m_loadingTorrents.insert(id, loadTorrentParams);
if (infoHash.isHybrid()) if (infoHash.isHybrid())
m_hybridTorrentsByAltID.insert(altID, nullptr); m_hybridTorrentsByAltID.insert(altID, nullptr);
if (!isFindingIncompleteFiles)
if (needFindIncompleteFiles)
{
const Path actualDownloadPath = useAutoTMM
? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
findIncompleteFiles(actualSavePath, actualDownloadPath, filePaths).then(this
, [this, id](const FileSearchResult &result)
{
const auto loadingTorrentsIter = m_loadingTorrents.find(id);
Q_ASSERT(loadingTorrentsIter != m_loadingTorrents.end());
if (loadingTorrentsIter == m_loadingTorrents.end()) [[unlikely]]
return;
LoadTorrentParams &params = loadingTorrentsIter.value();
lt::add_torrent_params &p = params.ltAddTorrentParams;
p.save_path = result.savePath.toString().toStdString();
const TorrentInfo torrentInfo {*p.ti};
const auto nativeIndexes = torrentInfo.nativeIndexes();
for (int i = 0; i < result.fileNames.size(); ++i)
p.renamed_files[nativeIndexes[i]] = result.fileNames[i].toString().toStdString();
m_nativeSession->async_add_torrent(p);
});
}
else
{
m_nativeSession->async_add_torrent(p); m_nativeSession->async_add_torrent(p);
}
return true; return true;
} }
void SessionImpl::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath QFuture<FileSearchResult> SessionImpl::findIncompleteFiles(const Path &savePath, const Path &downloadPath, const PathList &filePaths) const
, const Path &downloadPath, const PathList &filePaths) const
{ {
Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount())); QPromise<FileSearchResult> promise;
QFuture<FileSearchResult> future = promise.future();
const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash()); QMetaObject::invokeMethod(m_fileSearcher, [=, this, promise = std::move(promise)]() mutable
const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
QMetaObject::invokeMethod(m_fileSearcher, [=, this]
{ {
m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath, isAppendExtensionEnabled()); m_fileSearcher->search(filePaths, savePath, downloadPath, isAppendExtensionEnabled(), std::move(promise));
}); });
return future;
} }
void SessionImpl::enablePortMapping() void SessionImpl::enablePortMapping()

View file

@ -39,6 +39,7 @@
#include <QtContainerFwd> #include <QtContainerFwd>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QFuture>
#include <QHash> #include <QHash>
#include <QList> #include <QList>
#include <QMap> #include <QMap>
@ -53,6 +54,7 @@
#include "addtorrentparams.h" #include "addtorrentparams.h"
#include "cachestatus.h" #include "cachestatus.h"
#include "categoryoptions.h" #include "categoryoptions.h"
#include "filesearcher.h"
#include "session.h" #include "session.h"
#include "sessionstatus.h" #include "sessionstatus.h"
#include "torrentinfo.h" #include "torrentinfo.h"
@ -62,7 +64,6 @@ class QTimer;
class QUrl; class QUrl;
class BandwidthScheduler; class BandwidthScheduler;
class FileSearcher;
class FilterParserThread; class FilterParserThread;
class FreeDiskSpaceChecker; class FreeDiskSpaceChecker;
class NativeSessionExtension; class NativeSessionExtension;
@ -478,8 +479,7 @@ namespace BitTorrent
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode, MoveStorageContext context); bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode, MoveStorageContext context);
void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath QFuture<FileSearchResult> findIncompleteFiles(const Path &savePath, const Path &downloadPath, const PathList &filePaths = {}) const;
, const Path &downloadPath, const PathList &filePaths = {}) const;
void enablePortMapping(); void enablePortMapping();
void disablePortMapping(); void disablePortMapping();
@ -514,7 +514,6 @@ namespace BitTorrent
void generateResumeData(); void generateResumeData();
void handleIPFilterParsed(int ruleCount); void handleIPFilterParsed(int ruleCount);
void handleIPFilterError(); void handleIPFilterError();
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage); void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
private: private:

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -31,6 +31,7 @@
#include <QtContainerFwd> #include <QtContainerFwd>
#include <QtTypes> #include <QtTypes>
#include <QFuture>
#include <QMetaType> #include <QMetaType>
#include <QString> #include <QString>
@ -275,10 +276,7 @@ namespace BitTorrent
virtual bool isDHTDisabled() const = 0; virtual bool isDHTDisabled() const = 0;
virtual bool isPEXDisabled() const = 0; virtual bool isPEXDisabled() const = 0;
virtual bool isLSDDisabled() const = 0; virtual bool isLSDDisabled() const = 0;
virtual QList<PeerInfo> peers() const = 0;
virtual QBitArray pieces() const = 0; virtual QBitArray pieces() const = 0;
virtual QBitArray downloadingPieces() const = 0;
virtual QList<int> pieceAvailability() const = 0;
virtual qreal distributedCopies() const = 0; virtual qreal distributedCopies() const = 0;
virtual qreal maxRatio() const = 0; virtual qreal maxRatio() const = 0;
virtual int maxSeedingTime() const = 0; virtual int maxSeedingTime() const = 0;
@ -325,10 +323,10 @@ namespace BitTorrent
virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0; virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0;
virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0; virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0;
virtual void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const = 0; virtual QFuture<QList<PeerInfo>> fetchPeerInfo() const = 0;
virtual void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const = 0; virtual QFuture<QList<QUrl>> fetchURLSeeds() const = 0;
virtual void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const = 0; virtual QFuture<QList<int>> fetchPieceAvailability() const = 0;
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0; virtual QFuture<QBitArray> fetchDownloadingPieces() const = 0;
TorrentID id() const; TorrentID id() const;
bool isRunning() const; bool isRunning() const;

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2022-2025 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -28,6 +28,7 @@
#pragma once #pragma once
#include <QFuture>
#include <QObject> #include <QObject>
#include "base/pathfwd.h" #include "base/pathfwd.h"
@ -52,8 +53,7 @@ namespace BitTorrent
* This is not the same as torrrent availability, it is just a fraction of pieces * 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. * that can be downloaded right now. It varies between 0 to 1.
*/ */
virtual QList<qreal> availableFileFractions() const = 0; virtual QFuture<QList<qreal>> fetchAvailableFileFractions() const = 0;
virtual void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const = 0;
virtual void prioritizeFiles(const QList<DownloadPriority> &priorities) = 0; virtual void prioritizeFiles(const QList<DownloadPriority> &priorities) = 0;
virtual void flushCache() const = 0; virtual void flushCache() const = 0;

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -52,6 +52,7 @@
#include <QCache> #include <QCache>
#include <QDebug> #include <QDebug>
#include <QPointer> #include <QPointer>
#include <QPromise>
#include <QSet> #include <QSet>
#include <QStringList> #include <QStringList>
#include <QUrl> #include <QUrl>
@ -1465,48 +1466,11 @@ bool TorrentImpl::isLSDDisabled() const
return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd); return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
} }
QList<PeerInfo> TorrentImpl::peers() const
{
std::vector<lt::peer_info> nativePeers;
m_nativeHandle.get_peer_info(nativePeers);
QList<PeerInfo> peers;
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
for (const lt::peer_info &peer : nativePeers)
peers.append(PeerInfo(peer, pieces()));
return peers;
}
QBitArray TorrentImpl::pieces() const QBitArray TorrentImpl::pieces() const
{ {
return m_pieces; return m_pieces;
} }
QBitArray TorrentImpl::downloadingPieces() const
{
if (!hasMetadata())
return {};
std::vector<lt::partial_piece_info> queue;
m_nativeHandle.get_download_queue(queue);
QBitArray result {piecesCount()};
for (const lt::partial_piece_info &info : queue)
result.setBit(LT::toUnderlyingType(info.piece_index));
return result;
}
QList<int> TorrentImpl::pieceAvailability() const
{
std::vector<int> avail;
m_nativeHandle.piece_availability(avail);
return {avail.cbegin(), avail.cend()};
}
qreal TorrentImpl::distributedCopies() const qreal TorrentImpl::distributedCopies() const
{ {
return m_nativeStatus.distributed_copies; return m_nativeStatus.distributed_copies;
@ -1751,12 +1715,6 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
m_nativeHandle.prioritize_pieces(piecePriorities); m_nativeHandle.prioritize_pieces(piecePriorities);
} }
void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
{
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
endReceivedMetadataHandling(savePath, fileNames);
}
TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo) TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
{ {
const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end() const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
@ -2150,7 +2108,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
// URL seed list have been changed by libtorrent for some reason, so we need to update cached one. // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
// Unfortunately, URL seed list containing in "resume data" is generated according to different rules // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
// than the list we usually cache, so we have to request it from the appropriate source. // than the list we usually cache, so we have to request it from the appropriate source.
fetchURLSeeds([this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; }); fetchURLSeeds().then(this, [this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
} }
if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti) if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
@ -2197,7 +2155,12 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
filePaths[i] = Path(it->second); filePaths[i] = Path(it->second);
} }
m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths); m_session->findIncompleteFiles(savePath(), downloadPath(), filePaths).then(this
, [this](const FileSearchResult &result)
{
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
endReceivedMetadataHandling(result.savePath, result.fileNames);
});
} }
else else
{ {
@ -2930,9 +2893,9 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
return {}; return {};
} }
void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const QFuture<QList<PeerInfo>> TorrentImpl::fetchPeerInfo() const
{ {
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo> return invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
{ {
try try
{ {
@ -2947,13 +2910,12 @@ void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHand
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; return {};
} });
, std::move(resultHandler));
} }
void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const QFuture<QList<QUrl>> TorrentImpl::fetchURLSeeds() const
{ {
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl> return invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
{ {
try try
{ {
@ -2967,13 +2929,12 @@ void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler)
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; return {};
} });
, std::move(resultHandler));
} }
void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const QFuture<QList<int>> TorrentImpl::fetchPieceAvailability() const
{ {
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int> return invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
{ {
try try
{ {
@ -2984,13 +2945,12 @@ void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> result
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; return {};
} });
, std::move(resultHandler));
} }
void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const QFuture<QBitArray> TorrentImpl::fetchDownloadingPieces() const
{ {
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray return invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
{ {
try try
{ {
@ -3009,13 +2969,12 @@ void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultH
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; return {};
} });
, std::move(resultHandler));
} }
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const QFuture<QList<qreal>> TorrentImpl::fetchAvailableFileFractions() const
{ {
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal> return invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
{ {
if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0)) if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
return {}; return {};
@ -3049,8 +3008,7 @@ void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)>
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; return {};
} });
, std::move(resultHandler));
} }
void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities) void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
@ -3090,47 +3048,17 @@ void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
manageActualFilePaths(); manageActualFilePaths();
} }
QList<qreal> TorrentImpl::availableFileFractions() const template <typename Func>
QFuture<std::invoke_result_t<Func>> TorrentImpl::invokeAsync(Func &&func) const
{ {
Q_ASSERT(hasMetadata()); QPromise<std::invoke_result_t<Func>> promise;
const auto future = promise.future();
const int filesCount = this->filesCount(); m_session->invokeAsync([func = std::forward<Func>(func), promise = std::move(promise)]() mutable
if (filesCount <= 0) return {};
const QList<int> piecesAvailability = pieceAvailability();
// libtorrent returns empty array for seeding only torrents
if (piecesAvailability.empty()) return QList<qreal>(filesCount, -1);
QList<qreal> res;
res.reserve(filesCount);
for (int i = 0; i < filesCount; ++i)
{ {
const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i); promise.start();
promise.addResult(func());
int availablePieces = 0; promise.finish();
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<qreal>(availablePieces) / filePieces.size();
res.push_back(availability);
}
return res;
}
template <typename Func, typename Callback>
void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
{
m_session->invokeAsync([session = m_session
, func = std::move(func)
, resultHandler = std::move(resultHandler)
, thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
{
session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
{
if (thisTorrent)
resultHandler(result);
});
}); });
return future;
} }

View file

@ -203,10 +203,7 @@ namespace BitTorrent
bool isDHTDisabled() const override; bool isDHTDisabled() const override;
bool isPEXDisabled() const override; bool isPEXDisabled() const override;
bool isLSDDisabled() const override; bool isLSDDisabled() const override;
QList<PeerInfo> peers() const override;
QBitArray pieces() const override; QBitArray pieces() const override;
QBitArray downloadingPieces() const override;
QList<int> pieceAvailability() const override;
qreal distributedCopies() const override; qreal distributedCopies() const override;
qreal maxRatio() const override; qreal maxRatio() const override;
int maxSeedingTime() const override; int maxSeedingTime() const override;
@ -220,7 +217,6 @@ namespace BitTorrent
int connectionsCount() const override; int connectionsCount() const override;
int connectionsLimit() const override; int connectionsLimit() const override;
qlonglong nextAnnounce() const override; qlonglong nextAnnounce() const override;
QList<qreal> availableFileFractions() const override;
void setName(const QString &name) override; void setName(const QString &name) override;
void setSequentialDownload(bool enable) override; void setSequentialDownload(bool enable) override;
@ -258,11 +254,11 @@ namespace BitTorrent
nonstd::expected<QByteArray, QString> exportToBuffer() const override; nonstd::expected<QByteArray, QString> exportToBuffer() const override;
nonstd::expected<void, QString> exportToFile(const Path &path) const override; nonstd::expected<void, QString> exportToFile(const Path &path) const override;
void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const override; QFuture<QList<PeerInfo>> fetchPeerInfo() const override;
void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const override; QFuture<QList<QUrl>> fetchURLSeeds() const override;
void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const override; QFuture<QList<int>> fetchPieceAvailability() const override;
void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const override; QFuture<QBitArray> fetchDownloadingPieces() const override;
void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const override; QFuture<QList<qreal>> fetchAvailableFileFractions() const override;
bool needSaveResumeData() const; bool needSaveResumeData() const;
@ -278,7 +274,6 @@ namespace BitTorrent
void requestResumeData(lt::resume_data_flags_t flags = {}); void requestResumeData(lt::resume_data_flags_t flags = {});
void deferredRequestResumeData(); void deferredRequestResumeData();
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob); void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob);
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
TrackerEntryStatus updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo); TrackerEntryStatus updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo);
void resetTrackerEntryStatuses(); void resetTrackerEntryStatuses();
@ -326,8 +321,8 @@ namespace BitTorrent
nonstd::expected<lt::entry, QString> exportTorrent() const; nonstd::expected<lt::entry, QString> exportTorrent() const;
template <typename Func, typename Callback> template <typename Func>
void invokeAsync(Func func, Callback resultHandler) const; QFuture<std::invoke_result_t<Func>> invokeAsync(Func &&func) const;
SessionImpl *const m_session = nullptr; SessionImpl *const m_session = nullptr;
lt::session *m_nativeSession = nullptr; lt::session *m_nativeSession = nullptr;

View file

@ -32,6 +32,7 @@
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include <QtVersionChecks>
#include <QAction> #include <QAction>
#include <QByteArray> #include <QByteArray>
#include <QDateTime> #include <QDateTime>
@ -242,14 +243,13 @@ public:
return QList<qreal>(filesCount(), 0); return QList<qreal>(filesCount(), 0);
} }
QList<qreal> availableFileFractions() const override QFuture<QList<qreal>> fetchAvailableFileFractions() const override
{ {
return QList<qreal>(filesCount(), 0); #if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
} return QtFuture::makeReadyValueFuture(QList<qreal>(filesCount(), 0));
#else
void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const override return QtFuture::makeReadyFuture(QList<qreal>(filesCount(), 0));
{ #endif
resultHandler(availableFileFractions());
} }
void prioritizeFiles(const QList<BitTorrent::DownloadPriority> &priorities) override void prioritizeFiles(const QList<BitTorrent::DownloadPriority> &priorities) override

View file

@ -406,7 +406,7 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
return; return;
using TorrentPtr = QPointer<const BitTorrent::Torrent>; using TorrentPtr = QPointer<const BitTorrent::Torrent>;
torrent->fetchPeerInfo([this, torrent = TorrentPtr(torrent)](const QList<BitTorrent::PeerInfo> &peers) torrent->fetchPeerInfo().then(this, [this, torrent = TorrentPtr(torrent)](const QList<BitTorrent::PeerInfo> &peers)
{ {
if (torrent != m_properties->getCurrentTorrent()) if (torrent != m_properties->getCurrentTorrent())
return; return;

View file

@ -471,15 +471,15 @@ void PropertiesWidget::loadDynamicData()
if (m_torrent->hasMetadata()) if (m_torrent->hasMetadata())
{ {
using TorrentPtr = QPointer<BitTorrent::Torrent>;
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())); 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->isFinished() && !m_torrent->isStopped() && !m_torrent->isQueued() && !m_torrent->isChecking()) if (!m_torrent->isFinished() && !m_torrent->isStopped() && !m_torrent->isQueued() && !m_torrent->isChecking())
{ {
// Pieces availability // Pieces availability
showPiecesAvailability(true); showPiecesAvailability(true);
m_torrent->fetchPieceAvailability([this, torrent = TorrentPtr(m_torrent)](const QList<int> &pieceAvailability)
using TorrentPtr = QPointer<BitTorrent::Torrent>;
m_torrent->fetchPieceAvailability().then(this, [this, torrent = TorrentPtr(m_torrent)](const QList<int> &pieceAvailability)
{ {
if (torrent == m_torrent) if (torrent == m_torrent)
m_piecesAvailability->setAvailability(pieceAvailability); m_piecesAvailability->setAvailability(pieceAvailability);
@ -496,10 +496,9 @@ void PropertiesWidget::loadDynamicData()
qreal progress = m_torrent->progress() * 100.; qreal progress = m_torrent->progress() * 100.;
m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + u'%'); m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + u'%');
m_torrent->fetchDownloadingPieces([this, torrent = TorrentPtr(m_torrent)](const QBitArray &downloadingPieces) m_torrent->fetchDownloadingPieces().then(this, [this](const QBitArray &downloadingPieces)
{ {
if (torrent == m_torrent) m_downloadedPieces->setProgress(m_torrent->pieces(), downloadingPieces);
m_downloadedPieces->setProgress(m_torrent->pieces(), downloadingPieces);
}); });
} }
else else
@ -525,7 +524,7 @@ void PropertiesWidget::loadUrlSeeds()
return; return;
using TorrentPtr = QPointer<BitTorrent::Torrent>; using TorrentPtr = QPointer<BitTorrent::Torrent>;
m_torrent->fetchURLSeeds([this, torrent = TorrentPtr(m_torrent)](const QList<QUrl> &urlSeeds) m_torrent->fetchURLSeeds().then(this, [this, torrent = TorrentPtr(m_torrent)](const QList<QUrl> &urlSeeds)
{ {
if (torrent != m_torrent) if (torrent != m_torrent)
return; return;

View file

@ -219,7 +219,8 @@ void TorrentContentModel::updateFilesAvailability()
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata()); Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
using HandlerPtr = QPointer<BitTorrent::TorrentContentHandler>; using HandlerPtr = QPointer<BitTorrent::TorrentContentHandler>;
m_contentHandler->fetchAvailableFileFractions([this, handler = HandlerPtr(m_contentHandler)](const QList<qreal> &availableFileFractions) m_contentHandler->fetchAvailableFileFractions().then(this
, [this, handler = HandlerPtr(m_contentHandler)](const QList<qreal> &availableFileFractions)
{ {
if (handler != m_contentHandler) if (handler != m_contentHandler)
return; return;

View file

@ -309,7 +309,7 @@ void TrackerListModel::populate()
m_items->emplace_back(std::make_shared<Item>(u"** [LSD] **", privateTorrentMessage)); m_items->emplace_back(std::make_shared<Item>(u"** [LSD] **", privateTorrentMessage));
using TorrentPtr = QPointer<const BitTorrent::Torrent>; using TorrentPtr = QPointer<const BitTorrent::Torrent>;
m_torrent->fetchPeerInfo([this, torrent = TorrentPtr(m_torrent)](const QList<BitTorrent::PeerInfo> &peers) m_torrent->fetchPeerInfo().then(this, [this, torrent = TorrentPtr(m_torrent)](const QList<BitTorrent::PeerInfo> &peers)
{ {
if (torrent != m_torrent) if (torrent != m_torrent)
return; return;

View file

@ -745,7 +745,7 @@ void SyncController::torrentPeersAction()
QVariantMap data; QVariantMap data;
QVariantHash peers; QVariantHash peers;
const QList<BitTorrent::PeerInfo> peersList = torrent->peers(); const QList<BitTorrent::PeerInfo> peersList = torrent->fetchPeerInfo().result();
bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries(); bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries();

View file

@ -180,9 +180,11 @@ namespace
QJsonArray getStickyTrackers(const BitTorrent::Torrent *const torrent) QJsonArray getStickyTrackers(const BitTorrent::Torrent *const torrent)
{ {
int seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, leechesDHT = 0, leechesPeX = 0, leechesLSD = 0; int seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, leechesDHT = 0, leechesPeX = 0, leechesLSD = 0;
for (const BitTorrent::PeerInfo &peer : asConst(torrent->peers())) const QList<BitTorrent::PeerInfo> peersList = torrent->fetchPeerInfo().result();
for (const BitTorrent::PeerInfo &peer : peersList)
{ {
if (peer.isConnecting()) continue; if (peer.isConnecting())
continue;
if (peer.isSeed()) if (peer.isSeed())
{ {
@ -727,7 +729,7 @@ void TorrentsController::filesAction()
{ {
const QList<BitTorrent::DownloadPriority> priorities = torrent->filePriorities(); const QList<BitTorrent::DownloadPriority> priorities = torrent->filePriorities();
const QList<qreal> fp = torrent->filesProgress(); const QList<qreal> fp = torrent->filesProgress();
const QList<qreal> fileAvailability = torrent->availableFileFractions(); const QList<qreal> fileAvailability = torrent->fetchAvailableFileFractions().result();
const BitTorrent::TorrentInfo info = torrent->info(); const BitTorrent::TorrentInfo info = torrent->info();
for (const int index : asConst(fileIndexes)) for (const int index : asConst(fileIndexes))
{ {
@ -796,7 +798,7 @@ void TorrentsController::pieceStatesAction()
for (int i = 0; i < states.size(); ++i) for (int i = 0; i < states.size(); ++i)
pieceStates.append(static_cast<int>(states[i]) * 2); pieceStates.append(static_cast<int>(states[i]) * 2);
const QBitArray dlstates = torrent->downloadingPieces(); const QBitArray dlstates = torrent->fetchDownloadingPieces().result();
for (int i = 0; i < states.size(); ++i) for (int i = 0; i < states.size(); ++i)
{ {
if (dlstates[i]) if (dlstates[i])