Browse Source

Revamp tracker entries handling

PR #17017.
adaptive-webui-19844
Vladimir Golovnev 3 years ago committed by GitHub
parent
commit
7e0cd223fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/base/CMakeLists.txt
  2. 1
      src/base/base.pri
  3. 45
      src/base/bittorrent/extensiondata.h
  4. 11
      src/base/bittorrent/magneturi.cpp
  5. 7
      src/base/bittorrent/nativesessionextension.cpp
  6. 12
      src/base/bittorrent/nativesessionextension.h
  7. 13
      src/base/bittorrent/nativetorrentextension.cpp
  8. 8
      src/base/bittorrent/nativetorrentextension.h
  9. 130
      src/base/bittorrent/session.cpp
  10. 14
      src/base/bittorrent/session.h
  11. 6
      src/base/bittorrent/torrent.h
  12. 186
      src/base/bittorrent/torrentimpl.cpp
  13. 17
      src/base/bittorrent/torrentimpl.h
  14. 3
      src/base/bittorrent/torrentinfo.cpp
  15. 2
      src/base/bittorrent/torrentinfo.h
  16. 14
      src/base/bittorrent/trackerentry.cpp
  17. 14
      src/base/bittorrent/trackerentry.h
  18. 1
      src/gui/mainwindow.cpp
  19. 13
      src/gui/properties/trackerlistwidget.cpp
  20. 157
      src/gui/transferlistfilterswidget.cpp
  21. 18
      src/gui/transferlistfilterswidget.h
  22. 21
      src/webui/api/torrentscontroller.cpp

1
src/base/CMakeLists.txt

@ -13,6 +13,7 @@ add_library(qbt_base STATIC
bittorrent/customstorage.h bittorrent/customstorage.h
bittorrent/dbresumedatastorage.h bittorrent/dbresumedatastorage.h
bittorrent/downloadpriority.h bittorrent/downloadpriority.h
bittorrent/extensiondata.h
bittorrent/filesearcher.h bittorrent/filesearcher.h
bittorrent/filterparserthread.h bittorrent/filterparserthread.h
bittorrent/infohash.h bittorrent/infohash.h

1
src/base/base.pri

@ -12,6 +12,7 @@ HEADERS += \
$$PWD/bittorrent/customstorage.h \ $$PWD/bittorrent/customstorage.h \
$$PWD/bittorrent/downloadpriority.h \ $$PWD/bittorrent/downloadpriority.h \
$$PWD/bittorrent/dbresumedatastorage.h \ $$PWD/bittorrent/dbresumedatastorage.h \
$$PWD/bittorrent/extensiondata.h \
$$PWD/bittorrent/filesearcher.h \ $$PWD/bittorrent/filesearcher.h \
$$PWD/bittorrent/filterparserthread.h \ $$PWD/bittorrent/filterparserthread.h \
$$PWD/bittorrent/infohash.h \ $$PWD/bittorrent/infohash.h \

45
src/base/bittorrent/extensiondata.h

@ -0,0 +1,45 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
*
* 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 <vector>
#include <libtorrent/announce_entry.hpp>
#ifdef QBT_USES_LIBTORRENT2
#include <libtorrent/client_data.hpp>
using LTClientData = lt::client_data_t;
#else
using LTClientData = void *;
#endif
struct ExtensionData
{
std::vector<lt::announce_entry> trackers;
};

11
src/base/bittorrent/magneturi.cpp

@ -100,8 +100,15 @@ MagnetUri::MagnetUri(const QString &source)
m_name = QString::fromStdString(m_addTorrentParams.name); m_name = QString::fromStdString(m_addTorrentParams.name);
m_trackers.reserve(static_cast<decltype(m_trackers)::size_type>(m_addTorrentParams.trackers.size())); m_trackers.reserve(static_cast<decltype(m_trackers)::size_type>(m_addTorrentParams.trackers.size()));
for (const std::string &tracker : m_addTorrentParams.trackers) int tier = 0;
m_trackers.append({QString::fromStdString(tracker)}); auto tierIter = m_addTorrentParams.tracker_tiers.cbegin();
for (const std::string &url : m_addTorrentParams.trackers)
{
if (tierIter != m_addTorrentParams.tracker_tiers.cend())
tier = *tierIter++;
m_trackers.append({QString::fromStdString(url), tier});
}
m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(m_addTorrentParams.url_seeds.size())); m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(m_addTorrentParams.url_seeds.size()));
for (const std::string &urlSeed : m_addTorrentParams.url_seeds) for (const std::string &urlSeed : m_addTorrentParams.url_seeds)

7
src/base/bittorrent/nativesessionextension.cpp

@ -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-2022 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
@ -30,6 +30,7 @@
#include <libtorrent/alert_types.hpp> #include <libtorrent/alert_types.hpp>
#include "extensiondata.h"
#include "nativetorrentextension.h" #include "nativetorrentextension.h"
namespace namespace
@ -49,9 +50,9 @@ lt::feature_flags_t NativeSessionExtension::implemented_features()
return alert_feature; return alert_feature;
} }
std::shared_ptr<lt::torrent_plugin> NativeSessionExtension::new_torrent(const lt::torrent_handle &torrentHandle, ClientData) std::shared_ptr<lt::torrent_plugin> NativeSessionExtension::new_torrent(const lt::torrent_handle &torrentHandle, LTClientData clientData)
{ {
return std::make_shared<NativeTorrentExtension>(torrentHandle); return std::make_shared<NativeTorrentExtension>(torrentHandle, static_cast<ExtensionData *>(clientData));
} }
void NativeSessionExtension::on_alert(const lt::alert *alert) void NativeSessionExtension::on_alert(const lt::alert *alert)

12
src/base/bittorrent/nativesessionextension.h

@ -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-2022 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
@ -30,15 +30,11 @@
#include <libtorrent/extensions.hpp> #include <libtorrent/extensions.hpp>
#include "extensiondata.h"
class NativeSessionExtension final : public lt::plugin class NativeSessionExtension final : public lt::plugin
{ {
#ifdef QBT_USES_LIBTORRENT2
using ClientData = lt::client_data_t;
#else
using ClientData = void *;
#endif
lt::feature_flags_t implemented_features() override; lt::feature_flags_t implemented_features() override;
std::shared_ptr<lt::torrent_plugin> new_torrent(const lt::torrent_handle &torrentHandle, ClientData) override; std::shared_ptr<lt::torrent_plugin> new_torrent(const lt::torrent_handle &torrentHandle, LTClientData clientData) override;
void on_alert(const lt::alert *alert) override; void on_alert(const lt::alert *alert) override;
}; };

13
src/base/bittorrent/nativetorrentextension.cpp

@ -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-2022 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
@ -38,10 +38,19 @@ namespace
} }
} }
NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrentHandle) NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrentHandle, ExtensionData *data)
: m_torrentHandle {torrentHandle} : m_torrentHandle {torrentHandle}
, m_data {data}
{ {
on_state(m_torrentHandle.status({}).state); on_state(m_torrentHandle.status({}).state);
if (m_data)
m_data->trackers = m_torrentHandle.trackers();
}
NativeTorrentExtension::~NativeTorrentExtension()
{
delete m_data;
} }
bool NativeTorrentExtension::on_pause() bool NativeTorrentExtension::on_pause()

8
src/base/bittorrent/nativetorrentextension.h

