Support removing tracker from all torrents in WebUI/WebAPI

Closes #20661.
PR #21056.
This commit is contained in:
Thomas Piccirello 2024-09-16 02:47:10 -07:00 committed by GitHub
parent d19f7b12d9
commit d2b2afad23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 132 additions and 8 deletions

View file

@ -969,12 +969,33 @@ void TorrentsController::removeTrackersAction()
{ {
requireParams({u"hash"_s, u"urls"_s}); requireParams({u"hash"_s, u"urls"_s});
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]); const QString hashParam = params()[u"hash"_s];
const QStringList urlsParam = params()[u"urls"_s].split(u'|', Qt::SkipEmptyParts);
QStringList urls;
urls.reserve(urlsParam.size());
for (const QString &urlStr : urlsParam)
urls << QUrl::fromPercentEncoding(urlStr.toLatin1());
QList<BitTorrent::Torrent *> torrents;
if (hashParam == u"*"_s)
{
// remove trackers from all torrents
torrents = BitTorrent::Session::instance()->torrents();
}
else
{
// remove trackers from specified torrent
const auto id = BitTorrent::TorrentID::fromString(hashParam);
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id); BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
if (!torrent) if (!torrent)
throw APIError(APIErrorType::NotFound); throw APIError(APIErrorType::NotFound);
const QStringList urls = params()[u"urls"_s].split(u'|'); torrents.append(torrent);
}
for (BitTorrent::Torrent *const torrent : asConst(torrents))
torrent->removeTrackers(urls); torrent->removeTrackers(urls);
} }

View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="${LANG}">
<head>
<meta charset="UTF-8">
<title>QBT_TR(Remove tracker)QBT_TR[CONTEXT=confirmDeletionDlg]</title>
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css">
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
<script>
"use strict";
window.addEventListener("DOMContentLoaded", () => {
const host = new URI().getData("host");
const urls = new URI().getData("urls");
$("confirmDeleteTrackerText").textContent = "QBT_TR(Are you sure you want to remove tracker %1 from all torrents?)QBT_TR[CONTEXT=TrackersFilterWidget]".replace("%1", `"${host}"`);
$("cancelBtn").focus();
$("cancelBtn").addEvent("click", (e) => {
e.stopPropagation();
window.parent.qBittorrent.Client.closeWindows();
});
$("confirmBtn").addEvent("click", (e) => {
e.stopPropagation();
const cmd = "api/v2/torrents/removeTrackers";
new Request({
url: cmd,
method: "post",
data: {
hash: "*",
urls: urls,
},
onComplete: function() {
window.parent.qBittorrent.Client.closeWindows();
}
}).send();
});
});
</script>
</head>
<body>
<div style="padding: 10px 10px 0px 10px;">
<p id="confirmDeleteTrackerText"></p>
<div style="text-align: right;">
<input type="button" id="cancelBtn" value="QBT_TR(Cancel)QBT_TR[CONTEXT=MainWindow]">
<input type="button" id="confirmBtn" value="QBT_TR(Remove)QBT_TR[CONTEXT=MainWindow]">
</div>
</div>
</body>
</html>

View file

@ -228,7 +228,8 @@
<li><a href="#deleteTorrentsByTag"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]</a></li> <li><a href="#deleteTorrentsByTag"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
</ul> </ul>
<ul id="trackersFilterMenu" class="contextMenu"> <ul id="trackersFilterMenu" class="contextMenu">
<li><a href="#startTorrentsByTracker"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Start torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li> <li><a href="#deleteTracker"><img src="images/edit-clear.svg" alt="QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li class="separator"><a href="#startTorrentsByTracker"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Start torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li><a href="#stopTorrentsByTracker"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Stop torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li> <li><a href="#stopTorrentsByTracker"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Stop torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li><a href="#deleteTorrentsByTracker"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li> <li><a href="#deleteTorrentsByTracker"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
</ul> </ul>

View file

