From 8bc3458e878daadc7f9457c280a9b232e9a96595 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Sun, 14 Jan 2024 19:49:29 +0300 Subject: [PATCH] Improve interaction with backend --- src/base/CMakeLists.txt | 2 + src/base/bittorrent/sessionimpl.cpp | 12 +- src/base/bittorrent/sessionimpl.h | 1 + src/base/bittorrent/torrentbackend.cpp | 244 ++++++++++++++ src/base/bittorrent/torrentbackend.h | 81 +++++ src/base/bittorrent/torrentimpl.cpp | 427 ++++++++++--------------- src/base/bittorrent/torrentimpl.h | 16 +- 7 files changed, 510 insertions(+), 273 deletions(-) create mode 100644 src/base/bittorrent/torrentbackend.cpp create mode 100644 src/base/bittorrent/torrentbackend.h diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 38154e98a7a..43a57568cb9 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(qbt_base STATIC bittorrent/speedmonitor.h bittorrent/sslparameters.h bittorrent/torrent.h + bittorrent/torrentbackend.h bittorrent/torrentcontenthandler.h bittorrent/torrentcontentlayout.h bittorrent/torrentcontentremoveoption.h @@ -146,6 +147,7 @@ add_library(qbt_base STATIC bittorrent/speedmonitor.cpp bittorrent/sslparameters.cpp bittorrent/torrent.cpp + bittorrent/torrentbackend.cpp bittorrent/torrentcontenthandler.cpp bittorrent/torrentcontentremover.cpp bittorrent/torrentcreationmanager.cpp diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 5e52b3aa1f8..6a16a3a53dd 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -101,6 +101,7 @@ #include "nativesessionextension.h" #include "portforwarderimpl.h" #include "resumedatastorage.h" +#include "torrentbackend.h" #include "torrentcontentremover.h" #include "torrentdescriptor.h" #include "torrentimpl.h" @@ -530,6 +531,7 @@ SessionImpl::SessionImpl(QObject *parent) , m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)} , m_seedingLimitTimer {new QTimer(this)} , m_resumeDataTimer {new QTimer(this)} + , m_backendThread {new QThread} , m_ioThread {new QThread} , m_asyncWorker {new QThreadPool(this)} , m_recentErroredTorrentsTimer {new QTimer(this)} @@ -590,6 +592,8 @@ SessionImpl::SessionImpl(QObject *parent) , &Net::ProxyConfigurationManager::proxyConfigurationChanged , this, &SessionImpl::configureDeferred); + m_backendThread->start(); + m_fileSearcher = new FileSearcher; m_fileSearcher->moveToThread(m_ioThread.get()); connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater); @@ -5698,7 +5702,13 @@ void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert *alert) TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms) { - auto *const torrent = new TorrentImpl(this, m_nativeSession, nativeHandle, params); + auto *const torrentBackend = new TorrentBackend(m_nativeSession, nativeHandle); + torrentBackend->moveToThread(m_backendThread.get()); + connect(m_backendThread.get(), &QThread::finished, torrentBackend, &QObject::deleteLater); + + auto *const torrent = new TorrentImpl(this, torrentBackend, m_nativeSession, nativeHandle, params); + connect(torrent, &QObject::destroyed, torrentBackend, &QObject::deleteLater); + m_torrents.insert(torrent->id(), torrent); if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid()) m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent); diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index b1fe4efe0d9..17e6ce37459 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -772,6 +772,7 @@ namespace BitTorrent // Tracker QPointer m_tracker; + Utils::Thread::UniquePtr m_backendThread; Utils::Thread::UniquePtr m_ioThread; QThreadPool *m_asyncWorker = nullptr; ResumeDataStorage *m_resumeDataStorage = nullptr; diff --git a/src/base/bittorrent/torrentbackend.cpp b/src/base/bittorrent/torrentbackend.cpp new file mode 100644 index 00000000000..8cc83cc75d8 --- /dev/null +++ b/src/base/bittorrent/torrentbackend.cpp @@ -0,0 +1,244 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 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 "torrentbackend.h" + +#include +#include + +#include "extensiondata.h" +#include "peeraddress.h" +#include "sslparameters.h" +#include "trackerentry.h" + +#ifndef QBT_USES_LIBTORRENT2 +#include "customstorage.h" +#endif + +namespace +{ + lt::announce_entry makeLTAnnounceEntry(const QString &url, const int tier) + { + lt::announce_entry entry {url.toStdString()}; + entry.tier = tier; + return entry; + } +} + +BitTorrent::TorrentBackend::TorrentBackend(lt::session *ltSession, lt::torrent_handle ltTorrentHandle, QObject *parent) + : QObject(parent) + , m_ltSession {ltSession} + , m_ltTorrentHandle {std::move(ltTorrentHandle)} +{ +} + +void BitTorrent::TorrentBackend::start(const TorrentOperatingMode mode) +{ + m_ltTorrentHandle.clear_error(); + m_ltTorrentHandle.unset_flags(lt::torrent_flags::upload_mode); + + if (mode == TorrentOperatingMode::Forced) + { + m_ltTorrentHandle.unset_flags(lt::torrent_flags::auto_managed); + m_ltTorrentHandle.resume(); + } + else + { + m_ltTorrentHandle.set_flags(lt::torrent_flags::auto_managed); + } +} + +void BitTorrent::TorrentBackend::stop() +{ + m_ltTorrentHandle.unset_flags(lt::torrent_flags::auto_managed); + m_ltTorrentHandle.pause(); +} + +void BitTorrent::TorrentBackend::forceReannounce(const int index) +{ + m_ltTorrentHandle.force_reannounce(0, index); +} + +void BitTorrent::TorrentBackend::forceDHTAnnounce() +{ + m_ltTorrentHandle.force_dht_announce(); +} + +void BitTorrent::TorrentBackend::addTrackers(const QList &trackers) +{ + for (const TrackerEntry &tracker : trackers) + m_ltTorrentHandle.add_tracker(makeLTAnnounceEntry(tracker.url, tracker.tier)); +} + +void BitTorrent::TorrentBackend::replaceTrackers(const QList &trackers) +{ + std::vector ltAnnounceEntries; + ltAnnounceEntries.reserve(trackers.size()); + for (const TrackerEntry &tracker : trackers) + ltAnnounceEntries.emplace_back(makeLTAnnounceEntry(tracker.url, tracker.tier)); + m_ltTorrentHandle.replace_trackers(ltAnnounceEntries); +} + +void BitTorrent::TorrentBackend::addUrlSeeds(const QList &urlSeeds) +{ + for (const QUrl &url : urlSeeds) + m_ltTorrentHandle.add_url_seed(url.toString().toStdString()); +} + +void BitTorrent::TorrentBackend::removeUrlSeeds(const QList &urlSeeds) +{ + for (const QUrl &url : urlSeeds) + m_ltTorrentHandle.remove_url_seed(url.toString().toStdString()); +} + +void BitTorrent::TorrentBackend::connectPeer(const PeerAddress &peerAddress) +{ + try + { + lt::error_code ec; + const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec); + if (ec) + throw lt::system_error(ec); + + m_ltTorrentHandle.connect_peer({addr, peerAddress.port}); + } + catch (const lt::system_error &) + { + } +} + +void BitTorrent::TorrentBackend::clearPeers() +{ + m_ltTorrentHandle.clear_peers(); +} + +void BitTorrent::TorrentBackend::setSequentialDownload(const bool enable) +{ + if (enable) + m_ltTorrentHandle.set_flags(lt::torrent_flags::sequential_download); + else + m_ltTorrentHandle.unset_flags(lt::torrent_flags::sequential_download); +} + +void BitTorrent::TorrentBackend::setSuperSeeding(const bool enable) +{ + if (enable) + m_ltTorrentHandle.set_flags(lt::torrent_flags::super_seeding); + else + m_ltTorrentHandle.unset_flags(lt::torrent_flags::super_seeding); +} + +void BitTorrent::TorrentBackend::setDHTDisabled(const bool disable) +{ + if (disable) + m_ltTorrentHandle.set_flags(lt::torrent_flags::disable_dht); + else + m_ltTorrentHandle.unset_flags(lt::torrent_flags::disable_dht); +} + +void BitTorrent::TorrentBackend::setPEXDisabled(const bool disable) +{ + if (disable) + m_ltTorrentHandle.set_flags(lt::torrent_flags::disable_pex); + else + m_ltTorrentHandle.unset_flags(lt::torrent_flags::disable_pex); +} + +void BitTorrent::TorrentBackend::setLSDDisabled(const bool disable) +{ + if (disable) + m_ltTorrentHandle.set_flags(lt::torrent_flags::disable_lsd); + else + m_ltTorrentHandle.unset_flags(lt::torrent_flags::disable_lsd); +} + +void BitTorrent::TorrentBackend::setSSLParameters(const SSLParameters &sslParameters) +{ + m_ltTorrentHandle.set_ssl_certificate_buffer(sslParameters.certificate.toPem().toStdString() + , sslParameters.privateKey.toPem().toStdString(), sslParameters.dhParams.toStdString()); +} + +void BitTorrent::TorrentBackend::setDownloadLimit(const int limit) +{ + m_ltTorrentHandle.set_download_limit(limit); +} + +void BitTorrent::TorrentBackend::setUploadLimit(const int limit) +{ + m_ltTorrentHandle.set_upload_limit(limit); +} + +void BitTorrent::TorrentBackend::flushCache() +{ + m_ltTorrentHandle.flush_cache(); +} + +void BitTorrent::TorrentBackend::requestResumeData(const lt::resume_data_flags_t flags) +{ + m_ltTorrentHandle.save_resume_data(flags); +} + +void BitTorrent::TorrentBackend::reload(const lt::add_torrent_params <AddTorrentParams + , const bool isStopped, const TorrentOperatingMode operatingMode) +{ + const auto queuePos = m_ltTorrentHandle.queue_position(); + + m_ltSession->remove_torrent(m_ltTorrentHandle, lt::session::delete_partfile); + + lt::add_torrent_params p = ltAddTorrentParams; + p.flags |= lt::torrent_flags::update_subscribe + | lt::torrent_flags::override_trackers + | lt::torrent_flags::override_web_seeds; + + if (isStopped) + { + p.flags |= lt::torrent_flags::paused; + p.flags &= ~lt::torrent_flags::auto_managed; + } + else if (operatingMode == TorrentOperatingMode::AutoManaged) + { + p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused); + } + else + { + p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused); + } + + auto *const extensionData = new ExtensionData; + p.userdata = LTClientData(extensionData); +#ifndef QBT_USES_LIBTORRENT2 + p.storage = customStorageConstructor; +#endif + m_ltTorrentHandle = m_ltSession->add_torrent(p); + + // TODO: Handle status update! + // m_nativeStatus = extensionData->status; + + if (queuePos >= lt::queue_position_t {}) + m_ltTorrentHandle.queue_position_set(queuePos); +} diff --git a/src/base/bittorrent/torrentbackend.h b/src/base/bittorrent/torrentbackend.h new file mode 100644 index 00000000000..291da175df0 --- /dev/null +++ b/src/base/bittorrent/torrentbackend.h @@ -0,0 +1,81 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 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 +#include + +#include +#include +#include + +#include "torrent.h" +// TODO: Move TorrentOperatingMode to separate header! + +namespace BitTorrent +{ + struct PeerAddress; + struct SSLParameters; + struct TrackerEntry; + + class TorrentBackend final : public QObject + { + Q_OBJECT + Q_DISABLE_COPY_MOVE(TorrentBackend) + + public: + TorrentBackend(lt::session *ltSession, lt::torrent_handle ltTorrentHandle, QObject *parent = nullptr); + + void start(TorrentOperatingMode mode); + void stop(); + void forceReannounce(int index); + void forceDHTAnnounce(); + void addTrackers(const QList &trackers); + void replaceTrackers(const QList &trackers); + void addUrlSeeds(const QList &urlSeeds); + void removeUrlSeeds(const QList &urlSeeds); + void connectPeer(const PeerAddress &peerAddress); + void clearPeers(); + void setSequentialDownload(bool enable); + void setSuperSeeding(bool enable); + void setDHTDisabled(bool enable); + void setPEXDisabled(bool disable); + void setLSDDisabled(bool disable); + void setSSLParameters(const SSLParameters &sslParameters); + void setDownloadLimit(int limit); + void setUploadLimit(int limit); + void flushCache(); + void requestResumeData(lt::resume_data_flags_t flags); + void reload(const lt::add_torrent_params <AddTorrentParams, bool isStopped, TorrentOperatingMode operatingMode); + + private: + lt::session *m_ltSession; + lt::torrent_handle m_ltTorrentHandle; + }; +} diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 6d7ce0e3602..ef8fadc9018 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -48,6 +48,7 @@ #endif #include +#include #include #include #include @@ -71,6 +72,7 @@ #include "peeraddress.h" #include "peerinfo.h" #include "sessionimpl.h" +#include "torrentbackend.h" #include "trackerentry.h" #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) @@ -85,13 +87,6 @@ using namespace BitTorrent; namespace { - lt::announce_entry makeNativeAnnounceEntry(const QString &url, const int tier) - { - lt::announce_entry entry {url.toStdString()}; - entry.tier = tier; - return entry; - } - QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint) { const auto ltNow = lt::clock_type::now(); @@ -284,36 +279,36 @@ namespace // TorrentImpl -TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession - , const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms) +TorrentImpl::TorrentImpl(SessionImpl *session, TorrentBackend *backend, lt::session *nativeSession + , const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms) : Torrent(session) - , m_session(session) - , m_nativeSession(nativeSession) - , m_nativeHandle(nativeHandle) + , m_session {session} + , m_backend {backend} + , m_nativeSession {nativeSession} + , m_nativeHandle {nativeHandle} #ifdef QBT_USES_LIBTORRENT2 - , m_infoHash(m_nativeHandle.info_hashes()) + , m_infoHash {m_nativeHandle.info_hashes()} #else - , m_infoHash(m_nativeHandle.info_hash()) + , m_infoHash {m_nativeHandle.info_hash()} #endif - , m_name(params.name) - , m_savePath(params.savePath) - , m_downloadPath(params.downloadPath) - , m_category(params.category) - , m_tags(params.tags) - , m_ratioLimit(params.ratioLimit) - , m_seedingTimeLimit(params.seedingTimeLimit) - , m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit) - , m_shareLimitAction(params.shareLimitAction) - , m_operatingMode(params.operatingMode) - , m_contentLayout(params.contentLayout) - , m_hasFinishedStatus(params.hasFinishedStatus) - , m_hasFirstLastPiecePriority(params.firstLastPiecePriority) - , m_useAutoTMM(params.useAutoTMM) - , m_isStopped(params.stopped) - , m_sslParams(params.sslParameters) - , m_ltAddTorrentParams(params.ltAddTorrentParams) - , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit)) - , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit)) + , m_name {params.name} + , m_savePath {params.savePath} + , m_downloadPath {params.downloadPath} + , m_category {params.category} + , m_tags {params.tags} + , m_ratioLimit {params.ratioLimit} + , m_seedingTimeLimit {params.seedingTimeLimit} + , m_inactiveSeedingTimeLimit {params.inactiveSeedingTimeLimit} + , m_operatingMode {params.operatingMode} + , m_contentLayout {params.contentLayout} + , m_hasFinishedStatus {params.hasFinishedStatus} + , m_hasFirstLastPiecePriority {params.firstLastPiecePriority} + , m_useAutoTMM {params.useAutoTMM} + , m_isStopped {params.stopped} + , m_sslParams {params.sslParameters} + , m_ltAddTorrentParams {params.ltAddTorrentParams} + , m_downloadLimit {cleanLimitValue(m_ltAddTorrentParams.download_limit)} + , m_uploadLimit {cleanLimitValue(m_ltAddTorrentParams.upload_limit)} { if (m_ltAddTorrentParams.ti) { @@ -369,13 +364,6 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession applyFirstLastPiecePriority(m_hasFirstLastPiecePriority); } -TorrentImpl::~TorrentImpl() = default; - -bool TorrentImpl::isValid() const -{ - return m_nativeHandle.is_valid(); -} - Session *TorrentImpl::session() const { return m_session; @@ -576,14 +564,6 @@ Path TorrentImpl::actualStorageLocation() const return Path(m_nativeStatus.save_path); } -void TorrentImpl::setAutoManaged(const bool enable) -{ - if (enable) - m_nativeHandle.set_flags(lt::torrent_flags::auto_managed); - else - m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed); -} - Path TorrentImpl::makeActualPath(int index, const Path &path) const { Path actualPath = path; @@ -640,15 +620,13 @@ void TorrentImpl::addTrackers(QList trackers) trackers = QList(newTrackerSet.cbegin(), newTrackerSet.cend()); for (const TrackerEntry &tracker : asConst(trackers)) - { - m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier)); m_trackerEntryStatuses.append({tracker.url, tracker.tier}); - } std::sort(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end() - , [](const TrackerEntryStatus &left, const TrackerEntryStatus &right) { return left.tier < right.tier; }); + , [](const TrackerEntryStatus &lhs, const TrackerEntryStatus &rhs) { return lhs.tier < rhs.tier; }); + m_session->handleTorrentTrackersAdded(this, trackers); + backendInvoke(&TorrentBackend::addTrackers, trackers); deferredRequestResumeData(); - m_session->handleTorrentTrackersAdded(this, trackers); } void TorrentImpl::removeTrackers(const QStringList &trackers) @@ -660,18 +638,17 @@ void TorrentImpl::removeTrackers(const QStringList &trackers) removedTrackers.removeOne(tracker); } - std::vector nativeTrackers; - nativeTrackers.reserve(m_trackerEntryStatuses.size()); - for (const TrackerEntryStatus &tracker : asConst(m_trackerEntryStatuses)) - nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier)); + if (removedTrackers.isEmpty()) + return; - if (!removedTrackers.isEmpty()) - { - m_nativeHandle.replace_trackers(nativeTrackers); + m_session->handleTorrentTrackersRemoved(this, removedTrackers); - deferredRequestResumeData(); - m_session->handleTorrentTrackersRemoved(this, removedTrackers); - } + QList trackerEntries; + trackerEntries.reserve(m_trackerEntryStatuses.size()); + for (const TrackerEntryStatus &tracker : asConst(m_trackerEntryStatuses)) + trackerEntries.append({tracker.url, tracker.tier}); + backendInvoke(&TorrentBackend::replaceTrackers, trackerEntries); + deferredRequestResumeData(); } void TorrentImpl::replaceTrackers(QList trackers) @@ -684,25 +661,17 @@ void TorrentImpl::replaceTrackers(QList trackers) std::sort(trackers.begin(), trackers.end() , [](const TrackerEntry &left, const TrackerEntry &right) { return left.tier < right.tier; }); - std::vector nativeTrackers; - nativeTrackers.reserve(trackers.size()); m_trackerEntryStatuses.clear(); - for (const TrackerEntry &tracker : trackers) - { - nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier)); m_trackerEntryStatuses.append({tracker.url, tracker.tier}); - } - - m_nativeHandle.replace_trackers(nativeTrackers); + m_session->handleTorrentTrackersChanged(this); + backendInvoke(&TorrentBackend::replaceTrackers, trackers); // Clear the peer list if it's a private torrent since // we do not want to keep connecting with peers from old tracker. if (isPrivate()) - clearPeers(); - + backendInvoke(&TorrentBackend::clearPeers); deferredRequestResumeData(); - m_session->handleTorrentTrackersChanged(this); } QList TorrentImpl::urlSeeds() const @@ -712,116 +681,53 @@ QList TorrentImpl::urlSeeds() const void TorrentImpl::addUrlSeeds(const QList &urlSeeds) { - m_session->invokeAsync([urlSeeds, session = m_session - , nativeHandle = m_nativeHandle - , thisTorrent = QPointer(this)] - { - try - { - const std::set nativeSeeds = nativeHandle.url_seeds(); - QList currentSeeds; - currentSeeds.reserve(static_cast(nativeSeeds.size())); - for (const std::string &urlSeed : nativeSeeds) - currentSeeds.append(QString::fromStdString(urlSeed)); - - QList addedUrlSeeds; - addedUrlSeeds.reserve(urlSeeds.size()); - - for (const QUrl &url : urlSeeds) - { - if (!currentSeeds.contains(url)) - { - nativeHandle.add_url_seed(url.toString().toStdString()); - addedUrlSeeds.append(url); - } - } + QList addedUrlSeeds; + addedUrlSeeds.reserve(urlSeeds.size()); + std::copy_if(urlSeeds.cbegin(), urlSeeds.cend(), std::back_inserter(addedUrlSeeds) + , [this](const QUrl &url) { return !m_urlSeeds.contains(url); }); + if (addedUrlSeeds.isEmpty()) + return; - currentSeeds.append(addedUrlSeeds); - session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds] - { - if (!thisTorrent) - return; + m_urlSeeds.append(addedUrlSeeds); + m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds); - thisTorrent->m_urlSeeds = currentSeeds; - if (!addedUrlSeeds.isEmpty()) - { - thisTorrent->deferredRequestResumeData(); - session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds); - } - }); - } - catch (const std::exception &) {} - }); + backendInvoke(&TorrentBackend::addUrlSeeds, addedUrlSeeds); + deferredRequestResumeData(); } void TorrentImpl::removeUrlSeeds(const QList &urlSeeds) { - m_session->invokeAsync([urlSeeds, session = m_session - , nativeHandle = m_nativeHandle - , thisTorrent = QPointer(this)] + QList removedUrlSeeds; + removedUrlSeeds.reserve(urlSeeds.size()); + for (const QUrl &url : urlSeeds) { - try - { - const std::set nativeSeeds = nativeHandle.url_seeds(); - QList currentSeeds; - currentSeeds.reserve(static_cast(nativeSeeds.size())); - for (const std::string &urlSeed : nativeSeeds) - currentSeeds.append(QString::fromStdString(urlSeed)); - - QList removedUrlSeeds; - removedUrlSeeds.reserve(urlSeeds.size()); - - for (const QUrl &url : urlSeeds) - { - if (currentSeeds.removeOne(url)) - { - nativeHandle.remove_url_seed(url.toString().toStdString()); - removedUrlSeeds.append(url); - } - } - - session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds] - { - if (!thisTorrent) - return; + if (m_urlSeeds.removeOne(url)) + removedUrlSeeds.append(url); + } + if (removedUrlSeeds.isEmpty()) + return; - thisTorrent->m_urlSeeds = currentSeeds; + m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds); - if (!removedUrlSeeds.isEmpty()) - { - thisTorrent->deferredRequestResumeData(); - session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds); - } - }); - } - catch (const std::exception &) {} - }); + backendInvoke(&TorrentBackend::removeUrlSeeds, removedUrlSeeds); + deferredRequestResumeData(); } void TorrentImpl::clearPeers() { - m_nativeHandle.clear_peers(); + backendInvoke(&TorrentBackend::clearPeers); } bool TorrentImpl::connectPeer(const PeerAddress &peerAddress) { - lt::error_code ec; - const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec); - if (ec) return false; - - const lt::tcp::endpoint endpoint(addr, peerAddress.port); - try + if (isChecking() || isQueued()) { - m_nativeHandle.connect_peer(endpoint); - } - catch (const lt::system_error &err) - { - LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3") - .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING); + LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: Inconsistent torrent state.") + .arg(peerAddress.toString(), name()), Log::WARNING); return false; } - LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name())); + backendInvoke(&TorrentBackend::connectPeer, peerAddress); return true; } @@ -832,7 +738,7 @@ bool TorrentImpl::needSaveResumeData() const void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags) { - m_nativeHandle.save_resume_data(flags); + backendInvoke(&TorrentBackend::requestResumeData, flags); m_deferredRequestResumeDataInvoked = false; m_session->handleTorrentResumeDataRequested(this); @@ -1461,6 +1367,9 @@ bool TorrentImpl::isLSDDisabled() const QList TorrentImpl::peers() const { + if (!m_nativeHandle.is_valid()) + return {}; + std::vector nativePeers; m_nativeHandle.get_peer_info(nativePeers); @@ -1483,18 +1392,26 @@ QBitArray TorrentImpl::downloadingPieces() const if (!hasMetadata()) return {}; - std::vector queue; - m_nativeHandle.get_download_queue(queue); + if (m_nativeHandle.is_valid()) + { + std::vector 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)); + QBitArray result {piecesCount()}; + for (const lt::partial_piece_info &info : queue) + result.setBit(LT::toUnderlyingType(info.piece_index)); - return result; + return result; + } + + return {}; } QList TorrentImpl::pieceAvailability() const { + if (!m_nativeHandle.is_valid()) + return QVector(piecesCount(), 0); + std::vector avail; m_nativeHandle.piece_availability(avail); @@ -1628,12 +1545,12 @@ bool TorrentImpl::setCategory(const QString &category) void TorrentImpl::forceReannounce(const int index) { - m_nativeHandle.force_reannounce(0, index); + backendInvoke(&TorrentBackend::forceReannounce, index); } void TorrentImpl::forceDHTAnnounce() { - m_nativeHandle.force_dht_announce(); + backendInvoke(&TorrentBackend::forceDHTAnnounce); } void TorrentImpl::forceRecheck() @@ -1641,19 +1558,26 @@ void TorrentImpl::forceRecheck() if (!hasMetadata()) return; - m_nativeHandle.force_recheck(); // We have to force update the cached state, otherwise someone will be able to get // an incorrect one during the interval until the cached state is updated in a regular way. m_nativeStatus.state = lt::torrent_status::checking_resume_data; + m_nativeStatus.pieces.clear_all(); + m_nativeStatus.num_pieces = 0; if (m_hasMissingFiles) { m_hasMissingFiles = false; if (!isStopped()) { - setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged); if (m_operatingMode == TorrentOperatingMode::Forced) + { + m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed); m_nativeHandle.resume(); + } + else + { + m_nativeHandle.set_flags(lt::torrent_flags::auto_managed); + } } } @@ -1662,8 +1586,9 @@ void TorrentImpl::forceRecheck() m_completedFiles.fill(false); m_filesProgress.fill(0); m_pieces.fill(false); - m_nativeStatus.pieces.clear_all(); - m_nativeStatus.num_pieces = 0; + + if (m_nativeHandle.is_valid()) + m_nativeHandle.force_recheck(); if (isStopped()) { @@ -1676,16 +1601,11 @@ void TorrentImpl::forceRecheck() void TorrentImpl::setSequentialDownload(const bool enable) { if (enable) - { - m_nativeHandle.set_flags(lt::torrent_flags::sequential_download); m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value - } else - { - m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download); m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value - } + backendInvoke(&TorrentBackend::setSequentialDownload, enable); deferredRequestResumeData(); } @@ -1708,6 +1628,9 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled) { Q_ASSERT(hasMetadata()); + if (!m_nativeHandle.is_valid()) + return; + // Download first and last pieces first for every file in the torrent auto piecePriorities = std::vector(m_torrentInfo.piecesCount(), LT::toNative(DownloadPriority::Ignored)); @@ -1862,57 +1785,16 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi void TorrentImpl::reload() { - try - { - m_completedFiles.fill(false); - m_filesProgress.fill(0); - m_pieces.fill(false); - m_nativeStatus.pieces.clear_all(); - m_nativeStatus.num_pieces = 0; - - const auto queuePos = m_nativeHandle.queue_position(); - - m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile); - - lt::add_torrent_params p = m_ltAddTorrentParams; - p.flags |= lt::torrent_flags::update_subscribe - | lt::torrent_flags::override_trackers - | lt::torrent_flags::override_web_seeds; - - if (m_isStopped) - { - p.flags |= lt::torrent_flags::paused; - p.flags &= ~lt::torrent_flags::auto_managed; - } - else if (m_operatingMode == TorrentOperatingMode::AutoManaged) - { - p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused); - } - else - { - p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused); - } - - auto *const extensionData = new ExtensionData; - p.userdata = LTClientData(extensionData); -#ifndef QBT_USES_LIBTORRENT2 - p.storage = customStorageConstructor; -#endif - m_nativeHandle = m_nativeSession->add_torrent(p); - - m_nativeStatus = extensionData->status; + m_completedFiles.fill(false); + m_filesProgress.fill(0); + m_pieces.fill(false); + m_nativeStatus.pieces.clear_all(); + m_nativeStatus.num_pieces = 0; - if (queuePos >= lt::queue_position_t {}) - m_nativeHandle.queue_position_set(queuePos); - m_nativeStatus.queue_position = queuePos; + backendInvoke(&TorrentBackend::reload, m_ltAddTorrentParams, m_isStopped, m_operatingMode); - updateState(); - } - catch (const lt::system_error &err) - { - throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2") - .arg(id().toString(), QString::fromLocal8Bit(err.what()))); - } + // FIXME: It should be handled asynchronously! + updateState(); } void TorrentImpl::stop() @@ -1921,27 +1803,17 @@ void TorrentImpl::stop() { m_stopCondition = StopCondition::None; m_isStopped = true; + m_payloadRateMonitor.reset(); deferredRequestResumeData(); m_session->handleTorrentStopped(this); } if (m_maintenanceJob == MaintenanceJob::None) - { - setAutoManaged(false); - m_nativeHandle.pause(); - - m_payloadRateMonitor.reset(); - } + backendInvoke(&TorrentBackend::stop); } void TorrentImpl::start(const TorrentOperatingMode mode) { - if (hasError()) - { - m_nativeHandle.clear_error(); - m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode); - } - m_operatingMode = mode; if (m_hasMissingFiles) @@ -1961,11 +1833,7 @@ void TorrentImpl::start(const TorrentOperatingMode mode) } if (m_maintenanceJob == MaintenanceJob::None) - { - setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged); - if (m_operatingMode == TorrentOperatingMode::Forced) - m_nativeHandle.resume(); - } + backendInvoke(&TorrentBackend::start, mode); } void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context) @@ -2080,9 +1948,18 @@ void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_c { // torrent is internally paused using NativeTorrentExtension after files checked // so we need to resume it if there is no corresponding "stop condition" set - setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged); - if (m_operatingMode == TorrentOperatingMode::Forced) - m_nativeHandle.resume(); + if (m_nativeHandle.is_valid()) + { + if (m_operatingMode == TorrentOperatingMode::Forced) + { + m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed); + m_nativeHandle.resume(); + } + else + { + m_nativeHandle.set_flags(lt::torrent_flags::auto_managed); + } + } } } @@ -2594,8 +2471,7 @@ bool TorrentImpl::applySSLParameters() if (!m_sslParams.isValid()) return false; - m_nativeHandle.set_ssl_certificate_buffer(m_sslParams.certificate.toPem().toStdString() - , m_sslParams.privateKey.toPem().toStdString(), m_sslParams.dhParams.toStdString()); + backendInvoke(&TorrentBackend::setSSLParameters, m_sslParams); return true; } @@ -2735,8 +2611,9 @@ void TorrentImpl::setUploadLimit(const int limit) return; m_uploadLimit = cleanValue; - m_nativeHandle.set_upload_limit(m_uploadLimit); deferredRequestResumeData(); + + backendInvoke(&TorrentBackend::setUploadLimit, limit); } void TorrentImpl::setDownloadLimit(const int limit) @@ -2746,8 +2623,9 @@ void TorrentImpl::setDownloadLimit(const int limit) return; m_downloadLimit = cleanValue; - m_nativeHandle.set_download_limit(m_downloadLimit); deferredRequestResumeData(); + + backendInvoke(&TorrentBackend::setDownloadLimit, limit); } void TorrentImpl::setSuperSeeding(const bool enable) @@ -2756,11 +2634,13 @@ void TorrentImpl::setSuperSeeding(const bool enable) return; if (enable) - m_nativeHandle.set_flags(lt::torrent_flags::super_seeding); + m_nativeStatus.flags |= lt::torrent_flags::super_seeding; else - m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding); + m_nativeStatus.flags &= ~lt::torrent_flags::super_seeding; deferredRequestResumeData(); + + backendInvoke(&TorrentBackend::setSuperSeeding, enable); } void TorrentImpl::setDHTDisabled(const bool disable) @@ -2769,11 +2649,13 @@ void TorrentImpl::setDHTDisabled(const bool disable) return; if (disable) - m_nativeHandle.set_flags(lt::torrent_flags::disable_dht); + m_nativeStatus.flags |= lt::torrent_flags::disable_dht; else - m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht); + m_nativeStatus.flags &= ~lt::torrent_flags::disable_dht; deferredRequestResumeData(); + + backendInvoke(&TorrentBackend::setDHTDisabled, disable); } void TorrentImpl::setPEXDisabled(const bool disable) @@ -2782,11 +2664,13 @@ void TorrentImpl::setPEXDisabled(const bool disable) return; if (disable) - m_nativeHandle.set_flags(lt::torrent_flags::disable_pex); + m_nativeStatus.flags |= lt::torrent_flags::disable_pex; else - m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex); + m_nativeStatus.flags &= ~lt::torrent_flags::disable_pex; deferredRequestResumeData(); + + backendInvoke(&TorrentBackend::setPEXDisabled, disable); } void TorrentImpl::setLSDDisabled(const bool disable) @@ -2795,16 +2679,18 @@ void TorrentImpl::setLSDDisabled(const bool disable) return; if (disable) - m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd); + m_nativeStatus.flags |= lt::torrent_flags::disable_lsd; else - m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd); + m_nativeStatus.flags &= ~lt::torrent_flags::disable_lsd; deferredRequestResumeData(); + + backendInvoke(&TorrentBackend::setLSDDisabled, disable); } void TorrentImpl::flushCache() const { - m_nativeHandle.flush_cache(); + backendInvoke(&TorrentBackend::flushCache); } QString TorrentImpl::createMagnetURI() const @@ -3101,3 +2987,16 @@ void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const }); }); } + +template +void TorrentImpl::backendInvoke(Func &&func, Args &&...args) const +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + QMetaObject::invokeMethod(m_backend, std::forward(func), std::forward(args)...); +#else + QMetaObject::invokeMethod(m_backend, [this, func = std::forward(func), ...args = std::forward(args)] + { + (m_backend->*func)(args...); + }); +#endif +} diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index 9b0d4df2a7b..03a097bf179 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2023 Vladimir Golovnev + * Copyright (C) 2015-2024 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -60,6 +60,7 @@ namespace BitTorrent { class SessionImpl; + class TorrentBackend; struct LoadTorrentParams; enum class MoveStorageMode @@ -94,11 +95,8 @@ namespace BitTorrent Q_DISABLE_COPY_MOVE(TorrentImpl) public: - TorrentImpl(SessionImpl *session, lt::session *nativeSession - , const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms); - ~TorrentImpl() override; - - bool isValid() const; + TorrentImpl(SessionImpl *session, TorrentBackend *backend, lt::session *nativeSession + , const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms); Session *session() const override; @@ -309,8 +307,6 @@ namespace BitTorrent bool isMoveInProgress() const; - void setAutoManaged(bool enable); - Path makeActualPath(int index, const Path &path) const; Path makeUserPath(const Path &path) const; void adjustStorageLocation(); @@ -328,7 +324,11 @@ namespace BitTorrent template void invokeAsync(Func func, Callback resultHandler) const; + template + void backendInvoke(Func &&function, Args &&...args) const; + SessionImpl *const m_session = nullptr; + TorrentBackend *m_backend = nullptr; lt::session *m_nativeSession = nullptr; lt::torrent_handle m_nativeHandle; mutable lt::torrent_status m_nativeStatus;