@ -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-2022 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
@ -31,10 +31,13 @@
#include <libtorrent/extensions.hpp> #include <libtorrent/extensions.hpp>
#include <libtorrent/torrent_handle.hpp> #include <libtorrent/torrent_handle.hpp>
#include "extensiondata.h"
class NativeTorrentExtension final : public lt::torrent_plugin class NativeTorrentExtension final : public lt::torrent_plugin
{ {
public: public:
explicit NativeTorrentExtension(const lt::torrent_handle &torrentHandle); NativeTorrentExtension(const lt::torrent_handle &torrentHandle, ExtensionData *data);
~NativeTorrentExtension();
private: private:
bool on_pause() override; bool on_pause() override;
@ -42,4 +45,5 @@ private:
lt::torrent_handle m_torrentHandle; lt::torrent_handle m_torrentHandle;
lt::torrent_status::state_t m_state = lt::torrent_status::checking_resume_data; lt::torrent_status::state_t m_state = lt::torrent_status::checking_resume_data;
ExtensionData *m_data = nullptr;
}; };

130
src/base/bittorrent/session.cpp

@ -95,6 +95,7 @@
#include "customstorage.h" #include "customstorage.h"
#include "dbresumedatastorage.h" #include "dbresumedatastorage.h"
#include "downloadpriority.h" #include "downloadpriority.h"
#include "extensiondata.h"
#include "filesearcher.h" #include "filesearcher.h"
#include "filterparserthread.h" #include "filterparserthread.h"
#include "loadtorrentparams.h" #include "loadtorrentparams.h"
@ -292,67 +293,6 @@ namespace
return {}; return {};
} }
#endif #endif
#ifdef QBT_USES_LIBTORRENT2
TrackerEntryUpdateInfo getTrackerEntryUpdateInfo(const lt::announce_entry &nativeEntry, const lt::info_hash_t &hashes)
#else
TrackerEntryUpdateInfo getTrackerEntryUpdateInfo(const lt::announce_entry &nativeEntry)
#endif
{
TrackerEntryUpdateInfo result {};
int numUpdating = 0;
int numWorking = 0;
int numNotWorking = 0;
#ifdef QBT_USES_LIBTORRENT2
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1));
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
{
if (hashes.has(protocolVersion))
{
const lt::announce_infohash &infoHash = endpoint.info_hashes[protocolVersion];
if (!result.hasMessages)
result.hasMessages = !infoHash.message.empty();
if (infoHash.updating)
++numUpdating;
else if (infoHash.fails > 0)
++numNotWorking;
else if (nativeEntry.verified)
++numWorking;
}
}
}
#else
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size());
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{
if (!result.hasMessages)
result.hasMessages = !endpoint.message.empty();
if (endpoint.updating)
++numUpdating;
else if (endpoint.fails > 0)
++numNotWorking;
else if (nativeEntry.verified)
++numWorking;
}
#endif
if (numEndpoints > 0)
{
if (numUpdating > 0)
result.status = TrackerEntry::Updating;
else if (numWorking > 0)
result.status = TrackerEntry::Working;
else if (numNotWorking == numEndpoints)
result.status = TrackerEntry::NotWorking;
}
return result;
}
} }
const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>(); const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
@ -2327,17 +2267,6 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i) for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]); p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
if (isAddTrackersEnabled() && !torrentInfo.isPrivate())
{
p.trackers.reserve(static_cast<std::size_t>(m_additionalTrackerList.size()));
p.tracker_tiers.reserve(static_cast<std::size_t>(m_additionalTrackerList.size()));
for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
{
p.trackers.push_back(trackerEntry.url.toStdString());
p.tracker_tiers.push_back(trackerEntry.tier);
}
}
p.ti = torrentInfo.nativeInfo(); p.ti = torrentInfo.nativeInfo();
} }
else else
@ -2351,6 +2280,18 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
p.save_path = actualSavePath.toString().toStdString(); p.save_path = actualSavePath.toString().toStdString();
if (isAddTrackersEnabled() && !(hasMetadata && p.ti->priv()))
{
p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
p.tracker_tiers.resize(p.trackers.size(), 0);
for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
{
p.trackers.push_back(trackerEntry.url.toStdString());
p.tracker_tiers.push_back(trackerEntry.tier);
}
}
p.upload_limit = addTorrentParams.uploadLimit; p.upload_limit = addTorrentParams.uploadLimit;
p.download_limit = addTorrentParams.downloadLimit; p.download_limit = addTorrentParams.downloadLimit;
@ -2392,6 +2333,7 @@ bool Session::loadTorrent(LoadTorrentParams params)
{ {
lt::add_torrent_params &p = params.ltAddTorrentParams; lt::add_torrent_params &p = params.ltAddTorrentParams;
p.userdata = LTClientData(new ExtensionData);
#ifndef QBT_USES_LIBTORRENT2 #ifndef QBT_USES_LIBTORRENT2
p.storage = customStorageConstructor; p.storage = customStorageConstructor;
#endif #endif
@ -4147,12 +4089,12 @@ void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVect
emit trackersChanged(torrent); emit trackersChanged(torrent);
} }
void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers) void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers)
{ {
for (const TrackerEntry &deletedTracker : deletedTrackers) for (const QString &deletedTracker : deletedTrackers)
LogMsg(tr("Removed tracker from torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), deletedTracker.url)); LogMsg(tr("Removed tracker from torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), deletedTracker));
emit trackersRemoved(torrent, deletedTrackers); emit trackersRemoved(torrent, deletedTrackers);
if (torrent->trackers().empty()) if (torrent->trackers().isEmpty())
emit trackerlessStateChanged(torrent, true); emit trackerlessStateChanged(torrent, true);
emit trackersChanged(torrent); emit trackersChanged(torrent);
} }
@ -4831,6 +4773,7 @@ void Session::handleAlert(const lt::alert *a)
case lt::session_stats_alert::alert_type: case lt::session_stats_alert::alert_type:
handleSessionStatsAlert(static_cast<const lt::session_stats_alert*>(a)); handleSessionStatsAlert(static_cast<const lt::session_stats_alert*>(a));
break; break;
case lt::tracker_announce_alert::alert_type:
case lt::tracker_error_alert::alert_type: case lt::tracker_error_alert::alert_type:
case lt::tracker_reply_alert::alert_type: case lt::tracker_reply_alert::alert_type:
case lt::tracker_warning_alert::alert_type: case lt::tracker_warning_alert::alert_type:
@ -5374,43 +5317,30 @@ void Session::handleTrackerAlert(const lt::tracker_alert *a)
if (!torrent) if (!torrent)
return; return;
const QByteArray trackerURL {a->tracker_url()}; const auto trackerURL = QString::fromUtf8(a->tracker_url());
m_updatedTrackerEntries[torrent].insert(trackerURL); m_updatedTrackerEntries[torrent].insert(trackerURL);
if (a->type() == lt::tracker_reply_alert::alert_type) if (a->type() == lt::tracker_reply_alert::alert_type)
{ {
const int numPeers = static_cast<const lt::tracker_reply_alert *>(a)->num_peers; const int numPeers = static_cast<const lt::tracker_reply_alert *>(a)->num_peers;
torrent->updatePeerCount(QString::fromUtf8(trackerURL), a->local_endpoint, numPeers); torrent->updatePeerCount(trackerURL, a->local_endpoint, numPeers);
} }
} }
void Session::processTrackerStatuses() void Session::processTrackerStatuses()
{ {
QHash<Torrent *, QHash<QString, TrackerEntryUpdateInfo>> updateInfos;
for (auto it = m_updatedTrackerEntries.cbegin(); it != m_updatedTrackerEntries.cend(); ++it) for (auto it = m_updatedTrackerEntries.cbegin(); it != m_updatedTrackerEntries.cend(); ++it)
{ {
TorrentImpl *torrent = it.key(); auto torrent = static_cast<TorrentImpl *>(it.key());
const QSet<QByteArray> &updatedTrackers = it.value(); const QSet<QString> &updatedTrackers = it.value();
const std::vector<lt::announce_entry> trackerList = torrent->nativeHandle().trackers(); for (const QString &trackerURL : updatedTrackers)
for (const lt::announce_entry &announceEntry : trackerList) torrent->invalidateTrackerEntry(trackerURL);
{
const auto trackerURL = QByteArray::fromRawData(announceEntry.url.c_str(), announceEntry.url.size());
if (!updatedTrackers.contains(trackerURL))
continue;
#ifdef QBT_USES_LIBTORRENT2
const TrackerEntryUpdateInfo updateInfo = getTrackerEntryUpdateInfo(announceEntry, torrent->nativeHandle().info_hashes());
#else
const TrackerEntryUpdateInfo updateInfo = getTrackerEntryUpdateInfo(announceEntry);
#endif
if ((updateInfo.status == TrackerEntry::Working) || (updateInfo.status == TrackerEntry::NotWorking))
updateInfos[torrent][QString::fromUtf8(trackerURL)] = updateInfo;
}
} }
m_updatedTrackerEntries.clear(); if (!m_updatedTrackerEntries.isEmpty())
if (!updateInfos.isEmpty()) {
emit trackerEntriesUpdated(updateInfos); emit trackerEntriesUpdated(m_updatedTrackerEntries);
m_updatedTrackerEntries.clear();
}
} }

14
src/base/bittorrent/session.h

@ -211,12 +211,6 @@ namespace BitTorrent
} disk; } disk;
}; };
struct TrackerEntryUpdateInfo
{
TrackerEntry::Status status = TrackerEntry::NotContacted;
bool hasMessages = false;
};
class Session final : public QObject class Session final : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -518,7 +512,7 @@ namespace BitTorrent
void handleTorrentChecked(TorrentImpl *const torrent); void handleTorrentChecked(TorrentImpl *const torrent);
void handleTorrentFinished(TorrentImpl *const torrent); void handleTorrentFinished(TorrentImpl *const torrent);
void handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers); void handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers); void handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *const torrent); void handleTorrentTrackersChanged(TorrentImpl *const torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds); void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds); void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
@ -563,10 +557,10 @@ namespace BitTorrent
void trackerlessStateChanged(Torrent *torrent, bool trackerless); void trackerlessStateChanged(Torrent *torrent, bool trackerless);
void trackersAdded(Torrent *torrent, const QVector<TrackerEntry> &trackers); void trackersAdded(Torrent *torrent, const QVector<TrackerEntry> &trackers);
void trackersChanged(Torrent *torrent); void trackersChanged(Torrent *torrent);
void trackersRemoved(Torrent *torrent, const QVector<TrackerEntry> &trackers); void trackersRemoved(Torrent *torrent, const QStringList &trackers);
void trackerSuccess(Torrent *torrent, const QString &tracker); void trackerSuccess(Torrent *torrent, const QString &tracker);
void trackerWarning(Torrent *torrent, const QString &tracker); void trackerWarning(Torrent *torrent, const QString &tracker);
void trackerEntriesUpdated(const QHash<Torrent *, QHash<QString, TrackerEntryUpdateInfo>> &updateInfos); void trackerEntriesUpdated(const QHash<Torrent *, QSet<QString>> &updateInfos);
private slots: private slots:
void configureDeferred(); void configureDeferred();
@ -823,7 +817,7 @@ namespace BitTorrent
QMap<QString, CategoryOptions> m_categories; QMap<QString, CategoryOptions> m_categories;
QSet<QString> m_tags; QSet<QString> m_tags;
QHash<TorrentImpl *, QSet<QByteArray>> m_updatedTrackerEntries; QHash<Torrent *, QSet<QString>> m_updatedTrackerEntries;
// I/O errored torrents // I/O errored torrents
QSet<TorrentID> m_recentErroredTorrents; QSet<TorrentID> m_recentErroredTorrents;

6
src/base/bittorrent/torrent.h

@ -224,7 +224,6 @@ namespace BitTorrent
virtual bool hasMissingFiles() const = 0; virtual bool hasMissingFiles() const = 0;
virtual bool hasError() const = 0; virtual bool hasError() const = 0;
virtual int queuePosition() const = 0; virtual int queuePosition() const = 0;
virtual QVector<QString> trackerURLs() const = 0;
virtual QVector<TrackerEntry> trackers() const = 0; virtual QVector<TrackerEntry> trackers() const = 0;
virtual QVector<QUrl> urlSeeds() const = 0; virtual QVector<QUrl> urlSeeds() const = 0;
virtual QString error() const = 0; virtual QString error() const = 0;
@ -294,8 +293,9 @@ namespace BitTorrent
virtual void setPEXDisabled(bool disable) = 0; virtual void setPEXDisabled(bool disable) = 0;
virtual void setLSDDisabled(bool disable) = 0; virtual void setLSDDisabled(bool disable) = 0;
virtual void flushCache() const = 0; virtual void flushCache() const = 0;
virtual void addTrackers(const QVector<TrackerEntry> &trackers) = 0; virtual void addTrackers(QVector<TrackerEntry> trackers) = 0;
virtual void replaceTrackers(const QVector<TrackerEntry> &trackers) = 0; virtual void removeTrackers(const QStringList &trackers) = 0;
virtual void replaceTrackers(QVector<TrackerEntry> trackers) = 0;
virtual void addUrlSeeds(const QVector<QUrl> &urlSeeds) = 0; virtual void addUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0; virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
virtual bool connectPeer(const PeerAddress &peerAddress) = 0; virtual bool connectPeer(const PeerAddress &peerAddress) = 0;

186
src/base/bittorrent/torrentimpl.cpp

@ -48,6 +48,7 @@
#include <QByteArray> #include <QByteArray>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QSet>
#include <QStringList> #include <QStringList>
#include <QUrl> #include <QUrl>
@ -59,6 +60,7 @@
#include "base/utils/string.h" #include "base/utils/string.h"
#include "common.h" #include "common.h"
#include "downloadpriority.h" #include "downloadpriority.h"
#include "extensiondata.h"
#include "loadtorrentparams.h" #include "loadtorrentparams.h"
#include "ltqbitarray.h" #include "ltqbitarray.h"
#include "ltqhash.h" #include "ltqhash.h"
@ -66,7 +68,6 @@
#include "peeraddress.h" #include "peeraddress.h"
#include "peerinfo.h" #include "peerinfo.h"
#include "session.h" #include "session.h"
#include "trackerentry.h"
using namespace BitTorrent; using namespace BitTorrent;
@ -80,14 +81,16 @@ namespace
} }
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
TrackerEntry fromNativeAnnounceEntry(const lt::announce_entry &nativeEntry void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
, const lt::info_hash_t &hashes, const QMap<lt::tcp::endpoint, int> &trackerPeerCounts) , const lt::info_hash_t &hashes, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
#else #else
TrackerEntry fromNativeAnnounceEntry(const lt::announce_entry &nativeEntry void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
, const QMap<lt::tcp::endpoint, int> &trackerPeerCounts) , const QMap<TrackerEntry::Endpoint, int> &updateInfo)
#endif #endif
{ {
TrackerEntry trackerEntry {QString::fromStdString(nativeEntry.url), nativeEntry.tier}; Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
trackerEntry.tier = nativeEntry.tier;
int numUpdating = 0; int numUpdating = 0;
int numWorking = 0; int numWorking = 0;
@ -96,7 +99,6 @@ namespace
QString firstErrorMessage; QString firstErrorMessage;
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1)); const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1));
trackerEntry.endpoints.reserve(static_cast<decltype(trackerEntry.endpoints)::size_type>(numEndpoints));
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints) for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{ {
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2}) for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
@ -106,8 +108,7 @@ namespace
const lt::announce_infohash &infoHash = endpoint.info_hashes[protocolVersion]; const lt::announce_infohash &infoHash = endpoint.info_hashes[protocolVersion];
TrackerEntry::EndpointStats trackerEndpoint; TrackerEntry::EndpointStats trackerEndpoint;
trackerEndpoint.protocolVersion = (protocolVersion == lt::protocol_version::V1) ? 1 : 2; trackerEndpoint.numPeers = updateInfo.value(endpoint.local_endpoint, trackerEndpoint.numPeers);
trackerEndpoint.numPeers = trackerPeerCounts.value(endpoint.local_endpoint, -1);
trackerEndpoint.numSeeds = infoHash.scrape_complete; trackerEndpoint.numSeeds = infoHash.scrape_complete;
trackerEndpoint.numLeeches = infoHash.scrape_incomplete; trackerEndpoint.numLeeches = infoHash.scrape_incomplete;
trackerEndpoint.numDownloaded = infoHash.scrape_downloaded; trackerEndpoint.numDownloaded = infoHash.scrape_downloaded;
@ -136,7 +137,7 @@ namespace
const QString errorMessage = QString::fromLocal8Bit(infoHash.last_error.message().c_str()); const QString errorMessage = QString::fromLocal8Bit(infoHash.last_error.message().c_str());
trackerEndpoint.message = (!trackerMessage.isEmpty() ? trackerMessage : errorMessage); trackerEndpoint.message = (!trackerMessage.isEmpty() ? trackerMessage : errorMessage);
trackerEntry.endpoints.append(trackerEndpoint); trackerEntry.stats[endpoint.local_endpoint][(protocolVersion == lt::protocol_version::V1) ? 1 : 2] = trackerEndpoint;
trackerEntry.numPeers = std::max(trackerEntry.numPeers, trackerEndpoint.numPeers); trackerEntry.numPeers = std::max(trackerEntry.numPeers, trackerEndpoint.numPeers);
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, trackerEndpoint.numSeeds); trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, trackerEndpoint.numSeeds);
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, trackerEndpoint.numLeeches); trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, trackerEndpoint.numLeeches);
@ -151,11 +152,10 @@ namespace
} }
#else #else
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()); const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size());
trackerEntry.endpoints.reserve(static_cast<decltype(trackerEntry.endpoints)::size_type>(numEndpoints));
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints) for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{ {
TrackerEntry::EndpointStats trackerEndpoint; TrackerEntry::EndpointStats trackerEndpoint;
trackerEndpoint.numPeers = trackerPeerCounts.value(endpoint.local_endpoint, -1); trackerEndpoint.numPeers = updateInfo.value(endpoint.local_endpoint, trackerEndpoint.numPeers);
trackerEndpoint.numSeeds = endpoint.scrape_complete; trackerEndpoint.numSeeds = endpoint.scrape_complete;
trackerEndpoint.numLeeches = endpoint.scrape_incomplete; trackerEndpoint.numLeeches = endpoint.scrape_incomplete;
trackerEndpoint.numDownloaded = endpoint.scrape_downloaded; trackerEndpoint.numDownloaded = endpoint.scrape_downloaded;
@ -184,7 +184,7 @@ namespace
const QString errorMessage = QString::fromLocal8Bit(endpoint.last_error.message().c_str()); const QString errorMessage = QString::fromLocal8Bit(endpoint.last_error.message().c_str());
trackerEndpoint.message = (!trackerMessage.isEmpty() ? trackerMessage : errorMessage); trackerEndpoint.message = (!trackerMessage.isEmpty() ? trackerMessage : errorMessage);
trackerEntry.endpoints.append(trackerEndpoint); trackerEntry.stats[endpoint.local_endpoint][1] = trackerEndpoint;
trackerEntry.numPeers = std::max(trackerEntry.numPeers, trackerEndpoint.numPeers); trackerEntry.numPeers = std::max(trackerEntry.numPeers, trackerEndpoint.numPeers);
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, trackerEndpoint.numSeeds); trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, trackerEndpoint.numSeeds);
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, trackerEndpoint.numLeeches); trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, trackerEndpoint.numLeeches);
@ -214,8 +214,6 @@ namespace
trackerEntry.message = (!firstTrackerMessage.isEmpty() ? firstTrackerMessage : firstErrorMessage); trackerEntry.message = (!firstTrackerMessage.isEmpty() ? firstTrackerMessage : firstErrorMessage);
} }
} }
return trackerEntry;
} }
void initializeStatus(lt::torrent_status &status, const lt::add_torrent_params &params) void initializeStatus(lt::torrent_status &status, const lt::add_torrent_params &params)
@ -308,6 +306,11 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
} }
} }
const auto extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
for (const lt::announce_entry &announceEntry : extensionData->trackers)
m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
initializeStatus(m_nativeStatus, m_ltAddTorrentParams); initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
updateState(); updateState();
@ -523,107 +526,78 @@ void TorrentImpl::setAutoManaged(const bool enable)
m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed); m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
} }
QVector<QString> TorrentImpl::trackerURLs() const QVector<TrackerEntry> TorrentImpl::trackers() const
{ {
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers(); if (!m_updatedTrackerEntries.isEmpty())
refreshTrackerEntries();
QVector<QString> urls;
urls.reserve(static_cast<decltype(urls)::size_type>(nativeTrackers.size()));
for (const lt::announce_entry &tracker : nativeTrackers) return m_trackerEntries;
{
const QString trackerURL = QString::fromStdString(tracker.url);
urls.push_back(trackerURL);
}
return urls;
} }
QVector<TrackerEntry> TorrentImpl::trackers() const void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
{ {
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers(); // TODO: use std::erase_if() in C++20
trackers.erase(std::remove_if(trackers.begin(), trackers.end(), [](const TrackerEntry &entry) { return entry.url.isEmpty(); }), trackers.end());
QVector<TrackerEntry> entries; const auto newTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend())
entries.reserve(static_cast<decltype(entries)::size_type>(nativeTrackers.size())); - QSet<TrackerEntry>(m_trackerEntries.cbegin(), m_trackerEntries.cend());
if (newTrackers.isEmpty())
return;
for (const lt::announce_entry &tracker : nativeTrackers) trackers = QVector<TrackerEntry>(newTrackers.cbegin(), newTrackers.cend());
{ for (const TrackerEntry &tracker : trackers)
const QString trackerURL = QString::fromStdString(tracker.url); m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
#ifdef QBT_USES_LIBTORRENT2
entries << fromNativeAnnounceEntry(tracker, m_nativeHandle.info_hashes(), m_trackerPeerCounts[trackerURL]);
#else
entries << fromNativeAnnounceEntry(tracker, m_trackerPeerCounts[trackerURL]);
#endif
}
return entries; m_trackerEntries.append(trackers);
std::sort(m_trackerEntries.begin(), m_trackerEntries.end()
, [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTrackersAdded(this, trackers);
} }
void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers) void TorrentImpl::removeTrackers(const QStringList &trackers)
{ {
QSet<TrackerEntry> currentTrackers; QStringList removedTrackers = trackers;
for (const lt::announce_entry &entry : m_nativeHandle.trackers()) for (const QString &tracker : trackers)
currentTrackers.insert({QString::fromStdString(entry.url), entry.tier});
QVector<TrackerEntry> newTrackers;
newTrackers.reserve(trackers.size());
for (const TrackerEntry &tracker : trackers)
{ {
if (!currentTrackers.contains(tracker)) if (!m_trackerEntries.removeOne({tracker}))
{ removedTrackers.removeOne(tracker);
m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
newTrackers << tracker;
}
} }
if (!newTrackers.isEmpty()) std::vector<lt::announce_entry> nativeTrackers;
nativeTrackers.reserve(m_trackerEntries.size());
for (const TrackerEntry &tracker : asConst(m_trackerEntries))
nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
if (!removedTrackers.isEmpty())
{ {
m_nativeHandle.replace_trackers(nativeTrackers);
m_session->handleTorrentNeedSaveResumeData(this); m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTrackersAdded(this, newTrackers); m_session->handleTorrentTrackersRemoved(this, removedTrackers);
} }
} }
void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers) void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
{ {
QVector<TrackerEntry> currentTrackers = this->trackers(); // TODO: use std::erase_if() in C++20
trackers.erase(std::remove_if(trackers.begin(), trackers.end(), [](const TrackerEntry &entry) { return entry.url.isEmpty(); }), trackers.end());
QVector<TrackerEntry> newTrackers; std::sort(trackers.begin(), trackers.end()
newTrackers.reserve(trackers.size()); , [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
std::vector<lt::announce_entry> nativeTrackers; std::vector<lt::announce_entry> nativeTrackers;
nativeTrackers.reserve(trackers.size()); nativeTrackers.reserve(trackers.size());
for (const TrackerEntry &tracker : trackers) for (const TrackerEntry &tracker : trackers)
{
nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier)); nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
if (!currentTrackers.removeOne(tracker))
newTrackers << tracker;
}
m_nativeHandle.replace_trackers(nativeTrackers); m_nativeHandle.replace_trackers(nativeTrackers);
// 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();
m_trackerEntries = trackers;
m_session->handleTorrentNeedSaveResumeData(this); m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTrackersChanged(this);
if (newTrackers.isEmpty() && currentTrackers.isEmpty())
{
// when existing tracker reorders
m_session->handleTorrentTrackersChanged(this);
}
else
{
if (!currentTrackers.isEmpty())
m_session->handleTorrentTrackersRemoved(this, currentTrackers);
if (!newTrackers.isEmpty())
m_session->handleTorrentTrackersAdded(this, newTrackers);
// 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();
}
} }
QVector<QUrl> TorrentImpl::urlSeeds() const QVector<QUrl> TorrentImpl::urlSeeds() const
@ -1511,9 +1485,43 @@ void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileN
endReceivedMetadataHandling(savePath, fileNames); endReceivedMetadataHandling(savePath, fileNames);
} }
void TorrentImpl::updatePeerCount(const QString &trackerUrl, const lt::tcp::endpoint &endpoint, const int count) void TorrentImpl::updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, const int count)
{ {
m_trackerPeerCounts[trackerUrl][endpoint] = count; m_updatedTrackerEntries[trackerURL][endpoint] = count;
}
void TorrentImpl::invalidateTrackerEntry(const QString &trackerURL)
{
std::ignore = m_updatedTrackerEntries[trackerURL];
}
void TorrentImpl::refreshTrackerEntries() const
{
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers();
Q_ASSERT(nativeTrackers.size() == m_trackerEntries.size());
for (TrackerEntry &trackerEntry : m_trackerEntries)
{
const auto updatedTrackerIter = m_updatedTrackerEntries.find(trackerEntry.url);
if (updatedTrackerIter == m_updatedTrackerEntries.end())
continue;
const auto nativeTrackerIter = std::find_if(nativeTrackers.cbegin(), nativeTrackers.cend()
, [trackerURL = trackerEntry.url.toStdString()](const lt::announce_entry &announceEntry)
{
return (announceEntry.url == trackerURL);
});
Q_ASSERT(nativeTrackerIter != nativeTrackers.cend());
const lt::announce_entry &announceEntry = *nativeTrackerIter;
#ifdef QBT_USES_LIBTORRENT2
updateTrackerEntry(trackerEntry, announceEntry, m_nativeHandle.info_hashes(), updatedTrackerIter.value());
#else
updateTrackerEntry(trackerEntry, announceEntry, updatedTrackerIter.value());
#endif
}
m_updatedTrackerEntries.clear();
} }
std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const

17
src/base/bittorrent/torrentimpl.h

@ -34,7 +34,6 @@
#include <libtorrent/add_torrent_params.hpp> #include <libtorrent/add_torrent_params.hpp>
#include <libtorrent/fwd.hpp> #include <libtorrent/fwd.hpp>
#include <libtorrent/socket.hpp>
#include <libtorrent/torrent_handle.hpp> #include <libtorrent/torrent_handle.hpp>
#include <libtorrent/torrent_info.hpp> #include <libtorrent/torrent_info.hpp>
#include <libtorrent/torrent_status.hpp> #include <libtorrent/torrent_status.hpp>
@ -55,6 +54,7 @@
#include "torrent.h" #include "torrent.h"
#include "torrentcontentlayout.h" #include "torrentcontentlayout.h"
#include "torrentinfo.h" #include "torrentinfo.h"
#include "trackerentry.h"
namespace BitTorrent namespace BitTorrent
{ {
@ -156,7 +156,6 @@ namespace BitTorrent
bool hasMissingFiles() const override; bool hasMissingFiles() const override;
bool hasError() const override; bool hasError() const override;
int queuePosition() const override; int queuePosition() const override;
QVector<QString> trackerURLs() const override;
QVector<TrackerEntry> trackers() const override; QVector<TrackerEntry> trackers() const override;
QVector<QUrl> urlSeeds() const override; QVector<QUrl> urlSeeds() const override;
QString error() const override; QString error() const override;
@ -221,8 +220,9 @@ namespace BitTorrent
void setPEXDisabled(bool disable) override; void setPEXDisabled(bool disable) override;
void setLSDDisabled(bool disable) override; void setLSDDisabled(bool disable) override;
void flushCache() const override; void flushCache() const override;
void addTrackers(const QVector<TrackerEntry> &trackers) override; void addTrackers(QVector<TrackerEntry> trackers) override;
void replaceTrackers(const QVector<TrackerEntry> &trackers) override; void removeTrackers(const QStringList &trackers) override;
void replaceTrackers(QVector<TrackerEntry> trackers) override;
void addUrlSeeds(const QVector<QUrl> &urlSeeds) override; void addUrlSeeds(const QVector<QUrl> &urlSeeds) override;
void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override; void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override;
bool connectPeer(const PeerAddress &peerAddress) override; bool connectPeer(const PeerAddress &peerAddress) override;
@ -244,13 +244,15 @@ namespace BitTorrent
void saveResumeData(); void saveResumeData();
void handleMoveStorageJobFinished(const Path &path, bool hasOutstandingJob); void handleMoveStorageJobFinished(const Path &path, bool hasOutstandingJob);
void fileSearchFinished(const Path &savePath, const PathList &fileNames); void fileSearchFinished(const Path &savePath, const PathList &fileNames);
void updatePeerCount(const QString &trackerUrl, const lt::tcp::endpoint &endpoint, int count); void updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, int count);
void invalidateTrackerEntry(const QString &trackerURL);
private: private:
using EventTrigger = std::function<void ()>; using EventTrigger = std::function<void ()>;
std::shared_ptr<const lt::torrent_info> nativeTorrentInfo() const; std::shared_ptr<const lt::torrent_info> nativeTorrentInfo() const;
void refreshTrackerEntries() const;
void updateStatus(const lt::torrent_status &nativeStatus); void updateStatus(const lt::torrent_status &nativeStatus);
void updateState(); void updateState();
@ -310,7 +312,10 @@ namespace BitTorrent
MaintenanceJob m_maintenanceJob = MaintenanceJob::None; MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
QHash<QString, QMap<lt::tcp::endpoint, int>> m_trackerPeerCounts; // TODO: Use QHash<TrackerEntry::Endpoint, int> once Qt5 is dropped.
using TrackerEntryUpdateInfo = QMap<TrackerEntry::Endpoint, int>;
mutable QHash<QString, TrackerEntryUpdateInfo> m_updatedTrackerEntries;
mutable QVector<TrackerEntry> m_trackerEntries;
FileErrorInfo m_lastFileError; FileErrorInfo m_lastFileError;
// Persistent data // Persistent data

3
src/base/bittorrent/torrentinfo.cpp

@ -275,9 +275,8 @@ QVector<TrackerEntry> TorrentInfo::trackers() const
QVector<TrackerEntry> ret; QVector<TrackerEntry> ret;
ret.reserve(static_cast<decltype(ret)::size_type>(trackers.size())); ret.reserve(static_cast<decltype(ret)::size_type>(trackers.size()));
for (const lt::announce_entry &tracker : trackers) for (const lt::announce_entry &tracker : trackers)
ret.append({QString::fromStdString(tracker.url)}); ret.append({QString::fromStdString(tracker.url), tracker.tier});
return ret; return ret;
} }

