From cd2c448e6d01a26f20e0a411ad200e489199d143 Mon Sep 17 00:00:00 2001 From: sledgehammer999 Date: Sun, 16 Sep 2018 09:37:32 +0300 Subject: [PATCH 01/10] Bump Web API version --- src/webui/webapplication.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index cfe09bcbf..3ca0aaf90 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -43,9 +43,9 @@ #include "base/utils/net.h" #include "base/utils/version.h" -constexpr Utils::Version API_VERSION {2, 1, 1}; -constexpr int COMPAT_API_VERSION = 22; -constexpr int COMPAT_API_VERSION_MIN = 21; +constexpr Utils::Version API_VERSION {2, 2, 0}; +constexpr int COMPAT_API_VERSION = 23; +constexpr int COMPAT_API_VERSION_MIN = 23; class APIController; class WebApplication; From b4817875742b718eb4b4ec296fd12a2caf51f72e Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Mon, 17 Sep 2018 00:16:28 -0400 Subject: [PATCH 02/10] Rename Tracker List columns "Received" renamed to "Peers", "Peers" renamed to "Leeches". --- src/gui/properties/trackerlistwidget.cpp | 42 ++++++++++++------------ src/gui/properties/trackerlistwidget.h | 4 +-- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/gui/properties/trackerlistwidget.cpp b/src/gui/properties/trackerlistwidget.cpp index f2c85fbda..b196b12be 100644 --- a/src/gui/properties/trackerlistwidget.cpp +++ b/src/gui/properties/trackerlistwidget.cpp @@ -97,23 +97,23 @@ TrackerListWidget::TrackerListWidget(PropertiesWidget *properties) insertTopLevelItem(2, m_LSDItem); setRowColor(2, QColor("grey")); // Set static items alignment - m_DHTItem->setTextAlignment(COL_RECEIVED, (Qt::AlignRight | Qt::AlignVCenter)); - m_PEXItem->setTextAlignment(COL_RECEIVED, (Qt::AlignRight | Qt::AlignVCenter)); - m_LSDItem->setTextAlignment(COL_RECEIVED, (Qt::AlignRight | Qt::AlignVCenter)); - m_DHTItem->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); - m_PEXItem->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); - m_LSDItem->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); m_DHTItem->setTextAlignment(COL_PEERS, (Qt::AlignRight | Qt::AlignVCenter)); m_PEXItem->setTextAlignment(COL_PEERS, (Qt::AlignRight | Qt::AlignVCenter)); m_LSDItem->setTextAlignment(COL_PEERS, (Qt::AlignRight | Qt::AlignVCenter)); + m_DHTItem->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); + m_PEXItem->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); + m_LSDItem->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); + m_DHTItem->setTextAlignment(COL_LEECHES, (Qt::AlignRight | Qt::AlignVCenter)); + m_PEXItem->setTextAlignment(COL_LEECHES, (Qt::AlignRight | Qt::AlignVCenter)); + m_LSDItem->setTextAlignment(COL_LEECHES, (Qt::AlignRight | Qt::AlignVCenter)); m_DHTItem->setTextAlignment(COL_DOWNLOADED, (Qt::AlignRight | Qt::AlignVCenter)); m_PEXItem->setTextAlignment(COL_DOWNLOADED, (Qt::AlignRight | Qt::AlignVCenter)); m_LSDItem->setTextAlignment(COL_DOWNLOADED, (Qt::AlignRight | Qt::AlignVCenter)); // Set header alignment headerItem()->setTextAlignment(COL_TIER, (Qt::AlignRight | Qt::AlignVCenter)); - headerItem()->setTextAlignment(COL_RECEIVED, (Qt::AlignRight | Qt::AlignVCenter)); - headerItem()->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); headerItem()->setTextAlignment(COL_PEERS, (Qt::AlignRight | Qt::AlignVCenter)); + headerItem()->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); + headerItem()->setTextAlignment(COL_LEECHES, (Qt::AlignRight | Qt::AlignVCenter)); headerItem()->setTextAlignment(COL_DOWNLOADED, (Qt::AlignRight | Qt::AlignVCenter)); // Set hotkeys m_editHotkey = new QShortcut(Qt::Key_F2, this, nullptr, nullptr, Qt::WidgetShortcut); @@ -245,15 +245,15 @@ void TrackerListWidget::clear() m_trackerItems.clear(); m_DHTItem->setText(COL_STATUS, ""); m_DHTItem->setText(COL_SEEDS, ""); - m_DHTItem->setText(COL_PEERS, ""); + m_DHTItem->setText(COL_LEECHES, ""); m_DHTItem->setText(COL_MSG, ""); m_PEXItem->setText(COL_STATUS, ""); m_PEXItem->setText(COL_SEEDS, ""); - m_PEXItem->setText(COL_PEERS, ""); + m_PEXItem->setText(COL_LEECHES, ""); m_PEXItem->setText(COL_MSG, ""); m_LSDItem->setText(COL_STATUS, ""); m_LSDItem->setText(COL_SEEDS, ""); - m_LSDItem->setText(COL_PEERS, ""); + m_LSDItem->setText(COL_LEECHES, ""); m_LSDItem->setText(COL_MSG, ""); } @@ -314,11 +314,11 @@ void TrackerListWidget::loadStickyItems(BitTorrent::TorrentHandle *const torrent } m_DHTItem->setText(COL_SEEDS, QString::number(seedsDHT)); - m_DHTItem->setText(COL_PEERS, QString::number(peersDHT)); + m_DHTItem->setText(COL_LEECHES, QString::number(peersDHT)); m_PEXItem->setText(COL_SEEDS, QString::number(seedsPeX)); - m_PEXItem->setText(COL_PEERS, QString::number(peersPeX)); + m_PEXItem->setText(COL_LEECHES, QString::number(peersPeX)); m_LSDItem->setText(COL_SEEDS, QString::number(seedsLSD)); - m_LSDItem->setText(COL_PEERS, QString::number(peersLSD)); + m_LSDItem->setText(COL_LEECHES, QString::number(peersLSD)); } void TrackerListWidget::loadTrackers() @@ -366,21 +366,21 @@ void TrackerListWidget::loadTrackers() break; } - item->setText(COL_RECEIVED, QString::number(data.numPeers)); + item->setText(COL_PEERS, QString::number(data.numPeers)); #if LIBTORRENT_VERSION_NUM >= 10000 item->setText(COL_SEEDS, (entry.nativeEntry().scrape_complete > -1) ? QString::number(entry.nativeEntry().scrape_complete) : tr("N/A")); - item->setText(COL_PEERS, (entry.nativeEntry().scrape_incomplete > -1) ? QString::number(entry.nativeEntry().scrape_incomplete) : tr("N/A")); + item->setText(COL_LEECHES, (entry.nativeEntry().scrape_incomplete > -1) ? QString::number(entry.nativeEntry().scrape_incomplete) : tr("N/A")); item->setText(COL_DOWNLOADED, (entry.nativeEntry().scrape_downloaded > -1) ? QString::number(entry.nativeEntry().scrape_downloaded) : tr("N/A")); #else item->setText(COL_SEEDS, tr("N/A")); - item->setText(COL_PEERS, tr("N/A")); + item->setText(COL_LEECHES, tr("N/A")); item->setText(COL_DOWNLOADED, tr("N/A")); #endif item->setTextAlignment(COL_TIER, (Qt::AlignRight | Qt::AlignVCenter)); - item->setTextAlignment(COL_RECEIVED, (Qt::AlignRight | Qt::AlignVCenter)); - item->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); item->setTextAlignment(COL_PEERS, (Qt::AlignRight | Qt::AlignVCenter)); + item->setTextAlignment(COL_SEEDS, (Qt::AlignRight | Qt::AlignVCenter)); + item->setTextAlignment(COL_LEECHES, (Qt::AlignRight | Qt::AlignVCenter)); item->setTextAlignment(COL_DOWNLOADED, (Qt::AlignRight | Qt::AlignVCenter)); } // Remove old trackers @@ -592,9 +592,9 @@ QStringList TrackerListWidget::headerLabels() "#" , tr("URL") , tr("Status") - , tr("Received") - , tr("Seeds") , tr("Peers") + , tr("Seeds") + , tr("Leeches") , tr("Downloaded") , tr("Message") }; diff --git a/src/gui/properties/trackerlistwidget.h b/src/gui/properties/trackerlistwidget.h index 9d0ae9da1..f8b28c547 100644 --- a/src/gui/properties/trackerlistwidget.h +++ b/src/gui/properties/trackerlistwidget.h @@ -54,9 +54,9 @@ public: COL_TIER, COL_URL, COL_STATUS, - COL_RECEIVED, - COL_SEEDS, COL_PEERS, + COL_SEEDS, + COL_LEECHES, COL_DOWNLOADED, COL_MSG, From c89e9d43546bbe448fd5bfdddf5aab404bfb3970 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Sun, 2 Sep 2018 02:55:12 -0400 Subject: [PATCH 03/10] Reorder and rename Tracker list context menu option Adds an ellipses to indicate that the Edit option opens a dialog. Also moves Edit to top of the list to convey action's prominence. --- src/gui/properties/trackerlistwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/properties/trackerlistwidget.cpp b/src/gui/properties/trackerlistwidget.cpp index b196b12be..c4e6488a2 100644 --- a/src/gui/properties/trackerlistwidget.cpp +++ b/src/gui/properties/trackerlistwidget.cpp @@ -534,9 +534,9 @@ void TrackerListWidget::showTrackerListMenu(QPoint) QAction *delAct = nullptr; QAction *editAct = nullptr; if (!getSelectedTrackerItems().isEmpty()) { + editAct = menu.addAction(GuiIconProvider::instance()->getIcon("edit-rename"),tr("Edit tracker URL...")); delAct = menu.addAction(GuiIconProvider::instance()->getIcon("list-remove"), tr("Remove tracker")); copyAct = menu.addAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Copy tracker URL")); - editAct = menu.addAction(GuiIconProvider::instance()->getIcon("edit-rename"),tr("Edit selected tracker URL")); } QAction *reannounceSelAct = nullptr; QAction *reannounceAllAct = nullptr; From dd790d94c9d118417f93d850f0f79602f342d465 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Mon, 17 Sep 2018 00:09:58 -0400 Subject: [PATCH 04/10] Use const where appropriate --- src/webui/api/torrentscontroller.cpp | 38 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index c0482b411..bcd455f47 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -114,12 +114,12 @@ namespace void applyToTorrents(const QStringList &hashes, const std::function &func) { if ((hashes.size() == 1) && (hashes[0] == QLatin1String("all"))) { - for (BitTorrent::TorrentHandle *torrent : asConst(BitTorrent::Session::instance()->torrents())) + for (BitTorrent::TorrentHandle *const torrent : asConst(BitTorrent::Session::instance()->torrents())) func(torrent); } else { for (const QString &hash : hashes) { - BitTorrent::TorrentHandle *torrent = BitTorrent::Session::instance()->findTorrent(hash); + BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); if (torrent) func(torrent); } @@ -548,7 +548,7 @@ void TorrentsController::pauseAction() checkParams({"hashes"}); const QStringList hashes = params()["hashes"].split('|'); - applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->pause(); }); + applyToTorrents(hashes, [](BitTorrent::TorrentHandle *const torrent) { torrent->pause(); }); } void TorrentsController::resumeAction() @@ -556,7 +556,7 @@ void TorrentsController::resumeAction() checkParams({"hashes"}); const QStringList hashes = params()["hashes"].split('|'); - applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->resume(); }); + applyToTorrents(hashes, [](BitTorrent::TorrentHandle *const torrent) { torrent->resume(); }); } void TorrentsController::filePrioAction() @@ -606,7 +606,7 @@ void TorrentsController::uploadLimitAction() QVariantMap map; for (const QString &hash : hashes) { int limit = -1; - BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + const BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); if (torrent) limit = torrent->uploadLimit(); map[hash] = limit; @@ -623,7 +623,7 @@ void TorrentsController::downloadLimitAction() QVariantMap map; for (const QString &hash : hashes) { int limit = -1; - BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + const BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); if (torrent) limit = torrent->downloadLimit(); map[hash] = limit; @@ -641,7 +641,7 @@ void TorrentsController::setUploadLimitAction() limit = -1; const QStringList hashes {params()["hashes"].split('|')}; - applyToTorrents(hashes, [limit](BitTorrent::TorrentHandle *torrent) { torrent->setUploadLimit(limit); }); + applyToTorrents(hashes, [limit](BitTorrent::TorrentHandle *const torrent) { torrent->setUploadLimit(limit); }); } void TorrentsController::setDownloadLimitAction() @@ -653,7 +653,7 @@ void TorrentsController::setDownloadLimitAction() limit = -1; const QStringList hashes {params()["hashes"].split('|')}; - applyToTorrents(hashes, [limit](BitTorrent::TorrentHandle *torrent) { torrent->setDownloadLimit(limit); }); + applyToTorrents(hashes, [limit](BitTorrent::TorrentHandle *const torrent) { torrent->setDownloadLimit(limit); }); } void TorrentsController::setShareLimitsAction() @@ -664,7 +664,7 @@ void TorrentsController::setShareLimitsAction() const qlonglong seedingTimeLimit = params()["seedingTimeLimit"].toLongLong(); const QStringList hashes = params()["hashes"].split('|'); - applyToTorrents(hashes, [ratioLimit, seedingTimeLimit](BitTorrent::TorrentHandle *torrent) + applyToTorrents(hashes, [ratioLimit, seedingTimeLimit](BitTorrent::TorrentHandle *const torrent) { torrent->setRatioLimit(ratioLimit); torrent->setSeedingTimeLimit(seedingTimeLimit); @@ -676,7 +676,7 @@ void TorrentsController::toggleSequentialDownloadAction() checkParams({"hashes"}); const QStringList hashes {params()["hashes"].split('|')}; - applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->toggleSequentialDownload(); }); + applyToTorrents(hashes, [](BitTorrent::TorrentHandle *const torrent) { torrent->toggleSequentialDownload(); }); } void TorrentsController::toggleFirstLastPiecePrioAction() @@ -684,7 +684,7 @@ void TorrentsController::toggleFirstLastPiecePrioAction() checkParams({"hashes"}); const QStringList hashes {params()["hashes"].split('|')}; - applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->toggleFirstLastPiecePriority(); }); + applyToTorrents(hashes, [](BitTorrent::TorrentHandle *const torrent) { torrent->toggleFirstLastPiecePriority(); }); } void TorrentsController::setSuperSeedingAction() @@ -693,7 +693,7 @@ void TorrentsController::setSuperSeedingAction() const bool value {parseBool(params()["value"], false)}; const QStringList hashes {params()["hashes"].split('|')}; - applyToTorrents(hashes, [value](BitTorrent::TorrentHandle *torrent) { torrent->setSuperSeeding(value); }); + applyToTorrents(hashes, [value](BitTorrent::TorrentHandle *const torrent) { torrent->setSuperSeeding(value); }); } void TorrentsController::setForceStartAction() @@ -702,7 +702,7 @@ void TorrentsController::setForceStartAction() const bool value {parseBool(params()["value"], false)}; const QStringList hashes {params()["hashes"].split('|')}; - applyToTorrents(hashes, [value](BitTorrent::TorrentHandle *torrent) { torrent->resume(value); }); + applyToTorrents(hashes, [value](BitTorrent::TorrentHandle *const torrent) { torrent->resume(value); }); } void TorrentsController::deleteAction() @@ -711,7 +711,7 @@ void TorrentsController::deleteAction() const QStringList hashes {params()["hashes"].split('|')}; const bool deleteFiles {parseBool(params()["deleteFiles"], false)}; - applyToTorrents(hashes, [deleteFiles](BitTorrent::TorrentHandle *torrent) + applyToTorrents(hashes, [deleteFiles](BitTorrent::TorrentHandle *const torrent) { BitTorrent::Session::instance()->deleteTorrent(torrent->hash(), deleteFiles); }); @@ -779,7 +779,7 @@ void TorrentsController::setLocationAction() if (!QFileInfo(newLocation).isWritable()) throw APIError(APIErrorType::AccessDenied, tr("Cannot write to directory")); - applyToTorrents(hashes, [newLocation](BitTorrent::TorrentHandle *torrent) + applyToTorrents(hashes, [newLocation](BitTorrent::TorrentHandle *const torrent) { LogMsg(tr("WebUI Set location: moving \"%1\", from \"%2\" to \"%3\"") .arg(torrent->name(), Utils::Fs::toNativePath(torrent->savePath()), Utils::Fs::toNativePath(newLocation))); @@ -812,7 +812,7 @@ void TorrentsController::setAutoManagementAction() const QStringList hashes {params()["hashes"].split('|')}; const bool isEnabled {parseBool(params()["enable"], false)}; - applyToTorrents(hashes, [isEnabled](BitTorrent::TorrentHandle *torrent) + applyToTorrents(hashes, [isEnabled](BitTorrent::TorrentHandle *const torrent) { torrent->setAutoTMMEnabled(isEnabled); }); @@ -823,7 +823,7 @@ void TorrentsController::recheckAction() checkParams({"hashes"}); const QStringList hashes {params()["hashes"].split('|')}; - applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->forceRecheck(); }); + applyToTorrents(hashes, [](BitTorrent::TorrentHandle *const torrent) { torrent->forceRecheck(); }); } void TorrentsController::reannounceAction() @@ -831,7 +831,7 @@ void TorrentsController::reannounceAction() checkParams({"hashes"}); const QStringList hashes {params()["hashes"].split('|')}; - applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->forceReannounce(); }); + applyToTorrents(hashes, [](BitTorrent::TorrentHandle *const torrent) { torrent->forceReannounce(); }); } void TorrentsController::setCategoryAction() @@ -841,7 +841,7 @@ void TorrentsController::setCategoryAction() const QStringList hashes {params()["hashes"].split('|')}; const QString category {params()["category"].trimmed()}; - applyToTorrents(hashes, [category](BitTorrent::TorrentHandle *torrent) + applyToTorrents(hashes, [category](BitTorrent::TorrentHandle *const torrent) { if (!torrent->setCategory(category)) throw APIError(APIErrorType::Conflict, tr("Incorrect category name")); From b8e4c6b0be3692409ed0b6029237f7eaa7b2ab61 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Mon, 20 Aug 2018 01:26:57 -0400 Subject: [PATCH 05/10] Add additional Tracker columns to WebUI --- src/webui/api/torrentscontroller.cpp | 19 ++++++++++++++++--- src/webui/www/private/properties_content.html | 8 ++++++-- .../www/private/scripts/prop-trackers.js | 19 ++++++++++++------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index bcd455f47..f7f3d6994 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -56,8 +56,12 @@ // Tracker keys const char KEY_TRACKER_URL[] = "url"; const char KEY_TRACKER_STATUS[] = "status"; +const char KEY_TRACKER_TIER[] = "tier"; const char KEY_TRACKER_MSG[] = "msg"; -const char KEY_TRACKER_PEERS[] = "num_peers"; +const char KEY_TRACKER_PEERS_COUNT[] = "num_peers"; +const char KEY_TRACKER_SEEDS_COUNT[] = "num_seeds"; +const char KEY_TRACKER_LEECHES_COUNT[] = "num_leeches"; +const char KEY_TRACKER_DOWNLOADED_COUNT[] = "num_downloaded"; // Web seed keys const char KEY_WEBSEED_URL[] = "url"; @@ -295,7 +299,11 @@ void TorrentsController::propertiesAction() // The dictionary keys are: // - "url": Tracker URL // - "status": Tracker status -// - "num_peers": Tracker peer count +// - "tier": Tracker tier +// - "num_peers": Number of peers this torrent is currently connected to +// - "num_seeds": Number of peers that have the whole file +// - "num_leeches": Number of peers that are still downloading +// - "num_downloaded": Tracker downloaded count // - "msg": Tracker message (last) void TorrentsController::trackersAction() { @@ -323,10 +331,15 @@ void TorrentsController::trackersAction() case BitTorrent::TrackerEntry::NotWorking: status = tr("Not working"); break; } + trackerDict[KEY_TRACKER_TIER] = tracker.tier(); trackerDict[KEY_TRACKER_STATUS] = status; - trackerDict[KEY_TRACKER_PEERS] = data.numPeers; + trackerDict[KEY_TRACKER_PEERS_COUNT] = data.numPeers; trackerDict[KEY_TRACKER_MSG] = data.lastMessage.trimmed(); + trackerDict[KEY_TRACKER_SEEDS_COUNT] = tracker.nativeEntry().scrape_complete; + trackerDict[KEY_TRACKER_LEECHES_COUNT] = tracker.nativeEntry().scrape_incomplete; + trackerDict[KEY_TRACKER_DOWNLOADED_COUNT] = tracker.nativeEntry().scrape_downloaded; + trackerList.append(trackerDict); } diff --git a/src/webui/www/private/properties_content.html b/src/webui/www/private/properties_content.html index ecd56f4d8..ad491994e 100644 --- a/src/webui/www/private/properties_content.html +++ b/src/webui/www/private/properties_content.html @@ -84,10 +84,14 @@ + - - + + + + + diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index ba1708468..d745d4d42 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -32,7 +32,7 @@ var trackersDynTable = new Class({ }, insertRow: function(row) { - var url = row[0]; + var url = row[1]; if (this.rows.has(url)) { var tableRow = this.rows.get(url); this.updateRow(tableRow, row); @@ -85,12 +85,17 @@ var loadTrackersData = function() { if (trackers) { // Update Trackers data trackers.each(function(tracker) { - var row = []; - row.length = 4; - row[0] = escapeHtml(tracker.url); - row[1] = tracker.status; - row[2] = tracker.num_peers; - row[3] = escapeHtml(tracker.msg); + var row = [ + tracker.tier, + escapeHtml(tracker.url), + tracker.status, + tracker.num_peers, + (tracker.num_seeds >= 0) ? tracker.num_seeds : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", + (tracker.num_leeches >= 0) ? tracker.num_leeches : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", + (tracker.num_downloaded >= 0) ? tracker.num_downloaded : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", + escapeHtml(tracker.msg) + ]; + tTable.insertRow(row); }); } From 4947b0a44f1708ec6bf0a7a2b14a8fb61038a3d3 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Mon, 20 Aug 2018 01:27:25 -0400 Subject: [PATCH 06/10] Add DHT, PeX, and LSD to WebUI Tracker list --- src/webui/api/torrentscontroller.cpp | 70 +++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index f7f3d6994..696121dbf 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -40,6 +40,7 @@ #include #include "base/bittorrent/filepriority.h" +#include "base/bittorrent/peerinfo.h" #include "base/bittorrent/session.h" #include "base/bittorrent/torrenthandle.h" #include "base/bittorrent/torrentinfo.h" @@ -129,6 +130,72 @@ namespace } } } + + QVariantList getStickyTrackers(const BitTorrent::TorrentHandle *const torrent) + { + uint seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, leechesDHT = 0, leechesPeX = 0, leechesLSD = 0; + for (const BitTorrent::PeerInfo &peer : torrent->peers()) { + if (peer.isConnecting()) continue; + + if (peer.isSeed()) { + if (peer.fromDHT()) + ++seedsDHT; + if (peer.fromPeX()) + ++seedsPeX; + if (peer.fromLSD()) + ++seedsLSD; + } + else { + if (peer.fromDHT()) + ++leechesDHT; + if (peer.fromPeX()) + ++leechesPeX; + if (peer.fromLSD()) + ++leechesLSD; + } + } + + const int working = static_cast(BitTorrent::TrackerEntry::Working); + const int disabled = 0; + + const QString privateMsg {QCoreApplication::translate("TrackerListWidget", "This torrent is private")}; + const bool isTorrentPrivate = torrent->isPrivate(); + + const QVariantMap dht { + {KEY_TRACKER_URL, "** [DHT] **"}, + {KEY_TRACKER_TIER, ""}, + {KEY_TRACKER_MSG, (isTorrentPrivate ? privateMsg : "")}, + {KEY_TRACKER_STATUS, ((BitTorrent::Session::instance()->isDHTEnabled() && !isTorrentPrivate) ? working : disabled)}, + {KEY_TRACKER_PEERS_COUNT, 0}, + {KEY_TRACKER_DOWNLOADED_COUNT, 0}, + {KEY_TRACKER_SEEDS_COUNT, seedsDHT}, + {KEY_TRACKER_LEECHES_COUNT, leechesDHT} + }; + + const QVariantMap pex { + {KEY_TRACKER_URL, "** [PeX] **"}, + {KEY_TRACKER_TIER, ""}, + {KEY_TRACKER_MSG, (isTorrentPrivate ? privateMsg : "")}, + {KEY_TRACKER_STATUS, ((BitTorrent::Session::instance()->isPeXEnabled() && !isTorrentPrivate) ? working : disabled)}, + {KEY_TRACKER_PEERS_COUNT, 0}, + {KEY_TRACKER_DOWNLOADED_COUNT, 0}, + {KEY_TRACKER_SEEDS_COUNT, seedsPeX}, + {KEY_TRACKER_LEECHES_COUNT, leechesPeX} + }; + + const QVariantMap lsd { + {KEY_TRACKER_URL, "** [LSD] **"}, + {KEY_TRACKER_TIER, ""}, + {KEY_TRACKER_MSG, (isTorrentPrivate ? privateMsg : "")}, + {KEY_TRACKER_STATUS, ((BitTorrent::Session::instance()->isLSDEnabled() && !isTorrentPrivate) ? working : disabled)}, + {KEY_TRACKER_PEERS_COUNT, 0}, + {KEY_TRACKER_DOWNLOADED_COUNT, 0}, + {KEY_TRACKER_SEEDS_COUNT, seedsLSD}, + {KEY_TRACKER_LEECHES_COUNT, leechesLSD} + }; + + return QVariantList {dht, pex, lsd}; + } } // Returns all the torrents in JSON format. @@ -310,11 +377,12 @@ void TorrentsController::trackersAction() checkParams({"hash"}); const QString hash {params()["hash"]}; - QVariantList trackerList; BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); if (!torrent) throw APIError(APIErrorType::NotFound); + QVariantList trackerList = getStickyTrackers(torrent); + QHash trackersData = torrent->trackerInfos(); for (const BitTorrent::TrackerEntry &tracker : asConst(torrent->trackers())) { QVariantMap trackerDict; From 33351e3d8d442c02b7db346146bf7ab9283cdbd2 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Sun, 21 Oct 2018 19:42:56 -0400 Subject: [PATCH 07/10] Add WebUI Trackers context menu --- src/webui/api/torrentscontroller.cpp | 83 +++++++++++-- src/webui/api/torrentscontroller.h | 2 + src/webui/www/private/addtrackers.html | 11 ++ src/webui/www/private/css/style.css | 6 - src/webui/www/private/edittracker.html | 65 ++++++++++ src/webui/www/private/index.html | 6 + src/webui/www/private/properties_content.html | 2 +- .../www/private/scripts/prop-trackers.js | 115 ++++++++++++++++-- src/webui/www/webui.qrc | 1 + 9 files changed, 263 insertions(+), 28 deletions(-) create mode 100644 src/webui/www/private/edittracker.html diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 696121dbf..6c47499ab 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -377,7 +377,7 @@ void TorrentsController::trackersAction() checkParams({"hash"}); const QString hash {params()["hash"]}; - BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + const BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); if (!torrent) throw APIError(APIErrorType::NotFound); @@ -613,15 +613,82 @@ void TorrentsController::addTrackersAction() const QString hash = params()["hash"]; BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); - if (torrent) { - QList trackers; - for (QString url : asConst(params()["urls"].split('\n'))) { - url = url.trimmed(); - if (!url.isEmpty()) - trackers << url; + if (!torrent) + throw APIError(APIErrorType::NotFound); + + QList trackers; + for (const QString &urlStr : asConst(params()["urls"].split('\n'))) { + const QUrl url {urlStr.trimmed()}; + if (url.isValid()) + trackers << url.toString(); + } + torrent->addTrackers(trackers); +} + +void TorrentsController::editTrackerAction() +{ + checkParams({"hash", "origUrl", "newUrl"}); + + const QString hash = params()["hash"]; + const QString origUrl = params()["origUrl"]; + const QString newUrl = params()["newUrl"]; + + BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + const QUrl origTrackerUrl(origUrl); + const QUrl newTrackerUrl(newUrl); + if (origTrackerUrl == newTrackerUrl) + return; + if (!newTrackerUrl.isValid()) + throw APIError(APIErrorType::BadParams, "New tracker URL is invalid"); + + QList trackers = torrent->trackers(); + bool match = false; + for (BitTorrent::TrackerEntry &tracker : trackers) { + const QUrl trackerUrl(tracker.url()); + if (trackerUrl == newTrackerUrl) + throw APIError(APIErrorType::Conflict, "New tracker URL already exists"); + if (trackerUrl == origTrackerUrl) { + match = true; + BitTorrent::TrackerEntry newTracker(newTrackerUrl.toString()); + newTracker.setTier(tracker.tier()); + tracker = newTracker; } - torrent->addTrackers(trackers); } + if (!match) + throw APIError(APIErrorType::Conflict, "Tracker not found"); + + torrent->replaceTrackers(trackers); + if (!torrent->isPaused()) + torrent->forceReannounce(); +} + +void TorrentsController::removeTrackersAction() +{ + checkParams({"hash", "urls"}); + + const QString hash = params()["hash"]; + const QStringList urls = params()["urls"].split('|'); + + BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + QList remainingTrackers; + const QList trackers = torrent->trackers(); + for (const BitTorrent::TrackerEntry &entry : trackers) { + if (!urls.contains(entry.url())) + remainingTrackers.push_back(entry); + } + + if (remainingTrackers.size() == trackers.size()) + throw APIError(APIErrorType::Conflict, "No trackers were removed"); + + torrent->replaceTrackers(remainingTrackers); + if (!torrent->isPaused()) + torrent->forceReannounce(); } void TorrentsController::pauseAction() diff --git a/src/webui/api/torrentscontroller.h b/src/webui/api/torrentscontroller.h index bf4caf8f5..2f467a22e 100644 --- a/src/webui/api/torrentscontroller.h +++ b/src/webui/api/torrentscontroller.h @@ -59,6 +59,8 @@ private slots: void addAction(); void deleteAction(); void addTrackersAction(); + void editTrackerAction(); + void removeTrackersAction(); void filePrioAction(); void uploadLimitAction(); void downloadLimitAction(); diff --git a/src/webui/www/private/addtrackers.html b/src/webui/www/private/addtrackers.html index 52f26e0b1..88322f492 100644 --- a/src/webui/www/private/addtrackers.html +++ b/src/webui/www/private/addtrackers.html @@ -9,6 +9,17 @@ + + + + + +
+
+

