mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-21 05:43:32 -07:00
WebUI: Implement missing tracker list features
Implemented: Tracker endpoints in the list, missing "Tracker Error" and "Unreachable" status, "Next Announce" and "Min Announce" column and double click to edit tracker url. PR #23045.
This commit is contained in:
parent
2631692cff
commit
a265ba7fd2
6 changed files with 271 additions and 39 deletions
|
@ -1,5 +1,12 @@
|
||||||
# WebAPI Changelog
|
# WebAPI Changelog
|
||||||
|
|
||||||
|
## 2.13.0
|
||||||
|
* [#23045](https://github.com/qbittorrent/qBittorrent/pull/23045)
|
||||||
|
* `torrents/trackers` returns three new fields: `next_announce`, `min_announce` and `endpoints`
|
||||||
|
* `endpoints` is an array of tracker endpoints, each with `name`, `updating`, `status`, `msg`, `bt_version`, `num_peers`, `num_peers`, `num_leeches`, `num_downloaded`, `next_announce` and `min_announce` fields
|
||||||
|
* `torrents/trackers` now returns `5` and `6` in `status` field as possible values
|
||||||
|
* `5` for `Tracker error` and `6` for `Unreachable`
|
||||||
|
|
||||||
## 2.12.1
|
## 2.12.1
|
||||||
* [#23031](https://github.com/qbittorrent/qBittorrent/pull/23031)
|
* [#23031](https://github.com/qbittorrent/qBittorrent/pull/23031)
|
||||||
* Add `torrents/setComment` endpoint with parameters `hashes` and `comment` for setting a new torrent comment
|
* Add `torrents/setComment` endpoint with parameters `hashes` and `comment` for setting a new torrent comment
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
#include "torrentscontroller.h"
|
#include "torrentscontroller.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
@ -67,14 +69,19 @@
|
||||||
|
|
||||||
// Tracker keys
|
// Tracker keys
|
||||||
const QString KEY_TRACKER_URL = u"url"_s;
|
const QString KEY_TRACKER_URL = u"url"_s;
|
||||||
|
const QString KEY_TRACKER_NAME = u"name"_s;
|
||||||
const QString KEY_TRACKER_UPDATING = u"updating"_s;
|
const QString KEY_TRACKER_UPDATING = u"updating"_s;
|
||||||
const QString KEY_TRACKER_STATUS = u"status"_s;
|
const QString KEY_TRACKER_STATUS = u"status"_s;
|
||||||
const QString KEY_TRACKER_TIER = u"tier"_s;
|
const QString KEY_TRACKER_TIER = u"tier"_s;
|
||||||
const QString KEY_TRACKER_MSG = u"msg"_s;
|
const QString KEY_TRACKER_MSG = u"msg"_s;
|
||||||
|
const QString KEY_TRACKER_BT_VERSION = u"bt_version"_s;
|
||||||
const QString KEY_TRACKER_PEERS_COUNT = u"num_peers"_s;
|
const QString KEY_TRACKER_PEERS_COUNT = u"num_peers"_s;
|
||||||
const QString KEY_TRACKER_SEEDS_COUNT = u"num_seeds"_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_LEECHES_COUNT = u"num_leeches"_s;
|
||||||
const QString KEY_TRACKER_DOWNLOADED_COUNT = u"num_downloaded"_s;
|
const QString KEY_TRACKER_DOWNLOADED_COUNT = u"num_downloaded"_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;
|
||||||
|
|
||||||
// Web seed keys
|
// Web seed keys
|
||||||
const QString KEY_WEBSEED_URL = u"url"_s;
|
const QString KEY_WEBSEED_URL = u"url"_s;
|
||||||
|
@ -269,24 +276,52 @@ namespace
|
||||||
|
|
||||||
QJsonArray getTrackers(const BitTorrent::Torrent *const torrent)
|
QJsonArray getTrackers(const BitTorrent::Torrent *const torrent)
|
||||||
{
|
{
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
const auto timepointNow = BitTorrent::AnnounceTimePoint::clock::now();
|
||||||
|
const auto toSecondsSinceEpoch = [&now, &timepointNow](const BitTorrent::AnnounceTimePoint &time) -> qint64
|
||||||
|
{
|
||||||
|
const auto timeEpoch = (now + (time - timepointNow)).time_since_epoch();
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(timeEpoch).count();
|
||||||
|
};
|
||||||
|
|
||||||
QJsonArray trackerList;
|
QJsonArray trackerList;
|
||||||
|
|
||||||
for (const BitTorrent::TrackerEntryStatus &tracker : asConst(torrent->trackers()))
|
for (const BitTorrent::TrackerEntryStatus &tracker : asConst(torrent->trackers()))
|
||||||
{
|
{
|
||||||
const bool isNotWorking = (tracker.state == BitTorrent::TrackerEndpointState::NotWorking)
|
QJsonArray endpointsList;
|
||||||
|| (tracker.state == BitTorrent::TrackerEndpointState::TrackerError)
|
|
||||||
|| (tracker.state == BitTorrent::TrackerEndpointState::Unreachable);
|
for (const BitTorrent::TrackerEndpointStatus &endpoint : tracker.endpoints)
|
||||||
|
{
|
||||||
|
endpointsList << QJsonObject
|
||||||
|
{
|
||||||
|
{KEY_TRACKER_NAME, endpoint.name},
|
||||||
|
{KEY_TRACKER_UPDATING, endpoint.isUpdating},
|
||||||
|
{KEY_TRACKER_STATUS, static_cast<int>(endpoint.state)},
|
||||||
|
{KEY_TRACKER_MSG, endpoint.message},
|
||||||
|
{KEY_TRACKER_BT_VERSION, static_cast<int>(endpoint.btVersion)},
|
||||||
|
{KEY_TRACKER_PEERS_COUNT, endpoint.numPeers},
|
||||||
|
{KEY_TRACKER_SEEDS_COUNT, endpoint.numSeeds},
|
||||||
|
{KEY_TRACKER_LEECHES_COUNT, endpoint.numLeeches},
|
||||||
|
{KEY_TRACKER_DOWNLOADED_COUNT, endpoint.numDownloaded},
|
||||||
|
{KEY_TRACKER_NEXT_ANNOUNCE, toSecondsSinceEpoch(endpoint.nextAnnounceTime)},
|
||||||
|
{KEY_TRACKER_MIN_ANNOUNCE, toSecondsSinceEpoch(endpoint.minAnnounceTime)}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
trackerList << QJsonObject
|
trackerList << QJsonObject
|
||||||
{
|
{
|
||||||
{KEY_TRACKER_URL, tracker.url},
|
{KEY_TRACKER_URL, tracker.url},
|
||||||
{KEY_TRACKER_TIER, tracker.tier},
|
{KEY_TRACKER_TIER, tracker.tier},
|
||||||
{KEY_TRACKER_UPDATING, tracker.isUpdating},
|
{KEY_TRACKER_UPDATING, tracker.isUpdating},
|
||||||
{KEY_TRACKER_STATUS, static_cast<int>((isNotWorking ? BitTorrent::TrackerEndpointState::NotWorking : tracker.state))},
|
{KEY_TRACKER_STATUS, static_cast<int>(tracker.state)},
|
||||||
{KEY_TRACKER_MSG, tracker.message},
|
{KEY_TRACKER_MSG, tracker.message},
|
||||||
{KEY_TRACKER_PEERS_COUNT, tracker.numPeers},
|
{KEY_TRACKER_PEERS_COUNT, tracker.numPeers},
|
||||||
{KEY_TRACKER_SEEDS_COUNT, tracker.numSeeds},
|
{KEY_TRACKER_SEEDS_COUNT, tracker.numSeeds},
|
||||||
{KEY_TRACKER_LEECHES_COUNT, tracker.numLeeches},
|
{KEY_TRACKER_LEECHES_COUNT, tracker.numLeeches},
|
||||||
{KEY_TRACKER_DOWNLOADED_COUNT, tracker.numDownloaded}
|
{KEY_TRACKER_DOWNLOADED_COUNT, tracker.numDownloaded},
|
||||||
|
{KEY_TRACKER_NEXT_ANNOUNCE, toSecondsSinceEpoch(tracker.nextAnnounceTime)},
|
||||||
|
{KEY_TRACKER_MIN_ANNOUNCE, toSecondsSinceEpoch(tracker.minAnnounceTime)},
|
||||||
|
{KEY_TRACKER_ENDPOINTS, endpointsList}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
#include "base/utils/version.h"
|
#include "base/utils/version.h"
|
||||||
#include "api/isessionmanager.h"
|
#include "api/isessionmanager.h"
|
||||||
|
|
||||||
inline const Utils::Version<3, 2> API_VERSION {2, 12, 1};
|
inline const Utils::Version<3, 2> API_VERSION {2, 13, 0};
|
||||||
|
|
||||||
class APIController;
|
class APIController;
|
||||||
class AuthController;
|
class AuthController;
|
||||||
|
|
|
@ -259,11 +259,11 @@
|
||||||
</ul>
|
</ul>
|
||||||
<ul id="torrentTrackersMenu" class="contextMenu">
|
<ul id="torrentTrackersMenu" class="contextMenu">
|
||||||
<li><a href="#AddTracker"><img src="images/list-add.svg" alt="QBT_TR(Add trackers...)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Add trackers...)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
|
<li><a href="#AddTracker"><img src="images/list-add.svg" alt="QBT_TR(Add trackers...)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Add trackers...)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="#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><a href="#ReannounceTrackers" id="ReannounceTrackers"><img src="images/reannounce.svg" alt="QBT_TR(Force reannounce to selected tracker(s))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>
|
<li class="separator"><a href="#ReannounceAllTrackers" id="ReannounceAllTrackers"><img src="images/reannounce.svg" alt="QBT_TR(Force reannounce to all trackers)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>
|
||||||
|
|
|
@ -2078,15 +2078,68 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TorrentTrackersTable extends DynamicTable {
|
class TorrentTrackersTable extends DynamicTable {
|
||||||
|
collapseState = new Map(); // { rowId: String, isCollapsed: bool }
|
||||||
|
|
||||||
|
isTrackerCollapsed(id) {
|
||||||
|
return this.collapseState.get(id) ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTrackerCollapsed(id) {
|
||||||
|
this.collapseState.set(id, !this.isTrackerCollapsed(id));
|
||||||
|
this.#updateTrackerRowState(id, this.isTrackerCollapsed(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateEndpointVisibility(endpoint, shouldHide) {
|
||||||
|
const span = document.getElementById(`trackersTableTrackerUrl${endpoint}`);
|
||||||
|
// span won't exist if row has been filtered out
|
||||||
|
if (span === null)
|
||||||
|
return;
|
||||||
|
const tr = span.parentElement.parentElement;
|
||||||
|
tr.classList.toggle("invisible", shouldHide);
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateTrackerCollapseIcon(tracker, isCollapsed) {
|
||||||
|
const span = document.getElementById(`trackersTableTrackerUrl${tracker}`);
|
||||||
|
// span won't exist if row has been filtered out
|
||||||
|
if (span === null)
|
||||||
|
return;
|
||||||
|
const td = span.parentElement;
|
||||||
|
|
||||||
|
// rotate the collapse icon
|
||||||
|
const collapseIcon = td.firstElementChild;
|
||||||
|
collapseIcon.classList.toggle("rotate", isCollapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateTrackerRowState(id, shouldCollapse) {
|
||||||
|
// collapsed rows will be filtered out when using virtual list
|
||||||
|
if (this.useVirtualList)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.#updateTrackerCollapseIcon(id, shouldCollapse);
|
||||||
|
|
||||||
|
for (const row of this.getRowValues()) {
|
||||||
|
const parentId = row.full_data._tracker;
|
||||||
|
if (parentId === id)
|
||||||
|
this.#updateEndpointVisibility(row.rowId, shouldCollapse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCollapseState() {
|
||||||
|
this.collapseState.clear();
|
||||||
|
}
|
||||||
|
|
||||||
initColumns() {
|
initColumns() {
|
||||||
|
this.newColumn("url", "", "QBT_TR(URL/Announce Endpoint)QBT_TR[CONTEXT=TrackerListWidget]", 250, true);
|
||||||
this.newColumn("tier", "", "QBT_TR(Tier)QBT_TR[CONTEXT=TrackerListWidget]", 35, true);
|
this.newColumn("tier", "", "QBT_TR(Tier)QBT_TR[CONTEXT=TrackerListWidget]", 35, true);
|
||||||
this.newColumn("url", "", "QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget]", 250, true);
|
this.newColumn("btVersion", "", "QBT_TR(BT Protocol)QBT_TR[CONTEXT=TrackerListWidget]", 35, true);
|
||||||
this.newColumn("status", "", "QBT_TR(Status)QBT_TR[CONTEXT=TrackerListWidget]", 125, true);
|
this.newColumn("status", "", "QBT_TR(Status)QBT_TR[CONTEXT=TrackerListWidget]", 125, true);
|
||||||
this.newColumn("peers", "", "QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget]", 75, true);
|
this.newColumn("peers", "", "QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget]", 75, true);
|
||||||
this.newColumn("seeds", "", "QBT_TR(Seeds)QBT_TR[CONTEXT=TrackerListWidget]", 75, true);
|
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("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("downloaded", "", "QBT_TR(Times Downloaded)QBT_TR[CONTEXT=TrackerListWidget]", 100, true);
|
||||||
this.newColumn("message", "", "QBT_TR(Message)QBT_TR[CONTEXT=TrackerListWidget]", 250, 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);
|
||||||
|
|
||||||
this.initColumnsFunctions();
|
this.initColumnsFunctions();
|
||||||
}
|
}
|
||||||
|
@ -2101,6 +2154,43 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
|
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.columns["url"].updateTd = (td, row) => {
|
||||||
|
const id = row.rowId;
|
||||||
|
const data = row.full_data;
|
||||||
|
|
||||||
|
let collapseIcon = td.firstElementChild;
|
||||||
|
if (collapseIcon === null) {
|
||||||
|
collapseIcon = document.createElement("img");
|
||||||
|
collapseIcon.src = "images/go-down.svg";
|
||||||
|
collapseIcon.className = "filesTableCollapseIcon";
|
||||||
|
collapseIcon.addEventListener("click", (e) => {
|
||||||
|
const id = collapseIcon.dataset.id;
|
||||||
|
this.toggleTrackerCollapsed(id);
|
||||||
|
if (this.useVirtualList)
|
||||||
|
this.rerender();
|
||||||
|
});
|
||||||
|
td.append(collapseIcon);
|
||||||
|
}
|
||||||
|
if (data._isTracker) {
|
||||||
|
collapseIcon.style.display = "inline";
|
||||||
|
collapseIcon.style.visibility = data._hasEndpoints ? "visible" : "hidden";
|
||||||
|
collapseIcon.dataset.id = id;
|
||||||
|
collapseIcon.classList.toggle("rotate", this.isTrackerCollapsed(id));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
collapseIcon.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = td.children[1];
|
||||||
|
if (span === undefined) {
|
||||||
|
span = document.createElement("span");
|
||||||
|
td.append(span);
|
||||||
|
}
|
||||||
|
span.id = `trackersTableTrackerUrl${id}`;
|
||||||
|
span.textContent = data.url;
|
||||||
|
span.style.marginLeft = data._isTracker ? "0" : "20px";
|
||||||
|
};
|
||||||
|
|
||||||
this.columns["url"].compareRows = naturalSort;
|
this.columns["url"].compareRows = naturalSort;
|
||||||
this.columns["status"].compareRows = naturalSort;
|
this.columns["status"].compareRows = naturalSort;
|
||||||
this.columns["message"].compareRows = naturalSort;
|
this.columns["message"].compareRows = naturalSort;
|
||||||
|
@ -2155,6 +2245,8 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
statusClass = "trackerUpdating";
|
statusClass = "trackerUpdating";
|
||||||
break;
|
break;
|
||||||
case "QBT_TR(Not working)QBT_TR[CONTEXT=TrackerListWidget]":
|
case "QBT_TR(Not working)QBT_TR[CONTEXT=TrackerListWidget]":
|
||||||
|
case "QBT_TR(Tracker error)QBT_TR[CONTEXT=TrackerListWidget]":
|
||||||
|
case "QBT_TR(Unreachable)QBT_TR[CONTEXT=TrackerListWidget]":
|
||||||
statusClass = "trackerNotWorking";
|
statusClass = "trackerNotWorking";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2167,6 +2259,76 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
td.textContent = status;
|
td.textContent = status;
|
||||||
td.title = status;
|
td.title = status;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const friendlyDuration = function(td, row) {
|
||||||
|
const value = this.getRowValue(row) ?? 0;
|
||||||
|
const seconds = Math.max(value - (Date.now() / 1000), 0);
|
||||||
|
const duration = window.qBittorrent.Misc.friendlyDuration(seconds, window.qBittorrent.Misc.MAX_ETA);
|
||||||
|
td.textContent = duration;
|
||||||
|
td.title = duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.columns["nextAnnounce"].updateTd = friendlyDuration;
|
||||||
|
this.columns["minAnnounce"].updateTd = friendlyDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilteredAndSortedRows() {
|
||||||
|
const trackers = [];
|
||||||
|
const trakcerEndpoints = new Map();
|
||||||
|
|
||||||
|
for (const row of this.getRowValues()) {
|
||||||
|
const tracker = row.full_data._tracker;
|
||||||
|
if (tracker) {
|
||||||
|
if (this.useVirtualList && this.isTrackerCollapsed(tracker))
|
||||||
|
continue;
|
||||||
|
const endpoints = trakcerEndpoints.get(tracker);
|
||||||
|
if (endpoints === undefined)
|
||||||
|
trakcerEndpoints.set(tracker, [row]);
|
||||||
|
else
|
||||||
|
endpoints.push(row);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
trackers.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const column = this.columns[this.sortedColumn];
|
||||||
|
const isReverseSort = this.reverseSort === "0";
|
||||||
|
const sortRows = (row1, row2) => {
|
||||||
|
const result = column.compareRows(row1, row2);
|
||||||
|
return isReverseSort ? result : -result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
for (const tracker of trackers.sort(sortRows)) {
|
||||||
|
result.push(tracker);
|
||||||
|
const endpoints = trakcerEndpoints.get(tracker.rowId) || [];
|
||||||
|
result.push(...endpoints.sort(sortRows));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTable(fullUpdate = false) {
|
||||||
|
super.updateTable(fullUpdate);
|
||||||
|
if (!this.useVirtualList) {
|
||||||
|
for (const row of this.getRowValues()) {
|
||||||
|
if (row.full_data._isTracker)
|
||||||
|
continue;
|
||||||
|
this.#updateEndpointVisibility(row.rowId, this.isTrackerCollapsed(row.full_data._tracker));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupCommonEvents() {
|
||||||
|
super.setupCommonEvents();
|
||||||
|
this.dynamicTableDiv.addEventListener("dblclick", (e) => {
|
||||||
|
const tr = e.target.closest("tr");
|
||||||
|
if (!tr || (tr.rowId.startsWith("** [") || tr.rowId.startsWith("endpoint|")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
window.qBittorrent.PropTrackers.editTracker(tr);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ window.qBittorrent ??= {};
|
||||||
window.qBittorrent.PropTrackers ??= (() => {
|
window.qBittorrent.PropTrackers ??= (() => {
|
||||||
const exports = () => {
|
const exports = () => {
|
||||||
return {
|
return {
|
||||||
|
editTracker: editTrackerFN,
|
||||||
updateData: updateData,
|
updateData: updateData,
|
||||||
clear: clear
|
clear: clear
|
||||||
};
|
};
|
||||||
|
@ -42,6 +43,25 @@ window.qBittorrent.PropTrackers ??= (() => {
|
||||||
const torrentTrackersTable = new window.qBittorrent.DynamicTable.TorrentTrackersTable();
|
const torrentTrackersTable = new window.qBittorrent.DynamicTable.TorrentTrackersTable();
|
||||||
let loadTrackersDataTimer = -1;
|
let loadTrackersDataTimer = -1;
|
||||||
|
|
||||||
|
const trackerStatusText = (tracker) => {
|
||||||
|
if (tracker.updating)
|
||||||
|
return "QBT_TR(Updating...)QBT_TR[CONTEXT=TrackerListWidget]";
|
||||||
|
switch (tracker.status) {
|
||||||
|
case 0:
|
||||||
|
return "QBT_TR(Disabled)QBT_TR[CONTEXT=TrackerListWidget]";
|
||||||
|
case 1:
|
||||||
|
return "QBT_TR(Not contacted yet)QBT_TR[CONTEXT=TrackerListWidget]";
|
||||||
|
case 2:
|
||||||
|
return "QBT_TR(Working)QBT_TR[CONTEXT=TrackerListWidget]";
|
||||||
|
case 4:
|
||||||
|
return "QBT_TR(Not working)QBT_TR[CONTEXT=TrackerListWidget]";
|
||||||
|
case 5:
|
||||||
|
return "QBT_TR(Tracker error)QBT_TR[CONTEXT=TrackerListWidget]";
|
||||||
|
case 6:
|
||||||
|
return "QBT_TR(Unreachable)QBT_TR[CONTEXT=TrackerListWidget]";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadTrackersData = () => {
|
const loadTrackersData = () => {
|
||||||
if (document.hidden)
|
if (document.hidden)
|
||||||
return;
|
return;
|
||||||
|
@ -53,11 +73,13 @@ window.qBittorrent.PropTrackers ??= (() => {
|
||||||
const new_hash = torrentsTable.getCurrentTorrentID();
|
const new_hash = torrentsTable.getCurrentTorrentID();
|
||||||
if (new_hash === "") {
|
if (new_hash === "") {
|
||||||
torrentTrackersTable.clear();
|
torrentTrackersTable.clear();
|
||||||
|
torrentTrackersTable.clearCollapseState();
|
||||||
clearTimeout(loadTrackersDataTimer);
|
clearTimeout(loadTrackersDataTimer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (new_hash !== current_hash) {
|
if (new_hash !== current_hash) {
|
||||||
torrentTrackersTable.clear();
|
torrentTrackersTable.clear();
|
||||||
|
torrentTrackersTable.clearCollapseState();
|
||||||
current_hash = new_hash;
|
current_hash = new_hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,43 +101,50 @@ window.qBittorrent.PropTrackers ??= (() => {
|
||||||
if (trackers) {
|
if (trackers) {
|
||||||
torrentTrackersTable.clear();
|
torrentTrackersTable.clear();
|
||||||
|
|
||||||
|
const notApplicable = "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]";
|
||||||
trackers.each((tracker) => {
|
trackers.each((tracker) => {
|
||||||
let status;
|
|
||||||
|
|
||||||
if (tracker.updating) {
|
|
||||||
status = "QBT_TR(Updating...)QBT_TR[CONTEXT=TrackerListWidget]";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
switch (tracker.status) {
|
|
||||||
case 0:
|
|
||||||
status = "QBT_TR(Disabled)QBT_TR[CONTEXT=TrackerListWidget]";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
status = "QBT_TR(Not contacted yet)QBT_TR[CONTEXT=TrackerListWidget]";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
status = "QBT_TR(Working)QBT_TR[CONTEXT=TrackerListWidget]";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
status = "QBT_TR(Not working)QBT_TR[CONTEXT=TrackerListWidget]";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const row = {
|
const row = {
|
||||||
rowId: tracker.url,
|
rowId: tracker.url,
|
||||||
tier: (tracker.tier >= 0) ? tracker.tier : "",
|
tier: (tracker.tier >= 0) ? tracker.tier : "",
|
||||||
|
btVersion: "",
|
||||||
url: tracker.url,
|
url: tracker.url,
|
||||||
status: status,
|
status: trackerStatusText(tracker),
|
||||||
peers: (tracker.num_peers >= 0) ? tracker.num_peers : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]",
|
peers: (tracker.num_peers >= 0) ? tracker.num_peers : notApplicable,
|
||||||
seeds: (tracker.num_seeds >= 0) ? tracker.num_seeds : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]",
|
seeds: (tracker.num_seeds >= 0) ? tracker.num_seeds : notApplicable,
|
||||||
leeches: (tracker.num_leeches >= 0) ? tracker.num_leeches : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]",
|
leeches: (tracker.num_leeches >= 0) ? tracker.num_leeches : notApplicable,
|
||||||
downloaded: (tracker.num_downloaded >= 0) ? tracker.num_downloaded : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]",
|
downloaded: (tracker.num_downloaded >= 0) ? tracker.num_downloaded : notApplicable,
|
||||||
message: tracker.msg,
|
message: tracker.msg,
|
||||||
|
nextAnnounce: tracker.next_announce,
|
||||||
|
minAnnounce: tracker.min_announce,
|
||||||
|
_isTracker: true,
|
||||||
|
_hasEndpoints: tracker.endpoints && (tracker.endpoints.length > 0),
|
||||||
_sortable: !tracker.url.startsWith("** [")
|
_sortable: !tracker.url.startsWith("** [")
|
||||||
};
|
};
|
||||||
|
|
||||||
torrentTrackersTable.updateRowData(row);
|
torrentTrackersTable.updateRowData(row);
|
||||||
|
|
||||||
|
if (tracker.endpoints !== undefined) {
|
||||||
|
for (const endpoint of tracker.endpoints) {
|
||||||
|
const row = {
|
||||||
|
rowId: `endpoint|${tracker.url}|${endpoint.name}|${endpoint.bt_version}`,
|
||||||
|
tier: "",
|
||||||
|
btVersion: `v${endpoint.bt_version}`,
|
||||||
|
url: endpoint.name,
|
||||||
|
status: trackerStatusText(endpoint),
|
||||||
|
peers: (endpoint.num_peers >= 0) ? endpoint.num_peers : notApplicable,
|
||||||
|
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,
|
||||||
|
message: endpoint.msg,
|
||||||
|
nextAnnounce: endpoint.next_announce,
|
||||||
|
minAnnounce: endpoint.min_announce,
|
||||||
|
_isTracker: false,
|
||||||
|
_tracker: tracker.url,
|
||||||
|
_sortable: true,
|
||||||
|
};
|
||||||
|
torrentTrackersTable.updateRowData(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
torrentTrackersTable.updateTable(false);
|
torrentTrackersTable.updateTable(false);
|
||||||
|
@ -163,7 +192,7 @@ window.qBittorrent.PropTrackers ??= (() => {
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
const selectedTrackers = torrentTrackersTable.selectedRowsIds();
|
const selectedTrackers = torrentTrackersTable.selectedRowsIds();
|
||||||
const containsStaticTracker = selectedTrackers.some((tracker) => {
|
const containsStaticTracker = selectedTrackers.some((tracker) => {
|
||||||
return tracker.startsWith("** [");
|
return tracker.startsWith("** [") || tracker.startsWith("endpoint|");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (containsStaticTracker || (selectedTrackers.length === 0)) {
|
if (containsStaticTracker || (selectedTrackers.length === 0)) {
|
||||||
|
@ -171,7 +200,6 @@ window.qBittorrent.PropTrackers ??= (() => {
|
||||||
this.hideItem("RemoveTracker");
|
this.hideItem("RemoveTracker");
|
||||||
this.hideItem("CopyTrackerUrl");
|
this.hideItem("CopyTrackerUrl");
|
||||||
this.hideItem("ReannounceTrackers");
|
this.hideItem("ReannounceTrackers");
|
||||||
this.hideItem("ReannounceAllTrackers");
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (selectedTrackers.length === 1)
|
if (selectedTrackers.length === 1)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue