diff --git a/docs/rpc-spec.md b/docs/rpc-spec.md index 993fdd70f..b224badca 100644 --- a/docs/rpc-spec.md +++ b/docs/rpc-spec.md @@ -673,7 +673,7 @@ Response arguments: | Key | Value Type | Description | :-- | :-- | :-- | `port-is-open` | boolean | true if port is open, false if port is closed -| `ipProtocol` | string | `ipv4` if the test was carried out on IPv4, `ipv6` if the test was carried out on IPv6, unset if an error occured +| `ipProtocol` | string | `ipv4` if the test was carried out on IPv4, `ipv6` if the test was carried out on IPv6, unset if it cannot be determined ### 4.5 Session shutdown This method tells the transmission session to shut down. diff --git a/gtk/PrefsDialog.cc b/gtk/PrefsDialog.cc index 6c05ee891..246c14718 100644 --- a/gtk/PrefsDialog.cc +++ b/gtk/PrefsDialog.cc @@ -42,11 +42,14 @@ #include +#include #include #include #include +#include #include #include +#include using namespace libtransmission::Values; @@ -878,10 +881,23 @@ public: TR_DISABLE_COPY_MOVE(NetworkPage) private: + enum PortTestStatus : uint8_t + { + PORT_TEST_UNKNOWN = 0U, + PORT_TEST_CHECKING, + PORT_TEST_OPEN, + PORT_TEST_CLOSED, + PORT_TEST_ERROR + }; + + void portTestSetSensitive(); + void updatePortStatusText(); void onCorePrefsChanged(tr_quark key); - void onPortTested(bool isOpen); + void onPortTested(std::optional result, Session::PortTestIpProtocol ip_protocol); void onPortTest(); + static std::string_view getPortStatusText(PortTestStatus status) noexcept; + private: Glib::RefPtr core_; @@ -891,15 +907,18 @@ private: sigc::connection portTag_; sigc::connection prefsTag_; + + std::array portTestStatus_ = {}; }; void NetworkPage::onCorePrefsChanged(tr_quark const key) { if (key == TR_KEY_peer_port) { - gtr_label_set_text(*portLabel_, _("Status unknown")); - portButton_->set_sensitive(true); - portSpin_->set_sensitive(true); + portTestStatus_[Session::PORT_TEST_IPV4] = PORT_TEST_UNKNOWN; + portTestStatus_[Session::PORT_TEST_IPV6] = PORT_TEST_UNKNOWN; + updatePortStatusText(); + portTestSetSensitive(); } } @@ -909,28 +928,79 @@ NetworkPage::~NetworkPage() portTag_.disconnect(); } -void NetworkPage::onPortTested(bool isOpen) +std::string_view NetworkPage::getPortStatusText(PortTestStatus const status) noexcept { - portLabel_->set_markup(fmt::format( - isOpen ? _("Port is {markup_begin}open{markup_end}") : _("Port is {markup_begin}closed{markup_end}"), - fmt::arg("markup_begin", ""), - fmt::arg("markup_end", ""))); - portButton_->set_sensitive(true); - portSpin_->set_sensitive(true); + switch (status) + { + case PORT_TEST_UNKNOWN: + return C_("Port test status", "unknown"); + case PORT_TEST_CHECKING: + return C_("Port test status", "checking…"); + case PORT_TEST_OPEN: + return C_("Port test status", "open"); + case PORT_TEST_CLOSED: + return C_("Port test status", "closed"); + case PORT_TEST_ERROR: + return C_("Port test status", "error"); + default: + return {}; + } +} + +void NetworkPage::updatePortStatusText() +{ + auto const status_ipv4 = getPortStatusText(portTestStatus_[Session::PORT_TEST_IPV4]); + auto const status_ipv6 = getPortStatusText(portTestStatus_[Session::PORT_TEST_IPV6]); + + portLabel_->set_markup( + portTestStatus_[Session::PORT_TEST_IPV4] == portTestStatus_[Session::PORT_TEST_IPV6] ? + fmt::format(_("Status: {status}"), fmt::arg("status", status_ipv4)) : + fmt::format( + _("Status: {status_ipv4} (IPv4), {status_ipv6} (IPv6)"), + fmt::arg("status_ipv4", status_ipv4), + fmt::arg("status_ipv6", status_ipv6))); +} + +void NetworkPage::portTestSetSensitive() +{ + // Depend on the RPC call status instead of the UI status, so that the widgets + // won't be enabled even if the port peer port changed while we have port-test + // RPC call(s) in-flight. + auto const sensitive = !core_->port_test_pending(Session::PORT_TEST_IPV4) && + !core_->port_test_pending(Session::PORT_TEST_IPV6); + portButton_->set_sensitive(sensitive); + portSpin_->set_sensitive(sensitive); +} + +void NetworkPage::onPortTested(std::optional const result, Session::PortTestIpProtocol const ip_protocol) +{ + // Only update the UI if the current status is "checking", so that + // we won't show the port test results for the old peer port if it + // changed while we have port-test RPC call(s) in-flight. + if (portTestStatus_[ip_protocol] == PORT_TEST_CHECKING) + { + portTestStatus_[ip_protocol] = result ? (*result ? PORT_TEST_OPEN : PORT_TEST_CLOSED) : PORT_TEST_ERROR; + updatePortStatusText(); + } + portTestSetSensitive(); } void NetworkPage::onPortTest() { - portButton_->set_sensitive(false); - portSpin_->set_sensitive(false); - portLabel_->set_text(_("Testing TCP port…")); + portTestStatus_[Session::PORT_TEST_IPV4] = PORT_TEST_CHECKING; + portTestStatus_[Session::PORT_TEST_IPV6] = PORT_TEST_CHECKING; + updatePortStatusText(); if (!portTag_.connected()) { - portTag_ = core_->signal_port_tested().connect([this](bool is_open) { onPortTested(is_open); }); + portTag_ = core_->signal_port_tested().connect( + [this](std::optional status, Session::PortTestIpProtocol ip_protocol) { onPortTested(status, ip_protocol); }); } - core_->port_test(); + core_->port_test(Session::PORT_TEST_IPV4); + core_->port_test(Session::PORT_TEST_IPV6); + + portTestSetSensitive(); } NetworkPage::NetworkPage( @@ -944,6 +1014,7 @@ NetworkPage::NetworkPage( , portSpin_(init_spin_button("listening_port_spin", TR_KEY_peer_port, 1, std::numeric_limits::max(), 1)) { portButton_->signal_clicked().connect([this]() { onPortTest(); }); + updatePortStatusText(); prefsTag_ = core_->signal_prefs_changed().connect([this](auto key) { onCorePrefsChanged(key); }); diff --git a/gtk/Session.cc b/gtk/Session.cc index b56997de3..809f6b842 100644 --- a/gtk/Session.cc +++ b/gtk/Session.cc @@ -44,12 +44,14 @@ #include #include +#include #include // PRId64 #include // strstr #include #include #include #include +#include #include #include #include @@ -74,6 +76,9 @@ public: size_t get_active_torrent_count() const; + bool get_port_test_pending(PortTestIpProtocol ip_protocol); + void set_port_test_pending(bool pending, PortTestIpProtocol ip_protocol); + void update(); void torrents_added(); @@ -169,7 +174,7 @@ private: sigc::signal signal_blocklist_updated_; sigc::signal signal_busy_; sigc::signal signal_prefs_changed_; - sigc::signal signal_port_tested_; + sigc::signal, PortTestIpProtocol)> signal_port_tested_; sigc::signal const&, Torrent::ChangeFlags)> signal_torrents_changed_; Glib::RefPtr monitor_; @@ -182,6 +187,7 @@ private: bool inhibit_allowed_ = false; bool have_inhibit_cookie_ = false; bool dbus_error_ = false; + std::array port_test_pending_ = {}; guint inhibit_cookie_ = 0; gint busy_count_ = 0; Glib::RefPtr> raw_model_; @@ -1220,33 +1226,67 @@ void Session::Impl::send_rpc_request( **** Sending a test-port request via RPC ***/ -void Session::port_test() +void Session::port_test(PortTestIpProtocol const ip_protocol) { - auto const tag = nextTag; - ++nextTag; + static auto constexpr IpStr = std::array{ "ipv4"sv, "ipv6"sv }; + + if (port_test_pending(ip_protocol)) + { + return; + } + 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_ipProtocol, 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)); - tr_variant request; - tr_variantInitDict(&request, 2); - tr_variantDictAddStrView(&request, TR_KEY_method, "port-test"); - tr_variantDictAddInt(&request, TR_KEY_tag, tag); impl_->send_rpc_request( - request, + tr_variant{ std::move(request_map) }, tag, - [this](auto& response) + [this, ip_protocol](tr_variant& response) { - tr_variant* args = nullptr; - bool is_open = false; + impl_->set_port_test_pending(false, ip_protocol); - if (!tr_variantDictFindDict(&response, TR_KEY_arguments, &args) || - !tr_variantDictFindBool(args, TR_KEY_port_is_open, &is_open)) + auto status = std::optional{}; + if (tr_variant* args = nullptr; tr_variantDictFindDict(&response, TR_KEY_arguments, &args)) { - is_open = false; + if (auto result = bool{}; tr_variantDictFindBool(args, TR_KEY_port_is_open, &result)) + { + status = result; + } } - impl_->signal_port_tested().emit(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); }); } +bool Session::port_test_pending(Session::PortTestIpProtocol ip_protocol) const noexcept +{ + return impl_->get_port_test_pending(ip_protocol); +} + +bool Session::Impl::get_port_test_pending(Session::PortTestIpProtocol ip_protocol) +{ + return ip_protocol < NUM_PORT_TEST_IP_PROTOCOL && port_test_pending_[ip_protocol]; +} + +void Session::Impl::set_port_test_pending(bool pending, Session::PortTestIpProtocol ip_protocol) +{ + if (ip_protocol < NUM_PORT_TEST_IP_PROTOCOL) + { + port_test_pending_[ip_protocol] = pending; + } +} + /*** **** Updating a blocklist via RPC ***/ @@ -1386,7 +1426,7 @@ sigc::signal& Session::signal_prefs_changed() return impl_->signal_prefs_changed(); } -sigc::signal& Session::signal_port_tested() +sigc::signal, Session::PortTestIpProtocol)>& Session::signal_port_tested() { return impl_->signal_port_tested(); } diff --git a/gtk/Session.h b/gtk/Session.h index e4799f8bf..6a72e36b6 100644 --- a/gtk/Session.h +++ b/gtk/Session.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,13 @@ public: ERR_NO_MORE_TORRENTS = 1000 /* finished adding a batch */ }; + enum PortTestIpProtocol : uint8_t + { + PORT_TEST_IPV4, + PORT_TEST_IPV6, + NUM_PORT_TEST_IP_PROTOCOL // Must always be the last value + }; + using Model = IF_GTKMM4(Gio::ListModel, Gtk::TreeModel); public: @@ -128,7 +136,8 @@ public: *** **/ - void port_test(); + void port_test(PortTestIpProtocol ip_protocol); + bool port_test_pending(PortTestIpProtocol ip_protocol) const noexcept; void blocklist_update(); @@ -141,7 +150,7 @@ public: sigc::signal& signal_blocklist_updated(); sigc::signal& signal_busy(); sigc::signal& signal_prefs_changed(); - sigc::signal& signal_port_tested(); + sigc::signal, PortTestIpProtocol)>& signal_port_tested(); sigc::signal const&, Torrent::ChangeFlags)>& signal_torrents_changed(); protected: diff --git a/gtk/ui/gtk3/PrefsDialog.ui b/gtk/ui/gtk3/PrefsDialog.ui index 06c1a862c..1683c6142 100644 --- a/gtk/ui/gtk3/PrefsDialog.ui +++ b/gtk/ui/gtk3/PrefsDialog.ui @@ -1245,14 +1245,18 @@ - + True - True - True + False + ... + 0 + + + 1 - 0 + 1 @@ -1262,14 +1266,10 @@ True 12 - + True - False - Status unknown - 0 - - - + True + True True @@ -1295,7 +1295,7 @@ 1 - 1 + 0 diff --git a/gtk/ui/gtk4/PrefsDialog.ui b/gtk/ui/gtk4/PrefsDialog.ui index 523975ea7..c1744eb74 100644 --- a/gtk/ui/gtk4/PrefsDialog.ui +++ b/gtk/ui/gtk4/PrefsDialog.ui @@ -868,12 +868,16 @@ - - 1 + 1 + ... + 0 + + + 1 - 0 + 1 @@ -882,13 +886,13 @@ 1 12 - + + 1 1 - Status unknown - 0 - - - + + 1 + 0 + @@ -901,7 +905,7 @@ 1 - 1 + 0 diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index f4694baa5..b34109f53 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -1144,6 +1144,12 @@ void onPortTested(tr_web::FetchResponse const& web_response) auto const& [status, body, primary_ip, did_connect, did_timeout, user_data] = web_response; auto* data = static_cast(user_data); + if (auto const addr = tr_address::from_string(primary_ip); + data->args_out.find_if(TR_KEY_ipProtocol) == nullptr && addr && addr->is_valid()) + { + data->args_out.try_emplace(TR_KEY_ipProtocol, addr->is_ipv4() ? "ipv4"sv : "ipv6"sv); + } + if (status != 200) { tr_idle_function_done( @@ -1155,30 +1161,30 @@ void onPortTested(tr_web::FetchResponse const& web_response) return; } - auto const addr = tr_address::from_string(primary_ip); - if (!addr || !addr->is_valid()) - { - tr_idle_function_done(data, "Unknown error, please file a bug report to us"); - return; - } - data->args_out.try_emplace(TR_KEY_port_is_open, tr_strv_starts_with(body, '1')); - data->args_out.try_emplace(TR_KEY_ipProtocol, addr->is_ipv4() ? "ipv4"sv : "ipv6"sv); tr_idle_function_done(data, SuccessResult); } char const* portTest(tr_session* session, tr_variant::Map const& args_in, struct tr_rpc_idle_data* idle_data) { - auto ip_proto = tr_web::FetchOptions::IPProtocol::ANY; + static auto constexpr TimeoutSecs = 20s; + + auto const port = session->advertisedPeerPort(); + auto const url = fmt::format("https://portcheck.transmissionbt.com/{:d}", port.host()); + auto options = tr_web::FetchOptions{ url, onPortTested, idle_data }; + options.timeout_secs = TimeoutSecs; + if (auto const* val = args_in.find_if(TR_KEY_ipProtocol); val != nullptr) { if (*val == "ipv4"sv) { - ip_proto = tr_web::FetchOptions::IPProtocol::V4; + options.ip_proto = tr_web::FetchOptions::IPProtocol::V4; + idle_data->args_out.try_emplace(TR_KEY_ipProtocol, "ipv4"sv); } else if (*val == "ipv6"sv) { - ip_proto = tr_web::FetchOptions::IPProtocol::V6; + options.ip_proto = tr_web::FetchOptions::IPProtocol::V6; + idle_data->args_out.try_emplace(TR_KEY_ipProtocol, "ipv6"sv); } else { @@ -1186,10 +1192,6 @@ char const* portTest(tr_session* session, tr_variant::Map const& args_in, struct } } - auto const port = session->advertisedPeerPort(); - auto const url = fmt::format("https://portcheck.transmissionbt.com/{:d}", port.host()); - auto options = tr_web::FetchOptions{ url, onPortTested, idle_data }; - options.ip_proto = ip_proto; session->fetch(std::move(options)); return nullptr; } diff --git a/qt/PrefsDialog.cc b/qt/PrefsDialog.cc index 0be94f1f0..e1b718771 100644 --- a/qt/PrefsDialog.cc +++ b/qt/PrefsDialog.cc @@ -6,6 +6,7 @@ #include "PrefsDialog.h" #include +#include #include #include @@ -426,19 +427,70 @@ void PrefsDialog::initDesktopTab() // --- -void PrefsDialog::onPortTested(bool isOpen) +QString PrefsDialog::getPortStatusText(PrefsDialog::PortTestStatus status) noexcept { - ui_.testPeerPortButton->setEnabled(true); - widgets_[Prefs::PEER_PORT]->setEnabled(true); - ui_.peerPortStatusLabel->setText(isOpen ? tr("Port is open") : tr("Port is closed")); + switch (status) + { + case PORT_TEST_UNKNOWN: + return tr("unknown"); + case PORT_TEST_CHECKING: + return tr("checking…"); + case PORT_TEST_OPEN: + return tr("open"); + case PORT_TEST_CLOSED: + return tr("closed"); + case PORT_TEST_ERROR: + return tr("error"); + default: + return {}; + } +} + +void PrefsDialog::updatePortStatusLabel() +{ + auto const status_ipv4 = getPortStatusText(port_test_status_[Session::PORT_TEST_IPV4]); + auto const status_ipv6 = getPortStatusText(port_test_status_[Session::PORT_TEST_IPV6]); + + ui_.peerPortStatusLabel->setText( + port_test_status_[Session::PORT_TEST_IPV4] == port_test_status_[Session::PORT_TEST_IPV6] ? + tr("Status: %1").arg(status_ipv4) : + tr("Status: %1 (IPv4), %2 (IPv6)").arg(status_ipv4).arg(status_ipv6)); +} + +void PrefsDialog::portTestSetEnabled() +{ + // Depend on the RPC call status instead of the UI status, so that the widgets + // won't be enabled even if the port peer port changed while we have port-test + // RPC call(s) in-flight. + auto const sensitive = !session_.portTestPending(Session::PORT_TEST_IPV4) && + !session_.portTestPending(Session::PORT_TEST_IPV6); + ui_.testPeerPortButton->setEnabled(sensitive); + widgets_[Prefs::PEER_PORT]->setEnabled(sensitive); +} + +void PrefsDialog::onPortTested(std::optional result, Session::PortTestIpProtocol ip_protocol) +{ + // Only update the UI if the current status is "checking", so that + // we won't show the port test results for the old peer port if it + // changed while we have port-test RPC call(s) in-flight. + if (port_test_status_[ip_protocol] == PORT_TEST_CHECKING) + { + port_test_status_[ip_protocol] = result ? (*result ? PORT_TEST_OPEN : PORT_TEST_CLOSED) : PORT_TEST_ERROR; + updatePortStatusLabel(); + } + portTestSetEnabled(); } void PrefsDialog::onPortTest() { - ui_.peerPortStatusLabel->setText(tr("Testing TCP Port…")); - ui_.testPeerPortButton->setEnabled(false); - widgets_[Prefs::PEER_PORT]->setEnabled(false); - session_.portTest(); + port_test_status_[Session::PORT_TEST_IPV4] = PORT_TEST_CHECKING; + port_test_status_[Session::PORT_TEST_IPV6] = PORT_TEST_CHECKING; + updatePortStatusLabel(); + + session_.portTest(Session::PORT_TEST_IPV4); + session_.portTest(Session::PORT_TEST_IPV6); + + portTestSetEnabled(); } void PrefsDialog::initNetworkTab() @@ -464,6 +516,8 @@ void PrefsDialog::initNetworkTab() connect(ui_.testPeerPortButton, &QAbstractButton::clicked, this, &PrefsDialog::onPortTest); connect(&session_, &Session::portTested, this, &PrefsDialog::onPortTested); + + updatePortStatusLabel(); } // --- @@ -781,8 +835,10 @@ void PrefsDialog::refreshPref(int key) } case Prefs::PEER_PORT: - ui_.peerPortStatusLabel->setText(tr("Status unknown")); - ui_.testPeerPortButton->setEnabled(true); + port_test_status_[Session::PORT_TEST_IPV4] = PORT_TEST_UNKNOWN; + port_test_status_[Session::PORT_TEST_IPV6] = PORT_TEST_UNKNOWN; + updatePortStatusLabel(); + portTestSetEnabled(); break; default: diff --git a/qt/PrefsDialog.h b/qt/PrefsDialog.h index 77f3d5429..c2781f4e8 100644 --- a/qt/PrefsDialog.h +++ b/qt/PrefsDialog.h @@ -5,21 +5,21 @@ #pragma once +#include #include +#include #include #include "BaseDialog.h" #include "Prefs.h" +#include "Session.h" #include "ui_PrefsDialog.h" class QHttp; class QMessageBox; class QString; -class Prefs; -class Session; - class PrefsDialog : public BaseDialog { Q_OBJECT @@ -39,7 +39,7 @@ private slots: void encryptionEdited(int); void altSpeedDaysEdited(int); void sessionUpdated(); - void onPortTested(bool); + void onPortTested(std::optional, Session::PortTestIpProtocol); void onPortTest(); void onIdleLimitChanged(); void onQueueStalledMinutesChanged(); @@ -52,11 +52,23 @@ private slots: private: using key2widget_t = std::map; + enum PortTestStatus : uint8_t + { + PORT_TEST_UNKNOWN = 0U, + PORT_TEST_CHECKING, + PORT_TEST_OPEN, + PORT_TEST_CLOSED, + PORT_TEST_ERROR + }; + bool updateWidgetValue(QWidget* widget, int pref_key) const; + void portTestSetEnabled(); void linkWidgetToPref(QWidget* widget, int pref_key); void updateBlocklistLabel(); void updateDownloadingWidgetsLocality(); + void updatePortStatusLabel(); void updateSeedingWidgetsLocality(); + static QString getPortStatusText(PortTestStatus status) noexcept; void setPref(int key, QVariant const& v); @@ -75,6 +87,7 @@ private: bool const is_server_; bool is_local_ = {}; + std::array port_test_status_ = {}; key2widget_t widgets_; QWidgetList web_widgets_; diff --git a/qt/PrefsDialog.ui b/qt/PrefsDialog.ui index 92f0c109f..a029ba69c 100644 --- a/qt/PrefsDialog.ui +++ b/qt/PrefsDialog.ui @@ -650,7 +650,7 @@ - + 1 @@ -660,14 +660,7 @@ - - - - Status unknown - - - - + Te&st Port @@ -677,6 +670,13 @@ + + + + ... + + + diff --git a/qt/Session.cc b/qt/Session.cc index 471b3a5da..b1c0bbffb 100644 --- a/qt/Session.cc +++ b/qt/Session.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,8 @@ #include "Torrent.h" #include "VariantHelpers.h" +using namespace std::literals; + using ::trqt::variant_helpers::dictAdd; using ::trqt::variant_helpers::dictFind; using ::trqt::variant_helpers::getValue; @@ -81,32 +84,43 @@ void Session::sessionSet(tr_quark const key, QVariant const& value) exec("session-set", &args); } -void Session::portTest() +void Session::portTest(Session::PortTestIpProtocol const ip_protocol) { + static auto constexpr IpStr = std::array{ "ipv4"sv, "ipv6"sv }; + + if (portTestPending(ip_protocol)) + { + return; + } + port_test_pending_[ip_protocol] = true; + + auto args = tr_variant::make_map(1U); + tr_variantDictAddStrView(&args, TR_KEY_ipProtocol, IpStr[ip_protocol]); + + auto const response_func = [this, ip_protocol](RpcResponse const& r) + { + port_test_pending_[ip_protocol] = false; + + // 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 + emit portTested(dictFind(r.args.get(), TR_KEY_port_is_open), ip_protocol); + }; + auto* q = new RpcQueue{}; - q->add([this]() { return exec("port-test", nullptr); }); + q->add([this, &args]() { return exec("port-test", &args); }, response_func); - q->add( - [this](RpcResponse const& r) - { - bool is_open = false; - - if (r.success) - { - auto const value = dictFind(r.args.get(), TR_KEY_port_is_open); - if (value) - { - is_open = *value; - } - } - - emit portTested(is_open); - }); + q->add(response_func); q->run(); } +bool Session::portTestPending(Session::PortTestIpProtocol const ip_protocol) const noexcept +{ + return ip_protocol < NUM_PORT_TEST_IP_PROTOCOL && port_test_pending_[ip_protocol]; +} + void Session::copyMagnetLinkToClipboard(int torrent_id) { auto constexpr MagnetLinkKey = std::string_view{ "magnetLink" }; diff --git a/qt/Session.h b/qt/Session.h index d622d4999..70e36d4af 100644 --- a/qt/Session.h +++ b/qt/Session.h @@ -5,8 +5,10 @@ #pragma once +#include #include // int64_t #include +#include #include #include @@ -69,11 +71,20 @@ public: return blocklist_size_; } + enum PortTestIpProtocol : uint8_t + { + PORT_TEST_IPV4, + PORT_TEST_IPV6, + NUM_PORT_TEST_IP_PROTOCOL + }; + void setBlocklistSize(int64_t i); void updateBlocklist(); - void portTest(); + void portTest(PortTestIpProtocol ip_protocol); void copyMagnetLinkToClipboard(int torrent_id); + bool portTestPending(PortTestIpProtocol ip_protocol) const noexcept; + /** returns true if the transmission session is being run inside this client */ bool isServer() const; @@ -130,7 +141,7 @@ public slots: signals: void sourceChanged(); - void portTested(bool is_open); + void portTested(std::optional status, PortTestIpProtocol ip_protocol); void statsUpdated(); void sessionUpdated(); void blocklistUpdated(int); @@ -168,6 +179,7 @@ private: std::map> names_; int64_t blocklist_size_ = -1; + std::array port_test_pending_ = {}; tr_session* session_ = {}; QStringList idle_json_; tr_session_stats stats_ = EmptyStats;