diff --git a/src/webui/api/apicontroller.cpp b/src/webui/api/apicontroller.cpp index df7890cfc..2c6e61d5f 100644 --- a/src/webui/api/apicontroller.cpp +++ b/src/webui/api/apicontroller.cpp @@ -91,3 +91,8 @@ void APIController::setResult(const QJsonObject &result) { m_result = QJsonDocument(result); } + +void APIController::setResult(const QByteArray &result) +{ + m_result = result; +} diff --git a/src/webui/api/apicontroller.h b/src/webui/api/apicontroller.h index 91c0730b3..4defaae48 100644 --- a/src/webui/api/apicontroller.h +++ b/src/webui/api/apicontroller.h @@ -55,6 +55,7 @@ protected: void setResult(const QString &result); void setResult(const QJsonArray &result); void setResult(const QJsonObject &result); + void setResult(const QByteArray &result); private: StringMap m_params; diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 0c6869cc5..3b1cc284b 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -1414,3 +1414,19 @@ void TorrentsController::renameFolderAction() throw APIError(APIErrorType::Conflict, error.message()); } } + +void TorrentsController::exportAction() +{ + requireParams({u"hash"_qs}); + + const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_qs]); + const BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(id); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + const nonstd::expected result = torrent->exportToBuffer(); + if (!result) + throw APIError(APIErrorType::Conflict, tr("Unable to export torrent file. Error: %1").arg(result.error())); + + setResult(result.value()); +} diff --git a/src/webui/api/torrentscontroller.h b/src/webui/api/torrentscontroller.h index 61487e0b6..779959ca4 100644 --- a/src/webui/api/torrentscontroller.h +++ b/src/webui/api/torrentscontroller.h @@ -87,4 +87,5 @@ private slots: void toggleFirstLastPiecePrioAction(); void renameFileAction(); void renameFolderAction(); + void exportAction(); }; diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index b250871e5..92d340f71 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -278,6 +278,9 @@ void WebApplication::doProcessRequest() case QMetaType::QJsonDocument: print(result.toJsonDocument().toJson(QJsonDocument::Compact), Http::CONTENT_TYPE_JSON); break; + case QMetaType::QByteArray: + print(result.toByteArray(), Http::CONTENT_TYPE_TXT); + break; case QMetaType::QString: default: print(result.toString(), Http::CONTENT_TYPE_TXT); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index d56578141..47bafd24f 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -48,7 +48,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version API_VERSION {2, 8, 10}; +inline const Utils::Version API_VERSION {2, 8, 11}; class APIController; class AuthController; diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index 0bd78fa6b..f6d0fe15e 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -174,6 +174,9 @@
  • QBT_TR(Torrent ID)QBT_TR[CONTEXT=TransferListWidget] QBT_TR(Torrent ID)QBT_TR[CONTEXT=TransferListWidget]
  • +
  • + QBT_TR(Export .torrent)QBT_TR[CONTEXT=TransferListWidget] QBT_TR(Export .torrent)QBT_TR[CONTEXT=TransferListWidget] +
    • QBT_TR(Add category...)QBT_TR[CONTEXT=CategoryFilterWidget] QBT_TR(Add category...)QBT_TR[CONTEXT=CategoryFilterWidget]
    • diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index e16b78efa..299aa6170 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -88,6 +88,7 @@ let copyInfohashFN = function(policy) {}; let copyMagnetLinkFN = function() {}; let copyIdFN = function() {}; let setQueuePositionFN = function() {}; +let exportTorrentFN = function() {}; const initializeWindows = function() { saveWindowSize = function(windowId) { @@ -957,6 +958,26 @@ const initializeWindows = function() { return torrentsTable.selectedRowsIds().join("\n"); }; + exportTorrentFN = function() { + const hashes = torrentsTable.selectedRowsIds(); + for (const hash of hashes) { + const row = torrentsTable.rows.get(hash); + if (!row) return + + const name = row.full_data.name; + const url = new URI("api/v2/torrents/export"); + url.setData("hash", hash); + + // download response to file + const element = document.createElement("a"); + element.setAttribute("href", url); + element.setAttribute("download", name + ".torrent"); + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + } + }; + ['pause', 'resume'].each(function(item) { addClickEvent(item + 'All', function(e) { new Event(e).stop(); diff --git a/src/webui/www/private/views/transferlist.html b/src/webui/www/private/views/transferlist.html index 2092b896d..6bbad810a 100644 --- a/src/webui/www/private/views/transferlist.html +++ b/src/webui/www/private/views/transferlist.html @@ -97,6 +97,10 @@ superSeeding: function(element, ref) { setSuperSeedingFN(!ref.getItemChecked('superSeeding')); + }, + + exportTorrent: function(element, ref) { + exportTorrentFN(); } }, offsets: {