diff --git a/.tx/config b/.tx/config index 2b8d3296e..28fa38bf9 100644 --- a/.tx/config +++ b/.tx/config @@ -3,7 +3,7 @@ host = https://www.transifex.com [qbittorrent.qbittorrent_master] file_filter = src/lang/qbittorrent_.ts -lang_map = pt: pt_PT +lang_map = pt: pt_PT, zh: zh_CN source_file = src/lang/qbittorrent_en.ts source_lang = en type = QT @@ -19,7 +19,7 @@ mode = developer [qbittorrent.qbittorrent_webui] file_filter = src/webui/www/translations/webui_.ts -lang_map = pt: pt_PT +lang_map = pt: pt_PT, zh: zh_CN source_file = src/webui/www/translations/webui_en.ts source_lang = en type = QT diff --git a/src/app/application.cpp b/src/app/application.cpp index 4af731821..1b1bcfb85 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -882,7 +882,7 @@ void Application::createStartupProgressDialog() m_startupProgressDialog = new QProgressDialog(tr("Loading torrents..."), tr("Exit"), 0, 100); m_startupProgressDialog->setAttribute(Qt::WA_DeleteOnClose); m_startupProgressDialog->setWindowFlag(Qt::WindowMinimizeButtonHint); - m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediatelly by default + m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediately by default m_startupProgressDialog->setAutoReset(false); m_startupProgressDialog->setAutoClose(false); diff --git a/src/base/bittorrent/bencoderesumedatastorage.cpp b/src/base/bittorrent/bencoderesumedatastorage.cpp index f500c5e2d..5c13d0926 100644 --- a/src/base/bittorrent/bencoderesumedatastorage.cpp +++ b/src/base/bittorrent/bencoderesumedatastorage.cpp @@ -210,7 +210,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre return nonstd::make_unexpected(tr("Cannot parse resume data: invalid format")); LoadTorrentParams torrentParams; - torrentParams.restored = true; torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category")); torrentParams.name = fromLTString(resumeDataRoot.dict_find_string_value("qBt-name")); torrentParams.hasSeedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus"); diff --git a/src/base/bittorrent/dbresumedatastorage.cpp b/src/base/bittorrent/dbresumedatastorage.cpp index 2a3d9e0a8..dfaece90e 100644 --- a/src/base/bittorrent/dbresumedatastorage.cpp +++ b/src/base/bittorrent/dbresumedatastorage.cpp @@ -196,7 +196,6 @@ namespace BitTorrent LoadTorrentParams parseQueryResultRow(const QSqlQuery &query) { LoadTorrentParams resumeData; - resumeData.restored = true; resumeData.name = query.value(DB_COLUMN_NAME.name).toString(); resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString(); const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString(); diff --git a/src/base/bittorrent/loadtorrentparams.h b/src/base/bittorrent/loadtorrentparams.h index f49870c89..ca3d914fb 100644 --- a/src/base/bittorrent/loadtorrentparams.h +++ b/src/base/bittorrent/loadtorrentparams.h @@ -58,7 +58,5 @@ namespace BitTorrent qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; - - bool restored = false; // is existing torrent job? }; } diff --git a/src/base/bittorrent/peerinfo.cpp b/src/base/bittorrent/peerinfo.cpp index c3e66d7c8..45b8a158a 100644 --- a/src/base/bittorrent/peerinfo.cpp +++ b/src/base/bittorrent/peerinfo.cpp @@ -181,6 +181,31 @@ QString PeerInfo::client() const return QString::fromStdString(m_nativeInfo.client); } +QString PeerInfo::peerIdClient() const +{ + // when peer ID is not known yet it contains only zero bytes, + // do not create string in such case, return empty string instead + if (m_nativeInfo.pid.is_all_zeros()) + return {}; + + QString result; + + // interesting part of a typical peer ID is first 8 chars + for (int i = 0; i < 8; ++i) + { + const std::uint8_t c = m_nativeInfo.pid[i]; + + // ensure that the peer ID slice consists only of printable ASCII characters, + // this should filter out most of the improper IDs + if ((c < 32) || (c > 126)) + return tr("Unknown"); + + result += QChar::fromLatin1(c); + } + + return result; +} + qreal PeerInfo::progress() const { return m_nativeInfo.progress; diff --git a/src/base/bittorrent/peerinfo.h b/src/base/bittorrent/peerinfo.h index f7f51d177..6c7cb7b1c 100644 --- a/src/base/bittorrent/peerinfo.h +++ b/src/base/bittorrent/peerinfo.h @@ -78,6 +78,7 @@ namespace BitTorrent PeerAddress address() const; QString client() const; + QString peerIdClient() const; qreal progress() const; int payloadUpSpeed() const; int payloadDownSpeed() const; diff --git a/src/base/bittorrent/portforwarderimpl.cpp b/src/base/bittorrent/portforwarderimpl.cpp index cff7dedac..6ed58329e 100644 --- a/src/base/bittorrent/portforwarderimpl.cpp +++ b/src/base/bittorrent/portforwarderimpl.cpp @@ -30,6 +30,7 @@ #include +#include "base/algorithm.h" #include "base/logger.h" PortForwarderImpl::PortForwarderImpl(lt::session *provider, QObject *parent) @@ -63,45 +64,64 @@ void PortForwarderImpl::setEnabled(const bool enabled) m_storeActive = enabled; } -void PortForwarderImpl::addPort(const quint16 port) +void PortForwarderImpl::setPorts(const QString &profile, QSet ports) { - if (m_mappedPorts.contains(port)) - return; - - if (isEnabled()) - m_mappedPorts.insert(port, m_provider->add_port_mapping(lt::session::tcp, port, port)); - else - m_mappedPorts.insert(port, {}); -} - -void PortForwarderImpl::deletePort(const quint16 port) -{ - const auto iter = m_mappedPorts.find(port); - if (iter == m_mappedPorts.end()) - return; - - if (isEnabled()) + PortMapping &portMapping = m_portProfiles[profile]; + Algorithm::removeIf(portMapping, [this, &ports](const quint16 port, const std::vector &handles) { - for (const lt::port_mapping_t &portMapping : *iter) - m_provider->delete_port_mapping(portMapping); + // keep existing forwardings + const bool isAlreadyMapped = ports.remove(port); + if (isAlreadyMapped) + return false; + + // remove outdated forwardings + for (const lt::port_mapping_t &handle : handles) + m_provider->delete_port_mapping(handle); + m_forwardedPorts.remove(port); + + return true; + }); + + // add new forwardings + for (const quint16 port : ports) + { + // port already forwarded/taken by other profile, don't do anything + if (m_forwardedPorts.contains(port)) + continue; + + if (isEnabled()) + portMapping.insert(port, m_provider->add_port_mapping(lt::session::tcp, port, port)); + else + portMapping.insert(port, {}); + m_forwardedPorts.insert(port); } - m_mappedPorts.erase(iter); + if (portMapping.isEmpty()) + m_portProfiles.remove(profile); +} + +void PortForwarderImpl::removePorts(const QString &profile) +{ + setPorts(profile, {}); } void PortForwarderImpl::start() { - lt::settings_pack settingsPack = m_provider->get_settings(); + lt::settings_pack settingsPack; settingsPack.set_bool(lt::settings_pack::enable_upnp, true); settingsPack.set_bool(lt::settings_pack::enable_natpmp, true); - m_provider->apply_settings(settingsPack); + m_provider->apply_settings(std::move(settingsPack)); - for (auto iter = m_mappedPorts.begin(); iter != m_mappedPorts.end(); ++iter) + for (auto profileIter = m_portProfiles.begin(); profileIter != m_portProfiles.end(); ++profileIter) { - Q_ASSERT(iter.value().empty()); + PortMapping &portMapping = profileIter.value(); + for (auto iter = portMapping.begin(); iter != portMapping.end(); ++iter) + { + Q_ASSERT(iter.value().empty()); - const quint16 port = iter.key(); - iter.value() = m_provider->add_port_mapping(lt::session::tcp, port, port); + const quint16 port = iter.key(); + iter.value() = m_provider->add_port_mapping(lt::session::tcp, port, port); + } } LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO); @@ -109,14 +129,18 @@ void PortForwarderImpl::start() void PortForwarderImpl::stop() { - lt::settings_pack settingsPack = m_provider->get_settings(); + lt::settings_pack settingsPack; settingsPack.set_bool(lt::settings_pack::enable_upnp, false); settingsPack.set_bool(lt::settings_pack::enable_natpmp, false); - m_provider->apply_settings(settingsPack); + m_provider->apply_settings(std::move(settingsPack)); - // don't clear m_mappedPorts so a later `start()` call can restore the port forwarding - for (auto iter = m_mappedPorts.begin(); iter != m_mappedPorts.end(); ++iter) - iter.value().clear(); + // don't clear m_portProfiles so a later `start()` call can restore the port forwardings + for (auto profileIter = m_portProfiles.begin(); profileIter != m_portProfiles.end(); ++profileIter) + { + PortMapping &portMapping = profileIter.value(); + for (auto iter = portMapping.begin(); iter != portMapping.end(); ++iter) + iter.value().clear(); + } LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO); } diff --git a/src/base/bittorrent/portforwarderimpl.h b/src/base/bittorrent/portforwarderimpl.h index dee74ed1c..902b1002c 100644 --- a/src/base/bittorrent/portforwarderimpl.h +++ b/src/base/bittorrent/portforwarderimpl.h @@ -34,6 +34,7 @@ #include #include +#include #include "base/net/portforwarder.h" #include "base/settingvalue.h" @@ -50,8 +51,8 @@ public: bool isEnabled() const override; void setEnabled(bool enabled) override; - void addPort(quint16 port) override; - void deletePort(quint16 port) override; + void setPorts(const QString &profile, QSet ports) override; + void removePorts(const QString &profile) override; private: void start(); @@ -59,5 +60,8 @@ private: CachedSettingValue m_storeActive; lt::session *const m_provider = nullptr; - QHash> m_mappedPorts; + + using PortMapping = QHash>; // + QHash m_portProfiles; + QSet m_forwardedPorts; }; diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index e8757d375..7176d0223 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -80,6 +80,7 @@ #include "base/logger.h" #include "base/net/downloadmanager.h" #include "base/net/proxyconfigurationmanager.h" +#include "base/preferences.h" #include "base/profile.h" #include "base/torrentfileguard.h" #include "base/torrentfilter.h" @@ -548,8 +549,6 @@ SessionImpl::SessionImpl(QObject *parent) if (isExcludedFileNamesEnabled()) populateExcludedFileNamesRegExpList(); - enableTracker(isTrackerEnabled()); - connect(Net::ProxyConfigurationManager::instance() , &Net::ProxyConfigurationManager::proxyConfigurationChanged , this, &SessionImpl::configureDeferred); @@ -569,11 +568,14 @@ SessionImpl::SessionImpl(QObject *parent) m_ioThread->start(); + initMetrics(); + loadStatistics(); + // initialize PortForwarder instance new PortForwarderImpl(m_nativeSession); - initMetrics(); - loadStatistics(); + // start embedded tracker + enableTracker(isTrackerEnabled()); prepareStartup(); } @@ -1055,25 +1057,25 @@ void SessionImpl::adjustLimits() { if (isQueueingSystemEnabled()) { - lt::settings_pack settingsPack = m_nativeSession->get_settings(); - adjustLimits(settingsPack); - m_nativeSession->apply_settings(settingsPack); + lt::settings_pack settingsPack; + // Internally increase the queue limits to ensure that the magnet is started + settingsPack.set_int(lt::settings_pack::active_downloads, adjustLimit(maxActiveDownloads())); + settingsPack.set_int(lt::settings_pack::active_limit, adjustLimit(maxActiveTorrents())); + m_nativeSession->apply_settings(std::move(settingsPack)); } } void SessionImpl::applyBandwidthLimits() { - lt::settings_pack settingsPack = m_nativeSession->get_settings(); - applyBandwidthLimits(settingsPack); - m_nativeSession->apply_settings(settingsPack); + lt::settings_pack settingsPack; + settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit()); + settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit()); + m_nativeSession->apply_settings(std::move(settingsPack)); } void SessionImpl::configure() { - lt::settings_pack settingsPack = m_nativeSession->get_settings(); - loadLTSettings(settingsPack); - m_nativeSession->apply_settings(settingsPack); - + m_nativeSession->apply_settings(loadLTSettings()); configureComponents(); m_deferredConfigureScheduled = false; @@ -1424,10 +1426,11 @@ void SessionImpl::endStartup(ResumeSessionContext *context) void SessionImpl::initializeNativeSession() { - const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD); + lt::settings_pack pack = loadLTSettings(); - lt::settings_pack pack; + const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD); pack.set_str(lt::settings_pack::peer_fingerprint, peerId); + pack.set_bool(lt::settings_pack::listen_system_port_fallback, false); pack.set_str(lt::settings_pack::user_agent, USER_AGENT.toStdString()); pack.set_bool(lt::settings_pack::use_dht_as_fallback, false); @@ -1444,8 +1447,7 @@ void SessionImpl::initializeNativeSession() pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true); #endif - loadLTSettings(pack); - lt::session_params sessionParams {pack, {}}; + lt::session_params sessionParams {std::move(pack), {}}; #ifdef QBT_USES_LIBTORRENT2 switch (diskIOType()) { @@ -1503,28 +1505,14 @@ void SessionImpl::processBannedIPs(lt::ip_filter &filter) } } -void SessionImpl::adjustLimits(lt::settings_pack &settingsPack) const +int SessionImpl::adjustLimit(const int limit) const { - // Internally increase the queue limits to ensure that the magnet is started - const auto adjustLimit = [this](const int limit) -> int - { - if (limit <= -1) - return limit; - // check for overflow: (limit + m_extraLimit) < std::numeric_limits::max() - return (m_extraLimit < (std::numeric_limits::max() - limit)) - ? (limit + m_extraLimit) - : std::numeric_limits::max(); - }; - - settingsPack.set_int(lt::settings_pack::active_downloads, adjustLimit(maxActiveDownloads())); - settingsPack.set_int(lt::settings_pack::active_limit, adjustLimit(maxActiveTorrents())); -} - -void SessionImpl::applyBandwidthLimits(lt::settings_pack &settingsPack) const -{ - const bool altSpeedLimitEnabled = isAltGlobalSpeedLimitEnabled(); - settingsPack.set_int(lt::settings_pack::download_rate_limit, altSpeedLimitEnabled ? altGlobalDownloadSpeedLimit() : globalDownloadSpeedLimit()); - settingsPack.set_int(lt::settings_pack::upload_rate_limit, altSpeedLimitEnabled ? altGlobalUploadSpeedLimit() : globalUploadSpeedLimit()); + if (limit <= -1) + return limit; + // check for overflow: (limit + m_extraLimit) < std::numeric_limits::max() + return (m_extraLimit < (std::numeric_limits::max() - limit)) + ? (limit + m_extraLimit) + : std::numeric_limits::max(); } void SessionImpl::initMetrics() @@ -1569,8 +1557,10 @@ void SessionImpl::initMetrics() m_metricIndices.disk.diskJobTime = findMetricIndex("disk.disk_job_time"); } -void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack) +lt::settings_pack SessionImpl::loadLTSettings() const { + lt::settings_pack settingsPack; + const lt::alert_category_t alertMask = lt::alert::error_notification | lt::alert::file_progress_notification | lt::alert::ip_block_notification @@ -1588,8 +1578,10 @@ void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack) // It will not take affect until the listen_interfaces settings is updated settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize()); - configureNetworkInterfaces(settingsPack); - applyBandwidthLimits(settingsPack); + applyNetworkInterfacesSettings(settingsPack); + + settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit()); + settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit()); // The most secure, rc4 only so that all streams are encrypted settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4); @@ -1724,7 +1716,9 @@ void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack) // Queueing System if (isQueueingSystemEnabled()) { - adjustLimits(settingsPack); + // Internally increase the queue limits to ensure that the magnet is started + settingsPack.set_int(lt::settings_pack::active_downloads, adjustLimit(maxActiveDownloads())); + settingsPack.set_int(lt::settings_pack::active_limit, adjustLimit(maxActiveTorrents())); settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads()); settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing()); @@ -1840,9 +1834,11 @@ void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack) settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech); break; } + + return settingsPack; } -void SessionImpl::configureNetworkInterfaces(lt::settings_pack &settingsPack) +void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const { if (m_listenInterfaceConfigured) return; @@ -1985,16 +1981,27 @@ void SessionImpl::configurePeerClasses() void SessionImpl::enableTracker(const bool enable) { + const QString profile = u"embeddedTracker"_qs; + auto *portForwarder = Net::PortForwarder::instance(); + if (enable) { if (!m_tracker) m_tracker = new Tracker(this); m_tracker->start(); + + const auto *pref = Preferences::instance(); + if (pref->isTrackerPortForwardingEnabled()) + portForwarder->setPorts(profile, {static_cast(pref->getTrackerPort())}); + else + portForwarder->removePorts(profile); } else { delete m_tracker; + + portForwarder->removePorts(profile); } } @@ -2758,9 +2765,6 @@ bool SessionImpl::addTorrent_impl(const std::variant &so p.flags |= lt::torrent_flags::duplicate_is_error; - // Prevent torrent from saving initial resume data twice - p.flags &= ~lt::torrent_flags::need_save_resume; - p.added_time = std::time(nullptr); // Limits @@ -2903,10 +2907,8 @@ void SessionImpl::saveResumeData() saveTorrentsQueue(); for (const TorrentImpl *torrent : asConst(m_torrents)) - { torrent->nativeHandle().save_resume_data(lt::torrent_handle::only_if_modified); - ++m_numResumeData; - } + m_numResumeData += m_torrents.size(); QElapsedTimer timer; timer.start(); @@ -5212,10 +5214,8 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid()) m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent); - if (!params.restored) + if (isRestored()) { - m_resumeDataStorage->store(torrent->id(), params); - // The following is useless for newly added magnet if (torrent->hasMetadata()) { @@ -5230,7 +5230,7 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, m_seedingLimitTimer->start(); } - if (params.restored) + if (!isRestored()) { LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name())); } diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index d7a747f61..c788f9c53 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -474,11 +474,10 @@ namespace BitTorrent Q_INVOKABLE void configure(); void configureComponents(); void initializeNativeSession(); - void loadLTSettings(lt::settings_pack &settingsPack); - void configureNetworkInterfaces(lt::settings_pack &settingsPack); + lt::settings_pack loadLTSettings() const; + void applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const; void configurePeerClasses(); - void adjustLimits(lt::settings_pack &settingsPack) const; - void applyBandwidthLimits(lt::settings_pack &settingsPack) const; + int adjustLimit(int limit) const; void initMetrics(); void adjustLimits(); void applyBandwidthLimits(); @@ -553,7 +552,7 @@ namespace BitTorrent bool m_deferredConfigureScheduled = false; bool m_IPFilteringConfigured = false; - bool m_listenInterfaceConfigured = false; + mutable bool m_listenInterfaceConfigured = false; CachedSettingValue m_isDHTEnabled; CachedSettingValue m_isLSDEnabled; diff --git a/src/base/bittorrent/tracker.cpp b/src/base/bittorrent/tracker.cpp index a811502ec..583c01743 100644 --- a/src/base/bittorrent/tracker.cpp +++ b/src/base/bittorrent/tracker.cpp @@ -203,12 +203,12 @@ Tracker::Tracker(QObject *parent) bool Tracker::start() { - const QHostAddress ip = QHostAddress::Any; const int port = Preferences::instance()->getTrackerPort(); if (m_server->isListening()) { - if (m_server->serverPort() == port) + if (const int oldPort = m_server->serverPort() + ; oldPort == port) { // Already listening on the right port, just return return true; @@ -218,9 +218,9 @@ bool Tracker::start() m_server->close(); } - // Listen on the predefined port + // Listen on port + const QHostAddress ip = QHostAddress::Any; const bool listenSuccess = m_server->listen(ip, port); - if (listenSuccess) { LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2") diff --git a/src/base/net/downloadmanager.cpp b/src/base/net/downloadmanager.cpp index ac3022c6d..da5c80c43 100644 --- a/src/base/net/downloadmanager.cpp +++ b/src/base/net/downloadmanager.cpp @@ -124,7 +124,7 @@ namespace // Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents request.setRawHeader("Referer", request.url().toEncoded().data()); #ifdef QT_NO_COMPRESS - // The macro "QT_NO_COMPRESS" defined in QT will disable the zlib releated features + // The macro "QT_NO_COMPRESS" defined in QT will disable the zlib related features // and reply data auto-decompression in QT will also be disabled. But we can support // gzip encoding and manually decompress the reply data. request.setRawHeader("Accept-Encoding", "gzip"); diff --git a/src/base/net/portforwarder.h b/src/base/net/portforwarder.h index f23ca2197..6f835d223 100644 --- a/src/base/net/portforwarder.h +++ b/src/base/net/portforwarder.h @@ -29,6 +29,9 @@ #pragma once #include +#include + +class QString; namespace Net { @@ -45,8 +48,8 @@ namespace Net virtual bool isEnabled() const = 0; virtual void setEnabled(bool enabled) = 0; - virtual void addPort(quint16 port) = 0; - virtual void deletePort(quint16 port) = 0; + virtual void setPorts(const QString &profile, QSet ports) = 0; + virtual void removePorts(const QString &profile) = 0; private: static PortForwarder *m_instance; diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 59a601118..4cc99227c 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -1164,6 +1164,16 @@ void Preferences::setTrackerPort(const int port) setValue(u"Preferences/Advanced/trackerPort"_qs, port); } +bool Preferences::isTrackerPortForwardingEnabled() const +{ + return value(u"Preferences/Advanced/trackerPortForwarding"_qs, false); +} + +void Preferences::setTrackerPortForwardingEnabled(const bool enabled) +{ + setValue(u"Preferences/Advanced/trackerPortForwarding"_qs, enabled); +} + #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) bool Preferences::isUpdateCheckEnabled() const { diff --git a/src/base/preferences.h b/src/base/preferences.h index 534755b77..d22db8df5 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -299,6 +299,8 @@ public: #endif int getTrackerPort() const; void setTrackerPort(int port); + bool isTrackerPortForwardingEnabled() const; + void setTrackerPortForwardingEnabled(bool enabled); #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) bool isUpdateCheckEnabled() const; void setUpdateCheckEnabled(bool enabled); diff --git a/src/gui/advancedsettings.cpp b/src/gui/advancedsettings.cpp index 1f2dda5f5..fb8618f6d 100644 --- a/src/gui/advancedsettings.cpp +++ b/src/gui/advancedsettings.cpp @@ -97,6 +97,7 @@ namespace // embedded tracker TRACKER_STATUS, TRACKER_PORT, + TRACKER_PORT_FORWARDING, // libtorrent section LIBTORRENT_HEADER, ASYNC_IO_THREADS, @@ -292,7 +293,9 @@ void AdvancedSettings::saveAdvancedSettings() const // Tracker pref->setTrackerPort(m_spinBoxTrackerPort.value()); + pref->setTrackerPortForwardingEnabled(m_checkBoxTrackerPortForwarding.isChecked()); session->setTrackerEnabled(m_checkBoxTrackerStatus.isChecked()); + // Choking algorithm session->setChokingAlgorithm(m_comboBoxChokingAlgorithm.currentData().value()); // Seed choking algorithm @@ -732,6 +735,9 @@ void AdvancedSettings::loadAdvancedSettings() m_spinBoxTrackerPort.setMaximum(65535); m_spinBoxTrackerPort.setValue(pref->getTrackerPort()); addRow(TRACKER_PORT, tr("Embedded tracker port"), &m_spinBoxTrackerPort); + // Tracker port forwarding + m_checkBoxTrackerPortForwarding.setChecked(pref->isTrackerPortForwardingEnabled()); + addRow(TRACKER_PORT_FORWARDING, tr("Enable port forwarding for embedded tracker"), &m_checkBoxTrackerPortForwarding); // Choking algorithm m_comboBoxChokingAlgorithm.addItem(tr("Fixed slots"), QVariant::fromValue(BitTorrent::ChokingAlgorithm::FixedSlots)); m_comboBoxChokingAlgorithm.addItem(tr("Upload rate based"), QVariant::fromValue(BitTorrent::ChokingAlgorithm::RateBased)); diff --git a/src/gui/advancedsettings.h b/src/gui/advancedsettings.h index 7846ca5c6..79e3146b2 100644 --- a/src/gui/advancedsettings.h +++ b/src/gui/advancedsettings.h @@ -68,7 +68,7 @@ private: m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval, m_spinBoxRequestQueueSize; QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts, m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxReannounceWhenAddressChanged, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus, - m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers, + m_checkBoxTrackerPortForwarding, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers, m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity, m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport; QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm, diff --git a/src/gui/properties/peerlistwidget.cpp b/src/gui/properties/peerlistwidget.cpp index e4ed8a75a..5b490df9e 100644 --- a/src/gui/properties/peerlistwidget.cpp +++ b/src/gui/properties/peerlistwidget.cpp @@ -89,7 +89,7 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent) , m_properties(parent) { // Load settings - loadSettings(); + const bool columnLoaded = loadSettings(); // Visual settings setUniformRowHeights(true); setRootIsDecorated(false); @@ -109,6 +109,7 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent) m_listModel->setHeaderData(PeerListColumns::FLAGS, Qt::Horizontal, tr("Flags")); m_listModel->setHeaderData(PeerListColumns::CONNECTION, Qt::Horizontal, tr("Connection")); m_listModel->setHeaderData(PeerListColumns::CLIENT, Qt::Horizontal, tr("Client", "i.e.: Client application")); + m_listModel->setHeaderData(PeerListColumns::PEERID_CLIENT, Qt::Horizontal, tr("Peer ID Client", "i.e.: Client resolved from Peer ID")); m_listModel->setHeaderData(PeerListColumns::PROGRESS, Qt::Horizontal, tr("Progress", "i.e: % downloaded")); m_listModel->setHeaderData(PeerListColumns::DOWN_SPEED, Qt::Horizontal, tr("Down Speed", "i.e: Download speed")); m_listModel->setHeaderData(PeerListColumns::UP_SPEED, Qt::Horizontal, tr("Up Speed", "i.e: Upload speed")); @@ -130,8 +131,16 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent) m_proxyModel->setSourceModel(m_listModel); m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); setModel(m_proxyModel); + hideColumn(PeerListColumns::IP_HIDDEN); hideColumn(PeerListColumns::COL_COUNT); + + // Default hidden columns + if (!columnLoaded) + { + hideColumn(PeerListColumns::PEERID_CLIENT); + } + m_resolveCountries = Preferences::instance()->resolvePeerCountries(); if (!m_resolveCountries) hideColumn(PeerListColumns::COUNTRY); @@ -371,9 +380,9 @@ void PeerListWidget::clear() m_listModel->removeRows(0, nbrows); } -void PeerListWidget::loadSettings() +bool PeerListWidget::loadSettings() { - header()->restoreState(Preferences::instance()->getPeerListState()); + return header()->restoreState(Preferences::instance()->getPeerListState()); } void PeerListWidget::saveSettings() const @@ -461,6 +470,8 @@ void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTor setModelData(row, PeerListColumns::FLAGS, peer.flags(), peer.flags(), {}, peer.flagsDescription()); const QString client = peer.client().toHtmlEscaped(); setModelData(row, PeerListColumns::CLIENT, client, client, {}, client); + const QString peerIdClient = peer.peerIdClient().toHtmlEscaped(); + setModelData(row, PeerListColumns::PEERID_CLIENT, peerIdClient, peerIdClient); setModelData(row, PeerListColumns::PROGRESS, (Utils::String::fromDouble(peer.progress() * 100, 1) + u'%'), peer.progress(), intDataTextAlignment); const QString downSpeed = (hideValues && (peer.payloadDownSpeed() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.payloadDownSpeed(), true); setModelData(row, PeerListColumns::DOWN_SPEED, downSpeed, peer.payloadDownSpeed(), intDataTextAlignment); diff --git a/src/gui/properties/peerlistwidget.h b/src/gui/properties/peerlistwidget.h index a3e9e0d94..2276437c1 100644 --- a/src/gui/properties/peerlistwidget.h +++ b/src/gui/properties/peerlistwidget.h @@ -66,6 +66,7 @@ public: CONNECTION, FLAGS, CLIENT, + PEERID_CLIENT, PROGRESS, DOWN_SPEED, UP_SPEED, @@ -87,7 +88,7 @@ public: void clear(); private slots: - void loadSettings(); + bool loadSettings(); void saveSettings() const; void displayColumnHeaderMenu(); void showPeerListMenu(); diff --git a/src/gui/statusbar.cpp b/src/gui/statusbar.cpp index 09a6f290a..0f4bda38e 100644 --- a/src/gui/statusbar.cpp +++ b/src/gui/statusbar.cpp @@ -48,7 +48,7 @@ StatusBar::StatusBar(QWidget *parent) { #ifndef Q_OS_MACOS // Redefining global stylesheet breaks certain elements on mac like tabs. - // Qt checks whether the stylesheet class inherts("QMacStyle") and this becomes false. + // Qt checks whether the stylesheet class inherits("QMacStyle") and this becomes false. setStyleSheet(u"QStatusBar::item { border-width: 0; }"_qs); #endif diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index de1042259..9357fdc8e 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -221,11 +221,7 @@ void TorrentContentModel::updateFilesProgress(const QVector &fp) m_rootItem->recalculateProgress(); m_rootItem->recalculateAvailability(); - const QVector columns = - { - {TorrentContentModelItem::COL_PROGRESS, TorrentContentModelItem::COL_PROGRESS} - }; - notifySubtreeUpdated(index(0, 0), columns); + emit layoutChanged(); } void TorrentContentModel::updateFilesPriorities(const QVector &fprio) @@ -239,12 +235,7 @@ void TorrentContentModel::updateFilesPriorities(const QVectorsetPriority(static_cast(fprio[i])); - const QVector columns = - { - {TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME}, - {TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO} - }; - notifySubtreeUpdated(index(0, 0), columns); + emit layoutChanged(); } void TorrentContentModel::updateFilesAvailability(const QVector &fa) @@ -259,11 +250,7 @@ void TorrentContentModel::updateFilesAvailability(const QVector &fa) // Update folders progress in the tree m_rootItem->recalculateProgress(); - const QVector columns = - { - {TorrentContentModelItem::COL_AVAILABILITY, TorrentContentModelItem::COL_AVAILABILITY} - }; - notifySubtreeUpdated(index(0, 0), columns); + emit layoutChanged(); } QVector TorrentContentModel::getFilePriorities() const @@ -308,17 +295,13 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu if (currentPrio != newPrio) { + emit layoutAboutToBeChanged(); item->setPriority(newPrio); // Update folders progress in the tree m_rootItem->recalculateProgress(); m_rootItem->recalculateAvailability(); - const QVector columns = - { - {TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME}, - {TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO} - }; - notifySubtreeUpdated(index, columns); + emit layoutChanged(); emit filteredFilesChanged(); return true; @@ -350,14 +333,9 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu const auto newPrio = static_cast(value.toInt()); if (currentPrio != newPrio) { + emit layoutAboutToBeChanged(); item->setPriority(newPrio); - - const QVector columns = - { - {TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME}, - {TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO} - }; - notifySubtreeUpdated(index, columns); + emit layoutChanged(); if ((newPrio == BitTorrent::DownloadPriority::Ignored) || (currentPrio == BitTorrent::DownloadPriority::Ignored)) @@ -541,7 +519,8 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage & if (filesCount <= 0) return; - emit layoutAboutToBeChanged(); + beginResetModel(); + // Initialize files_index array qDebug("Torrent contains %d files", filesCount); m_filesIndex.reserve(filesCount); @@ -588,56 +567,6 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage & lastParent->appendChild(fileItem); m_filesIndex.push_back(fileItem); } - emit layoutChanged(); -} - -void TorrentContentModel::notifySubtreeUpdated(const QModelIndex &index, const QVector &columns) -{ - // For best performance, `columns` entries should be arranged from left to right - - Q_ASSERT(index.isValid()); - - // emit itself - for (const ColumnInterval &column : columns) - emit dataChanged(index.siblingAtColumn(column.first()), index.siblingAtColumn(column.last())); - - // propagate up the model - QModelIndex parentIndex = parent(index); - while (parentIndex.isValid()) - { - for (const ColumnInterval &column : columns) - emit dataChanged(parentIndex.siblingAtColumn(column.first()), parentIndex.siblingAtColumn(column.last())); - parentIndex = parent(parentIndex); - } - - // propagate down the model - QVector parentIndexes; - - if (hasChildren(index)) - parentIndexes.push_back(index); - - while (!parentIndexes.isEmpty()) - { - const QModelIndex parent = parentIndexes.takeLast(); - - const int childCount = rowCount(parent); - const QModelIndex child = this->index(0, 0, parent); - - // emit this generation - for (const ColumnInterval &column : columns) - { - const QModelIndex childTopLeft = child.siblingAtColumn(column.first()); - const QModelIndex childBottomRight = child.sibling((childCount - 1), column.last()); - emit dataChanged(childTopLeft, childBottomRight); - } - - // check generations further down - parentIndexes.reserve(childCount); - for (int i = 0; i < childCount; ++i) - { - const QModelIndex sibling = child.siblingAtRow(i); - if (hasChildren(sibling)) - parentIndexes.push_back(sibling); - } - } + + endResetModel(); } diff --git a/src/gui/torrentcontentmodel.h b/src/gui/torrentcontentmodel.h index 481cf2ead..84f00976a 100644 --- a/src/gui/torrentcontentmodel.h +++ b/src/gui/torrentcontentmodel.h @@ -81,10 +81,6 @@ signals: void filteredFilesChanged(); private: - using ColumnInterval = IndexInterval; - - void notifySubtreeUpdated(const QModelIndex &index, const QVector &columns); - TorrentContentModelFolder *m_rootItem = nullptr; QVector m_filesIndex; QFileIconProvider *m_fileIconProvider = nullptr; diff --git a/src/gui/torrentcontenttreeview.cpp b/src/gui/torrentcontenttreeview.cpp index 984baca52..81a90e2ee 100644 --- a/src/gui/torrentcontenttreeview.cpp +++ b/src/gui/torrentcontenttreeview.cpp @@ -79,25 +79,19 @@ void TorrentContentTreeView::keyPressEvent(QKeyEvent *event) event->accept(); - QModelIndex current = currentNameCell(); - - QVariant value = current.data(Qt::CheckStateRole); + const QVariant value = currentNameCell().data(Qt::CheckStateRole); if (!value.isValid()) { Q_ASSERT(false); return; } - Qt::CheckState state = (static_cast(value.toInt()) == Qt::Checked - ? Qt::Unchecked : Qt::Checked); - + const Qt::CheckState state = (static_cast(value.toInt()) == Qt::Checked) + ? Qt::Unchecked : Qt::Checked; const QModelIndexList selection = selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME); for (const QModelIndex &index : selection) - { - Q_ASSERT(index.column() == TorrentContentModelItem::COL_NAME); model()->setData(index, state, Qt::CheckStateRole); - } } void TorrentContentTreeView::renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage) @@ -142,16 +136,16 @@ void TorrentContentTreeView::renameSelectedFile(BitTorrent::AbstractFileStorage } } -QModelIndex TorrentContentTreeView::currentNameCell() +QModelIndex TorrentContentTreeView::currentNameCell() const { - QModelIndex current = currentIndex(); + const QModelIndex current = currentIndex(); if (!current.isValid()) { Q_ASSERT(false); return {}; } - return model()->index(current.row(), TorrentContentModelItem::COL_NAME, current.parent()); + return current.siblingAtColumn(TorrentContentModelItem::COL_NAME); } void TorrentContentTreeView::wheelEvent(QWheelEvent *event) diff --git a/src/gui/torrentcontenttreeview.h b/src/gui/torrentcontenttreeview.h index 1e8a5cfa7..f03be6a2f 100644 --- a/src/gui/torrentcontenttreeview.h +++ b/src/gui/torrentcontenttreeview.h @@ -49,6 +49,6 @@ public: void renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage); private: - QModelIndex currentNameCell(); + QModelIndex currentNameCell() const; void wheelEvent(QWheelEvent *event) override; }; diff --git a/src/gui/transferlistmodel.cpp b/src/gui/transferlistmodel.cpp index a1a372e30..14c8c4cb8 100644 --- a/src/gui/transferlistmodel.cpp +++ b/src/gui/transferlistmodel.cpp @@ -632,9 +632,12 @@ bool TransferListModel::setData(const QModelIndex &index, const QVariant &value, void TransferListModel::addTorrents(const QVector &torrents) { - int row = m_torrentList.size(); - beginInsertRows({}, row, (row + torrents.size())); + qsizetype row = m_torrentList.size(); + const qsizetype total = row + torrents.size(); + beginInsertRows({}, row, total); + + m_torrentList.reserve(total); for (BitTorrent::Torrent *torrent : torrents) { Q_ASSERT(!m_torrentMap.contains(torrent)); diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 93e8906b7..196334702 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -363,12 +363,30 @@ void TransferListWidget::setSelectedTorrentsLocation() void TransferListWidget::pauseAllTorrents() { + // Show confirmation if user would really like to Pause All + const QMessageBox::StandardButton ret = + QMessageBox::question(this, tr("Confirm pause") + , tr("Would you like to pause all torrents?") + , (QMessageBox::Yes | QMessageBox::No)); + + if (ret != QMessageBox::Yes) + return; + for (BitTorrent::Torrent *const torrent : asConst(BitTorrent::Session::instance()->torrents())) torrent->pause(); } void TransferListWidget::resumeAllTorrents() { + // Show confirmation if user would really like to Resume All + const QMessageBox::StandardButton ret = + QMessageBox::question(this, tr("Confirm resume") + , tr("Would you like to resume all torrents?") + , (QMessageBox::Yes | QMessageBox::No)); + + if (ret != QMessageBox::Yes) + return; + for (BitTorrent::Torrent *const torrent : asConst(BitTorrent::Session::instance()->torrents())) torrent->resume(); } diff --git a/src/lang/lang.qrc b/src/lang/lang.qrc index 2657f5374..62908ac87 100644 --- a/src/lang/lang.qrc +++ b/src/lang/lang.qrc @@ -53,7 +53,7 @@ qbittorrent_uk.qm qbittorrent_uz@Latn.qm qbittorrent_vi.qm - qbittorrent_zh.qm + qbittorrent_zh_CN.qm qbittorrent_zh_HK.qm qbittorrent_zh_TW.qm diff --git a/src/lang/qbittorrent_zh.ts b/src/lang/qbittorrent_zh_CN.ts similarity index 100% rename from src/lang/qbittorrent_zh.ts rename to src/lang/qbittorrent_zh_CN.ts diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index c8d66ea64..0b82613c7 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -372,6 +372,7 @@ void AppController::preferencesAction() // Embedded tracker data[u"enable_embedded_tracker"_qs] = session->isTrackerEnabled(); data[u"embedded_tracker_port"_qs] = pref->getTrackerPort(); + data[u"embedded_tracker_port_forwarding"_qs] = pref->isTrackerPortForwardingEnabled(); // Choking algorithm data[u"upload_slots_behavior"_qs] = static_cast(session->chokingAlgorithm()); // Seed choking algorithm @@ -899,6 +900,8 @@ void AppController::setPreferencesAction() // Embedded tracker if (hasKey(u"embedded_tracker_port"_qs)) pref->setTrackerPort(it.value().toInt()); + if (hasKey(u"embedded_tracker_port_forwarding"_qs)) + pref->setTrackerPortForwardingEnabled(it.value().toBool()); if (hasKey(u"enable_embedded_tracker"_qs)) session->setTrackerEnabled(it.value().toBool()); // Choking algorithm diff --git a/src/webui/api/synccontroller.cpp b/src/webui/api/synccontroller.cpp index 173280c6e..f7770aeb5 100644 --- a/src/webui/api/synccontroller.cpp +++ b/src/webui/api/synccontroller.cpp @@ -66,6 +66,7 @@ namespace // Peer keys const QString KEY_PEER_CLIENT = u"client"_qs; + const QString KEY_PEER_ID_CLIENT = u"peer_id_client"_qs; const QString KEY_PEER_CONNECTION_TYPE = u"connection"_qs; const QString KEY_PEER_COUNTRY = u"country"_qs; const QString KEY_PEER_COUNTRY_CODE = u"country_code"_qs; @@ -561,6 +562,7 @@ void SyncController::torrentPeersAction() {KEY_PEER_IP, pi.address().ip.toString()}, {KEY_PEER_PORT, pi.address().port}, {KEY_PEER_CLIENT, pi.client()}, + {KEY_PEER_ID_CLIENT, pi.peerIdClient()}, {KEY_PEER_PROGRESS, pi.progress()}, {KEY_PEER_DOWN_SPEED, pi.payloadDownSpeed()}, {KEY_PEER_UP_SPEED, pi.payloadUpSpeed()}, diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 79b5f9c69..54a3f3c4e 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -52,7 +52,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 8, 16}; +inline const Utils::Version<3, 2> API_VERSION {2, 8, 18}; class APIController; class AuthController; diff --git a/src/webui/webui.cpp b/src/webui/webui.cpp index 5698e65e1..dc1b43596 100644 --- a/src/webui/webui.cpp +++ b/src/webui/webui.cpp @@ -50,25 +50,21 @@ void WebUI::configure() { m_isErrored = false; // clear previous error state - Preferences *const pref = Preferences::instance(); - - const quint16 oldPort = m_port; - m_port = pref->getWebUiPort(); + const QString portForwardingProfile = u"webui"_qs; + const Preferences *pref = Preferences::instance(); + const quint16 port = pref->getWebUiPort(); if (pref->isWebUiEnabled()) { - // UPnP/NAT-PMP + // Port forwarding + auto *portForwarder = Net::PortForwarder::instance(); if (pref->useUPnPForWebUIPort()) { - if (m_port != oldPort) - { - Net::PortForwarder::instance()->deletePort(oldPort); - Net::PortForwarder::instance()->addPort(m_port); - } + portForwarder->setPorts(portForwardingProfile, {port}); } else { - Net::PortForwarder::instance()->deletePort(oldPort); + portForwarder->removePorts(portForwardingProfile); } // http server @@ -81,7 +77,7 @@ void WebUI::configure() else { if ((m_httpServer->serverAddress().toString() != serverAddressString) - || (m_httpServer->serverPort() != m_port)) + || (m_httpServer->serverPort() != port)) m_httpServer->close(); } @@ -112,15 +108,15 @@ void WebUI::configure() { const auto address = ((serverAddressString == u"*") || serverAddressString.isEmpty()) ? QHostAddress::Any : QHostAddress(serverAddressString); - bool success = m_httpServer->listen(address, m_port); + bool success = m_httpServer->listen(address, port); if (success) { - LogMsg(tr("Web UI: Now listening on IP: %1, port: %2").arg(serverAddressString).arg(m_port)); + LogMsg(tr("Web UI: Now listening on IP: %1, port: %2").arg(serverAddressString).arg(port)); } else { const QString errorMsg = tr("Web UI: Unable to bind to IP: %1, port: %2. Reason: %3") - .arg(serverAddressString).arg(m_port).arg(m_httpServer->errorString()); + .arg(serverAddressString).arg(port).arg(m_httpServer->errorString()); LogMsg(errorMsg, Log::CRITICAL); qCritical() << errorMsg; @@ -144,7 +140,7 @@ void WebUI::configure() } else { - Net::PortForwarder::instance()->deletePort(oldPort); + Net::PortForwarder::instance()->removePorts(portForwardingProfile); delete m_httpServer; delete m_webapp; diff --git a/src/webui/webui.h b/src/webui/webui.h index f3fd37422..e2c63b828 100644 --- a/src/webui/webui.h +++ b/src/webui/webui.h @@ -66,5 +66,4 @@ private: QPointer m_httpServer; QPointer m_dnsUpdater; QPointer m_webapp; - quint16 m_port = 0; }; diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 6a35ae5ec..6f72009ab 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -1498,6 +1498,7 @@ window.qBittorrent.DynamicTable = (function() { this.newColumn('connection', '', 'QBT_TR(Connection)QBT_TR[CONTEXT=PeerListWidget]', 50, true); this.newColumn('flags', '', 'QBT_TR(Flags)QBT_TR[CONTEXT=PeerListWidget]', 50, true); this.newColumn('client', '', 'QBT_TR(Client)QBT_TR[CONTEXT=PeerListWidget]', 140, true); + this.newColumn('peer_id_client', '', 'QBT_TR(Peer ID Client)QBT_TR[CONTEXT=PeerListWidget]', 60, false); this.newColumn('progress', '', 'QBT_TR(Progress)QBT_TR[CONTEXT=PeerListWidget]', 50, true); this.newColumn('dl_speed', '', 'QBT_TR(Down Speed)QBT_TR[CONTEXT=PeerListWidget]', 50, true); this.newColumn('up_speed', '', 'QBT_TR(Up Speed)QBT_TR[CONTEXT=PeerListWidget]', 50, true); diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index dd93e4d4c..87748bf85 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -979,18 +979,34 @@ const initializeWindows = function() { } }; - ['pause', 'resume'].each(function(item) { - addClickEvent(item + 'All', function(e) { - new Event(e).stop(); + addClickEvent('pauseAll', (e) => { + new Event(e).stop(); + + if (confirm('QBT_TR(Would you like to pause all torrents?)QBT_TR[CONTEXT=MainWindow]')) { new Request({ - url: 'api/v2/torrents/' + item, + url: 'api/v2/torrents/pause', method: 'post', data: { hashes: "all" } }).send(); updateMainData(); - }); + } + }); + + addClickEvent('resumeAll', (e) => { + new Event(e).stop(); + + if (confirm('QBT_TR(Would you like to resume all torrents?)QBT_TR[CONTEXT=MainWindow]')) { + new Request({ + url: 'api/v2/torrents/resume', + method: 'post', + data: { + hashes: "all" + } + }).send(); + updateMainData(); + } }); ['pause', 'resume', 'recheck'].each(function(item) { diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index fbc237828..78f1b42c5 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -1016,6 +1016,14 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD + + + + + + + +
@@ -2099,6 +2107,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD $('blockPeersOnPrivilegedPorts').setProperty('checked', pref.block_peers_on_privileged_ports); $('enableEmbeddedTracker').setProperty('checked', pref.enable_embedded_tracker); $('embeddedTrackerPort').setProperty('value', pref.embedded_tracker_port); + $('embeddedTrackerPortForwarding').setProperty('checked', pref.embedded_tracker_port_forwarding); $('uploadSlotsBehavior').setProperty('value', pref.upload_slots_behavior); $('uploadChokingAlgorithm').setProperty('value', pref.upload_choking_algorithm); $('announceAllTrackers').setProperty('checked', pref.announce_to_all_trackers); @@ -2526,6 +2535,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD settings.set('block_peers_on_privileged_ports', $('blockPeersOnPrivilegedPorts').getProperty('checked')); settings.set('enable_embedded_tracker', $('enableEmbeddedTracker').getProperty('checked')); settings.set('embedded_tracker_port', $('embeddedTrackerPort').getProperty('value')); + settings.set('embedded_tracker_port_forwarding', $('embeddedTrackerPortForwarding').getProperty('checked')); settings.set('upload_slots_behavior', $('uploadSlotsBehavior').getProperty('value')); settings.set('upload_choking_algorithm', $('uploadChokingAlgorithm').getProperty('value')); settings.set('announce_to_all_trackers', $('announceAllTrackers').getProperty('checked')); diff --git a/src/webui/www/translations/webui_translations.qrc b/src/webui/www/translations/webui_translations.qrc index c4287cc02..cbb6a9a19 100644 --- a/src/webui/www/translations/webui_translations.qrc +++ b/src/webui/www/translations/webui_translations.qrc @@ -53,7 +53,7 @@ webui_uk.qm webui_uz@Latn.qm webui_vi.qm - webui_zh.qm + webui_zh_CN.qm webui_zh_HK.qm webui_zh_TW.qm diff --git a/src/webui/www/translations/webui_zh.ts b/src/webui/www/translations/webui_zh_CN.ts similarity index 100% rename from src/webui/www/translations/webui_zh.ts rename to src/webui/www/translations/webui_zh_CN.ts