qBittorrent
session.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
4  * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give permission to
21  * link this program with the OpenSSL project's "OpenSSL" library (or with
22  * modified versions of it that use the same license as the "OpenSSL" library),
23  * and distribute the linked executables. You must obey the GNU General Public
24  * License in all respects for all of the code used other than "OpenSSL". If you
25  * modify file(s), you may extend this exception to your version of the file(s),
26  * but you are not obligated to do so. If you do not wish to do so, delete this
27  * exception statement from your version.
28  */
29 
30 #include "session.h"
31 
32 #include <algorithm>
33 #include <cstdint>
34 #include <ctime>
35 #include <queue>
36 #include <string>
37 #include <utility>
38 
39 #ifdef Q_OS_WIN
40 #include <Windows.h>
41 #include <wincrypt.h>
42 #include <iphlpapi.h>
43 #endif
44 
45 #include <libtorrent/alert_types.hpp>
46 #include <libtorrent/error_code.hpp>
47 #include <libtorrent/extensions/smart_ban.hpp>
48 #include <libtorrent/extensions/ut_metadata.hpp>
49 #include <libtorrent/extensions/ut_pex.hpp>
50 #include <libtorrent/ip_filter.hpp>
51 #include <libtorrent/magnet_uri.hpp>
52 #include <libtorrent/session.hpp>
53 #include <libtorrent/session_stats.hpp>
54 #include <libtorrent/session_status.hpp>
55 #include <libtorrent/torrent_info.hpp>
56 
57 #include <QDebug>
58 #include <QDir>
59 #include <QFile>
60 #include <QHostAddress>
61 #include <QJsonArray>
62 #include <QJsonDocument>
63 #include <QJsonObject>
64 #include <QJsonValue>
65 #include <QNetworkAddressEntry>
66 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
67 #include <QNetworkConfigurationManager>
68 #endif
69 #include <QNetworkInterface>
70 #include <QRegularExpression>
71 #include <QString>
72 #include <QThread>
73 #include <QTimer>
74 #include <QUuid>
75 
76 #include "base/algorithm.h"
77 #include "base/global.h"
78 #include "base/logger.h"
81 #include "base/profile.h"
82 #include "base/torrentfileguard.h"
83 #include "base/torrentfilter.h"
84 #include "base/unicodestrings.h"
85 #include "base/utils/bytearray.h"
86 #include "base/utils/fs.h"
87 #include "base/utils/io.h"
88 #include "base/utils/misc.h"
89 #include "base/utils/net.h"
90 #include "base/utils/random.h"
91 #include "base/version.h"
92 #include "bandwidthscheduler.h"
94 #include "common.h"
95 #include "customstorage.h"
96 #include "dbresumedatastorage.h"
97 #include "downloadpriority.h"
98 #include "filesearcher.h"
99 #include "filterparserthread.h"
100 #include "loadtorrentparams.h"
101 #include "lttypecast.h"
102 #include "magneturi.h"
103 #include "nativesessionextension.h"
104 #include "portforwarderimpl.h"
105 #include "statistics.h"
106 #include "torrentimpl.h"
107 #include "tracker.h"
108 
109 using namespace BitTorrent;
110 
111 const QString CATEGORIES_FILE_NAME {QStringLiteral("categories.json")};
112 
113 namespace
114 {
115  const char PEER_ID[] = "qB";
116  const char USER_AGENT[] = "qBittorrent/" QBT_VERSION_2;
117 
118  void torrentQueuePositionUp(const lt::torrent_handle &handle)
119  {
120  try
121  {
122  handle.queue_position_up();
123  }
124  catch (const std::exception &exc)
125  {
126  qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
127  }
128  }
129 
130  void torrentQueuePositionDown(const lt::torrent_handle &handle)
131  {
132  try
133  {
134  handle.queue_position_down();
135  }
136  catch (const std::exception &exc)
137  {
138  qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
139  }
140  }
141 
142  void torrentQueuePositionTop(const lt::torrent_handle &handle)
143  {
144  try
145  {
146  handle.queue_position_top();
147  }
148  catch (const std::exception &exc)
149  {
150  qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
151  }
152  }
153 
154  void torrentQueuePositionBottom(const lt::torrent_handle &handle)
155  {
156  try
157  {
158  handle.queue_position_bottom();
159  }
160  catch (const std::exception &exc)
161  {
162  qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
163  }
164  }
165 
166  QMap<QString, CategoryOptions> expandCategories(const QMap<QString, CategoryOptions> &categories)
167  {
168  QMap<QString, CategoryOptions> expanded = categories;
169 
170  for (auto i = categories.cbegin(); i != categories.cend(); ++i)
171  {
172  const QString &category = i.key();
173  for (const QString &subcat : asConst(Session::expandCategory(category)))
174  {
175  if (!expanded.contains(subcat))
176  expanded[subcat] = {};
177  }
178  }
179 
180  return expanded;
181  }
182 
183  QString toString(const lt::socket_type_t socketType)
184  {
185  switch (socketType)
186  {
187 #ifdef QBT_USES_LIBTORRENT2
188  case lt::socket_type_t::http:
189  return QLatin1String("HTTP");
190  case lt::socket_type_t::http_ssl:
191  return QLatin1String("HTTP_SSL");
192 #endif
193  case lt::socket_type_t::i2p:
194  return QLatin1String("I2P");
195  case lt::socket_type_t::socks5:
196  return QLatin1String("SOCKS5");
197 #ifdef QBT_USES_LIBTORRENT2
198  case lt::socket_type_t::socks5_ssl:
199  return QLatin1String("SOCKS5_SSL");
200 #endif
201  case lt::socket_type_t::tcp:
202  return QLatin1String("TCP");
203  case lt::socket_type_t::tcp_ssl:
204  return QLatin1String("TCP_SSL");
205 #ifdef QBT_USES_LIBTORRENT2
206  case lt::socket_type_t::utp:
207  return QLatin1String("UTP");
208 #else
209  case lt::socket_type_t::udp:
210  return QLatin1String("UDP");
211 #endif
212  case lt::socket_type_t::utp_ssl:
213  return QLatin1String("UTP_SSL");
214  }
215  return QLatin1String("INVALID");
216  }
217 
218  QString toString(const lt::address &address)
219  {
220  try
221  {
222  return QString::fromLatin1(address.to_string().c_str());
223  }
224  catch (const std::exception &)
225  {
226  // suppress conversion error
227  }
228  return {};
229  }
230 
231  template <typename T>
233  {
234  LowerLimited(T limit, T ret)
235  : m_limit(limit)
236  , m_ret(ret)
237  {
238  }
239 
240  explicit LowerLimited(T limit)
241  : LowerLimited(limit, limit)
242  {
243  }
244 
245  T operator()(T val) const
246  {
247  return val <= m_limit ? m_ret : val;
248  }
249 
250  private:
251  const T m_limit;
252  const T m_ret;
253  };
254 
255  template <typename T>
256  LowerLimited<T> lowerLimited(T limit) { return LowerLimited<T>(limit); }
257 
258  template <typename T>
259  LowerLimited<T> lowerLimited(T limit, T ret) { return LowerLimited<T>(limit, ret); }
260 
261  template <typename T>
262  auto clampValue(const T lower, const T upper)
263  {
264  return [lower, upper](const T value) -> T
265  {
266  if (value < lower)
267  return lower;
268  if (value > upper)
269  return upper;
270  return value;
271  };
272  }
273 
274 #ifdef Q_OS_WIN
275  QString convertIfaceNameToGuid(const QString &name)
276  {
277  // Under Windows XP or on Qt version <= 5.5 'name' will be a GUID already.
278  const QUuid uuid(name);
279  if (!uuid.isNull())
280  return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
281 
282  NET_LUID luid {};
283  const LONG res = ::ConvertInterfaceNameToLuidW(name.toStdWString().c_str(), &luid);
284  if (res == 0)
285  {
286  GUID guid;
287  if (::ConvertInterfaceLuidToGuid(&luid, &guid) == 0)
288  return QUuid(guid).toString().toUpper();
289  }
290 
291  return {};
292  }
293 #endif
294 }
295 
296 const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
297 
298 // Session
299 
300 Session *Session::m_instance = nullptr;
301 
302 #define BITTORRENT_KEY(name) "BitTorrent/" name
303 #define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY("Session/") name
304 
305 Session::Session(QObject *parent)
306  : QObject(parent)
307  , m_isDHTEnabled(BITTORRENT_SESSION_KEY("DHTEnabled"), true)
308  , m_isLSDEnabled(BITTORRENT_SESSION_KEY("LSDEnabled"), true)
309  , m_isPeXEnabled(BITTORRENT_SESSION_KEY("PeXEnabled"), true)
310  , m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY("IPFilteringEnabled"), false)
311  , m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY("TrackerFilteringEnabled"), false)
312  , m_IPFilterFile(BITTORRENT_SESSION_KEY("IPFilter"))
313  , m_announceToAllTrackers(BITTORRENT_SESSION_KEY("AnnounceToAllTrackers"), false)
314  , m_announceToAllTiers(BITTORRENT_SESSION_KEY("AnnounceToAllTiers"), true)
315  , m_asyncIOThreads(BITTORRENT_SESSION_KEY("AsyncIOThreadsCount"), 10)
316  , m_hashingThreads(BITTORRENT_SESSION_KEY("HashingThreadsCount"), 2)
317  , m_filePoolSize(BITTORRENT_SESSION_KEY("FilePoolSize"), 5000)
318  , m_checkingMemUsage(BITTORRENT_SESSION_KEY("CheckingMemUsageSize"), 32)
319  , m_diskCacheSize(BITTORRENT_SESSION_KEY("DiskCacheSize"), -1)
320  , m_diskCacheTTL(BITTORRENT_SESSION_KEY("DiskCacheTTL"), 60)
321  , m_useOSCache(BITTORRENT_SESSION_KEY("UseOSCache"), true)
322 #ifdef Q_OS_WIN
323  , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY("CoalesceReadWrite"), true)
324 #else
325  , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY("CoalesceReadWrite"), false)
326 #endif
327  , m_usePieceExtentAffinity(BITTORRENT_SESSION_KEY("PieceExtentAffinity"), false)
328  , m_isSuggestMode(BITTORRENT_SESSION_KEY("SuggestMode"), false)
329  , m_sendBufferWatermark(BITTORRENT_SESSION_KEY("SendBufferWatermark"), 500)
330  , m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY("SendBufferLowWatermark"), 10)
331  , m_sendBufferWatermarkFactor(BITTORRENT_SESSION_KEY("SendBufferWatermarkFactor"), 50)
332  , m_connectionSpeed(BITTORRENT_SESSION_KEY("ConnectionSpeed"), 30)
333  , m_socketBacklogSize(BITTORRENT_SESSION_KEY("SocketBacklogSize"), 30)
334  , m_isAnonymousModeEnabled(BITTORRENT_SESSION_KEY("AnonymousModeEnabled"), false)
335  , m_isQueueingEnabled(BITTORRENT_SESSION_KEY("QueueingSystemEnabled"), false)
336  , m_maxActiveDownloads(BITTORRENT_SESSION_KEY("MaxActiveDownloads"), 3, lowerLimited(-1))
337  , m_maxActiveUploads(BITTORRENT_SESSION_KEY("MaxActiveUploads"), 3, lowerLimited(-1))
338  , m_maxActiveTorrents(BITTORRENT_SESSION_KEY("MaxActiveTorrents"), 5, lowerLimited(-1))
339  , m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY("IgnoreSlowTorrentsForQueueing"), false)
340  , m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY("SlowTorrentsDownloadRate"), 2)
341  , m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY("SlowTorrentsUploadRate"), 2)
342  , m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY("SlowTorrentsInactivityTimer"), 60)
343  , m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
344  , m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
345  , m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
346  , m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x20)
347  , m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
348  , m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
349  , m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
350  , m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY("MaxConcurrentHTTPAnnounces"), 50)
351  , m_isReannounceWhenAddressChangedEnabled(BITTORRENT_SESSION_KEY("ReannounceWhenAddressChanged"), false)
352  , m_stopTrackerTimeout(BITTORRENT_SESSION_KEY("StopTrackerTimeout"), 5)
353  , m_maxConnections(BITTORRENT_SESSION_KEY("MaxConnections"), 500, lowerLimited(0, -1))
354  , m_maxUploads(BITTORRENT_SESSION_KEY("MaxUploads"), 20, lowerLimited(0, -1))
355  , m_maxConnectionsPerTorrent(BITTORRENT_SESSION_KEY("MaxConnectionsPerTorrent"), 100, lowerLimited(0, -1))
356  , m_maxUploadsPerTorrent(BITTORRENT_SESSION_KEY("MaxUploadsPerTorrent"), 4, lowerLimited(0, -1))
357  , m_btProtocol(BITTORRENT_SESSION_KEY("BTProtocol"), BTProtocol::Both
358  , clampValue(BTProtocol::Both, BTProtocol::UTP))
359  , m_isUTPRateLimited(BITTORRENT_SESSION_KEY("uTPRateLimited"), true)
360  , m_utpMixedMode(BITTORRENT_SESSION_KEY("uTPMixedMode"), MixedModeAlgorithm::TCP
361  , clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
362  , m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), false)
363  , m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
364  , m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), true)
365  , m_SSRFMitigationEnabled(BITTORRENT_SESSION_KEY("SSRFMitigation"), true)
366  , m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY("BlockPeersOnPrivilegedPorts"), false)
367  , m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
368  , m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers"))
369  , m_globalMaxRatio(BITTORRENT_SESSION_KEY("GlobalMaxRatio"), -1, [](qreal r) { return r < 0 ? -1. : r;})
370  , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY("GlobalMaxSeedingMinutes"), -1, lowerLimited(-1))
371  , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY("AddTorrentPaused"), false)
372  , m_torrentContentLayout(BITTORRENT_SESSION_KEY("TorrentContentLayout"), TorrentContentLayout::Original)
373  , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY("AddExtensionToIncompleteFiles"), false)
374  , m_refreshInterval(BITTORRENT_SESSION_KEY("RefreshInterval"), 1500)
375  , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY("Preallocation"), false)
376  , m_torrentExportDirectory(BITTORRENT_SESSION_KEY("TorrentExportDirectory"))
377  , m_finishedTorrentExportDirectory(BITTORRENT_SESSION_KEY("FinishedTorrentExportDirectory"))
378  , m_globalDownloadSpeedLimit(BITTORRENT_SESSION_KEY("GlobalDLSpeedLimit"), 0, lowerLimited(0))
379  , m_globalUploadSpeedLimit(BITTORRENT_SESSION_KEY("GlobalUPSpeedLimit"), 0, lowerLimited(0))
380  , m_altGlobalDownloadSpeedLimit(BITTORRENT_SESSION_KEY("AlternativeGlobalDLSpeedLimit"), 10, lowerLimited(0))
381  , m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY("AlternativeGlobalUPSpeedLimit"), 10, lowerLimited(0))
382  , m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY("UseAlternativeGlobalSpeedLimit"), false)
383  , m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY("BandwidthSchedulerEnabled"), false)
384  , m_saveResumeDataInterval(BITTORRENT_SESSION_KEY("SaveResumeDataInterval"), 60)
385  , m_port(BITTORRENT_SESSION_KEY("Port"), -1)
386  , m_networkInterface(BITTORRENT_SESSION_KEY("Interface"))
387  , m_networkInterfaceName(BITTORRENT_SESSION_KEY("InterfaceName"))
388  , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY("InterfaceAddress"))
389  , m_encryption(BITTORRENT_SESSION_KEY("Encryption"), 0)
390  , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY("ProxyPeerConnections"), false)
391  , m_chokingAlgorithm(BITTORRENT_SESSION_KEY("ChokingAlgorithm"), ChokingAlgorithm::FixedSlots
392  , clampValue(ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased))
393  , m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY("SeedChokingAlgorithm"), SeedChokingAlgorithm::FastestUpload
394  , clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
395  , m_storedTags(BITTORRENT_SESSION_KEY("Tags"))
396  , m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause)
398  , m_downloadPath(BITTORRENT_SESSION_KEY("TempPath"), specialFolderLocation(SpecialFolder::Downloads) + QLatin1String("/temp"), Utils::Fs::toUniformPath)
399  , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY("SubcategoriesEnabled"), false)
400  , m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY("TempPathEnabled"), false)
401  , m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY("DisableAutoTMMByDefault"), true)
402  , m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/CategoryChanged"), false)
403  , m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/DefaultSavePathChanged"), true)
404  , m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/CategorySavePathChanged"), true)
405  , m_isTrackerEnabled(BITTORRENT_KEY("TrackerEnabled"), false)
406  , m_peerTurnover(BITTORRENT_SESSION_KEY("PeerTurnover"), 4)
407  , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY("PeerTurnoverCutOff"), 90)
408  , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY("PeerTurnoverInterval"), 300)
409  , m_bannedIPs("State/BannedIPs"
410  , QStringList()
411  , [](const QStringList &value)
412  {
413  QStringList tmp = value;
414  tmp.sort();
415  return tmp;
416  }
417  )
418  , m_resumeDataStorageType(BITTORRENT_SESSION_KEY("ResumeDataStorageType"), ResumeDataStorageType::Legacy)
419 #if defined(Q_OS_WIN)
420  , m_OSMemoryPriority(BITTORRENT_KEY("OSMemoryPriority"), OSMemoryPriority::BelowNormal)
421 #endif
422  , m_seedingLimitTimer {new QTimer {this}}
423  , m_resumeDataTimer {new QTimer {this}}
424  , m_statistics {new Statistics {this}}
425  , m_ioThread {new QThread {this}}
426  , m_recentErroredTorrentsTimer {new QTimer {this}}
427 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
428  , m_networkManager {new QNetworkConfigurationManager {this}}
429 #endif
430 {
431  if (port() < 0)
432  m_port = Utils::Random::rand(1024, 65535);
433 
434  m_recentErroredTorrentsTimer->setSingleShot(true);
435  m_recentErroredTorrentsTimer->setInterval(1000);
436  connect(m_recentErroredTorrentsTimer, &QTimer::timeout
437  , this, [this]() { m_recentErroredTorrents.clear(); });
438 
439  m_seedingLimitTimer->setInterval(10000);
440  connect(m_seedingLimitTimer, &QTimer::timeout, this, &Session::processShareLimits);
441 
442  initializeNativeSession();
443  configureComponents();
444 
445  if (isBandwidthSchedulerEnabled())
446  enableBandwidthScheduler();
447 
448  loadCategories();
449  if (isSubcategoriesEnabled())
450  {
451  // if subcategories support changed manually
452  m_categories = expandCategories(m_categories);
453  }
454 
455  const QStringList storedTags = m_storedTags.get();
456  m_tags = {storedTags.cbegin(), storedTags.cend()};
457 
458  enqueueRefresh();
459  updateSeedingLimitTimer();
460  populateAdditionalTrackers();
461 
462  enableTracker(isTrackerEnabled());
463 
466  , this, &Session::configureDeferred);
467 
468 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
469  // Network configuration monitor
470  connect(m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &Session::networkOnlineStateChanged);
471  connect(m_networkManager, &QNetworkConfigurationManager::configurationAdded, this, &Session::networkConfigurationChange);
472  connect(m_networkManager, &QNetworkConfigurationManager::configurationRemoved, this, &Session::networkConfigurationChange);
473  connect(m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &Session::networkConfigurationChange);
474 #endif
475 
476  m_fileSearcher = new FileSearcher;
477  m_fileSearcher->moveToThread(m_ioThread);
478  connect(m_ioThread, &QThread::finished, m_fileSearcher, &QObject::deleteLater);
479  connect(m_fileSearcher, &FileSearcher::searchFinished, this, &Session::fileSearchFinished);
480 
481  m_ioThread->start();
482 
483  // Regular saving of fastresume data
484  connect(m_resumeDataTimer, &QTimer::timeout, this, [this]() { generateResumeData(); });
485  const int saveInterval = saveResumeDataInterval();
486  if (saveInterval > 0)
487  {
488  m_resumeDataTimer->setInterval(saveInterval * 60 * 1000);
489  m_resumeDataTimer->start();
490  }
491 
492  // initialize PortForwarder instance
493  new PortForwarderImpl {m_nativeSession};
494 
495  initMetrics();
496 }
497 
499 {
500  return m_isDHTEnabled;
501 }
502 
503 void Session::setDHTEnabled(bool enabled)
504 {
505  if (enabled != m_isDHTEnabled)
506  {
507  m_isDHTEnabled = enabled;
509  LogMsg(tr("DHT support [%1]").arg(enabled ? tr("ON") : tr("OFF")), Log::INFO);
510  }
511 }
512 
514 {
515  return m_isLSDEnabled;
516 }
517 
518 void Session::setLSDEnabled(const bool enabled)
519 {
520  if (enabled != m_isLSDEnabled)
521  {
522  m_isLSDEnabled = enabled;
524  LogMsg(tr("Local Peer Discovery support [%1]").arg(enabled ? tr("ON") : tr("OFF"))
525  , Log::INFO);
526  }
527 }
528 
530 {
531  return m_isPeXEnabled;
532 }
533 
534 void Session::setPeXEnabled(const bool enabled)
535 {
536  m_isPeXEnabled = enabled;
537  if (m_wasPexEnabled != enabled)
538  LogMsg(tr("Restart is required to toggle PeX support"), Log::WARNING);
539 }
540 
542 {
544 }
545 
546 void Session::setDownloadPathEnabled(const bool enabled)
547 {
548  if (enabled != isDownloadPathEnabled())
549  {
550  m_isDownloadPathEnabled = enabled;
551  for (TorrentImpl *const torrent : asConst(m_torrents))
552  torrent->handleDownloadPathChanged();
553  }
554 }
555 
557 {
559 }
560 
561 void Session::setAppendExtensionEnabled(const bool enabled)
562 {
563  if (isAppendExtensionEnabled() != enabled)
564  {
565  m_isAppendExtensionEnabled = enabled;
566 
567  // append or remove .!qB extension for incomplete files
568  for (TorrentImpl *const torrent : asConst(m_torrents))
569  torrent->handleAppendExtensionToggled();
570  }
571 }
572 
574 {
575  return m_refreshInterval;
576 }
577 
579 {
580  if (value != refreshInterval())
581  {
583  }
584 }
585 
587 {
589 }
590 
591 void Session::setPreallocationEnabled(const bool enabled)
592 {
593  m_isPreallocationEnabled = enabled;
594 }
595 
597 {
599 }
600 
602 {
603  path = Utils::Fs::toUniformPath(path);
604  if (path != torrentExportDirectory())
606 }
607 
609 {
611 }
612 
614 {
615  path = Utils::Fs::toUniformPath(path);
616  if (path != finishedTorrentExportDirectory())
618 }
619 
620 QString Session::savePath() const
621 {
622  return m_savePath;
623 }
624 
625 QString Session::downloadPath() const
626 {
627  return m_downloadPath;
628 }
629 
630 bool Session::isValidCategoryName(const QString &name)
631 {
632  static const QRegularExpression re(R"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)");
633  if (!name.isEmpty() && (name.indexOf(re) != 0))
634  {
635  qDebug() << "Incorrect category name:" << name;
636  return false;
637  }
638 
639  return true;
640 }
641 
642 QStringList Session::expandCategory(const QString &category)
643 {
644  QStringList result;
645  if (!isValidCategoryName(category))
646  return result;
647 
648  int index = 0;
649  while ((index = category.indexOf('/', index)) >= 0)
650  {
651  result << category.left(index);
652  ++index;
653  }
654  result << category;
655 
656  return result;
657 }
658 
659 QStringList Session::categories() const
660 {
661  return m_categories.keys();
662 }
663 
664 CategoryOptions Session::categoryOptions(const QString &categoryName) const
665 {
666  return m_categories.value(categoryName);
667 }
668 
669 QString Session::categorySavePath(const QString &categoryName) const
670 {
671  const QString basePath = savePath();
672  if (categoryName.isEmpty())
673  return basePath;
674 
675  QString path = m_categories.value(categoryName).savePath;
676  if (path.isEmpty()) // use implicit save path
677  path = Utils::Fs::toValidFileSystemName(categoryName, true);
678 
679  return (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, basePath));
680 }
681 
682 QString Session::categoryDownloadPath(const QString &categoryName) const
683 {
684  const CategoryOptions categoryOptions = m_categories.value(categoryName);
685  const CategoryOptions::DownloadPathOption downloadPathOption =
687  if (!downloadPathOption.enabled)
688  return {};
689 
690  const QString basePath = downloadPath();
691  if (categoryName.isEmpty())
692  return basePath;
693 
694  const QString path = (!downloadPathOption.path.isEmpty()
695  ? downloadPathOption.path
696  : Utils::Fs::toValidFileSystemName(categoryName, true)); // use implicit download path
697 
698  return (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, basePath));
699 }
700 
701 bool Session::addCategory(const QString &name, const CategoryOptions &options)
702 {
703  if (name.isEmpty())
704  return false;
705 
706  if (!isValidCategoryName(name) || m_categories.contains(name))
707  return false;
708 
710  {
711  for (const QString &parent : asConst(expandCategory(name)))
712  {
713  if ((parent != name) && !m_categories.contains(parent))
714  {
715  m_categories[parent] = {};
716  emit categoryAdded(parent);
717  }
718  }
719  }
720 
721  m_categories[name] = options;
722  storeCategories();
723  emit categoryAdded(name);
724 
725  return true;
726 }
727 
728 bool Session::editCategory(const QString &name, const CategoryOptions &options)
729 {
730  const auto it = m_categories.find(name);
731  if (it == m_categories.end())
732  return false;
733 
734  CategoryOptions &currentOptions = it.value();
735  if (options == currentOptions)
736  return false;
737 
738  currentOptions = options;
739  storeCategories();
741  {
742  for (TorrentImpl *const torrent : asConst(m_torrents))
743  {
744  if (torrent->category() == name)
745  torrent->setAutoTMMEnabled(false);
746  }
747  }
748  else
749  {
750  for (TorrentImpl *const torrent : asConst(m_torrents))
751  {
752  if (torrent->category() == name)
753  torrent->handleCategoryOptionsChanged();
754  }
755  }
756 
757  return true;
758 }
759 
760 bool Session::removeCategory(const QString &name)
761 {
762  for (TorrentImpl *const torrent : asConst(m_torrents))
763  {
764  if (torrent->belongsToCategory(name))
765  torrent->setCategory("");
766  }
767 
768  // remove stored category and its subcategories if exist
769  bool result = false;
771  {
772  // remove subcategories
773  const QString test = name + '/';
774  Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const CategoryOptions &)
775  {
776  if (category.startsWith(test))
777  {
778  result = true;
779  emit categoryRemoved(category);
780  return true;
781  }
782  return false;
783  });
784  }
785 
786  result = (m_categories.remove(name) > 0) || result;
787 
788  if (result)
789  {
790  // update stored categories
791  storeCategories();
792  emit categoryRemoved(name);
793  }
794 
795  return result;
796 }
797 
799 {
801 }
802 
804 {
805  if (isSubcategoriesEnabled() == value) return;
806 
807  if (value)
808  {
809  // expand categories to include all parent categories
811  // update stored categories
812  storeCategories();
813  }
814  else
815  {
816  // reload categories
817  loadCategories();
818  }
819 
822 }
823 
824 QSet<QString> Session::tags() const
825 {
826  return m_tags;
827 }
828 
829 bool Session::isValidTag(const QString &tag)
830 {
831  return (!tag.trimmed().isEmpty() && !tag.contains(','));
832 }
833 
834 bool Session::hasTag(const QString &tag) const
835 {
836  return m_tags.contains(tag);
837 }
838 
839 bool Session::addTag(const QString &tag)
840 {
841  if (!isValidTag(tag) || hasTag(tag))
842  return false;
843 
844  m_tags.insert(tag);
845  m_storedTags = m_tags.values();
846  emit tagAdded(tag);
847  return true;
848 }
849 
850 bool Session::removeTag(const QString &tag)
851 {
852  if (m_tags.remove(tag))
853  {
854  for (TorrentImpl *const torrent : asConst(m_torrents))
855  torrent->removeTag(tag);
856  m_storedTags = m_tags.values();
857  emit tagRemoved(tag);
858  return true;
859  }
860  return false;
861 }
862 
864 {
866 }
867 
869 {
871 }
872 
874 {
876 }
877 
879 {
881 }
882 
884 {
886 }
887 
889 {
891 }
892 
894 {
896 }
897 
899 {
901 }
902 
904 {
905  return m_isAddTorrentPaused;
906 }
907 
909 {
911 }
912 
914 {
915  return m_isTrackerEnabled;
916 }
917 
918 void Session::setTrackerEnabled(const bool enabled)
919 {
920  if (m_isTrackerEnabled != enabled)
921  m_isTrackerEnabled = enabled;
922 
923  // call enableTracker() unconditionally, otherwise port change won't trigger
924  // tracker restart
925  enableTracker(enabled);
926 }
927 
929 {
930  return m_globalMaxRatio;
931 }
932 
933 // Torrents with a ratio superior to the given value will
934 // be automatically deleted
935 void Session::setGlobalMaxRatio(qreal ratio)
936 {
937  if (ratio < 0)
938  ratio = -1.;
939 
940  if (ratio != globalMaxRatio())
941  {
942  m_globalMaxRatio = ratio;
944  }
945 }
946 
948 {
950 }
951 
953 {
954  if (minutes < 0)
955  minutes = -1;
956 
957  if (minutes != globalMaxSeedingMinutes())
958  {
959  m_globalMaxSeedingMinutes = minutes;
961  }
962 }
963 
964 // Main destructor
966 {
967  // Do some BT related saving
968  saveResumeData();
969 
970  // We must delete FilterParserThread
971  // before we delete lt::session
972  delete m_filterParser;
973 
974  // We must delete PortForwarderImpl before
975  // we delete lt::session
977 
978  qDebug("Deleting the session");
979  delete m_nativeSession;
980 
981  m_ioThread->quit();
982  m_ioThread->wait();
983 }
984 
986 {
987  if (!m_instance)
988  m_instance = new Session;
989 }
990 
992 {
993  delete m_instance;
994  m_instance = nullptr;
995 }
996 
998 {
999  return m_instance;
1000 }
1001 
1003 {
1005  {
1006  lt::settings_pack settingsPack = m_nativeSession->get_settings();
1007  adjustLimits(settingsPack);
1008  m_nativeSession->apply_settings(settingsPack);
1009  }
1010 }
1011 
1013 {
1014  lt::settings_pack settingsPack = m_nativeSession->get_settings();
1015  applyBandwidthLimits(settingsPack);
1016  m_nativeSession->apply_settings(settingsPack);
1017 }
1018 
1020 {
1021  lt::settings_pack settingsPack = m_nativeSession->get_settings();
1022  loadLTSettings(settingsPack);
1023  m_nativeSession->apply_settings(settingsPack);
1024 
1026 
1028 }
1029 
1031 {
1032  // This function contains components/actions that:
1033  // 1. Need to be setup at start up
1034  // 2. When deferred configure is called
1035 
1037 
1039  {
1040  if (isIPFilteringEnabled())
1041  enableIPFilter();
1042  else
1043  disableIPFilter();
1044  m_IPFilteringConfigured = true;
1045  }
1046 
1047 #if defined(Q_OS_WIN)
1048  applyOSMemoryPriority();
1049 #endif
1050 }
1051 
1053 {
1054  const lt::alert_category_t alertMask = lt::alert::error_notification
1055  | lt::alert::file_progress_notification
1056  | lt::alert::ip_block_notification
1057  | lt::alert::peer_notification
1058  | lt::alert::performance_warning
1059  | lt::alert::port_mapping_notification
1060  | lt::alert::status_notification
1061  | lt::alert::storage_notification
1062  | lt::alert::tracker_notification;
1063  const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
1064 
1065  lt::settings_pack pack;
1066  pack.set_int(lt::settings_pack::alert_mask, alertMask);
1067  pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
1068  pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
1070  pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
1071  // Speed up exit
1072  pack.set_int(lt::settings_pack::auto_scrape_interval, 1200); // 20 minutes
1073  pack.set_int(lt::settings_pack::auto_scrape_min_interval, 900); // 15 minutes
1074  // libtorrent 1.1 enables UPnP & NAT-PMP by default
1075  // turn them off before `lt::session` ctor to avoid split second effects
1076  pack.set_bool(lt::settings_pack::enable_upnp, false);
1077  pack.set_bool(lt::settings_pack::enable_natpmp, false);
1078 
1079 #ifdef QBT_USES_LIBTORRENT2
1080  // preserve the same behavior as in earlier libtorrent versions
1081  pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
1082 #endif
1083 
1084  loadLTSettings(pack);
1085  lt::session_params sessionParams {pack, {}};
1086 #ifdef QBT_USES_LIBTORRENT2
1087  sessionParams.disk_io_constructor = customDiskIOConstructor;
1088 #endif
1089  m_nativeSession = new lt::session {sessionParams};
1090 
1091  LogMsg(tr("Peer ID: ") + QString::fromStdString(peerId));
1092  LogMsg(tr("HTTP User-Agent is '%1'").arg(USER_AGENT));
1093  LogMsg(tr("DHT support [%1]").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1094  LogMsg(tr("Local Peer Discovery support [%1]").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1095  LogMsg(tr("PeX support [%1]").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1096  LogMsg(tr("Anonymous mode [%1]").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1097  LogMsg(tr("Encryption support [%1]").arg((encryption() == 0) ? tr("ON") :
1098  ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO);
1099 
1100  m_nativeSession->set_alert_notify([this]()
1101  {
1102  QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection);
1103  });
1104 
1105  // Enabling plugins
1106  m_nativeSession->add_extension(&lt::create_smart_ban_plugin);
1107  m_nativeSession->add_extension(&lt::create_ut_metadata_plugin);
1108  if (isPeXEnabled())
1109  m_nativeSession->add_extension(&lt::create_ut_pex_plugin);
1110 
1111  m_nativeSession->add_extension(std::make_shared<NativeSessionExtension>());
1112 }
1113 
1114 void Session::processBannedIPs(lt::ip_filter &filter)
1115 {
1116  // First, import current filter
1117  for (const QString &ip : asConst(m_bannedIPs.get()))
1118  {
1119  lt::error_code ec;
1120  const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1121  Q_ASSERT(!ec);
1122  if (!ec)
1123  filter.add_rule(addr, addr, lt::ip_filter::blocked);
1124  }
1125 }
1126 
1127 void Session::adjustLimits(lt::settings_pack &settingsPack) const
1128 {
1129  // Internally increase the queue limits to ensure that the magnet is started
1130  const int maxDownloads = maxActiveDownloads();
1131  const int maxActive = maxActiveTorrents();
1132 
1133  settingsPack.set_int(lt::settings_pack::active_downloads
1134  , maxDownloads > -1 ? maxDownloads + m_extraLimit : maxDownloads);
1135  settingsPack.set_int(lt::settings_pack::active_limit
1136  , maxActive > -1 ? maxActive + m_extraLimit : maxActive);
1137 }
1138 
1139 void Session::applyBandwidthLimits(lt::settings_pack &settingsPack) const
1140 {
1141  const bool altSpeedLimitEnabled = isAltGlobalSpeedLimitEnabled();
1142  settingsPack.set_int(lt::settings_pack::download_rate_limit, altSpeedLimitEnabled ? altGlobalDownloadSpeedLimit() : globalDownloadSpeedLimit());
1143  settingsPack.set_int(lt::settings_pack::upload_rate_limit, altSpeedLimitEnabled ? altGlobalUploadSpeedLimit() : globalUploadSpeedLimit());
1144 }
1145 
1147 {
1148  const auto findMetricIndex = [](const char *name) -> int
1149  {
1150  const int index = lt::find_metric_idx(name);
1151  Q_ASSERT(index >= 0);
1152  return index;
1153  };
1154 
1155  // TODO: switch to "designated initializers" in C++20
1156  m_metricIndices.net.hasIncomingConnections = findMetricIndex("net.has_incoming_connections");
1157  m_metricIndices.net.sentPayloadBytes = findMetricIndex("net.sent_payload_bytes");
1158  m_metricIndices.net.recvPayloadBytes = findMetricIndex("net.recv_payload_bytes");
1159  m_metricIndices.net.sentBytes = findMetricIndex("net.sent_bytes");
1160  m_metricIndices.net.recvBytes = findMetricIndex("net.recv_bytes");
1161  m_metricIndices.net.sentIPOverheadBytes = findMetricIndex("net.sent_ip_overhead_bytes");
1162  m_metricIndices.net.recvIPOverheadBytes = findMetricIndex("net.recv_ip_overhead_bytes");
1163  m_metricIndices.net.sentTrackerBytes = findMetricIndex("net.sent_tracker_bytes");
1164  m_metricIndices.net.recvTrackerBytes = findMetricIndex("net.recv_tracker_bytes");
1165  m_metricIndices.net.recvRedundantBytes = findMetricIndex("net.recv_redundant_bytes");
1166  m_metricIndices.net.recvFailedBytes = findMetricIndex("net.recv_failed_bytes");
1167 
1168  m_metricIndices.peer.numPeersConnected = findMetricIndex("peer.num_peers_connected");
1169  m_metricIndices.peer.numPeersDownDisk = findMetricIndex("peer.num_peers_down_disk");
1170  m_metricIndices.peer.numPeersUpDisk = findMetricIndex("peer.num_peers_up_disk");
1171 
1172  m_metricIndices.dht.dhtBytesIn = findMetricIndex("dht.dht_bytes_in");
1173  m_metricIndices.dht.dhtBytesOut = findMetricIndex("dht.dht_bytes_out");
1174  m_metricIndices.dht.dhtNodes = findMetricIndex("dht.dht_nodes");
1175 
1176  m_metricIndices.disk.diskBlocksInUse = findMetricIndex("disk.disk_blocks_in_use");
1177  m_metricIndices.disk.numBlocksRead = findMetricIndex("disk.num_blocks_read");
1178 #ifndef QBT_USES_LIBTORRENT2
1179  m_metricIndices.disk.numBlocksCacheHits = findMetricIndex("disk.num_blocks_cache_hits");
1180 #endif
1181  m_metricIndices.disk.writeJobs = findMetricIndex("disk.num_write_ops");
1182  m_metricIndices.disk.readJobs = findMetricIndex("disk.num_read_ops");
1183  m_metricIndices.disk.hashJobs = findMetricIndex("disk.num_blocks_hashed");
1184  m_metricIndices.disk.queuedDiskJobs = findMetricIndex("disk.queued_disk_jobs");
1185  m_metricIndices.disk.diskJobTime = findMetricIndex("disk.disk_job_time");
1186 }
1187 
1188 void Session::loadLTSettings(lt::settings_pack &settingsPack)
1189 {
1190  settingsPack.set_int(lt::settings_pack::connection_speed, connectionSpeed());
1191 
1192  // from libtorrent doc:
1193  // It will not take affect until the listen_interfaces settings is updated
1194  settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
1195 
1196  configureNetworkInterfaces(settingsPack);
1197  applyBandwidthLimits(settingsPack);
1198 
1199  // The most secure, rc4 only so that all streams are encrypted
1200  settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
1201  settingsPack.set_bool(lt::settings_pack::prefer_rc4, true);
1202  switch (encryption())
1203  {
1204  case 0: // Enabled
1205  settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_enabled);
1206  settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_enabled);
1207  break;
1208  case 1: // Forced
1209  settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_forced);
1210  settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_forced);
1211  break;
1212  default: // Disabled
1213  settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_disabled);
1214  settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_disabled);
1215  }
1216 
1217  // proxy
1218  const auto proxyManager = Net::ProxyConfigurationManager::instance();
1219  const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
1220 
1221  switch (proxyConfig.type)
1222  {
1223  case Net::ProxyType::HTTP:
1224  settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http);
1225  break;
1227  settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http_pw);
1228  break;
1230  settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks4);
1231  break;
1233  settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
1234  break;
1236  settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5_pw);
1237  break;
1238  case Net::ProxyType::None:
1239  default:
1240  settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
1241  }
1242 
1243  if (proxyConfig.type != Net::ProxyType::None)
1244  {
1245  settingsPack.set_str(lt::settings_pack::proxy_hostname, proxyConfig.ip.toStdString());
1246  settingsPack.set_int(lt::settings_pack::proxy_port, proxyConfig.port);
1247 
1248  if (proxyManager->isAuthenticationRequired())
1249  {
1250  settingsPack.set_str(lt::settings_pack::proxy_username, proxyConfig.username.toStdString());
1251  settingsPack.set_str(lt::settings_pack::proxy_password, proxyConfig.password.toStdString());
1252  }
1253 
1254  settingsPack.set_bool(lt::settings_pack::proxy_peer_connections, isProxyPeerConnectionsEnabled());
1255  }
1256 
1257  settingsPack.set_bool(lt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
1258  settingsPack.set_bool(lt::settings_pack::announce_to_all_tiers, announceToAllTiers());
1259 
1260  settingsPack.set_int(lt::settings_pack::peer_turnover, peerTurnover());
1261  settingsPack.set_int(lt::settings_pack::peer_turnover_cutoff, peerTurnoverCutoff());
1262  settingsPack.set_int(lt::settings_pack::peer_turnover_interval, peerTurnoverInterval());
1263 
1264  settingsPack.set_int(lt::settings_pack::aio_threads, asyncIOThreads());
1265 #ifdef QBT_USES_LIBTORRENT2
1266  settingsPack.set_int(lt::settings_pack::hashing_threads, hashingThreads());
1267 #endif
1268  settingsPack.set_int(lt::settings_pack::file_pool_size, filePoolSize());
1269 
1270  const int checkingMemUsageSize = checkingMemUsage() * 64;
1271  settingsPack.set_int(lt::settings_pack::checking_mem_usage, checkingMemUsageSize);
1272 
1273 #ifndef QBT_USES_LIBTORRENT2
1274  const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
1275  settingsPack.set_int(lt::settings_pack::cache_size, cacheSize);
1276  settingsPack.set_int(lt::settings_pack::cache_expiry, diskCacheTTL());
1277 #endif
1278 
1279  lt::settings_pack::io_buffer_mode_t mode = useOSCache() ? lt::settings_pack::enable_os_cache
1280  : lt::settings_pack::disable_os_cache;
1281  settingsPack.set_int(lt::settings_pack::disk_io_read_mode, mode);
1282  settingsPack.set_int(lt::settings_pack::disk_io_write_mode, mode);
1283 
1284 #ifndef QBT_USES_LIBTORRENT2
1285  settingsPack.set_bool(lt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
1286  settingsPack.set_bool(lt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
1287 #endif
1288 
1289  settingsPack.set_bool(lt::settings_pack::piece_extent_affinity, usePieceExtentAffinity());
1290 
1291  settingsPack.set_int(lt::settings_pack::suggest_mode, isSuggestModeEnabled()
1292  ? lt::settings_pack::suggest_read_cache : lt::settings_pack::no_piece_suggestions);
1293 
1294  settingsPack.set_int(lt::settings_pack::send_buffer_watermark, sendBufferWatermark() * 1024);
1295  settingsPack.set_int(lt::settings_pack::send_buffer_low_watermark, sendBufferLowWatermark() * 1024);
1296  settingsPack.set_int(lt::settings_pack::send_buffer_watermark_factor, sendBufferWatermarkFactor());
1297 
1298  settingsPack.set_bool(lt::settings_pack::anonymous_mode, isAnonymousModeEnabled());
1299 
1300  // Queueing System
1302  {
1303  adjustLimits(settingsPack);
1304 
1305  settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
1306  settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
1307  settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
1308  settingsPack.set_int(lt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
1309  settingsPack.set_int(lt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
1310  }
1311  else
1312  {
1313  settingsPack.set_int(lt::settings_pack::active_downloads, -1);
1314  settingsPack.set_int(lt::settings_pack::active_seeds, -1);
1315  settingsPack.set_int(lt::settings_pack::active_limit, -1);
1316  }
1317  settingsPack.set_int(lt::settings_pack::active_tracker_limit, -1);
1318  settingsPack.set_int(lt::settings_pack::active_dht_limit, -1);
1319  settingsPack.set_int(lt::settings_pack::active_lsd_limit, -1);
1320  settingsPack.set_int(lt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
1321 
1322  // Outgoing ports
1323  settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
1324  settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1);
1325  // UPnP lease duration
1326  settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
1327  // Type of service
1328  settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
1329  // Include overhead in transfer limits
1330  settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
1331  // IP address to announce to trackers
1332  settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
1333  // Max concurrent HTTP announces
1334  settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
1335  // Stop tracker timeout
1336  settingsPack.set_int(lt::settings_pack::stop_tracker_timeout, stopTrackerTimeout());
1337  // * Max connections limit
1338  settingsPack.set_int(lt::settings_pack::connections_limit, maxConnections());
1339  // * Global max upload slots
1340  settingsPack.set_int(lt::settings_pack::unchoke_slots_limit, maxUploads());
1341  // uTP
1342  switch (btProtocol())
1343  {
1344  case BTProtocol::Both:
1345  default:
1346  settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1347  settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1348  settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1349  settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1350  break;
1351 
1352  case BTProtocol::TCP:
1353  settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1354  settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1355  settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, false);
1356  settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
1357  break;
1358 
1359  case BTProtocol::UTP:
1360  settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, false);
1361  settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, false);
1362  settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1363  settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1364  break;
1365  }
1366 
1367  switch (utpMixedMode())
1368  {
1369  case MixedModeAlgorithm::TCP:
1370  default:
1371  settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::prefer_tcp);
1372  break;
1373  case MixedModeAlgorithm::Proportional:
1374  settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::peer_proportional);
1375  break;
1376  }
1377 
1378  settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
1379 
1380  settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
1381 
1382  settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
1383 
1384  settingsPack.set_bool(lt::settings_pack::ssrf_mitigation, isSSRFMitigationEnabled());
1385 
1386  settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
1387 
1388  settingsPack.set_bool(lt::settings_pack::apply_ip_filter_to_trackers, isTrackerFilteringEnabled());
1389 
1390  settingsPack.set_bool(lt::settings_pack::enable_dht, isDHTEnabled());
1391  if (isDHTEnabled())
1392  settingsPack.set_str(lt::settings_pack::dht_bootstrap_nodes, "dht.libtorrent.org:25401,router.bittorrent.com:6881,router.utorrent.com:6881,dht.transmissionbt.com:6881,dht.aelitis.com:6881");
1393  settingsPack.set_bool(lt::settings_pack::enable_lsd, isLSDEnabled());
1394 
1395  switch (chokingAlgorithm())
1396  {
1397  case ChokingAlgorithm::FixedSlots:
1398  default:
1399  settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::fixed_slots_choker);
1400  break;
1401  case ChokingAlgorithm::RateBased:
1402  settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::rate_based_choker);
1403  break;
1404  }
1405 
1406  switch (seedChokingAlgorithm())
1407  {
1408  case SeedChokingAlgorithm::RoundRobin:
1409  settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::round_robin);
1410  break;
1411  case SeedChokingAlgorithm::FastestUpload:
1412  default:
1413  settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::fastest_upload);
1414  break;
1415  case SeedChokingAlgorithm::AntiLeech:
1416  settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
1417  break;
1418  }
1419 }
1420 
1421 void Session::configureNetworkInterfaces(lt::settings_pack &settingsPack)
1422 {
1424  return;
1425 
1426  if (port() > 0) // user has specified port number
1427  settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);
1428 
1429  QStringList endpoints;
1430  QStringList outgoingInterfaces;
1431  const QString portString = ':' + QString::number(port());
1432 
1433  for (const QString &ip : asConst(getListeningIPs()))
1434  {
1435  const QHostAddress addr {ip};
1436  if (!addr.isNull())
1437  {
1438  const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
1439  const QString ip = isIPv6
1440  ? Utils::Net::canonicalIPv6Addr(addr).toString()
1441  : addr.toString();
1442 
1443  endpoints << ((isIPv6 ? ('[' + ip + ']') : ip) + portString);
1444 
1445  if ((ip != QLatin1String("0.0.0.0")) && (ip != QLatin1String("::")))
1446  outgoingInterfaces << ip;
1447  }
1448  else
1449  {
1450  // ip holds an interface name
1451 #ifdef Q_OS_WIN
1452  // On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
1453  // the interface's LUID and not the GUID.
1454  // Libtorrent expects GUIDs for the 'listen_interfaces' setting.
1455  const QString guid = convertIfaceNameToGuid(ip);
1456  if (!guid.isEmpty())
1457  {
1458  endpoints << (guid + portString);
1459  outgoingInterfaces << guid;
1460  }
1461  else
1462  {
1463  LogMsg(tr("Could not get GUID of network interface: %1").arg(ip) , Log::WARNING);
1464  // Since we can't get the GUID, we'll pass the interface name instead.
1465  // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
1466  endpoints << (ip + portString);
1467  outgoingInterfaces << ip;
1468  }
1469 #else
1470  endpoints << (ip + portString);
1471  outgoingInterfaces << ip;
1472 #endif
1473  }
1474  }
1475 
1476  const QString finalEndpoints = endpoints.join(',');
1477  settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
1478  LogMsg(tr("Trying to listen on: %1", "e.g: Trying to listen on: 192.168.0.1:6881")
1479  .arg(finalEndpoints), Log::INFO);
1480 
1481  settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(',').toStdString());
1483 }
1484 
1486 {
1487  lt::ip_filter f;
1488  // lt::make_address("255.255.255.255") crashes on some people's systems
1489  // so instead we use address_v4::broadcast()
1490  // Proactively do the same for 0.0.0.0 and address_v4::any()
1491  f.add_rule(lt::address_v4::any()
1492  , lt::address_v4::broadcast()
1493  , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
1494 
1495  // IPv6 may not be available on OS and the parsing
1496  // would result in an exception -> abnormal program termination
1497  // Affects Windows XP
1498  try
1499  {
1500  f.add_rule(lt::address_v6::any()
1501  , lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1502  , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
1503  }
1504  catch (const std::exception &) {}
1505 
1506  if (ignoreLimitsOnLAN())
1507  {
1508  // local networks
1509  f.add_rule(lt::make_address("10.0.0.0")
1510  , lt::make_address("10.255.255.255")
1511  , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
1512  f.add_rule(lt::make_address("172.16.0.0")
1513  , lt::make_address("172.31.255.255")
1514  , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
1515  f.add_rule(lt::make_address("192.168.0.0")
1516  , lt::make_address("192.168.255.255")
1517  , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
1518  // link local
1519  f.add_rule(lt::make_address("169.254.0.0")
1520  , lt::make_address("169.254.255.255")
1521  , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
1522  // loopback
1523  f.add_rule(lt::make_address("127.0.0.0")
1524  , lt::make_address("127.255.255.255")
1525  , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
1526 
1527  // IPv6 may not be available on OS and the parsing
1528  // would result in an exception -> abnormal program termination
1529  // Affects Windows XP
1530  try
1531  {
1532  // link local
1533  f.add_rule(lt::make_address("fe80::")
1534  , lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1535  , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
1536  // unique local addresses
1537  f.add_rule(lt::make_address("fc00::")
1538  , lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1539  , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
1540  // loopback
1541  f.add_rule(lt::address_v6::loopback()
1542  , lt::address_v6::loopback()
1543  , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
1544  }
1545  catch (const std::exception &) {}
1546  }
1547  m_nativeSession->set_peer_class_filter(f);
1548 
1549  lt::peer_class_type_filter peerClassTypeFilter;
1550  peerClassTypeFilter.add(lt::peer_class_type_filter::tcp_socket, lt::session::tcp_peer_class_id);
1551  peerClassTypeFilter.add(lt::peer_class_type_filter::ssl_tcp_socket, lt::session::tcp_peer_class_id);
1552  peerClassTypeFilter.add(lt::peer_class_type_filter::i2p_socket, lt::session::tcp_peer_class_id);
1553  if (!isUTPRateLimited())
1554  {
1555  peerClassTypeFilter.disallow(lt::peer_class_type_filter::utp_socket
1556  , lt::session::global_peer_class_id);
1557  peerClassTypeFilter.disallow(lt::peer_class_type_filter::ssl_utp_socket
1558  , lt::session::global_peer_class_id);
1559  }
1560  m_nativeSession->set_peer_class_type_filter(peerClassTypeFilter);
1561 }
1562 
1563 void Session::enableTracker(const bool enable)
1564 {
1565  if (enable)
1566  {
1567  if (!m_tracker)
1568  m_tracker = new Tracker(this);
1569 
1570  m_tracker->start();
1571  }
1572  else
1573  {
1574  delete m_tracker;
1575  }
1576 }
1577 
1579 {
1580  if (!m_bwScheduler)
1581  {
1582  m_bwScheduler = new BandwidthScheduler(this);
1585  }
1586  m_bwScheduler->start();
1587 }
1588 
1590 {
1591  m_additionalTrackerList.clear();
1592 
1593  const QString trackers = additionalTrackers();
1594  for (QStringView tracker : asConst(QStringView(trackers).split(u'\n')))
1595  {
1596  tracker = tracker.trimmed();
1597  if (!tracker.isEmpty())
1598  m_additionalTrackerList.append({tracker.toString()});
1599  }
1600 }
1601 
1603 {
1604  qDebug("Processing share limits...");
1605 
1606  // We shouldn't iterate over `m_torrents` in the loop below
1607  // since `deleteTorrent()` modifies it indirectly
1608  const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
1609  for (TorrentImpl *const torrent : torrents)
1610  {
1611  if (torrent->isSeed() && !torrent->isForced())
1612  {
1613  if (torrent->ratioLimit() != Torrent::NO_RATIO_LIMIT)
1614  {
1615  const qreal ratio = torrent->realRatio();
1616  qreal ratioLimit = torrent->ratioLimit();
1617  if (ratioLimit == Torrent::USE_GLOBAL_RATIO)
1618  // If Global Max Ratio is really set...
1619  ratioLimit = globalMaxRatio();
1620 
1621  if (ratioLimit >= 0)
1622  {
1623  qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
1624 
1625  if ((ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
1626  {
1627  if (m_maxRatioAction == Remove)
1628  {
1629  LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
1630  deleteTorrent(torrent->id());
1631  }
1632  else if (m_maxRatioAction == DeleteFiles)
1633  {
1634  LogMsg(tr("'%1' reached the maximum ratio you set. Removed torrent and its files.").arg(torrent->name()));
1635  deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
1636  }
1637  else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
1638  {
1639  torrent->pause();
1640  LogMsg(tr("'%1' reached the maximum ratio you set. Paused.").arg(torrent->name()));
1641  }
1642  else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
1643  {
1644  torrent->setSuperSeeding(true);
1645  LogMsg(tr("'%1' reached the maximum ratio you set. Enabled super seeding for it.").arg(torrent->name()));
1646  }
1647  continue;
1648  }
1649  }
1650  }
1651 
1652  if (torrent->seedingTimeLimit() != Torrent::NO_SEEDING_TIME_LIMIT)
1653  {
1654  const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
1655  int seedingTimeLimit = torrent->seedingTimeLimit();
1656  if (seedingTimeLimit == Torrent::USE_GLOBAL_SEEDING_TIME)
1657  {
1658  // If Global Seeding Time Limit is really set...
1659  seedingTimeLimit = globalMaxSeedingMinutes();
1660  }
1661 
1662  if (seedingTimeLimit >= 0)
1663  {
1664  if ((seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
1665  {
1666  if (m_maxRatioAction == Remove)
1667  {
1668  LogMsg(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
1669  deleteTorrent(torrent->id());
1670  }
1671  else if (m_maxRatioAction == DeleteFiles)
1672  {
1673  LogMsg(tr("'%1' reached the maximum seeding time you set. Removed torrent and its files.").arg(torrent->name()));
1674  deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
1675  }
1676  else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
1677  {
1678  torrent->pause();
1679  LogMsg(tr("'%1' reached the maximum seeding time you set. Paused.").arg(torrent->name()));
1680  }
1681  else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
1682  {
1683  torrent->setSuperSeeding(true);
1684  LogMsg(tr("'%1' reached the maximum seeding time you set. Enabled super seeding for it.").arg(torrent->name()));
1685  }
1686  }
1687  }
1688  }
1689  }
1690  }
1691 }
1692 
1693 // Add to BitTorrent session the downloaded torrent file
1695 {
1696  switch (result.status)
1697  {
1699  emit downloadFromUrlFinished(result.url);
1700  if (const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::load(result.data); loadResult)
1701  addTorrent(loadResult.value(), m_downloadedTorrents.take(result.url));
1702  else
1703  LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING);
1704  break;
1706  emit downloadFromUrlFinished(result.url);
1707  addTorrent(MagnetUri(result.magnet), m_downloadedTorrents.take(result.url));
1708  break;
1709  default:
1710  emit downloadFromUrlFailed(result.url, result.errorString);
1711  }
1712 }
1713 
1714 void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames)
1715 {
1716  TorrentImpl *torrent = m_torrents.value(id);
1717  if (torrent)
1718  {
1719  torrent->fileSearchFinished(savePath, fileNames);
1720  return;
1721  }
1722 
1723  const auto loadingTorrentsIter = m_loadingTorrents.find(id);
1724  if (loadingTorrentsIter != m_loadingTorrents.end())
1725  {
1726  LoadTorrentParams params = loadingTorrentsIter.value();
1727  m_loadingTorrents.erase(loadingTorrentsIter);
1728 
1729  lt::add_torrent_params &p = params.ltAddTorrentParams;
1730 
1731  p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
1732  const TorrentInfo torrentInfo {*p.ti};
1733  const auto nativeIndexes = torrentInfo.nativeIndexes();
1734  for (int i = 0; i < fileNames.size(); ++i)
1735  p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
1736 
1737  loadTorrent(params);
1738  }
1739 }
1740 
1741 // Return the torrent handle, given its hash
1743 {
1744  return m_torrents.value(id);
1745 }
1746 
1748 {
1749  return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentImpl *torrent)
1750  {
1751  return TorrentFilter::ActiveTorrent.match(torrent);
1752  });
1753 }
1754 
1756 {
1757  return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
1758  {
1759  return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored());
1760  });
1761 }
1762 
1764 {
1765  return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
1766  {
1767  return (torrent->isSeed() && !torrent->isPaused());
1768  });
1769 }
1770 
1771 void Session::banIP(const QString &ip)
1772 {
1773  QStringList bannedIPs = m_bannedIPs;
1774  if (!bannedIPs.contains(ip))
1775  {
1776  lt::ip_filter filter = m_nativeSession->get_ip_filter();
1777  lt::error_code ec;
1778  const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1779  Q_ASSERT(!ec);
1780  if (ec) return;
1781  filter.add_rule(addr, addr, lt::ip_filter::blocked);
1782  m_nativeSession->set_ip_filter(filter);
1783 
1784  bannedIPs << ip;
1785  bannedIPs.sort();
1787  }
1788 }
1789 
1790 // Delete a torrent from the session, given its hash
1791 // and from the disk, if the corresponding deleteOption is chosen
1792 bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
1793 {
1794  TorrentImpl *const torrent = m_torrents.take(id);
1795  if (!torrent) return false;
1796 
1797  qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
1798  emit torrentAboutToBeRemoved(torrent);
1799 
1800  // Remove it from session
1801  if (deleteOption == DeleteTorrent)
1802  {
1803  m_removingTorrents[torrent->id()] = {torrent->name(), "", deleteOption};
1804 
1805  const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
1806  const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
1807  , [&nativeHandle](const MoveStorageJob &job)
1808  {
1809  return job.torrentHandle == nativeHandle;
1810  });
1811  if (iter != m_moveStorageQueue.end())
1812  {
1813  // We shouldn't actually remove torrent until existing "move storage jobs" are done
1814  torrentQueuePositionBottom(nativeHandle);
1815  nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
1816  nativeHandle.pause();
1817  }
1818  else
1819  {
1820  m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
1821  }
1822  }
1823  else
1824  {
1825  m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
1826 
1827  if (m_moveStorageQueue.size() > 1)
1828  {
1829  // Delete "move storage job" for the deleted torrent
1830  // (note: we shouldn't delete active job)
1831  const auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
1832  , [torrent](const MoveStorageJob &job)
1833  {
1834  return job.torrentHandle == torrent->nativeHandle();
1835  });
1836  if (iter != m_moveStorageQueue.end())
1837  m_moveStorageQueue.erase(iter);
1838  }
1839 
1840  m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
1841  }
1842 
1843  // Remove it from torrent resume directory
1844  m_resumeDataStorage->remove(torrent->id());
1845 
1846  delete torrent;
1847  return true;
1848 }
1849 
1851 {
1852  const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
1853  if (downloadedMetadataIter == m_downloadedMetadata.end()) return false;
1854 
1855  m_downloadedMetadata.erase(downloadedMetadataIter);
1856  --m_extraLimit;
1857  adjustLimits();
1858  m_nativeSession->remove_torrent(m_nativeSession->find_torrent(id), lt::session::delete_files);
1859  return true;
1860 }
1861 
1862 void Session::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
1863 {
1864  using ElementType = std::pair<int, const TorrentImpl *>;
1865  std::priority_queue<ElementType
1866  , std::vector<ElementType>
1867  , std::greater<ElementType>> torrentQueue;
1868 
1869  // Sort torrents by queue position
1870  for (const TorrentID &id : ids)
1871  {
1872  const TorrentImpl *torrent = m_torrents.value(id);
1873  if (!torrent) continue;
1874  if (const int position = torrent->queuePosition(); position >= 0)
1875  torrentQueue.emplace(position, torrent);
1876  }
1877 
1878  // Increase torrents queue position (starting with the one in the highest queue position)
1879  while (!torrentQueue.empty())
1880  {
1881  const TorrentImpl *torrent = torrentQueue.top().second;
1883  torrentQueue.pop();
1884  }
1885 
1887 }
1888 
1889 void Session::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
1890 {
1891  using ElementType = std::pair<int, const TorrentImpl *>;
1892  std::priority_queue<ElementType> torrentQueue;
1893 
1894  // Sort torrents by queue position
1895  for (const TorrentID &id : ids)
1896  {
1897  const TorrentImpl *torrent = m_torrents.value(id);
1898  if (!torrent) continue;
1899  if (const int position = torrent->queuePosition(); position >= 0)
1900  torrentQueue.emplace(position, torrent);
1901  }
1902 
1903  // Decrease torrents queue position (starting with the one in the lowest queue position)
1904  while (!torrentQueue.empty())
1905  {
1906  const TorrentImpl *torrent = torrentQueue.top().second;
1908  torrentQueue.pop();
1909  }
1910 
1911  for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i)
1912  torrentQueuePositionBottom(m_nativeSession->find_torrent(*i));
1913 
1915 }
1916 
1917 void Session::topTorrentsQueuePos(const QVector<TorrentID> &ids)
1918 {
1919  using ElementType = std::pair<int, const TorrentImpl *>;
1920  std::priority_queue<ElementType> torrentQueue;
1921 
1922  // Sort torrents by queue position
1923  for (const TorrentID &id : ids)
1924  {
1925  const TorrentImpl *torrent = m_torrents.value(id);
1926  if (!torrent) continue;
1927  if (const int position = torrent->queuePosition(); position >= 0)
1928  torrentQueue.emplace(position, torrent);
1929  }
1930 
1931  // Top torrents queue position (starting with the one in the lowest queue position)
1932  while (!torrentQueue.empty())
1933  {
1934  const TorrentImpl *torrent = torrentQueue.top().second;
1936  torrentQueue.pop();
1937  }
1938 
1940 }
1941 
1942 void Session::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
1943 {
1944  using ElementType = std::pair<int, const TorrentImpl *>;
1945  std::priority_queue<ElementType
1946  , std::vector<ElementType>
1947  , std::greater<ElementType>> torrentQueue;
1948 
1949  // Sort torrents by queue position
1950  for (const TorrentID &id : ids)
1951  {
1952  const TorrentImpl *torrent = m_torrents.value(id);
1953  if (!torrent) continue;
1954  if (const int position = torrent->queuePosition(); position >= 0)
1955  torrentQueue.emplace(position, torrent);
1956  }
1957 
1958  // Bottom torrents queue position (starting with the one in the highest queue position)
1959  while (!torrentQueue.empty())
1960  {
1961  const TorrentImpl *torrent = torrentQueue.top().second;
1963  torrentQueue.pop();
1964  }
1965 
1966  for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i)
1967  torrentQueuePositionBottom(m_nativeSession->find_torrent(*i));
1968 
1970 }
1971 
1973 {
1974  if (m_needSaveResumeDataTorrents.empty())
1975  {
1976  QMetaObject::invokeMethod(this, [this]()
1977  {
1978  for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
1979  {
1980  TorrentImpl *torrent = m_torrents.value(torrentID);
1981  if (torrent)
1982  torrent->saveResumeData();
1983  }
1985  }, Qt::QueuedConnection);
1986  }
1987 
1988  m_needSaveResumeDataTorrents.insert(torrent->id());
1989 }
1990 
1992 {
1993  qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
1994  ++m_numResumeData;
1995 }
1996 
1997 QVector<Torrent *> Session::torrents() const
1998 {
1999  QVector<Torrent *> result;
2000  result.reserve(m_torrents.size());
2001  for (TorrentImpl *torrent : asConst(m_torrents))
2002  result << torrent;
2003 
2004  return result;
2005 }
2006 
2007 bool Session::addTorrent(const QString &source, const AddTorrentParams &params)
2008 {
2009  // `source`: .torrent file path/url or magnet uri
2010 
2012  {
2013  LogMsg(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source));
2014  // Launch downloader
2017  m_downloadedTorrents[source] = params;
2018  return true;
2019  }
2020 
2021  const MagnetUri magnetUri {source};
2022  if (magnetUri.isValid())
2023  return addTorrent(magnetUri, params);
2024 
2025  TorrentFileGuard guard {source};
2026  const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(source);
2027  if (!loadResult)
2028  {
2029  LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING);
2030  return false;
2031  }
2032 
2033  guard.markAsAddedToSession();
2034  return addTorrent(loadResult.value(), params);
2035 }
2036 
2037 bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &params)
2038 {
2039  if (!magnetUri.isValid()) return false;
2040 
2041  return addTorrent_impl(magnetUri, params);
2042 }
2043 
2044 bool Session::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams &params)
2045 {
2046  return addTorrent_impl(torrentInfo, params);
2047 }
2048 
2050 {
2051  LoadTorrentParams loadTorrentParams;
2052 
2053  loadTorrentParams.name = addTorrentParams.name;
2054  loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(!isAutoTMMDisabledByDefault());
2055  loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
2056  loadTorrentParams.hasSeedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
2057  loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
2058  loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged);
2059  loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused());
2060  loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
2061  loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
2062 
2063  if (!loadTorrentParams.useAutoTMM)
2064  {
2065  loadTorrentParams.savePath = (QDir::isAbsolutePath(addTorrentParams.savePath)
2066  ? addTorrentParams.savePath
2067  : Utils::Fs::resolvePath(addTorrentParams.savePath, savePath()));
2068 
2069  const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(isDownloadPathEnabled());
2070  if (useDownloadPath)
2071  {
2072  loadTorrentParams.downloadPath = (QDir::isAbsolutePath(addTorrentParams.downloadPath)
2073  ? addTorrentParams.downloadPath
2074  : Utils::Fs::resolvePath(addTorrentParams.downloadPath, downloadPath()));
2075  }
2076  }
2077 
2078  const QString category = addTorrentParams.category;
2079  if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
2080  loadTorrentParams.category = "";
2081  else
2082  loadTorrentParams.category = category;
2083 
2084  for (const QString &tag : addTorrentParams.tags)
2085  {
2086  if (hasTag(tag) || addTag(tag))
2087  loadTorrentParams.tags.insert(tag);
2088  }
2089 
2090  return loadTorrentParams;
2091 }
2092 
2093 // Add a torrent to the BitTorrent session
2094 bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams)
2095 {
2096  const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
2097  const auto id = TorrentID::fromInfoHash(hasMetadata ? std::get<TorrentInfo>(source).infoHash() : std::get<MagnetUri>(source).infoHash());
2098 
2099  // It looks illogical that we don't just use an existing handle,
2100  // but as previous experience has shown, it actually creates unnecessary
2101  // problems and unwanted behavior due to the fact that it was originally
2102  // added with parameters other than those provided by the user.
2104 
2105  // We should not add the torrent if it is already
2106  // processed or is pending to add to session
2107  if (m_loadingTorrents.contains(id))
2108  return false;
2109 
2110  TorrentImpl *const torrent = m_torrents.value(id);
2111  if (torrent)
2112  { // a duplicate torrent is added
2113  if (torrent->isPrivate())
2114  return false;
2115 
2116  if (hasMetadata)
2117  {
2118  const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
2119 
2120  if (torrentInfo.isPrivate())
2121  return false;
2122 
2123  // merge trackers and web seeds
2124  torrent->addTrackers(torrentInfo.trackers());
2125  torrent->addUrlSeeds(torrentInfo.urlSeeds());
2126  }
2127  else
2128  {
2129  const MagnetUri &magnetUri = std::get<MagnetUri>(source);
2130 
2131  // merge trackers and web seeds
2132  torrent->addTrackers(magnetUri.trackers());
2133  torrent->addUrlSeeds(magnetUri.urlSeeds());
2134  }
2135 
2136  return true;
2137  }
2138 
2139  LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams);
2140  lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
2141 
2142  bool isFindingIncompleteFiles = false;
2143 
2144  const bool useAutoTMM = loadTorrentParams.useAutoTMM;
2145  const QString actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
2146 
2147  if (hasMetadata)
2148  {
2149  const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
2150 
2151  // if torrent name wasn't explicitly set we handle the case of
2152  // initial renaming of torrent content and rename torrent accordingly
2153  if (loadTorrentParams.name.isEmpty())
2154  {
2155  QString contentName = torrentInfo.rootFolder();
2156  if (contentName.isEmpty() && (torrentInfo.filesCount() == 1))
2157  contentName = Utils::Fs::fileName(torrentInfo.filePath(0));
2158 
2159  if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
2160  loadTorrentParams.name = contentName;
2161  }
2162 
2163  Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
2164 
2165  const TorrentContentLayout contentLayout = ((loadTorrentParams.contentLayout == TorrentContentLayout::Original)
2166  ? detectContentLayout(torrentInfo.filePaths()) : loadTorrentParams.contentLayout);
2167  QStringList filePaths = (!addTorrentParams.filePaths.isEmpty() ? addTorrentParams.filePaths : torrentInfo.filePaths());
2168  applyContentLayout(filePaths, contentLayout, Utils::Fs::findRootFolder(torrentInfo.filePaths()));
2169 
2170  if (!loadTorrentParams.hasSeedStatus)
2171  {
2172  const QString actualDownloadPath = useAutoTMM
2173  ? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
2174  findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
2175  isFindingIncompleteFiles = true;
2176  }
2177 
2178  const auto nativeIndexes = torrentInfo.nativeIndexes();
2179  if (!filePaths.isEmpty())
2180  {
2181  for (int index = 0; index < addTorrentParams.filePaths.size(); ++index)
2182  p.renamed_files[nativeIndexes[index]] = Utils::Fs::toNativePath(addTorrentParams.filePaths.at(index)).toStdString();
2183  }
2184 
2185  Q_ASSERT(p.file_priorities.empty());
2186  const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2187  // Use qBittorrent default priority rather than libtorrent's (4)
2188  p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2189  Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
2190  for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
2191  p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
2192 
2193  p.ti = torrentInfo.nativeInfo();
2194  }
2195  else
2196  {
2197  const MagnetUri &magnetUri = std::get<MagnetUri>(source);
2198  p = magnetUri.addTorrentParams();
2199 
2200  if (loadTorrentParams.name.isEmpty() && !p.name.empty())
2201  loadTorrentParams.name = QString::fromStdString(p.name);
2202  }
2203 
2204  p.save_path = Utils::Fs::toNativePath(actualSavePath).toStdString();
2205 
2206  p.upload_limit = addTorrentParams.uploadLimit;
2207  p.download_limit = addTorrentParams.downloadLimit;
2208 
2209  // Preallocation mode
2210  p.storage_mode = isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse;
2211 
2212  if (addTorrentParams.sequential)
2213  p.flags |= lt::torrent_flags::sequential_download;
2214  else
2215  p.flags &= ~lt::torrent_flags::sequential_download;
2216 
2217  // Seeding mode
2218  // Skip checking and directly start seeding
2219  if (addTorrentParams.skipChecking)
2220  p.flags |= lt::torrent_flags::seed_mode;
2221  else
2222  p.flags &= ~lt::torrent_flags::seed_mode;
2223 
2224  if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::AutoManaged))
2225  p.flags |= lt::torrent_flags::paused;
2226  else
2227  p.flags &= ~lt::torrent_flags::paused;
2228  if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::Forced))
2229  p.flags &= ~lt::torrent_flags::auto_managed;
2230  else
2231  p.flags |= lt::torrent_flags::auto_managed;
2232 
2233  p.added_time = std::time(nullptr);
2234 
2235  if (!isFindingIncompleteFiles)
2236  return loadTorrent(loadTorrentParams);
2237 
2238  m_loadingTorrents.insert(id, loadTorrentParams);
2239  return true;
2240 }
2241 
2242 // Add a torrent to the BitTorrent session
2244 {
2245  lt::add_torrent_params &p = params.ltAddTorrentParams;
2246 
2247 #ifndef QBT_USES_LIBTORRENT2
2248  p.storage = customStorageConstructor;
2249 #endif
2250  // Limits
2251  p.max_connections = maxConnectionsPerTorrent();
2252  p.max_uploads = maxUploadsPerTorrent();
2253 
2254  const bool hasMetadata = (p.ti && p.ti->is_valid());
2255 #ifdef QBT_USES_LIBTORRENT2
2256  const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hashes() : p.info_hashes);
2257 #else
2258  const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hash() : p.info_hash);
2259 #endif
2260  m_loadingTorrents.insert(id, params);
2261 
2262  // Adding torrent to BitTorrent session
2263  m_nativeSession->async_add_torrent(p);
2264 
2265  return true;
2266 }
2267 
2268 void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath
2269  , const QString &downloadPath, const QStringList &filePaths) const
2270 {
2271  Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
2272 
2273  const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
2274  const QStringList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
2275  QMetaObject::invokeMethod(m_fileSearcher, [=]()
2276  {
2277  m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath);
2278  });
2279 }
2280 
2281 // Add a torrent to libtorrent session in hidden mode
2282 // and force it to download its metadata
2283 bool Session::downloadMetadata(const MagnetUri &magnetUri)
2284 {
2285  if (!magnetUri.isValid()) return false;
2286 
2287  const auto id = TorrentID::fromInfoHash(magnetUri.infoHash());
2288  const QString name = magnetUri.name();
2289 
2290  // We should not add torrent if it's already
2291  // processed or adding to session
2292  if (m_torrents.contains(id)) return false;
2293  if (m_loadingTorrents.contains(id)) return false;
2294  if (m_downloadedMetadata.contains(id)) return false;
2295 
2296  qDebug("Adding torrent to preload metadata...");
2297  qDebug(" -> Torrent ID: %s", qUtf8Printable(id.toString()));
2298  qDebug(" -> Name: %s", qUtf8Printable(name));
2299 
2300  lt::add_torrent_params p = magnetUri.addTorrentParams();
2301 
2302  // Flags
2303  // Preallocation mode
2304  if (isPreallocationEnabled())
2305  p.storage_mode = lt::storage_mode_allocate;
2306  else
2307  p.storage_mode = lt::storage_mode_sparse;
2308 
2309  // Limits
2310  p.max_connections = maxConnectionsPerTorrent();
2311  p.max_uploads = maxUploadsPerTorrent();
2312 
2313  const QString savePath = Utils::Fs::tempPath() + id.toString();
2314  p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
2315 
2316  // Forced start
2317  p.flags &= ~lt::torrent_flags::paused;
2318  p.flags &= ~lt::torrent_flags::auto_managed;
2319 
2320  // Solution to avoid accidental file writes
2321  p.flags |= lt::torrent_flags::upload_mode;
2322 
2323 #ifndef QBT_USES_LIBTORRENT2
2324  p.storage = customStorageConstructor;
2325 #endif
2326 
2327  // Adding torrent to libtorrent session
2328  lt::error_code ec;
2329  lt::torrent_handle h = m_nativeSession->add_torrent(p, ec);
2330  if (ec) return false;
2331 
2332  // waiting for metadata...
2333  m_downloadedMetadata.insert(h.info_hash());
2334  ++m_extraLimit;
2335  adjustLimits();
2336 
2337  return true;
2338 }
2339 
2340 void Session::exportTorrentFile(const TorrentInfo &torrentInfo, const QString &folderPath, const QString &baseName)
2341 {
2342  const QString validName = Utils::Fs::toValidFileSystemName(baseName);
2343  QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
2344  const QDir exportDir {folderPath};
2345  if (exportDir.exists() || exportDir.mkpath(exportDir.absolutePath()))
2346  {
2347  QString newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename);
2348  int counter = 0;
2349  while (QFile::exists(newTorrentPath))
2350  {
2351  // Append number to torrent name to make it unique
2352  torrentExportFilename = QString::fromLatin1("%1 %2.torrent").arg(validName).arg(++counter);
2353  newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename);
2354  }
2355 
2356  const nonstd::expected<void, QString> result = torrentInfo.saveToFile(newTorrentPath);
2357  if (!result)
2358  {
2359  LogMsg(tr("Couldn't export torrent metadata file '%1'. Reason: %2.")
2360  .arg(newTorrentPath, result.error()), Log::WARNING);
2361  }
2362  }
2363 }
2364 
2366 {
2367  for (TorrentImpl *const torrent : asConst(m_torrents))
2368  {
2369  if (!torrent->isValid()) continue;
2370 
2371  if (torrent->needSaveResumeData())
2372  {
2373  torrent->saveResumeData();
2374  m_needSaveResumeDataTorrents.remove(torrent->id());
2375  }
2376  }
2377 }
2378 
2379 // Called on exit
2381 {
2382  // Pause session
2383  m_nativeSession->pause();
2384 
2388 
2389  while (m_numResumeData > 0)
2390  {
2391  const std::vector<lt::alert *> alerts = getPendingAlerts(lt::seconds {30});
2392  if (alerts.empty())
2393  {
2394  LogMsg(tr("Error: Aborted saving resume data for %1 outstanding torrents.").arg(QString::number(m_numResumeData))
2395  , Log::CRITICAL);
2396  break;
2397  }
2398 
2399  for (const lt::alert *a : alerts)
2400  {
2401  switch (a->type())
2402  {
2403  case lt::save_resume_data_failed_alert::alert_type:
2404  case lt::save_resume_data_alert::alert_type:
2406  break;
2407  }
2408  }
2409  }
2410 }
2411 
2413 {
2414  QVector<TorrentID> queue;
2415  for (const TorrentImpl *torrent : asConst(m_torrents))
2416  {
2417  // We require actual (non-cached) queue position here!
2418  const int queuePos = LT::toUnderlyingType(torrent->nativeHandle().queue_position());
2419  if (queuePos >= 0)
2420  {
2421  if (queuePos >= queue.size())
2422  queue.resize(queuePos + 1);
2423  queue[queuePos] = torrent->id();
2424  }
2425  }
2426 
2428 }
2429 
2431 {
2433 }
2434 
2435 void Session::setSavePath(const QString &path)
2436 {
2437  const QString baseSavePath = specialFolderLocation(SpecialFolder::Downloads);
2438  const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, baseSavePath));
2439  if (resolvedPath == m_savePath) return;
2440 
2441  m_savePath = resolvedPath;
2442 
2444  {
2445  for (TorrentImpl *const torrent : asConst(m_torrents))
2446  torrent->setAutoTMMEnabled(false);
2447  }
2448  else
2449  {
2450  for (TorrentImpl *const torrent : asConst(m_torrents))
2451  torrent->handleCategoryOptionsChanged();
2452  }
2453 }
2454 
2455 void Session::setDownloadPath(const QString &path)
2456 {
2457  const QString baseDownloadPath = specialFolderLocation(SpecialFolder::Downloads) + QLatin1String("/temp");
2458  const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, baseDownloadPath));
2459  if (resolvedPath != m_downloadPath)
2460  {
2461  m_downloadPath = resolvedPath;
2462 
2463  for (TorrentImpl *const torrent : asConst(m_torrents))
2464  torrent->handleDownloadPathChanged();
2465  }
2466 }
2467 
2468 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
2469 void Session::networkOnlineStateChanged(const bool online)
2470 {
2471  LogMsg(tr("System network status changed to %1", "e.g: System network status changed to ONLINE").arg(online ? tr("ONLINE") : tr("OFFLINE")), Log::INFO);
2472 }
2473 
2474 void Session::networkConfigurationChange(const QNetworkConfiguration &cfg)
2475 {
2476  const QString configuredInterfaceName = networkInterface();
2477  // Empty means "Any Interface". In this case libtorrent has binded to 0.0.0.0 so any change to any interface will
2478  // be automatically picked up. Otherwise we would rebinding here to 0.0.0.0 again.
2479  if (configuredInterfaceName.isEmpty()) return;
2480 
2481  const QString changedInterface = cfg.name();
2482 
2483  if (configuredInterfaceName == changedInterface)
2484  {
2485  LogMsg(tr("Network configuration of %1 has changed, refreshing session binding", "e.g: Network configuration of tun0 has changed, refreshing session binding").arg(changedInterface), Log::INFO);
2487  }
2488 }
2489 #endif
2490 
2491 QStringList Session::getListeningIPs() const
2492 {
2493  QStringList IPs;
2494 
2495  const QString ifaceName = networkInterface();
2496  const QString ifaceAddr = networkInterfaceAddress();
2497  const QHostAddress configuredAddr(ifaceAddr);
2498  const bool allIPv4 = (ifaceAddr == QLatin1String("0.0.0.0")); // Means All IPv4 addresses
2499  const bool allIPv6 = (ifaceAddr == QLatin1String("::")); // Means All IPv6 addresses
2500 
2501  if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull())
2502  {
2503  LogMsg(tr("Configured network interface address %1 isn't valid.", "Configured network interface address 124.5.158.1 isn't valid.").arg(ifaceAddr), Log::CRITICAL);
2504  // Pass the invalid user configured interface name/address to libtorrent
2505  // in hopes that it will come online later.
2506  // This will not cause IP leak but allow user to reconnect the interface
2507  // and re-establish connection without restarting the client.
2508  IPs.append(ifaceAddr);
2509  return IPs;
2510  }
2511 
2512  if (ifaceName.isEmpty())
2513  {
2514  if (ifaceAddr.isEmpty())
2515  return {QLatin1String("0.0.0.0"), QLatin1String("::")}; // Indicates all interfaces + all addresses (aka default)
2516 
2517  if (allIPv4)
2518  return {QLatin1String("0.0.0.0")};
2519 
2520  if (allIPv6)
2521  return {QLatin1String("::")};
2522  }
2523 
2524  const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
2525  {
2526  if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
2527  || (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
2528  return;
2529 
2530  if ((match == addr) || allIPv4 || allIPv6)
2531  IPs.append(addr.toString());
2532  };
2533 
2534  if (ifaceName.isEmpty())
2535  {
2536  const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
2537  for (const auto &addr : addresses)
2538  checkAndAddIP(addr, configuredAddr);
2539 
2540  // At this point ifaceAddr was non-empty
2541  // If IPs.isEmpty() it means the configured Address was not found
2542  if (IPs.isEmpty())
2543  {
2544  LogMsg(tr("Can't find the configured address '%1' to listen on"
2545  , "Can't find the configured address '192.168.1.3' to listen on")
2546  .arg(ifaceAddr), Log::CRITICAL);
2547  IPs.append(ifaceAddr);
2548  }
2549 
2550  return IPs;
2551  }
2552 
2553  // Attempt to listen on provided interface
2554  const QNetworkInterface networkIFace = QNetworkInterface::interfaceFromName(ifaceName);
2555  if (!networkIFace.isValid())
2556  {
2557  qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName));
2558  LogMsg(tr("The network interface defined is invalid: %1").arg(ifaceName), Log::CRITICAL);
2559  IPs.append(ifaceName);
2560  return IPs;
2561  }
2562 
2563  if (ifaceAddr.isEmpty())
2564  {
2565  IPs.append(ifaceName);
2566  return IPs; // On Windows calling code converts it to GUID
2567  }
2568 
2569  const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
2570  qDebug() << "This network interface has " << addresses.size() << " IP addresses";
2571  for (const QNetworkAddressEntry &entry : addresses)
2572  checkAndAddIP(entry.ip(), configuredAddr);
2573 
2574  // Make sure there is at least one IP
2575  // At this point there was an explicit interface and an explicit address set
2576  // and the address should have been found
2577  if (IPs.isEmpty())
2578  {
2579  LogMsg(tr("Can't find the configured address '%1' to listen on"
2580  , "Can't find the configured address '192.168.1.3' to listen on")
2581  .arg(ifaceAddr), Log::CRITICAL);
2582  IPs.append(ifaceAddr);
2583  }
2584 
2585  return IPs;
2586 }
2587 
2588 // Set the ports range in which is chosen the port
2589 // the BitTorrent session will listen to
2591 {
2594 }
2595 
2597 {
2598  // Unfortunately the value was saved as KiB instead of B.
2599  // But it is better to pass it around internally(+ webui) as Bytes.
2600  return m_globalDownloadSpeedLimit * 1024;
2601 }
2602 
2604 {
2605  // Unfortunately the value was saved as KiB instead of B.
2606  // But it is better to pass it around internally(+ webui) as Bytes.
2607  if (limit == globalDownloadSpeedLimit())
2608  return;
2609 
2610  if (limit <= 0)
2612  else if (limit <= 1024)
2614  else
2615  m_globalDownloadSpeedLimit = (limit / 1024);
2616 
2619 }
2620 
2622 {
2623  // Unfortunately the value was saved as KiB instead of B.
2624  // But it is better to pass it around internally(+ webui) as Bytes.
2625  return m_globalUploadSpeedLimit * 1024;
2626 }
2627 
2629 {
2630  // Unfortunately the value was saved as KiB instead of B.
2631  // But it is better to pass it around internally(+ webui) as Bytes.
2632  if (limit == globalUploadSpeedLimit())
2633  return;
2634 
2635  if (limit <= 0)
2637  else if (limit <= 1024)
2639  else
2640  m_globalUploadSpeedLimit = (limit / 1024);
2641 
2644 }
2645 
2647 {
2648  // Unfortunately the value was saved as KiB instead of B.
2649  // But it is better to pass it around internally(+ webui) as Bytes.
2650  return m_altGlobalDownloadSpeedLimit * 1024;
2651 }
2652 
2654 {
2655  // Unfortunately the value was saved as KiB instead of B.
2656  // But it is better to pass it around internally(+ webui) as Bytes.
2657  if (limit == altGlobalDownloadSpeedLimit())
2658  return;
2659 
2660  if (limit <= 0)
2662  else if (limit <= 1024)
2664  else
2665  m_altGlobalDownloadSpeedLimit = (limit / 1024);
2666 
2669 }
2670 
2672 {
2673  // Unfortunately the value was saved as KiB instead of B.
2674  // But it is better to pass it around internally(+ webui) as Bytes.
2675  return m_altGlobalUploadSpeedLimit * 1024;
2676 }
2677 
2679 {
2680  // Unfortunately the value was saved as KiB instead of B.
2681  // But it is better to pass it around internally(+ webui) as Bytes.
2682  if (limit == altGlobalUploadSpeedLimit())
2683  return;
2684 
2685  if (limit <= 0)
2687  else if (limit <= 1024)
2689  else
2690  m_altGlobalUploadSpeedLimit = (limit / 1024);
2691 
2694 }
2695 
2697 {
2701 }
2702 
2703 void Session::setDownloadSpeedLimit(const int limit)
2704 {
2707  else
2709 }
2710 
2712 {
2716 }
2717 
2718 void Session::setUploadSpeedLimit(const int limit)
2719 {
2722  else
2724 }
2725 
2727 {
2729 }
2730 
2732 {
2733  if (enabled == isAltGlobalSpeedLimitEnabled()) return;
2734 
2735  // Save new state to remember it on startup
2738  // Notify
2740 }
2741 
2743 {
2745 }
2746 
2748 {
2749  if (enabled != isBandwidthSchedulerEnabled())
2750  {
2752  if (enabled)
2754  else
2755  delete m_bwScheduler;
2756  }
2757 }
2758 
2760 {
2761  return m_saveResumeDataInterval;
2762 }
2763 
2765 {
2767  return;
2768 
2770 
2771  if (value > 0)
2772  {
2773  m_resumeDataTimer->setInterval(value * 60 * 1000);
2774  m_resumeDataTimer->start();
2775  }
2776  else
2777  {
2778  m_resumeDataTimer->stop();
2779  }
2780 }
2781 
2782 int Session::port() const
2783 {
2784  return m_port;
2785 }
2786 
2787 void Session::setPort(const int port)
2788 {
2789  if (port != m_port)
2790  {
2791  m_port = port;
2793 
2796  }
2797 }
2798 
2800 {
2801  return m_networkInterface;
2802 }
2803 
2804 void Session::setNetworkInterface(const QString &iface)
2805 {
2806  if (iface != networkInterface())
2807  {
2808  m_networkInterface = iface;
2810  }
2811 }
2812 
2814 {
2815  return m_networkInterfaceName;
2816 }
2817 
2818 void Session::setNetworkInterfaceName(const QString &name)
2819 {
2820  m_networkInterfaceName = name;
2821 }
2822 
2824 {
2826 }
2827 
2828 void Session::setNetworkInterfaceAddress(const QString &address)
2829 {
2830  if (address != networkInterfaceAddress())
2831  {
2832  m_networkInterfaceAddress = address;
2834  }
2835 }
2836 
2838 {
2839  return m_encryption;
2840 }
2841 
2842 void Session::setEncryption(const int state)
2843 {
2844  if (state != encryption())
2845  {
2846  m_encryption = state;
2848  LogMsg(tr("Encryption support [%1]").arg(
2849  state == 0 ? tr("ON") : ((state == 1) ? tr("FORCED") : tr("OFF")))
2850  , Log::INFO);
2851  }
2852 }
2853 
2855 {
2857 }
2858 
2860 {
2861  if (enabled != isProxyPeerConnectionsEnabled())
2862  {
2865  }
2866 }
2867 
2869 {
2870  return m_chokingAlgorithm;
2871 }
2872 
2874 {
2875  if (mode == m_chokingAlgorithm) return;
2876 
2877  m_chokingAlgorithm = mode;
2879 }
2880 
2882 {
2883  return m_seedChokingAlgorithm;
2884 }
2885 
2887 {
2888  if (mode == m_seedChokingAlgorithm) return;
2889 
2890  m_seedChokingAlgorithm = mode;
2892 }
2893 
2895 {
2896  return m_isAddTrackersEnabled;
2897 }
2898 
2899 void Session::setAddTrackersEnabled(const bool enabled)
2900 {
2901  m_isAddTrackersEnabled = enabled;
2902 }
2903 
2905 {
2906  return m_additionalTrackers;
2907 }
2908 
2909 void Session::setAdditionalTrackers(const QString &trackers)
2910 {
2911  if (trackers != additionalTrackers())
2912  {
2913  m_additionalTrackers = trackers;
2915  }
2916 }
2917 
2919 {
2920  return m_isIPFilteringEnabled;
2921 }
2922 
2923 void Session::setIPFilteringEnabled(const bool enabled)
2924 {
2925  if (enabled != m_isIPFilteringEnabled)
2926  {
2927  m_isIPFilteringEnabled = enabled;
2928  m_IPFilteringConfigured = false;
2930  }
2931 }
2932 
2933 QString Session::IPFilterFile() const
2934 {
2936 }
2937 
2938 void Session::setIPFilterFile(QString path)
2939 {
2940  path = Utils::Fs::toUniformPath(path);
2941  if (path != IPFilterFile())
2942  {
2943  m_IPFilterFile = path;
2944  m_IPFilteringConfigured = false;
2946  }
2947 }
2948 
2949 void Session::setBannedIPs(const QStringList &newList)
2950 {
2951  if (newList == m_bannedIPs)
2952  return; // do nothing
2953  // here filter out incorrect IP
2954  QStringList filteredList;
2955  for (const QString &ip : newList)
2956  {
2957  if (Utils::Net::isValidIP(ip))
2958  {
2959  // the same IPv6 addresses could be written in different forms;
2960  // QHostAddress::toString() result format follows RFC5952;
2961  // thus we avoid duplicate entries pointing to the same address
2962  filteredList << QHostAddress(ip).toString();
2963  }
2964  else
2965  {
2966  LogMsg(tr("%1 is not a valid IP address and was rejected while applying the list of banned IP addresses.")
2967  .arg(ip)
2968  , Log::WARNING);
2969  }
2970  }
2971  // now we have to sort IPs and make them unique
2972  filteredList.sort();
2973  filteredList.removeDuplicates();
2974  // Again ensure that the new list is different from the stored one.
2975  if (filteredList == m_bannedIPs)
2976  return; // do nothing
2977  // store to session settings
2978  // also here we have to recreate filter list including 3rd party ban file
2979  // and install it again into m_session
2980  m_bannedIPs = filteredList;
2981  m_IPFilteringConfigured = false;
2983 }
2984 
2986 {
2987  return m_resumeDataStorageType;
2988 }
2989 
2991 {
2992  m_resumeDataStorageType = type;
2993 }
2994 
2995 QStringList Session::bannedIPs() const
2996 {
2997  return m_bannedIPs;
2998 }
2999 
3000 #if defined(Q_OS_WIN)
3001 OSMemoryPriority Session::getOSMemoryPriority() const
3002 {
3003  return m_OSMemoryPriority;
3004 }
3005 
3006 void Session::setOSMemoryPriority(const OSMemoryPriority priority)
3007 {
3008  if (m_OSMemoryPriority == priority)
3009  return;
3010 
3011  m_OSMemoryPriority = priority;
3013 }
3014 
3015 void Session::applyOSMemoryPriority() const
3016 {
3017  using SETPROCESSINFORMATION = BOOL (WINAPI *)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD);
3018  const auto setProcessInformation = Utils::Misc::loadWinAPI<SETPROCESSINFORMATION>("Kernel32.dll", "SetProcessInformation");
3019  if (!setProcessInformation) // only available on Windows >= 8
3020  return;
3021 
3022 #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
3023  // this dummy struct is required to compile successfully when targeting older Windows version
3024  struct MEMORY_PRIORITY_INFORMATION
3025  {
3026  ULONG MemoryPriority;
3027  };
3028 
3029 #define MEMORY_PRIORITY_LOWEST 0
3030 #define MEMORY_PRIORITY_VERY_LOW 1
3031 #define MEMORY_PRIORITY_LOW 2
3032 #define MEMORY_PRIORITY_MEDIUM 3
3033 #define MEMORY_PRIORITY_BELOW_NORMAL 4
3034 #define MEMORY_PRIORITY_NORMAL 5
3035 #endif
3036 
3037  MEMORY_PRIORITY_INFORMATION prioInfo {};
3038  switch (getOSMemoryPriority())
3039  {
3040  case OSMemoryPriority::Normal:
3041  default:
3042  prioInfo.MemoryPriority = MEMORY_PRIORITY_NORMAL;
3043  break;
3044  case OSMemoryPriority::BelowNormal:
3045  prioInfo.MemoryPriority = MEMORY_PRIORITY_BELOW_NORMAL;
3046  break;
3047  case OSMemoryPriority::Medium:
3048  prioInfo.MemoryPriority = MEMORY_PRIORITY_MEDIUM;
3049  break;
3050  case OSMemoryPriority::Low:
3051  prioInfo.MemoryPriority = MEMORY_PRIORITY_LOW;
3052  break;
3053  case OSMemoryPriority::VeryLow:
3054  prioInfo.MemoryPriority = MEMORY_PRIORITY_VERY_LOW;
3055  break;
3056  }
3057  setProcessInformation(::GetCurrentProcess(), ProcessMemoryPriority, &prioInfo, sizeof(prioInfo));
3058 }
3059 #endif
3060 
3062 {
3064 }
3065 
3067 {
3068  max = (max > 0) ? max : -1;
3069  if (max != maxConnectionsPerTorrent())
3070  {
3072 
3073  // Apply this to all session torrents
3074  for (const lt::torrent_handle &handle : m_nativeSession->get_torrents())
3075  {
3076  try
3077  {
3078  handle.set_max_connections(max);
3079  }
3080  catch (const std::exception &) {}
3081  }
3082  }
3083 }
3084 
3086 {
3087  return m_maxUploadsPerTorrent;
3088 }
3089 
3091 {
3092  max = (max > 0) ? max : -1;
3093  if (max != maxUploadsPerTorrent())
3094  {
3095  m_maxUploadsPerTorrent = max;
3096 
3097  // Apply this to all session torrents
3098  for (const lt::torrent_handle &handle : m_nativeSession->get_torrents())
3099  {
3100  try
3101  {
3102  handle.set_max_uploads(max);
3103  }
3104  catch (const std::exception &) {}
3105  }
3106  }
3107 }
3108 
3110 {
3111  return m_announceToAllTrackers;
3112 }
3113 
3115 {
3116  if (val != m_announceToAllTrackers)
3117  {
3120  }
3121 }
3122 
3124 {
3125  return m_announceToAllTiers;
3126 }
3127 
3129 {
3130  if (val != m_announceToAllTiers)
3131  {
3132  m_announceToAllTiers = val;
3134  }
3135 }
3136 
3138 {
3139  return m_peerTurnover;
3140 }
3141 
3142 void Session::setPeerTurnover(const int val)
3143 {
3144  if (val == m_peerTurnover)
3145  return;
3146 
3147  m_peerTurnover = val;
3149 }
3150 
3152 {
3153  return m_peerTurnoverCutoff;
3154 }
3155 
3157 {
3158  if (val == m_peerTurnoverCutoff)
3159  return;
3160 
3161  m_peerTurnoverCutoff = val;
3163 }
3164 
3166 {
3167  return m_peerTurnoverInterval;
3168 }
3169 
3171 {
3172  if (val == m_peerTurnoverInterval)
3173  return;
3174 
3175  m_peerTurnoverInterval = val;
3177 }
3178 
3180 {
3181  return std::clamp(m_asyncIOThreads.get(), 1, 1024);
3182 }
3183 
3184 void Session::setAsyncIOThreads(const int num)
3185 {
3186  if (num == m_asyncIOThreads)
3187  return;
3188 
3189  m_asyncIOThreads = num;
3191 }
3192 
3194 {
3195  return std::clamp(m_hashingThreads.get(), 1, 1024);
3196 }
3197 
3198 void Session::setHashingThreads(const int num)
3199 {
3200  if (num == m_hashingThreads)
3201  return;
3202 
3203  m_hashingThreads = num;
3205 }
3206 
3208 {
3209  return m_filePoolSize;
3210 }
3211 
3212 void Session::setFilePoolSize(const int size)
3213 {
3214  if (size == m_filePoolSize)
3215  return;
3216 
3217  m_filePoolSize = size;
3219 }
3220 
3222 {
3223  return std::max(1, m_checkingMemUsage.get());
3224 }
3225 
3227 {
3228  size = std::max(size, 1);
3229 
3230  if (size == m_checkingMemUsage)
3231  return;
3232 
3233  m_checkingMemUsage = size;
3235 }
3236 
3238 {
3239 #ifdef QBT_APP_64BIT
3240  return std::min(m_diskCacheSize.get(), 33554431); // 32768GiB
3241 #else
3242  // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
3243  // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
3244  return std::min(m_diskCacheSize.get(), 1536);
3245 #endif
3246 }
3247 
3249 {
3250 #ifdef QBT_APP_64BIT
3251  size = std::min(size, 33554431); // 32768GiB
3252 #else
3253  // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
3254  size = std::min(size, 1536);
3255 #endif
3256  if (size != m_diskCacheSize)
3257  {
3258  m_diskCacheSize = size;
3260  }
3261 }
3262 
3264 {
3265  return m_diskCacheTTL;
3266 }
3267 
3268 void Session::setDiskCacheTTL(const int ttl)
3269 {
3270  if (ttl != m_diskCacheTTL)
3271  {
3272  m_diskCacheTTL = ttl;
3274  }
3275 }
3276 
3278 {
3279  return m_useOSCache;
3280 }
3281 
3282 void Session::setUseOSCache(const bool use)
3283 {
3284  if (use != m_useOSCache)
3285  {
3286  m_useOSCache = use;
3288  }
3289 }
3290 
3292 {
3294 }
3295 
3297 {
3298  if (enabled == m_coalesceReadWriteEnabled) return;
3299 
3300  m_coalesceReadWriteEnabled = enabled;
3302 }
3303 
3305 {
3306  return m_isSuggestMode;
3307 }
3308 
3310 {
3311  return m_usePieceExtentAffinity;
3312 }
3313 
3314 void Session::setPieceExtentAffinity(const bool enabled)
3315 {
3316  if (enabled == m_usePieceExtentAffinity) return;
3317 
3318  m_usePieceExtentAffinity = enabled;
3320 }
3321 
3322 void Session::setSuggestMode(const bool mode)
3323 {
3324  if (mode == m_isSuggestMode) return;
3325 
3326  m_isSuggestMode = mode;
3328 }
3329 
3331 {
3332  return m_sendBufferWatermark;
3333 }
3334 
3336 {
3337  if (value == m_sendBufferWatermark) return;
3338 
3341 }
3342 
3344 {
3345  return m_sendBufferLowWatermark;
3346 }
3347 
3349 {
3350  if (value == m_sendBufferLowWatermark) return;
3351 
3354 }
3355 
3357 {
3359 }
3360 
3362 {
3363  if (value == m_sendBufferWatermarkFactor) return;
3364 
3367 }
3368 
3370 {
3371  return m_connectionSpeed;
3372 }
3373 
3375 {
3376  if (value == m_connectionSpeed) return;
3377 
3380 }
3381 
3383 {
3384  return m_socketBacklogSize;
3385 }
3386 
3388 {
3389  if (value == m_socketBacklogSize) return;
3390 
3393 }
3394 
3396 {
3397  return m_isAnonymousModeEnabled;
3398 }
3399 
3400 void Session::setAnonymousModeEnabled(const bool enabled)
3401 {
3402  if (enabled != m_isAnonymousModeEnabled)
3403  {
3404  m_isAnonymousModeEnabled = enabled;
3406  LogMsg(tr("Anonymous mode [%1]").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
3407  , Log::INFO);
3408  }
3409 }
3410 
3412 {
3413  return m_isQueueingEnabled;
3414 }
3415 
3416 void Session::setQueueingSystemEnabled(const bool enabled)
3417 {
3418  if (enabled != m_isQueueingEnabled)
3419  {
3420  m_isQueueingEnabled = enabled;
3422 
3423  if (enabled)
3425  else
3427  }
3428 }
3429 
3431 {
3432  return m_maxActiveDownloads;
3433 }
3434 
3436 {
3437  max = std::max(max, -1);
3438  if (max != m_maxActiveDownloads)
3439  {
3440  m_maxActiveDownloads = max;
3442  }
3443 }
3444 
3446 {
3447  return m_maxActiveUploads;
3448 }
3449 
3451 {
3452  max = std::max(max, -1);
3453  if (max != m_maxActiveUploads)
3454  {
3455  m_maxActiveUploads = max;
3457  }
3458 }
3459 
3461 {
3462  return m_maxActiveTorrents;
3463 }
3464 
3466 {
3467  max = std::max(max, -1);
3468  if (max != m_maxActiveTorrents)
3469  {
3470  m_maxActiveTorrents = max;
3472  }
3473 }
3474 
3476 {
3478 }
3479 
3481 {
3482  if (ignore != m_ignoreSlowTorrentsForQueueing)
3483  {
3486  }
3487 }
3488 
3490 {
3492 }
3493 
3494 void Session::setDownloadRateForSlowTorrents(const int rateInKibiBytes)
3495 {
3496  if (rateInKibiBytes == m_downloadRateForSlowTorrents)
3497  return;
3498 
3499  m_downloadRateForSlowTorrents = rateInKibiBytes;
3501 }
3502 
3504 {
3506 }
3507 
3508 void Session::setUploadRateForSlowTorrents(const int rateInKibiBytes)
3509 {
3510  if (rateInKibiBytes == m_uploadRateForSlowTorrents)
3511  return;
3512 
3513  m_uploadRateForSlowTorrents = rateInKibiBytes;
3515 }
3516 
3518 {
3520 }
3521 
3522 void Session::setSlowTorrentsInactivityTimer(const int timeInSeconds)
3523 {
3524  if (timeInSeconds == m_slowTorrentsInactivityTimer)
3525  return;
3526 
3527  m_slowTorrentsInactivityTimer = timeInSeconds;
3529 }
3530 
3532 {
3533  return m_outgoingPortsMin;
3534 }
3535 
3536 void Session::setOutgoingPortsMin(const int min)
3537 {
3538  if (min != m_outgoingPortsMin)
3539  {
3540  m_outgoingPortsMin = min;
3542  }
3543 }
3544 
3546 {
3547  return m_outgoingPortsMax;
3548 }
3549 
3550 void Session::setOutgoingPortsMax(const int max)
3551 {
3552  if (max != m_outgoingPortsMax)
3553  {
3554  m_outgoingPortsMax = max;
3556  }
3557 }
3558 
3560 {
3561  return m_UPnPLeaseDuration;
3562 }
3563 
3564 void Session::setUPnPLeaseDuration(const int duration)
3565 {
3566  if (duration != m_UPnPLeaseDuration)
3567  {
3568  m_UPnPLeaseDuration = duration;
3570  }
3571 }
3572 
3573 int Session::peerToS() const
3574 {
3575  return m_peerToS;
3576 }
3577 
3579 {
3580  if (value == m_peerToS)
3581  return;
3582 
3583  m_peerToS = value;
3585 }
3586 
3588 {
3589  return m_ignoreLimitsOnLAN;
3590 }
3591 
3592 void Session::setIgnoreLimitsOnLAN(const bool ignore)
3593 {
3594  if (ignore != m_ignoreLimitsOnLAN)
3595  {
3596  m_ignoreLimitsOnLAN = ignore;
3598  }
3599 }
3600 
3602 {
3604 }
3605 
3606 void Session::setIncludeOverheadInLimits(const bool include)
3607 {
3608  if (include != m_includeOverheadInLimits)
3609  {
3610  m_includeOverheadInLimits = include;
3612  }
3613 }
3614 
3615 QString Session::announceIP() const
3616 {
3617  return m_announceIP;
3618 }
3619 
3620 void Session::setAnnounceIP(const QString &ip)
3621 {
3622  if (ip != m_announceIP)
3623  {
3624  m_announceIP = ip;
3626  }
3627 }
3628 
3630 {
3632 }
3633 
3635 {
3637  return;
3638 
3641 }
3642 
3644 {
3646 }
3647 
3649 {
3651  return;
3652 
3654 }
3655 
3657 {
3658  for (const lt::torrent_handle &torrent : m_nativeSession->get_torrents())
3659  torrent.force_reannounce(0, -1, lt::torrent_handle::ignore_min_interval);
3660 }
3661 
3663 {
3664  return m_stopTrackerTimeout;
3665 }
3666 
3668 {
3669  if (value == m_stopTrackerTimeout)
3670  return;
3671 
3674 }
3675 
3677 {
3678  return m_maxConnections;
3679 }
3680 
3682 {
3683  max = (max > 0) ? max : -1;
3684  if (max != m_maxConnections)
3685  {
3686  m_maxConnections = max;
3688  }
3689 }
3690 
3692 {
3693  return m_maxUploads;
3694 }
3695 
3697 {
3698  max = (max > 0) ? max : -1;
3699  if (max != m_maxUploads)
3700  {
3701  m_maxUploads = max;
3703  }
3704 }
3705 
3707 {
3708  return m_btProtocol;
3709 }
3710 
3712 {
3713  if ((protocol < BTProtocol::Both) || (BTProtocol::UTP < protocol))
3714  return;
3715 
3716  if (protocol == m_btProtocol) return;
3717 
3718  m_btProtocol = protocol;
3720 }
3721 
3723 {
3724  return m_isUTPRateLimited;
3725 }
3726 
3727 void Session::setUTPRateLimited(const bool limited)
3728 {
3729  if (limited != m_isUTPRateLimited)
3730  {
3731  m_isUTPRateLimited = limited;
3733  }
3734 }
3735 
3737 {
3738  return m_utpMixedMode;
3739 }
3740 
3742 {
3743  if (mode == m_utpMixedMode) return;
3744 
3745  m_utpMixedMode = mode;
3747 }
3748 
3750 {
3751  return m_IDNSupportEnabled;
3752 }
3753 
3754 void Session::setIDNSupportEnabled(const bool enabled)
3755 {
3756  if (enabled == m_IDNSupportEnabled) return;
3757 
3758  m_IDNSupportEnabled = enabled;
3760 }
3761 
3763 {
3765 }
3766 
3768 {
3769  if (enabled == m_multiConnectionsPerIpEnabled) return;
3770 
3773 }
3774 
3776 {
3778 }
3779 
3781 {
3782  if (enabled == m_validateHTTPSTrackerCertificate) return;
3783 
3786 }
3787 
3789 {
3790  return m_SSRFMitigationEnabled;
3791 }
3792 
3793 void Session::setSSRFMitigationEnabled(const bool enabled)
3794 {
3795  if (enabled == m_SSRFMitigationEnabled) return;
3796 
3797  m_SSRFMitigationEnabled = enabled;
3799 }
3800 
3802 {
3804 }
3805 
3807 {
3808  if (enabled == m_blockPeersOnPrivilegedPorts) return;
3809 
3812 }
3813 
3815 {
3817 }
3818 
3819 void Session::setTrackerFilteringEnabled(const bool enabled)
3820 {
3821  if (enabled != m_isTrackerFilteringEnabled)
3822  {
3823  m_isTrackerFilteringEnabled = enabled;
3825  }
3826 }
3827 
3829 {
3830  return m_nativeSession->is_listening();
3831 }
3832 
3834 {
3835  return static_cast<MaxRatioAction>(m_maxRatioAction.get());
3836 }
3837 
3839 {
3840  m_maxRatioAction = static_cast<int>(act);
3841 }
3842 
3843 // If this functions returns true, we cannot add torrent to session,
3844 // but it is still possible to merge trackers in some cases
3845 bool Session::isKnownTorrent(const TorrentID &id) const
3846 {
3847  return (m_torrents.contains(id)
3848  || m_loadingTorrents.contains(id)
3849  || m_downloadedMetadata.contains(id));
3850 }
3851 
3853 {
3856  {
3857  if (m_seedingLimitTimer->isActive())
3858  m_seedingLimitTimer->stop();
3859  }
3860  else if (!m_seedingLimitTimer->isActive())
3861  {
3862  m_seedingLimitTimer->start();
3863  }
3864 }
3865 
3867 {
3869 }
3870 
3872 {
3873 }
3874 
3876 {
3877  emit torrentSavePathChanged(torrent);
3878 }
3879 
3880 void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
3881 {
3882  emit torrentCategoryChanged(torrent, oldCategory);
3883 }
3884 
3885 void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
3886 {
3887  emit torrentTagAdded(torrent, tag);
3888 }
3889 
3890 void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
3891 {
3892  emit torrentTagRemoved(torrent, tag);
3893 }
3894 
3896 {
3897  emit torrentSavingModeChanged(torrent);
3898 }
3899 
3900 void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
3901 {
3902  for (const TrackerEntry &newTracker : newTrackers)
3903  LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url, torrent->name()));
3904  emit trackersAdded(torrent, newTrackers);
3905  if (torrent->trackers().size() == newTrackers.size())
3906  emit trackerlessStateChanged(torrent, false);
3907  emit trackersChanged(torrent);
3908 }
3909 
3910 void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers)
3911 {
3912  for (const TrackerEntry &deletedTracker : deletedTrackers)
3913  LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url, torrent->name()));
3914  emit trackersRemoved(torrent, deletedTrackers);
3915  if (torrent->trackers().empty())
3916  emit trackerlessStateChanged(torrent, true);
3917  emit trackersChanged(torrent);
3918 }
3919 
3921 {
3922  emit trackersChanged(torrent);
3923 }
3924 
3925 void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
3926 {
3927  for (const QUrl &newUrlSeed : newUrlSeeds)
3928  LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
3929 }
3930 
3931 void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
3932 {
3933  for (const QUrl &urlSeed : urlSeeds)
3934  LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
3935 }
3936 
3938 {
3939  // Copy the torrent file to the export folder
3940  if (!torrentExportDirectory().isEmpty())
3941  {
3942 #ifdef QBT_USES_LIBTORRENT2
3943  const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file_with_hashes()};
3944 #else
3945  const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file()};
3946 #endif
3947  exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
3948  }
3949 
3950  emit torrentMetadataReceived(torrent);
3951 }
3952 
3954 {
3955  emit torrentPaused(torrent);
3956 }
3957 
3959 {
3960  emit torrentResumed(torrent);
3961 }
3962 
3964 {
3965  emit torrentFinishedChecking(torrent);
3966 }
3967 
3969 {
3970  emit torrentFinished(torrent);
3971 
3972  qDebug("Checking if the torrent contains torrent files to download");
3973  // Check if there are torrent files inside
3974  for (const QString &torrentRelpath : asConst(torrent->filePaths()))
3975  {
3976  if (torrentRelpath.endsWith(".torrent", Qt::CaseInsensitive))
3977  {
3978  qDebug("Found possible recursive torrent download.");
3979  const QString torrentFullpath = torrent->actualStorageLocation() + '/' + torrentRelpath;
3980  qDebug("Full subtorrent path is %s", qUtf8Printable(torrentFullpath));
3981  if (TorrentInfo::loadFromFile(torrentFullpath))
3982  {
3983  qDebug("emitting recursiveTorrentDownloadPossible()");
3984  emit recursiveTorrentDownloadPossible(torrent);
3985  break;
3986  }
3987  else
3988  {
3989  qDebug("Caught error loading torrent");
3990  LogMsg(tr("Unable to decode '%1' torrent file.").arg(Utils::Fs::toNativePath(torrentFullpath)), Log::CRITICAL);
3991  }
3992  }
3993  }
3994 
3995  // Move .torrent file to another folder
3996  if (!finishedTorrentExportDirectory().isEmpty())
3997  {
3998 #ifdef QBT_USES_LIBTORRENT2
3999  const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file_with_hashes()};
4000 #else
4001  const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file()};
4002 #endif
4003  exportTorrentFile(torrentInfo, finishedTorrentExportDirectory(), torrent->name());
4004  }
4005 
4006  if (!hasUnfinishedTorrents())
4007  emit allTorrentsFinished();
4008 }
4009 
4011 {
4012  --m_numResumeData;
4013 
4014  m_resumeDataStorage->store(torrent->id(), data);
4015 }
4016 
4017 void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl)
4018 {
4019  emit trackerSuccess(torrent, trackerUrl);
4020 }
4021 
4022 void Session::handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl)
4023 {
4024  emit trackerError(torrent, trackerUrl);
4025 }
4026 
4027 bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, const MoveStorageMode mode)
4028 {
4029  Q_ASSERT(torrent);
4030 
4031  const lt::torrent_handle torrentHandle = torrent->nativeHandle();
4032  const QString currentLocation = Utils::Fs::toNativePath(torrent->actualStorageLocation());
4033 
4034  if (m_moveStorageQueue.size() > 1)
4035  {
4036  const auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
4037  , [&torrentHandle](const MoveStorageJob &job)
4038  {
4039  return job.torrentHandle == torrentHandle;
4040  });
4041 
4042  if (iter != m_moveStorageQueue.end())
4043  {
4044  // remove existing inactive job
4045  m_moveStorageQueue.erase(iter);
4046  LogMsg(tr("Cancelled moving \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, iter->path));
4047  }
4048  }
4049 
4050  if (!m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle))
4051  {
4052  // if there is active job for this torrent prevent creating meaningless
4053  // job that will move torrent to the same location as current one
4054  if (QDir {m_moveStorageQueue.first().path} == QDir {newPath})
4055  {
4056  LogMsg(tr("Couldn't enqueue move of \"%1\" to \"%2\". Torrent is currently moving to the same destination location.")
4057  .arg(torrent->name(), newPath));
4058  return false;
4059  }
4060  }
4061  else
4062  {
4063  if (QDir {currentLocation} == QDir {newPath})
4064  {
4065  LogMsg(tr("Couldn't enqueue move of \"%1\" from \"%2\" to \"%3\". Both paths point to the same location.")
4066  .arg(torrent->name(), currentLocation, newPath));
4067  return false;
4068  }
4069  }
4070 
4071  const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode};
4072  m_moveStorageQueue << moveStorageJob;
4073  LogMsg(tr("Enqueued to move \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, newPath));
4074 
4075  if (m_moveStorageQueue.size() == 1)
4076  moveTorrentStorage(moveStorageJob);
4077 
4078  return true;
4079 }
4080 
4082 {
4083 #ifdef QBT_USES_LIBTORRENT2
4084  const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
4085 #else
4086  const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
4087 #endif
4088  const TorrentImpl *torrent = m_torrents.value(id);
4089  const QString torrentName = (torrent ? torrent->name() : id.toString());
4090  LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path));
4091 
4092  job.torrentHandle.move_storage(job.path.toUtf8().constData()
4093  , ((job.mode == MoveStorageMode::Overwrite)
4094  ? lt::move_flags_t::always_replace_files : lt::move_flags_t::dont_replace));
4095 }
4096 
4098 {
4099  const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
4100  if (!m_moveStorageQueue.isEmpty())
4102 
4103  const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
4104  , [&finishedJob](const MoveStorageJob &job)
4105  {
4106  return job.torrentHandle == finishedJob.torrentHandle;
4107  });
4108 
4109  const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
4110 
4111  TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
4112  if (torrent)
4113  {
4114  torrent->handleMoveStorageJobFinished(torrentHasOutstandingJob);
4115  }
4116  else if (!torrentHasOutstandingJob)
4117  {
4118  // Last job is completed for torrent that being removing, so actually remove it
4119  const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
4120  const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
4121  if (removingTorrentData.deleteOption == DeleteTorrent)
4122  m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
4123  }
4124 }
4125 
4127 {
4128  QJsonObject jsonObj;
4129  for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
4130  {
4131  const QString &categoryName = it.key();
4132  const CategoryOptions &categoryOptions = it.value();
4133  jsonObj[categoryName] = categoryOptions.toJSON();
4134  }
4135 
4136  const QString path = QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CATEGORIES_FILE_NAME);
4137  const QByteArray data = QJsonDocument(jsonObj).toJson();
4138  const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
4139  if (!result)
4140  {
4141  LogMsg(tr("Couldn't store Categories configuration to %1. Error: %2")
4142  .arg(path, result.error()), Log::WARNING);
4143  }
4144 }
4145 
4147 {
4148  const auto legacyCategories = SettingValue<QVariantMap>("BitTorrent/Session/Categories").get();
4149  for (auto it = legacyCategories.cbegin(); it != legacyCategories.cend(); ++it)
4150  {
4151  const QString categoryName = it.key();
4153  categoryOptions.savePath = it.value().toString();
4154  m_categories[categoryName] = categoryOptions;
4155  }
4156 
4157  storeCategories();
4158 }
4159 
4161 {
4162  m_categories.clear();
4163 
4164  QFile confFile {QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CATEGORIES_FILE_NAME)};
4165  if (!confFile.exists())
4166  {
4167  // TODO: Remove the following upgrade code in v4.5
4168  // == BEGIN UPGRADE CODE ==
4171  // == END UPGRADE CODE ==
4172 
4173 // return;
4174  }
4175 
4176  if (!confFile.open(QFile::ReadOnly))
4177  {
4178  LogMsg(tr("Couldn't load Categories from %1. Error: %2")
4179  .arg(confFile.fileName(), confFile.errorString()), Log::CRITICAL);
4180  return;
4181  }
4182 
4183  QJsonParseError jsonError;
4184  const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
4185  if (jsonError.error != QJsonParseError::NoError)
4186  {
4187  LogMsg(tr("Couldn't parse Categories configuration from %1. Error: %2")
4188  .arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
4189  return;
4190  }
4191 
4192  if (!jsonDoc.isObject())
4193  {
4194  LogMsg(tr("Couldn't load Categories configuration from %1. Invalid data format.")
4195  .arg(confFile.fileName()), Log::WARNING);
4196  return;
4197  }
4198 
4199 
4200  const QJsonObject jsonObj = jsonDoc.object();
4201  for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
4202  {
4203  const QString &categoryName = it.key();
4204  const auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
4205  m_categories[categoryName] = categoryOptions;
4206  }
4207 }
4208 
4209 void Session::handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl)
4210 {
4211  emit trackerWarning(torrent, trackerUrl);
4212 }
4213 
4215 {
4216  return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
4217  {
4218  return (torrent->ratioLimit() >= 0);
4219  });
4220 }
4221 
4223 {
4224  return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
4225  {
4226  return (torrent->seedingTimeLimit() >= 0);
4227  });
4228 }
4229 
4231 {
4233  return;
4234 
4236  QMetaObject::invokeMethod(this, qOverload<>(&Session::configure), Qt::QueuedConnection);
4237 }
4238 
4239 // Enable IP Filtering
4240 // this method creates ban list from scratch combining user ban list and 3rd party ban list file
4242 {
4243  qDebug("Enabling IPFilter");
4244  // 1. Parse the IP filter
4245  // 2. In the slot add the manually banned IPs to the provided lt::ip_filter
4246  // 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
4247  // set between clearing the old one and setting the new one.
4248  if (!m_filterParser)
4249  {
4250  m_filterParser = new FilterParserThread(this);
4253  }
4254  m_filterParser->processFilterFile(IPFilterFile());
4255 }
4256 
4257 // Disable IP Filtering
4259 {
4260  qDebug("Disabling IPFilter");
4261  if (m_filterParser)
4262  {
4263  disconnect(m_filterParser.data(), nullptr, this, nullptr);
4264  delete m_filterParser;
4265  }
4266 
4267  // Add the banned IPs after the IPFilter disabling
4268  // which creates an empty filter and overrides all previously
4269  // applied bans.
4270  lt::ip_filter filter;
4271  processBannedIPs(filter);
4272  m_nativeSession->set_ip_filter(filter);
4273 }
4274 
4276 {
4277  TorrentImpl *const torrent = m_torrents.value(id);
4278  if (!torrent) return;
4279 
4280  for (const QString &torrentRelpath : asConst(torrent->filePaths()))
4281  {
4282  if (torrentRelpath.endsWith(QLatin1String(".torrent")))
4283  {
4284  LogMsg(tr("Recursive download of file '%1' embedded in torrent '%2'"
4285  , "Recursive download of 'test.torrent' embedded in torrent 'test2'")
4286  .arg(Utils::Fs::toNativePath(torrentRelpath), torrent->name()));
4287  const QString torrentFullpath = torrent->savePath() + '/' + torrentRelpath;
4288 
4289  AddTorrentParams params;
4290  // Passing the save path along to the sub torrent file
4291  params.savePath = torrent->savePath();
4292  const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(torrentFullpath);
4293  if (loadResult)
4294  addTorrent(loadResult.value(), params);
4295  else
4296  LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING);
4297  }
4298  }
4299 }
4300 
4302 {
4303  return m_status;
4304 }
4305 
4307 {
4308  return m_cacheStatus;
4309 }
4310 
4312 {
4313  qDebug("Initializing torrents resume data storage...");
4314 
4315  const QString dbPath = Utils::Fs::expandPathAbs(
4316  specialFolderLocation(SpecialFolder::Data) + QLatin1String("/torrents.db"));
4317  const bool dbStorageExists = QFile::exists(dbPath);
4318 
4319  ResumeDataStorage *startupStorage = nullptr;
4320  if (resumeDataStorageType() == ResumeDataStorageType::SQLite)
4321  {
4322  m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
4323 
4324  if (!dbStorageExists)
4325  {
4326  const QString dataPath = Utils::Fs::expandPathAbs(
4327  specialFolderLocation(SpecialFolder::Data) + QLatin1String("/BT_backup"));
4328  startupStorage = new BencodeResumeDataStorage(dataPath, this);
4329  }
4330  }
4331  else
4332  {
4333  const QString dataPath = Utils::Fs::expandPathAbs(
4334  specialFolderLocation(SpecialFolder::Data) + QLatin1String("/BT_backup"));
4335  m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
4336 
4337  if (dbStorageExists)
4338  startupStorage = new DBResumeDataStorage(dbPath, this);
4339  }
4340 
4341  if (!startupStorage)
4342  startupStorage = m_resumeDataStorage;
4343 
4344  qDebug("Starting up torrents...");
4345 
4346  const QVector<TorrentID> torrents = startupStorage->registeredTorrents();
4347  int resumedTorrentsCount = 0;
4348  QVector<TorrentID> queue;
4349  for (const TorrentID &torrentID : torrents)
4350  {
4351  const std::optional<LoadTorrentParams> loadResumeDataResult = startupStorage->load(torrentID);
4352  if (loadResumeDataResult)
4353  {
4354  LoadTorrentParams resumeData = *loadResumeDataResult;
4355  bool needStore = false;
4356 
4357  if (m_resumeDataStorage != startupStorage)
4358  {
4359  needStore = true;
4360  if (isQueueingSystemEnabled() && !resumeData.hasSeedStatus)
4361  queue.append(torrentID);
4362  }
4363 
4364  // TODO: Remove the following upgrade code in v4.5
4365  // == BEGIN UPGRADE CODE ==
4367  {
4368  if (!resumeData.useAutoTMM)
4369  {
4370  resumeData.downloadPath = downloadPath();
4371  needStore = true;
4372  }
4373  }
4374  // == END UPGRADE CODE ==
4375 
4376  if (needStore)
4377  m_resumeDataStorage->store(torrentID, resumeData);
4378 
4379  qDebug() << "Starting up torrent" << torrentID.toString() << "...";
4380  if (!loadTorrent(resumeData))
4381  {
4382  LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
4383  .arg(torrentID.toString()), Log::CRITICAL);
4384  }
4385 
4386  // process add torrent messages before message queue overflow
4387  if ((resumedTorrentsCount % 100) == 0) readAlerts();
4388 
4389  ++resumedTorrentsCount;
4390  }
4391  else
4392  {
4393  LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
4394  .arg(torrentID.toString()), Log::CRITICAL);
4395  }
4396  }
4397 
4398  if (m_resumeDataStorage != startupStorage)
4399  {
4400  delete startupStorage;
4402  Utils::Fs::forceRemove(dbPath);
4403 
4406  }
4407 }
4408 
4409 quint64 Session::getAlltimeDL() const
4410 {
4411  return m_statistics->getAlltimeDL();
4412 }
4413 
4414 quint64 Session::getAlltimeUL() const
4415 {
4416  return m_statistics->getAlltimeUL();
4417 }
4418 
4420 {
4421  Q_ASSERT(!m_refreshEnqueued);
4422 
4423  QTimer::singleShot(refreshInterval(), this, [this] ()
4424  {
4425  m_nativeSession->post_torrent_updates();
4426  m_nativeSession->post_session_stats();
4427  });
4428 
4429  m_refreshEnqueued = true;
4430 }
4431 
4432 void Session::handleIPFilterParsed(const int ruleCount)
4433 {
4434  if (m_filterParser)
4435  {
4436  lt::ip_filter filter = m_filterParser->IPfilter();
4437  processBannedIPs(filter);
4438  m_nativeSession->set_ip_filter(filter);
4439  }
4440  LogMsg(tr("Successfully parsed the provided IP filter: %1 rules were applied.", "%1 is a number").arg(ruleCount));
4441  emit IPFilterParsed(false, ruleCount);
4442 }
4443 
4445 {
4446  lt::ip_filter filter;
4447  processBannedIPs(filter);
4448  m_nativeSession->set_ip_filter(filter);
4449 
4450  LogMsg(tr("Error: Failed to parse the provided IP filter."), Log::CRITICAL);
4451  emit IPFilterParsed(true, 0);
4452 }
4453 
4454 std::vector<lt::alert *> Session::getPendingAlerts(const lt::time_duration time) const
4455 {
4456  if (time > lt::time_duration::zero())
4457  m_nativeSession->wait_for_alert(time);
4458 
4459  std::vector<lt::alert *> alerts;
4460  m_nativeSession->pop_alerts(&alerts);
4461  return alerts;
4462 }
4463 
4465 {
4466  return m_torrentContentLayout;
4467 }
4468 
4470 {
4472 }
4473 
4474 // Read alerts sent by the BitTorrent session
4476 {
4477  const std::vector<lt::alert *> alerts = getPendingAlerts();
4478  for (const lt::alert *a : alerts)
4479  handleAlert(a);
4480 }
4481 
4482 void Session::handleAlert(const lt::alert *a)
4483 {
4484  try
4485  {
4486  switch (a->type())
4487  {
4488 #ifdef QBT_USES_LIBTORRENT2
4489  case lt::file_prio_alert::alert_type:
4490 #endif
4491  case lt::file_renamed_alert::alert_type:
4492  case lt::file_completed_alert::alert_type:
4493  case lt::torrent_finished_alert::alert_type:
4494  case lt::save_resume_data_alert::alert_type:
4495  case lt::save_resume_data_failed_alert::alert_type:
4496  case lt::torrent_paused_alert::alert_type:
4497  case lt::torrent_resumed_alert::alert_type:
4498  case lt::tracker_error_alert::alert_type:
4499  case lt::tracker_reply_alert::alert_type:
4500  case lt::tracker_warning_alert::alert_type:
4501  case lt::fastresume_rejected_alert::alert_type:
4502  case lt::torrent_checked_alert::alert_type:
4503  case lt::metadata_received_alert::alert_type:
4505  break;
4506  case lt::state_update_alert::alert_type:
4507  handleStateUpdateAlert(static_cast<const lt::state_update_alert*>(a));
4508  break;
4509  case lt::session_stats_alert::alert_type:
4510  handleSessionStatsAlert(static_cast<const lt::session_stats_alert*>(a));
4511  break;
4512  case lt::file_error_alert::alert_type:
4513  handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
4514  break;
4515  case lt::add_torrent_alert::alert_type:
4516  handleAddTorrentAlert(static_cast<const lt::add_torrent_alert*>(a));
4517  break;
4518  case lt::torrent_removed_alert::alert_type:
4519  handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert*>(a));
4520  break;
4521  case lt::torrent_deleted_alert::alert_type:
4522  handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert*>(a));
4523  break;
4524  case lt::torrent_delete_failed_alert::alert_type:
4525  handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert*>(a));
4526  break;
4527  case lt::portmap_error_alert::alert_type:
4528  handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert*>(a));
4529  break;
4530  case lt::portmap_alert::alert_type:
4531  handlePortmapAlert(static_cast<const lt::portmap_alert*>(a));
4532  break;
4533  case lt::peer_blocked_alert::alert_type:
4534  handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert*>(a));
4535  break;
4536  case lt::peer_ban_alert::alert_type:
4537  handlePeerBanAlert(static_cast<const lt::peer_ban_alert*>(a));
4538  break;
4539  case lt::url_seed_alert::alert_type:
4540  handleUrlSeedAlert(static_cast<const lt::url_seed_alert*>(a));
4541  break;
4542  case lt::listen_succeeded_alert::alert_type:
4543  handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert*>(a));
4544  break;
4545  case lt::listen_failed_alert::alert_type:
4546  handleListenFailedAlert(static_cast<const lt::listen_failed_alert*>(a));
4547  break;
4548  case lt::external_ip_alert::alert_type:
4549  handleExternalIPAlert(static_cast<const lt::external_ip_alert*>(a));
4550  break;
4551  case lt::alerts_dropped_alert::alert_type:
4552  handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(a));
4553  break;
4554  case lt::storage_moved_alert::alert_type:
4555  handleStorageMovedAlert(static_cast<const lt::storage_moved_alert*>(a));
4556  break;
4557  case lt::storage_moved_failed_alert::alert_type:
4558  handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert*>(a));
4559  break;
4560  case lt::socks5_alert::alert_type:
4561  handleSocks5Alert(static_cast<const lt::socks5_alert *>(a));
4562  break;
4563  }
4564  }
4565  catch (const std::exception &exc)
4566  {
4567  qWarning() << "Caught exception in " << Q_FUNC_INFO << ": " << QString::fromStdString(exc.what());
4568  }
4569 }
4570 
4571 void Session::dispatchTorrentAlert(const lt::alert *a)
4572 {
4573  TorrentImpl *const torrent = m_torrents.value(static_cast<const lt::torrent_alert*>(a)->handle.info_hash());
4574  if (torrent)
4575  {
4576  torrent->handleAlert(a);
4577  return;
4578  }
4579 
4580  switch (a->type())
4581  {
4582  case lt::metadata_received_alert::alert_type:
4583  handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
4584  break;
4585  }
4586 }
4587 
4588 void Session::createTorrent(const lt::torrent_handle &nativeHandle)
4589 {
4590 #ifdef QBT_USES_LIBTORRENT2
4591  const auto torrentID = TorrentID::fromInfoHash(nativeHandle.info_hashes());
4592 #else
4593  const auto torrentID = TorrentID::fromInfoHash(nativeHandle.info_hash());
4594 #endif
4595 
4596  Q_ASSERT(m_loadingTorrents.contains(torrentID));
4597 
4598  const LoadTorrentParams params = m_loadingTorrents.take(torrentID);
4599 
4600  auto *const torrent = new TorrentImpl {this, m_nativeSession, nativeHandle, params};
4601  m_torrents.insert(torrent->id(), torrent);
4602 
4603  const bool hasMetadata = torrent->hasMetadata();
4604 
4605  if (params.restored)
4606  {
4607  LogMsg(tr("'%1' restored.", "'torrent name' restored.").arg(torrent->name()));
4608  }
4609  else
4610  {
4611  m_resumeDataStorage->store(torrent->id(), params);
4612 
4613  // The following is useless for newly added magnet
4614  if (hasMetadata)
4615  {
4616  // Copy the torrent file to the export folder
4617  if (!torrentExportDirectory().isEmpty())
4618  {
4619  const TorrentInfo torrentInfo {*params.ltAddTorrentParams.ti};
4620  exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
4621  }
4622  }
4623 
4624  if (isAddTrackersEnabled() && !torrent->isPrivate())
4625  torrent->addTrackers(m_additionalTrackerList);
4626 
4627  LogMsg(tr("'%1' added to download list.", "'torrent name' was added to download list.")
4628  .arg(torrent->name()));
4629  }
4630 
4631  if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
4632  && !m_seedingLimitTimer->isActive())
4633  m_seedingLimitTimer->start();
4634 
4635  // Send torrent addition signal
4636  emit torrentLoaded(torrent);
4637  // Send new torrent signal
4638  if (!params.restored)
4639  emit torrentAdded(torrent);
4640 
4641  // Torrent could have error just after adding to libtorrent
4642  if (torrent->hasError())
4643  LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: %2.").arg(torrent->name(), torrent->error()), Log::WARNING);
4644 }
4645 
4646 void Session::handleAddTorrentAlert(const lt::add_torrent_alert *p)
4647 {
4648  if (p->error)
4649  {
4650  const QString msg = QString::fromStdString(p->message());
4651  LogMsg(tr("Couldn't load torrent. Reason: %1.").arg(msg), Log::WARNING);
4652  emit loadTorrentFailed(msg);
4653 
4654  const lt::add_torrent_params &params = p->params;
4655  const bool hasMetadata = (params.ti && params.ti->is_valid());
4656 #ifdef QBT_USES_LIBTORRENT2
4657  const auto id = TorrentID::fromInfoHash(hasMetadata ? params.ti->info_hashes() : params.info_hashes);
4658 #else
4659  const auto id = TorrentID::fromInfoHash(hasMetadata ? params.ti->info_hash() : params.info_hash);
4660 #endif
4661  m_loadingTorrents.remove(id);
4662  }
4663  else if (m_loadingTorrents.contains(p->handle.info_hash()))
4664  {
4665  createTorrent(p->handle);
4666  }
4667 }
4668 
4669 void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
4670 {
4671 #ifdef QBT_USES_LIBTORRENT2
4672  const auto id = TorrentID::fromInfoHash(p->info_hashes);
4673 #else
4674  const auto id = TorrentID::fromInfoHash(p->info_hash);
4675 #endif
4676 
4677  const auto removingTorrentDataIter = m_removingTorrents.find(id);
4678  if (removingTorrentDataIter != m_removingTorrents.end())
4679  {
4680  if (removingTorrentDataIter->deleteOption == DeleteTorrent)
4681  {
4682  LogMsg(tr("'%1' was removed from the transfer list.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4683  m_removingTorrents.erase(removingTorrentDataIter);
4684  }
4685  }
4686 }
4687 
4688 void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
4689 {
4690 #ifdef QBT_USES_LIBTORRENT2
4691  const auto id = TorrentID::fromInfoHash(p->info_hashes);
4692 #else
4693  const auto id = TorrentID::fromInfoHash(p->info_hash);
4694 #endif
4695 
4696  const auto removingTorrentDataIter = m_removingTorrents.find(id);
4697 
4698  if (removingTorrentDataIter == m_removingTorrents.end())
4699  return;
4700 
4701  Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
4702  LogMsg(tr("'%1' was removed from the transfer list and hard disk.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4703  m_removingTorrents.erase(removingTorrentDataIter);
4704 }
4705 
4706 void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
4707 {
4708 #ifdef QBT_USES_LIBTORRENT2
4709  const auto id = TorrentID::fromInfoHash(p->info_hashes);
4710 #else
4711  const auto id = TorrentID::fromInfoHash(p->info_hash);
4712 #endif
4713 
4714  const auto removingTorrentDataIter = m_removingTorrents.find(id);
4715 
4716  if (removingTorrentDataIter == m_removingTorrents.end())
4717  return;
4718 
4719  if (p->error)
4720  {
4721  // libtorrent won't delete the directory if it contains files not listed in the torrent,
4722  // so we remove the directory ourselves
4723  Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
4724 
4725  LogMsg(tr("'%1' was removed from the transfer list but the files couldn't be deleted. Error: %2", "'xxx.avi' was removed...")
4726  .arg(removingTorrentDataIter->name, QString::fromLocal8Bit(p->error.message().c_str()))
4727  , Log::WARNING);
4728  }
4729  else // torrent without metadata, hence no files on disk
4730  {
4731  LogMsg(tr("'%1' was removed from the transfer list.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4732  }
4733  m_removingTorrents.erase(removingTorrentDataIter);
4734 }
4735 
4736 void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
4737 {
4738 #ifdef QBT_USES_LIBTORRENT2
4739  const auto id = TorrentID::fromInfoHash(p->handle.info_hashes());
4740 #else
4741  const auto id = TorrentID::fromInfoHash(p->handle.info_hash());
4742 #endif
4743 
4744  const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
4745 
4746  if (downloadedMetadataIter != m_downloadedMetadata.end())
4747  {
4748  const TorrentInfo metadata {*p->handle.torrent_file()};
4749 
4750  m_downloadedMetadata.erase(downloadedMetadataIter);
4751  --m_extraLimit;
4752  adjustLimits();
4753  m_nativeSession->remove_torrent(p->handle, lt::session::delete_files);
4754 
4755  emit metadataDownloaded(metadata);
4756  }
4757 }
4758 
4759 void Session::handleFileErrorAlert(const lt::file_error_alert *p)
4760 {
4761  TorrentImpl *const torrent = m_torrents.value(p->handle.info_hash());
4762  if (!torrent)
4763  return;
4764 
4765  torrent->handleAlert(p);
4766 
4767  const TorrentID id = torrent->id();
4768  if (!m_recentErroredTorrents.contains(id))
4769  {
4770  m_recentErroredTorrents.insert(id);
4771 
4772  const QString msg = QString::fromStdString(p->message());
4773  LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: %3")
4774  .arg(torrent->name(), p->filename(), msg)
4775  , Log::WARNING);
4776  emit fullDiskError(torrent, msg);
4777  }
4778 
4780 }
4781 
4782 void Session::handlePortmapWarningAlert(const lt::portmap_error_alert *p)
4783 {
4784  LogMsg(tr("UPnP/NAT-PMP: Port mapping failure, message: %1").arg(QString::fromStdString(p->message())), Log::CRITICAL);
4785 }
4786 
4787 void Session::handlePortmapAlert(const lt::portmap_alert *p)
4788 {
4789  qDebug("UPnP Success, msg: %s", p->message().c_str());
4790  LogMsg(tr("UPnP/NAT-PMP: Port mapping successful, message: %1").arg(QString::fromStdString(p->message())), Log::INFO);
4791 }
4792 
4793 void Session::handlePeerBlockedAlert(const lt::peer_blocked_alert *p)
4794 {
4795  QString reason;
4796  switch (p->reason)
4797  {
4798  case lt::peer_blocked_alert::ip_filter:
4799  reason = tr("IP filter", "this peer was blocked. Reason: IP filter.");
4800  break;
4801  case lt::peer_blocked_alert::port_filter:
4802  reason = tr("port filter", "this peer was blocked. Reason: port filter.");
4803  break;
4804  case lt::peer_blocked_alert::i2p_mixed:
4805  reason = tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg("I2P"); // don't translate I2P
4806  break;
4807  case lt::peer_blocked_alert::privileged_ports:
4808  reason = tr("use of privileged port", "this peer was blocked. Reason: use of privileged port.");
4809  break;
4810  case lt::peer_blocked_alert::utp_disabled:
4811  reason = tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(QString::fromUtf8(C_UTP)); // don't translate μTP
4812  break;
4813  case lt::peer_blocked_alert::tcp_disabled:
4814  reason = tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg("TCP"); // don't translate TCP
4815  break;
4816  }
4817 
4818  const QString ip {toString(p->endpoint.address())};
4819  if (!ip.isEmpty())
4820  Logger::instance()->addPeer(ip, true, reason);
4821 }
4822 
4823 void Session::handlePeerBanAlert(const lt::peer_ban_alert *p)
4824 {
4825  const QString ip {toString(p->endpoint.address())};
4826  if (!ip.isEmpty())
4827  Logger::instance()->addPeer(ip, false);
4828 }
4829 
4830 void Session::handleUrlSeedAlert(const lt::url_seed_alert *p)
4831 {
4832  const TorrentImpl *torrent = m_torrents.value(p->handle.info_hash());
4833  if (!torrent)
4834  return;
4835 
4836  if (p->error)
4837  {
4838  LogMsg(tr("URL seed name lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
4839  .arg(torrent->name(), p->server_url(), QString::fromStdString(p->message()))
4840  , Log::WARNING);
4841  }
4842  else
4843  {
4844  LogMsg(tr("Received error message from a URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
4845  .arg(torrent->name(), p->server_url(), p->error_message())
4846  , Log::WARNING);
4847  }
4848 }
4849 
4850 void Session::handleListenSucceededAlert(const lt::listen_succeeded_alert *p)
4851 {
4852  const QString proto {toString(p->socket_type)};
4853  LogMsg(tr("Successfully listening on IP: %1, port: %2/%3"
4854  , "e.g: Successfully listening on IP: 192.168.0.1, port: TCP/6881")
4855  .arg(toString(p->address), proto, QString::number(p->port)), Log::INFO);
4856 
4857  // Force reannounce on all torrents because some trackers blacklist some ports
4859 }
4860 
4861 void Session::handleListenFailedAlert(const lt::listen_failed_alert *p)
4862 {
4863  const QString proto {toString(p->socket_type)};
4864  LogMsg(tr("Failed to listen on IP: %1, port: %2/%3. Reason: %4"
4865  , "e.g: Failed to listen on IP: 192.168.0.1, port: TCP/6881. Reason: already in use")
4866  .arg(toString(p->address), proto, QString::number(p->port)
4867  , QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
4868 }
4869 
4870 void Session::handleExternalIPAlert(const lt::external_ip_alert *p)
4871 {
4872  const QString externalIP {toString(p->external_address)};
4873  LogMsg(tr("Detected external IP: %1", "e.g. Detected external IP: 1.1.1.1")
4874  .arg(externalIP), Log::INFO);
4875 
4876  if (m_lastExternalIP != externalIP)
4877  {
4880  m_lastExternalIP = externalIP;
4881  }
4882 }
4883 
4884 void Session::handleSessionStatsAlert(const lt::session_stats_alert *p)
4885 {
4886  const qreal interval = lt::total_milliseconds(p->timestamp() - m_statsLastTimestamp) / 1000.;
4887  m_statsLastTimestamp = p->timestamp();
4888 
4889  const auto stats = p->counters();
4890 
4892 
4893  const int64_t ipOverheadDownload = stats[m_metricIndices.net.recvIPOverheadBytes];
4894  const int64_t ipOverheadUpload = stats[m_metricIndices.net.sentIPOverheadBytes];
4895  const int64_t totalDownload = stats[m_metricIndices.net.recvBytes] + ipOverheadDownload;
4896  const int64_t totalUpload = stats[m_metricIndices.net.sentBytes] + ipOverheadUpload;
4897  const int64_t totalPayloadDownload = stats[m_metricIndices.net.recvPayloadBytes];
4898  const int64_t totalPayloadUpload = stats[m_metricIndices.net.sentPayloadBytes];
4899  const int64_t trackerDownload = stats[m_metricIndices.net.recvTrackerBytes];
4900  const int64_t trackerUpload = stats[m_metricIndices.net.sentTrackerBytes];
4901  const int64_t dhtDownload = stats[m_metricIndices.dht.dhtBytesIn];
4902  const int64_t dhtUpload = stats[m_metricIndices.dht.dhtBytesOut];
4903 
4904  auto calcRate = [interval](const quint64 previous, const quint64 current)
4905  {
4906  Q_ASSERT(current >= previous);
4907  return static_cast<quint64>((current - previous) / interval);
4908  };
4909 
4910  m_status.payloadDownloadRate = calcRate(m_status.totalPayloadDownload, totalPayloadDownload);
4911  m_status.payloadUploadRate = calcRate(m_status.totalPayloadUpload, totalPayloadUpload);
4912  m_status.downloadRate = calcRate(m_status.totalDownload, totalDownload);
4913  m_status.uploadRate = calcRate(m_status.totalUpload, totalUpload);
4914  m_status.ipOverheadDownloadRate = calcRate(m_status.ipOverheadDownload, ipOverheadDownload);
4915  m_status.ipOverheadUploadRate = calcRate(m_status.ipOverheadUpload, ipOverheadUpload);
4916  m_status.dhtDownloadRate = calcRate(m_status.dhtDownload, dhtDownload);
4917  m_status.dhtUploadRate = calcRate(m_status.dhtUpload, dhtUpload);
4918  m_status.trackerDownloadRate = calcRate(m_status.trackerDownload, trackerDownload);
4919  m_status.trackerUploadRate = calcRate(m_status.trackerUpload, trackerUpload);
4920 
4921  m_status.totalDownload = totalDownload;
4922  m_status.totalUpload = totalUpload;
4923  m_status.totalPayloadDownload = totalPayloadDownload;
4924  m_status.totalPayloadUpload = totalPayloadUpload;
4925  m_status.ipOverheadDownload = ipOverheadDownload;
4926  m_status.ipOverheadUpload = ipOverheadUpload;
4927  m_status.trackerDownload = trackerDownload;
4928  m_status.trackerUpload = trackerUpload;
4929  m_status.dhtDownload = dhtDownload;
4930  m_status.dhtUpload = dhtUpload;
4937 
4940 
4941 #ifndef QBT_USES_LIBTORRENT2
4942  const int64_t numBlocksRead = stats[m_metricIndices.disk.numBlocksRead];
4943  const int64_t numBlocksCacheHits = stats[m_metricIndices.disk.numBlocksCacheHits];
4944  m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max<int64_t>((numBlocksCacheHits + numBlocksRead), 1);
4945 #endif
4946 
4947  const int64_t totalJobs = stats[m_metricIndices.disk.writeJobs] + stats[m_metricIndices.disk.readJobs]
4948  + stats[m_metricIndices.disk.hashJobs];
4949  m_cacheStatus.averageJobTime = (totalJobs > 0)
4950  ? (stats[m_metricIndices.disk.diskJobTime] / totalJobs) : 0;
4951 
4952  emit statsUpdated();
4953 
4954  if (m_refreshEnqueued)
4955  m_refreshEnqueued = false;
4956  else
4957  enqueueRefresh();
4958 }
4959 
4960 void Session::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
4961 {
4962  LogMsg(tr("Error: Internal alert queue full and alerts were dropped, you might see degraded performance. Dropped alert types: %1. Message: %2")
4963  .arg(QString::fromStdString(p->dropped_alerts.to_string()), QString::fromStdString(p->message())), Log::CRITICAL);
4964 }
4965 
4966 void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
4967 {
4968  Q_ASSERT(!m_moveStorageQueue.isEmpty());
4969 
4970  const MoveStorageJob &currentJob = m_moveStorageQueue.first();
4971  Q_ASSERT(currentJob.torrentHandle == p->handle);
4972 
4973  const QString newPath {p->storage_path()};
4974  Q_ASSERT(newPath == currentJob.path);
4975 
4976 #ifdef QBT_USES_LIBTORRENT2
4977  const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
4978 #else
4979  const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
4980 #endif
4981 
4982  TorrentImpl *torrent = m_torrents.value(id);
4983  const QString torrentName = (torrent ? torrent->name() : id.toString());
4984  LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath));
4985 
4987 }
4988 
4989 void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
4990 {
4991  Q_ASSERT(!m_moveStorageQueue.isEmpty());
4992 
4993  const MoveStorageJob &currentJob = m_moveStorageQueue.first();
4994  Q_ASSERT(currentJob.torrentHandle == p->handle);
4995 
4996 #ifdef QBT_USES_LIBTORRENT2
4997  const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
4998 #else
4999  const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
5000 #endif
5001 
5002  TorrentImpl *torrent = m_torrents.value(id);
5003  const QString torrentName = (torrent ? torrent->name() : id.toString());
5004  const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path);
5005  const QString errorMessage = QString::fromStdString(p->message());
5006  LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.")
5007  .arg(torrentName, currentLocation, currentJob.path, errorMessage), Log::CRITICAL);
5008 
5010 }
5011 
5012 void Session::handleStateUpdateAlert(const lt::state_update_alert *p)
5013 {
5014  QVector<Torrent *> updatedTorrents;
5015  updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(p->status.size()));
5016 
5017  for (const lt::torrent_status &status : p->status)
5018  {
5019 #ifdef QBT_USES_LIBTORRENT2
5020  const auto id = TorrentID::fromInfoHash(status.info_hashes);
5021 #else
5022  const auto id = TorrentID::fromInfoHash(status.info_hash);
5023 #endif
5024  TorrentImpl *const torrent = m_torrents.value(id);
5025  if (!torrent)
5026  continue;
5027 
5028  torrent->handleStateUpdate(status);
5029  updatedTorrents.push_back(torrent);
5030  }
5031 
5032  if (!updatedTorrents.isEmpty())
5033  emit torrentsUpdated(updatedTorrents);
5034 
5035  if (m_refreshEnqueued)
5036  m_refreshEnqueued = false;
5037  else
5038  enqueueRefresh();
5039 }
5040 
5041 void Session::handleSocks5Alert(const lt::socks5_alert *p) const
5042 {
5043  if (p->error)
5044  {
5045  LogMsg(tr("SOCKS5 proxy error. Message: %1").arg(QString::fromStdString(p->message()))
5046  , Log::WARNING);
5047  }
5048 }
void bandwidthLimitRequested(bool alternative)
QVector< TrackerEntry > trackers() const
Definition: magneturi.cpp:125
lt::add_torrent_params addTorrentParams() const
Definition: magneturi.cpp:140
bool isValid() const
Definition: magneturi.cpp:110
InfoHash infoHash() const
Definition: magneturi.cpp:115
QString name() const
Definition: magneturi.cpp:120
QVector< QUrl > urlSeeds() const
Definition: magneturi.cpp:130
virtual void remove(const TorrentID &id) const =0
virtual void storeQueue(const QVector< TorrentID > &queue) const =0
virtual void store(const TorrentID &id, const LoadTorrentParams &resumeData) const =0
virtual QVector< TorrentID > registeredTorrents() const =0
virtual std::optional< LoadTorrentParams > load(const TorrentID &id) const =0
void reannounceToAllTrackers() const
Definition: session.cpp:3656
int refreshInterval() const
Definition: session.cpp:573
void populateAdditionalTrackers()
Definition: session.cpp:1589
CachedSettingValue< int > m_globalMaxSeedingMinutes
Definition: session.h:716
void handleAddTorrentAlert(const lt::add_torrent_alert *p)
Definition: session.cpp:4646
QString networkInterfaceName() const
Definition: session.cpp:2813
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector< QUrl > &newUrlSeeds)
Definition: session.cpp:3925
QSet< QString > m_tags
Definition: session.h:790
CachedSettingValue< int > m_stopTrackerTimeout
Definition: session.h:700
bool removeTag(const QString &tag)
Definition: session.cpp:850
void loadLTSettings(lt::settings_pack &settingsPack)
Definition: session.cpp:1188
void setAddTrackersEnabled(bool enabled)
Definition: session.cpp:2899
bool blockPeersOnPrivilegedPorts() const
Definition: session.cpp:3801
QString additionalTrackers() const
Definition: session.cpp:2904
CachedSettingValue< int > m_socketBacklogSize
Definition: session.h:681
void setDiskCacheTTL(int ttl)
Definition: session.cpp:3268
void setOutgoingPortsMax(int max)
Definition: session.cpp:3550
bool m_IPFilteringConfigured
Definition: session.h:656
void setGlobalMaxSeedingMinutes(int minutes)
Definition: session.cpp:952
void setPeerTurnoverInterval(int num)
Definition: session.cpp:3170
CachedSettingValue< SeedChokingAlgorithm > m_seedChokingAlgorithm
Definition: session.h:738
std::vector< lt::alert * > getPendingAlerts(lt::time_duration time=lt::time_duration::zero()) const
Definition: session.cpp:4454
void subcategoriesSupportChanged()
QSet< TorrentID > m_needSaveResumeDataTorrents
Definition: session.h:788
void setBannedIPs(const QStringList &newList)
Definition: session.cpp:2949
void setDownloadRateForSlowTorrents(int rateInKibiBytes)
Definition: session.cpp:3494
void handleSessionStatsAlert(const lt::session_stats_alert *p)
Definition: session.cpp:4884
void handlePeerBanAlert(const lt::peer_ban_alert *p)
Definition: session.cpp:4823
bool isListening() const
Definition: session.cpp:3828
void setIPFilterFile(QString path)
Definition: session.cpp:2938
void handleTorrentChecked(TorrentImpl *const torrent)
Definition: session.cpp:3963
CachedSettingValue< QStringList > m_bannedIPs
Definition: session.h:753
QString torrentExportDirectory() const
Definition: session.cpp:596
QStringList getListeningIPs() const
Definition: session.cpp:2491
void torrentTagAdded(Torrent *torrent, const QString &tag)
void setTorrentExportDirectory(QString path)
Definition: session.cpp:601
void handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
Definition: session.cpp:3885
void handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
Definition: session.cpp:3880
int maxActiveDownloads() const
Definition: session.cpp:3430
int peerTurnover() const
Definition: session.cpp:3137
CachedSettingValue< bool > m_isTrackerFilteringEnabled
Definition: session.h:663
void removeTorrentsQueue() const
Definition: session.cpp:2430
void setPort(int port)
Definition: session.cpp:2787
qreal globalMaxRatio() const
Definition: session.cpp:928
void handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector< TrackerEntry > &deletedTrackers)
Definition: session.cpp:3910
void torrentSavingModeChanged(Torrent *torrent)
QString m_lastExternalIP
Definition: session.h:807
CachedSettingValue< int > m_saveResumeDataInterval
Definition: session.h:730
int maxActiveUploads() const
Definition: session.cpp:3445
QVector< Torrent * > torrents() const
Definition: session.cpp:1997
lt::time_point m_statsLastTimestamp
Definition: session.h:797
void handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
Definition: session.cpp:4960
int uploadSpeedLimit() const
Definition: session.cpp:2711
CachedSettingValue< bool > m_isLSDEnabled
Definition: session.h:660
void torrentPaused(Torrent *torrent)
bool cancelDownloadMetadata(const TorrentID &id)
Definition: session.cpp:1850
void setResumeDataStorageType(ResumeDataStorageType type)
Definition: session.cpp:2990