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 @@ QBT_TR(#)QBT_TR[CONTEXT=TrackerListWidget] - QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget] Add Trackers + QBT_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] 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.css private/download.html private/downloadlimit.html + private/edittracker.html private/filters.html private/index.html private/installsearchplugin.html