@ -644,6 +644,12 @@ window.addEventListener("DOMContentLoaded", () => {
trackerFilterList.appendChild(createLink(TRACKERS_ALL, "QBT_TR(All (%1))QBT_TR[CONTEXT=TrackerFiltersList]", torrentsCount)); trackerFilterList.appendChild(createLink(TRACKERS_ALL, "QBT_TR(All (%1))QBT_TR[CONTEXT=TrackerFiltersList]", torrentsCount));
trackerFilterList.appendChild(createLink(TRACKERS_TRACKERLESS, "QBT_TR(Trackerless (%1))QBT_TR[CONTEXT=TrackerFiltersList]", trackerlessTorrentsCount)); trackerFilterList.appendChild(createLink(TRACKERS_TRACKERLESS, "QBT_TR(Trackerless (%1))QBT_TR[CONTEXT=TrackerFiltersList]", trackerlessTorrentsCount));
// Remove unused trackers
for (const [key, { trackerTorrentMap }] of trackerList) {
if (trackerTorrentMap.size === 0)
trackerList.delete(key);
}
// Sort trackers by hostname // Sort trackers by hostname
const sortedList = []; const sortedList = [];
trackerList.forEach(({ host, trackerTorrentMap }, hash) => { trackerList.forEach(({ host, trackerTorrentMap }, hash) => {

View file

@ -36,6 +36,7 @@ window.qBittorrent.ContextMenu ??= (() => {
TorrentsTableContextMenu: TorrentsTableContextMenu, TorrentsTableContextMenu: TorrentsTableContextMenu,
CategoriesFilterContextMenu: CategoriesFilterContextMenu, CategoriesFilterContextMenu: CategoriesFilterContextMenu,
TagsFilterContextMenu: TagsFilterContextMenu, TagsFilterContextMenu: TagsFilterContextMenu,
TrackersFilterContextMenu: TrackersFilterContextMenu,
SearchPluginsTableContextMenu: SearchPluginsTableContextMenu, SearchPluginsTableContextMenu: SearchPluginsTableContextMenu,
RssFeedContextMenu: RssFeedContextMenu, RssFeedContextMenu: RssFeedContextMenu,
RssArticleContextMenu: RssArticleContextMenu, RssArticleContextMenu: RssArticleContextMenu,
@ -604,6 +605,17 @@ window.qBittorrent.ContextMenu ??= (() => {
} }
}); });
const TrackersFilterContextMenu = new Class({
Extends: ContextMenu,
updateMenuItems: function() {
const id = Number(this.options.element.id);
if ((id !== TRACKERS_ALL) && (id !== TRACKERS_TRACKERLESS))
this.showItem("deleteTracker");
else
this.hideItem("deleteTracker");
}
});
const SearchPluginsTableContextMenu = new Class({ const SearchPluginsTableContextMenu = new Class({
Extends: ContextMenu, Extends: ContextMenu,

View file

@ -132,6 +132,7 @@ let deleteTorrentsByTagFN = function() {};
let startTorrentsByTrackerFN = function() {}; let startTorrentsByTrackerFN = function() {};
let stopTorrentsByTrackerFN = function() {}; let stopTorrentsByTrackerFN = function() {};
let deleteTorrentsByTrackerFN = function() {}; let deleteTorrentsByTrackerFN = function() {};
let deleteTrackerFN = function() {};
let copyNameFN = function() {}; let copyNameFN = function() {};
let copyInfohashFN = function(policy) {}; let copyInfohashFN = function(policy) {};
let copyMagnetLinkFN = function() {}; let copyMagnetLinkFN = function() {};
@ -1134,6 +1135,33 @@ const initializeWindows = function() {
} }
}; };
deleteTrackerFN = function(trackerHash) {
const trackerHashInt = Number.parseInt(trackerHash, 10);
if ((trackerHashInt === TRACKERS_ALL) || (trackerHashInt === TRACKERS_TRACKERLESS))
return;
const tracker = trackerList.get(trackerHashInt);
const host = tracker.host;
const urls = [...tracker.trackerTorrentMap.keys()];
new MochaUI.Window({
id: "confirmDeletionPage",
title: "QBT_TR(Remove tracker)QBT_TR[CONTEXT=confirmDeletionDlg]",
loadMethod: "iframe",
contentURL: new URI("confirmtrackerdeletion.html").setData("host", host).setData("urls", urls.map(encodeURIComponent).join("|")).toString(),
scrollbars: false,
resizable: true,
maximizable: false,
padding: 10,
width: 424,
height: 100,
onCloseComplete: function() {
updateMainData();
setTrackerFilter(TRACKERS_ALL);
}
});
};
copyNameFN = function() { copyNameFN = function() {
const selectedRows = torrentsTable.selectedRowsIds(); const selectedRows = torrentsTable.selectedRowsIds();
const names = []; const names = [];

View file

@ -153,10 +153,13 @@
} }
}); });
const trackersFilterContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ const trackersFilterContextMenu = new window.qBittorrent.ContextMenu.TrackersFilterContextMenu({
targets: ".trackersFilterContextMenuTarget", targets: ".trackersFilterContextMenuTarget",
menu: "trackersFilterMenu", menu: "trackersFilterMenu",
actions: { actions: {
deleteTracker: function(element, ref) {
deleteTrackerFN(element.id);
},
startTorrentsByTracker: function(element, ref) { startTorrentsByTracker: function(element, ref) {
startTorrentsByTrackerFN(element.id); startTorrentsByTrackerFN(element.id);
}, },

View file

@ -5,6 +5,7 @@
<file>private/confirmfeeddeletion.html</file> <file>private/confirmfeeddeletion.html</file>
<file>private/confirmruleclear.html</file> <file>private/confirmruleclear.html</file>
<file>private/confirmruledeletion.html</file> <file>private/confirmruledeletion.html</file>
<file>private/confirmtrackerdeletion.html</file>
<file>private/css/Core.css</file> <file>private/css/Core.css</file>
<file>private/css/dynamicTable.css</file> <file>private/css/dynamicTable.css</file>
<file>private/css/Layout.css</file> <file>private/css/Layout.css</file>