From d3c432ee9219e10113025c2a7098f5f3e7d49dfe Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Tue, 1 Jul 2025 07:53:44 -0700 Subject: [PATCH 1/3] Reannounce DHT when reannouncing all trackers This matches the behavior exhibited by `TrackerListWidget` when reannouncing to a torrent's complete tracker list. --- src/gui/transferlistwidget.cpp | 3 +++ src/webui/api/torrentscontroller.cpp | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 6fb71bfaa..f78dd8481 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -649,7 +649,10 @@ void TransferListWidget::recheckSelectedTorrents() void TransferListWidget::reannounceSelectedTorrents() { for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents())) + { torrent->forceReannounce(); + torrent->forceDHTAnnounce(); + } } int TransferListWidget::visibleColumnsCount() const diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index fc2d6d4cb..c0e4f777d 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -1619,7 +1619,11 @@ void TorrentsController::reannounceAction() requireParams({u"hashes"_s}); const QStringList hashes {params()[u"hashes"_s].split(u'|')}; - applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) { torrent->forceReannounce(); }); + applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) + { + torrent->forceReannounce(); + torrent->forceDHTAnnounce(); + }); setResult(QString()); } From ee4baa39c4d5f58678d9a0ba72726858364ac765 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Tue, 1 Jul 2025 07:58:40 -0700 Subject: [PATCH 2/3] Skip reannounce if torrent is stopped This matches the behavior exhibited elsewhere, but was not consistently applied. --- src/gui/transferlistwidget.cpp | 7 +++++-- src/webui/api/torrentscontroller.cpp | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index f78dd8481..3cc83cb59 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -650,8 +650,11 @@ void TransferListWidget::reannounceSelectedTorrents() { for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents())) { - torrent->forceReannounce(); - torrent->forceDHTAnnounce(); + if (!torrent->isStopped()) + { + torrent->forceReannounce(); + torrent->forceDHTAnnounce(); + } } } diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index c0e4f777d..0574c8843 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -1621,9 +1621,12 @@ void TorrentsController::reannounceAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) { - torrent->forceReannounce(); - torrent->forceDHTAnnounce(); - }); + if (!torrent->isStopped()) + { + torrent->forceReannounce(); + torrent->forceDHTAnnounce(); + } + }); setResult(QString()); } From 032957f0f8da1f63a54601476b004230ca9e87b8 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Tue, 1 Jul 2025 08:43:42 -0700 Subject: [PATCH 3/3] WebUI: Support reannouncing individual trackers --- WebAPI_Changelog.md | 5 +++ src/webui/api/torrentscontroller.cpp | 24 ++++++++++- src/webui/webapplication.h | 2 +- src/webui/www/private/index.html | 2 + src/webui/www/private/scripts/dynamicTable.js | 5 +++ .../www/private/scripts/prop-trackers.js | 40 +++++++++++++++++++ 6 files changed, 75 insertions(+), 3 deletions(-) diff --git a/WebAPI_Changelog.md b/WebAPI_Changelog.md index 375e24d69..08f5a0b37 100644 --- a/WebAPI_Changelog.md +++ b/WebAPI_Changelog.md @@ -1,5 +1,10 @@ # WebAPI Changelog +## 2.11.10 + +* [#22954](https://github.com/qbittorrent/qBittorrent/pull/22954) + * `torrents/reannounce` supports specifying trackers via `trackers` field + ## 2.11.9 * [#21015](https://github.com/qbittorrent/qBittorrent/pull/21015) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 0574c8843..3d91d0203 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -1619,13 +1619,33 @@ void TorrentsController::reannounceAction() requireParams({u"hashes"_s}); const QStringList hashes {params()[u"hashes"_s].split(u'|')}; - applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) + const QStringList urlsParam {params()[u"urls"_s].split(u'|', Qt::SkipEmptyParts)}; + + QSet urls; + urls.reserve(urlsParam.size()); + for (const QString &urlStr : urlsParam) + urls << QUrl::fromPercentEncoding(urlStr.toLatin1()); + + applyToTorrents(hashes, [&urls](BitTorrent::Torrent *const torrent) { - if (!torrent->isStopped()) + if (torrent->isStopped()) + return; + + if (urls.isEmpty()) { torrent->forceReannounce(); torrent->forceDHTAnnounce(); } + else + { + const QList &trackers = torrent->trackers(); + for (qsizetype i = 0; i < trackers.size(); ++i) + { + const BitTorrent::TrackerEntryStatus &status = trackers.at(i); + if (urls.contains(status.url)) + torrent->forceReannounce(i); + } + } }); setResult(QString()); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 6ceb28593..2098e3635 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -53,7 +53,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 11, 9}; +inline const Utils::Version<3, 2> API_VERSION {2, 11, 10}; class APIController; class AuthController; diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index 47fa063e9..255c4d4f1 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -248,6 +248,8 @@
  • QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget]
  • QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]
  • QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]
  • +
  • QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Force reannounce to selected tracker(s))QBT_TR[CONTEXT=TrackerListWidget]
  • +
  • QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Force reannounce to all trackers)QBT_TR[CONTEXT=TrackerListWidget]
    • QBT_TR(Add peers...)QBT_TR[CONTEXT=PeerListWidget] QBT_TR(Add peers...)QBT_TR[CONTEXT=PeerListWidget]
    • diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 032a79a1e..3d3f656a8 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -1818,6 +1818,11 @@ window.qBittorrent.DynamicTable ??= (() => { onSelectedRowChanged() { updatePropertiesPanel(); } + + isStopped(hash) { + const row = this.getRow(hash); + return (row === undefined) ? true : row.full_data.state.includes("stopped"); + } } class TorrentPeersTable extends DynamicTable { diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index 945688aef..1ad3864c8 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -148,6 +148,12 @@ window.qBittorrent.PropTrackers ??= (() => { }, RemoveTracker: (element, ref) => { removeTrackerFN(element); + }, + ReannounceTrackers: (element, ref) => { + reannounceTrackersFN(element, torrentTrackersTable.selectedRowsIds()); + }, + ReannounceAllTrackers: (element, ref) => { + reannounceTrackersFN(element, []); } }, offsets: { @@ -164,6 +170,8 @@ window.qBittorrent.PropTrackers ??= (() => { this.hideItem("EditTracker"); this.hideItem("RemoveTracker"); this.hideItem("CopyTrackerUrl"); + this.hideItem("ReannounceTrackers"); + this.hideItem("ReannounceAllTrackers"); } else { if (selectedTrackers.length === 1) @@ -173,6 +181,16 @@ window.qBittorrent.PropTrackers ??= (() => { this.showItem("RemoveTracker"); this.showItem("CopyTrackerUrl"); + + const torrentHash = torrentsTable.getCurrentTorrentID(); + if (torrentsTable.isStopped(torrentHash)) { + this.hideItem("ReannounceTrackers"); + this.hideItem("ReannounceAllTrackers"); + } + else { + this.showItem("ReannounceTrackers"); + this.showItem("ReannounceAllTrackers"); + } } } }); @@ -253,6 +271,28 @@ window.qBittorrent.PropTrackers ??= (() => { }); }; + const reannounceTrackersFN = (element, trackers) => { + if (current_hash.length === 0) + return; + + const body = new URLSearchParams({ + hashes: current_hash + }); + if (trackers.length > 0) + body.set("urls", trackers.map(encodeURIComponent).join("|")); + + fetch("api/v2/torrents/reannounce", { + method: "POST", + body: body + }) + .then((response) => { + if (!response.ok) + return; + + updateData(); + }); + }; + const clear = () => { torrentTrackersTable.clear(); };