1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2016 qBittorrent project
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL".  If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
29 #include "advancedsettings.h"
30 
31 #include <limits>
32 
33 #include <QHeaderView>
34 #include <QHostAddress>
35 #include <QLabel>
36 #include <QNetworkInterface>
37 
38 #include "base/bittorrent/session.h"
39 #include "base/global.h"
40 #include "base/preferences.h"
41 #include "base/unicodestrings.h"
42 #include "app/application.h"
43 #include "gui/addnewtorrentdialog.h"
44 #include "gui/mainwindow.h"
45 
46 namespace
47 {
makeLink(const QString & url,const QString & linkLabel)48     QString makeLink(const QString &url, const QString &linkLabel)
49     {
50          return QStringLiteral("<a href=\"%1\">%2</a>").arg(url, linkLabel);
51     }
52 
53     enum AdvSettingsCols
54     {
55         PROPERTY,
56         VALUE,
57         COL_COUNT
58     };
59 
60     enum AdvSettingsRows
61     {
62         // qBittorrent section
63         QBITTORRENT_HEADER,
64 #if defined(Q_OS_WIN)
65         OS_MEMORY_PRIORITY,
66 #endif
67         // network interface
68         NETWORK_IFACE,
69         //Optional network address
70         NETWORK_IFACE_ADDRESS,
71         // behavior
72         SAVE_RESUME_DATA_INTERVAL,
73         CONFIRM_RECHECK_TORRENT,
74         RECHECK_COMPLETED,
75         // UI related
76         LIST_REFRESH,
77         RESOLVE_HOSTS,
78         RESOLVE_COUNTRIES,
79         PROGRAM_NOTIFICATIONS,
80         TORRENT_ADDED_NOTIFICATIONS,
81         CONFIRM_REMOVE_ALL_TAGS,
82         DOWNLOAD_TRACKER_FAVICON,
83         SAVE_PATH_HISTORY_LENGTH,
84         ENABLE_SPEED_WIDGET,
85 #ifndef Q_OS_MACOS
86         ENABLE_ICONS_IN_MENUS,
87 #endif
88         // embedded tracker
89         TRACKER_STATUS,
90         TRACKER_PORT,
91         // libtorrent section
92         LIBTORRENT_HEADER,
93         ASYNC_IO_THREADS,
94 #if (LIBTORRENT_VERSION_NUM >= 20000)
95         HASHING_THREADS,
96 #endif
97         FILE_POOL_SIZE,
98         CHECKING_MEM_USAGE,
99 #if (LIBTORRENT_VERSION_NUM < 20000)
100         // cache
101         DISK_CACHE,
102         DISK_CACHE_TTL,
103 #endif
104         OS_CACHE,
105 #if (LIBTORRENT_VERSION_NUM < 20000)
106         COALESCE_RW,
107 #endif
108         PIECE_EXTENT_AFFINITY,
109         SUGGEST_MODE,
110         SEND_BUF_WATERMARK,
111         SEND_BUF_LOW_WATERMARK,
112         SEND_BUF_WATERMARK_FACTOR,
113         // networking & ports
114         SOCKET_BACKLOG_SIZE,
115         OUTGOING_PORT_MIN,
116         OUTGOING_PORT_MAX,
117         UPNP_LEASE_DURATION,
118         PEER_TOS,
119         UTP_MIX_MODE,
120         IDN_SUPPORT,
121         MULTI_CONNECTIONS_PER_IP,
122         VALIDATE_HTTPS_TRACKER_CERTIFICATE,
123         SSRF_MITIGATION,
124         BLOCK_PEERS_ON_PRIVILEGED_PORTS,
125         // seeding
126         CHOKING_ALGORITHM,
127         SEED_CHOKING_ALGORITHM,
128         // tracker
129         ANNOUNCE_ALL_TRACKERS,
130         ANNOUNCE_ALL_TIERS,
131         ANNOUNCE_IP,
132         MAX_CONCURRENT_HTTP_ANNOUNCES,
133         STOP_TRACKER_TIMEOUT,
134         PEER_TURNOVER,
135         PEER_TURNOVER_CUTOFF,
136         PEER_TURNOVER_INTERVAL,
137 
138         ROW_COUNT
139     };
140 }
141 
AdvancedSettings(QWidget * parent)142 AdvancedSettings::AdvancedSettings(QWidget *parent)
143     : QTableWidget(parent)
144 {
145     // column
146     setColumnCount(COL_COUNT);
147     QStringList header = {tr("Setting"), tr("Value", "Value set for this setting")};
148     setHorizontalHeaderLabels(header);
149     // row
150     setRowCount(ROW_COUNT);
151     verticalHeader()->setVisible(false);
152     // etc.
153     setAlternatingRowColors(true);
154     setSelectionMode(QAbstractItemView::NoSelection);
155     setEditTriggers(QAbstractItemView::NoEditTriggers);
156     // Load settings
157     loadAdvancedSettings();
158     resizeColumnToContents(0);
159     horizontalHeader()->setStretchLastSection(true);
160 }
161 
saveAdvancedSettings()162 void AdvancedSettings::saveAdvancedSettings()
163 {
164     Preferences *const pref = Preferences::instance();
165     BitTorrent::Session *const session = BitTorrent::Session::instance();
166 
167 #if defined(Q_OS_WIN)
168     BitTorrent::OSMemoryPriority prio = BitTorrent::OSMemoryPriority::Normal;
169     switch (m_comboBoxOSMemoryPriority.currentIndex())
170     {
171     case 0:
172     default:
173         prio = BitTorrent::OSMemoryPriority::Normal;
174         break;
175     case 1:
176         prio = BitTorrent::OSMemoryPriority::BelowNormal;
177         break;
178     case 2:
179         prio = BitTorrent::OSMemoryPriority::Medium;
180         break;
181     case 3:
182         prio = BitTorrent::OSMemoryPriority::Low;
183         break;
184     case 4:
185         prio = BitTorrent::OSMemoryPriority::VeryLow;
186         break;
187     }
188     session->setOSMemoryPriority(prio);
189 #endif
190     // Async IO threads
191     session->setAsyncIOThreads(m_spinBoxAsyncIOThreads.value());
192 #if (LIBTORRENT_VERSION_NUM >= 20000)
193     // Hashing threads
194     session->setHashingThreads(m_spinBoxHashingThreads.value());
195 #endif
196     // File pool size
197     session->setFilePoolSize(m_spinBoxFilePoolSize.value());
198     // Checking Memory Usage
199     session->setCheckingMemUsage(m_spinBoxCheckingMemUsage.value());
200 #if (LIBTORRENT_VERSION_NUM < 20000)
201     // Disk write cache
202     session->setDiskCacheSize(m_spinBoxCache.value());
203     session->setDiskCacheTTL(m_spinBoxCacheTTL.value());
204 #endif
205     // Enable OS cache
206     session->setUseOSCache(m_checkBoxOsCache.isChecked());
207 #if (LIBTORRENT_VERSION_NUM < 20000)
208     // Coalesce reads & writes
209     session->setCoalesceReadWriteEnabled(m_checkBoxCoalesceRW.isChecked());
210 #endif
211     // Piece extent affinity
212     session->setPieceExtentAffinity(m_checkBoxPieceExtentAffinity.isChecked());
213     // Suggest mode
214     session->setSuggestMode(m_checkBoxSuggestMode.isChecked());
215     // Send buffer watermark
216     session->setSendBufferWatermark(m_spinBoxSendBufferWatermark.value());
217     session->setSendBufferLowWatermark(m_spinBoxSendBufferLowWatermark.value());
218     session->setSendBufferWatermarkFactor(m_spinBoxSendBufferWatermarkFactor.value());
219     // Socket listen backlog size
220     session->setSocketBacklogSize(m_spinBoxSocketBacklogSize.value());
221     // Save resume data interval
222     session->setSaveResumeDataInterval(m_spinBoxSaveResumeDataInterval.value());
223     // Outgoing ports
224     session->setOutgoingPortsMin(m_spinBoxOutgoingPortsMin.value());
225     session->setOutgoingPortsMax(m_spinBoxOutgoingPortsMax.value());
226     // UPnP lease duration
227     session->setUPnPLeaseDuration(m_spinBoxUPnPLeaseDuration.value());
228     // Type of service
229     session->setPeerToS(m_spinBoxPeerToS.value());
230     // uTP-TCP mixed mode
231     session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(m_comboBoxUtpMixedMode.currentIndex()));
232     // Support internationalized domain name (IDN)
233     session->setIDNSupportEnabled(m_checkBoxIDNSupport.isChecked());
234     // multiple connections per IP
235     session->setMultiConnectionsPerIpEnabled(m_checkBoxMultiConnectionsPerIp.isChecked());
236     // Validate HTTPS tracker certificate
237     session->setValidateHTTPSTrackerCertificate(m_checkBoxValidateHTTPSTrackerCertificate.isChecked());
238     // SSRF mitigation
239     session->setSSRFMitigationEnabled(m_checkBoxSSRFMitigation.isChecked());
240     // Disallow connection to peers on privileged ports
241     session->setBlockPeersOnPrivilegedPorts(m_checkBoxBlockPeersOnPrivilegedPorts.isChecked());
242     // Recheck torrents on completion
243     pref->recheckTorrentsOnCompletion(m_checkBoxRecheckCompleted.isChecked());
244     // Transfer list refresh interval
245     session->setRefreshInterval(m_spinBoxListRefresh.value());
246     // Peer resolution
247     pref->resolvePeerCountries(m_checkBoxResolveCountries.isChecked());
248     pref->resolvePeerHostNames(m_checkBoxResolveHosts.isChecked());
249     // Network interface
250     if (m_comboBoxInterface.currentIndex() == 0)
251     {
252         // All interfaces (default)
253         session->setNetworkInterface(QString());
254         session->setNetworkInterfaceName(QString());
255     }
256     else
257     {
258         session->setNetworkInterface(m_comboBoxInterface.itemData(m_comboBoxInterface.currentIndex()).toString());
259         session->setNetworkInterfaceName(m_comboBoxInterface.currentText());
260     }
261 
262     // Interface address
263     // Construct a QHostAddress to filter malformed strings
264     const QHostAddress ifaceAddr(m_comboBoxInterfaceAddress.currentData().toString().trimmed());
265     session->setNetworkInterfaceAddress(ifaceAddr.toString());
266 
267     // Announce IP
268     // Construct a QHostAddress to filter malformed strings
269     const QHostAddress addr(m_lineEditAnnounceIP.text().trimmed());
270     session->setAnnounceIP(addr.toString());
271     // Max concurrent HTTP announces
272     session->setMaxConcurrentHTTPAnnounces(m_spinBoxMaxConcurrentHTTPAnnounces.value());
273     // Stop tracker timeout
274     session->setStopTrackerTimeout(m_spinBoxStopTrackerTimeout.value());
275     // Program notification
276     MainWindow *const mainWindow = static_cast<Application*>(QCoreApplication::instance())->mainWindow();
277     mainWindow->setNotificationsEnabled(m_checkBoxProgramNotifications.isChecked());
278     mainWindow->setTorrentAddedNotificationsEnabled(m_checkBoxTorrentAddedNotifications.isChecked());
279     // Misc GUI properties
280     mainWindow->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked());
281     AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value());
282     pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
283 #ifndef Q_OS_MACOS
284     pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
285 #endif
286 
287     // Tracker
288     pref->setTrackerPort(m_spinBoxTrackerPort.value());
289     session->setTrackerEnabled(m_checkBoxTrackerStatus.isChecked());
290     // Choking algorithm
291     session->setChokingAlgorithm(static_cast<BitTorrent::ChokingAlgorithm>(m_comboBoxChokingAlgorithm.currentIndex()));
292     // Seed choking algorithm
293     session->setSeedChokingAlgorithm(static_cast<BitTorrent::SeedChokingAlgorithm>(m_comboBoxSeedChokingAlgorithm.currentIndex()));
294 
295     pref->setConfirmTorrentRecheck(m_checkBoxConfirmTorrentRecheck.isChecked());
296 
297     pref->setConfirmRemoveAllTags(m_checkBoxConfirmRemoveAllTags.isChecked());
298 
299     session->setAnnounceToAllTrackers(m_checkBoxAnnounceAllTrackers.isChecked());
300     session->setAnnounceToAllTiers(m_checkBoxAnnounceAllTiers.isChecked());
301 
302     session->setPeerTurnover(m_spinBoxPeerTurnover.value());
303     session->setPeerTurnoverCutoff(m_spinBoxPeerTurnoverCutoff.value());
304     session->setPeerTurnoverInterval(m_spinBoxPeerTurnoverInterval.value());
305 }
306 
307 #if (LIBTORRENT_VERSION_NUM < 20000)
updateCacheSpinSuffix(int value)308 void AdvancedSettings::updateCacheSpinSuffix(int value)
309 {
310     if (value == 0)
311         m_spinBoxCache.setSuffix(tr(" (disabled)"));
312     else if (value < 0)
313         m_spinBoxCache.setSuffix(tr(" (auto)"));
314     else
315         m_spinBoxCache.setSuffix(tr(" MiB"));
316 }
317 #endif
318 
updateSaveResumeDataIntervalSuffix(const int value)319 void AdvancedSettings::updateSaveResumeDataIntervalSuffix(const int value)
320 {
321     if (value > 0)
322         m_spinBoxSaveResumeDataInterval.setSuffix(tr(" min", " minutes"));
323     else
324         m_spinBoxSaveResumeDataInterval.setSuffix(tr(" (disabled)"));
325 }
326 
updateInterfaceAddressCombo()327 void AdvancedSettings::updateInterfaceAddressCombo()
328 {
329     // Try to get the currently selected interface name
330     const QString ifaceName = m_comboBoxInterface.itemData(m_comboBoxInterface.currentIndex()).toString(); // Empty string for the first element
331     const QString currentAddress = BitTorrent::Session::instance()->networkInterfaceAddress();
332 
333     // Clear all items and reinsert them, default to all
334     m_comboBoxInterfaceAddress.clear();
335     m_comboBoxInterfaceAddress.addItem(tr("All addresses"), {});
336     m_comboBoxInterfaceAddress.addItem(tr("All IPv4 addresses"), QLatin1String("0.0.0.0"));
337     m_comboBoxInterfaceAddress.addItem(tr("All IPv6 addresses"), QLatin1String("::"));
338 
339     const auto populateCombo = [this](const QHostAddress &addr)
340     {
341         if (addr.protocol() == QAbstractSocket::IPv4Protocol)
342         {
343             const QString str = addr.toString();
344             m_comboBoxInterfaceAddress.addItem(str, str);
345         }
346         else if (addr.protocol() == QAbstractSocket::IPv6Protocol)
347         {
348             const QString str = Utils::Net::canonicalIPv6Addr(addr).toString();
349             m_comboBoxInterfaceAddress.addItem(str, str);
350         }
351     };
352 
353     if (ifaceName.isEmpty())
354     {
355         for (const QHostAddress &addr : asConst(QNetworkInterface::allAddresses()))
356             populateCombo(addr);
357     }
358     else
359     {
360         const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
361         const QList<QNetworkAddressEntry> addresses = iface.addressEntries();
362         for (const QNetworkAddressEntry &entry : addresses)
363             populateCombo(entry.ip());
364     }
365 
366     const int index = m_comboBoxInterfaceAddress.findData(currentAddress);
367     m_comboBoxInterfaceAddress.setCurrentIndex(std::max(index, 0));
368 }
369 
loadAdvancedSettings()370 void AdvancedSettings::loadAdvancedSettings()
371 {
372     const Preferences *const pref = Preferences::instance();
373     const BitTorrent::Session *const session = BitTorrent::Session::instance();
374 
375     // add section headers
376     auto *labelQbtLink = new QLabel(
377         makeLink(QLatin1String("https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent#Advanced")
378                  , tr("Open documentation"))
379         , this);
380     labelQbtLink->setOpenExternalLinks(true);
381     addRow(QBITTORRENT_HEADER, QString::fromLatin1("<b>%1</b>").arg(tr("qBittorrent Section")), labelQbtLink);
382     static_cast<QLabel *>(cellWidget(QBITTORRENT_HEADER, PROPERTY))->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
383 
384     auto *labelLibtorrentLink = new QLabel(
385         makeLink(QLatin1String("https://www.libtorrent.org/reference-Settings.html")
386                  , tr("Open documentation"))
387         , this);
388     labelLibtorrentLink->setOpenExternalLinks(true);
389     addRow(LIBTORRENT_HEADER, QString::fromLatin1("<b>%1</b>").arg(tr("libtorrent Section")), labelLibtorrentLink);
390     static_cast<QLabel *>(cellWidget(LIBTORRENT_HEADER, PROPERTY))->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
391 
392 #if defined(Q_OS_WIN)
393     m_comboBoxOSMemoryPriority.addItems({tr("Normal"), tr("Below normal"), tr("Medium"), tr("Low"), tr("Very low")});
394     int OSMemoryPriorityIndex = 0;
395     switch (session->getOSMemoryPriority())
396     {
397     default:
398     case BitTorrent::OSMemoryPriority::Normal:
399         OSMemoryPriorityIndex = 0;
400         break;
401     case BitTorrent::OSMemoryPriority::BelowNormal:
402         OSMemoryPriorityIndex = 1;
403         break;
404     case BitTorrent::OSMemoryPriority::Medium:
405         OSMemoryPriorityIndex = 2;
406         break;
407     case BitTorrent::OSMemoryPriority::Low:
408         OSMemoryPriorityIndex = 3;
409         break;
410     case BitTorrent::OSMemoryPriority::VeryLow:
411         OSMemoryPriorityIndex = 4;
412         break;
413     }
414     m_comboBoxOSMemoryPriority.setCurrentIndex(OSMemoryPriorityIndex);
415     addRow(OS_MEMORY_PRIORITY, (tr("Process memory priority (Windows >= 8 only)")
416         + ' ' + makeLink("https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-memory_priority_information", "(?)"))
417         , &m_comboBoxOSMemoryPriority);
418 #endif
419 
420     // Async IO threads
421     m_spinBoxAsyncIOThreads.setMinimum(1);
422     m_spinBoxAsyncIOThreads.setMaximum(1024);
423     m_spinBoxAsyncIOThreads.setValue(session->asyncIOThreads());
424     addRow(ASYNC_IO_THREADS, (tr("Asynchronous I/O threads") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#aio_threads", "(?)"))
425             , &m_spinBoxAsyncIOThreads);
426 
427 #if (LIBTORRENT_VERSION_NUM >= 20000)
428     // Hashing threads
429     m_spinBoxHashingThreads.setMinimum(1);
430     m_spinBoxHashingThreads.setMaximum(1024);
431     m_spinBoxHashingThreads.setValue(session->hashingThreads());
432     addRow(HASHING_THREADS, (tr("Hashing threads") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#hashing_threads", "(?)"))
433             , &m_spinBoxHashingThreads);
434 #endif
435 
436     // File pool size
437     m_spinBoxFilePoolSize.setMinimum(1);
438     m_spinBoxFilePoolSize.setMaximum(std::numeric_limits<int>::max());
439     m_spinBoxFilePoolSize.setValue(session->filePoolSize());
440     addRow(FILE_POOL_SIZE, (tr("File pool size") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#file_pool_size", "(?)"))
441         , &m_spinBoxFilePoolSize);
442 
443     // Checking Memory Usage
444     m_spinBoxCheckingMemUsage.setMinimum(1);
445     // When build as 32bit binary, set the maximum value lower to prevent crashes.
446 #ifdef QBT_APP_64BIT
447     m_spinBoxCheckingMemUsage.setMaximum(1024);
448 #else
449     // Allocate at most 128MiB out of the remaining 512MiB (see the cache part below)
450     m_spinBoxCheckingMemUsage.setMaximum(128);
451 #endif
452     m_spinBoxCheckingMemUsage.setValue(session->checkingMemUsage());
453     m_spinBoxCheckingMemUsage.setSuffix(tr(" MiB"));
454     addRow(CHECKING_MEM_USAGE, (tr("Outstanding memory when checking torrents") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#checking_mem_usage", "(?)"))
455             , &m_spinBoxCheckingMemUsage);
456 #if (LIBTORRENT_VERSION_NUM < 20000)
457     // Disk write cache
458     m_spinBoxCache.setMinimum(-1);
459     // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes.
460 #ifdef QBT_APP_64BIT
461     m_spinBoxCache.setMaximum(33554431);  // 32768GiB
462 #else
463     // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
464     m_spinBoxCache.setMaximum(1536);
465 #endif
466     m_spinBoxCache.setValue(session->diskCacheSize());
467     updateCacheSpinSuffix(m_spinBoxCache.value());
468     connect(&m_spinBoxCache, qOverload<int>(&QSpinBox::valueChanged)
469             , this, &AdvancedSettings::updateCacheSpinSuffix);
470     addRow(DISK_CACHE, (tr("Disk cache") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#cache_size", "(?)"))
471             , &m_spinBoxCache);
472     // Disk cache expiry
473     m_spinBoxCacheTTL.setMinimum(1);
474     m_spinBoxCacheTTL.setMaximum(std::numeric_limits<int>::max());
475     m_spinBoxCacheTTL.setValue(session->diskCacheTTL());
476     m_spinBoxCacheTTL.setSuffix(tr(" s", " seconds"));
477     addRow(DISK_CACHE_TTL, (tr("Disk cache expiry interval") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#cache_expiry", "(?)"))
478             , &m_spinBoxCacheTTL);
479 #endif
480     // Enable OS cache
481     m_checkBoxOsCache.setChecked(session->useOSCache());
482     addRow(OS_CACHE, (tr("Enable OS cache") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#disk_io_write_mode", "(?)"))
483             , &m_checkBoxOsCache);
484 #if (LIBTORRENT_VERSION_NUM < 20000)
485     // Coalesce reads & writes
486     m_checkBoxCoalesceRW.setChecked(session->isCoalesceReadWriteEnabled());
487     addRow(COALESCE_RW, (tr("Coalesce reads & writes") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#coalesce_reads", "(?)"))
488             , &m_checkBoxCoalesceRW);
489 #endif
490     // Piece extent affinity
491     m_checkBoxPieceExtentAffinity.setChecked(session->usePieceExtentAffinity());
492     addRow(PIECE_EXTENT_AFFINITY, (tr("Use piece extent affinity") + ' ' + makeLink("https://libtorrent.org/single-page-ref.html#piece_extent_affinity", "(?)")), &m_checkBoxPieceExtentAffinity);
493     // Suggest mode
494     m_checkBoxSuggestMode.setChecked(session->isSuggestModeEnabled());
495     addRow(SUGGEST_MODE, (tr("Send upload piece suggestions") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#suggest_mode", "(?)"))
496             , &m_checkBoxSuggestMode);
497     // Send buffer watermark
498     m_spinBoxSendBufferWatermark.setMinimum(1);
499     m_spinBoxSendBufferWatermark.setMaximum(std::numeric_limits<int>::max());
500     m_spinBoxSendBufferWatermark.setSuffix(tr(" KiB"));
501     m_spinBoxSendBufferWatermark.setValue(session->sendBufferWatermark());
502     addRow(SEND_BUF_WATERMARK, (tr("Send buffer watermark") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#send_buffer_watermark", "(?)"))
503             , &m_spinBoxSendBufferWatermark);
504     m_spinBoxSendBufferLowWatermark.setMinimum(1);
505     m_spinBoxSendBufferLowWatermark.setMaximum(std::numeric_limits<int>::max());
506     m_spinBoxSendBufferLowWatermark.setSuffix(tr(" KiB"));
507     m_spinBoxSendBufferLowWatermark.setValue(session->sendBufferLowWatermark());
508     addRow(SEND_BUF_LOW_WATERMARK, (tr("Send buffer low watermark") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#send_buffer_low_watermark", "(?)"))
509             , &m_spinBoxSendBufferLowWatermark);
510     m_spinBoxSendBufferWatermarkFactor.setMinimum(1);
511     m_spinBoxSendBufferWatermarkFactor.setMaximum(std::numeric_limits<int>::max());
512     m_spinBoxSendBufferWatermarkFactor.setSuffix(" %");
513     m_spinBoxSendBufferWatermarkFactor.setValue(session->sendBufferWatermarkFactor());
514     addRow(SEND_BUF_WATERMARK_FACTOR, (tr("Send buffer watermark factor") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#send_buffer_watermark_factor", "(?)"))
515             , &m_spinBoxSendBufferWatermarkFactor);
516     // Socket listen backlog size
517     m_spinBoxSocketBacklogSize.setMinimum(1);
518     m_spinBoxSocketBacklogSize.setMaximum(std::numeric_limits<int>::max());
519     m_spinBoxSocketBacklogSize.setValue(session->socketBacklogSize());
520     addRow(SOCKET_BACKLOG_SIZE, (tr("Socket backlog size") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#listen_queue_size", "(?)"))
521             , &m_spinBoxSocketBacklogSize);
522     // Save resume data interval
523     m_spinBoxSaveResumeDataInterval.setMinimum(0);
524     m_spinBoxSaveResumeDataInterval.setMaximum(std::numeric_limits<int>::max());
525     m_spinBoxSaveResumeDataInterval.setValue(session->saveResumeDataInterval());
526     connect(&m_spinBoxSaveResumeDataInterval, qOverload<int>(&QSpinBox::valueChanged)
527         , this, &AdvancedSettings::updateSaveResumeDataIntervalSuffix);
528     updateSaveResumeDataIntervalSuffix(m_spinBoxSaveResumeDataInterval.value());
529     addRow(SAVE_RESUME_DATA_INTERVAL, tr("Save resume data interval", "How often the fastresume file is saved."), &m_spinBoxSaveResumeDataInterval);
530     // Outgoing port Min
531     m_spinBoxOutgoingPortsMin.setMinimum(0);
532     m_spinBoxOutgoingPortsMin.setMaximum(65535);
533     m_spinBoxOutgoingPortsMin.setValue(session->outgoingPortsMin());
534     addRow(OUTGOING_PORT_MIN, (tr("Outgoing ports (Min) [0: Disabled]")
535         + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#outgoing_port", "(?)"))
536         , &m_spinBoxOutgoingPortsMin);
537     // Outgoing port Min
538     m_spinBoxOutgoingPortsMax.setMinimum(0);
539     m_spinBoxOutgoingPortsMax.setMaximum(65535);
540     m_spinBoxOutgoingPortsMax.setValue(session->outgoingPortsMax());
541     addRow(OUTGOING_PORT_MAX, (tr("Outgoing ports (Max) [0: Disabled]")
542         + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#outgoing_port", "(?)"))
543         , &m_spinBoxOutgoingPortsMax);
544     // UPnP lease duration
545     m_spinBoxUPnPLeaseDuration.setMinimum(0);
546     m_spinBoxUPnPLeaseDuration.setMaximum(std::numeric_limits<int>::max());
547     m_spinBoxUPnPLeaseDuration.setValue(session->UPnPLeaseDuration());
548     m_spinBoxUPnPLeaseDuration.setSuffix(tr(" s", " seconds"));
549     addRow(UPNP_LEASE_DURATION, (tr("UPnP lease duration [0: Permanent lease]") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#upnp_lease_duration", "(?)"))
550         , &m_spinBoxUPnPLeaseDuration);
551     // Type of service
552     m_spinBoxPeerToS.setMinimum(0);
553     m_spinBoxPeerToS.setMaximum(255);
554     m_spinBoxPeerToS.setValue(session->peerToS());
555     addRow(PEER_TOS, (tr("Type of service (ToS) for connections to peers") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#peer_tos", "(?)"))
556         , &m_spinBoxPeerToS);
557     // uTP-TCP mixed mode
558     m_comboBoxUtpMixedMode.addItems({tr("Prefer TCP"), tr("Peer proportional (throttles TCP)")});
559     m_comboBoxUtpMixedMode.setCurrentIndex(static_cast<int>(session->utpMixedMode()));
560     addRow(UTP_MIX_MODE, (tr("%1-TCP mixed mode algorithm", "uTP-TCP mixed mode algorithm").arg(C_UTP)
561             + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#mixed_mode_algorithm", "(?)"))
562             , &m_comboBoxUtpMixedMode);
563     // Support internationalized domain name (IDN)
564     m_checkBoxIDNSupport.setChecked(session->isIDNSupportEnabled());
565     addRow(IDN_SUPPORT, (tr("Support internationalized domain name (IDN)")
566             + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_idna", "(?)"))
567             , &m_checkBoxIDNSupport);
568     // multiple connections per IP
569     m_checkBoxMultiConnectionsPerIp.setChecked(session->multiConnectionsPerIpEnabled());
570     addRow(MULTI_CONNECTIONS_PER_IP, (tr("Allow multiple connections from the same IP address")
571             + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_multiple_connections_per_ip", "(?)"))
572             , &m_checkBoxMultiConnectionsPerIp);
573     // Validate HTTPS tracker certificate
574     m_checkBoxValidateHTTPSTrackerCertificate.setChecked(session->validateHTTPSTrackerCertificate());
575     addRow(VALIDATE_HTTPS_TRACKER_CERTIFICATE, (tr("Validate HTTPS tracker certificates")
576             + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#validate_https_trackers", "(?)"))
577             , &m_checkBoxValidateHTTPSTrackerCertificate);
578     // SSRF mitigation
579     m_checkBoxSSRFMitigation.setChecked(session->isSSRFMitigationEnabled());
580     addRow(SSRF_MITIGATION, (tr("Server-side request forgery (SSRF) mitigation")
581         + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#ssrf_mitigation", "(?)"))
582         , &m_checkBoxSSRFMitigation);
583     // Disallow connection to peers on privileged ports
584     m_checkBoxBlockPeersOnPrivilegedPorts.setChecked(session->blockPeersOnPrivilegedPorts());
585     addRow(BLOCK_PEERS_ON_PRIVILEGED_PORTS, (tr("Disallow connection to peers on privileged ports") + ' ' + makeLink("https://libtorrent.org/single-page-ref.html#no_connect_privileged_ports", "(?)")), &m_checkBoxBlockPeersOnPrivilegedPorts);
586     // Recheck completed torrents
587     m_checkBoxRecheckCompleted.setChecked(pref->recheckTorrentsOnCompletion());
588     addRow(RECHECK_COMPLETED, tr("Recheck torrents on completion"), &m_checkBoxRecheckCompleted);
589     // Transfer list refresh interval
590     m_spinBoxListRefresh.setMinimum(30);
591     m_spinBoxListRefresh.setMaximum(99999);
592     m_spinBoxListRefresh.setValue(session->refreshInterval());
593     m_spinBoxListRefresh.setSuffix(tr(" ms", " milliseconds"));
594     addRow(LIST_REFRESH, tr("Transfer list refresh interval"), &m_spinBoxListRefresh);
595     // Resolve Peer countries
596     m_checkBoxResolveCountries.setChecked(pref->resolvePeerCountries());
597     addRow(RESOLVE_COUNTRIES, tr("Resolve peer countries"), &m_checkBoxResolveCountries);
598     // Resolve peer hosts
599     m_checkBoxResolveHosts.setChecked(pref->resolvePeerHostNames());
600     addRow(RESOLVE_HOSTS, tr("Resolve peer host names"), &m_checkBoxResolveHosts);
601     // Network interface
602     m_comboBoxInterface.addItem(tr("Any interface", "i.e. Any network interface"));
603     const QString currentInterface = session->networkInterface();
604     bool interfaceExists = currentInterface.isEmpty();
605     int i = 1;
606     for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces()))
607     {
608         m_comboBoxInterface.addItem(iface.humanReadableName(), iface.name());
609         if (!currentInterface.isEmpty() && (iface.name() == currentInterface))
610         {
611             m_comboBoxInterface.setCurrentIndex(i);
612             interfaceExists = true;
613         }
614         ++i;
615     }
616     // Saved interface does not exist, show it anyway
617     if (!interfaceExists)
618     {
619         m_comboBoxInterface.addItem(session->networkInterfaceName(), currentInterface);
620         m_comboBoxInterface.setCurrentIndex(i);
621     }
622     connect(&m_comboBoxInterface, qOverload<int>(&QComboBox::currentIndexChanged)
623         , this, &AdvancedSettings::updateInterfaceAddressCombo);
624     addRow(NETWORK_IFACE, tr("Network interface"), &m_comboBoxInterface);
625     // Network interface address
626     updateInterfaceAddressCombo();
627     addRow(NETWORK_IFACE_ADDRESS, tr("Optional IP address to bind to"), &m_comboBoxInterfaceAddress);
628     // Announce IP
629     m_lineEditAnnounceIP.setText(session->announceIP());
630     addRow(ANNOUNCE_IP, (tr("IP Address to report to trackers (requires restart)")
631         + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#announce_ip", "(?)"))
632         , &m_lineEditAnnounceIP);
633     // Max concurrent HTTP announces
634     m_spinBoxMaxConcurrentHTTPAnnounces.setMaximum(std::numeric_limits<int>::max());
635     m_spinBoxMaxConcurrentHTTPAnnounces.setValue(session->maxConcurrentHTTPAnnounces());
636     addRow(MAX_CONCURRENT_HTTP_ANNOUNCES, (tr("Max concurrent HTTP announces") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#max_concurrent_http_announces", "(?)"))
637            , &m_spinBoxMaxConcurrentHTTPAnnounces);
638     // Stop tracker timeout
639     m_spinBoxStopTrackerTimeout.setValue(session->stopTrackerTimeout());
640     m_spinBoxStopTrackerTimeout.setSuffix(tr(" s", " seconds"));
641     addRow(STOP_TRACKER_TIMEOUT, (tr("Stop tracker timeout") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#stop_tracker_timeout", "(?)"))
642            , &m_spinBoxStopTrackerTimeout);
643 
644     // Program notifications
645     const MainWindow *const mainWindow = static_cast<Application*>(QCoreApplication::instance())->mainWindow();
646     m_checkBoxProgramNotifications.setChecked(mainWindow->isNotificationsEnabled());
647     addRow(PROGRAM_NOTIFICATIONS, tr("Display notifications"), &m_checkBoxProgramNotifications);
648     // Torrent added notifications
649     m_checkBoxTorrentAddedNotifications.setChecked(mainWindow->isTorrentAddedNotificationsEnabled());
650     addRow(TORRENT_ADDED_NOTIFICATIONS, tr("Display notifications for added torrents"), &m_checkBoxTorrentAddedNotifications);
651     // Download tracker's favicon
652     m_checkBoxTrackerFavicon.setChecked(mainWindow->isDownloadTrackerFavicon());
653     addRow(DOWNLOAD_TRACKER_FAVICON, tr("Download tracker's favicon"), &m_checkBoxTrackerFavicon);
654     // Save path history length
655     m_spinBoxSavePathHistoryLength.setRange(AddNewTorrentDialog::minPathHistoryLength, AddNewTorrentDialog::maxPathHistoryLength);
656     m_spinBoxSavePathHistoryLength.setValue(AddNewTorrentDialog::savePathHistoryLength());
657     addRow(SAVE_PATH_HISTORY_LENGTH, tr("Save path history length"), &m_spinBoxSavePathHistoryLength);
658     // Enable speed graphs
659     m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
660     addRow(ENABLE_SPEED_WIDGET, tr("Enable speed graphs"), &m_checkBoxSpeedWidgetEnabled);
661 #ifndef Q_OS_MACOS
662     // Enable icons in menus
663     m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
664     addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
665 #endif
666     // Tracker State
667     m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
668     addRow(TRACKER_STATUS, tr("Enable embedded tracker"), &m_checkBoxTrackerStatus);
669     // Tracker port
670     m_spinBoxTrackerPort.setMinimum(1);
671     m_spinBoxTrackerPort.setMaximum(65535);
672     m_spinBoxTrackerPort.setValue(pref->getTrackerPort());
673     addRow(TRACKER_PORT, tr("Embedded tracker port"), &m_spinBoxTrackerPort);
674     // Choking algorithm
675     m_comboBoxChokingAlgorithm.addItems({tr("Fixed slots"), tr("Upload rate based")});
676     m_comboBoxChokingAlgorithm.setCurrentIndex(static_cast<int>(session->chokingAlgorithm()));
677     addRow(CHOKING_ALGORITHM, (tr("Upload slots behavior") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#choking_algorithm", "(?)"))
678             , &m_comboBoxChokingAlgorithm);
679     // Seed choking algorithm
680     m_comboBoxSeedChokingAlgorithm.addItems({tr("Round-robin"), tr("Fastest upload"), tr("Anti-leech")});
681     m_comboBoxSeedChokingAlgorithm.setCurrentIndex(static_cast<int>(session->seedChokingAlgorithm()));
682     addRow(SEED_CHOKING_ALGORITHM, (tr("Upload choking algorithm") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#seed_choking_algorithm", "(?)"))
683             , &m_comboBoxSeedChokingAlgorithm);
684 
685     // Torrent recheck confirmation
686     m_checkBoxConfirmTorrentRecheck.setChecked(pref->confirmTorrentRecheck());
687     addRow(CONFIRM_RECHECK_TORRENT, tr("Confirm torrent recheck"), &m_checkBoxConfirmTorrentRecheck);
688 
689     // Remove all tags confirmation
690     m_checkBoxConfirmRemoveAllTags.setChecked(pref->confirmRemoveAllTags());
691     addRow(CONFIRM_REMOVE_ALL_TAGS, tr("Confirm removal of all tags"), &m_checkBoxConfirmRemoveAllTags);
692 
693     // Announce to all trackers in a tier
694     m_checkBoxAnnounceAllTrackers.setChecked(session->announceToAllTrackers());
695     addRow(ANNOUNCE_ALL_TRACKERS, (tr("Always announce to all trackers in a tier")
696         + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#announce_to_all_trackers", "(?)"))
697         , &m_checkBoxAnnounceAllTrackers);
698 
699     // Announce to all tiers
700     m_checkBoxAnnounceAllTiers.setChecked(session->announceToAllTiers());
701     addRow(ANNOUNCE_ALL_TIERS, (tr("Always announce to all tiers")
702         + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#announce_to_all_tiers", "(?)"))
703         , &m_checkBoxAnnounceAllTiers);
704 
705     m_spinBoxPeerTurnover.setMinimum(0);
706     m_spinBoxPeerTurnover.setMaximum(100);
707     m_spinBoxPeerTurnover.setValue(session->peerTurnover());
708     m_spinBoxPeerTurnover.setSuffix(" %");
709     addRow(PEER_TURNOVER, (tr("Peer turnover disconnect percentage") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#peer_turnover", "(?)"))
710             , &m_spinBoxPeerTurnover);
711     m_spinBoxPeerTurnoverCutoff.setMinimum(0);
712     m_spinBoxPeerTurnoverCutoff.setMaximum(100);
713     m_spinBoxPeerTurnoverCutoff.setSuffix(" %");
714     m_spinBoxPeerTurnoverCutoff.setValue(session->peerTurnoverCutoff());
715     addRow(PEER_TURNOVER_CUTOFF, (tr("Peer turnover threshold percentage") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#peer_turnover", "(?)"))
716             , &m_spinBoxPeerTurnoverCutoff);
717     m_spinBoxPeerTurnoverInterval.setMinimum(30);
718     m_spinBoxPeerTurnoverInterval.setMaximum(3600);
719     m_spinBoxPeerTurnoverInterval.setSuffix(tr(" s", " seconds"));
720     m_spinBoxPeerTurnoverInterval.setValue(session->peerTurnoverInterval());
721     addRow(PEER_TURNOVER_INTERVAL, (tr("Peer turnover disconnect interval") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#peer_turnover", "(?)"))
722             , &m_spinBoxPeerTurnoverInterval);
723 }
724 
725 template <typename T>
addRow(const int row,const QString & text,T * widget)726 void AdvancedSettings::addRow(const int row, const QString &text, T *widget)
727 {
728     auto label = new QLabel(text);
729     label->setOpenExternalLinks(true);
730 
731     setCellWidget(row, PROPERTY, label);
732     setCellWidget(row, VALUE, widget);
733 
734     if constexpr (std::is_same_v<T, QCheckBox>)
735         connect(widget, &QCheckBox::stateChanged, this, &AdvancedSettings::settingsChanged);
736     else if constexpr (std::is_same_v<T, QSpinBox>)
737         connect(widget, qOverload<int>(&QSpinBox::valueChanged), this, &AdvancedSettings::settingsChanged);
738     else if constexpr (std::is_same_v<T, QComboBox>)
739         connect(widget, qOverload<int>(&QComboBox::currentIndexChanged), this, &AdvancedSettings::settingsChanged);
740     else if constexpr (std::is_same_v<T, QLineEdit>)
741         connect(widget, &QLineEdit::textChanged, this, &AdvancedSettings::settingsChanged);
742 }
743