diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 7e1b9dc6c..8255f2055 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -468,6 +468,9 @@ namespace BitTorrent virtual void topTorrentsQueuePos(const QList &ids) = 0; virtual void bottomTorrentsQueuePos(const QList &ids) = 0; + virtual QString lastExternalIPv4Address() const = 0; + virtual QString lastExternalIPv6Address() const = 0; + signals: void startupProgressUpdated(int progress); void addTorrentFailed(const InfoHash &infoHash, const QString &reason); diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index cc0796df8..d54cb8c50 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -4967,6 +4967,16 @@ void SessionImpl::setTrackerFilteringEnabled(const bool enabled) } } +QString SessionImpl::lastExternalIPv4Address() const +{ + return m_lastExternalIPv4Address; +} + +QString SessionImpl::lastExternalIPv6Address() const +{ + return m_lastExternalIPv6Address; +} + bool SessionImpl::isListening() const { return m_nativeSessionExtension->isSessionListening(); @@ -5988,11 +5998,19 @@ void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *alert) LogMsg(tr("Detected external IP. IP: \"%1\"") .arg(externalIP), Log::INFO); - if (m_lastExternalIP != externalIP) + const bool isIPv6 = alert->external_address.is_v6(); + const bool isIPv4 = alert->external_address.is_v4(); + if (isIPv6 && (externalIP != m_lastExternalIPv6Address)) { - if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty()) + if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIPv6Address.isEmpty()) reannounceToAllTrackers(); - m_lastExternalIP = externalIP; + m_lastExternalIPv6Address = externalIP; + } + else if (isIPv4 && (externalIP != m_lastExternalIPv4Address)) + { + if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIPv4Address.isEmpty()) + reannounceToAllTrackers(); + m_lastExternalIPv4Address = externalIP; } } diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index 0a1d13693..83f36e668 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -443,6 +443,9 @@ namespace BitTorrent void topTorrentsQueuePos(const QList &ids) override; void bottomTorrentsQueuePos(const QList &ids) override; + QString lastExternalIPv4Address() const override; + QString lastExternalIPv6Address() const override; + // Torrent interface void handleTorrentResumeDataRequested(const TorrentImpl *torrent); void handleTorrentShareLimitChanged(TorrentImpl *torrent); @@ -813,7 +816,8 @@ namespace BitTorrent QList m_moveStorageQueue; - QString m_lastExternalIP; + QString m_lastExternalIPv4Address; + QString m_lastExternalIPv6Address; bool m_needUpgradeDownloadPath = false; diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 793848c98..ed9ecd316 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -359,6 +359,19 @@ void Preferences::setStatusbarDisplayed(const bool displayed) setValue(u"Preferences/General/StatusbarDisplayed"_s, displayed); } +bool Preferences::isStatusbarExternalIPDisplayed() const +{ + return value(u"Preferences/General/StatusbarExternalIPDisplayed"_s, false); +} + +void Preferences::setStatusbarExternalIPDisplayed(const bool displayed) +{ + if (displayed == isStatusbarExternalIPDisplayed()) + return; + + setValue(u"Preferences/General/StatusbarExternalIPDisplayed"_s, displayed); +} + bool Preferences::isSplashScreenDisabled() const { return value(u"Preferences/General/NoSplashScreen"_s, true); diff --git a/src/base/preferences.h b/src/base/preferences.h index 3aa369576..b1a912477 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -119,6 +119,8 @@ public: void setHideZeroComboValues(int n); bool isStatusbarDisplayed() const; void setStatusbarDisplayed(bool displayed); + bool isStatusbarExternalIPDisplayed() const; + void setStatusbarExternalIPDisplayed(bool displayed); bool isToolbarDisplayed() const; void setToolbarDisplayed(bool displayed); bool isSplashScreenDisabled() const; diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index 63b185ed3..a96b353a6 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -352,6 +352,7 @@ void OptionsDialog::loadBehaviorTabOptions() // Groupbox's check state must be initialized after some of its children if they are manually enabled/disabled m_ui->checkFileLog->setChecked(app()->isFileLoggerEnabled()); + m_ui->checkBoxExternalIPStatusBar->setChecked(pref->isStatusbarExternalIPDisplayed()); m_ui->checkBoxPerformanceWarning->setChecked(session->isPerformanceWarningEnabled()); connect(m_ui->comboLanguage, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); @@ -439,6 +440,7 @@ void OptionsDialog::loadBehaviorTabOptions() connect(m_ui->spinFileLogAge, qSpinBoxValueChanged, this, &ThisType::enableApplyButton); connect(m_ui->comboFileLogAgeType, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); + connect(m_ui->checkBoxExternalIPStatusBar, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBoxPerformanceWarning, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); } @@ -534,6 +536,7 @@ void OptionsDialog::saveBehaviorTabOptions() const app()->setStartUpWindowState(m_ui->windowStateComboBox->currentData().value()); + pref->setStatusbarExternalIPDisplayed(m_ui->checkBoxExternalIPStatusBar->isChecked()); session->setPerformanceWarningEnabled(m_ui->checkBoxPerformanceWarning->isChecked()); } diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index b122b429f..91bac21fc 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -814,6 +814,13 @@ + + + + Show external IP in status bar + + + diff --git a/src/gui/statusbar.cpp b/src/gui/statusbar.cpp index 8eef15e7b..9f4070808 100644 --- a/src/gui/statusbar.cpp +++ b/src/gui/statusbar.cpp @@ -38,6 +38,7 @@ #include "base/bittorrent/session.h" #include "base/bittorrent/sessionstatus.h" +#include "base/preferences.h" #include "base/utils/misc.h" #include "speedlimitdialog.h" #include "uithememanager.h" @@ -86,6 +87,9 @@ StatusBar::StatusBar(QWidget *parent) m_upSpeedLbl->setStyleSheet(u"text-align:left;"_s); m_upSpeedLbl->setMinimumWidth(200); + m_lastExternalIPsLbl = new QLabel(tr("External IP: N/A")); + m_lastExternalIPsLbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + m_DHTLbl = new QLabel(tr("DHT: %1 nodes").arg(0), this); m_DHTLbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); @@ -129,24 +133,33 @@ StatusBar::StatusBar(QWidget *parent) #ifndef Q_OS_MACOS statusSep4->setFrameShadow(QFrame::Raised); #endif - layout->addWidget(m_DHTLbl); + QFrame *statusSep5 = new QFrame(this); + statusSep5->setFrameStyle(QFrame::VLine); +#ifndef Q_OS_MACOS + statusSep5->setFrameShadow(QFrame::Raised); +#endif + layout->addWidget(m_lastExternalIPsLbl); layout->addWidget(statusSep1); - layout->addWidget(m_connecStatusLblIcon); + layout->addWidget(m_DHTLbl); layout->addWidget(statusSep2); + layout->addWidget(m_connecStatusLblIcon); + layout->addWidget(statusSep3); layout->addWidget(m_altSpeedsBtn); layout->addWidget(statusSep4); layout->addWidget(m_dlSpeedLbl); - layout->addWidget(statusSep3); + layout->addWidget(statusSep5); layout->addWidget(m_upSpeedLbl); addPermanentWidget(container); setStyleSheet(u"QWidget {margin: 0;}"_s); container->adjustSize(); adjustSize(); + updateExternalAddressesVisibility(); // Is DHT enabled m_DHTLbl->setVisible(session->isDHTEnabled()); refresh(); connect(session, &BitTorrent::Session::statsUpdated, this, &StatusBar::refresh); + connect(Preferences::instance(), &Preferences::changed, this, &StatusBar::optionsSaved); } StatusBar::~StatusBar() @@ -212,6 +225,28 @@ void StatusBar::updateDHTNodesNumber() } } +void StatusBar::updateExternalAddressesLabel() +{ + const QString lastExternalIPv4Address = BitTorrent::Session::instance()->lastExternalIPv4Address(); + const QString lastExternalIPv6Address = BitTorrent::Session::instance()->lastExternalIPv6Address(); + QString addressText = tr("External IP: N/A"); + + const bool hasIPv4Address = !lastExternalIPv4Address.isEmpty(); + const bool hasIPv6Address = !lastExternalIPv6Address.isEmpty(); + + if (hasIPv4Address && hasIPv6Address) + addressText = tr("External IPs: %1, %2").arg(lastExternalIPv4Address, lastExternalIPv6Address); + else if (hasIPv4Address || hasIPv6Address) + addressText = tr("External IP: %1%2").arg(lastExternalIPv4Address, lastExternalIPv6Address); + + m_lastExternalIPsLbl->setText(addressText); +} + +void StatusBar::updateExternalAddressesVisibility() +{ + m_lastExternalIPsLbl->setVisible(Preferences::instance()->isStatusbarExternalIPDisplayed()); +} + void StatusBar::updateSpeedLabels() { const BitTorrent::SessionStatus &sessionStatus = BitTorrent::Session::instance()->status(); @@ -235,6 +270,7 @@ void StatusBar::refresh() { updateConnectionStatus(); updateDHTNodesNumber(); + updateExternalAddressesLabel(); updateSpeedLabels(); } @@ -261,3 +297,8 @@ void StatusBar::capSpeed() dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->open(); } + +void StatusBar::optionsSaved() +{ + updateExternalAddressesVisibility(); +} diff --git a/src/gui/statusbar.h b/src/gui/statusbar.h index be5f993d0..d46846707 100644 --- a/src/gui/statusbar.h +++ b/src/gui/statusbar.h @@ -58,14 +58,18 @@ private slots: void refresh(); void updateAltSpeedsBtn(bool alternative); void capSpeed(); + void optionsSaved(); private: void updateConnectionStatus(); void updateDHTNodesNumber(); + void updateExternalAddressesLabel(); + void updateExternalAddressesVisibility(); void updateSpeedLabels(); QPushButton *m_dlSpeedLbl = nullptr; QPushButton *m_upSpeedLbl = nullptr; + QLabel *m_lastExternalIPsLbl = nullptr; QLabel *m_DHTLbl = nullptr; QPushButton *m_connecStatusLblIcon = nullptr; QPushButton *m_altSpeedsBtn = nullptr; diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 14517be7d..c56ab79fa 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -136,6 +136,7 @@ void AppController::preferencesAction() // Language data[u"locale"_s] = pref->getLocale(); data[u"performance_warning"_s] = session->isPerformanceWarningEnabled(); + data[u"status_bar_external_ip"_s] = pref->isStatusbarExternalIPDisplayed(); // Transfer List data[u"confirm_torrent_deletion"_s] = pref->confirmTorrentDeletion(); // Log file @@ -519,6 +520,8 @@ void AppController::setPreferencesAction() pref->setLocale(locale); } } + if (hasKey(u"status_bar_external_ip"_s)) + pref->setStatusbarExternalIPDisplayed(it.value().toBool()); if (hasKey(u"performance_warning"_s)) session->setPerformanceWarningEnabled(it.value().toBool()); // Transfer List diff --git a/src/webui/api/synccontroller.cpp b/src/webui/api/synccontroller.cpp index f493fe776..96ef9dd28 100644 --- a/src/webui/api/synccontroller.cpp +++ b/src/webui/api/synccontroller.cpp @@ -85,6 +85,8 @@ namespace const QString KEY_TRANSFER_DLRATELIMIT = u"dl_rate_limit"_s; const QString KEY_TRANSFER_DLSPEED = u"dl_info_speed"_s; const QString KEY_TRANSFER_FREESPACEONDISK = u"free_space_on_disk"_s; + const QString KEY_TRANSFER_LAST_EXTERNAL_ADDRESS_V4 = u"last_external_address_v4"_s; + const QString KEY_TRANSFER_LAST_EXTERNAL_ADDRESS_V6 = u"last_external_address_v6"_s; const QString KEY_TRANSFER_UPDATA = u"up_info_data"_s; const QString KEY_TRANSFER_UPRATELIMIT = u"up_rate_limit"_s; const QString KEY_TRANSFER_UPSPEED = u"up_info_speed"_s; @@ -159,6 +161,8 @@ namespace map[KEY_TRANSFER_AVERAGE_TIME_QUEUE] = cacheStatus.averageJobTime; map[KEY_TRANSFER_TOTAL_QUEUED_SIZE] = cacheStatus.queuedBytes; + map[KEY_TRANSFER_LAST_EXTERNAL_ADDRESS_V4] = session->lastExternalIPv4Address(); + map[KEY_TRANSFER_LAST_EXTERNAL_ADDRESS_V6] = session->lastExternalIPv6Address(); map[KEY_TRANSFER_DHT_NODES] = sessionStatus.dhtNodes; map[KEY_TRANSFER_CONNECTION_STATUS] = session->isListening() ? (sessionStatus.hasIncomingConnections ? u"connected"_s : u"firewalled"_s) @@ -446,6 +450,8 @@ void SyncController::updateFreeDiskSpace(const qint64 freeDiskSpace) // - "dl_info_data": bytes downloaded // - "dl_info_speed": download speed // - "dl_rate_limit: download rate limit +// - "last_external_address_v4": last external address v4 +// - "last_external_address_v6": last external address v6 // - "up_info_data: bytes uploaded // - "up_info_speed: upload speed // - "up_rate_limit: upload speed limit diff --git a/src/webui/api/transfercontroller.cpp b/src/webui/api/transfercontroller.cpp index 6bb9abea2..66ecb2747 100644 --- a/src/webui/api/transfercontroller.cpp +++ b/src/webui/api/transfercontroller.cpp @@ -45,6 +45,8 @@ const QString KEY_TRANSFER_DLRATELIMIT = u"dl_rate_limit"_s; const QString KEY_TRANSFER_UPSPEED = u"up_info_speed"_s; const QString KEY_TRANSFER_UPDATA = u"up_info_data"_s; const QString KEY_TRANSFER_UPRATELIMIT = u"up_rate_limit"_s; +const QString KEY_TRANSFER_LAST_EXTERNAL_ADDRESS_V4 = u"last_external_address_v4"_s; +const QString KEY_TRANSFER_LAST_EXTERNAL_ADDRESS_V6 = u"last_external_address_v6"_s; const QString KEY_TRANSFER_DHT_NODES = u"dht_nodes"_s; const QString KEY_TRANSFER_CONNECTION_STATUS = u"connection_status"_s; @@ -57,11 +59,14 @@ const QString KEY_TRANSFER_CONNECTION_STATUS = u"connection_status"_s; // - "up_info_data": Data uploaded this session // - "dl_rate_limit": Download rate limit // - "up_rate_limit": Upload rate limit +// - "last_external_address_v4": external IPv4 address +// - "last_external_address_v6": external IPv6 address // - "dht_nodes": DHT nodes connected to // - "connection_status": Connection status void TransferController::infoAction() { - const BitTorrent::SessionStatus &sessionStatus = BitTorrent::Session::instance()->status(); + const auto *btSession = BitTorrent::Session::instance(); + const BitTorrent::SessionStatus &sessionStatus = btSession->status(); QJsonObject dict; @@ -69,10 +74,12 @@ void TransferController::infoAction() dict[KEY_TRANSFER_DLDATA] = static_cast(sessionStatus.totalPayloadDownload); dict[KEY_TRANSFER_UPSPEED] = static_cast(sessionStatus.payloadUploadRate); dict[KEY_TRANSFER_UPDATA] = static_cast(sessionStatus.totalPayloadUpload); - dict[KEY_TRANSFER_DLRATELIMIT] = BitTorrent::Session::instance()->downloadSpeedLimit(); - dict[KEY_TRANSFER_UPRATELIMIT] = BitTorrent::Session::instance()->uploadSpeedLimit(); + dict[KEY_TRANSFER_DLRATELIMIT] = btSession->downloadSpeedLimit(); + dict[KEY_TRANSFER_UPRATELIMIT] = btSession->uploadSpeedLimit(); + dict[KEY_TRANSFER_LAST_EXTERNAL_ADDRESS_V4] = btSession->lastExternalIPv4Address(); + dict[KEY_TRANSFER_LAST_EXTERNAL_ADDRESS_V6] = btSession->lastExternalIPv6Address(); dict[KEY_TRANSFER_DHT_NODES] = static_cast(sessionStatus.dhtNodes); - if (!BitTorrent::Session::instance()->isListening()) + if (!btSession->isListening()) dict[KEY_TRANSFER_CONNECTION_STATUS] = u"disconnected"_s; else dict[KEY_TRANSFER_CONNECTION_STATUS] = sessionStatus.hasIncomingConnections ? u"connected"_s : u"firewalled"_s; diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index 59cac3b67..ac9bb2327 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -278,8 +278,10 @@ - - + + + + QBT_TR(Connection status: Firewalled)QBT_TR[CONTEXT=MainWindow] diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index ed6eb51fd..3a9ba783b 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -956,6 +956,28 @@ window.addEventListener("DOMContentLoaded", () => { $("freeSpaceOnDisk").textContent = "QBT_TR(Free space: %1)QBT_TR[CONTEXT=HttpServer]".replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.free_space_on_disk)); + const externalIPsElement = document.getElementById("externalIPs"); + if (window.qBittorrent.Cache.preferences.get().status_bar_external_ip) { + const lastExternalAddressV4 = serverState.last_external_address_v4; + const lastExternalAddressV6 = serverState.last_external_address_v6; + const hasIPv4Address = lastExternalAddressV4 !== ""; + const hasIPv6Address = lastExternalAddressV6 !== ""; + let lastExternalAddressLabel = "QBT_TR(External IP: N/A)QBT_TR[CONTEXT=HttpServer]"; + if (hasIPv4Address && hasIPv6Address) + lastExternalAddressLabel = "QBT_TR(External IPs: %1, %2)QBT_TR[CONTEXT=HttpServer]"; + else if (hasIPv4Address || hasIPv6Address) + lastExternalAddressLabel = "QBT_TR(External IP: %1%2)QBT_TR[CONTEXT=HttpServer]"; + // replace in reverse order ('%2' before '%1') in case address contains a % character. + // for example, see https://en.wikipedia.org/wiki/IPv6_address#Scoped_literal_IPv6_addresses_(with_zone_index) + externalIPsElement.textContent = lastExternalAddressLabel.replace("%2", lastExternalAddressV6).replace("%1", lastExternalAddressV4); + externalIPsElement.classList.remove("invisible"); + externalIPsElement.previousElementSibling.classList.remove("invisible"); + } + else { + externalIPsElement.classList.add("invisible"); + externalIPsElement.previousElementSibling.classList.add("invisible"); + } + const dhtElement = document.getElementById("DHTNodes"); if (window.qBittorrent.Cache.preferences.get().dht) { dhtElement.textContent = "QBT_TR(DHT: %1 nodes)QBT_TR[CONTEXT=StatusBar]".replace("%1", serverState.dht_nodes); diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index e9bb537e9..3776e6621 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -90,6 +90,11 @@ +
+ + +
+
@@ -2157,6 +2162,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD // Language updateWebuiLocaleSelect(pref.locale); updateColoSchemeSelect(); + $("statusBarExternalIP").checked = pref.status_bar_external_ip; $("performanceWarning").checked = pref.performance_warning; document.getElementById("displayFullURLTrackerColumn").checked = (LocalPreferences.get("full_url_tracker_column", "false") === "true"); document.getElementById("hideZeroFiltersCheckbox").checked = (LocalPreferences.get("hide_zero_status_filters", "false") === "true"); @@ -2582,6 +2588,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD LocalPreferences.set("color_scheme", "light"); else LocalPreferences.set("color_scheme", "dark"); + settings["status_bar_external_ip"] = $("statusBarExternalIP").checked; settings["performance_warning"] = $("performanceWarning").checked; LocalPreferences.set("full_url_tracker_column", document.getElementById("displayFullURLTrackerColumn").checked.toString()); LocalPreferences.set("hide_zero_status_filters", document.getElementById("hideZeroFiltersCheckbox").checked.toString());