mirror of
https://github.com/transmission/transmission.git
synced 2026-02-14 23:19:34 +00:00
* fix: legacy RPC tag should ignore non-integers * refactor: delegate legacy RPC callback to api-compat * refactor: move `TR_KEY_alt_speed_down_kebab` handling * refactor: move `TR_KEY_alt_speed_enabled_kebab` handling * refactor: move `TR_KEY_alt_speed_time_begin_kebab` handling * refactor: move `TR_KEY_alt_speed_time_day_kebab` handling * refactor: move `TR_KEY_alt_speed_time_enabled_kebab` handling * refactor: move `TR_KEY_alt_speed_time_end_kebab` handling * refactor: move `TR_KEY_alt_speed_up_kebab` handling * refactor: move `TR_KEY_anti_brute_force_enabled_kebab` handling * refactor: move `TR_KEY_anti_brute_force_threshold_kebab` handling * refactor: move `TR_KEY_blocklist_enabled_kebab` handling * refactor: move `TR_KEY_blocklist_size_kebab` handling * refactor: move `TR_KEY_blocklist_url_kebab` handling * refactor: move `TR_KEY_cache_size_mb_kebab` handling * refactor: move `TR_KEY_config_dir_kebab` handling * refactor: move `TR_KEY_default_trackers_kebab` handling * refactor: move `TR_KEY_dht_enabled_kebab` handling * refactor: move `TR_KEY_download_dir_kebab` handling * refactor: move `TR_KEY_download_dir_free_space_kebab` handling * refactor: move `TR_KEY_download_queue_enabled_kebab` handling * refactor: move `TR_KEY_download_queue_size_kebab` handling * refactor: move `TR_KEY_idle_seeding_limit_kebab` handling * refactor: move `TR_KEY_idle_seeding_limit_enabled_kebab` handling * refactor: move `TR_KEY_incomplete_dir_kebab` handling * refactor: move `TR_KEY_incomplete_dir_enabled_kebab` handling * refactor: move `TR_KEY_lpd_enabled_kebab` handling * refactor: move `TR_KEY_peer_limit_global_kebab` handling * refactor: move `TR_KEY_peer_limit_per_torrent_kebab` handling * refactor: move `TR_KEY_peer_port_kebab` handling * refactor: move `TR_KEY_peer_port_random_on_start_kebab` handling * refactor: move `TR_KEY_pex_enabled_kebab` handling * refactor: move `TR_KEY_port_forwarding_enabled_kebab` handling * refactor: move `TR_KEY_queue_stalled_enabled_kebab` handling * refactor: move `TR_KEY_queue_stalled_minutes_kebab` handling * refactor: move `TR_KEY_rename_partial_files_kebab` handling * refactor: move `TR_KEY_rpc_version_kebab` handling * refactor: move `TR_KEY_rpc_version_minimum_kebab` handling * refactor: move `TR_KEY_rpc_version_semver_kebab` handling * refactor: move `TR_KEY_script_torrent_added_enabled_kebab` handling * refactor: move `TR_KEY_script_torrent_added_filename_kebab` handling * refactor: move `TR_KEY_script_torrent_done_enabled_kebab` handling * refactor: move `TR_KEY_script_torrent_done_filename_kebab` handling * refactor: move `TR_KEY_script_torrent_done_seeding_enabled_kebab` handling * refactor: move `TR_KEY_script_torrent_done_seeding_filename_kebab` handling * refactor: remove `tr_quark_convert` usage for `tr_session::Scripts` * refactor: move `TR_KEY_seed_ratio_limit_camel` handling * refactor: move `TR_KEY_seed_ratio_limited_camel` handling * refactor: move `TR_KEY_seed_queue_enabled_kebab` handling * refactor: move `TR_KEY_seed_queue_size_kebab` handling * refactor: move `TR_KEY_session_id_kebab` handling * refactor: move `TR_KEY_speed_limit_down_kebab` handling * refactor: move `TR_KEY_speed_limit_down_enabled_kebab` handling * refactor: move `TR_KEY_speed_limit_up_kebab` handling * refactor: move `TR_KEY_speed_limit_up_enabled_kebab` handling * refactor: move `TR_KEY_start_added_torrents_kebab` handling * refactor: move `TR_KEY_tcp_enabled_kebab` handling * refactor: move `TR_KEY_trash_original_torrent_files_kebab` handling * refactor: move `TR_KEY_utp_enabled_kebab` handling * refactor: move `TR_KEY_recently_active_kebab` handling * refactor: move `TR_KEY_delete_local_data_kebab` handling * refactor: move `TR_KEY_activity_date_camel` handling * refactor: move `TR_KEY_added_date_camel` handling * refactor: move `TR_KEY_bandwidth_priority_camel` handling * refactor: move `TR_KEY_bytes_completed_camel` handling * refactor: move `TR_KEY_corrupt_ever_camel` handling * refactor: move `TR_KEY_date_created_camel` handling * refactor: move `TR_KEY_desired_available_camel` handling * refactor: move `TR_KEY_done_date_camel` handling * refactor: move `TR_KEY_download_dir_camel` handling * refactor: move `TR_KEY_download_limit_camel` handling * refactor: move `TR_KEY_download_limited_camel` handling * refactor: move `TR_KEY_downloaded_ever_camel` handling * refactor: move `TR_KEY_edit_date_camel` handling * refactor: move `TR_KEY_error_string_camel` handling * refactor: move `TR_KEY_eta_idle_camel` handling * refactor: move `TR_KEY_file_stats_camel` handling * refactor: move `TR_KEY_file_count_kebab` handling * refactor: move `TR_KEY_hash_string_camel` handling * refactor: move `TR_KEY_have_unchecked_camel` handling * refactor: move `TR_KEY_have_valid_camel` handling * refactor: move `TR_KEY_honors_session_limits_camel` handling * refactor: move `TR_KEY_is_finished_camel` handling * refactor: move `TR_KEY_is_private_camel` handling * refactor: move `TR_KEY_is_stalled_camel` handling * refactor: move `TR_KEY_left_until_done_camel` handling * refactor: move `TR_KEY_magnet_link_camel` handling * refactor: move `TR_KEY_manual_announce_time_camel` handling * refactor: move `TR_KEY_max_connected_peers_camel` handling * refactor: move `TR_KEY_metadata_percent_complete_camel` handling * refactor: move `TR_KEY_peer_limit_kebab` handling * refactor: move `TR_KEY_peers_connected_camel` handling * refactor: move `TR_KEY_peers_from_camel` handling * refactor: move `TR_KEY_peers_getting_from_us_camel` handling * refactor: move `TR_KEY_peers_sending_to_us_camel` handling * refactor: move `TR_KEY_percent_complete_camel` handling * refactor: move `TR_KEY_percent_done_camel` handling * refactor: move `TR_KEY_piece_count_camel` handling * refactor: move `TR_KEY_piece_size_camel` handling * refactor: move `TR_KEY_primary_mime_type_kebab` handling * refactor: move `TR_KEY_queue_position_camel` handling * refactor: move `TR_KEY_rate_download_camel` handling * refactor: move `TR_KEY_rate_upload_camel` handling * refactor: move `TR_KEY_recheck_progress_camel` handling * refactor: move `TR_KEY_seconds_downloading_camel` handling * refactor: move `TR_KEY_seconds_seeding_camel` handling * refactor: move `TR_KEY_seed_idle_limit_camel` handling * refactor: move `TR_KEY_seed_idle_mode_camel` handling * refactor: move `TR_KEY_seed_ratio_mode_camel` handling * refactor: move `TR_KEY_size_when_done_camel` handling * refactor: move `TR_KEY_start_date_camel` handling * refactor: move `TR_KEY_torrent_file_camel` handling * refactor: move `TR_KEY_total_size_camel` handling * refactor: move `TR_KEY_tracker_list_camel` handling * refactor: move `TR_KEY_tracker_stats_camel` handling * refactor: move `TR_KEY_upload_limit_camel` handling * refactor: move `TR_KEY_upload_limited_camel` handling * refactor: move `TR_KEY_upload_ratio_camel` handling * refactor: move `TR_KEY_uploaded_ever_camel` handling * refactor: move `TR_KEY_webseeds_sending_to_us_camel` handling * refactor: move `TR_KEY_announce_state_camel` handling * refactor: move `TR_KEY_download_count_camel` handling * refactor: move `TR_KEY_has_announced_camel` handling * refactor: move `TR_KEY_has_scraped_camel` handling * refactor: move `TR_KEY_is_backup_camel` handling * refactor: move `TR_KEY_last_announce_peer_count_camel` handling * refactor: move `TR_KEY_last_announce_result_camel` handling * refactor: move `TR_KEY_last_announce_start_time_camel` handling * refactor: move `TR_KEY_last_announce_succeeded_camel` handling * refactor: move `TR_KEY_last_announce_time_camel` handling * refactor: move `TR_KEY_last_announce_timed_out_camel` handling * refactor: move `TR_KEY_last_scrape_result_camel` handling * refactor: move `TR_KEY_last_scrape_start_time_camel` handling * refactor: move `TR_KEY_last_scrape_succeeded_camel` handling * refactor: move `TR_KEY_last_scrape_time_camel` handling * refactor: move `TR_KEY_last_scrape_timed_out_camel` handling * refactor: move `TR_KEY_leecher_count_camel` handling * refactor: move `TR_KEY_next_announce_time_camel` handling * refactor: move `TR_KEY_next_scrape_time_camel` handling * refactor: move `TR_KEY_scrape_state_camel` handling * refactor: move `TR_KEY_seeder_count_camel` handling * refactor: move `TR_KEY_client_is_choked_camel` handling * refactor: move `TR_KEY_client_is_interested_camel` handling * refactor: move `TR_KEY_client_name_camel` handling * refactor: move `TR_KEY_flag_str_camel` handling * refactor: move `TR_KEY_is_downloading_from_camel` handling * refactor: move `TR_KEY_is_encrypted_camel` handling * refactor: move `TR_KEY_is_incoming_camel` handling * refactor: move `TR_KEY_is_utp_camel` handling * refactor: move `TR_KEY_is_uploading_to_camel` handling * refactor: move `TR_KEY_peer_is_choked_camel` handling * refactor: move `TR_KEY_peer_is_interested_camel` handling * refactor: move `TR_KEY_rate_to_client_camel` handling * refactor: move `TR_KEY_rate_to_peer_camel` handling * refactor: move `TR_KEY_from_cache_camel` handling * refactor: move `TR_KEY_from_dht_camel` handling * refactor: move `TR_KEY_from_incoming_camel` handling * refactor: move `TR_KEY_from_lpd_camel` handling * refactor: move `TR_KEY_from_ltep_camel` handling * refactor: move `TR_KEY_from_pex_camel` handling * refactor: move `TR_KEY_from_pex_camel` handling * refactor: move `TR_KEY_tracker_add_camel` handling * refactor: move `TR_KEY_tracker_remove_camel` handling * refactor: move `TR_KEY_tracker_replace_camel` handling * refactor: move `TR_KEY_downloaded_bytes_camel` handling * refactor: move `TR_KEY_files_added_camel` handling * refactor: move `TR_KEY_seconds_active_camel` handling * refactor: move `TR_KEY_session_count_camel` handling * refactor: move `TR_KEY_uploaded_bytes_camel` handling * refactor: move `TR_KEY_active_torrent_count_camel` handling * refactor: move `TR_KEY_cumulative_stats_kebab` handling * refactor: move `TR_KEY_cumulative_stats_kebab` handling * refactor: move `TR_KEY_download_speed_camel` handling * refactor: move `TR_KEY_paused_torrent_count_camel` handling * refactor: move `TR_KEY_torrent_count_camel` handling * refactor: move `TR_KEY_upload_speed_camel` handling * refactor: move `TR_KEY_files_unwanted_kebab` handling * refactor: move `TR_KEY_files_wanted_kebab` handling * refactor: move `TR_KEY_priority_high_kebab` handling * refactor: move `TR_KEY_priority_low_kebab` handling * refactor: move `TR_KEY_priority_normal_kebab` handling * refactor: move `TR_KEY_port_is_open_kebab` handling * refactor: move `TR_KEY_torrent_duplicate_kebab` handling * refactor: move `TR_KEY_torrent_added_kebab` handling * refactor: move `TR_KEY_memory_bytes_kebab` handling * refactor: move `TR_KEY_memory_units_kebab` handling * refactor: move `TR_KEY_size_bytes_kebab` handling * refactor: move `TR_KEY_size_units_kebab` handling * refactor: move `TR_KEY_speed_bytes_kebab` handling * refactor: move `TR_KEY_speed_units_kebab` handling * refactor: remove DoneCb * perf: adjust reserve capacity
336 lines
9.6 KiB
C++
336 lines
9.6 KiB
C++
// This file Copyright © Mnemosyne LLC.
|
|
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
|
// or any future license endorsed by Mnemosyne LLC.
|
|
// License text can be found in the licenses/ folder.
|
|
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include "RpcClient.h"
|
|
|
|
#include <QApplication>
|
|
#include <QAuthenticator>
|
|
#include <QHostAddress>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkReply>
|
|
#include <QNetworkRequest>
|
|
#include <QVersionNumber>
|
|
|
|
#include <libtransmission/api-compat.h>
|
|
#include <libtransmission/rpcimpl.h>
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/version.h> // LONG_VERSION_STRING
|
|
|
|
#include "VariantHelpers.h"
|
|
|
|
using ::trqt::variant_helpers::dictFind;
|
|
namespace api_compat = libtransmission::api_compat;
|
|
|
|
namespace
|
|
{
|
|
|
|
char constexpr const* const RequestBodyKey{ "requestBody" };
|
|
char constexpr const* const RequestFutureinterfacePropertyKey{ "requestReplyFutureInterface" };
|
|
|
|
[[nodiscard]] int64_t nextId()
|
|
{
|
|
static int64_t id = {};
|
|
return id++;
|
|
}
|
|
|
|
[[nodiscard]] std::pair<tr_variant, int64_t> buildRequest(tr_quark const method, tr_variant* params)
|
|
{
|
|
auto const id = nextId();
|
|
|
|
auto req = tr_variant::Map{ 4U };
|
|
req.try_emplace(TR_KEY_jsonrpc, tr_variant::unmanaged_string(JsonRpc::Version));
|
|
req.try_emplace(TR_KEY_method, tr_variant::unmanaged_string(method));
|
|
req.try_emplace(TR_KEY_id, id);
|
|
if (params != nullptr)
|
|
{
|
|
req.try_emplace(TR_KEY_params, params->clone());
|
|
}
|
|
|
|
return { std::move(req), id };
|
|
}
|
|
} // namespace
|
|
|
|
RpcClient::RpcClient(QObject* parent)
|
|
: QObject{ parent }
|
|
{
|
|
qRegisterMetaType<TrVariantPtr>("TrVariantPtr");
|
|
}
|
|
|
|
void RpcClient::stop()
|
|
{
|
|
session_ = nullptr;
|
|
session_id_.clear();
|
|
url_.clear();
|
|
network_style_ = DefaultNetworkStyle;
|
|
|
|
if (nam_ != nullptr)
|
|
{
|
|
nam_->deleteLater();
|
|
nam_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void RpcClient::start(tr_session* session)
|
|
{
|
|
session_ = session;
|
|
}
|
|
|
|
void RpcClient::start(QUrl const& url)
|
|
{
|
|
url_ = url;
|
|
url_is_loopback_ = QHostAddress{ url_.host() }.isLoopback();
|
|
}
|
|
|
|
RpcResponseFuture RpcClient::exec(tr_quark const method, tr_variant* args)
|
|
{
|
|
auto [req, id] = buildRequest(method, args);
|
|
|
|
auto promise = QFutureInterface<RpcResponse>{};
|
|
promise.setExpectedResultCount(1);
|
|
promise.setProgressRange(0, 1);
|
|
promise.setProgressValue(0);
|
|
promise.reportStarted();
|
|
|
|
if (session_ != nullptr)
|
|
{
|
|
sendLocalRequest(req, promise, id);
|
|
}
|
|
else if (!url_.isEmpty())
|
|
{
|
|
api_compat::convert(req, network_style_);
|
|
auto const json = tr_variant_serde::json().compact().to_string(req);
|
|
auto const body = QByteArray::fromStdString(json);
|
|
sendNetworkRequest(body, promise);
|
|
}
|
|
|
|
return promise.future();
|
|
}
|
|
|
|
void RpcClient::sendNetworkRequest(QByteArray const& body, QFutureInterface<RpcResponse> const& promise)
|
|
{
|
|
auto req = QNetworkRequest{};
|
|
QNetworkRequest request;
|
|
request.setUrl(url_);
|
|
request.setRawHeader("User-Agent", "Transmisson/" SHORT_VERSION_STRING);
|
|
if (!session_id_.isEmpty())
|
|
{
|
|
request.setRawHeader(TR_RPC_SESSION_ID_HEADER, session_id_);
|
|
}
|
|
|
|
if (verbose_)
|
|
{
|
|
qInfo() << "sending POST " << qPrintable(url_.path());
|
|
|
|
for (QByteArray const& name : req.rawHeaderList())
|
|
{
|
|
qInfo() << name.constData() << ": " << req.rawHeader(name).constData();
|
|
}
|
|
|
|
qInfo() << "Body:";
|
|
qInfo() << body.constData();
|
|
}
|
|
|
|
if (QNetworkReply* reply = networkAccessManager()->post(req, body))
|
|
{
|
|
reply->setProperty(RequestBodyKey, body);
|
|
reply->setProperty(RequestFutureinterfacePropertyKey, QVariant::fromValue(promise));
|
|
|
|
connect(reply, &QNetworkReply::downloadProgress, this, &RpcClient::dataReadProgress);
|
|
connect(reply, &QNetworkReply::uploadProgress, this, &RpcClient::dataSendProgress);
|
|
}
|
|
}
|
|
|
|
void RpcClient::sendLocalRequest(tr_variant& req, QFutureInterface<RpcResponse> const& promise, int64_t const id)
|
|
{
|
|
if (verbose_)
|
|
{
|
|
fmt::print("{:s}:{:d} sending req:\n{:s}\n", __FILE__, __LINE__, tr_variant_serde::json().to_string(req));
|
|
}
|
|
|
|
local_requests_.try_emplace(id, promise);
|
|
tr_rpc_request_exec(
|
|
session_,
|
|
req,
|
|
[this](tr_session* /*session*/, tr_variant&& response)
|
|
{
|
|
api_compat::convert_incoming_data(response);
|
|
|
|
if (verbose_)
|
|
{
|
|
auto serde = tr_variant_serde::json();
|
|
serde.compact();
|
|
fmt::print("{:s}:{:d} got response:\n{:s}\n", __FILE__, __LINE__, serde.to_string(response));
|
|
}
|
|
|
|
// this callback is invoked in the libtransmission thread, so we don't want
|
|
// to process the response here... let's push it over to the Qt thread.
|
|
auto shared = std::make_shared<tr_variant>(std::move(response));
|
|
QMetaObject::invokeMethod(this, "localRequestFinished", Qt::QueuedConnection, Q_ARG(TrVariantPtr, shared));
|
|
});
|
|
}
|
|
|
|
QNetworkAccessManager* RpcClient::networkAccessManager()
|
|
{
|
|
if (nam_ == nullptr)
|
|
{
|
|
nam_ = new QNetworkAccessManager{};
|
|
|
|
connect(nam_, &QNetworkAccessManager::finished, this, &RpcClient::networkRequestFinished);
|
|
|
|
connect(nam_, &QNetworkAccessManager::authenticationRequired, this, &RpcClient::httpAuthenticationRequired);
|
|
}
|
|
|
|
return nam_;
|
|
}
|
|
|
|
void RpcClient::networkRequestFinished(QNetworkReply* reply)
|
|
{
|
|
reply->deleteLater();
|
|
|
|
auto promise = reply->property(RequestFutureinterfacePropertyKey).value<QFutureInterface<RpcResponse>>();
|
|
|
|
if (verbose_)
|
|
{
|
|
qInfo() << "http response header:";
|
|
|
|
for (QByteArray const& b : reply->rawHeaderList())
|
|
{
|
|
qInfo() << b.constData() << ": " << reply->rawHeader(b).constData();
|
|
}
|
|
}
|
|
|
|
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409 &&
|
|
reply->hasRawHeader(TR_RPC_SESSION_ID_HEADER))
|
|
{
|
|
// we got a 409 telling us our session id has expired.
|
|
// update it and resubmit the request.
|
|
|
|
auto version_str = QString::fromUtf8("unknown");
|
|
|
|
if (reply->hasRawHeader(TR_RPC_RPC_VERSION_HEADER))
|
|
{
|
|
network_style_ = api_compat::Style::Tr5;
|
|
|
|
version_str = QString::fromUtf8(reply->rawHeader(TR_RPC_RPC_VERSION_HEADER));
|
|
if (QVersionNumber::fromString(version_str).majorVersion() > TrRpcVersionSemverMajor)
|
|
{
|
|
fmt::print(
|
|
stderr,
|
|
"Server '{:s}' RPC version is {:s}, which may be incompatible with our version {:s}.\n",
|
|
url_.toDisplayString().toStdString(),
|
|
version_str.toStdString(),
|
|
TrRpcVersionSemver);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
network_style_ = api_compat::Style::Tr4;
|
|
}
|
|
|
|
if (verbose_)
|
|
{
|
|
fmt::print(
|
|
"Server '{:s}' RPC version is {:s}. Using style {:d}\n",
|
|
url_.toDisplayString().toStdString(),
|
|
version_str.toStdString(),
|
|
static_cast<int>(network_style_));
|
|
}
|
|
|
|
session_id_ = reply->rawHeader(TR_RPC_SESSION_ID_HEADER);
|
|
sendNetworkRequest(reply->property(RequestBodyKey).toByteArray(), promise);
|
|
return;
|
|
}
|
|
|
|
emit networkResponse(reply->error(), reply->errorString());
|
|
|
|
if (reply->error() != QNetworkReply::NoError)
|
|
{
|
|
RpcResponse result;
|
|
result.networkError = reply->error();
|
|
|
|
promise.setProgressValueAndText(1, reply->errorString());
|
|
promise.reportFinished(&result);
|
|
}
|
|
else
|
|
{
|
|
auto const json = reply->readAll().trimmed().toStdString();
|
|
|
|
if (verbose_)
|
|
{
|
|
fmt::print("{:s}:{:d} got raw response:\n{:s}\n", __FILE__, __LINE__, json);
|
|
}
|
|
|
|
auto response = RpcResponse{};
|
|
|
|
if (auto var = tr_variant_serde::json().parse(json))
|
|
{
|
|
api_compat::convert_incoming_data(*var);
|
|
|
|
if (verbose_)
|
|
{
|
|
auto serde = tr_variant_serde::json();
|
|
serde.compact();
|
|
fmt::print("{:s}:{:d} compat response:\n{:s}\n", __FILE__, __LINE__, serde.to_string(*var));
|
|
}
|
|
|
|
response = parseResponseData(*var);
|
|
}
|
|
|
|
promise.setProgressValue(1);
|
|
promise.reportFinished(&response);
|
|
}
|
|
}
|
|
|
|
void RpcClient::localRequestFinished(TrVariantPtr response)
|
|
{
|
|
if (auto node = local_requests_.extract(parseResponseId(*response)))
|
|
{
|
|
auto const result = parseResponseData(*response);
|
|
|
|
auto& promise = node.mapped();
|
|
promise.setProgressRange(0, 1);
|
|
promise.setProgressValue(1);
|
|
promise.reportFinished(&result);
|
|
}
|
|
}
|
|
|
|
int64_t RpcClient::parseResponseId(tr_variant& response) const
|
|
{
|
|
return dictFind<int>(&response, TR_KEY_id).value_or(-1);
|
|
}
|
|
|
|
RpcResponse RpcClient::parseResponseData(tr_variant& response) const
|
|
{
|
|
auto ret = RpcResponse{};
|
|
|
|
ret.success = false;
|
|
ret.errmsg = QStringLiteral("unknown error");
|
|
|
|
if (auto* response_map = response.get_if<tr_variant::Map>())
|
|
{
|
|
if (auto* result = response_map->find_if<tr_variant::Map>(TR_KEY_result))
|
|
{
|
|
ret.success = true;
|
|
ret.errmsg.clear();
|
|
ret.args = std::make_shared<tr_variant>(std::move(*result));
|
|
}
|
|
|
|
if (auto* error_map = response_map->find_if<tr_variant::Map>(TR_KEY_error))
|
|
{
|
|
if (auto const errmsg = error_map->value_if<std::string_view>(TR_KEY_message))
|
|
{
|
|
ret.errmsg = QString::fromUtf8(std::data(*errmsg), std::size(*errmsg));
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|