From 11ac376af7830cfd5696e79370f2eab9e066743e Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Fri, 13 Jun 2025 16:25:01 +0300 Subject: [PATCH 1/2] feat: Allow `Rename all` on multiple torrents * Introduced new `torrents/bulkFiles` endpoint for fetching list of files from multiple torrents in a single call * Updated `JS` that load files into the file-rename table --- WebAPI_Changelog.md | 5 +++ src/webui/api/torrentscontroller.cpp | 40 +++++++++++++++++ src/webui/api/torrentscontroller.h | 1 + src/webui/webapplication.h | 2 +- src/webui/www/private/rename_files.html | 5 ++- src/webui/www/private/scripts/contextmenu.js | 1 - src/webui/www/private/scripts/mocha-init.js | 45 ++++++++++--------- src/webui/www/private/scripts/rename-files.js | 2 +- 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/WebAPI_Changelog.md b/WebAPI_Changelog.md index c2164f65a..eed1e131a 100644 --- a/WebAPI_Changelog.md +++ b/WebAPI_Changelog.md @@ -1,5 +1,10 @@ # WebAPI Changelog +## 2.11.9 + +* [#???](https://github.com/qbittorrent/qBittorrent/pull/) + * Introduce `torrents/bulkFiles`, accepts a list of `hash`es separated with `|` (pipe), returns list of files for all selected torrents + ## 2.11.8 * [#21349](https://github.com/qbittorrent/qBittorrent/pull/21349) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 9fe658f95..ad8205f57 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -786,6 +786,46 @@ void TorrentsController::filesAction() setResult(fileList); } +// Returns a list of all the files in the list of torrent in JSON format. +// The return value is a JSON-formatted list of dictionaries. +// The dictionary keys are: +// - "index": File index +// - "name": File name +// - "size": File size +// - "progress": File progress +// - "priority": File priority +// - "is_seed": Flag indicating if torrent is seeding/complete +// - "piece_range": Piece index range, the first number is the starting piece index +// and the second number is the ending piece index (inclusive) +// - "torrent_hash": The hash of the torrent from which this file originates +void TorrentsController::bulkFilesAction() +{ + requireParams({u"hash"_s}); + + const auto ids = params()[u"hash"_s].split(u'|', Qt::SkipEmptyParts); + QVariantList fileList; + for (const QString &id : ids) + { + const BitTorrent::Torrent *torrent = BitTorrent::Session::instance()->getTorrent(BitTorrent::TorrentID::fromString(id)); + if (!torrent) + continue; + if (!torrent->hasMetadata()) + continue; // skip torrents without metadata + + auto currentFileList = getFiles(torrent); + // Add torrent ID to each file + for (QJsonValueRef file : currentFileList) + { + QJsonObject fileObj = file.toObject(); + fileObj[u"torrent_hash"] = torrent->id().toString(); + file = fileObj; + } + fileList.append(currentFileList.toVariantList()); + } + + setResult(QJsonArray::fromVariantList(fileList)); +} + // Returns an array of hashes (of each pieces respectively) for a torrent in JSON format. // The return value is a JSON-formatted array of strings (hex strings). void TorrentsController::pieceHashesAction() diff --git a/src/webui/api/torrentscontroller.h b/src/webui/api/torrentscontroller.h index a233048ac..34c1c97b6 100644 --- a/src/webui/api/torrentscontroller.h +++ b/src/webui/api/torrentscontroller.h @@ -48,6 +48,7 @@ private slots: void editWebSeedAction(); void removeWebSeedsAction(); void filesAction(); + void bulkFilesAction(); void pieceHashesAction(); void pieceStatesAction(); void startAction(); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index e39140eff..6ceb28593 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -53,7 +53,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 11, 8}; +inline const Utils::Version<3, 2> API_VERSION {2, 11, 9}; class APIController; class AuthController; diff --git a/src/webui/www/private/rename_files.html b/src/webui/www/private/rename_files.html index bdfc46e75..2bd6e6988 100644 --- a/src/webui/www/private/rename_files.html +++ b/src/webui/www/private/rename_files.html @@ -298,7 +298,8 @@ path: file.name, original: window.qBittorrent.Filesystem.fileName(file.name), renamed: "", - size: file.size + size: file.size, + torrentHash: file.torrent_hash }; return row; @@ -375,7 +376,7 @@ }; const setupTable = (selectedRows) => { - const url = new URL("api/v2/torrents/files", window.location); + const url = new URL("api/v2/torrents/bulkFiles", window.location); url.search = new URLSearchParams({ hash: data.hash }); diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index 687a8c1cf..79b95f8e4 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -397,7 +397,6 @@ window.qBittorrent.ContextMenu ??= (() => { : this.hideItem("renameFiles"); } else { - this.hideItem("renameFiles"); this.hideItem("rename"); } diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index 9e358f442..fb089a3c1 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -740,28 +740,31 @@ const initializeWindows = () => { renameFilesFN = () => { const hashes = torrentsTable.selectedRowsIds(); - if (hashes.length === 1) { - const hash = hashes[0]; + const hashList = hashes.join("|"); + const rows = []; + hashes.forEach((hash) => { const row = torrentsTable.getRow(hash); - if (row) { - new MochaUI.Window({ - id: "multiRenamePage", - icon: "images/qbittorrent-tray.svg", - title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]", - data: { hash: hash, selectedRows: [] }, - loadMethod: "xhr", - contentURL: "rename_files.html", - scrollbars: false, - resizable: true, - maximizable: false, - paddingVertical: 0, - paddingHorizontal: 0, - width: 800, - height: 420, - resizeLimit: { x: [800], y: [420] } - }); - } - } + if (row) + rows.push(row); + }); + if (rows.length === 0) + return; + new MochaUI.Window({ + id: "multiRenamePage", + icon: "images/qbittorrent-tray.svg", + title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]", + data: { hash: hashList, selectedRows: rows }, + loadMethod: "xhr", + contentURL: "rename_files.html", + scrollbars: false, + resizable: true, + maximizable: false, + paddingVertical: 0, + paddingHorizontal: 0, + width: 800, + height: 420, + resizeLimit: { x: [800], y: [420] } + }); }; startVisibleTorrentsFN = () => { diff --git a/src/webui/www/private/scripts/rename-files.js b/src/webui/www/private/scripts/rename-files.js index 3548d05b2..79463f3fc 100644 --- a/src/webui/www/private/scripts/rename-files.js +++ b/src/webui/www/private/scripts/rename-files.js @@ -244,7 +244,7 @@ window.qBittorrent.MultiRename ??= (() => { await fetch((isFolder ? "api/v2/torrents/renameFolder" : "api/v2/torrents/renameFile"), { method: "POST", body: new URLSearchParams({ - hash: this.hash, + hash: match.data.torrentHash, oldPath: oldPath, newPath: newPath }) From 020a226ad70a81276d310f5c7fbfe41d75e15fb2 Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Fri, 13 Jun 2025 16:57:04 +0300 Subject: [PATCH 2/2] Update WebAPI_Changelog after creating PR --- WebAPI_Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebAPI_Changelog.md b/WebAPI_Changelog.md index eed1e131a..d8c0e1fac 100644 --- a/WebAPI_Changelog.md +++ b/WebAPI_Changelog.md @@ -2,7 +2,7 @@ ## 2.11.9 -* [#???](https://github.com/qbittorrent/qBittorrent/pull/) +* [#22863](https://github.com/qbittorrent/qBittorrent/pull/22863) * Introduce `torrents/bulkFiles`, accepts a list of `hash`es separated with `|` (pipe), returns list of files for all selected torrents ## 2.11.8