refactor: use the new jsonrpc API for RPC calls in tr-gtk (#7938)

* feat: copy TR_RPC_VERBOSE env var feature from tr-qt
This commit is contained in:
Charles Kerr
2025-12-16 15:20:51 -06:00
committed by GitHub
parent c56723ef1f
commit f8970a9183
7 changed files with 180 additions and 207 deletions

View File

@@ -25,6 +25,7 @@
#include "Utils.h"
#include <libtransmission/transmission.h>
#include <libtransmission/api-compat.h>
#include <libtransmission/log.h>
#include <libtransmission/quark.h>
#include <libtransmission/rpcimpl.h>
@@ -198,7 +199,7 @@ private:
void start_all_torrents();
void pause_all_torrents();
void copy_magnet_link_to_clipboard(Glib::RefPtr<Torrent> const& torrent) const;
bool call_rpc_for_selected_torrents(std::string const& method);
bool call_rpc_for_selected_torrents(tr_quark method);
void remove_selected(bool delete_files);
static tr_rpc_callback_status on_rpc_changed(
@@ -1416,25 +1417,18 @@ void Application::Impl::show_about_dialog()
d->show();
}
bool Application::Impl::call_rpc_for_selected_torrents(std::string const& method)
bool Application::Impl::call_rpc_for_selected_torrents(tr_quark const method)
{
tr_variant top;
bool invoked = false;
auto* session = core_->get_session();
tr_variantInitDict(&top, 2);
tr_variantDictAddStrView(&top, TR_KEY_method, method);
auto* const args = tr_variantDictAddDict(&top, TR_KEY_arguments, 1);
auto* const ids = tr_variantDictAddList(args, TR_KEY_ids, 0);
wind_->for_each_selected_torrent([ids](auto const& torrent) { tr_variantListAddInt(ids, torrent->get_id()); });
if (tr_variantListSize(ids) != 0)
auto const ids = get_selected_torrent_ids();
if (std::empty(ids))
{
tr_rpc_request_exec(session, top, {});
invoked = true;
return false;
}
return invoked;
auto params = tr_variant::Map{ 1U };
params.try_emplace(TR_KEY_ids, Session::to_variant(ids));
core_->exec(method, std::move(params));
return true;
}
void Application::Impl::remove_selected(bool delete_files)
@@ -1447,22 +1441,12 @@ void Application::Impl::remove_selected(bool delete_files)
void Application::Impl::start_all_torrents()
{
auto* session = core_->get_session();
tr_variant request;
tr_variantInitDict(&request, 1);
tr_variantDictAddStrView(&request, TR_KEY_method, tr_quark_get_string_view(TR_KEY_torrent_start_kebab));
tr_rpc_request_exec(session, request, {});
core_->exec(TR_KEY_torrent_start, {});
}
void Application::Impl::pause_all_torrents()
{
auto* session = core_->get_session();
tr_variant request;
tr_variantInitDict(&request, 1);
tr_variantDictAddStrView(&request, TR_KEY_method, tr_quark_get_string_view(TR_KEY_torrent_stop_kebab));
tr_rpc_request_exec(session, request, {});
core_->exec(TR_KEY_torrent_stop, {});
}
void Application::Impl::copy_magnet_link_to_clipboard(Glib::RefPtr<Torrent> const& torrent) const
@@ -1483,6 +1467,38 @@ void gtr_actions_handler(Glib::ustring const& action_name, gpointer user_data)
static_cast<Application::Impl*>(user_data)->actions_handler(action_name);
}
namespace
{
[[nodiscard]] std::optional<tr_quark> get_rpc_method(std::string_view const str)
{
if (auto quark = tr_quark_lookup(str)) // method-name, methodName, method_name
{
quark = tr_quark_convert(*quark); // method_name
switch (*quark)
{
// method_name
case TR_KEY_queue_move_bottom:
case TR_KEY_queue_move_down:
case TR_KEY_queue_move_top:
case TR_KEY_queue_move_up:
case TR_KEY_torrent_reannounce:
case TR_KEY_torrent_start:
case TR_KEY_torrent_start_now:
case TR_KEY_torrent_stop:
case TR_KEY_torrent_verify:
return quark;
default:
break;
}
}
return {};
}
} // namespace
void Application::Impl::actions_handler(Glib::ustring const& action_name)
{
bool changed = false;
@@ -1533,13 +1549,9 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
w->show();
}
}
else if (
// TODO: migrate from _kebab
action_name == "torrent-start" || action_name == "torrent-start-now" || action_name == "torrent-stop" ||
action_name == "torrent-reannounce" || action_name == "torrent-verify" || action_name == "queue-move-top" ||
action_name == "queue-move-up" || action_name == "queue-move-down" || action_name == "queue-move-bottom")
else if (auto const method = get_rpc_method(action_name.raw()))
{
changed = call_rpc_for_selected_torrents(action_name);
changed = call_rpc_for_selected_torrents(*method);
}
else if (action_name == "open-torrent-folder")
{

View File

@@ -109,9 +109,14 @@ private:
void onScrapeToggled();
void onBackupToggled();
void torrent_set_bool(tr_quark key, bool value);
void torrent_set_int(tr_quark key, int value);
void torrent_set_real(tr_quark key, double value);
template<typename T>
void torrent_set_field(tr_quark const key, T value)
{
auto params = tr_variant::Map{ 2U };
params.try_emplace(key, std::forward<T>(value));
params.try_emplace(TR_KEY_ids, Session::to_variant(ids_));
core_->exec(TR_KEY_torrent_set, std::move(params));
}
void refreshInfo(std::vector<tr_torrent*> const& torrents);
void refreshPeers(std::vector<tr_torrent*> const& torrents);
@@ -436,88 +441,34 @@ void DetailsDialog::Impl::refreshOptions(std::vector<tr_torrent*> const& torrent
}
}
void DetailsDialog::Impl::torrent_set_bool(tr_quark key, bool value)
{
tr_variant top;
tr_variantInitDict(&top, 2);
tr_variantDictAddStrView(&top, TR_KEY_method, tr_quark_get_string_view(TR_KEY_torrent_set_kebab));
tr_variant* const args = tr_variantDictAddDict(&top, TR_KEY_arguments, 2);
tr_variantDictAddBool(args, key, value);
tr_variant* const ids = tr_variantDictAddList(args, TR_KEY_ids, ids_.size());
for (auto const id : ids_)
{
tr_variantListAddInt(ids, id);
}
core_->exec(top);
}
void DetailsDialog::Impl::torrent_set_int(tr_quark key, int value)
{
tr_variant top;
tr_variantInitDict(&top, 2);
tr_variantDictAddStrView(&top, TR_KEY_method, tr_quark_get_string_view(TR_KEY_torrent_set_kebab));
tr_variant* const args = tr_variantDictAddDict(&top, TR_KEY_arguments, 2);
tr_variantDictAddInt(args, key, value);
tr_variant* const ids = tr_variantDictAddList(args, TR_KEY_ids, ids_.size());
for (auto const id : ids_)
{
tr_variantListAddInt(ids, id);
}
core_->exec(top);
}
void DetailsDialog::Impl::torrent_set_real(tr_quark key, double value)
{
tr_variant top;
tr_variantInitDict(&top, 2);
tr_variantDictAddStrView(&top, TR_KEY_method, tr_quark_get_string_view(TR_KEY_torrent_set_kebab));
tr_variant* const args = tr_variantDictAddDict(&top, TR_KEY_arguments, 2);
tr_variantDictAddReal(args, key, value);
tr_variant* const ids = tr_variantDictAddList(args, TR_KEY_ids, ids_.size());
for (auto const id : ids_)
{
tr_variantListAddInt(ids, id);
}
core_->exec(top);
}
void DetailsDialog::Impl::options_page_init(Glib::RefPtr<Gtk::Builder> const& /*builder*/)
{
auto const speed_units_kbyps_str = Speed::units().display_name(Speed::Units::KByps);
honor_limits_check_tag_ = honor_limits_check_->signal_toggled().connect(
[this]() { torrent_set_bool(TR_KEY_honors_session_limits_camel, honor_limits_check_->get_active()); });
[this]() { torrent_set_field(TR_KEY_honors_session_limits, honor_limits_check_->get_active()); });
down_limited_check_->set_label(
fmt::format(fmt::runtime(down_limited_check_->get_label().raw()), fmt::arg("speed_units", speed_units_kbyps_str)));
down_limited_check_tag_ = down_limited_check_->signal_toggled().connect(
[this]() { torrent_set_bool(TR_KEY_download_limited_camel, down_limited_check_->get_active()); });
[this]() { torrent_set_field(TR_KEY_download_limited, down_limited_check_->get_active()); });
down_limit_spin_->set_adjustment(Gtk::Adjustment::create(0, 0, std::numeric_limits<int>::max(), 5));
down_limit_spin_tag_ = down_limit_spin_->signal_value_changed().connect(
[this]() { torrent_set_int(TR_KEY_download_limit_camel, down_limit_spin_->get_value_as_int()); });
[this]() { torrent_set_field(TR_KEY_download_limit, down_limit_spin_->get_value_as_int()); });
up_limited_check_->set_label(
fmt::format(fmt::runtime(up_limited_check_->get_label().raw()), fmt::arg("speed_units", speed_units_kbyps_str)));
up_limited_check_tag_ = up_limited_check_->signal_toggled().connect(
[this]() { torrent_set_bool(TR_KEY_upload_limited_camel, up_limited_check_->get_active()); });
[this]() { torrent_set_field(TR_KEY_upload_limited, up_limited_check_->get_active()); });
up_limit_sping_->set_adjustment(Gtk::Adjustment::create(0, 0, std::numeric_limits<int>::max(), 5));
up_limit_spin_tag_ = up_limit_sping_->signal_value_changed().connect(
[this]() { torrent_set_int(TR_KEY_upload_limit_camel, up_limit_sping_->get_value_as_int()); });
[this]() { torrent_set_field(TR_KEY_upload_limit, up_limit_sping_->get_value_as_int()); });
gtr_priority_combo_init(*bandwidth_combo_);
bandwidth_combo_tag_ = bandwidth_combo_->signal_changed().connect(
[this]() { torrent_set_int(TR_KEY_bandwidth_priority_camel, gtr_combo_box_get_active_enum(*bandwidth_combo_)); });
[this]() { torrent_set_field(TR_KEY_bandwidth_priority, gtr_combo_box_get_active_enum(*bandwidth_combo_)); });
gtr_combo_box_set_enum(
*ratio_combo_,
@@ -529,13 +480,13 @@ void DetailsDialog::Impl::options_page_init(Glib::RefPtr<Gtk::Builder> const& /*
ratio_combo_tag_ = ratio_combo_->signal_changed().connect(
[this]()
{
torrent_set_int(TR_KEY_seed_ratio_mode_camel, gtr_combo_box_get_active_enum(*ratio_combo_));
torrent_set_field(TR_KEY_seed_ratio_mode, gtr_combo_box_get_active_enum(*ratio_combo_));
refresh();
});
ratio_spin_->set_adjustment(Gtk::Adjustment::create(0, 0, 1000, .05));
ratio_spin_->set_width_chars(7);
ratio_spin_tag_ = ratio_spin_->signal_value_changed().connect(
[this]() { torrent_set_real(TR_KEY_seed_ratio_limit_camel, ratio_spin_->get_value()); });
[this]() { torrent_set_field(TR_KEY_seed_ratio_limit, ratio_spin_->get_value()); });
gtr_combo_box_set_enum(
*idle_combo_,
@@ -547,16 +498,16 @@ void DetailsDialog::Impl::options_page_init(Glib::RefPtr<Gtk::Builder> const& /*
idle_combo_tag_ = idle_combo_->signal_changed().connect(
[this]()
{
torrent_set_int(TR_KEY_seed_idle_mode_camel, gtr_combo_box_get_active_enum(*idle_combo_));
torrent_set_field(TR_KEY_seed_idle_mode, gtr_combo_box_get_active_enum(*idle_combo_));
refresh();
});
idle_spin_->set_adjustment(Gtk::Adjustment::create(1, 1, 40320, 5));
idle_spin_tag_ = idle_spin_->signal_value_changed().connect(
[this]() { torrent_set_int(TR_KEY_seed_idle_limit_camel, idle_spin_->get_value_as_int()); });
[this]() { torrent_set_field(TR_KEY_seed_idle_limit, idle_spin_->get_value_as_int()); });
max_peers_spin_->set_adjustment(Gtk::Adjustment::create(1, 1, 3000, 5));
max_peers_spin_tag_ = max_peers_spin_->signal_value_changed().connect(
[this]() { torrent_set_int(TR_KEY_peer_limit_kebab, max_peers_spin_->get_value_as_int()); });
[this]() { torrent_set_field(TR_KEY_peer_limit, max_peers_spin_->get_value_as_int()); });
}
/****
@@ -2388,16 +2339,11 @@ void AddTrackerDialog::on_response(int response)
{
if (tr_urlIsValidTracker(url.c_str()))
{
tr_variant top;
tr_variantInitDict(&top, 2);
tr_variantDictAddStrView(&top, TR_KEY_method, tr_quark_get_string_view(TR_KEY_torrent_set_kebab));
auto* const args = tr_variantDictAddDict(&top, TR_KEY_arguments, 2);
tr_variantDictAddInt(args, TR_KEY_id, torrent_id_);
auto* const trackers = tr_variantDictAddList(args, TR_KEY_tracker_add_camel, 1);
tr_variantListAddStr(trackers, url.raw());
core_->exec(top);
// TODO(ckerr) migrate to `TR_KEY_tracker_list`
auto params = tr_variant::Map{ 2U };
params.try_emplace(TR_KEY_ids, Session::to_variant({ torrent_id_ }));
params.try_emplace(TR_KEY_tracker_add, Session::to_variant({ url.raw() }));
core_->exec(TR_KEY_torrent_set, std::move(params));
parent_.refresh();
}
else
@@ -2435,16 +2381,12 @@ void DetailsDialog::Impl::on_tracker_list_remove_button_clicked()
{
auto const torrent_id = iter->get_value(tracker_cols.torrent_id);
auto const tracker_id = iter->get_value(tracker_cols.tracker_id);
tr_variant top;
tr_variantInitDict(&top, 2);
tr_variantDictAddStrView(&top, TR_KEY_method, tr_quark_get_string_view(TR_KEY_torrent_set_kebab));
auto* const args = tr_variantDictAddDict(&top, TR_KEY_arguments, 2);
tr_variantDictAddInt(args, TR_KEY_id, torrent_id);
auto* const trackers = tr_variantDictAddList(args, TR_KEY_tracker_remove_camel, 1);
tr_variantListAddInt(trackers, tracker_id);
core_->exec(top);
// TODO(ckerr): migrate to `TR_KEY_tracker_list`
auto params = tr_variant::Map{ 2U };
params.try_emplace(TR_KEY_ids, Session::to_variant({ torrent_id }));
params.try_emplace(TR_KEY_tracker_remove, Session::to_variant({ tracker_id }));
core_->exec(TR_KEY_torrent_set, std::move(params));
refresh();
}
}

View File

@@ -91,7 +91,7 @@ public:
void remove_torrent(tr_torrent_id_t id, bool delete_files);
void send_rpc_request(tr_variant const& request, int64_t tag, std::function<void(tr_variant&)> const& response_func);
void send_rpc_request(tr_quark method, tr_variant const& params, std::function<void(tr_variant&)> on_response);
void commit_prefs_change(tr_quark key);
@@ -1008,16 +1008,11 @@ void Session::update()
impl_->update();
}
void Session::start_now(tr_torrent_id_t id)
void Session::start_now(tr_torrent_id_t const id)
{
tr_variant top;
tr_variantInitDict(&top, 2);
tr_variantDictAddStrView(&top, TR_KEY_method, "torrent-start-now");
auto* args = tr_variantDictAddDict(&top, TR_KEY_arguments, 1);
auto* ids = tr_variantDictAddList(args, TR_KEY_ids, 1);
tr_variantListAddInt(ids, id);
exec(top);
auto params = tr_variant::Map{ 1U };
params.try_emplace(TR_KEY_ids, to_variant({ id }));
exec(TR_KEY_torrent_start_now, std::move(params));
}
void Session::Impl::update()
@@ -1208,31 +1203,34 @@ void Session::set_pref(tr_quark const key, double newval)
****
***/
/* #define DEBUG_RPC */
namespace
{
int64_t nextTag = 1;
int64_t nextId = 1;
bool const verbose_ = tr_env_key_exists("TR_RPC_VERBOSE");
std::map<int64_t, std::function<void(tr_variant&)>> pendingRequests;
bool core_read_rpc_response_idle(tr_variant& response)
{
if (int64_t tag = 0; tr_variantDictFindInt(&response, TR_KEY_tag, &tag))
if (verbose_)
{
if (auto const data_it = pendingRequests.find(tag); data_it != pendingRequests.end())
{
if (auto const& response_func = data_it->second; response_func)
{
response_func(response);
}
fmt::print("{:s}:{:d} got response:\n{:s}\n", __FILE__, __LINE__, tr_variant_serde::json().to_string(response));
}
pendingRequests.erase(data_it);
}
else
if (auto const* resmap = response.get_if<tr_variant::Map>())
{
if (auto const id = resmap->value_if<int64_t>(TR_KEY_id))
{
gtr_warning(fmt::format(fmt::runtime(_("Couldn't find pending RPC request for tag {tag}")), fmt::arg("tag", tag)));
if (auto const nh = pendingRequests.extract(*id))
{
nh.mapped()(response);
}
else
{
gtr_warning(fmt::format(fmt::runtime(_("Couldn't find pending RPC request for id {id}")), fmt::arg("id", *id)));
}
}
}
@@ -1248,26 +1246,45 @@ void core_read_rpc_response(tr_session* /*session*/, tr_variant&& response)
} // namespace
void Session::Impl::send_rpc_request(
tr_variant const& request,
int64_t tag,
std::function<void(tr_variant&)> const& response_func)
tr_quark const method,
tr_variant const& params,
std::function<void(tr_variant&)> on_response)
{
if (session_ == nullptr)
{
gtr_error("GTK+ client doesn't support connections to remote servers yet.");
return;
}
else
// build the jsonrpc request
auto reqmap = tr_variant::Map{ 4U };
reqmap.try_emplace(TR_KEY_jsonrpc, tr_variant::unmanaged_string(JsonRpc::Version));
reqmap.try_emplace(TR_KEY_method, tr_variant::unmanaged_string(method));
// add params if there are any
if (params.has_value())
{
/* remember this request */
pendingRequests.try_emplace(tag, response_func);
/* make the request */
#ifdef DEBUG_RPC
gtr_message(fmt::format("request: [{}]", tr_variantToStr(request, TR_VARIANT_FMT_JSON_LEAN)));
#endif
tr_rpc_request_exec(session_, request, core_read_rpc_response);
reqmap.try_emplace(TR_KEY_params, params.clone());
}
// add id if we want a response
auto callback = std::function<void(tr_session*, tr_variant&&)>{};
if (on_response)
{
auto const id = nextId++;
pendingRequests.try_emplace(id, std::move(on_response));
reqmap.try_emplace(TR_KEY_id, id);
callback = core_read_rpc_response;
}
auto req = tr_variant{ std::move(reqmap) };
if (verbose_)
{
fmt::print("{:s}:{:d} sending req:\n{:s}\n", __FILE__, __LINE__, tr_variant_serde::json().to_string(req));
}
tr_rpc_request_exec(session_, req, std::move(callback));
}
/***
@@ -1284,36 +1301,30 @@ void Session::port_test(PortTestIpProtocol const ip_protocol)
}
impl_->set_port_test_pending(true, ip_protocol);
auto const tag = nextTag++;
auto arguments_map = tr_variant::Map{ 1U };
arguments_map.try_emplace(TR_KEY_ip_protocol, tr_variant::unmanaged_string(IpStr[ip_protocol]));
auto request_map = tr_variant::Map{ 3U };
request_map.try_emplace(TR_KEY_method, tr_variant::unmanaged_string("port-test"sv));
request_map.try_emplace(TR_KEY_tag, tag);
request_map.try_emplace(TR_KEY_arguments, std::move(arguments_map));
auto params = tr_variant::Map{ 1U };
params.try_emplace(TR_KEY_ip_protocol, tr_variant::unmanaged_string(IpStr[ip_protocol]));
impl_->send_rpc_request(
tr_variant{ std::move(request_map) },
tag,
TR_KEY_port_test,
std::move(params),
[this, ip_protocol](tr_variant& response)
{
impl_->set_port_test_pending(false, ip_protocol);
auto status = std::optional<bool>{};
if (tr_variant* args = nullptr; tr_variantDictFindDict(&response, TR_KEY_arguments, &args))
auto is_open = std::optional<bool>();
if (auto const* resmap = response.get_if<tr_variant::Map>())
{
if (auto result = bool{}; tr_variantDictFindBool(args, TR_KEY_port_is_open_kebab, &result))
if (auto const* result = resmap->find_if<tr_variant::Map>(TR_KEY_result))
{
status = result;
is_open = result->value_if<bool>(TR_KEY_port_is_open);
}
}
// If for whatever reason the status optional is empty here,
// then something must have gone wrong with the port test,
// so the UI should show the "error" state
impl_->signal_port_tested().emit(status, ip_protocol);
impl_->signal_port_tested().emit(is_open, ip_protocol);
});
}
@@ -1341,46 +1352,35 @@ void Session::Impl::set_port_test_pending(bool pending, Session::PortTestIpProto
void Session::blocklist_update()
{
auto const tag = nextTag;
++nextTag;
tr_variant request;
tr_variantInitDict(&request, 2);
tr_variantDictAddStrView(&request, TR_KEY_method, "blocklist-update");
tr_variantDictAddInt(&request, TR_KEY_tag, tag);
impl_->send_rpc_request(
request,
tag,
[this](auto& response)
TR_KEY_blocklist_update,
tr_variant{}, // no params
[this](tr_variant& response)
{
tr_variant* args = nullptr;
int64_t ruleCount = 0;
std::optional<int64_t> n_rules;
if (!tr_variantDictFindDict(&response, TR_KEY_arguments, &args) ||
!tr_variantDictFindInt(args, TR_KEY_blocklist_size_kebab, &ruleCount))
if (auto const* resmap = response.get_if<tr_variant::Map>())
{
ruleCount = -1;
if (auto const* result = resmap->find_if<tr_variant::Map>(TR_KEY_result))
{
n_rules = result->value_if<int64_t>(TR_KEY_blocklist_size);
}
}
if (ruleCount > 0)
if (n_rules.has_value())
{
gtr_pref_int_set(TR_KEY_blocklist_date, tr_time());
}
impl_->signal_blocklist_updated().emit(ruleCount >= 0);
impl_->signal_blocklist_updated().emit(*n_rules >= 0);
});
}
/***
****
***/
// ---
void Session::exec(tr_variant const& request)
void Session::exec(tr_quark method, tr_variant const& params)
{
auto const tag = nextTag;
++nextTag;
impl_->send_rpc_request(request, tag, {});
impl_->send_rpc_request(method, params, {});
}
/***

View File

@@ -134,16 +134,35 @@ public:
void set_pref(tr_quark key, int val);
void set_pref(tr_quark key, double val);
/**
***
**/
// ---
// Helper for building RPC payloads.
// TODO(C++20): fold these two into a single std::span method
template<typename T>
[[nodiscard]] static auto to_variant(std::vector<T> const& items)
{
auto vec = tr_variant::Vector{};
vec.reserve(std::size(items));
for (auto const& item : items)
{
vec.emplace_back(item);
}
return vec;
}
template<typename T>
[[nodiscard]] static auto to_variant(std::initializer_list<T> items)
{
return to_variant(std::vector<T>{ std::move(items) });
}
// ---
void port_test(PortTestIpProtocol ip_protocol);
bool port_test_pending(PortTestIpProtocol ip_protocol) const noexcept;
void blocklist_update();
void exec(tr_variant const& request);
void exec(tr_quark method, tr_variant const& params);
void open_folder(tr_torrent_id_t torrent_id) const;

View File

@@ -691,7 +691,7 @@ uint64_t tr_ntohll(uint64_t netlonglong)
// --- ENVIRONMENT
bool tr_env_key_exists(char const* key)
bool tr_env_key_exists(char const* key) noexcept
{
TR_ASSERT(key != nullptr);

View File

@@ -286,7 +286,7 @@ constexpr void tr_timeUpdate(time_t now) noexcept
// ---
/** @brief Check if environment variable exists. */
[[nodiscard]] bool tr_env_key_exists(char const* key);
[[nodiscard]] bool tr_env_key_exists(char const* key) noexcept;
/** @brief Get environment variable value as string. */
[[nodiscard]] std::string tr_env_get_string(std::string_view key, std::string_view default_value = {});

View File

@@ -230,7 +230,7 @@ public:
return it->second.value_if<Type>();
}
return {};
return std::nullopt;
}
template<typename Type>
@@ -241,7 +241,7 @@ public:
return it->second.value_if<Type>();
}
return {};
return std::nullopt;
}
private: