From dca7293c13c96f34d81c39a98219a8fe38940ef4 Mon Sep 17 00:00:00 2001 From: LWWZH Date: Tue, 26 May 2026 17:37:16 +1000 Subject: [PATCH 1/6] feat: add 'Retries' column to Trackers tab showing consecutive announce failure count --- src/base/bittorrent/torrentimpl.cpp | 3 +++ src/base/bittorrent/trackerentrystatus.cpp | 1 + src/base/bittorrent/trackerentrystatus.h | 2 ++ src/gui/trackerlist/trackerlistmodel.cpp | 10 ++++++++++ src/gui/trackerlist/trackerlistmodel.h | 1 + src/webui/api/torrentscontroller.cpp | 3 +++ src/webui/www/private/scripts/dynamicTable.js | 1 + src/webui/www/private/scripts/prop-trackers.js | 2 ++ 8 files changed, 23 insertions(+) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index aed514052ca6..d9c10c7c3f74 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -147,6 +147,7 @@ namespace trackerEndpointStatus.numSeeds = ltAnnounceInfo.scrape_complete; trackerEndpointStatus.numLeeches = ltAnnounceInfo.scrape_incomplete; trackerEndpointStatus.numDownloaded = ltAnnounceInfo.scrape_downloaded; + trackerEndpointStatus.numFails = ltAnnounceInfo.fails; trackerEndpointStatus.nextAnnounceTime = ltAnnounceInfo.next_announce; trackerEndpointStatus.minAnnounceTime = ltAnnounceInfo.min_announce; @@ -249,6 +250,7 @@ namespace trackerEntryStatus.numSeeds = -1; trackerEntryStatus.numLeeches = -1; trackerEntryStatus.numDownloaded = -1; + trackerEntryStatus.numFails = -1; trackerEntryStatus.nextAnnounceTime = {}; trackerEntryStatus.minAnnounceTime = {}; trackerEntryStatus.message.clear(); @@ -259,6 +261,7 @@ namespace trackerEntryStatus.numSeeds = std::max(trackerEntryStatus.numSeeds, endpointStatus.numSeeds); trackerEntryStatus.numLeeches = std::max(trackerEntryStatus.numLeeches, endpointStatus.numLeeches); trackerEntryStatus.numDownloaded = std::max(trackerEntryStatus.numDownloaded, endpointStatus.numDownloaded); + trackerEntryStatus.numFails = std::max(trackerEntryStatus.numFails, endpointStatus.numFails); if (endpointStatus.state == trackerEntryStatus.state) { diff --git a/src/base/bittorrent/trackerentrystatus.cpp b/src/base/bittorrent/trackerentrystatus.cpp index 35acd09d374a..8663d758ebc7 100644 --- a/src/base/bittorrent/trackerentrystatus.cpp +++ b/src/base/bittorrent/trackerentrystatus.cpp @@ -38,6 +38,7 @@ void BitTorrent::TrackerEntryStatus::clear() numSeeds = -1; numLeeches = -1; numDownloaded = -1; + numFails = -1; nextAnnounceTime = {}; minAnnounceTime = {}; endpoints.clear(); diff --git a/src/base/bittorrent/trackerentrystatus.h b/src/base/bittorrent/trackerentrystatus.h index 026580623ced..0d74c6a74ebf 100644 --- a/src/base/bittorrent/trackerentrystatus.h +++ b/src/base/bittorrent/trackerentrystatus.h @@ -59,6 +59,7 @@ namespace BitTorrent int numSeeds = -1; int numLeeches = -1; int numDownloaded = -1; + int numFails = -1; AnnounceTimePoint nextAnnounceTime {}; AnnounceTimePoint minAnnounceTime {}; @@ -77,6 +78,7 @@ namespace BitTorrent int numSeeds = -1; int numLeeches = -1; int numDownloaded = -1; + int numFails = -1; AnnounceTimePoint nextAnnounceTime {}; AnnounceTimePoint minAnnounceTime {}; diff --git a/src/gui/trackerlist/trackerlistmodel.cpp b/src/gui/trackerlist/trackerlistmodel.cpp index 54be08fd46e4..47f5609a7fec 100644 --- a/src/gui/trackerlist/trackerlistmodel.cpp +++ b/src/gui/trackerlist/trackerlistmodel.cpp @@ -125,6 +125,7 @@ struct TrackerListModel::Item final int numSeeds = -1; int numLeeches = -1; int numDownloaded = -1; + int numFails = -1; BitTorrent::AnnounceTimePoint nextAnnounceTime; BitTorrent::AnnounceTimePoint minAnnounceTime; @@ -195,6 +196,7 @@ void TrackerListModel::Item::fillFrom(const BitTorrent::TrackerEntryStatus &trac numSeeds = trackerEntryStatus.numSeeds; numLeeches = trackerEntryStatus.numLeeches; numDownloaded = trackerEntryStatus.numDownloaded; + numFails = trackerEntryStatus.numFails; nextAnnounceTime = trackerEntryStatus.nextAnnounceTime; minAnnounceTime = trackerEntryStatus.minAnnounceTime; secsToNextAnnounce = 0; @@ -215,6 +217,7 @@ void TrackerListModel::Item::fillFrom(const BitTorrent::TrackerEndpointStatus &e numSeeds = endpointStatus.numSeeds; numLeeches = endpointStatus.numLeeches; numDownloaded = endpointStatus.numDownloaded; + numFails = endpointStatus.numFails; nextAnnounceTime = endpointStatus.nextAnnounceTime; minAnnounceTime = endpointStatus.minAnnounceTime; secsToNextAnnounce = 0; @@ -519,6 +522,8 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o return tr("Next Announce"); case COL_MIN_ANNOUNCE: return tr("Min Announce"); + case COL_RETRIES: + return tr("Retries"); default: return {}; } @@ -533,6 +538,7 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o case COL_TIMES_DOWNLOADED: case COL_NEXT_ANNOUNCE: case COL_MIN_ANNOUNCE: + case COL_RETRIES: return QVariant {Qt::AlignRight | Qt::AlignVCenter}; default: return {}; @@ -618,6 +624,8 @@ QVariant TrackerListModel::data(const QModelIndex &index, const int role) const return prettyCount(itemPtr->numLeeches); case COL_TIMES_DOWNLOADED: return prettyCount(itemPtr->numDownloaded); + case COL_RETRIES: + return prettyCount(itemPtr->numFails); case COL_MSG: return itemPtr->message; case COL_NEXT_ANNOUNCE: @@ -647,6 +655,8 @@ QVariant TrackerListModel::data(const QModelIndex &index, const int role) const return itemPtr->numLeeches; case COL_TIMES_DOWNLOADED: return itemPtr->numDownloaded; + case COL_RETRIES: + return itemPtr->numFails; case COL_MSG: return itemPtr->message; case COL_NEXT_ANNOUNCE: diff --git a/src/gui/trackerlist/trackerlistmodel.h b/src/gui/trackerlist/trackerlistmodel.h index 523f894b5c7c..7adfab525b26 100644 --- a/src/gui/trackerlist/trackerlistmodel.h +++ b/src/gui/trackerlist/trackerlistmodel.h @@ -65,6 +65,7 @@ class TrackerListModel final : public QAbstractItemModel COL_MSG, COL_NEXT_ANNOUNCE, COL_MIN_ANNOUNCE, + COL_RETRIES, COL_COUNT }; diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index ca029944c99b..61f7e6f962fc 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -82,6 +82,7 @@ const QString KEY_TRACKER_PEERS_COUNT = u"num_peers"_s; const QString KEY_TRACKER_SEEDS_COUNT = u"num_seeds"_s; const QString KEY_TRACKER_LEECHES_COUNT = u"num_leeches"_s; const QString KEY_TRACKER_DOWNLOADED_COUNT = u"num_downloaded"_s; +const QString KEY_TRACKER_FAIL_COUNT = u"num_fails"_s; const QString KEY_TRACKER_NEXT_ANNOUNCE = u"next_announce"_s; const QString KEY_TRACKER_MIN_ANNOUNCE = u"min_announce"_s; const QString KEY_TRACKER_ENDPOINTS = u"endpoints"_s; @@ -306,6 +307,7 @@ namespace {KEY_TRACKER_SEEDS_COUNT, endpoint.numSeeds}, {KEY_TRACKER_LEECHES_COUNT, endpoint.numLeeches}, {KEY_TRACKER_DOWNLOADED_COUNT, endpoint.numDownloaded}, + {KEY_TRACKER_FAIL_COUNT, endpoint.numFails}, {KEY_TRACKER_NEXT_ANNOUNCE, toSecondsSinceEpoch(endpoint.nextAnnounceTime)}, {KEY_TRACKER_MIN_ANNOUNCE, toSecondsSinceEpoch(endpoint.minAnnounceTime)} }; @@ -322,6 +324,7 @@ namespace {KEY_TRACKER_SEEDS_COUNT, tracker.numSeeds}, {KEY_TRACKER_LEECHES_COUNT, tracker.numLeeches}, {KEY_TRACKER_DOWNLOADED_COUNT, tracker.numDownloaded}, + {KEY_TRACKER_FAIL_COUNT, tracker.numFails}, {KEY_TRACKER_NEXT_ANNOUNCE, toSecondsSinceEpoch(tracker.nextAnnounceTime)}, {KEY_TRACKER_MIN_ANNOUNCE, toSecondsSinceEpoch(tracker.minAnnounceTime)}, {KEY_TRACKER_ENDPOINTS, endpointsList} diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 17b7602a7833..3504c750e89e 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -2154,6 +2154,7 @@ window.qBittorrent.DynamicTable ??= (() => { this.newColumn("seeds", "", "QBT_TR(Seeds)QBT_TR[CONTEXT=TrackerListWidget]", 75, true); this.newColumn("leeches", "", "QBT_TR(Leeches)QBT_TR[CONTEXT=TrackerListWidget]", 75, true); this.newColumn("downloaded", "", "QBT_TR(Times Downloaded)QBT_TR[CONTEXT=TrackerListWidget]", 100, true); + this.newColumn("fails", "", "QBT_TR(Retries)QBT_TR[CONTEXT=TrackerListWidget]", 50, true); this.newColumn("message", "", "QBT_TR(Message)QBT_TR[CONTEXT=TrackerListWidget]", 250, true); this.newColumn("nextAnnounce", "", "QBT_TR(Next Announce)QBT_TR[CONTEXT=TrackerListWidget]", 150, true); this.newColumn("minAnnounce", "", "QBT_TR(Min Announce)QBT_TR[CONTEXT=TrackerListWidget]", 150, true); diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index 6b0d4c84441b..90510cb3a5d8 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -113,6 +113,7 @@ window.qBittorrent.PropTrackers ??= (() => { seeds: (tracker.num_seeds >= 0) ? tracker.num_seeds : notApplicable, leeches: (tracker.num_leeches >= 0) ? tracker.num_leeches : notApplicable, downloaded: (tracker.num_downloaded >= 0) ? tracker.num_downloaded : notApplicable, + fails: (tracker.num_fails >= 0) ? tracker.num_fails : notApplicable, message: tracker.msg, nextAnnounce: tracker.next_announce, minAnnounce: tracker.min_announce, @@ -134,6 +135,7 @@ window.qBittorrent.PropTrackers ??= (() => { seeds: (endpoint.num_seeds >= 0) ? endpoint.num_seeds : notApplicable, leeches: (endpoint.num_leeches >= 0) ? endpoint.num_leeches : notApplicable, downloaded: (endpoint.num_downloaded >= 0) ? endpoint.num_downloaded : notApplicable, + fails: (endpoint.num_fails >= 0) ? endpoint.num_fails : notApplicable, message: endpoint.msg, nextAnnounce: endpoint.next_announce, minAnnounce: endpoint.min_announce, From 7e1d78be944ca779a59859912525c356e6e08809 Mon Sep 17 00:00:00 2001 From: LWWZH Date: Tue, 26 May 2026 20:56:25 +1000 Subject: [PATCH 2/6] fix alignment, aggregation, column order, and WebUI sorting for Retries column --- src/base/bittorrent/torrentimpl.cpp | 2 +- src/gui/trackerlist/trackerlistmodel.cpp | 7 ++++--- src/gui/trackerlist/trackerlistmodel.h | 2 +- src/webui/www/private/scripts/dynamicTable.js | 3 +++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index d9c10c7c3f74..2be5b8f60ac1 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -261,7 +261,7 @@ namespace trackerEntryStatus.numSeeds = std::max(trackerEntryStatus.numSeeds, endpointStatus.numSeeds); trackerEntryStatus.numLeeches = std::max(trackerEntryStatus.numLeeches, endpointStatus.numLeeches); trackerEntryStatus.numDownloaded = std::max(trackerEntryStatus.numDownloaded, endpointStatus.numDownloaded); - trackerEntryStatus.numFails = std::max(trackerEntryStatus.numFails, endpointStatus.numFails); + trackerEntryStatus.numFails = std::min(trackerEntryStatus.numFails, endpointStatus.numFails); if (endpointStatus.state == trackerEntryStatus.state) { diff --git a/src/gui/trackerlist/trackerlistmodel.cpp b/src/gui/trackerlist/trackerlistmodel.cpp index 47f5609a7fec..df9f2d6a577a 100644 --- a/src/gui/trackerlist/trackerlistmodel.cpp +++ b/src/gui/trackerlist/trackerlistmodel.cpp @@ -516,14 +516,14 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o return tr("Leeches"); case COL_TIMES_DOWNLOADED: return tr("Times Downloaded"); + case COL_RETRIES: + return tr("Retries"); case COL_MSG: return tr("Message"); case COL_NEXT_ANNOUNCE: return tr("Next Announce"); case COL_MIN_ANNOUNCE: return tr("Min Announce"); - case COL_RETRIES: - return tr("Retries"); default: return {}; } @@ -536,9 +536,9 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o case COL_SEEDS: case COL_LEECHES: case COL_TIMES_DOWNLOADED: + case COL_RETRIES: case COL_NEXT_ANNOUNCE: case COL_MIN_ANNOUNCE: - case COL_RETRIES: return QVariant {Qt::AlignRight | Qt::AlignVCenter}; default: return {}; @@ -583,6 +583,7 @@ QVariant TrackerListModel::data(const QModelIndex &index, const int role) const case COL_SEEDS: case COL_LEECHES: case COL_TIMES_DOWNLOADED: + case COL_RETRIES: case COL_NEXT_ANNOUNCE: case COL_MIN_ANNOUNCE: return QVariant {Qt::AlignRight | Qt::AlignVCenter}; diff --git a/src/gui/trackerlist/trackerlistmodel.h b/src/gui/trackerlist/trackerlistmodel.h index 7adfab525b26..cb2a043bfedf 100644 --- a/src/gui/trackerlist/trackerlistmodel.h +++ b/src/gui/trackerlist/trackerlistmodel.h @@ -62,10 +62,10 @@ class TrackerListModel final : public QAbstractItemModel COL_SEEDS, COL_LEECHES, COL_TIMES_DOWNLOADED, + COL_RETRIES, COL_MSG, COL_NEXT_ANNOUNCE, COL_MIN_ANNOUNCE, - COL_RETRIES, COL_COUNT }; diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 3504c750e89e..45ec3c8854c8 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -2245,6 +2245,9 @@ window.qBittorrent.DynamicTable ??= (() => { this.columns["seeds"].compareRows = sortMixed; this.columns["leeches"].compareRows = sortMixed; this.columns["downloaded"].compareRows = sortMixed; + this.columns["fails"].compareRows = sortMixed; + this.columns["nextAnnounce"].compareRows = sortNumbers; + this.columns["minAnnounce"].compareRows = sortNumbers; this.columns["status"].updateTd = function(td, row) { let statusClass = "trackerUnknown"; From 7440918350c69fe4506e7b78a24f397076b063d4 Mon Sep 17 00:00:00 2001 From: LWWZH Date: Wed, 27 May 2026 07:31:00 +1000 Subject: [PATCH 3/6] fix: properly aggregate tracker-level numFails by skipping -1 sentinel --- src/base/bittorrent/torrentimpl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 2be5b8f60ac1..d8b082008676 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -261,7 +261,8 @@ namespace trackerEntryStatus.numSeeds = std::max(trackerEntryStatus.numSeeds, endpointStatus.numSeeds); trackerEntryStatus.numLeeches = std::max(trackerEntryStatus.numLeeches, endpointStatus.numLeeches); trackerEntryStatus.numDownloaded = std::max(trackerEntryStatus.numDownloaded, endpointStatus.numDownloaded); - trackerEntryStatus.numFails = std::min(trackerEntryStatus.numFails, endpointStatus.numFails); + if (endpointStatus.numFails >= 0) + trackerEntryStatus.numFails = (trackerEntryStatus.numFails < 0) ? endpointStatus.numFails : std::min(trackerEntryStatus.numFails, endpointStatus.numFails); if (endpointStatus.state == trackerEntryStatus.state) { From 40d4c4031ac03d36627f571c3cf2aa6fae6ca266 Mon Sep 17 00:00:00 2001 From: LWWZH Date: Thu, 16 Jul 2026 09:34:50 +1000 Subject: [PATCH 4/6] resolve reviews --- WebAPI_Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WebAPI_Changelog.md b/WebAPI_Changelog.md index bf55889422f8..5f16d785f3e9 100644 --- a/WebAPI_Changelog.md +++ b/WebAPI_Changelog.md @@ -1,6 +1,8 @@ # WebAPI Changelog ## 2.16.0 +* [#24388](https://github.com/qbittorrent/qBittorrent/pull/24388) + * `torrents/trackers` endpoint now includes `num_fails` field for tracker entries, reporting the number of consecutive failed announce attempts * [#24346](https://github.com/qbittorrent/qBittorrent/pull/24346) * `torrentcreator/addTask` endpoint accepts a new bool option `ignoreDotfiles` to control whether dotfiles are ignored * `torrentcreator/status` endpoint returns a new bool field `ignoreDotfiles` for reporting whether dotfiles were ignored From 1eb1ca50598009fc00bca68ad18a36e683592436 Mon Sep 17 00:00:00 2001 From: Zihao Date: Thu, 16 Jul 2026 09:38:37 +1000 Subject: [PATCH 5/6] Apply suggestion from @glassez Co-authored-by: Vladimir Golovnev --- src/base/bittorrent/torrentimpl.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index d8b082008676..8be2931673c6 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -262,7 +262,11 @@ namespace trackerEntryStatus.numLeeches = std::max(trackerEntryStatus.numLeeches, endpointStatus.numLeeches); trackerEntryStatus.numDownloaded = std::max(trackerEntryStatus.numDownloaded, endpointStatus.numDownloaded); if (endpointStatus.numFails >= 0) - trackerEntryStatus.numFails = (trackerEntryStatus.numFails < 0) ? endpointStatus.numFails : std::min(trackerEntryStatus.numFails, endpointStatus.numFails); + { + trackerEntryStatus.numFails = (trackerEntryStatus.numFails < 0) + ? endpointStatus.numFails + : std::min(trackerEntryStatus.numFails, endpointStatus.numFails); + } if (endpointStatus.state == trackerEntryStatus.state) { From 4d5fa753db00867ae5dac7961e58c5cbebaf7e73 Mon Sep 17 00:00:00 2001 From: Zihao Date: Thu, 16 Jul 2026 09:43:23 +1000 Subject: [PATCH 6/6] Update WebAPI Changelog for version 2.16.0 --- WebAPI_Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebAPI_Changelog.md b/WebAPI_Changelog.md index b76b3c99a7ec..7c9636761ff5 100644 --- a/WebAPI_Changelog.md +++ b/WebAPI_Changelog.md @@ -1,9 +1,9 @@ # WebAPI Changelog ## 2.16.0 + * [#24388](https://github.com/qbittorrent/qBittorrent/pull/24388) * `torrents/trackers` endpoint now includes `num_fails` field for tracker entries, reporting the number of consecutive failed announce attempts - * [#23989](https://github.com/qbittorrent/qBittorrent/pull/23989) * `sync/torrentPeers` endpoint now includes calculated `contribution` to a peer's progress * [#24346](https://github.com/qbittorrent/qBittorrent/pull/24346)