diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 290cb2168..20b2eff25 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -969,13 +969,34 @@ void TorrentsController::removeTrackersAction() { requireParams({u"hash"_s, u"urls"_s}); - const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]); - BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id); - if (!torrent) - throw APIError(APIErrorType::NotFound); + const QString hashParam = params()[u"hash"_s]; + const QStringList urlsParam = params()[u"urls"_s].split(u'|', Qt::SkipEmptyParts); - const QStringList urls = params()[u"urls"_s].split(u'|'); - torrent->removeTrackers(urls); + QStringList urls; + urls.reserve(urlsParam.size()); + for (const QString &urlStr : urlsParam) + urls << QUrl::fromPercentEncoding(urlStr.toLatin1()); + + QList 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); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + torrents.append(torrent); + } + + for (BitTorrent::Torrent *const torrent : asConst(torrents)) + torrent->removeTrackers(urls); } void TorrentsController::addPeersAction() diff --git a/src/webui/www/private/confirmtrackerdeletion.html b/src/webui/www/private/confirmtrackerdeletion.html new file mode 100644 index 000000000..cd8c467a3 --- /dev/null +++ b/src/webui/www/private/confirmtrackerdeletion.html @@ -0,0 +1,52 @@ + + + + + + QBT_TR(Remove tracker)QBT_TR[CONTEXT=confirmDeletionDlg] + + + + + + + +
+

+
+ + +
+
+ + + diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index 44f3c1af6..f8508db75 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -228,7 +228,8 @@
  • QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget] QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]
  • diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 80f2e7a55..e18830daa 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -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_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 const sortedList = []; trackerList.forEach(({ host, trackerTorrentMap }, hash) => { diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index 7df02d14c..e6ac92cfe 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -36,6 +36,7 @@ window.qBittorrent.ContextMenu ??= (() => { TorrentsTableContextMenu: TorrentsTableContextMenu, CategoriesFilterContextMenu: CategoriesFilterContextMenu, TagsFilterContextMenu: TagsFilterContextMenu, + TrackersFilterContextMenu: TrackersFilterContextMenu, SearchPluginsTableContextMenu: SearchPluginsTableContextMenu, RssFeedContextMenu: RssFeedContextMenu, 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({ Extends: ContextMenu, diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index 24ccdb332..9620ac38e 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -132,6 +132,7 @@ let deleteTorrentsByTagFN = function() {}; let startTorrentsByTrackerFN = function() {}; let stopTorrentsByTrackerFN = function() {}; let deleteTorrentsByTrackerFN = function() {}; +let deleteTrackerFN = function() {}; let copyNameFN = function() {}; let copyInfohashFN = function(policy) {}; 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() { const selectedRows = torrentsTable.selectedRowsIds(); const names = []; diff --git a/src/webui/www/private/views/filters.html b/src/webui/www/private/views/filters.html index dab2df3d9..6a97d3d93 100644 --- a/src/webui/www/private/views/filters.html +++ b/src/webui/www/private/views/filters.html @@ -153,10 +153,13 @@ } }); - const trackersFilterContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ + const trackersFilterContextMenu = new window.qBittorrent.ContextMenu.TrackersFilterContextMenu({ targets: ".trackersFilterContextMenuTarget", menu: "trackersFilterMenu", actions: { + deleteTracker: function(element, ref) { + deleteTrackerFN(element.id); + }, startTorrentsByTracker: function(element, ref) { startTorrentsByTrackerFN(element.id); }, diff --git a/src/webui/www/webui.qrc b/src/webui/www/webui.qrc index 1f0c06530..9b5a256c4 100644 --- a/src/webui/www/webui.qrc +++ b/src/webui/www/webui.qrc @@ -5,6 +5,7 @@ private/confirmfeeddeletion.html private/confirmruleclear.html private/confirmruledeletion.html + private/confirmtrackerdeletion.html private/css/Core.css private/css/dynamicTable.css private/css/Layout.css