2
src/base/bittorrent/torrentinfo.h

@ -30,8 +30,8 @@
#include <libtorrent/torrent_info.hpp> #include <libtorrent/torrent_info.hpp>
#include <QCoreApplication>
#include <QtContainerFwd> #include <QtContainerFwd>
#include <QCoreApplication>
#include <QVector> #include <QVector>
#include "base/3rdparty/expected.hpp" #include "base/3rdparty/expected.hpp"

14
src/base/bittorrent/trackerentry.cpp

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2022 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,22 +28,16 @@
#include "trackerentry.h" #include "trackerentry.h"
#include <QUrl>
bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right) bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right)
{ {
return ((left.tier == right.tier) return (left.url == right.url);
&& QUrl(left.url) == QUrl(right.url));
} }
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
std::size_t BitTorrent::qHash(const TrackerEntry &key, const std::size_t seed) std::size_t BitTorrent::qHash(const TrackerEntry &key, const std::size_t seed)
{
return qHashMulti(seed, key.url, key.tier);
}
#else #else
uint BitTorrent::qHash(const TrackerEntry &key, const uint seed) uint BitTorrent::qHash(const TrackerEntry &key, const uint seed)
#endif
{ {
return (::qHash(key.url, seed) ^ ::qHash(key.tier)); return ::qHash(key.url, seed);
} }
#endif

14
src/base/bittorrent/trackerentry.h

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2022 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,14 +28,19 @@
#pragma once #pragma once
#include <libtorrent/socket.hpp>
#include <QtGlobal> #include <QtGlobal>
#include <QHash>
#include <QMap>
#include <QString> #include <QString>
#include <QVector>
namespace BitTorrent namespace BitTorrent
{ {
struct TrackerEntry struct TrackerEntry
{ {
using Endpoint = lt::tcp::endpoint;
enum Status enum Status
{ {
NotContacted = 1, NotContacted = 1,
@ -46,8 +51,6 @@ namespace BitTorrent
struct EndpointStats struct EndpointStats
{ {
int protocolVersion = 1;
Status status = NotContacted; Status status = NotContacted;
int numPeers = -1; int numPeers = -1;
int numSeeds = -1; int numSeeds = -1;
@ -59,7 +62,8 @@ namespace BitTorrent
QString url {}; QString url {};
int tier = 0; int tier = 0;
QVector<EndpointStats> endpoints {}; // TODO: Use QHash<TrackerEntry::Endpoint, QHash<int, EndpointStats>> once Qt5 is dropped.
QMap<Endpoint, QHash<int, EndpointStats>> stats {};
// Deprecated fields // Deprecated fields
Status status = NotContacted; Status status = NotContacted;

1
src/gui/mainwindow.cpp

@ -1495,6 +1495,7 @@ void MainWindow::showFiltersSidebar(const bool show)
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers); connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers); connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerlessStateChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::changeTrackerless); connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerlessStateChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::changeTrackerless);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntriesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntriesUpdated); connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntriesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntriesUpdated);
} }

13
src/gui/properties/trackerlistwidget.cpp

@ -473,18 +473,7 @@ void TrackerListWidget::deleteSelectedTrackers()
delete item; delete item;
} }
// Iterate over the trackers and remove the selected ones torrent->removeTrackers(urlsToRemove);
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
QVector<BitTorrent::TrackerEntry> remainingTrackers;
remainingTrackers.reserve(trackers.size());
for (const BitTorrent::TrackerEntry &entry : trackers)
{
if (!urlsToRemove.contains(entry.url))
remainingTrackers.push_back(entry);
}
torrent->replaceTrackers(remainingTrackers);
if (!torrent->isPaused()) if (!torrent->isPaused())
torrent->forceReannounce(); torrent->forceReannounce();

157
src/gui/transferlistfilterswidget.cpp

@ -38,6 +38,7 @@
#include <QUrl> #include <QUrl>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "base/algorithm.h"
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h" #include "base/bittorrent/torrent.h"
#include "base/global.h" #include "base/global.h"
@ -376,7 +377,8 @@ TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *tran
auto *warningTracker = new QListWidgetItem(this); auto *warningTracker = new QListWidgetItem(this);
warningTracker->setData(Qt::DisplayRole, tr("Warning (0)")); warningTracker->setData(Qt::DisplayRole, tr("Warning (0)"));
warningTracker->setData(Qt::DecorationRole, style()->standardIcon(QStyle::SP_MessageBoxWarning)); warningTracker->setData(Qt::DecorationRole, style()->standardIcon(QStyle::SP_MessageBoxWarning));
m_trackers[NULL_HOST] = {};
m_trackers[NULL_HOST] = {{}, noTracker};
setCurrentRow(0, QItemSelectionModel::SelectCurrent); setCurrentRow(0, QItemSelectionModel::SelectCurrent);
toggleFilter(Preferences::instance()->getTrackerFilterState()); toggleFilter(Preferences::instance()->getTrackerFilterState());
@ -388,6 +390,70 @@ TrackerFiltersList::~TrackerFiltersList()
Utils::Fs::removeFile(iconPath); Utils::Fs::removeFile(iconPath);
} }
void TrackerFiltersList::addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers)
{
const BitTorrent::TorrentID torrentID = torrent->id();
for (const BitTorrent::TrackerEntry &tracker : trackers)
addItem(tracker.url, torrentID);
}
void TrackerFiltersList::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
{
const BitTorrent::TorrentID torrentID = torrent->id();
for (const QString &tracker : trackers)
removeItem(tracker, torrentID);
}
void TrackerFiltersList::refreshTrackers(const BitTorrent::Torrent *torrent)
{
const BitTorrent::TorrentID torrentID = torrent->id();
m_errors.remove(torrentID);
m_warnings.remove(torrentID);
Algorithm::removeIf(m_trackers, [this, &torrentID](const QString &host, TrackerData &trackerData)
{
QSet<BitTorrent::TorrentID> &torrentIDs = trackerData.torrents;
if (!torrentIDs.remove(torrentID))
return false;
QListWidgetItem *trackerItem = trackerData.item;
if (!host.isEmpty() && torrentIDs.isEmpty())
{
if (currentItem() == trackerItem)
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
delete trackerItem;
return true;
}
trackerItem->setText(u"%1 (%2)"_qs.arg((host.isEmpty() ? tr("Trackerless") : host), QString::number(torrentIDs.size())));
return false;
});
const QVector<BitTorrent::TrackerEntry> trackerEntries = torrent->trackers();
const bool isTrackerless = trackerEntries.isEmpty();
if (isTrackerless)
{
addItem(NULL_HOST, torrentID);
}
else
{
for (const BitTorrent::TrackerEntry &trackerEntry : trackerEntries)
addItem(trackerEntry.url, torrentID);
}
updateGeometry();
}
void TrackerFiltersList::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless)
{
if (trackerless)
addItem(NULL_HOST, torrent->id());
else
removeItem(NULL_HOST, torrent->id());
}
void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::TorrentID &id) void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::TorrentID &id)
{ {
const QString host = getHost(tracker); const QString host = getHost(tracker);
@ -400,9 +466,7 @@ void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::Torre
if (trackersIt->torrents.contains(id)) if (trackersIt->torrents.contains(id))
return; return;
trackerItem = (host == NULL_HOST) trackerItem = trackersIt->item;
? item(TRACKERLESS_ROW)
: trackersIt->item;
} }
else else
{ {
@ -421,15 +485,7 @@ void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::Torre
QSet<BitTorrent::TorrentID> &torrentIDs = trackersIt->torrents; QSet<BitTorrent::TorrentID> &torrentIDs = trackersIt->torrents;
torrentIDs.insert(id); torrentIDs.insert(id);
if (host == NULL_HOST) trackerItem->setText(u"%1 (%2)"_qs.arg(((host == NULL_HOST) ? tr("Trackerless") : host), QString::number(torrentIDs.size())));
{
trackerItem->setText(tr("Trackerless (%1)").arg(torrentIDs.size()));
if (currentRow() == TRACKERLESS_ROW)
applyFilter(TRACKERLESS_ROW);
return;
}
trackerItem->setText(u"%1 (%2)"_qs.arg(host, QString::number(torrentIDs.size())));
if (exists) if (exists)
{ {
if (item(currentRow()) == trackerItem) if (item(currentRow()) == trackerItem)
@ -496,7 +552,7 @@ void TrackerFiltersList::removeItem(const QString &trackerURL, const BitTorrent:
trackerItem = m_trackers.value(host).item; trackerItem = m_trackers.value(host).item;
if (torrentIDs.empty()) if (torrentIDs.isEmpty())
{ {
if (currentItem() == trackerItem) if (currentItem() == trackerItem)
setCurrentRow(0, QItemSelectionModel::SelectCurrent); setCurrentRow(0, QItemSelectionModel::SelectCurrent);
@ -521,14 +577,6 @@ void TrackerFiltersList::removeItem(const QString &trackerURL, const BitTorrent:
applyFilter(currentRow()); applyFilter(currentRow());
} }
void TrackerFiltersList::changeTrackerless(const bool trackerless, const BitTorrent::TorrentID &id)
{
if (trackerless)
addItem(NULL_HOST, id);
else
removeItem(NULL_HOST, id);
}
void TrackerFiltersList::setDownloadTrackerFavicon(bool value) void TrackerFiltersList::setDownloadTrackerFavicon(bool value)
{ {
if (value == m_downloadTrackerFavicon) return; if (value == m_downloadTrackerFavicon) return;
@ -549,49 +597,51 @@ void TrackerFiltersList::setDownloadTrackerFavicon(bool value)
} }
} }
void TrackerFiltersList::handleTrackerEntriesUpdated(const QHash<BitTorrent::Torrent *, QHash<QString, BitTorrent::TrackerEntryUpdateInfo>> &updateInfos) void TrackerFiltersList::handleTrackerEntriesUpdated(const QHash<BitTorrent::Torrent *, QSet<QString>> &updateInfos)
{ {
for (auto torrentsIt = updateInfos.cbegin(); torrentsIt != updateInfos.cend(); ++torrentsIt) for (auto torrentsIt = updateInfos.cbegin(); torrentsIt != updateInfos.cend(); ++torrentsIt)
{ {
const BitTorrent::TorrentID id = torrentsIt.key()->id(); const BitTorrent::Torrent *torrent = torrentsIt.key();
const QHash<QString, BitTorrent::TrackerEntryUpdateInfo> &infos = torrentsIt.value(); const QSet<QString> &trackerURLs = torrentsIt.value();
const BitTorrent::TorrentID id = torrent->id();
auto errorHashesIt = m_errors.find(id); auto errorHashesIt = m_errors.find(id);
auto warningHashesIt = m_warnings.find(id); auto warningHashesIt = m_warnings.find(id);
for (auto trackerIt = infos.cbegin(); trackerIt != infos.cend(); ++trackerIt) const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
for (const BitTorrent::TrackerEntry &trackerEntry : trackers)
{ {
const QString &trackerURL = trackerIt.key(); if (!trackerURLs.contains(trackerEntry.url))
const BitTorrent::TrackerEntryUpdateInfo &updateInfo = trackerIt.value(); continue;
if (updateInfo.status == BitTorrent::TrackerEntry::Working) if (trackerEntry.status == BitTorrent::TrackerEntry::Working)
{ {
if (errorHashesIt != m_errors.end()) if (errorHashesIt != m_errors.end())
{ {
QSet<QString> &errored = errorHashesIt.value(); QSet<QString> &errored = errorHashesIt.value();
errored.remove(trackerURL); errored.remove(trackerEntry.url);
} }
if (!updateInfo.hasMessages) if (trackerEntry.message.isEmpty())
{ {
if (warningHashesIt != m_warnings.end()) if (warningHashesIt != m_warnings.end())
{ {
QSet<QString> &warned = *warningHashesIt; QSet<QString> &warned = *warningHashesIt;
warned.remove(trackerURL); warned.remove(trackerEntry.url);
} }
} }
else else
{ {
if (warningHashesIt == m_warnings.end()) if (warningHashesIt == m_warnings.end())
warningHashesIt = m_warnings.insert(id, {}); warningHashesIt = m_warnings.insert(id, {});
warningHashesIt.value().insert(trackerURL); warningHashesIt.value().insert(trackerEntry.url);
} }
} }
else if (updateInfo.status == BitTorrent::TrackerEntry::NotWorking) else if (trackerEntry.status == BitTorrent::TrackerEntry::NotWorking)
{ {
if (errorHashesIt == m_errors.end()) if (errorHashesIt == m_errors.end())
errorHashesIt = m_errors.insert(id, {}); errorHashesIt = m_errors.insert(id, {});
errorHashesIt.value().insert(trackerURL); errorHashesIt.value().insert(trackerEntry.url);
} }
} }
@ -680,13 +730,13 @@ void TrackerFiltersList::applyFilter(const int row)
void TrackerFiltersList::handleNewTorrent(BitTorrent::Torrent *const torrent) void TrackerFiltersList::handleNewTorrent(BitTorrent::Torrent *const torrent)
{ {
const BitTorrent::TorrentID torrentID {torrent->id()}; const BitTorrent::TorrentID torrentID = torrent->id();
const QVector<QString> trackerURLs {torrent->trackerURLs()}; const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
for (const QString &trackerURL : trackerURLs) for (const BitTorrent::TrackerEntry &tracker : trackers)
addItem(trackerURL, torrentID); addItem(tracker.url, torrentID);
// Check for trackerless torrent // Check for trackerless torrent
if (trackerURLs.isEmpty()) if (trackers.isEmpty())
addItem(NULL_HOST, torrentID); addItem(NULL_HOST, torrentID);
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(++m_totalTorrents)); item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(++m_totalTorrents));
@ -694,13 +744,13 @@ void TrackerFiltersList::handleNewTorrent(BitTorrent::Torrent *const torrent)
void TrackerFiltersList::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) void TrackerFiltersList::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
{ {
const BitTorrent::TorrentID torrentID {torrent->id()}; const BitTorrent::TorrentID torrentID = torrent->id();
const QVector<QString> trackerURLs {torrent->trackerURLs()}; const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
for (const QString &trackerURL : trackerURLs) for (const BitTorrent::TrackerEntry &tracker : trackers)
removeItem(trackerURL, torrentID); removeItem(tracker.url, torrentID);
// Check for trackerless torrent // Check for trackerless torrent
if (trackerURLs.isEmpty()) if (trackers.isEmpty())
removeItem(NULL_HOST, torrentID); removeItem(NULL_HOST, torrentID);
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(--m_totalTorrents)); item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(--m_totalTorrents));
@ -838,22 +888,25 @@ void TransferListFiltersWidget::setDownloadTrackerFavicon(bool value)
void TransferListFiltersWidget::addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers) void TransferListFiltersWidget::addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers)
{ {
for (const BitTorrent::TrackerEntry &tracker : trackers) m_trackerFilters->addTrackers(torrent, trackers);
m_trackerFilters->addItem(tracker.url, torrent->id());
} }
void TransferListFiltersWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers) void TransferListFiltersWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
{ {
for (const BitTorrent::TrackerEntry &tracker : trackers) m_trackerFilters->removeTrackers(torrent, trackers);
m_trackerFilters->removeItem(tracker.url, torrent->id()); }
void TransferListFiltersWidget::refreshTrackers(const BitTorrent::Torrent *torrent)
{
m_trackerFilters->refreshTrackers(torrent);
} }
void TransferListFiltersWidget::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless) void TransferListFiltersWidget::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless)
{ {
m_trackerFilters->changeTrackerless(trackerless, torrent->id()); m_trackerFilters->changeTrackerless(torrent, trackerless);
} }
void TransferListFiltersWidget::trackerEntriesUpdated(const QHash<BitTorrent::Torrent *, QHash<QString, BitTorrent::TrackerEntryUpdateInfo>> &updateInfos) void TransferListFiltersWidget::trackerEntriesUpdated(const QHash<BitTorrent::Torrent *, QSet<QString>> &updateInfos)
{ {
m_trackerFilters->handleTrackerEntriesUpdated(updateInfos); m_trackerFilters->handleTrackerEntriesUpdated(updateInfos);
} }

18
src/gui/transferlistfilterswidget.h

@ -124,12 +124,12 @@ public:
TrackerFiltersList(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon); TrackerFiltersList(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon);
~TrackerFiltersList() override; ~TrackerFiltersList() override;
// Redefine addItem() to make sure the list stays sorted void addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers);
void addItem(const QString &tracker, const BitTorrent::TorrentID &id); void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers);
void removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id); void refreshTrackers(const BitTorrent::Torrent *torrent);
void changeTrackerless(bool trackerless, const BitTorrent::TorrentID &id); void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless);
void handleTrackerEntriesUpdated(const QHash<BitTorrent::Torrent *, QSet<QString>> &updateInfos);
void setDownloadTrackerFavicon(bool value); void setDownloadTrackerFavicon(bool value);
void handleTrackerEntriesUpdated(const QHash<BitTorrent::Torrent *, QHash<QString, BitTorrent::TrackerEntryUpdateInfo>> &updateInfos);
private slots: private slots:
void handleFavicoDownloadFinished(const Net::DownloadResult &result); void handleFavicoDownloadFinished(const Net::DownloadResult &result);
@ -141,6 +141,9 @@ private:
void applyFilter(int row) override; void applyFilter(int row) override;
void handleNewTorrent(BitTorrent::Torrent *const torrent) override; void handleNewTorrent(BitTorrent::Torrent *const torrent) override;
void torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) override; void torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) override;
void addItem(const QString &tracker, const BitTorrent::TorrentID &id);
void removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id);
QString trackerFromRow(int row) const; QString trackerFromRow(int row) const;
int rowFromTracker(const QString &tracker) const; int rowFromTracker(const QString &tracker) const;
QSet<BitTorrent::TorrentID> getTorrentIDs(int row) const; QSet<BitTorrent::TorrentID> getTorrentIDs(int row) const;
@ -174,9 +177,10 @@ public:
public slots: public slots:
void addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers); void addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers);
void removeTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers); void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers);
void refreshTrackers(const BitTorrent::Torrent *torrent);
void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless); void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless);
void trackerEntriesUpdated(const QHash<BitTorrent::Torrent *, QHash<QString, BitTorrent::TrackerEntryUpdateInfo>> &updateInfos); void trackerEntriesUpdated(const QHash<BitTorrent::Torrent *, QSet<QString>> &updateInfos);
private slots: private slots:
void onCategoryFilterStateChanged(bool enabled); void onCategoryFilterStateChanged(bool enabled);

21
src/webui/api/torrentscontroller.cpp

@ -767,8 +767,8 @@ void TorrentsController::editTrackerAction()
if (!torrent) if (!torrent)
throw APIError(APIErrorType::NotFound); throw APIError(APIErrorType::NotFound);
const QUrl origTrackerUrl(origUrl); const QUrl origTrackerUrl {origUrl};
const QUrl newTrackerUrl(newUrl); const QUrl newTrackerUrl {newUrl};
if (origTrackerUrl == newTrackerUrl) if (origTrackerUrl == newTrackerUrl)
return; return;
if (!newTrackerUrl.isValid()) if (!newTrackerUrl.isValid())
@ -778,7 +778,7 @@ void TorrentsController::editTrackerAction()
bool match = false; bool match = false;
for (BitTorrent::TrackerEntry &tracker : trackers) for (BitTorrent::TrackerEntry &tracker : trackers)
{ {
const QUrl trackerUrl(tracker.url); const QUrl trackerUrl {tracker.url};
if (trackerUrl == newTrackerUrl) if (trackerUrl == newTrackerUrl)
throw APIError(APIErrorType::Conflict, u"New tracker URL already exists"_qs); throw APIError(APIErrorType::Conflict, u"New tracker URL already exists"_qs);
if (trackerUrl == origTrackerUrl) if (trackerUrl == origTrackerUrl)
@ -806,20 +806,7 @@ void TorrentsController::removeTrackersAction()
throw APIError(APIErrorType::NotFound); throw APIError(APIErrorType::NotFound);
const QStringList urls = params()[u"urls"_qs].split(u'|'); const QStringList urls = params()[u"urls"_qs].split(u'|');
torrent->removeTrackers(urls);
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
QVector<BitTorrent::TrackerEntry> remainingTrackers;
remainingTrackers.reserve(trackers.size());
for (const BitTorrent::TrackerEntry &entry : trackers)
{
if (!urls.contains(entry.url))
remainingTrackers.push_back(entry);
}
if (remainingTrackers.size() == trackers.size())
throw APIError(APIErrorType::Conflict, u"No trackers were removed"_qs);
torrent->replaceTrackers(remainingTrackers);
if (!torrent->isPaused()) if (!torrent->isPaused())
torrent->forceReannounce(); torrent->forceReannounce();

Loading…
Cancel
Save