WebUI: Support reannouncing individual trackers

PR #22954.
This commit is contained in:
Thomas Piccirello 2025-07-19 09:29:14 +02:00 committed by GitHub
commit 163f683186
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 72 additions and 3 deletions

View file

@ -4,6 +4,8 @@
* [#22932](https://github.com/qbittorrent/qBittorrent/pull/22932) * [#22932](https://github.com/qbittorrent/qBittorrent/pull/22932)
* `torrents/categories` and `sync/maindata` now serialize categories' `downloadPath` to `null`, rather than `undefined` * `torrents/categories` and `sync/maindata` now serialize categories' `downloadPath` to `null`, rather than `undefined`
* [#22954](https://github.com/qbittorrent/qBittorrent/pull/22954)
* `torrents/reannounce` supports specifying individual trackers via `trackers` field
## 2.11.9 ## 2.11.9

View file

@ -1618,10 +1618,30 @@ void TorrentsController::reannounceAction()
requireParams({u"hashes"_s}); requireParams({u"hashes"_s});
const QStringList hashes {params()[u"hashes"_s].split(u'|')}; 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<QString> urls;
urls.reserve(urlsParam.size());
for (const QString &urlStr : urlsParam)
urls << QUrl::fromPercentEncoding(urlStr.toLatin1());
applyToTorrents(hashes, [&urls](BitTorrent::Torrent *const torrent)
{
if (urls.isEmpty())
{ {
torrent->forceReannounce(); torrent->forceReannounce();
torrent->forceDHTAnnounce(); torrent->forceDHTAnnounce();
}
else
{
const QList<BitTorrent::TrackerEntryStatus> &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()); setResult(QString());

View file

@ -248,6 +248,8 @@
<li class="separator"><a href="#EditTracker"><img src="images/edit-rename.svg" alt="QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget]</a></li> <li class="separator"><a href="#EditTracker"><img src="images/edit-rename.svg" alt="QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
<li><a href="#RemoveTracker"><img src="images/list-remove.svg" alt="QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]</a></li> <li><a href="#RemoveTracker"><img src="images/list-remove.svg" alt="QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
<li><a href="#CopyTrackerUrl" id="CopyTrackerUrl"><img src="images/edit-copy.svg" alt="QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]</a></li> <li><a href="#CopyTrackerUrl" id="CopyTrackerUrl"><img src="images/edit-copy.svg" alt="QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
<li><a href="#ReannounceTrackers" id="ReannounceTrackers"><img src="images/view-refresh.svg" alt="QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Force reannounce to selected tracker(s))QBT_TR[CONTEXT=TrackerListWidget]</a></li>
<li class="separator"><a href="#ReannounceAllTrackers" id="ReannounceAllTrackers"><img src="images/view-refresh.svg" alt="QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Force reannounce to all trackers)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
</ul> </ul>
<ul id="torrentPeersMenu" class="contextMenu"> <ul id="torrentPeersMenu" class="contextMenu">
<li><a href="#addPeer"><img src="images/peers-add.svg" alt="QBT_TR(Add peers...)QBT_TR[CONTEXT=PeerListWidget]"> QBT_TR(Add peers...)QBT_TR[CONTEXT=PeerListWidget]</a></li> <li><a href="#addPeer"><img src="images/peers-add.svg" alt="QBT_TR(Add peers...)QBT_TR[CONTEXT=PeerListWidget]"> QBT_TR(Add peers...)QBT_TR[CONTEXT=PeerListWidget]</a></li>

View file

@ -1818,6 +1818,11 @@ window.qBittorrent.DynamicTable ??= (() => {
onSelectedRowChanged() { onSelectedRowChanged() {
updatePropertiesPanel(); updatePropertiesPanel();
} }
isStopped(hash) {
const row = this.getRow(hash);
return (row === undefined) ? true : row.full_data.state.includes("stopped");
}
} }
class TorrentPeersTable extends DynamicTable { class TorrentPeersTable extends DynamicTable {

View file

@ -148,6 +148,12 @@ window.qBittorrent.PropTrackers ??= (() => {
}, },
RemoveTracker: (element, ref) => { RemoveTracker: (element, ref) => {
removeTrackerFN(element); removeTrackerFN(element);
},
ReannounceTrackers: (element, ref) => {
reannounceTrackersFN(element, torrentTrackersTable.selectedRowsIds());
},
ReannounceAllTrackers: (element, ref) => {
reannounceTrackersFN(element, []);
} }
}, },
offsets: { offsets: {
@ -164,6 +170,8 @@ window.qBittorrent.PropTrackers ??= (() => {
this.hideItem("EditTracker"); this.hideItem("EditTracker");
this.hideItem("RemoveTracker"); this.hideItem("RemoveTracker");
this.hideItem("CopyTrackerUrl"); this.hideItem("CopyTrackerUrl");
this.hideItem("ReannounceTrackers");
this.hideItem("ReannounceAllTrackers");
} }
else { else {
if (selectedTrackers.length === 1) if (selectedTrackers.length === 1)
@ -173,6 +181,16 @@ window.qBittorrent.PropTrackers ??= (() => {
this.showItem("RemoveTracker"); this.showItem("RemoveTracker");
this.showItem("CopyTrackerUrl"); 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 = () => { const clear = () => {
torrentTrackersTable.clear(); torrentTrackersTable.clear();
}; };