QBT_TR(Tracker URL:)QBT_TR[CONTEXT=TrackerListWidget]

+
+ +
+
+ +
+ + + diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index a04261257..b8d5c525b 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -158,6 +158,12 @@
  • QBT_TR(Pause torrents)QBT_TR[CONTEXT=CategoryFilterWidget] QBT_TR(Pause torrents)QBT_TR[CONTEXT=CategoryFilterWidget]
  • QBT_TR(Delete torrents)QBT_TR[CONTEXT=CategoryFilterWidget] QBT_TR(Delete torrents)QBT_TR[CONTEXT=CategoryFilterWidget]
  • +
    diff --git a/src/webui/www/private/properties_content.html b/src/webui/www/private/properties_content.html index ad491994e..2ec80038d 100644 --- a/src/webui/www/private/properties_content.html +++ b/src/webui/www/private/properties_content.html @@ -85,7 +85,7 @@
    - + diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index d745d4d42..f61f9a1be 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -2,9 +2,10 @@ var trackersDynTable = new Class({ initialize: function() {}, - setup: function(table) { + setup: function(table, contextMenu) { this.table = $(table); this.rows = new Hash(); + this.contextMenu = contextMenu; }, removeRow: function(url) { @@ -46,11 +47,13 @@ var trackersDynTable = new Class({ td.set('html', row[i]); td.injectInside(tr); } + this.contextMenu.addTarget(tr); tr.injectInside(this.table); - }, + } }); var current_hash = ""; +var selectedTracker = ""; var loadTrackersDataTimer; var loadTrackersData = function() { @@ -61,13 +64,13 @@ var loadTrackersData = function() { } var new_hash = torrentsTable.getCurrentTorrentHash(); if (new_hash === "") { - tTable.removeAllRows(); + torrentTrackersTable.removeAllRows(); clearTimeout(loadTrackersDataTimer); loadTrackersDataTimer = loadTrackersData.delay(10000); return; } if (new_hash != current_hash) { - tTable.removeAllRows(); + torrentTrackersTable.removeAllRows(); current_hash = new_hash; } var url = new URI('api/v2/torrents/trackers?hash=' + current_hash); @@ -82,6 +85,8 @@ var loadTrackersData = function() { }, onSuccess: function(trackers) { $('error_div').set('html', ''); + torrentTrackersTable.removeAllRows(); + if (trackers) { // Update Trackers data trackers.each(function(tracker) { @@ -96,12 +101,9 @@ var loadTrackersData = function() { escapeHtml(tracker.msg) ]; - tTable.insertRow(row); + torrentTrackersTable.insertRow(row); }); } - else { - tTable.removeAllRows(); - } clearTimeout(loadTrackersDataTimer); loadTrackersDataTimer = loadTrackersData.delay(10000); } @@ -113,11 +115,42 @@ var updateTrackersData = function() { loadTrackersData(); }; -tTable = new trackersDynTable(); -tTable.setup($('trackersTable')); +var torrentTrackersContextMenu = new ContextMenu({ + targets: '.torrentTrackersMenuTarget', + menu: 'torrentTrackersMenu', + actions: { + AddTracker: function(element, ref) { + addTrackerFN(); + }, + EditTracker: function(element, ref) { + editTrackerFN(element); + }, + RemoveTracker: function(element, ref) { + removeTrackerFN(element); + } + }, + offsets: { + x: -15, + y: 2 + }, + onShow: function() { + var element = this.options.element; + selectedTracker = element; + if (element.childNodes[1].innerText.indexOf("** [") === 0) { + this.hideItem('EditTracker'); + this.hideItem('RemoveTracker'); + this.hideItem('CopyTrackerUrl'); + } + else { + this.showItem('EditTracker'); + this.showItem('RemoveTracker'); + this.showItem('CopyTrackerUrl'); + } + this.options.element.firstChild.click(); + } +}); -// Add trackers code -$('addTrackersPlus').addEvent('click', function addTrackerDlg() { +var addTrackerFN = function() { if (current_hash.length === 0) return; new MochaUI.Window({ id: 'trackersPage', @@ -131,6 +164,62 @@ $('addTrackersPlus').addEvent('click', function addTrackerDlg() { paddingVertical: 0, paddingHorizontal: 0, width: 500, - height: 250 + height: 250, + onCloseComplete: function() { + updateTrackersData(); + } + }); +}; + +var editTrackerFN = function(element) { + if (current_hash.length === 0) return; + + var trackerUrl = encodeURIComponent(element.childNodes[1].innerText); + new MochaUI.Window({ + id: 'trackersPage', + title: "QBT_TR(Tracker editing)QBT_TR[CONTEXT=TrackerListWidget]", + loadMethod: 'iframe', + contentURL: 'edittracker.html?hash=' + current_hash + '&url=' + trackerUrl, + scrollbars: true, + resizable: false, + maximizable: false, + closable: true, + paddingVertical: 0, + paddingHorizontal: 0, + width: 500, + height: 150, + onCloseComplete: function() { + updateTrackersData(); + } }); +}; + +var removeTrackerFN = function(element) { + if (current_hash.length === 0) return; + + var trackerUrl = element.childNodes[1].innerText; + new Request({ + url: 'api/v2/torrents/removeTrackers', + method: 'post', + data: { + hash: current_hash, + urls: trackerUrl + }, + onSuccess: function() { + updateTrackersData(); + } + }).send(); +}; + +torrentTrackersTable = new trackersDynTable(); +torrentTrackersTable.setup($('trackersTable'), torrentTrackersContextMenu); + +new ClipboardJS('#CopyTrackerUrl', { + text: function(trigger) { + if (selectedTracker) { + var url = selectedTracker.childNodes[1].innerText; + selectedTracker = ""; + return url; + } + } }); diff --git a/src/webui/www/webui.qrc b/src/webui/www/webui.qrc index 999fa25a4..2c7559cd8 100644 --- a/src/webui/www/webui.qrc +++ b/src/webui/www/webui.qrc @@ -11,6 +11,7 @@ private/css/Window.cssprivate/download.htmlprivate/downloadlimit.html + private/edittracker.htmlprivate/filters.htmlprivate/index.htmlprivate/installsearchplugin.html From 9e1f7a72b771f08e886e2173ca59af7fa4290142 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Wed, 19 Sep 2018 00:06:28 -0400 Subject: [PATCH 08/10] Simplify map initialization --- src/webui/api/torrentscontroller.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 6c47499ab..c864b21ce 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -385,8 +385,6 @@ void TorrentsController::trackersAction() QHash trackersData = torrent->trackerInfos(); for (const BitTorrent::TrackerEntry &tracker : asConst(torrent->trackers())) { - QVariantMap trackerDict; - trackerDict[KEY_TRACKER_URL] = tracker.url(); const BitTorrent::TrackerInfo data = trackersData.value(tracker.url()); QString status; switch (tracker.status()) { @@ -399,16 +397,17 @@ void TorrentsController::trackersAction() case BitTorrent::TrackerEntry::NotWorking: status = tr("Not working"); break; } - trackerDict[KEY_TRACKER_TIER] = tracker.tier(); - trackerDict[KEY_TRACKER_STATUS] = status; - trackerDict[KEY_TRACKER_PEERS_COUNT] = data.numPeers; - trackerDict[KEY_TRACKER_MSG] = data.lastMessage.trimmed(); - trackerDict[KEY_TRACKER_SEEDS_COUNT] = tracker.nativeEntry().scrape_complete; - trackerDict[KEY_TRACKER_LEECHES_COUNT] = tracker.nativeEntry().scrape_incomplete; - trackerDict[KEY_TRACKER_DOWNLOADED_COUNT] = tracker.nativeEntry().scrape_downloaded; - - trackerList.append(trackerDict); + trackerList << QVariantMap { + {KEY_TRACKER_URL, tracker.url()}, + {KEY_TRACKER_TIER, tracker.tier()}, + {KEY_TRACKER_STATUS, status}, + {KEY_TRACKER_PEERS_COUNT, data.numPeers}, + {KEY_TRACKER_MSG, data.lastMessage.trimmed()}, + {KEY_TRACKER_SEEDS_COUNT, tracker.nativeEntry().scrape_complete}, + {KEY_TRACKER_LEECHES_COUNT, tracker.nativeEntry().scrape_incomplete}, + {KEY_TRACKER_DOWNLOADED_COUNT, tracker.nativeEntry().scrape_downloaded} + }; } setResult(QJsonArray::fromVariantList(trackerList)); From 718f66e6a2ed80ea2392717b2ff2c126719d24da Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Mon, 3 Dec 2018 00:07:11 -0500 Subject: [PATCH 09/10] Remove condition for unsupported libtorrent version --- src/gui/properties/trackerlistwidget.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/gui/properties/trackerlistwidget.cpp b/src/gui/properties/trackerlistwidget.cpp index c4e6488a2..c27494968 100644 --- a/src/gui/properties/trackerlistwidget.cpp +++ b/src/gui/properties/trackerlistwidget.cpp @@ -367,15 +367,9 @@ void TrackerListWidget::loadTrackers() } item->setText(COL_PEERS, QString::number(data.numPeers)); -#if LIBTORRENT_VERSION_NUM >= 10000 item->setText(COL_SEEDS, (entry.nativeEntry().scrape_complete > -1) ? QString::number(entry.nativeEntry().scrape_complete) : tr("N/A")); item->setText(COL_LEECHES, (entry.nativeEntry().scrape_incomplete > -1) ? QString::number(entry.nativeEntry().scrape_incomplete) : tr("N/A")); item->setText(COL_DOWNLOADED, (entry.nativeEntry().scrape_downloaded > -1) ? QString::number(entry.nativeEntry().scrape_downloaded) : tr("N/A")); -#else - item->setText(COL_SEEDS, tr("N/A")); - item->setText(COL_LEECHES, tr("N/A")); - item->setText(COL_DOWNLOADED, tr("N/A")); -#endif item->setTextAlignment(COL_TIER, (Qt::AlignRight | Qt::AlignVCenter)); item->setTextAlignment(COL_PEERS, (Qt::AlignRight | Qt::AlignVCenter)); From 7f349732ee57dc504d87d9ad7c494216af0e53e3 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Mon, 10 Dec 2018 23:39:22 -0500 Subject: [PATCH 10/10] Send numeric status without translation --- src/base/bittorrent/trackerentry.h | 8 +++---- src/webui/api/torrentscontroller.cpp | 13 +----------- .../www/private/scripts/prop-trackers.js | 21 ++++++++++++++++++- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/base/bittorrent/trackerentry.h b/src/base/bittorrent/trackerentry.h index fcaf9af7d..e2930e0a9 100644 --- a/src/base/bittorrent/trackerentry.h +++ b/src/base/bittorrent/trackerentry.h @@ -44,10 +44,10 @@ namespace BitTorrent public: enum Status { - NotContacted, - Working, - Updating, - NotWorking + NotContacted = 1, + Working = 2, + Updating = 3, + NotWorking = 4 }; TrackerEntry(const QString &url); diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index c864b21ce..6edbe1d31 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -386,22 +386,11 @@ void TorrentsController::trackersAction() QHash trackersData = torrent->trackerInfos(); for (const BitTorrent::TrackerEntry &tracker : asConst(torrent->trackers())) { const BitTorrent::TrackerInfo data = trackersData.value(tracker.url()); - QString status; - switch (tracker.status()) { - case BitTorrent::TrackerEntry::NotContacted: - status = tr("Not contacted yet"); break; - case BitTorrent::TrackerEntry::Updating: - status = tr("Updating..."); break; - case BitTorrent::TrackerEntry::Working: - status = tr("Working"); break; - case BitTorrent::TrackerEntry::NotWorking: - status = tr("Not working"); break; - } trackerList << QVariantMap { {KEY_TRACKER_URL, tracker.url()}, {KEY_TRACKER_TIER, tracker.tier()}, - {KEY_TRACKER_STATUS, status}, + {KEY_TRACKER_STATUS, static_cast(tracker.status())}, {KEY_TRACKER_PEERS_COUNT, data.numPeers}, {KEY_TRACKER_MSG, data.lastMessage.trimmed()}, {KEY_TRACKER_SEEDS_COUNT, tracker.nativeEntry().scrape_complete}, diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index f61f9a1be..84d053153 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -90,10 +90,29 @@ var loadTrackersData = function() { if (trackers) { // Update Trackers data trackers.each(function(tracker) { + var status; + 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 3: + status = "QBT_TR(Updating...)QBT_TR[CONTEXT=TrackerListWidget]"; + break; + case 4: + status = "QBT_TR(Not working)QBT_TR[CONTEXT=TrackerListWidget]"; + break; + } + var row = [ tracker.tier, escapeHtml(tracker.url), - tracker.status, + status, tracker.num_peers, (tracker.num_seeds >= 0) ? tracker.num_seeds : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", (tracker.num_leeches >= 0) ? tracker.num_leeches : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]",
    QBT_TR(#)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget] Add Trackers QBT_TR(Status)QBT_TR[CONTEXT=TrackerListWidget]QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget]QBT_TR(Message)QBT_TR[CONTEXT=TrackerListWidget]QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget]QBT_TR(Seeds)QBT_TR[CONTEXT=TrackerListWidget]QBT_TR(Leeches)QBT_TR[CONTEXT=TrackerListWidget]QBT_TR(Downloaded)QBT_TR[CONTEXT=TrackerListWidget]QBT_TR(Message)QBT_TR[CONTEXT=TrackerListWidget]
    QBT_TR(#)QBT_TR[CONTEXT=TrackerListWidget]QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget] Add TrackersQBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Status)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Seeds)QBT_TR[CONTEXT=TrackerListWidget]