Merge pull request #18014 from sledgehammer999/maybe_backport

Backports to v4_5_x
This commit is contained in:
sledgehammer999 2022-11-14 17:16:32 +02:00 committed by GitHub
commit bff9189e52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 288 additions and 239 deletions

View file

@ -3,7 +3,7 @@ host = https://www.transifex.com
[qbittorrent.qbittorrent_master]
file_filter = src/lang/qbittorrent_<lang>.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_<lang>.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

View file

@ -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);

View file

@ -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");

View file

@ -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();

View file

@ -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?
};
}

View file

@ -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;

View file

@ -78,6 +78,7 @@ namespace BitTorrent
PeerAddress address() const;
QString client() const;
QString peerIdClient() const;
qreal progress() const;
int payloadUpSpeed() const;
int payloadDownSpeed() const;

View file

@ -30,6 +30,7 @@
#include <libtorrent/session.hpp>
#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<quint16> 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<lt::port_mapping_t> &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);
}

View file

@ -34,6 +34,7 @@
#include <libtorrent/portmap.hpp>
#include <QHash>
#include <QSet>
#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<quint16> ports) override;
void removePorts(const QString &profile) override;
private:
void start();
@ -59,5 +60,8 @@ private:
CachedSettingValue<bool> m_storeActive;
lt::session *const m_provider = nullptr;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
using PortMapping = QHash<quint16, std::vector<lt::port_mapping_t>>; // <port, handles>
QHash<QString, PortMapping> m_portProfiles;
QSet<quint16> m_forwardedPorts;
};

View file

@ -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<int>::max()
return (m_extraLimit < (std::numeric_limits<int>::max() - limit))
? (limit + m_extraLimit)
: std::numeric_limits<int>::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<int>::max()
return (m_extraLimit < (std::numeric_limits<int>::max() - limit))
? (limit + m_extraLimit)
: std::numeric_limits<int>::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<quint16>(pref->getTrackerPort())});
else
portForwarder->removePorts(profile);
}
else
{
delete m_tracker;
portForwarder->removePorts(profile);
}
}
@ -2758,9 +2765,6 @@ bool SessionImpl::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &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()));
}

View file

@ -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<bool> m_isDHTEnabled;
CachedSettingValue<bool> m_isLSDEnabled;

View file

@ -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")

View file

@ -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");

View file

@ -29,6 +29,9 @@
#pragma once
#include <QObject>
#include <QSet>
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<quint16> ports) = 0;
virtual void removePorts(const QString &profile) = 0;
private:
static PortForwarder *m_instance;

View file

@ -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
{

View file

@ -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);

View file

@ -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<BitTorrent::ChokingAlgorithm>());
// 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));

View file

@ -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,

View file

@ -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);

View file

@ -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();

View file

@ -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

View file

@ -221,11 +221,7 @@ void TorrentContentModel::updateFilesProgress(const QVector<qreal> &fp)
m_rootItem->recalculateProgress();
m_rootItem->recalculateAvailability();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_PROGRESS, TorrentContentModelItem::COL_PROGRESS}
};
notifySubtreeUpdated(index(0, 0), columns);
emit layoutChanged();
}
void TorrentContentModel::updateFilesPriorities(const QVector<BitTorrent::DownloadPriority> &fprio)
@ -239,12 +235,7 @@ void TorrentContentModel::updateFilesPriorities(const QVector<BitTorrent::Downlo
for (int i = 0; i < fprio.size(); ++i)
m_filesIndex[i]->setPriority(static_cast<BitTorrent::DownloadPriority>(fprio[i]));
const QVector<ColumnInterval> 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<qreal> &fa)
@ -259,11 +250,7 @@ void TorrentContentModel::updateFilesAvailability(const QVector<qreal> &fa)
// Update folders progress in the tree
m_rootItem->recalculateProgress();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_AVAILABILITY, TorrentContentModelItem::COL_AVAILABILITY}
};
notifySubtreeUpdated(index(0, 0), columns);
emit layoutChanged();
}
QVector<BitTorrent::DownloadPriority> 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<ColumnInterval> 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<BitTorrent::DownloadPriority>(value.toInt());
if (currentPrio != newPrio)
{
emit layoutAboutToBeChanged();
item->setPriority(newPrio);
const QVector<ColumnInterval> 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<ColumnInterval> &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<QModelIndex> 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();
}

View file

@ -81,10 +81,6 @@ signals:
void filteredFilesChanged();
private:
using ColumnInterval = IndexInterval<int>;
void notifySubtreeUpdated(const QModelIndex &index, const QVector<ColumnInterval> &columns);
TorrentContentModelFolder *m_rootItem = nullptr;
QVector<TorrentContentModelFile *> m_filesIndex;
QFileIconProvider *m_fileIconProvider = nullptr;

View file

@ -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<Qt::CheckState>(value.toInt()) == Qt::Checked
? Qt::Unchecked : Qt::Checked);
const Qt::CheckState state = (static_cast<Qt::CheckState>(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)

View file

@ -49,6 +49,6 @@ public:
void renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage);
private:
QModelIndex currentNameCell();
QModelIndex currentNameCell() const;
void wheelEvent(QWheelEvent *event) override;
};

View file

@ -632,9 +632,12 @@ bool TransferListModel::setData(const QModelIndex &index, const QVariant &value,
void TransferListModel::addTorrents(const QVector<BitTorrent::Torrent *> &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));

View file

@ -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();
}

View file

@ -53,7 +53,7 @@
<file>qbittorrent_uk.qm</file>
<file>qbittorrent_uz@Latn.qm</file>
<file>qbittorrent_vi.qm</file>
<file>qbittorrent_zh.qm</file>
<file>qbittorrent_zh_CN.qm</file>
<file>qbittorrent_zh_HK.qm</file>
<file>qbittorrent_zh_TW.qm</file>
</qresource>

View file

@ -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<int>(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

View file

@ -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()},

View file

@ -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;

View file

@ -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;

View file

@ -66,5 +66,4 @@ private:
QPointer<Http::Server> m_httpServer;
QPointer<Net::DNSUpdater> m_dnsUpdater;
QPointer<WebApplication> m_webapp;
quint16 m_port = 0;
};

View file

@ -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);

View file

@ -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) {

View file

@ -1016,6 +1016,14 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
<input type="text" id="embeddedTrackerPort" style="width: 15em;" />
</td>
</tr>
<tr>
<td>
<label for="embeddedTrackerPortForwarding">QBT_TR(Enable port forwarding for embedded tracker:)QBT_TR[CONTEXT=OptionsDialog]</label>
</td>
<td>
<input type="checkbox" id="embeddedTrackerPortForwarding" />
</td>
</tr>
</table>
</fieldset>
<fieldset class="settings">
@ -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'));

View file

@ -53,7 +53,7 @@
<file>webui_uk.qm</file>
<file>webui_uz@Latn.qm</file>
<file>webui_vi.qm</file>
<file>webui_zh.qm</file>
<file>webui_zh_CN.qm</file>
<file>webui_zh_HK.qm</file>
<file>webui_zh_TW.qm</file>
</qresource>