WebUI: Support managing web seeds

Closes #8475.
PR #21055.
This commit is contained in:
Thomas Piccirello 2024-09-28 00:37:36 -07:00 committed by GitHub
commit 81def39d8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 304 additions and 87 deletions

View file

@ -531,11 +531,11 @@ void PropertiesWidget::loadUrlSeeds()
return;
m_ui->listWebSeeds->clear();
qDebug("Loading URL seeds");
qDebug("Loading web seeds");
// Add url seeds
for (const QUrl &urlSeed : urlSeeds)
{
qDebug("Loading URL seed: %s", qUtf8Printable(urlSeed.toString()));
qDebug("Loading web seed: %s", qUtf8Printable(urlSeed.toString()));
new QListWidgetItem(urlSeed.toString(), m_ui->listWebSeeds);
}
});
@ -550,16 +550,16 @@ void PropertiesWidget::displayWebSeedListMenu()
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_s), tr("New Web seed"), this, &PropertiesWidget::askWebSeed);
menu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_s), tr("Add web seed..."), this, &PropertiesWidget::askWebSeed);
if (!rows.isEmpty())
{
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_s, u"list-remove"_s), tr("Remove Web seed")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_s, u"list-remove"_s), tr("Remove web seed")
, this, &PropertiesWidget::deleteSelectedUrlSeeds);
menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-copy"_s), tr("Copy Web seed URL")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-copy"_s), tr("Copy web seed URL")
, this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_s), tr("Edit Web seed URL")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_s), tr("Edit web seed URL...")
, this, &PropertiesWidget::editWebSeed);
}
@ -607,14 +607,14 @@ void PropertiesWidget::askWebSeed()
{
bool ok = false;
// Ask user for a new url seed
const QString urlSeed = AutoExpandableDialog::getText(this, tr("New URL seed", "New HTTP source"),
tr("New URL seed:"), QLineEdit::Normal,
const QString urlSeed = AutoExpandableDialog::getText(this, tr("Add web seed", "Add HTTP source"),
tr("Add web seed:"), QLineEdit::Normal,
u"http://www."_s, &ok);
if (!ok) return;
qDebug("Adding %s web seed", qUtf8Printable(urlSeed));
if (!m_ui->listWebSeeds->findItems(urlSeed, Qt::MatchFixedString).empty())
{
QMessageBox::warning(this, u"qBittorrent"_s, tr("This URL seed is already in the list."), QMessageBox::Ok);
QMessageBox::warning(this, u"qBittorrent"_s, tr("This web seed is already in the list."), QMessageBox::Ok);
return;
}
if (m_torrent)
@ -667,7 +667,7 @@ void PropertiesWidget::editWebSeed()
if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty())
{
QMessageBox::warning(this, u"qBittorrent"_s,
tr("This URL seed is already in the list."),
tr("This web seed is already in the list."),
QMessageBox::Ok);
return;
}

View file

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="${LANG}">
<head>
<meta charset="UTF-8">
<title>QBT_TR(Add web seeds)QBT_TR[CONTEXT=HttpServer]</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", () => {
new Keyboard({
defaultEventType: "keydown",
events: {
"Escape": function(event) {
window.parent.qBittorrent.Client.closeWindows();
event.preventDefault();
},
"Esc": function(event) {
window.parent.qBittorrent.Client.closeWindows();
event.preventDefault();
}
}
}).activate();
$("urls").focus();
$("addWebSeedsButton").addEvent("click", (e) => {
e.stopPropagation();
const hash = new URI().getData("hash");
new Request({
url: "api/v2/torrents/addWebSeeds",
method: "post",
data: {
hash: hash,
urls: $("urls").value.split("\n").map(w => encodeURIComponent(w.trim())).filter(w => (w.length > 0)).join("|")
},
onComplete: function() {
window.parent.qBittorrent.Client.closeWindows();
}
}).send();
});
});
</script>
</head>
<body>
<div style="text-align: center;">
<br>
<label for="urls">QBT_TR(List of web seeds to add (one per line):)QBT_TR[CONTEXT=HttpServer]</label>
<textarea name="list" id="urls" rows="10" cols="1"></textarea>
<br>
<input type="button" value="QBT_TR(Add)QBT_TR[CONTEXT=HttpServer]" id="addWebSeedsButton">
</div>
</body>
</html>

View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="${LANG}">
<head>
<meta charset="UTF-8">
<title>QBT_TR(Edit web seed)QBT_TR[CONTEXT=HttpServer]</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", () => {
new Keyboard({
defaultEventType: "keydown",
events: {
"Enter": function(event) {
$("editWebSeedButton").click();
event.preventDefault();
},
"Escape": function(event) {
window.parent.qBittorrent.Client.closeWindows();
event.preventDefault();
},
"Esc": function(event) {
window.parent.qBittorrent.Client.closeWindows();
event.preventDefault();
}
}
}).activate();
const origUrl = new URI().getData("url");
$("url").value = decodeURIComponent(origUrl);
$("url").focus();
$("editWebSeedButton").addEvent("click", (e) => {
e.stopPropagation();
const hash = new URI().getData("hash");
new Request({
url: "api/v2/torrents/editWebSeed",
method: "post",
data: {
hash: hash,
origUrl: origUrl,
newUrl: encodeURIComponent($("url").value.trim()),
},
onComplete: function() {
window.parent.qBittorrent.Client.closeWindows();
}
}).send();
});
});
</script>
</head>
<body>
<div style="text-align: center;">
<br>
<label for="url">QBT_TR(Web seed URL:)QBT_TR[CONTEXT=PropertiesWidget]</label>
<div style="text-align: center; padding-top: 10px;">
<input type="text" id="url" style="width: 90%;">
</div>
<br>
<input type="button" value="QBT_TR(Edit)QBT_TR[CONTEXT=HttpServer]" id="editWebSeedButton">
</div>
</body>
</html>

View file

@ -244,6 +244,12 @@
<li><a href="#copyPeer" id="CopyPeerInfo"><img src="images/edit-copy.svg" alt="QBT_TR(Copy IP:port)QBT_TR[CONTEXT=PeerListWidget]"> QBT_TR(Copy IP:port)QBT_TR[CONTEXT=PeerListWidget]</a></li>
<li class="separator"><a href="#banPeer"><img src="images/peers-remove.svg" alt="QBT_TR(Ban peer permanently)QBT_TR[CONTEXT=PeerListWidget]"> QBT_TR(Ban peer permanently)QBT_TR[CONTEXT=PeerListWidget]</a></li>
</ul>
<ul id="torrentWebseedsMenu" class="contextMenu">
<li><a href="#AddWebSeeds"><img src="images/list-add.svg" alt="QBT_TR(Add web seeds...)QBT_TR[CONTEXT=PropertiesWidget]"> QBT_TR(Add web seeds...)QBT_TR[CONTEXT=PropertiesWidget]</a></li>
<li><a href="#RemoveWebSeed"><img src="images/list-remove.svg" alt="QBT_TR(Remove web seed)QBT_TR[CONTEXT=PropertiesWidget]"> QBT_TR(Remove web seed)QBT_TR[CONTEXT=PropertiesWidget]</a></li>
<li class="separator"><a href="#CopyWebseedUrl" id="CopyWebseedUrl"><img src="images/edit-copy.svg" alt="QBT_TR(Copy web seed URL)QBT_TR[CONTEXT=PropertiesWidget]"> QBT_TR(Copy web seed URL)QBT_TR[CONTEXT=PropertiesWidget]</a></li>
<li><a href="#EditWebSeed"><img src="images/edit-rename.svg" alt="QBT_TR(Edit web seed URL...)QBT_TR[CONTEXT=PropertiesWidget]"> QBT_TR(Edit web seed URL...)QBT_TR[CONTEXT=PropertiesWidget]</a></li>
</ul>
<ul id="torrentFilesMenu" class="contextMenu">
<li><a href="#Rename"><img src="images/edit-rename.svg" alt="QBT_TR(Rename...)QBT_TR[CONTEXT=PropertiesWidget]"> QBT_TR(Rename...)QBT_TR[CONTEXT=PropertiesWidget]</a></li>
<li class="separator">

View file

@ -50,7 +50,8 @@ window.qBittorrent.DynamicTable ??= (() => {
RssArticleTable: RssArticleTable,
RssDownloaderRulesTable: RssDownloaderRulesTable,
RssDownloaderFeedSelectionTable: RssDownloaderFeedSelectionTable,
RssDownloaderArticlesTable: RssDownloaderArticlesTable
RssDownloaderArticlesTable: RssDownloaderArticlesTable,
TorrentWebseedsTable: TorrentWebseedsTable
};
};
@ -3251,6 +3252,14 @@ window.qBittorrent.DynamicTable ??= (() => {
}
});
const TorrentWebseedsTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn("url", "", "QBT_TR(URL)QBT_TR[CONTEXT=HttpServer]", 500, true);
},
});
return exports();
})();
Object.freeze(window.qBittorrent.DynamicTable);

View file

@ -36,55 +36,7 @@ window.qBittorrent.PropWebseeds ??= (() => {
};
};
const webseedsDynTable = new Class({
initialize: function() {},
setup: function(table) {
this.table = $(table);
this.rows = new Hash();
},
removeRow: function(url) {
if (this.rows.has(url)) {
this.rows.get(url).destroy();
this.rows.erase(url);
return true;
}
return false;
},
removeAllRows: function() {
this.rows.each((tr, url) => {
this.removeRow(url);
});
},
updateRow: function(tr, row) {
const tds = tr.getElements("td");
for (let i = 0; i < row.length; ++i)
tds[i].textContent = row[i];
return true;
},
insertRow: function(row) {
const url = row[0];
if (this.rows.has(url)) {
const tableRow = this.rows.get(url);
this.updateRow(tableRow, row);
return;
}
// this.removeRow(id);
const tr = new Element("tr");
this.rows.set(url, tr);
for (let i = 0; i < row.length; ++i) {
const td = document.createElement("td");
td.textContent = row[i];
tr.appendChild(td);
}
tr.injectInside(this.table);
},
});
const torrentWebseedsTable = new window.qBittorrent.DynamicTable.TorrentWebseedsTable();
let current_hash = "";
@ -97,41 +49,41 @@ window.qBittorrent.PropWebseeds ??= (() => {
}
const new_hash = torrentsTable.getCurrentTorrentID();
if (new_hash === "") {
wsTable.removeAllRows();
torrentWebseedsTable.clear();
clearTimeout(loadWebSeedsDataTimer);
loadWebSeedsDataTimer = loadWebSeedsData.delay(10000);
return;
}
if (new_hash !== current_hash) {
wsTable.removeAllRows();
torrentWebseedsTable.clear();
current_hash = new_hash;
}
const url = new URI("api/v2/torrents/webseeds?hash=" + current_hash);
new Request.JSON({
url: url,
url: new URI("api/v2/torrents/webseeds").setData("hash", current_hash),
method: "get",
noCache: true,
onFailure: function() {
$("error_div").textContent = "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
onComplete: function() {
clearTimeout(loadWebSeedsDataTimer);
loadWebSeedsDataTimer = loadWebSeedsData.delay(20000);
loadWebSeedsDataTimer = loadWebSeedsData.delay(10000);
},
onSuccess: function(webseeds) {
$("error_div").textContent = "";
const selectedWebseeds = torrentWebseedsTable.selectedRowsIds();
torrentWebseedsTable.clear();
if (webseeds) {
// Update WebSeeds data
webseeds.each((webseed) => {
const row = [];
row.length = 1;
row[0] = webseed.url;
wsTable.insertRow(row);
torrentWebseedsTable.updateRowData({
rowId: webseed.url,
url: webseed.url,
});
});
}
else {
wsTable.removeAllRows();
}
clearTimeout(loadWebSeedsDataTimer);
loadWebSeedsDataTimer = loadWebSeedsData.delay(10000);
torrentWebseedsTable.updateTable(false);
if (selectedWebseeds.length > 0)
torrentWebseedsTable.reselectRows(selectedWebseeds);
}
}).send();
};
@ -142,8 +94,123 @@ window.qBittorrent.PropWebseeds ??= (() => {
loadWebSeedsData();
};
const wsTable = new webseedsDynTable();
wsTable.setup($("webseedsTable"));
const torrentWebseedsContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: "#torrentWebseedsTableDiv",
menu: "torrentWebseedsMenu",
actions: {
AddWebSeeds: function(element, ref) {
addWebseedFN();
},
EditWebSeed: function(element, ref) {
// only allow editing of one row
element.firstChild.click();
editWebSeedFN(element);
},
RemoveWebSeed: function(element, ref) {
removeWebSeedFN(element);
}
},
offsets: {
x: -15,
y: 2
},
onShow: function() {
const selectedWebseeds = torrentWebseedsTable.selectedRowsIds();
if (selectedWebseeds.length === 0) {
this.hideItem("EditWebSeed");
this.hideItem("RemoveWebSeed");
this.hideItem("CopyWebseedUrl");
}
else {
if (selectedWebseeds.length === 1)
this.showItem("EditWebSeed");
else
this.hideItem("EditWebSeed");
this.showItem("RemoveWebSeed");
this.showItem("CopyWebseedUrl");
}
}
});
const addWebseedFN = function() {
if (current_hash.length === 0)
return;
new MochaUI.Window({
id: "webseedsPage",
title: "QBT_TR(Add web seeds)QBT_TR[CONTEXT=HttpServer]",
loadMethod: "iframe",
contentURL: "addwebseeds.html?hash=" + current_hash,
scrollbars: true,
resizable: false,
maximizable: false,
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
height: 250,
onCloseComplete: function() {
updateData();
}
});
};
const editWebSeedFN = function(element) {
if (current_hash.length === 0)
return;
const selectedWebseeds = torrentWebseedsTable.selectedRowsIds();
if (selectedWebseeds.length > 1)
return;
const webseedUrl = selectedWebseeds[0];
new MochaUI.Window({
id: "webseedsPage",
title: "QBT_TR(Web seed editing)QBT_TR[CONTEXT=PropertiesWidget]",
loadMethod: "iframe",
contentURL: "editwebseed.html?hash=" + current_hash + "&url=" + encodeURIComponent(webseedUrl),
scrollbars: true,
resizable: false,
maximizable: false,
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
height: 150,
onCloseComplete: function() {
updateData();
}
});
};
const removeWebSeedFN = function(element) {
if (current_hash.length === 0)
return;
const selectedWebseeds = torrentWebseedsTable.selectedRowsIds();
new Request({
url: "api/v2/torrents/removeWebSeeds",
method: "post",
data: {
hash: current_hash,
urls: selectedWebseeds.map(webseed => encodeURIComponent(webseed)).join("|")
},
onSuccess: function() {
updateData();
}
}).send();
};
new ClipboardJS("#CopyWebseedUrl", {
text: function(trigger) {
return torrentWebseedsTable.selectedRowsIds().join("\n");
}
});
torrentWebseedsTable.setup("torrentWebseedsTableDiv", "torrentWebseedsTableFixedHeaderDiv", torrentWebseedsContextMenu);
return exports();
})();

View file

@ -146,15 +146,22 @@
<div id="propWebSeeds" class="propertiesTabContent invisible unselectable">
<div id="webseeds">
<table class="dynamicTable" style="width: 100%">
<div id="torrentWebseedsTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
<table class="dynamicTable" style="position:relative;">
<thead>
<tr>
<th scope="col">QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget]</th>
</tr>
<tr class="dynamicTableHeader"></tr>
</thead>
<tbody id="webseedsTable"></tbody>
</table>
</div>
<div id="torrentWebseedsTableDiv" class="dynamicTableDiv">
<table class="dynamicTable">
<thead>
<tr class="dynamicTableHeader"></tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<div id="propFiles" class="propertiesTabContent invisible unselectable">

View file

@ -2,6 +2,7 @@
<qresource prefix="/www">
<file>private/addpeers.html</file>
<file>private/addtrackers.html</file>
<file>private/addwebseeds.html</file>
<file>private/confirmfeeddeletion.html</file>
<file>private/confirmruleclear.html</file>
<file>private/confirmruledeletion.html</file>
@ -18,6 +19,7 @@
<file>private/download.html</file>
<file>private/downloadlimit.html</file>
<file>private/edittracker.html</file>
<file>private/editwebseed.html</file>
<file>private/images/3-state-checkbox.gif</file>
<file>private/images/application-exit.svg</file>
<file>private/images/application-rss.svg</file>