Sync translations from Transifex and run lupdate
[qBittorrent.git] / src / base / bittorrent / session.cpp
blob4773bd96044e26f72f3fba6e7d6d2f090148bfb1
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>
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.
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.
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.
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.
30 #include "session.h"
32 #include <algorithm>
33 #include <cstdint>
34 #include <queue>
35 #include <string>
36 #include <utility>
38 #ifdef Q_OS_WIN
39 #include <Windows.h>
40 #include <wincrypt.h>
41 #include <iphlpapi.h>
42 #endif
44 #include <libtorrent/alert_types.hpp>
45 #include <libtorrent/error_code.hpp>
46 #include <libtorrent/extensions/smart_ban.hpp>
47 #include <libtorrent/extensions/ut_metadata.hpp>
48 #include <libtorrent/extensions/ut_pex.hpp>
49 #include <libtorrent/ip_filter.hpp>
50 #include <libtorrent/magnet_uri.hpp>
51 #include <libtorrent/session.hpp>
52 #include <libtorrent/session_stats.hpp>
53 #include <libtorrent/session_status.hpp>
54 #include <libtorrent/torrent_info.hpp>
56 #include <QDebug>
57 #include <QDir>
58 #include <QFile>
59 #include <QHostAddress>
60 #include <QNetworkAddressEntry>
61 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
62 #include <QNetworkConfigurationManager>
63 #endif
64 #include <QNetworkInterface>
65 #include <QRegularExpression>
66 #include <QString>
67 #include <QThread>
68 #include <QTimer>
69 #include <QUuid>
71 #include "base/algorithm.h"
72 #include "base/exceptions.h"
73 #include "base/global.h"
74 #include "base/logger.h"
75 #include "base/net/downloadmanager.h"
76 #include "base/net/proxyconfigurationmanager.h"
77 #include "base/profile.h"
78 #include "base/torrentfileguard.h"
79 #include "base/torrentfilter.h"
80 #include "base/unicodestrings.h"
81 #include "base/utils/bytearray.h"
82 #include "base/utils/fs.h"
83 #include "base/utils/misc.h"
84 #include "base/utils/net.h"
85 #include "base/utils/random.h"
86 #include "base/version.h"
87 #include "bandwidthscheduler.h"
88 #include "bencoderesumedatastorage.h"
89 #include "common.h"
90 #include "customstorage.h"
91 #include "dbresumedatastorage.h"
92 #include "downloadpriority.h"
93 #include "filesearcher.h"
94 #include "filterparserthread.h"
95 #include "loadtorrentparams.h"
96 #include "ltunderlyingtype.h"
97 #include "magneturi.h"
98 #include "nativesessionextension.h"
99 #include "portforwarderimpl.h"
100 #include "statistics.h"
101 #include "torrentimpl.h"
102 #include "tracker.h"
104 using namespace BitTorrent;
106 namespace
108 const char PEER_ID[] = "qB";
109 const char USER_AGENT[] = "qBittorrent/" QBT_VERSION_2;
111 void torrentQueuePositionUp(const lt::torrent_handle &handle)
115 handle.queue_position_up();
117 catch (const std::exception &exc)
119 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
123 void torrentQueuePositionDown(const lt::torrent_handle &handle)
127 handle.queue_position_down();
129 catch (const std::exception &exc)
131 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
135 void torrentQueuePositionTop(const lt::torrent_handle &handle)
139 handle.queue_position_top();
141 catch (const std::exception &exc)
143 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
147 void torrentQueuePositionBottom(const lt::torrent_handle &handle)
151 handle.queue_position_bottom();
153 catch (const std::exception &exc)
155 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
159 QStringMap map_cast(const QVariantMap &map)
161 QStringMap result;
162 for (auto i = map.cbegin(); i != map.cend(); ++i)
163 result[i.key()] = i.value().toString();
164 return result;
167 QVariantMap map_cast(const QStringMap &map)
169 QVariantMap result;
170 for (auto i = map.cbegin(); i != map.cend(); ++i)
171 result[i.key()] = i.value();
172 return result;
175 QString normalizePath(const QString &path)
177 QString tmp = Utils::Fs::toUniformPath(path.trimmed());
178 if (!tmp.isEmpty() && !tmp.endsWith('/'))
179 return tmp + '/';
180 return tmp;
183 QString normalizeSavePath(QString path, const QString &defaultPath = specialFolderLocation(SpecialFolder::Downloads))
185 path = path.trimmed();
186 if (path.isEmpty())
187 path = Utils::Fs::toUniformPath(defaultPath.trimmed());
189 return normalizePath(path);
192 QStringMap expandCategories(const QStringMap &categories)
194 QStringMap expanded = categories;
196 for (auto i = categories.cbegin(); i != categories.cend(); ++i)
198 const QString &category = i.key();
199 for (const QString &subcat : asConst(Session::expandCategory(category)))
201 if (!expanded.contains(subcat))
202 expanded[subcat] = "";
206 return expanded;
209 QString toString(const lt::socket_type_t socketType)
211 switch (socketType)
213 #ifdef QBT_USES_LIBTORRENT2
214 case lt::socket_type_t::http:
215 return QLatin1String("HTTP");
216 case lt::socket_type_t::http_ssl:
217 return QLatin1String("HTTP_SSL");
218 #endif
219 case lt::socket_type_t::i2p:
220 return QLatin1String("I2P");
221 case lt::socket_type_t::socks5:
222 return QLatin1String("SOCKS5");
223 #ifdef QBT_USES_LIBTORRENT2
224 case lt::socket_type_t::socks5_ssl:
225 return QLatin1String("SOCKS5_SSL");
226 #endif
227 case lt::socket_type_t::tcp:
228 return QLatin1String("TCP");
229 case lt::socket_type_t::tcp_ssl:
230 return QLatin1String("TCP_SSL");
231 #ifdef QBT_USES_LIBTORRENT2
232 case lt::socket_type_t::utp:
233 return QLatin1String("UTP");
234 #else
235 case lt::socket_type_t::udp:
236 return QLatin1String("UDP");
237 #endif
238 case lt::socket_type_t::utp_ssl:
239 return QLatin1String("UTP_SSL");
241 return QLatin1String("INVALID");
244 QString toString(const lt::address &address)
248 return QString::fromLatin1(address.to_string().c_str());
250 catch (const std::exception &)
252 // suppress conversion error
254 return {};
257 template <typename T>
258 struct LowerLimited
260 LowerLimited(T limit, T ret)
261 : m_limit(limit)
262 , m_ret(ret)
266 explicit LowerLimited(T limit)
267 : LowerLimited(limit, limit)
271 T operator()(T val) const
273 return val <= m_limit ? m_ret : val;
276 private:
277 const T m_limit;
278 const T m_ret;
281 template <typename T>
282 LowerLimited<T> lowerLimited(T limit) { return LowerLimited<T>(limit); }
284 template <typename T>
285 LowerLimited<T> lowerLimited(T limit, T ret) { return LowerLimited<T>(limit, ret); }
287 template <typename T>
288 auto clampValue(const T lower, const T upper)
290 return [lower, upper](const T value) -> T
292 if (value < lower)
293 return lower;
294 if (value > upper)
295 return upper;
296 return value;
300 #ifdef Q_OS_WIN
301 QString convertIfaceNameToGuid(const QString &name)
303 // Under Windows XP or on Qt version <= 5.5 'name' will be a GUID already.
304 const QUuid uuid(name);
305 if (!uuid.isNull())
306 return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
308 NET_LUID luid {};
309 const LONG res = ::ConvertInterfaceNameToLuidW(name.toStdWString().c_str(), &luid);
310 if (res == 0)
312 GUID guid;
313 if (::ConvertInterfaceLuidToGuid(&luid, &guid) == 0)
314 return QUuid(guid).toString().toUpper();
317 return {};
319 #endif
322 const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
324 // Session
326 Session *Session::m_instance = nullptr;
328 #define BITTORRENT_KEY(name) "BitTorrent/" name
329 #define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY("Session/") name
331 Session::Session(QObject *parent)
332 : QObject(parent)
333 , m_isDHTEnabled(BITTORRENT_SESSION_KEY("DHTEnabled"), true)
334 , m_isLSDEnabled(BITTORRENT_SESSION_KEY("LSDEnabled"), true)
335 , m_isPeXEnabled(BITTORRENT_SESSION_KEY("PeXEnabled"), true)
336 , m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY("IPFilteringEnabled"), false)
337 , m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY("TrackerFilteringEnabled"), false)
338 , m_IPFilterFile(BITTORRENT_SESSION_KEY("IPFilter"))
339 , m_announceToAllTrackers(BITTORRENT_SESSION_KEY("AnnounceToAllTrackers"), false)
340 , m_announceToAllTiers(BITTORRENT_SESSION_KEY("AnnounceToAllTiers"), true)
341 , m_asyncIOThreads(BITTORRENT_SESSION_KEY("AsyncIOThreadsCount"), 10)
342 , m_hashingThreads(BITTORRENT_SESSION_KEY("HashingThreadsCount"), 2)
343 , m_filePoolSize(BITTORRENT_SESSION_KEY("FilePoolSize"), 5000)
344 , m_checkingMemUsage(BITTORRENT_SESSION_KEY("CheckingMemUsageSize"), 32)
345 , m_diskCacheSize(BITTORRENT_SESSION_KEY("DiskCacheSize"), -1)
346 , m_diskCacheTTL(BITTORRENT_SESSION_KEY("DiskCacheTTL"), 60)
347 , m_useOSCache(BITTORRENT_SESSION_KEY("UseOSCache"), true)
348 #ifdef Q_OS_WIN
349 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY("CoalesceReadWrite"), true)
350 #else
351 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY("CoalesceReadWrite"), false)
352 #endif
353 , m_usePieceExtentAffinity(BITTORRENT_SESSION_KEY("PieceExtentAffinity"), false)
354 , m_isSuggestMode(BITTORRENT_SESSION_KEY("SuggestMode"), false)
355 , m_sendBufferWatermark(BITTORRENT_SESSION_KEY("SendBufferWatermark"), 500)
356 , m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY("SendBufferLowWatermark"), 10)
357 , m_sendBufferWatermarkFactor(BITTORRENT_SESSION_KEY("SendBufferWatermarkFactor"), 50)
358 , m_connectionSpeed(BITTORRENT_SESSION_KEY("ConnectionSpeed"), 30)
359 , m_socketBacklogSize(BITTORRENT_SESSION_KEY("SocketBacklogSize"), 30)
360 , m_isAnonymousModeEnabled(BITTORRENT_SESSION_KEY("AnonymousModeEnabled"), false)
361 , m_isQueueingEnabled(BITTORRENT_SESSION_KEY("QueueingSystemEnabled"), false)
362 , m_maxActiveDownloads(BITTORRENT_SESSION_KEY("MaxActiveDownloads"), 3, lowerLimited(-1))
363 , m_maxActiveUploads(BITTORRENT_SESSION_KEY("MaxActiveUploads"), 3, lowerLimited(-1))
364 , m_maxActiveTorrents(BITTORRENT_SESSION_KEY("MaxActiveTorrents"), 5, lowerLimited(-1))
365 , m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY("IgnoreSlowTorrentsForQueueing"), false)
366 , m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY("SlowTorrentsDownloadRate"), 2)
367 , m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY("SlowTorrentsUploadRate"), 2)
368 , m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY("SlowTorrentsInactivityTimer"), 60)
369 , m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
370 , m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
371 , m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
372 , m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x20)
373 , m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
374 , m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
375 , m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
376 , m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY("MaxConcurrentHTTPAnnounces"), 50)
377 , m_isReannounceWhenAddressChangedEnabled(BITTORRENT_SESSION_KEY("ReannounceWhenAddressChanged"), false)
378 , m_stopTrackerTimeout(BITTORRENT_SESSION_KEY("StopTrackerTimeout"), 5)
379 , m_maxConnections(BITTORRENT_SESSION_KEY("MaxConnections"), 500, lowerLimited(0, -1))
380 , m_maxUploads(BITTORRENT_SESSION_KEY("MaxUploads"), 20, lowerLimited(0, -1))
381 , m_maxConnectionsPerTorrent(BITTORRENT_SESSION_KEY("MaxConnectionsPerTorrent"), 100, lowerLimited(0, -1))
382 , m_maxUploadsPerTorrent(BITTORRENT_SESSION_KEY("MaxUploadsPerTorrent"), 4, lowerLimited(0, -1))
383 , m_btProtocol(BITTORRENT_SESSION_KEY("BTProtocol"), BTProtocol::Both
384 , clampValue(BTProtocol::Both, BTProtocol::UTP))
385 , m_isUTPRateLimited(BITTORRENT_SESSION_KEY("uTPRateLimited"), true)
386 , m_utpMixedMode(BITTORRENT_SESSION_KEY("uTPMixedMode"), MixedModeAlgorithm::TCP
387 , clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
388 , m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), false)
389 , m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
390 , m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), true)
391 , m_SSRFMitigationEnabled(BITTORRENT_SESSION_KEY("SSRFMitigation"), true)
392 , m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY("BlockPeersOnPrivilegedPorts"), false)
393 , m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
394 , m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers"))
395 , m_globalMaxRatio(BITTORRENT_SESSION_KEY("GlobalMaxRatio"), -1, [](qreal r) { return r < 0 ? -1. : r;})
396 , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY("GlobalMaxSeedingMinutes"), -1, lowerLimited(-1))
397 , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY("AddTorrentPaused"), false)
398 , m_torrentContentLayout(BITTORRENT_SESSION_KEY("TorrentContentLayout"), TorrentContentLayout::Original)
399 , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY("AddExtensionToIncompleteFiles"), false)
400 , m_refreshInterval(BITTORRENT_SESSION_KEY("RefreshInterval"), 1500)
401 , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY("Preallocation"), false)
402 , m_torrentExportDirectory(BITTORRENT_SESSION_KEY("TorrentExportDirectory"))
403 , m_finishedTorrentExportDirectory(BITTORRENT_SESSION_KEY("FinishedTorrentExportDirectory"))
404 , m_globalDownloadSpeedLimit(BITTORRENT_SESSION_KEY("GlobalDLSpeedLimit"), 0, lowerLimited(0))
405 , m_globalUploadSpeedLimit(BITTORRENT_SESSION_KEY("GlobalUPSpeedLimit"), 0, lowerLimited(0))
406 , m_altGlobalDownloadSpeedLimit(BITTORRENT_SESSION_KEY("AlternativeGlobalDLSpeedLimit"), 10, lowerLimited(0))
407 , m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY("AlternativeGlobalUPSpeedLimit"), 10, lowerLimited(0))
408 , m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY("UseAlternativeGlobalSpeedLimit"), false)
409 , m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY("BandwidthSchedulerEnabled"), false)
410 , m_saveResumeDataInterval(BITTORRENT_SESSION_KEY("SaveResumeDataInterval"), 60)
411 , m_port(BITTORRENT_SESSION_KEY("Port"), -1)
412 , m_networkInterface(BITTORRENT_SESSION_KEY("Interface"))
413 , m_networkInterfaceName(BITTORRENT_SESSION_KEY("InterfaceName"))
414 , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY("InterfaceAddress"))
415 , m_encryption(BITTORRENT_SESSION_KEY("Encryption"), 0)
416 , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY("ProxyPeerConnections"), false)
417 , m_chokingAlgorithm(BITTORRENT_SESSION_KEY("ChokingAlgorithm"), ChokingAlgorithm::FixedSlots
418 , clampValue(ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased))
419 , m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY("SeedChokingAlgorithm"), SeedChokingAlgorithm::FastestUpload
420 , clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
421 , m_storedCategories(BITTORRENT_SESSION_KEY("Categories"))
422 , m_storedTags(BITTORRENT_SESSION_KEY("Tags"))
423 , m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause)
424 , m_defaultSavePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads), normalizePath)
425 , m_tempPath(BITTORRENT_SESSION_KEY("TempPath"), defaultSavePath() + "temp/", normalizePath)
426 , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY("SubcategoriesEnabled"), false)
427 , m_isTempPathEnabled(BITTORRENT_SESSION_KEY("TempPathEnabled"), false)
428 , m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY("DisableAutoTMMByDefault"), true)
429 , m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/CategoryChanged"), false)
430 , m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/DefaultSavePathChanged"), true)
431 , m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/CategorySavePathChanged"), true)
432 , m_isTrackerEnabled(BITTORRENT_KEY("TrackerEnabled"), false)
433 , m_peerTurnover(BITTORRENT_SESSION_KEY("PeerTurnover"), 4)
434 , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY("PeerTurnoverCutOff"), 90)
435 , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY("PeerTurnoverInterval"), 300)
436 , m_bannedIPs("State/BannedIPs"
437 , QStringList()
438 , [](const QStringList &value)
440 QStringList tmp = value;
441 tmp.sort();
442 return tmp;
445 , m_resumeDataStorageType(BITTORRENT_SESSION_KEY("ResumeDataStorageType"), ResumeDataStorageType::Legacy)
446 #if defined(Q_OS_WIN)
447 , m_OSMemoryPriority(BITTORRENT_KEY("OSMemoryPriority"), OSMemoryPriority::BelowNormal)
448 #endif
449 , m_seedingLimitTimer {new QTimer {this}}
450 , m_resumeDataTimer {new QTimer {this}}
451 , m_statistics {new Statistics {this}}
452 , m_ioThread {new QThread {this}}
453 , m_recentErroredTorrentsTimer {new QTimer {this}}
454 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
455 , m_networkManager {new QNetworkConfigurationManager {this}}
456 #endif
458 if (port() < 0)
459 m_port = Utils::Random::rand(1024, 65535);
461 m_recentErroredTorrentsTimer->setSingleShot(true);
462 m_recentErroredTorrentsTimer->setInterval(1000);
463 connect(m_recentErroredTorrentsTimer, &QTimer::timeout
464 , this, [this]() { m_recentErroredTorrents.clear(); });
466 m_seedingLimitTimer->setInterval(10000);
467 connect(m_seedingLimitTimer, &QTimer::timeout, this, &Session::processShareLimits);
469 initializeNativeSession();
470 configureComponents();
472 if (isBandwidthSchedulerEnabled())
473 enableBandwidthScheduler();
475 m_categories = map_cast(m_storedCategories);
476 if (isSubcategoriesEnabled())
478 // if subcategories support changed manually
479 m_categories = expandCategories(m_categories);
480 m_storedCategories = map_cast(m_categories);
483 const QStringList storedTags = m_storedTags.get();
484 m_tags = {storedTags.cbegin(), storedTags.cend()};
486 enqueueRefresh();
487 updateSeedingLimitTimer();
488 populateAdditionalTrackers();
490 enableTracker(isTrackerEnabled());
492 connect(Net::ProxyConfigurationManager::instance()
493 , &Net::ProxyConfigurationManager::proxyConfigurationChanged
494 , this, &Session::configureDeferred);
496 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
497 // Network configuration monitor
498 connect(m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &Session::networkOnlineStateChanged);
499 connect(m_networkManager, &QNetworkConfigurationManager::configurationAdded, this, &Session::networkConfigurationChange);
500 connect(m_networkManager, &QNetworkConfigurationManager::configurationRemoved, this, &Session::networkConfigurationChange);
501 connect(m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &Session::networkConfigurationChange);
502 #endif
504 m_fileSearcher = new FileSearcher;
505 m_fileSearcher->moveToThread(m_ioThread);
506 connect(m_ioThread, &QThread::finished, m_fileSearcher, &QObject::deleteLater);
507 connect(m_fileSearcher, &FileSearcher::searchFinished, this, &Session::fileSearchFinished);
509 m_ioThread->start();
511 // Regular saving of fastresume data
512 connect(m_resumeDataTimer, &QTimer::timeout, this, [this]() { generateResumeData(); });
513 const int saveInterval = saveResumeDataInterval();
514 if (saveInterval > 0)
516 m_resumeDataTimer->setInterval(saveInterval * 60 * 1000);
517 m_resumeDataTimer->start();
520 // initialize PortForwarder instance
521 new PortForwarderImpl {m_nativeSession};
523 initMetrics();
526 bool Session::isDHTEnabled() const
528 return m_isDHTEnabled;
531 void Session::setDHTEnabled(bool enabled)
533 if (enabled != m_isDHTEnabled)
535 m_isDHTEnabled = enabled;
536 configureDeferred();
537 LogMsg(tr("DHT support [%1]").arg(enabled ? tr("ON") : tr("OFF")), Log::INFO);
541 bool Session::isLSDEnabled() const
543 return m_isLSDEnabled;
546 void Session::setLSDEnabled(const bool enabled)
548 if (enabled != m_isLSDEnabled)
550 m_isLSDEnabled = enabled;
551 configureDeferred();
552 LogMsg(tr("Local Peer Discovery support [%1]").arg(enabled ? tr("ON") : tr("OFF"))
553 , Log::INFO);
557 bool Session::isPeXEnabled() const
559 return m_isPeXEnabled;
562 void Session::setPeXEnabled(const bool enabled)
564 m_isPeXEnabled = enabled;
565 if (m_wasPexEnabled != enabled)
566 LogMsg(tr("Restart is required to toggle PeX support"), Log::WARNING);
569 bool Session::isTempPathEnabled() const
571 return m_isTempPathEnabled;
574 void Session::setTempPathEnabled(const bool enabled)
576 if (enabled != isTempPathEnabled())
578 m_isTempPathEnabled = enabled;
579 for (TorrentImpl *const torrent : asConst(m_torrents))
580 torrent->handleTempPathChanged();
584 bool Session::isAppendExtensionEnabled() const
586 return m_isAppendExtensionEnabled;
589 void Session::setAppendExtensionEnabled(const bool enabled)
591 if (isAppendExtensionEnabled() != enabled)
593 m_isAppendExtensionEnabled = enabled;
595 // append or remove .!qB extension for incomplete files
596 for (TorrentImpl *const torrent : asConst(m_torrents))
597 torrent->handleAppendExtensionToggled();
601 int Session::refreshInterval() const
603 return m_refreshInterval;
606 void Session::setRefreshInterval(const int value)
608 if (value != refreshInterval())
610 m_refreshInterval = value;
614 bool Session::isPreallocationEnabled() const
616 return m_isPreallocationEnabled;
619 void Session::setPreallocationEnabled(const bool enabled)
621 m_isPreallocationEnabled = enabled;
624 QString Session::torrentExportDirectory() const
626 return Utils::Fs::toUniformPath(m_torrentExportDirectory);
629 void Session::setTorrentExportDirectory(QString path)
631 path = Utils::Fs::toUniformPath(path);
632 if (path != torrentExportDirectory())
633 m_torrentExportDirectory = path;
636 QString Session::finishedTorrentExportDirectory() const
638 return Utils::Fs::toUniformPath(m_finishedTorrentExportDirectory);
641 void Session::setFinishedTorrentExportDirectory(QString path)
643 path = Utils::Fs::toUniformPath(path);
644 if (path != finishedTorrentExportDirectory())
645 m_finishedTorrentExportDirectory = path;
648 QString Session::defaultSavePath() const
650 return Utils::Fs::toUniformPath(m_defaultSavePath);
653 QString Session::tempPath() const
655 return Utils::Fs::toUniformPath(m_tempPath);
658 QString Session::torrentTempPath(const TorrentInfo &torrentInfo) const
660 if ((torrentInfo.filesCount() > 1) && !torrentInfo.hasRootFolder())
661 return tempPath() + torrentInfo.name() + '/';
663 return tempPath();
666 bool Session::isValidCategoryName(const QString &name)
668 static const QRegularExpression re(R"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)");
669 if (!name.isEmpty() && (name.indexOf(re) != 0))
671 qDebug() << "Incorrect category name:" << name;
672 return false;
675 return true;
678 QStringList Session::expandCategory(const QString &category)
680 QStringList result;
681 if (!isValidCategoryName(category))
682 return result;
684 int index = 0;
685 while ((index = category.indexOf('/', index)) >= 0)
687 result << category.left(index);
688 ++index;
690 result << category;
692 return result;
695 QStringMap Session::categories() const
697 return m_categories;
700 QString Session::categorySavePath(const QString &categoryName) const
702 const QString basePath = m_defaultSavePath;
703 if (categoryName.isEmpty()) return basePath;
705 QString path = m_categories.value(categoryName);
706 if (path.isEmpty()) // use implicit save path
707 path = Utils::Fs::toValidFileSystemName(categoryName, true);
709 if (!QDir::isAbsolutePath(path))
710 path.prepend(basePath);
712 return normalizeSavePath(path);
715 bool Session::addCategory(const QString &name, const QString &savePath)
717 if (name.isEmpty()) return false;
718 if (!isValidCategoryName(name) || m_categories.contains(name))
719 return false;
721 if (isSubcategoriesEnabled())
723 for (const QString &parent : asConst(expandCategory(name)))
725 if ((parent != name) && !m_categories.contains(parent))
727 m_categories[parent] = "";
728 emit categoryAdded(parent);
733 m_categories[name] = savePath;
734 m_storedCategories = map_cast(m_categories);
735 emit categoryAdded(name);
737 return true;
740 bool Session::editCategory(const QString &name, const QString &savePath)
742 if (!m_categories.contains(name)) return false;
743 if (categorySavePath(name) == savePath) return false;
745 m_categories[name] = savePath;
746 m_storedCategories = map_cast(m_categories);
747 if (isDisableAutoTMMWhenCategorySavePathChanged())
749 for (TorrentImpl *const torrent : asConst(m_torrents))
750 if (torrent->category() == name)
751 torrent->setAutoTMMEnabled(false);
753 else
755 for (TorrentImpl *const torrent : asConst(m_torrents))
756 if (torrent->category() == name)
757 torrent->handleCategorySavePathChanged();
760 return true;
763 bool Session::removeCategory(const QString &name)
765 for (TorrentImpl *const torrent : asConst(m_torrents))
766 if (torrent->belongsToCategory(name))
767 torrent->setCategory("");
769 // remove stored category and its subcategories if exist
770 bool result = false;
771 if (isSubcategoriesEnabled())
773 // remove subcategories
774 const QString test = name + '/';
775 Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const QString &)
777 if (category.startsWith(test))
779 result = true;
780 emit categoryRemoved(category);
781 return true;
783 return false;
787 result = (m_categories.remove(name) > 0) || result;
789 if (result)
791 // update stored categories
792 m_storedCategories = map_cast(m_categories);
793 emit categoryRemoved(name);
796 return result;
799 bool Session::isSubcategoriesEnabled() const
801 return m_isSubcategoriesEnabled;
804 void Session::setSubcategoriesEnabled(const bool value)
806 if (isSubcategoriesEnabled() == value) return;
808 if (value)
810 // expand categories to include all parent categories
811 m_categories = expandCategories(m_categories);
812 // update stored categories
813 m_storedCategories = map_cast(m_categories);
815 else
817 // reload categories
818 m_categories = map_cast(m_storedCategories);
821 m_isSubcategoriesEnabled = value;
822 emit subcategoriesSupportChanged();
825 QSet<QString> Session::tags() const
827 return m_tags;
830 bool Session::isValidTag(const QString &tag)
832 return (!tag.trimmed().isEmpty() && !tag.contains(','));
835 bool Session::hasTag(const QString &tag) const
837 return m_tags.contains(tag);
840 bool Session::addTag(const QString &tag)
842 if (!isValidTag(tag) || hasTag(tag))
843 return false;
845 m_tags.insert(tag);
846 m_storedTags = m_tags.values();
847 emit tagAdded(tag);
848 return true;
851 bool Session::removeTag(const QString &tag)
853 if (m_tags.remove(tag))
855 for (TorrentImpl *const torrent : asConst(m_torrents))
856 torrent->removeTag(tag);
857 m_storedTags = m_tags.values();
858 emit tagRemoved(tag);
859 return true;
861 return false;
864 bool Session::isAutoTMMDisabledByDefault() const
866 return m_isAutoTMMDisabledByDefault;
869 void Session::setAutoTMMDisabledByDefault(const bool value)
871 m_isAutoTMMDisabledByDefault = value;
874 bool Session::isDisableAutoTMMWhenCategoryChanged() const
876 return m_isDisableAutoTMMWhenCategoryChanged;
879 void Session::setDisableAutoTMMWhenCategoryChanged(const bool value)
881 m_isDisableAutoTMMWhenCategoryChanged = value;
884 bool Session::isDisableAutoTMMWhenDefaultSavePathChanged() const
886 return m_isDisableAutoTMMWhenDefaultSavePathChanged;
889 void Session::setDisableAutoTMMWhenDefaultSavePathChanged(const bool value)
891 m_isDisableAutoTMMWhenDefaultSavePathChanged = value;
894 bool Session::isDisableAutoTMMWhenCategorySavePathChanged() const
896 return m_isDisableAutoTMMWhenCategorySavePathChanged;
899 void Session::setDisableAutoTMMWhenCategorySavePathChanged(const bool value)
901 m_isDisableAutoTMMWhenCategorySavePathChanged = value;
904 bool Session::isAddTorrentPaused() const
906 return m_isAddTorrentPaused;
909 void Session::setAddTorrentPaused(const bool value)
911 m_isAddTorrentPaused = value;
914 bool Session::isTrackerEnabled() const
916 return m_isTrackerEnabled;
919 void Session::setTrackerEnabled(const bool enabled)
921 if (m_isTrackerEnabled != enabled)
922 m_isTrackerEnabled = enabled;
924 // call enableTracker() unconditionally, otherwise port change won't trigger
925 // tracker restart
926 enableTracker(enabled);
929 qreal Session::globalMaxRatio() const
931 return m_globalMaxRatio;
934 // Torrents with a ratio superior to the given value will
935 // be automatically deleted
936 void Session::setGlobalMaxRatio(qreal ratio)
938 if (ratio < 0)
939 ratio = -1.;
941 if (ratio != globalMaxRatio())
943 m_globalMaxRatio = ratio;
944 updateSeedingLimitTimer();
948 int Session::globalMaxSeedingMinutes() const
950 return m_globalMaxSeedingMinutes;
953 void Session::setGlobalMaxSeedingMinutes(int minutes)
955 if (minutes < 0)
956 minutes = -1;
958 if (minutes != globalMaxSeedingMinutes())
960 m_globalMaxSeedingMinutes = minutes;
961 updateSeedingLimitTimer();
965 // Main destructor
966 Session::~Session()
968 // Do some BT related saving
969 saveResumeData();
971 // We must delete FilterParserThread
972 // before we delete lt::session
973 delete m_filterParser;
975 // We must delete PortForwarderImpl before
976 // we delete lt::session
977 delete Net::PortForwarder::instance();
979 qDebug("Deleting the session");
980 delete m_nativeSession;
982 m_ioThread->quit();
983 m_ioThread->wait();
986 void Session::initInstance()
988 if (!m_instance)
989 m_instance = new Session;
992 void Session::freeInstance()
994 delete m_instance;
995 m_instance = nullptr;
998 Session *Session::instance()
1000 return m_instance;
1003 void Session::adjustLimits()
1005 if (isQueueingSystemEnabled())
1007 lt::settings_pack settingsPack = m_nativeSession->get_settings();
1008 adjustLimits(settingsPack);
1009 m_nativeSession->apply_settings(settingsPack);
1013 void Session::applyBandwidthLimits()
1015 lt::settings_pack settingsPack = m_nativeSession->get_settings();
1016 applyBandwidthLimits(settingsPack);
1017 m_nativeSession->apply_settings(settingsPack);
1020 void Session::configure()
1022 lt::settings_pack settingsPack = m_nativeSession->get_settings();
1023 loadLTSettings(settingsPack);
1024 m_nativeSession->apply_settings(settingsPack);
1026 configureComponents();
1028 m_deferredConfigureScheduled = false;
1031 void Session::configureComponents()
1033 // This function contains components/actions that:
1034 // 1. Need to be setup at start up
1035 // 2. When deferred configure is called
1037 configurePeerClasses();
1039 if (!m_IPFilteringConfigured)
1041 if (isIPFilteringEnabled())
1042 enableIPFilter();
1043 else
1044 disableIPFilter();
1045 m_IPFilteringConfigured = true;
1048 #if defined(Q_OS_WIN)
1049 applyOSMemoryPriority();
1050 #endif
1053 void Session::initializeNativeSession()
1055 const lt::alert_category_t alertMask = lt::alert::error_notification
1056 | lt::alert::file_progress_notification
1057 | lt::alert::ip_block_notification
1058 | lt::alert::peer_notification
1059 | lt::alert::performance_warning
1060 | lt::alert::port_mapping_notification
1061 | lt::alert::status_notification
1062 | lt::alert::storage_notification
1063 | lt::alert::tracker_notification;
1064 const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
1066 lt::settings_pack pack;
1067 pack.set_int(lt::settings_pack::alert_mask, alertMask);
1068 pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
1069 pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
1070 pack.set_str(lt::settings_pack::user_agent, USER_AGENT);
1071 pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
1072 // Speed up exit
1073 pack.set_int(lt::settings_pack::auto_scrape_interval, 1200); // 20 minutes
1074 pack.set_int(lt::settings_pack::auto_scrape_min_interval, 900); // 15 minutes
1075 // libtorrent 1.1 enables UPnP & NAT-PMP by default
1076 // turn them off before `lt::session` ctor to avoid split second effects
1077 pack.set_bool(lt::settings_pack::enable_upnp, false);
1078 pack.set_bool(lt::settings_pack::enable_natpmp, false);
1080 #ifdef QBT_USES_LIBTORRENT2
1081 // preserve the same behavior as in earlier libtorrent versions
1082 pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
1083 #endif
1085 loadLTSettings(pack);
1086 lt::session_params sessionParams {pack, {}};
1087 #ifdef QBT_USES_LIBTORRENT2
1088 sessionParams.disk_io_constructor = customDiskIOConstructor;
1089 #endif
1090 m_nativeSession = new lt::session {sessionParams};
1092 LogMsg(tr("Peer ID: ") + QString::fromStdString(peerId));
1093 LogMsg(tr("HTTP User-Agent is '%1'").arg(USER_AGENT));
1094 LogMsg(tr("DHT support [%1]").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1095 LogMsg(tr("Local Peer Discovery support [%1]").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1096 LogMsg(tr("PeX support [%1]").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1097 LogMsg(tr("Anonymous mode [%1]").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1098 LogMsg(tr("Encryption support [%1]").arg((encryption() == 0) ? tr("ON") :
1099 ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO);
1101 m_nativeSession->set_alert_notify([this]()
1103 QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection);
1106 // Enabling plugins
1107 m_nativeSession->add_extension(&lt::create_smart_ban_plugin);
1108 m_nativeSession->add_extension(&lt::create_ut_metadata_plugin);
1109 if (isPeXEnabled())
1110 m_nativeSession->add_extension(&lt::create_ut_pex_plugin);
1112 m_nativeSession->add_extension(std::make_shared<NativeSessionExtension>());
1115 void Session::processBannedIPs(lt::ip_filter &filter)
1117 // First, import current filter
1118 for (const QString &ip : asConst(m_bannedIPs.get()))
1120 lt::error_code ec;
1121 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1122 Q_ASSERT(!ec);
1123 if (!ec)
1124 filter.add_rule(addr, addr, lt::ip_filter::blocked);
1128 void Session::adjustLimits(lt::settings_pack &settingsPack) const
1130 // Internally increase the queue limits to ensure that the magnet is started
1131 const int maxDownloads = maxActiveDownloads();
1132 const int maxActive = maxActiveTorrents();
1134 settingsPack.set_int(lt::settings_pack::active_downloads
1135 , maxDownloads > -1 ? maxDownloads + m_extraLimit : maxDownloads);
1136 settingsPack.set_int(lt::settings_pack::active_limit
1137 , maxActive > -1 ? maxActive + m_extraLimit : maxActive);
1140 void Session::applyBandwidthLimits(lt::settings_pack &settingsPack) const
1142 const bool altSpeedLimitEnabled = isAltGlobalSpeedLimitEnabled();
1143 settingsPack.set_int(lt::settings_pack::download_rate_limit, altSpeedLimitEnabled ? altGlobalDownloadSpeedLimit() : globalDownloadSpeedLimit());
1144 settingsPack.set_int(lt::settings_pack::upload_rate_limit, altSpeedLimitEnabled ? altGlobalUploadSpeedLimit() : globalUploadSpeedLimit());
1147 void Session::initMetrics()
1149 const auto findMetricIndex = [](const char *name) -> int
1151 const int index = lt::find_metric_idx(name);
1152 Q_ASSERT(index >= 0);
1153 return index;
1156 // TODO: switch to "designated initializers" in C++20
1157 m_metricIndices.net.hasIncomingConnections = findMetricIndex("net.has_incoming_connections");
1158 m_metricIndices.net.sentPayloadBytes = findMetricIndex("net.sent_payload_bytes");
1159 m_metricIndices.net.recvPayloadBytes = findMetricIndex("net.recv_payload_bytes");
1160 m_metricIndices.net.sentBytes = findMetricIndex("net.sent_bytes");
1161 m_metricIndices.net.recvBytes = findMetricIndex("net.recv_bytes");
1162 m_metricIndices.net.sentIPOverheadBytes = findMetricIndex("net.sent_ip_overhead_bytes");
1163 m_metricIndices.net.recvIPOverheadBytes = findMetricIndex("net.recv_ip_overhead_bytes");
1164 m_metricIndices.net.sentTrackerBytes = findMetricIndex("net.sent_tracker_bytes");
1165 m_metricIndices.net.recvTrackerBytes = findMetricIndex("net.recv_tracker_bytes");
1166 m_metricIndices.net.recvRedundantBytes = findMetricIndex("net.recv_redundant_bytes");
1167 m_metricIndices.net.recvFailedBytes = findMetricIndex("net.recv_failed_bytes");
1169 m_metricIndices.peer.numPeersConnected = findMetricIndex("peer.num_peers_connected");
1170 m_metricIndices.peer.numPeersDownDisk = findMetricIndex("peer.num_peers_down_disk");
1171 m_metricIndices.peer.numPeersUpDisk = findMetricIndex("peer.num_peers_up_disk");
1173 m_metricIndices.dht.dhtBytesIn = findMetricIndex("dht.dht_bytes_in");
1174 m_metricIndices.dht.dhtBytesOut = findMetricIndex("dht.dht_bytes_out");
1175 m_metricIndices.dht.dhtNodes = findMetricIndex("dht.dht_nodes");
1177 m_metricIndices.disk.diskBlocksInUse = findMetricIndex("disk.disk_blocks_in_use");
1178 m_metricIndices.disk.numBlocksRead = findMetricIndex("disk.num_blocks_read");
1179 #ifndef QBT_USES_LIBTORRENT2
1180 m_metricIndices.disk.numBlocksCacheHits = findMetricIndex("disk.num_blocks_cache_hits");
1181 #endif
1182 m_metricIndices.disk.writeJobs = findMetricIndex("disk.num_write_ops");
1183 m_metricIndices.disk.readJobs = findMetricIndex("disk.num_read_ops");
1184 m_metricIndices.disk.hashJobs = findMetricIndex("disk.num_blocks_hashed");
1185 m_metricIndices.disk.queuedDiskJobs = findMetricIndex("disk.queued_disk_jobs");
1186 m_metricIndices.disk.diskJobTime = findMetricIndex("disk.disk_job_time");
1189 void Session::loadLTSettings(lt::settings_pack &settingsPack)
1191 settingsPack.set_int(lt::settings_pack::connection_speed, connectionSpeed());
1193 // from libtorrent doc:
1194 // It will not take affect until the listen_interfaces settings is updated
1195 settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
1197 configureNetworkInterfaces(settingsPack);
1198 applyBandwidthLimits(settingsPack);
1200 // The most secure, rc4 only so that all streams are encrypted
1201 settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
1202 settingsPack.set_bool(lt::settings_pack::prefer_rc4, true);
1203 switch (encryption())
1205 case 0: // Enabled
1206 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_enabled);
1207 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_enabled);
1208 break;
1209 case 1: // Forced
1210 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_forced);
1211 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_forced);
1212 break;
1213 default: // Disabled
1214 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_disabled);
1215 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_disabled);
1218 // proxy
1219 const auto proxyManager = Net::ProxyConfigurationManager::instance();
1220 const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
1222 switch (proxyConfig.type)
1224 case Net::ProxyType::HTTP:
1225 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http);
1226 break;
1227 case Net::ProxyType::HTTP_PW:
1228 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http_pw);
1229 break;
1230 case Net::ProxyType::SOCKS4:
1231 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks4);
1232 break;
1233 case Net::ProxyType::SOCKS5:
1234 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
1235 break;
1236 case Net::ProxyType::SOCKS5_PW:
1237 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5_pw);
1238 break;
1239 case Net::ProxyType::None:
1240 default:
1241 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
1244 if (proxyConfig.type != Net::ProxyType::None)
1246 settingsPack.set_str(lt::settings_pack::proxy_hostname, proxyConfig.ip.toStdString());
1247 settingsPack.set_int(lt::settings_pack::proxy_port, proxyConfig.port);
1249 if (proxyManager->isAuthenticationRequired())
1251 settingsPack.set_str(lt::settings_pack::proxy_username, proxyConfig.username.toStdString());
1252 settingsPack.set_str(lt::settings_pack::proxy_password, proxyConfig.password.toStdString());
1255 settingsPack.set_bool(lt::settings_pack::proxy_peer_connections, isProxyPeerConnectionsEnabled());
1258 settingsPack.set_bool(lt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
1259 settingsPack.set_bool(lt::settings_pack::announce_to_all_tiers, announceToAllTiers());
1261 settingsPack.set_int(lt::settings_pack::peer_turnover, peerTurnover());
1262 settingsPack.set_int(lt::settings_pack::peer_turnover_cutoff, peerTurnoverCutoff());
1263 settingsPack.set_int(lt::settings_pack::peer_turnover_interval, peerTurnoverInterval());
1265 settingsPack.set_int(lt::settings_pack::aio_threads, asyncIOThreads());
1266 #ifdef QBT_USES_LIBTORRENT2
1267 settingsPack.set_int(lt::settings_pack::hashing_threads, hashingThreads());
1268 #endif
1269 settingsPack.set_int(lt::settings_pack::file_pool_size, filePoolSize());
1271 const int checkingMemUsageSize = checkingMemUsage() * 64;
1272 settingsPack.set_int(lt::settings_pack::checking_mem_usage, checkingMemUsageSize);
1274 #ifndef QBT_USES_LIBTORRENT2
1275 const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
1276 settingsPack.set_int(lt::settings_pack::cache_size, cacheSize);
1277 settingsPack.set_int(lt::settings_pack::cache_expiry, diskCacheTTL());
1278 #endif
1280 lt::settings_pack::io_buffer_mode_t mode = useOSCache() ? lt::settings_pack::enable_os_cache
1281 : lt::settings_pack::disable_os_cache;
1282 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, mode);
1283 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, mode);
1285 #ifndef QBT_USES_LIBTORRENT2
1286 settingsPack.set_bool(lt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
1287 settingsPack.set_bool(lt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
1288 #endif
1290 settingsPack.set_bool(lt::settings_pack::piece_extent_affinity, usePieceExtentAffinity());
1292 settingsPack.set_int(lt::settings_pack::suggest_mode, isSuggestModeEnabled()
1293 ? lt::settings_pack::suggest_read_cache : lt::settings_pack::no_piece_suggestions);
1295 settingsPack.set_int(lt::settings_pack::send_buffer_watermark, sendBufferWatermark() * 1024);
1296 settingsPack.set_int(lt::settings_pack::send_buffer_low_watermark, sendBufferLowWatermark() * 1024);
1297 settingsPack.set_int(lt::settings_pack::send_buffer_watermark_factor, sendBufferWatermarkFactor());
1299 settingsPack.set_bool(lt::settings_pack::anonymous_mode, isAnonymousModeEnabled());
1301 // Queueing System
1302 if (isQueueingSystemEnabled())
1304 adjustLimits(settingsPack);
1306 settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
1307 settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
1308 settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
1309 settingsPack.set_int(lt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
1310 settingsPack.set_int(lt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
1312 else
1314 settingsPack.set_int(lt::settings_pack::active_downloads, -1);
1315 settingsPack.set_int(lt::settings_pack::active_seeds, -1);
1316 settingsPack.set_int(lt::settings_pack::active_limit, -1);
1318 settingsPack.set_int(lt::settings_pack::active_tracker_limit, -1);
1319 settingsPack.set_int(lt::settings_pack::active_dht_limit, -1);
1320 settingsPack.set_int(lt::settings_pack::active_lsd_limit, -1);
1321 settingsPack.set_int(lt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
1323 // Outgoing ports
1324 settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
1325 settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1);
1326 // UPnP lease duration
1327 settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
1328 // Type of service
1329 settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
1330 // Include overhead in transfer limits
1331 settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
1332 // IP address to announce to trackers
1333 settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
1334 // Max concurrent HTTP announces
1335 settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
1336 // Stop tracker timeout
1337 settingsPack.set_int(lt::settings_pack::stop_tracker_timeout, stopTrackerTimeout());
1338 // * Max connections limit
1339 settingsPack.set_int(lt::settings_pack::connections_limit, maxConnections());
1340 // * Global max upload slots
1341 settingsPack.set_int(lt::settings_pack::unchoke_slots_limit, maxUploads());
1342 // uTP
1343 switch (btProtocol())
1345 case BTProtocol::Both:
1346 default:
1347 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1348 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1349 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1350 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1351 break;
1353 case BTProtocol::TCP:
1354 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1355 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1356 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, false);
1357 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
1358 break;
1360 case BTProtocol::UTP:
1361 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, false);
1362 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, false);
1363 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1364 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1365 break;
1368 switch (utpMixedMode())
1370 case MixedModeAlgorithm::TCP:
1371 default:
1372 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::prefer_tcp);
1373 break;
1374 case MixedModeAlgorithm::Proportional:
1375 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::peer_proportional);
1376 break;
1379 settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
1381 settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
1383 settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
1385 settingsPack.set_bool(lt::settings_pack::ssrf_mitigation, isSSRFMitigationEnabled());
1387 settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
1389 settingsPack.set_bool(lt::settings_pack::apply_ip_filter_to_trackers, isTrackerFilteringEnabled());
1391 settingsPack.set_bool(lt::settings_pack::enable_dht, isDHTEnabled());
1392 if (isDHTEnabled())
1393 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");
1394 settingsPack.set_bool(lt::settings_pack::enable_lsd, isLSDEnabled());
1396 switch (chokingAlgorithm())
1398 case ChokingAlgorithm::FixedSlots:
1399 default:
1400 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::fixed_slots_choker);
1401 break;
1402 case ChokingAlgorithm::RateBased:
1403 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::rate_based_choker);
1404 break;
1407 switch (seedChokingAlgorithm())
1409 case SeedChokingAlgorithm::RoundRobin:
1410 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::round_robin);
1411 break;
1412 case SeedChokingAlgorithm::FastestUpload:
1413 default:
1414 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::fastest_upload);
1415 break;
1416 case SeedChokingAlgorithm::AntiLeech:
1417 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
1418 break;
1422 void Session::configureNetworkInterfaces(lt::settings_pack &settingsPack)
1424 if (m_listenInterfaceConfigured)
1425 return;
1427 if (port() > 0) // user has specified port number
1428 settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);
1430 QStringList endpoints;
1431 QStringList outgoingInterfaces;
1432 const QString portString = ':' + QString::number(port());
1434 for (const QString &ip : asConst(getListeningIPs()))
1436 const QHostAddress addr {ip};
1437 if (!addr.isNull())
1439 const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
1440 const QString ip = isIPv6
1441 ? Utils::Net::canonicalIPv6Addr(addr).toString()
1442 : addr.toString();
1444 endpoints << ((isIPv6 ? ('[' + ip + ']') : ip) + portString);
1446 if ((ip != QLatin1String("0.0.0.0")) && (ip != QLatin1String("::")))
1447 outgoingInterfaces << ip;
1449 else
1451 // ip holds an interface name
1452 #ifdef Q_OS_WIN
1453 // On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
1454 // the interface's LUID and not the GUID.
1455 // Libtorrent expects GUIDs for the 'listen_interfaces' setting.
1456 const QString guid = convertIfaceNameToGuid(ip);
1457 if (!guid.isEmpty())
1459 endpoints << (guid + portString);
1460 outgoingInterfaces << guid;
1462 else
1464 LogMsg(tr("Could not get GUID of network interface: %1").arg(ip) , Log::WARNING);
1465 // Since we can't get the GUID, we'll pass the interface name instead.
1466 // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
1467 endpoints << (ip + portString);
1468 outgoingInterfaces << ip;
1470 #else
1471 endpoints << (ip + portString);
1472 outgoingInterfaces << ip;
1473 #endif
1477 const QString finalEndpoints = endpoints.join(',');
1478 settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
1479 LogMsg(tr("Trying to listen on: %1", "e.g: Trying to listen on: 192.168.0.1:6881")
1480 .arg(finalEndpoints), Log::INFO);
1482 settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(',').toStdString());
1483 m_listenInterfaceConfigured = true;
1486 void Session::configurePeerClasses()
1488 lt::ip_filter f;
1489 // lt::make_address("255.255.255.255") crashes on some people's systems
1490 // so instead we use address_v4::broadcast()
1491 // Proactively do the same for 0.0.0.0 and address_v4::any()
1492 f.add_rule(lt::address_v4::any()
1493 , lt::address_v4::broadcast()
1494 , 1 << toLTUnderlyingType(lt::session::global_peer_class_id));
1496 // IPv6 may not be available on OS and the parsing
1497 // would result in an exception -> abnormal program termination
1498 // Affects Windows XP
1501 f.add_rule(lt::address_v6::any()
1502 , lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1503 , 1 << toLTUnderlyingType(lt::session::global_peer_class_id));
1505 catch (const std::exception &) {}
1507 if (ignoreLimitsOnLAN())
1509 // local networks
1510 f.add_rule(lt::make_address("10.0.0.0")
1511 , lt::make_address("10.255.255.255")
1512 , 1 << toLTUnderlyingType(lt::session::local_peer_class_id));
1513 f.add_rule(lt::make_address("172.16.0.0")
1514 , lt::make_address("172.31.255.255")
1515 , 1 << toLTUnderlyingType(lt::session::local_peer_class_id));
1516 f.add_rule(lt::make_address("192.168.0.0")
1517 , lt::make_address("192.168.255.255")
1518 , 1 << toLTUnderlyingType(lt::session::local_peer_class_id));
1519 // link local
1520 f.add_rule(lt::make_address("169.254.0.0")
1521 , lt::make_address("169.254.255.255")
1522 , 1 << toLTUnderlyingType(lt::session::local_peer_class_id));
1523 // loopback
1524 f.add_rule(lt::make_address("127.0.0.0")
1525 , lt::make_address("127.255.255.255")
1526 , 1 << toLTUnderlyingType(lt::session::local_peer_class_id));
1528 // IPv6 may not be available on OS and the parsing
1529 // would result in an exception -> abnormal program termination
1530 // Affects Windows XP
1533 // link local
1534 f.add_rule(lt::make_address("fe80::")
1535 , lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1536 , 1 << toLTUnderlyingType(lt::session::local_peer_class_id));
1537 // unique local addresses
1538 f.add_rule(lt::make_address("fc00::")
1539 , lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1540 , 1 << toLTUnderlyingType(lt::session::local_peer_class_id));
1541 // loopback
1542 f.add_rule(lt::address_v6::loopback()
1543 , lt::address_v6::loopback()
1544 , 1 << toLTUnderlyingType(lt::session::local_peer_class_id));
1546 catch (const std::exception &) {}
1548 m_nativeSession->set_peer_class_filter(f);
1550 lt::peer_class_type_filter peerClassTypeFilter;
1551 peerClassTypeFilter.add(lt::peer_class_type_filter::tcp_socket, lt::session::tcp_peer_class_id);
1552 peerClassTypeFilter.add(lt::peer_class_type_filter::ssl_tcp_socket, lt::session::tcp_peer_class_id);
1553 peerClassTypeFilter.add(lt::peer_class_type_filter::i2p_socket, lt::session::tcp_peer_class_id);
1554 if (!isUTPRateLimited())
1556 peerClassTypeFilter.disallow(lt::peer_class_type_filter::utp_socket
1557 , lt::session::global_peer_class_id);
1558 peerClassTypeFilter.disallow(lt::peer_class_type_filter::ssl_utp_socket
1559 , lt::session::global_peer_class_id);
1561 m_nativeSession->set_peer_class_type_filter(peerClassTypeFilter);
1564 void Session::enableTracker(const bool enable)
1566 if (enable)
1568 if (!m_tracker)
1569 m_tracker = new Tracker(this);
1571 m_tracker->start();
1573 else
1575 delete m_tracker;
1579 void Session::enableBandwidthScheduler()
1581 if (!m_bwScheduler)
1583 m_bwScheduler = new BandwidthScheduler(this);
1584 connect(m_bwScheduler.data(), &BandwidthScheduler::bandwidthLimitRequested
1585 , this, &Session::setAltGlobalSpeedLimitEnabled);
1587 m_bwScheduler->start();
1590 void Session::populateAdditionalTrackers()
1592 m_additionalTrackerList.clear();
1594 const QString trackers = additionalTrackers();
1595 for (QStringView tracker : asConst(QStringView(trackers).split(u'\n')))
1597 tracker = tracker.trimmed();
1598 if (!tracker.isEmpty())
1599 m_additionalTrackerList.append({tracker.toString()});
1603 void Session::processShareLimits()
1605 qDebug("Processing share limits...");
1607 // We shouldn't iterate over `m_torrents` in the loop below
1608 // since `deleteTorrent()` modifies it indirectly
1609 const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
1610 for (TorrentImpl *const torrent : torrents)
1612 if (torrent->isSeed() && !torrent->isForced())
1614 if (torrent->ratioLimit() != Torrent::NO_RATIO_LIMIT)
1616 const qreal ratio = torrent->realRatio();
1617 qreal ratioLimit = torrent->ratioLimit();
1618 if (ratioLimit == Torrent::USE_GLOBAL_RATIO)
1619 // If Global Max Ratio is really set...
1620 ratioLimit = globalMaxRatio();
1622 if (ratioLimit >= 0)
1624 qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
1626 if ((ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
1628 if (m_maxRatioAction == Remove)
1630 LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
1631 deleteTorrent(torrent->id());
1633 else if (m_maxRatioAction == DeleteFiles)
1635 LogMsg(tr("'%1' reached the maximum ratio you set. Removed torrent and its files.").arg(torrent->name()));
1636 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
1638 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
1640 torrent->pause();
1641 LogMsg(tr("'%1' reached the maximum ratio you set. Paused.").arg(torrent->name()));
1643 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
1645 torrent->setSuperSeeding(true);
1646 LogMsg(tr("'%1' reached the maximum ratio you set. Enabled super seeding for it.").arg(torrent->name()));
1648 continue;
1653 if (torrent->seedingTimeLimit() != Torrent::NO_SEEDING_TIME_LIMIT)
1655 const qlonglong seedingTimeInMinutes = torrent->seedingTime() / 60;
1656 int seedingTimeLimit = torrent->seedingTimeLimit();
1657 if (seedingTimeLimit == Torrent::USE_GLOBAL_SEEDING_TIME)
1659 // If Global Seeding Time Limit is really set...
1660 seedingTimeLimit = globalMaxSeedingMinutes();
1663 if (seedingTimeLimit >= 0)
1665 if ((seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
1667 if (m_maxRatioAction == Remove)
1669 LogMsg(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
1670 deleteTorrent(torrent->id());
1672 else if (m_maxRatioAction == DeleteFiles)
1674 LogMsg(tr("'%1' reached the maximum seeding time you set. Removed torrent and its files.").arg(torrent->name()));
1675 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
1677 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
1679 torrent->pause();
1680 LogMsg(tr("'%1' reached the maximum seeding time you set. Paused.").arg(torrent->name()));
1682 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
1684 torrent->setSuperSeeding(true);
1685 LogMsg(tr("'%1' reached the maximum seeding time you set. Enabled super seeding for it.").arg(torrent->name()));
1694 // Add to BitTorrent session the downloaded torrent file
1695 void Session::handleDownloadFinished(const Net::DownloadResult &result)
1697 switch (result.status)
1699 case Net::DownloadStatus::Success:
1700 emit downloadFromUrlFinished(result.url);
1701 addTorrent(TorrentInfo::load(result.data), m_downloadedTorrents.take(result.url));
1702 break;
1703 case Net::DownloadStatus::RedirectedToMagnet:
1704 emit downloadFromUrlFinished(result.url);
1705 addTorrent(MagnetUri {result.magnet}, m_downloadedTorrents.take(result.url));
1706 break;
1707 default:
1708 emit downloadFromUrlFailed(result.url, result.errorString);
1712 void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames)
1714 TorrentImpl *torrent = m_torrents.value(id);
1715 if (torrent)
1717 torrent->fileSearchFinished(savePath, fileNames);
1718 return;
1721 const auto loadingTorrentsIter = m_loadingTorrents.find(id);
1722 if (loadingTorrentsIter != m_loadingTorrents.end())
1724 LoadTorrentParams params = loadingTorrentsIter.value();
1725 m_loadingTorrents.erase(loadingTorrentsIter);
1727 lt::add_torrent_params &p = params.ltAddTorrentParams;
1729 p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
1730 for (int i = 0; i < fileNames.size(); ++i)
1731 p.renamed_files[lt::file_index_t {i}] = fileNames[i].toStdString();
1733 loadTorrent(params);
1737 // Return the torrent handle, given its hash
1738 Torrent *Session::findTorrent(const TorrentID &id) const
1740 return m_torrents.value(id);
1743 bool Session::hasActiveTorrents() const
1745 return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentImpl *torrent)
1747 return TorrentFilter::ActiveTorrent.match(torrent);
1751 bool Session::hasUnfinishedTorrents() const
1753 return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
1755 return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored());
1759 bool Session::hasRunningSeed() const
1761 return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
1763 return (torrent->isSeed() && !torrent->isPaused());
1767 void Session::banIP(const QString &ip)
1769 QStringList bannedIPs = m_bannedIPs;
1770 if (!bannedIPs.contains(ip))
1772 lt::ip_filter filter = m_nativeSession->get_ip_filter();
1773 lt::error_code ec;
1774 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1775 Q_ASSERT(!ec);
1776 if (ec) return;
1777 filter.add_rule(addr, addr, lt::ip_filter::blocked);
1778 m_nativeSession->set_ip_filter(filter);
1780 bannedIPs << ip;
1781 bannedIPs.sort();
1782 m_bannedIPs = bannedIPs;
1786 // Delete a torrent from the session, given its hash
1787 // and from the disk, if the corresponding deleteOption is chosen
1788 bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
1790 TorrentImpl *const torrent = m_torrents.take(id);
1791 if (!torrent) return false;
1793 qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
1794 emit torrentAboutToBeRemoved(torrent);
1796 // Remove it from session
1797 if (deleteOption == DeleteTorrent)
1799 m_removingTorrents[torrent->id()] = {torrent->name(), "", deleteOption};
1801 const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
1802 const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
1803 , [&nativeHandle](const MoveStorageJob &job)
1805 return job.torrentHandle == nativeHandle;
1807 if (iter != m_moveStorageQueue.end())
1809 // We shouldn't actually remove torrent until existing "move storage jobs" are done
1810 torrentQueuePositionBottom(nativeHandle);
1811 nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
1812 nativeHandle.pause();
1814 else
1816 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
1819 else
1821 QString rootPath = torrent->rootPath(true);
1822 if (!rootPath.isEmpty() && torrent->useTempPath())
1824 // torrent without root folder still has it in its temporary save path
1825 rootPath = torrent->actualStorageLocation();
1828 m_removingTorrents[torrent->id()] = {torrent->name(), rootPath, deleteOption};
1830 if (m_moveStorageQueue.size() > 1)
1832 // Delete "move storage job" for the deleted torrent
1833 // (note: we shouldn't delete active job)
1834 const auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
1835 , [torrent](const MoveStorageJob &job)
1837 return job.torrentHandle == torrent->nativeHandle();
1839 if (iter != m_moveStorageQueue.end())
1840 m_moveStorageQueue.erase(iter);
1843 m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
1846 // Remove it from torrent resume directory
1847 m_resumeDataStorage->remove(torrent->id());
1849 delete torrent;
1850 return true;
1853 bool Session::cancelDownloadMetadata(const TorrentID &id)
1855 const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
1856 if (downloadedMetadataIter == m_downloadedMetadata.end()) return false;
1858 m_downloadedMetadata.erase(downloadedMetadataIter);
1859 --m_extraLimit;
1860 adjustLimits();
1861 m_nativeSession->remove_torrent(m_nativeSession->find_torrent(id), lt::session::delete_files);
1862 return true;
1865 void Session::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
1867 using ElementType = std::pair<int, const TorrentImpl *>;
1868 std::priority_queue<ElementType
1869 , std::vector<ElementType>
1870 , std::greater<ElementType>> torrentQueue;
1872 // Sort torrents by queue position
1873 for (const TorrentID &id : ids)
1875 const TorrentImpl *torrent = m_torrents.value(id);
1876 if (!torrent) continue;
1877 if (const int position = torrent->queuePosition(); position >= 0)
1878 torrentQueue.emplace(position, torrent);
1881 // Increase torrents queue position (starting with the one in the highest queue position)
1882 while (!torrentQueue.empty())
1884 const TorrentImpl *torrent = torrentQueue.top().second;
1885 torrentQueuePositionUp(torrent->nativeHandle());
1886 torrentQueue.pop();
1889 saveTorrentsQueue();
1892 void Session::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
1894 using ElementType = std::pair<int, const TorrentImpl *>;
1895 std::priority_queue<ElementType> torrentQueue;
1897 // Sort torrents by queue position
1898 for (const TorrentID &id : ids)
1900 const TorrentImpl *torrent = m_torrents.value(id);
1901 if (!torrent) continue;
1902 if (const int position = torrent->queuePosition(); position >= 0)
1903 torrentQueue.emplace(position, torrent);
1906 // Decrease torrents queue position (starting with the one in the lowest queue position)
1907 while (!torrentQueue.empty())
1909 const TorrentImpl *torrent = torrentQueue.top().second;
1910 torrentQueuePositionDown(torrent->nativeHandle());
1911 torrentQueue.pop();
1914 for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i)
1915 torrentQueuePositionBottom(m_nativeSession->find_torrent(*i));
1917 saveTorrentsQueue();
1920 void Session::topTorrentsQueuePos(const QVector<TorrentID> &ids)
1922 using ElementType = std::pair<int, const TorrentImpl *>;
1923 std::priority_queue<ElementType> torrentQueue;
1925 // Sort torrents by queue position
1926 for (const TorrentID &id : ids)
1928 const TorrentImpl *torrent = m_torrents.value(id);
1929 if (!torrent) continue;
1930 if (const int position = torrent->queuePosition(); position >= 0)
1931 torrentQueue.emplace(position, torrent);
1934 // Top torrents queue position (starting with the one in the lowest queue position)
1935 while (!torrentQueue.empty())
1937 const TorrentImpl *torrent = torrentQueue.top().second;
1938 torrentQueuePositionTop(torrent->nativeHandle());
1939 torrentQueue.pop();
1942 saveTorrentsQueue();
1945 void Session::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
1947 using ElementType = std::pair<int, const TorrentImpl *>;
1948 std::priority_queue<ElementType
1949 , std::vector<ElementType>
1950 , std::greater<ElementType>> torrentQueue;
1952 // Sort torrents by queue position
1953 for (const TorrentID &id : ids)
1955 const TorrentImpl *torrent = m_torrents.value(id);
1956 if (!torrent) continue;
1957 if (const int position = torrent->queuePosition(); position >= 0)
1958 torrentQueue.emplace(position, torrent);
1961 // Bottom torrents queue position (starting with the one in the highest queue position)
1962 while (!torrentQueue.empty())
1964 const TorrentImpl *torrent = torrentQueue.top().second;
1965 torrentQueuePositionBottom(torrent->nativeHandle());
1966 torrentQueue.pop();
1969 for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i)
1970 torrentQueuePositionBottom(m_nativeSession->find_torrent(*i));
1972 saveTorrentsQueue();
1975 void Session::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
1977 if (m_needSaveResumeDataTorrents.empty())
1979 QMetaObject::invokeMethod(this, [this]()
1981 for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
1983 TorrentImpl *torrent = m_torrents.value(torrentID);
1984 if (torrent)
1985 torrent->saveResumeData();
1987 m_needSaveResumeDataTorrents.clear();
1988 }, Qt::QueuedConnection);
1991 m_needSaveResumeDataTorrents.insert(torrent->id());
1994 void Session::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
1996 qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
1997 ++m_numResumeData;
2000 QVector<Torrent *> Session::torrents() const
2002 QVector<Torrent *> result;
2003 result.reserve(m_torrents.size());
2004 for (TorrentImpl *torrent : asConst(m_torrents))
2005 result << torrent;
2007 return result;
2010 bool Session::addTorrent(const QString &source, const AddTorrentParams &params)
2012 // `source`: .torrent file path/url or magnet uri
2014 if (Net::DownloadManager::hasSupportedScheme(source))
2016 LogMsg(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source));
2017 // Launch downloader
2018 Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE)
2019 , this, &Session::handleDownloadFinished);
2020 m_downloadedTorrents[source] = params;
2021 return true;
2024 const MagnetUri magnetUri {source};
2025 if (magnetUri.isValid())
2026 return addTorrent(magnetUri, params);
2028 TorrentFileGuard guard {source};
2029 if (addTorrent(TorrentInfo::loadFromFile(source), params))
2031 guard.markAsAddedToSession();
2032 return true;
2035 return false;
2038 bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &params)
2040 if (!magnetUri.isValid()) return false;
2042 return addTorrent_impl(magnetUri, params);
2045 bool Session::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams &params)
2047 if (!torrentInfo.isValid()) return false;
2049 return addTorrent_impl(torrentInfo, params);
2052 LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorrentParams)
2054 LoadTorrentParams loadTorrentParams;
2056 loadTorrentParams.name = addTorrentParams.name;
2057 loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
2058 loadTorrentParams.hasSeedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
2059 loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
2060 loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged);
2061 loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused());
2062 loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
2063 loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
2065 const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!isAutoTMMDisabledByDefault());
2066 if (useAutoTMM)
2067 loadTorrentParams.savePath = "";
2068 else if (addTorrentParams.savePath.isEmpty())
2069 loadTorrentParams.savePath = defaultSavePath();
2070 else if (QDir(addTorrentParams.savePath).isRelative())
2071 loadTorrentParams.savePath = QDir(defaultSavePath()).absoluteFilePath(addTorrentParams.savePath);
2072 else
2073 loadTorrentParams.savePath = normalizePath(addTorrentParams.savePath);
2075 const QString category = addTorrentParams.category;
2076 if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
2077 loadTorrentParams.category = "";
2078 else
2079 loadTorrentParams.category = addTorrentParams.category;
2081 for (const QString &tag : addTorrentParams.tags)
2083 if (hasTag(tag) || addTag(tag))
2084 loadTorrentParams.tags.insert(tag);
2087 return loadTorrentParams;
2090 // Add a torrent to the BitTorrent session
2091 bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams)
2093 const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
2094 TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
2095 const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source));
2096 const auto id = TorrentID::fromInfoHash(hasMetadata ? metadata.infoHash() : magnetUri.infoHash());
2098 // It looks illogical that we don't just use an existing handle,
2099 // but as previous experience has shown, it actually creates unnecessary
2100 // problems and unwanted behavior due to the fact that it was originally
2101 // added with parameters other than those provided by the user.
2102 cancelDownloadMetadata(id);
2104 // We should not add the torrent if it is already
2105 // processed or is pending to add to session
2106 if (m_loadingTorrents.contains(id))
2107 return false;
2109 TorrentImpl *const torrent = m_torrents.value(id);
2110 if (torrent)
2111 { // a duplicate torrent is added
2112 if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
2113 return false;
2115 // merge trackers and web seeds
2116 torrent->addTrackers(hasMetadata ? metadata.trackers() : magnetUri.trackers());
2117 torrent->addUrlSeeds(hasMetadata ? metadata.urlSeeds() : magnetUri.urlSeeds());
2118 return true;
2121 LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams);
2122 lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
2124 bool isFindingIncompleteFiles = false;
2126 // If empty then Automatic mode, otherwise Manual mode
2127 const QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
2128 if (hasMetadata)
2130 metadata.setContentLayout(loadTorrentParams.contentLayout);
2132 if (!loadTorrentParams.hasSeedStatus)
2134 findIncompleteFiles(metadata, actualSavePath);
2135 isFindingIncompleteFiles = true;
2138 // if torrent name wasn't explicitly set we handle the case of
2139 // initial renaming of torrent content and rename torrent accordingly
2140 if (loadTorrentParams.name.isEmpty())
2142 QString contentName = metadata.rootFolder();
2143 if (contentName.isEmpty() && (metadata.filesCount() == 1))
2144 contentName = metadata.fileName(0);
2146 if (!contentName.isEmpty() && (contentName != metadata.name()))
2147 loadTorrentParams.name = contentName;
2150 Q_ASSERT(p.file_priorities.empty());
2151 if (addTorrentParams.filePriorities.empty())
2153 // Use qBittorrent default priority rather than libtorrent's (4)
2154 p.file_priorities = std::vector(metadata.filesCount(), static_cast<lt::download_priority_t>(
2155 static_cast<lt::download_priority_t::underlying_type>(DownloadPriority::Normal)));
2157 else
2159 std::transform(addTorrentParams.filePriorities.cbegin(), addTorrentParams.filePriorities.cend()
2160 , std::back_inserter(p.file_priorities), [](const DownloadPriority priority)
2162 return static_cast<lt::download_priority_t>(
2163 static_cast<lt::download_priority_t::underlying_type>(priority));
2167 p.ti = metadata.nativeInfo();
2169 else
2171 p = magnetUri.addTorrentParams();
2173 if (loadTorrentParams.name.isEmpty() && !p.name.empty())
2174 loadTorrentParams.name = QString::fromStdString(p.name);
2177 p.save_path = Utils::Fs::toNativePath(actualSavePath).toStdString();
2179 p.upload_limit = addTorrentParams.uploadLimit;
2180 p.download_limit = addTorrentParams.downloadLimit;
2182 // Preallocation mode
2183 p.storage_mode = isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse;
2185 if (addTorrentParams.sequential)
2186 p.flags |= lt::torrent_flags::sequential_download;
2187 else
2188 p.flags &= ~lt::torrent_flags::sequential_download;
2190 // Seeding mode
2191 // Skip checking and directly start seeding
2192 if (addTorrentParams.skipChecking)
2193 p.flags |= lt::torrent_flags::seed_mode;
2194 else
2195 p.flags &= ~lt::torrent_flags::seed_mode;
2197 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::AutoManaged))
2198 p.flags |= lt::torrent_flags::paused;
2199 else
2200 p.flags &= ~lt::torrent_flags::paused;
2201 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::Forced))
2202 p.flags &= ~lt::torrent_flags::auto_managed;
2203 else
2204 p.flags |= lt::torrent_flags::auto_managed;
2206 if (!isFindingIncompleteFiles)
2207 return loadTorrent(loadTorrentParams);
2209 m_loadingTorrents.insert(id, loadTorrentParams);
2210 return true;
2213 // Add a torrent to the BitTorrent session
2214 bool Session::loadTorrent(LoadTorrentParams params)
2216 lt::add_torrent_params &p = params.ltAddTorrentParams;
2218 #ifndef QBT_USES_LIBTORRENT2
2219 p.storage = customStorageConstructor;
2220 #endif
2221 // Limits
2222 p.max_connections = maxConnectionsPerTorrent();
2223 p.max_uploads = maxUploadsPerTorrent();
2225 const bool hasMetadata = (p.ti && p.ti->is_valid());
2226 #ifdef QBT_USES_LIBTORRENT2
2227 const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hashes() : p.info_hashes);
2228 #else
2229 const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hash() : p.info_hash);
2230 #endif
2231 m_loadingTorrents.insert(id, params);
2233 // Adding torrent to BitTorrent session
2234 m_nativeSession->async_add_torrent(p);
2236 return true;
2239 void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const
2241 const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
2242 const QStringList originalFileNames = torrentInfo.filePaths();
2243 const QString completeSavePath = savePath;
2244 const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
2245 QMetaObject::invokeMethod(m_fileSearcher, [=]()
2247 m_fileSearcher->search(searchId, originalFileNames, completeSavePath, incompleteSavePath);
2251 // Add a torrent to libtorrent session in hidden mode
2252 // and force it to download its metadata
2253 bool Session::downloadMetadata(const MagnetUri &magnetUri)
2255 if (!magnetUri.isValid()) return false;
2257 const auto id = TorrentID::fromInfoHash(magnetUri.infoHash());
2258 const QString name = magnetUri.name();
2260 // We should not add torrent if it's already
2261 // processed or adding to session
2262 if (m_torrents.contains(id)) return false;
2263 if (m_loadingTorrents.contains(id)) return false;
2264 if (m_downloadedMetadata.contains(id)) return false;
2266 qDebug("Adding torrent to preload metadata...");
2267 qDebug(" -> Torrent ID: %s", qUtf8Printable(id.toString()));
2268 qDebug(" -> Name: %s", qUtf8Printable(name));
2270 lt::add_torrent_params p = magnetUri.addTorrentParams();
2272 // Flags
2273 // Preallocation mode
2274 if (isPreallocationEnabled())
2275 p.storage_mode = lt::storage_mode_allocate;
2276 else
2277 p.storage_mode = lt::storage_mode_sparse;
2279 // Limits
2280 p.max_connections = maxConnectionsPerTorrent();
2281 p.max_uploads = maxUploadsPerTorrent();
2283 const QString savePath = Utils::Fs::tempPath() + id.toString();
2284 p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
2286 // Forced start
2287 p.flags &= ~lt::torrent_flags::paused;
2288 p.flags &= ~lt::torrent_flags::auto_managed;
2290 // Solution to avoid accidental file writes
2291 p.flags |= lt::torrent_flags::upload_mode;
2293 #ifndef QBT_USES_LIBTORRENT2
2294 p.storage = customStorageConstructor;
2295 #endif
2297 // Adding torrent to libtorrent session
2298 lt::error_code ec;
2299 lt::torrent_handle h = m_nativeSession->add_torrent(p, ec);
2300 if (ec) return false;
2302 // waiting for metadata...
2303 m_downloadedMetadata.insert(h.info_hash());
2304 ++m_extraLimit;
2305 adjustLimits();
2307 return true;
2310 void Session::exportTorrentFile(const TorrentInfo &torrentInfo, const QString &folderPath, const QString &baseName)
2312 const QString validName = Utils::Fs::toValidFileSystemName(baseName);
2313 QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
2314 const QDir exportDir {folderPath};
2315 if (exportDir.exists() || exportDir.mkpath(exportDir.absolutePath()))
2317 QString newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename);
2318 int counter = 0;
2319 while (QFile::exists(newTorrentPath))
2321 // Append number to torrent name to make it unique
2322 torrentExportFilename = QString::fromLatin1("%1 %2.torrent").arg(validName).arg(++counter);
2323 newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename);
2328 torrentInfo.saveToFile(newTorrentPath);
2330 catch (const RuntimeError &err)
2332 LogMsg(tr("Couldn't export torrent metadata file '%1'. Reason: %2.")
2333 .arg(newTorrentPath, err.message()), Log::WARNING);
2338 void Session::generateResumeData()
2340 for (TorrentImpl *const torrent : asConst(m_torrents))
2342 if (!torrent->isValid()) continue;
2344 if (torrent->needSaveResumeData())
2346 torrent->saveResumeData();
2347 m_needSaveResumeDataTorrents.remove(torrent->id());
2352 // Called on exit
2353 void Session::saveResumeData()
2355 // Pause session
2356 m_nativeSession->pause();
2358 if (isQueueingSystemEnabled())
2359 saveTorrentsQueue();
2360 generateResumeData();
2362 while (m_numResumeData > 0)
2364 const std::vector<lt::alert *> alerts = getPendingAlerts(lt::seconds {30});
2365 if (alerts.empty())
2367 LogMsg(tr("Error: Aborted saving resume data for %1 outstanding torrents.").arg(QString::number(m_numResumeData))
2368 , Log::CRITICAL);
2369 break;
2372 for (const lt::alert *a : alerts)
2374 switch (a->type())
2376 case lt::save_resume_data_failed_alert::alert_type:
2377 case lt::save_resume_data_alert::alert_type:
2378 dispatchTorrentAlert(a);
2379 break;
2385 void Session::saveTorrentsQueue() const
2387 QVector<TorrentID> queue;
2388 for (const TorrentImpl *torrent : asConst(m_torrents))
2390 // We require actual (non-cached) queue position here!
2391 const int queuePos = toLTUnderlyingType(torrent->nativeHandle().queue_position());
2392 if (queuePos >= 0)
2394 if (queuePos >= queue.size())
2395 queue.resize(queuePos + 1);
2396 queue[queuePos] = torrent->id();
2400 m_resumeDataStorage->storeQueue(queue);
2403 void Session::removeTorrentsQueue() const
2405 m_resumeDataStorage->storeQueue({});
2408 void Session::setDefaultSavePath(QString path)
2410 path = normalizeSavePath(path);
2411 if (path == m_defaultSavePath) return;
2413 m_defaultSavePath = path;
2415 if (isDisableAutoTMMWhenDefaultSavePathChanged())
2416 for (TorrentImpl *const torrent : asConst(m_torrents))
2417 torrent->setAutoTMMEnabled(false);
2418 else
2419 for (TorrentImpl *const torrent : asConst(m_torrents))
2420 torrent->handleCategorySavePathChanged();
2423 void Session::setTempPath(QString path)
2425 path = normalizeSavePath(path, defaultSavePath() + "temp/");
2426 if (path == m_tempPath) return;
2428 m_tempPath = path;
2430 for (TorrentImpl *const torrent : asConst(m_torrents))
2431 torrent->handleTempPathChanged();
2434 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
2435 void Session::networkOnlineStateChanged(const bool online)
2437 LogMsg(tr("System network status changed to %1", "e.g: System network status changed to ONLINE").arg(online ? tr("ONLINE") : tr("OFFLINE")), Log::INFO);
2440 void Session::networkConfigurationChange(const QNetworkConfiguration &cfg)
2442 const QString configuredInterfaceName = networkInterface();
2443 // Empty means "Any Interface". In this case libtorrent has binded to 0.0.0.0 so any change to any interface will
2444 // be automatically picked up. Otherwise we would rebinding here to 0.0.0.0 again.
2445 if (configuredInterfaceName.isEmpty()) return;
2447 const QString changedInterface = cfg.name();
2449 if (configuredInterfaceName == changedInterface)
2451 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);
2452 configureListeningInterface();
2455 #endif
2457 QStringList Session::getListeningIPs() const
2459 QStringList IPs;
2461 const QString ifaceName = networkInterface();
2462 const QString ifaceAddr = networkInterfaceAddress();
2463 const QHostAddress configuredAddr(ifaceAddr);
2464 const bool allIPv4 = (ifaceAddr == QLatin1String("0.0.0.0")); // Means All IPv4 addresses
2465 const bool allIPv6 = (ifaceAddr == QLatin1String("::")); // Means All IPv6 addresses
2467 if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull())
2469 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);
2470 // Pass the invalid user configured interface name/address to libtorrent
2471 // in hopes that it will come online later.
2472 // This will not cause IP leak but allow user to reconnect the interface
2473 // and re-establish connection without restarting the client.
2474 IPs.append(ifaceAddr);
2475 return IPs;
2478 if (ifaceName.isEmpty())
2480 if (ifaceAddr.isEmpty())
2481 return {QLatin1String("0.0.0.0"), QLatin1String("::")}; // Indicates all interfaces + all addresses (aka default)
2483 if (allIPv4)
2484 return {QLatin1String("0.0.0.0")};
2486 if (allIPv6)
2487 return {QLatin1String("::")};
2490 const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
2492 if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
2493 || (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
2494 return;
2496 if ((match == addr) || allIPv4 || allIPv6)
2497 IPs.append(addr.toString());
2500 if (ifaceName.isEmpty())
2502 const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
2503 for (const auto &addr : addresses)
2504 checkAndAddIP(addr, configuredAddr);
2506 // At this point ifaceAddr was non-empty
2507 // If IPs.isEmpty() it means the configured Address was not found
2508 if (IPs.isEmpty())
2510 LogMsg(tr("Can't find the configured address '%1' to listen on"
2511 , "Can't find the configured address '192.168.1.3' to listen on")
2512 .arg(ifaceAddr), Log::CRITICAL);
2513 IPs.append(ifaceAddr);
2516 return IPs;
2519 // Attempt to listen on provided interface
2520 const QNetworkInterface networkIFace = QNetworkInterface::interfaceFromName(ifaceName);
2521 if (!networkIFace.isValid())
2523 qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName));
2524 LogMsg(tr("The network interface defined is invalid: %1").arg(ifaceName), Log::CRITICAL);
2525 IPs.append(ifaceName);
2526 return IPs;
2529 if (ifaceAddr.isEmpty())
2531 IPs.append(ifaceName);
2532 return IPs; // On Windows calling code converts it to GUID
2535 const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
2536 qDebug("This network interface has %d IP addresses", addresses.size());
2537 for (const QNetworkAddressEntry &entry : addresses)
2538 checkAndAddIP(entry.ip(), configuredAddr);
2540 // Make sure there is at least one IP
2541 // At this point there was an explicit interface and an explicit address set
2542 // and the address should have been found
2543 if (IPs.isEmpty())
2545 LogMsg(tr("Can't find the configured address '%1' to listen on"
2546 , "Can't find the configured address '192.168.1.3' to listen on")
2547 .arg(ifaceAddr), Log::CRITICAL);
2548 IPs.append(ifaceAddr);
2551 return IPs;
2554 // Set the ports range in which is chosen the port
2555 // the BitTorrent session will listen to
2556 void Session::configureListeningInterface()
2558 m_listenInterfaceConfigured = false;
2559 configureDeferred();
2562 int Session::globalDownloadSpeedLimit() const
2564 // Unfortunately the value was saved as KiB instead of B.
2565 // But it is better to pass it around internally(+ webui) as Bytes.
2566 return m_globalDownloadSpeedLimit * 1024;
2569 void Session::setGlobalDownloadSpeedLimit(const int limit)
2571 // Unfortunately the value was saved as KiB instead of B.
2572 // But it is better to pass it around internally(+ webui) as Bytes.
2573 if (limit == globalDownloadSpeedLimit())
2574 return;
2576 if (limit <= 0)
2577 m_globalDownloadSpeedLimit = 0;
2578 else if (limit <= 1024)
2579 m_globalDownloadSpeedLimit = 1;
2580 else
2581 m_globalDownloadSpeedLimit = (limit / 1024);
2583 if (!isAltGlobalSpeedLimitEnabled())
2584 configureDeferred();
2587 int Session::globalUploadSpeedLimit() const
2589 // Unfortunately the value was saved as KiB instead of B.
2590 // But it is better to pass it around internally(+ webui) as Bytes.
2591 return m_globalUploadSpeedLimit * 1024;
2594 void Session::setGlobalUploadSpeedLimit(const int limit)
2596 // Unfortunately the value was saved as KiB instead of B.
2597 // But it is better to pass it around internally(+ webui) as Bytes.
2598 if (limit == globalUploadSpeedLimit())
2599 return;
2601 if (limit <= 0)
2602 m_globalUploadSpeedLimit = 0;
2603 else if (limit <= 1024)
2604 m_globalUploadSpeedLimit = 1;
2605 else
2606 m_globalUploadSpeedLimit = (limit / 1024);
2608 if (!isAltGlobalSpeedLimitEnabled())
2609 configureDeferred();
2612 int Session::altGlobalDownloadSpeedLimit() const
2614 // Unfortunately the value was saved as KiB instead of B.
2615 // But it is better to pass it around internally(+ webui) as Bytes.
2616 return m_altGlobalDownloadSpeedLimit * 1024;
2619 void Session::setAltGlobalDownloadSpeedLimit(const int limit)
2621 // Unfortunately the value was saved as KiB instead of B.
2622 // But it is better to pass it around internally(+ webui) as Bytes.
2623 if (limit == altGlobalDownloadSpeedLimit())
2624 return;
2626 if (limit <= 0)
2627 m_altGlobalDownloadSpeedLimit = 0;
2628 else if (limit <= 1024)
2629 m_altGlobalDownloadSpeedLimit = 1;
2630 else
2631 m_altGlobalDownloadSpeedLimit = (limit / 1024);
2633 if (isAltGlobalSpeedLimitEnabled())
2634 configureDeferred();
2637 int Session::altGlobalUploadSpeedLimit() const
2639 // Unfortunately the value was saved as KiB instead of B.
2640 // But it is better to pass it around internally(+ webui) as Bytes.
2641 return m_altGlobalUploadSpeedLimit * 1024;
2644 void Session::setAltGlobalUploadSpeedLimit(const int limit)
2646 // Unfortunately the value was saved as KiB instead of B.
2647 // But it is better to pass it around internally(+ webui) as Bytes.
2648 if (limit == altGlobalUploadSpeedLimit())
2649 return;
2651 if (limit <= 0)
2652 m_altGlobalUploadSpeedLimit = 0;
2653 else if (limit <= 1024)
2654 m_altGlobalUploadSpeedLimit = 1;
2655 else
2656 m_altGlobalUploadSpeedLimit = (limit / 1024);
2658 if (isAltGlobalSpeedLimitEnabled())
2659 configureDeferred();
2662 int Session::downloadSpeedLimit() const
2664 return isAltGlobalSpeedLimitEnabled()
2665 ? altGlobalDownloadSpeedLimit()
2666 : globalDownloadSpeedLimit();
2669 void Session::setDownloadSpeedLimit(const int limit)
2671 if (isAltGlobalSpeedLimitEnabled())
2672 setAltGlobalDownloadSpeedLimit(limit);
2673 else
2674 setGlobalDownloadSpeedLimit(limit);
2677 int Session::uploadSpeedLimit() const
2679 return isAltGlobalSpeedLimitEnabled()
2680 ? altGlobalUploadSpeedLimit()
2681 : globalUploadSpeedLimit();
2684 void Session::setUploadSpeedLimit(const int limit)
2686 if (isAltGlobalSpeedLimitEnabled())
2687 setAltGlobalUploadSpeedLimit(limit);
2688 else
2689 setGlobalUploadSpeedLimit(limit);
2692 bool Session::isAltGlobalSpeedLimitEnabled() const
2694 return m_isAltGlobalSpeedLimitEnabled;
2697 void Session::setAltGlobalSpeedLimitEnabled(const bool enabled)
2699 if (enabled == isAltGlobalSpeedLimitEnabled()) return;
2701 // Save new state to remember it on startup
2702 m_isAltGlobalSpeedLimitEnabled = enabled;
2703 applyBandwidthLimits();
2704 // Notify
2705 emit speedLimitModeChanged(m_isAltGlobalSpeedLimitEnabled);
2708 bool Session::isBandwidthSchedulerEnabled() const
2710 return m_isBandwidthSchedulerEnabled;
2713 void Session::setBandwidthSchedulerEnabled(const bool enabled)
2715 if (enabled != isBandwidthSchedulerEnabled())
2717 m_isBandwidthSchedulerEnabled = enabled;
2718 if (enabled)
2719 enableBandwidthScheduler();
2720 else
2721 delete m_bwScheduler;
2725 int Session::saveResumeDataInterval() const
2727 return m_saveResumeDataInterval;
2730 void Session::setSaveResumeDataInterval(const int value)
2732 if (value == m_saveResumeDataInterval)
2733 return;
2735 m_saveResumeDataInterval = value;
2737 if (value > 0)
2739 m_resumeDataTimer->setInterval(value * 60 * 1000);
2740 m_resumeDataTimer->start();
2742 else
2744 m_resumeDataTimer->stop();
2748 int Session::port() const
2750 return m_port;
2753 void Session::setPort(const int port)
2755 if (port != m_port)
2757 m_port = port;
2758 configureListeningInterface();
2760 if (isReannounceWhenAddressChangedEnabled())
2761 reannounceToAllTrackers();
2765 QString Session::networkInterface() const
2767 return m_networkInterface;
2770 void Session::setNetworkInterface(const QString &iface)
2772 if (iface != networkInterface())
2774 m_networkInterface = iface;
2775 configureListeningInterface();
2779 QString Session::networkInterfaceName() const
2781 return m_networkInterfaceName;
2784 void Session::setNetworkInterfaceName(const QString &name)
2786 m_networkInterfaceName = name;
2789 QString Session::networkInterfaceAddress() const
2791 return m_networkInterfaceAddress;
2794 void Session::setNetworkInterfaceAddress(const QString &address)
2796 if (address != networkInterfaceAddress())
2798 m_networkInterfaceAddress = address;
2799 configureListeningInterface();
2803 int Session::encryption() const
2805 return m_encryption;
2808 void Session::setEncryption(const int state)
2810 if (state != encryption())
2812 m_encryption = state;
2813 configureDeferred();
2814 LogMsg(tr("Encryption support [%1]").arg(
2815 state == 0 ? tr("ON") : ((state == 1) ? tr("FORCED") : tr("OFF")))
2816 , Log::INFO);
2820 bool Session::isProxyPeerConnectionsEnabled() const
2822 return m_isProxyPeerConnectionsEnabled;
2825 void Session::setProxyPeerConnectionsEnabled(const bool enabled)
2827 if (enabled != isProxyPeerConnectionsEnabled())
2829 m_isProxyPeerConnectionsEnabled = enabled;
2830 configureDeferred();
2834 ChokingAlgorithm Session::chokingAlgorithm() const
2836 return m_chokingAlgorithm;
2839 void Session::setChokingAlgorithm(const ChokingAlgorithm mode)
2841 if (mode == m_chokingAlgorithm) return;
2843 m_chokingAlgorithm = mode;
2844 configureDeferred();
2847 SeedChokingAlgorithm Session::seedChokingAlgorithm() const
2849 return m_seedChokingAlgorithm;
2852 void Session::setSeedChokingAlgorithm(const SeedChokingAlgorithm mode)
2854 if (mode == m_seedChokingAlgorithm) return;
2856 m_seedChokingAlgorithm = mode;
2857 configureDeferred();
2860 bool Session::isAddTrackersEnabled() const
2862 return m_isAddTrackersEnabled;
2865 void Session::setAddTrackersEnabled(const bool enabled)
2867 m_isAddTrackersEnabled = enabled;
2870 QString Session::additionalTrackers() const
2872 return m_additionalTrackers;
2875 void Session::setAdditionalTrackers(const QString &trackers)
2877 if (trackers != additionalTrackers())
2879 m_additionalTrackers = trackers;
2880 populateAdditionalTrackers();
2884 bool Session::isIPFilteringEnabled() const
2886 return m_isIPFilteringEnabled;
2889 void Session::setIPFilteringEnabled(const bool enabled)
2891 if (enabled != m_isIPFilteringEnabled)
2893 m_isIPFilteringEnabled = enabled;
2894 m_IPFilteringConfigured = false;
2895 configureDeferred();
2899 QString Session::IPFilterFile() const
2901 return Utils::Fs::toUniformPath(m_IPFilterFile);
2904 void Session::setIPFilterFile(QString path)
2906 path = Utils::Fs::toUniformPath(path);
2907 if (path != IPFilterFile())
2909 m_IPFilterFile = path;
2910 m_IPFilteringConfigured = false;
2911 configureDeferred();
2915 void Session::setBannedIPs(const QStringList &newList)
2917 if (newList == m_bannedIPs)
2918 return; // do nothing
2919 // here filter out incorrect IP
2920 QStringList filteredList;
2921 for (const QString &ip : newList)
2923 if (Utils::Net::isValidIP(ip))
2925 // the same IPv6 addresses could be written in different forms;
2926 // QHostAddress::toString() result format follows RFC5952;
2927 // thus we avoid duplicate entries pointing to the same address
2928 filteredList << QHostAddress(ip).toString();
2930 else
2932 LogMsg(tr("%1 is not a valid IP address and was rejected while applying the list of banned addresses.")
2933 .arg(ip)
2934 , Log::WARNING);
2937 // now we have to sort IPs and make them unique
2938 filteredList.sort();
2939 filteredList.removeDuplicates();
2940 // Again ensure that the new list is different from the stored one.
2941 if (filteredList == m_bannedIPs)
2942 return; // do nothing
2943 // store to session settings
2944 // also here we have to recreate filter list including 3rd party ban file
2945 // and install it again into m_session
2946 m_bannedIPs = filteredList;
2947 m_IPFilteringConfigured = false;
2948 configureDeferred();
2951 ResumeDataStorageType Session::resumeDataStorageType() const
2953 return m_resumeDataStorageType;
2956 void Session::setResumeDataStorageType(const ResumeDataStorageType type)
2958 m_resumeDataStorageType = type;
2961 QStringList Session::bannedIPs() const
2963 return m_bannedIPs;
2966 #if defined(Q_OS_WIN)
2967 OSMemoryPriority Session::getOSMemoryPriority() const
2969 return m_OSMemoryPriority;
2972 void Session::setOSMemoryPriority(const OSMemoryPriority priority)
2974 if (m_OSMemoryPriority == priority)
2975 return;
2977 m_OSMemoryPriority = priority;
2978 configureDeferred();
2981 void Session::applyOSMemoryPriority() const
2983 using SETPROCESSINFORMATION = BOOL (WINAPI *)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD);
2984 const auto setProcessInformation = Utils::Misc::loadWinAPI<SETPROCESSINFORMATION>("Kernel32.dll", "SetProcessInformation");
2985 if (!setProcessInformation) // only available on Windows >= 8
2986 return;
2988 #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
2989 // this dummy struct is required to compile successfully when targeting older Windows version
2990 struct MEMORY_PRIORITY_INFORMATION
2992 ULONG MemoryPriority;
2995 #define MEMORY_PRIORITY_LOWEST 0
2996 #define MEMORY_PRIORITY_VERY_LOW 1
2997 #define MEMORY_PRIORITY_LOW 2
2998 #define MEMORY_PRIORITY_MEDIUM 3
2999 #define MEMORY_PRIORITY_BELOW_NORMAL 4
3000 #define MEMORY_PRIORITY_NORMAL 5
3001 #endif
3003 MEMORY_PRIORITY_INFORMATION prioInfo {};
3004 switch (getOSMemoryPriority())
3006 case OSMemoryPriority::Normal:
3007 default:
3008 prioInfo.MemoryPriority = MEMORY_PRIORITY_NORMAL;
3009 break;
3010 case OSMemoryPriority::BelowNormal:
3011 prioInfo.MemoryPriority = MEMORY_PRIORITY_BELOW_NORMAL;
3012 break;
3013 case OSMemoryPriority::Medium:
3014 prioInfo.MemoryPriority = MEMORY_PRIORITY_MEDIUM;
3015 break;
3016 case OSMemoryPriority::Low:
3017 prioInfo.MemoryPriority = MEMORY_PRIORITY_LOW;
3018 break;
3019 case OSMemoryPriority::VeryLow:
3020 prioInfo.MemoryPriority = MEMORY_PRIORITY_VERY_LOW;
3021 break;
3023 setProcessInformation(::GetCurrentProcess(), ProcessMemoryPriority, &prioInfo, sizeof(prioInfo));
3025 #endif
3027 int Session::maxConnectionsPerTorrent() const
3029 return m_maxConnectionsPerTorrent;
3032 void Session::setMaxConnectionsPerTorrent(int max)
3034 max = (max > 0) ? max : -1;
3035 if (max != maxConnectionsPerTorrent())
3037 m_maxConnectionsPerTorrent = max;
3039 // Apply this to all session torrents
3040 for (const lt::torrent_handle &handle : m_nativeSession->get_torrents())
3044 handle.set_max_connections(max);
3046 catch (const std::exception &) {}
3051 int Session::maxUploadsPerTorrent() const
3053 return m_maxUploadsPerTorrent;
3056 void Session::setMaxUploadsPerTorrent(int max)
3058 max = (max > 0) ? max : -1;
3059 if (max != maxUploadsPerTorrent())
3061 m_maxUploadsPerTorrent = max;
3063 // Apply this to all session torrents
3064 for (const lt::torrent_handle &handle : m_nativeSession->get_torrents())
3068 handle.set_max_uploads(max);
3070 catch (const std::exception &) {}
3075 bool Session::announceToAllTrackers() const
3077 return m_announceToAllTrackers;
3080 void Session::setAnnounceToAllTrackers(const bool val)
3082 if (val != m_announceToAllTrackers)
3084 m_announceToAllTrackers = val;
3085 configureDeferred();
3089 bool Session::announceToAllTiers() const
3091 return m_announceToAllTiers;
3094 void Session::setAnnounceToAllTiers(const bool val)
3096 if (val != m_announceToAllTiers)
3098 m_announceToAllTiers = val;
3099 configureDeferred();
3103 int Session::peerTurnover() const
3105 return m_peerTurnover;
3108 void Session::setPeerTurnover(const int val)
3110 if (val == m_peerTurnover)
3111 return;
3113 m_peerTurnover = val;
3114 configureDeferred();
3117 int Session::peerTurnoverCutoff() const
3119 return m_peerTurnoverCutoff;
3122 void Session::setPeerTurnoverCutoff(const int val)
3124 if (val == m_peerTurnoverCutoff)
3125 return;
3127 m_peerTurnoverCutoff = val;
3128 configureDeferred();
3131 int Session::peerTurnoverInterval() const
3133 return m_peerTurnoverInterval;
3136 void Session::setPeerTurnoverInterval(const int val)
3138 if (val == m_peerTurnoverInterval)
3139 return;
3141 m_peerTurnoverInterval = val;
3142 configureDeferred();
3145 int Session::asyncIOThreads() const
3147 return qBound(1, m_asyncIOThreads.get(), 1024);
3150 void Session::setAsyncIOThreads(const int num)
3152 if (num == m_asyncIOThreads)
3153 return;
3155 m_asyncIOThreads = num;
3156 configureDeferred();
3159 int Session::hashingThreads() const
3161 return qBound(1, m_hashingThreads.get(), 1024);
3164 void Session::setHashingThreads(const int num)
3166 if (num == m_hashingThreads)
3167 return;
3169 m_hashingThreads = num;
3170 configureDeferred();
3173 int Session::filePoolSize() const
3175 return m_filePoolSize;
3178 void Session::setFilePoolSize(const int size)
3180 if (size == m_filePoolSize)
3181 return;
3183 m_filePoolSize = size;
3184 configureDeferred();
3187 int Session::checkingMemUsage() const
3189 return qMax(1, m_checkingMemUsage.get());
3192 void Session::setCheckingMemUsage(int size)
3194 size = qMax(size, 1);
3196 if (size == m_checkingMemUsage)
3197 return;
3199 m_checkingMemUsage = size;
3200 configureDeferred();
3203 int Session::diskCacheSize() const
3205 #ifdef QBT_APP_64BIT
3206 return qMin(m_diskCacheSize.get(), 33554431); // 32768GiB
3207 #else
3208 // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
3209 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
3210 return qMin(m_diskCacheSize.get(), 1536);
3211 #endif
3214 void Session::setDiskCacheSize(int size)
3216 #ifdef QBT_APP_64BIT
3217 size = qMin(size, 33554431); // 32768GiB
3218 #else
3219 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
3220 size = qMin(size, 1536);
3221 #endif
3222 if (size != m_diskCacheSize)
3224 m_diskCacheSize = size;
3225 configureDeferred();
3229 int Session::diskCacheTTL() const
3231 return m_diskCacheTTL;
3234 void Session::setDiskCacheTTL(const int ttl)
3236 if (ttl != m_diskCacheTTL)
3238 m_diskCacheTTL = ttl;
3239 configureDeferred();
3243 bool Session::useOSCache() const
3245 return m_useOSCache;
3248 void Session::setUseOSCache(const bool use)
3250 if (use != m_useOSCache)
3252 m_useOSCache = use;
3253 configureDeferred();
3257 bool Session::isCoalesceReadWriteEnabled() const
3259 return m_coalesceReadWriteEnabled;
3262 void Session::setCoalesceReadWriteEnabled(const bool enabled)
3264 if (enabled == m_coalesceReadWriteEnabled) return;
3266 m_coalesceReadWriteEnabled = enabled;
3267 configureDeferred();
3270 bool Session::isSuggestModeEnabled() const
3272 return m_isSuggestMode;
3275 bool Session::usePieceExtentAffinity() const
3277 return m_usePieceExtentAffinity;
3280 void Session::setPieceExtentAffinity(const bool enabled)
3282 if (enabled == m_usePieceExtentAffinity) return;
3284 m_usePieceExtentAffinity = enabled;
3285 configureDeferred();
3288 void Session::setSuggestMode(const bool mode)
3290 if (mode == m_isSuggestMode) return;
3292 m_isSuggestMode = mode;
3293 configureDeferred();
3296 int Session::sendBufferWatermark() const
3298 return m_sendBufferWatermark;
3301 void Session::setSendBufferWatermark(const int value)
3303 if (value == m_sendBufferWatermark) return;
3305 m_sendBufferWatermark = value;
3306 configureDeferred();
3309 int Session::sendBufferLowWatermark() const
3311 return m_sendBufferLowWatermark;
3314 void Session::setSendBufferLowWatermark(const int value)
3316 if (value == m_sendBufferLowWatermark) return;
3318 m_sendBufferLowWatermark = value;
3319 configureDeferred();
3322 int Session::sendBufferWatermarkFactor() const
3324 return m_sendBufferWatermarkFactor;
3327 void Session::setSendBufferWatermarkFactor(const int value)
3329 if (value == m_sendBufferWatermarkFactor) return;
3331 m_sendBufferWatermarkFactor = value;
3332 configureDeferred();
3335 int Session::connectionSpeed() const
3337 return m_connectionSpeed;
3340 void Session::setConnectionSpeed(const int value)
3342 if (value == m_connectionSpeed) return;
3344 m_connectionSpeed = value;
3345 configureDeferred();
3348 int Session::socketBacklogSize() const
3350 return m_socketBacklogSize;
3353 void Session::setSocketBacklogSize(const int value)
3355 if (value == m_socketBacklogSize) return;
3357 m_socketBacklogSize = value;
3358 configureDeferred();
3361 bool Session::isAnonymousModeEnabled() const
3363 return m_isAnonymousModeEnabled;
3366 void Session::setAnonymousModeEnabled(const bool enabled)
3368 if (enabled != m_isAnonymousModeEnabled)
3370 m_isAnonymousModeEnabled = enabled;
3371 configureDeferred();
3372 LogMsg(tr("Anonymous mode [%1]").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
3373 , Log::INFO);
3377 bool Session::isQueueingSystemEnabled() const
3379 return m_isQueueingEnabled;
3382 void Session::setQueueingSystemEnabled(const bool enabled)
3384 if (enabled != m_isQueueingEnabled)
3386 m_isQueueingEnabled = enabled;
3387 configureDeferred();
3389 if (enabled)
3390 saveTorrentsQueue();
3391 else
3392 removeTorrentsQueue();
3396 int Session::maxActiveDownloads() const
3398 return m_maxActiveDownloads;
3401 void Session::setMaxActiveDownloads(int max)
3403 max = std::max(max, -1);
3404 if (max != m_maxActiveDownloads)
3406 m_maxActiveDownloads = max;
3407 configureDeferred();
3411 int Session::maxActiveUploads() const
3413 return m_maxActiveUploads;
3416 void Session::setMaxActiveUploads(int max)
3418 max = std::max(max, -1);
3419 if (max != m_maxActiveUploads)
3421 m_maxActiveUploads = max;
3422 configureDeferred();
3426 int Session::maxActiveTorrents() const
3428 return m_maxActiveTorrents;
3431 void Session::setMaxActiveTorrents(int max)
3433 max = std::max(max, -1);
3434 if (max != m_maxActiveTorrents)
3436 m_maxActiveTorrents = max;
3437 configureDeferred();
3441 bool Session::ignoreSlowTorrentsForQueueing() const
3443 return m_ignoreSlowTorrentsForQueueing;
3446 void Session::setIgnoreSlowTorrentsForQueueing(const bool ignore)
3448 if (ignore != m_ignoreSlowTorrentsForQueueing)
3450 m_ignoreSlowTorrentsForQueueing = ignore;
3451 configureDeferred();
3455 int Session::downloadRateForSlowTorrents() const
3457 return m_downloadRateForSlowTorrents;
3460 void Session::setDownloadRateForSlowTorrents(const int rateInKibiBytes)
3462 if (rateInKibiBytes == m_downloadRateForSlowTorrents)
3463 return;
3465 m_downloadRateForSlowTorrents = rateInKibiBytes;
3466 configureDeferred();
3469 int Session::uploadRateForSlowTorrents() const
3471 return m_uploadRateForSlowTorrents;
3474 void Session::setUploadRateForSlowTorrents(const int rateInKibiBytes)
3476 if (rateInKibiBytes == m_uploadRateForSlowTorrents)
3477 return;
3479 m_uploadRateForSlowTorrents = rateInKibiBytes;
3480 configureDeferred();
3483 int Session::slowTorrentsInactivityTimer() const
3485 return m_slowTorrentsInactivityTimer;
3488 void Session::setSlowTorrentsInactivityTimer(const int timeInSeconds)
3490 if (timeInSeconds == m_slowTorrentsInactivityTimer)
3491 return;
3493 m_slowTorrentsInactivityTimer = timeInSeconds;
3494 configureDeferred();
3497 int Session::outgoingPortsMin() const
3499 return m_outgoingPortsMin;
3502 void Session::setOutgoingPortsMin(const int min)
3504 if (min != m_outgoingPortsMin)
3506 m_outgoingPortsMin = min;
3507 configureDeferred();
3511 int Session::outgoingPortsMax() const
3513 return m_outgoingPortsMax;
3516 void Session::setOutgoingPortsMax(const int max)
3518 if (max != m_outgoingPortsMax)
3520 m_outgoingPortsMax = max;
3521 configureDeferred();
3525 int Session::UPnPLeaseDuration() const
3527 return m_UPnPLeaseDuration;
3530 void Session::setUPnPLeaseDuration(const int duration)
3532 if (duration != m_UPnPLeaseDuration)
3534 m_UPnPLeaseDuration = duration;
3535 configureDeferred();
3539 int Session::peerToS() const
3541 return m_peerToS;
3544 void Session::setPeerToS(const int value)
3546 if (value == m_peerToS)
3547 return;
3549 m_peerToS = value;
3550 configureDeferred();
3553 bool Session::ignoreLimitsOnLAN() const
3555 return m_ignoreLimitsOnLAN;
3558 void Session::setIgnoreLimitsOnLAN(const bool ignore)
3560 if (ignore != m_ignoreLimitsOnLAN)
3562 m_ignoreLimitsOnLAN = ignore;
3563 configureDeferred();
3567 bool Session::includeOverheadInLimits() const
3569 return m_includeOverheadInLimits;
3572 void Session::setIncludeOverheadInLimits(const bool include)
3574 if (include != m_includeOverheadInLimits)
3576 m_includeOverheadInLimits = include;
3577 configureDeferred();
3581 QString Session::announceIP() const
3583 return m_announceIP;
3586 void Session::setAnnounceIP(const QString &ip)
3588 if (ip != m_announceIP)
3590 m_announceIP = ip;
3591 configureDeferred();
3595 int Session::maxConcurrentHTTPAnnounces() const
3597 return m_maxConcurrentHTTPAnnounces;
3600 void Session::setMaxConcurrentHTTPAnnounces(const int value)
3602 if (value == m_maxConcurrentHTTPAnnounces)
3603 return;
3605 m_maxConcurrentHTTPAnnounces = value;
3606 configureDeferred();
3609 bool Session::isReannounceWhenAddressChangedEnabled() const
3611 return m_isReannounceWhenAddressChangedEnabled;
3614 void Session::setReannounceWhenAddressChangedEnabled(const bool enabled)
3616 if (enabled == m_isReannounceWhenAddressChangedEnabled)
3617 return;
3619 m_isReannounceWhenAddressChangedEnabled = enabled;
3622 void Session::reannounceToAllTrackers() const
3624 for (const lt::torrent_handle &torrent : m_nativeSession->get_torrents())
3625 torrent.force_reannounce(0, -1, lt::torrent_handle::ignore_min_interval);
3628 int Session::stopTrackerTimeout() const
3630 return m_stopTrackerTimeout;
3633 void Session::setStopTrackerTimeout(const int value)
3635 if (value == m_stopTrackerTimeout)
3636 return;
3638 m_stopTrackerTimeout = value;
3639 configureDeferred();
3642 int Session::maxConnections() const
3644 return m_maxConnections;
3647 void Session::setMaxConnections(int max)
3649 max = (max > 0) ? max : -1;
3650 if (max != m_maxConnections)
3652 m_maxConnections = max;
3653 configureDeferred();
3657 int Session::maxUploads() const
3659 return m_maxUploads;
3662 void Session::setMaxUploads(int max)
3664 max = (max > 0) ? max : -1;
3665 if (max != m_maxUploads)
3667 m_maxUploads = max;
3668 configureDeferred();
3672 BTProtocol Session::btProtocol() const
3674 return m_btProtocol;
3677 void Session::setBTProtocol(const BTProtocol protocol)
3679 if ((protocol < BTProtocol::Both) || (BTProtocol::UTP < protocol))
3680 return;
3682 if (protocol == m_btProtocol) return;
3684 m_btProtocol = protocol;
3685 configureDeferred();
3688 bool Session::isUTPRateLimited() const
3690 return m_isUTPRateLimited;
3693 void Session::setUTPRateLimited(const bool limited)
3695 if (limited != m_isUTPRateLimited)
3697 m_isUTPRateLimited = limited;
3698 configureDeferred();
3702 MixedModeAlgorithm Session::utpMixedMode() const
3704 return m_utpMixedMode;
3707 void Session::setUtpMixedMode(const MixedModeAlgorithm mode)
3709 if (mode == m_utpMixedMode) return;
3711 m_utpMixedMode = mode;
3712 configureDeferred();
3715 bool Session::isIDNSupportEnabled() const
3717 return m_IDNSupportEnabled;
3720 void Session::setIDNSupportEnabled(const bool enabled)
3722 if (enabled == m_IDNSupportEnabled) return;
3724 m_IDNSupportEnabled = enabled;
3725 configureDeferred();
3728 bool Session::multiConnectionsPerIpEnabled() const
3730 return m_multiConnectionsPerIpEnabled;
3733 void Session::setMultiConnectionsPerIpEnabled(const bool enabled)
3735 if (enabled == m_multiConnectionsPerIpEnabled) return;
3737 m_multiConnectionsPerIpEnabled = enabled;
3738 configureDeferred();
3741 bool Session::validateHTTPSTrackerCertificate() const
3743 return m_validateHTTPSTrackerCertificate;
3746 void Session::setValidateHTTPSTrackerCertificate(const bool enabled)
3748 if (enabled == m_validateHTTPSTrackerCertificate) return;
3750 m_validateHTTPSTrackerCertificate = enabled;
3751 configureDeferred();
3754 bool Session::isSSRFMitigationEnabled() const
3756 return m_SSRFMitigationEnabled;
3759 void Session::setSSRFMitigationEnabled(const bool enabled)
3761 if (enabled == m_SSRFMitigationEnabled) return;
3763 m_SSRFMitigationEnabled = enabled;
3764 configureDeferred();
3767 bool Session::blockPeersOnPrivilegedPorts() const
3769 return m_blockPeersOnPrivilegedPorts;
3772 void Session::setBlockPeersOnPrivilegedPorts(const bool enabled)
3774 if (enabled == m_blockPeersOnPrivilegedPorts) return;
3776 m_blockPeersOnPrivilegedPorts = enabled;
3777 configureDeferred();
3780 bool Session::isTrackerFilteringEnabled() const
3782 return m_isTrackerFilteringEnabled;
3785 void Session::setTrackerFilteringEnabled(const bool enabled)
3787 if (enabled != m_isTrackerFilteringEnabled)
3789 m_isTrackerFilteringEnabled = enabled;
3790 configureDeferred();
3794 bool Session::isListening() const
3796 return m_nativeSession->is_listening();
3799 MaxRatioAction Session::maxRatioAction() const
3801 return static_cast<MaxRatioAction>(m_maxRatioAction.get());
3804 void Session::setMaxRatioAction(const MaxRatioAction act)
3806 m_maxRatioAction = static_cast<int>(act);
3809 // If this functions returns true, we cannot add torrent to session,
3810 // but it is still possible to merge trackers in some cases
3811 bool Session::isKnownTorrent(const TorrentID &id) const
3813 return (m_torrents.contains(id)
3814 || m_loadingTorrents.contains(id)
3815 || m_downloadedMetadata.contains(id));
3818 void Session::updateSeedingLimitTimer()
3820 if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
3821 && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit())
3823 if (m_seedingLimitTimer->isActive())
3824 m_seedingLimitTimer->stop();
3826 else if (!m_seedingLimitTimer->isActive())
3828 m_seedingLimitTimer->start();
3832 void Session::handleTorrentShareLimitChanged(TorrentImpl *const)
3834 updateSeedingLimitTimer();
3837 void Session::handleTorrentNameChanged(TorrentImpl *const)
3841 void Session::handleTorrentSavePathChanged(TorrentImpl *const torrent)
3843 emit torrentSavePathChanged(torrent);
3846 void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
3848 emit torrentCategoryChanged(torrent, oldCategory);
3851 void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
3853 emit torrentTagAdded(torrent, tag);
3856 void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
3858 emit torrentTagRemoved(torrent, tag);
3861 void Session::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
3863 emit torrentSavingModeChanged(torrent);
3866 void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
3868 for (const TrackerEntry &newTracker : newTrackers)
3869 LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url, torrent->name()));
3870 emit trackersAdded(torrent, newTrackers);
3871 if (torrent->trackers().size() == newTrackers.size())
3872 emit trackerlessStateChanged(torrent, false);
3873 emit trackersChanged(torrent);
3876 void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers)
3878 for (const TrackerEntry &deletedTracker : deletedTrackers)
3879 LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url, torrent->name()));
3880 emit trackersRemoved(torrent, deletedTrackers);
3881 if (torrent->trackers().empty())
3882 emit trackerlessStateChanged(torrent, true);
3883 emit trackersChanged(torrent);
3886 void Session::handleTorrentTrackersChanged(TorrentImpl *const torrent)
3888 emit trackersChanged(torrent);
3891 void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
3893 for (const QUrl &newUrlSeed : newUrlSeeds)
3894 LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
3897 void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
3899 for (const QUrl &urlSeed : urlSeeds)
3900 LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
3903 void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
3905 // Copy the torrent file to the export folder
3906 if (!torrentExportDirectory().isEmpty())
3908 #ifdef QBT_USES_LIBTORRENT2
3909 const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file_with_hashes()};
3910 #else
3911 const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file()};
3912 #endif
3913 exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
3916 emit torrentMetadataReceived(torrent);
3919 void Session::handleTorrentPaused(TorrentImpl *const torrent)
3921 emit torrentPaused(torrent);
3924 void Session::handleTorrentResumed(TorrentImpl *const torrent)
3926 emit torrentResumed(torrent);
3929 void Session::handleTorrentChecked(TorrentImpl *const torrent)
3931 emit torrentFinishedChecking(torrent);
3934 void Session::handleTorrentFinished(TorrentImpl *const torrent)
3936 emit torrentFinished(torrent);
3938 qDebug("Checking if the torrent contains torrent files to download");
3939 // Check if there are torrent files inside
3940 for (int i = 0; i < torrent->filesCount(); ++i)
3942 const QString torrentRelpath = torrent->filePath(i);
3943 if (torrentRelpath.endsWith(".torrent", Qt::CaseInsensitive))
3945 qDebug("Found possible recursive torrent download.");
3946 const QString torrentFullpath = torrent->savePath(true) + '/' + torrentRelpath;
3947 qDebug("Full subtorrent path is %s", qUtf8Printable(torrentFullpath));
3948 TorrentInfo torrentInfo = TorrentInfo::loadFromFile(torrentFullpath);
3949 if (torrentInfo.isValid())
3951 qDebug("emitting recursiveTorrentDownloadPossible()");
3952 emit recursiveTorrentDownloadPossible(torrent);
3953 break;
3955 else
3957 qDebug("Caught error loading torrent");
3958 LogMsg(tr("Unable to decode '%1' torrent file.").arg(Utils::Fs::toNativePath(torrentFullpath)), Log::CRITICAL);
3963 // Move .torrent file to another folder
3964 if (!finishedTorrentExportDirectory().isEmpty())
3966 #ifdef QBT_USES_LIBTORRENT2
3967 const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file_with_hashes()};
3968 #else
3969 const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file()};
3970 #endif
3971 exportTorrentFile(torrentInfo, finishedTorrentExportDirectory(), torrent->name());
3974 if (!hasUnfinishedTorrents())
3975 emit allTorrentsFinished();
3978 void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
3980 --m_numResumeData;
3982 m_resumeDataStorage->store(torrent->id(), data);
3985 void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl)
3987 emit trackerSuccess(torrent, trackerUrl);
3990 void Session::handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl)
3992 emit trackerError(torrent, trackerUrl);
3995 bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, const MoveStorageMode mode)
3997 Q_ASSERT(torrent);
3999 const lt::torrent_handle torrentHandle = torrent->nativeHandle();
4000 const QString currentLocation = torrent->actualStorageLocation();
4002 if (m_moveStorageQueue.size() > 1)
4004 const auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
4005 , [&torrentHandle](const MoveStorageJob &job)
4007 return job.torrentHandle == torrentHandle;
4010 if (iter != m_moveStorageQueue.end())
4012 // remove existing inactive job
4013 m_moveStorageQueue.erase(iter);
4014 LogMsg(tr("Cancelled moving \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, iter->path));
4018 if (!m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle))
4020 // if there is active job for this torrent prevent creating meaningless
4021 // job that will move torrent to the same location as current one
4022 if (QDir {m_moveStorageQueue.first().path} == QDir {newPath})
4024 LogMsg(tr("Couldn't enqueue move of \"%1\" to \"%2\". Torrent is currently moving to the same destination location.")
4025 .arg(torrent->name(), newPath));
4026 return false;
4029 else
4031 if (QDir {currentLocation} == QDir {newPath})
4033 LogMsg(tr("Couldn't enqueue move of \"%1\" from \"%2\" to \"%3\". Both paths point to the same location.")
4034 .arg(torrent->name(), currentLocation, newPath));
4035 return false;
4039 const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode};
4040 m_moveStorageQueue << moveStorageJob;
4041 LogMsg(tr("Enqueued to move \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, newPath));
4043 if (m_moveStorageQueue.size() == 1)
4044 moveTorrentStorage(moveStorageJob);
4046 return true;
4049 void Session::moveTorrentStorage(const MoveStorageJob &job) const
4051 #ifdef QBT_USES_LIBTORRENT2
4052 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
4053 #else
4054 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
4055 #endif
4056 const TorrentImpl *torrent = m_torrents.value(id);
4057 const QString torrentName = (torrent ? torrent->name() : id.toString());
4058 LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path));
4060 job.torrentHandle.move_storage(job.path.toUtf8().constData()
4061 , ((job.mode == MoveStorageMode::Overwrite)
4062 ? lt::move_flags_t::always_replace_files : lt::move_flags_t::dont_replace));
4065 void Session::handleMoveTorrentStorageJobFinished()
4067 const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
4068 if (!m_moveStorageQueue.isEmpty())
4069 moveTorrentStorage(m_moveStorageQueue.first());
4071 const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
4072 , [&finishedJob](const MoveStorageJob &job)
4074 return job.torrentHandle == finishedJob.torrentHandle;
4077 const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
4079 TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
4080 if (torrent)
4082 torrent->handleMoveStorageJobFinished(torrentHasOutstandingJob);
4084 else if (!torrentHasOutstandingJob)
4086 // Last job is completed for torrent that being removing, so actually remove it
4087 const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
4088 const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
4089 if (removingTorrentData.deleteOption == DeleteTorrent)
4090 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
4094 void Session::handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl)
4096 emit trackerWarning(torrent, trackerUrl);
4099 bool Session::hasPerTorrentRatioLimit() const
4101 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
4103 return (torrent->ratioLimit() >= 0);
4107 bool Session::hasPerTorrentSeedingTimeLimit() const
4109 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
4111 return (torrent->seedingTimeLimit() >= 0);
4115 void Session::configureDeferred()
4117 if (m_deferredConfigureScheduled)
4118 return;
4120 m_deferredConfigureScheduled = true;
4121 QMetaObject::invokeMethod(this, qOverload<>(&Session::configure), Qt::QueuedConnection);
4124 // Enable IP Filtering
4125 // this method creates ban list from scratch combining user ban list and 3rd party ban list file
4126 void Session::enableIPFilter()
4128 qDebug("Enabling IPFilter");
4129 // 1. Parse the IP filter
4130 // 2. In the slot add the manually banned IPs to the provided lt::ip_filter
4131 // 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
4132 // set between clearing the old one and setting the new one.
4133 if (!m_filterParser)
4135 m_filterParser = new FilterParserThread(this);
4136 connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &Session::handleIPFilterParsed);
4137 connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &Session::handleIPFilterError);
4139 m_filterParser->processFilterFile(IPFilterFile());
4142 // Disable IP Filtering
4143 void Session::disableIPFilter()
4145 qDebug("Disabling IPFilter");
4146 if (m_filterParser)
4148 disconnect(m_filterParser.data(), nullptr, this, nullptr);
4149 delete m_filterParser;
4152 // Add the banned IPs after the IPFilter disabling
4153 // which creates an empty filter and overrides all previously
4154 // applied bans.
4155 lt::ip_filter filter;
4156 processBannedIPs(filter);
4157 m_nativeSession->set_ip_filter(filter);
4160 void Session::recursiveTorrentDownload(const TorrentID &id)
4162 TorrentImpl *const torrent = m_torrents.value(id);
4163 if (!torrent) return;
4165 for (int i = 0; i < torrent->filesCount(); ++i)
4167 const QString torrentRelpath = torrent->filePath(i);
4168 if (torrentRelpath.endsWith(".torrent"))
4170 LogMsg(tr("Recursive download of file '%1' embedded in torrent '%2'"
4171 , "Recursive download of 'test.torrent' embedded in torrent 'test2'")
4172 .arg(Utils::Fs::toNativePath(torrentRelpath), torrent->name()));
4173 const QString torrentFullpath = torrent->savePath() + '/' + torrentRelpath;
4175 AddTorrentParams params;
4176 // Passing the save path along to the sub torrent file
4177 params.savePath = torrent->savePath();
4178 addTorrent(TorrentInfo::loadFromFile(torrentFullpath), params);
4183 const SessionStatus &Session::status() const
4185 return m_status;
4188 const CacheStatus &Session::cacheStatus() const
4190 return m_cacheStatus;
4193 void Session::startUpTorrents()
4195 qDebug("Initializing torrents resume data storage...");
4197 const QString dbPath = Utils::Fs::expandPathAbs(
4198 specialFolderLocation(SpecialFolder::Data) + QLatin1String("torrents.db"));
4199 const bool dbStorageExists = QFile::exists(dbPath);
4201 ResumeDataStorage *startupStorage = nullptr;
4202 if (resumeDataStorageType() == ResumeDataStorageType::SQLite)
4204 m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
4206 if (!dbStorageExists)
4208 const QString dataPath = Utils::Fs::expandPathAbs(
4209 specialFolderLocation(SpecialFolder::Data) + QLatin1String("BT_backup"));
4210 startupStorage = new BencodeResumeDataStorage(dataPath, this);
4213 else
4215 const QString dataPath = Utils::Fs::expandPathAbs(
4216 specialFolderLocation(SpecialFolder::Data) + QLatin1String("BT_backup"));
4217 m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
4219 if (dbStorageExists)
4220 startupStorage = new DBResumeDataStorage(dbPath, this);
4223 if (!startupStorage)
4224 startupStorage = m_resumeDataStorage;
4226 qDebug("Starting up torrents...");
4228 const QVector<TorrentID> torrents = startupStorage->registeredTorrents();
4229 int resumedTorrentsCount = 0;
4230 QVector<TorrentID> queue;
4231 for (const TorrentID &torrentID : torrents)
4233 const std::optional<LoadTorrentParams> resumeData = startupStorage->load(torrentID);
4234 if (resumeData)
4236 if (m_resumeDataStorage != startupStorage)
4238 m_resumeDataStorage->store(torrentID, *resumeData);
4239 if (isQueueingSystemEnabled() && !resumeData->hasSeedStatus)
4240 queue.append(torrentID);
4243 qDebug() << "Starting up torrent" << torrentID.toString() << "...";
4244 if (!loadTorrent(*resumeData))
4245 LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
4246 .arg(torrentID.toString()), Log::CRITICAL);
4248 // process add torrent messages before message queue overflow
4249 if ((resumedTorrentsCount % 100) == 0) readAlerts();
4251 ++resumedTorrentsCount;
4253 else
4255 LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
4256 .arg(torrentID.toString()), Log::CRITICAL);
4260 if (m_resumeDataStorage != startupStorage)
4262 delete startupStorage;
4263 if (resumeDataStorageType() == ResumeDataStorageType::Legacy)
4264 Utils::Fs::forceRemove(dbPath);
4266 if (isQueueingSystemEnabled())
4267 m_resumeDataStorage->storeQueue(queue);
4271 quint64 Session::getAlltimeDL() const
4273 return m_statistics->getAlltimeDL();
4276 quint64 Session::getAlltimeUL() const
4278 return m_statistics->getAlltimeUL();
4281 void Session::enqueueRefresh()
4283 Q_ASSERT(!m_refreshEnqueued);
4285 QTimer::singleShot(refreshInterval(), this, [this] ()
4287 m_nativeSession->post_torrent_updates();
4288 m_nativeSession->post_session_stats();
4291 m_refreshEnqueued = true;
4294 void Session::handleIPFilterParsed(const int ruleCount)
4296 if (m_filterParser)
4298 lt::ip_filter filter = m_filterParser->IPfilter();
4299 processBannedIPs(filter);
4300 m_nativeSession->set_ip_filter(filter);
4302 LogMsg(tr("Successfully parsed the provided IP filter: %1 rules were applied.", "%1 is a number").arg(ruleCount));
4303 emit IPFilterParsed(false, ruleCount);
4306 void Session::handleIPFilterError()
4308 lt::ip_filter filter;
4309 processBannedIPs(filter);
4310 m_nativeSession->set_ip_filter(filter);
4312 LogMsg(tr("Error: Failed to parse the provided IP filter."), Log::CRITICAL);
4313 emit IPFilterParsed(true, 0);
4316 std::vector<lt::alert *> Session::getPendingAlerts(const lt::time_duration time) const
4318 if (time > lt::time_duration::zero())
4319 m_nativeSession->wait_for_alert(time);
4321 std::vector<lt::alert *> alerts;
4322 m_nativeSession->pop_alerts(&alerts);
4323 return alerts;
4326 TorrentContentLayout Session::torrentContentLayout() const
4328 return m_torrentContentLayout;
4331 void Session::setTorrentContentLayout(const TorrentContentLayout value)
4333 m_torrentContentLayout = value;
4336 // Read alerts sent by the BitTorrent session
4337 void Session::readAlerts()
4339 const std::vector<lt::alert *> alerts = getPendingAlerts();
4340 for (const lt::alert *a : alerts)
4341 handleAlert(a);
4344 void Session::handleAlert(const lt::alert *a)
4348 switch (a->type())
4350 #ifdef QBT_USES_LIBTORRENT2
4351 case lt::file_prio_alert::alert_type:
4352 #endif
4353 case lt::file_renamed_alert::alert_type:
4354 case lt::file_completed_alert::alert_type:
4355 case lt::torrent_finished_alert::alert_type:
4356 case lt::save_resume_data_alert::alert_type:
4357 case lt::save_resume_data_failed_alert::alert_type:
4358 case lt::torrent_paused_alert::alert_type:
4359 case lt::torrent_resumed_alert::alert_type:
4360 case lt::tracker_error_alert::alert_type:
4361 case lt::tracker_reply_alert::alert_type:
4362 case lt::tracker_warning_alert::alert_type:
4363 case lt::fastresume_rejected_alert::alert_type:
4364 case lt::torrent_checked_alert::alert_type:
4365 case lt::metadata_received_alert::alert_type:
4366 dispatchTorrentAlert(a);
4367 break;
4368 case lt::state_update_alert::alert_type:
4369 handleStateUpdateAlert(static_cast<const lt::state_update_alert*>(a));
4370 break;
4371 case lt::session_stats_alert::alert_type:
4372 handleSessionStatsAlert(static_cast<const lt::session_stats_alert*>(a));
4373 break;
4374 case lt::file_error_alert::alert_type:
4375 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
4376 break;
4377 case lt::add_torrent_alert::alert_type:
4378 handleAddTorrentAlert(static_cast<const lt::add_torrent_alert*>(a));
4379 break;
4380 case lt::torrent_removed_alert::alert_type:
4381 handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert*>(a));
4382 break;
4383 case lt::torrent_deleted_alert::alert_type:
4384 handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert*>(a));
4385 break;
4386 case lt::torrent_delete_failed_alert::alert_type:
4387 handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert*>(a));
4388 break;
4389 case lt::portmap_error_alert::alert_type:
4390 handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert*>(a));
4391 break;
4392 case lt::portmap_alert::alert_type:
4393 handlePortmapAlert(static_cast<const lt::portmap_alert*>(a));
4394 break;
4395 case lt::peer_blocked_alert::alert_type:
4396 handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert*>(a));
4397 break;
4398 case lt::peer_ban_alert::alert_type:
4399 handlePeerBanAlert(static_cast<const lt::peer_ban_alert*>(a));
4400 break;
4401 case lt::url_seed_alert::alert_type:
4402 handleUrlSeedAlert(static_cast<const lt::url_seed_alert*>(a));
4403 break;
4404 case lt::listen_succeeded_alert::alert_type:
4405 handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert*>(a));
4406 break;
4407 case lt::listen_failed_alert::alert_type:
4408 handleListenFailedAlert(static_cast<const lt::listen_failed_alert*>(a));
4409 break;
4410 case lt::external_ip_alert::alert_type:
4411 handleExternalIPAlert(static_cast<const lt::external_ip_alert*>(a));
4412 break;
4413 case lt::alerts_dropped_alert::alert_type:
4414 handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(a));
4415 break;
4416 case lt::storage_moved_alert::alert_type:
4417 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert*>(a));
4418 break;
4419 case lt::storage_moved_failed_alert::alert_type:
4420 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert*>(a));
4421 break;
4422 case lt::socks5_alert::alert_type:
4423 handleSocks5Alert(static_cast<const lt::socks5_alert *>(a));
4424 break;
4427 catch (const std::exception &exc)
4429 qWarning() << "Caught exception in " << Q_FUNC_INFO << ": " << QString::fromStdString(exc.what());
4433 void Session::dispatchTorrentAlert(const lt::alert *a)
4435 TorrentImpl *const torrent = m_torrents.value(static_cast<const lt::torrent_alert*>(a)->handle.info_hash());
4436 if (torrent)
4438 torrent->handleAlert(a);
4439 return;
4442 switch (a->type())
4444 case lt::metadata_received_alert::alert_type:
4445 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
4446 break;
4450 void Session::createTorrent(const lt::torrent_handle &nativeHandle)
4452 #ifdef QBT_USES_LIBTORRENT2
4453 const auto torrentID = TorrentID::fromInfoHash(nativeHandle.info_hashes());
4454 #else
4455 const auto torrentID = TorrentID::fromInfoHash(nativeHandle.info_hash());
4456 #endif
4458 Q_ASSERT(m_loadingTorrents.contains(torrentID));
4460 const LoadTorrentParams params = m_loadingTorrents.take(torrentID);
4462 auto *const torrent = new TorrentImpl {this, m_nativeSession, nativeHandle, params};
4463 m_torrents.insert(torrent->id(), torrent);
4465 const bool hasMetadata = torrent->hasMetadata();
4467 if (params.restored)
4469 LogMsg(tr("'%1' restored.", "'torrent name' restored.").arg(torrent->name()));
4471 else
4473 m_resumeDataStorage->store(torrent->id(), params);
4475 // The following is useless for newly added magnet
4476 if (hasMetadata)
4478 // Copy the torrent file to the export folder
4479 if (!torrentExportDirectory().isEmpty())
4481 const TorrentInfo torrentInfo {params.ltAddTorrentParams.ti};
4482 exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
4486 if (isAddTrackersEnabled() && !torrent->isPrivate())
4487 torrent->addTrackers(m_additionalTrackerList);
4489 LogMsg(tr("'%1' added to download list.", "'torrent name' was added to download list.")
4490 .arg(torrent->name()));
4493 if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
4494 && !m_seedingLimitTimer->isActive())
4495 m_seedingLimitTimer->start();
4497 // Send torrent addition signal
4498 emit torrentLoaded(torrent);
4499 // Send new torrent signal
4500 if (!params.restored)
4501 emit torrentAdded(torrent);
4503 // Torrent could have error just after adding to libtorrent
4504 if (torrent->hasError())
4505 LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: %2.").arg(torrent->name(), torrent->error()), Log::WARNING);
4508 void Session::handleAddTorrentAlert(const lt::add_torrent_alert *p)
4510 if (p->error)
4512 const QString msg = QString::fromStdString(p->message());
4513 LogMsg(tr("Couldn't load torrent. Reason: %1.").arg(msg), Log::WARNING);
4514 emit loadTorrentFailed(msg);
4516 const lt::add_torrent_params &params = p->params;
4517 const bool hasMetadata = (params.ti && params.ti->is_valid());
4518 #ifdef QBT_USES_LIBTORRENT2
4519 const auto id = TorrentID::fromInfoHash(hasMetadata ? params.ti->info_hashes() : params.info_hashes);
4520 #else
4521 const auto id = TorrentID::fromInfoHash(hasMetadata ? params.ti->info_hash() : params.info_hash);
4522 #endif
4523 m_loadingTorrents.remove(id);
4525 else if (m_loadingTorrents.contains(p->handle.info_hash()))
4527 createTorrent(p->handle);
4531 void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
4533 #ifdef QBT_USES_LIBTORRENT2
4534 const auto id = TorrentID::fromInfoHash(p->info_hashes);
4535 #else
4536 const auto id = TorrentID::fromInfoHash(p->info_hash);
4537 #endif
4539 const auto removingTorrentDataIter = m_removingTorrents.find(id);
4540 if (removingTorrentDataIter != m_removingTorrents.end())
4542 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
4544 LogMsg(tr("'%1' was removed from the transfer list.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4545 m_removingTorrents.erase(removingTorrentDataIter);
4550 void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
4552 #ifdef QBT_USES_LIBTORRENT2
4553 const auto id = TorrentID::fromInfoHash(p->info_hashes);
4554 #else
4555 const auto id = TorrentID::fromInfoHash(p->info_hash);
4556 #endif
4558 const auto removingTorrentDataIter = m_removingTorrents.find(id);
4560 if (removingTorrentDataIter == m_removingTorrents.end())
4561 return;
4563 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
4564 LogMsg(tr("'%1' was removed from the transfer list and hard disk.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4565 m_removingTorrents.erase(removingTorrentDataIter);
4568 void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
4570 #ifdef QBT_USES_LIBTORRENT2
4571 const auto id = TorrentID::fromInfoHash(p->info_hashes);
4572 #else
4573 const auto id = TorrentID::fromInfoHash(p->info_hash);
4574 #endif
4576 const auto removingTorrentDataIter = m_removingTorrents.find(id);
4578 if (removingTorrentDataIter == m_removingTorrents.end())
4579 return;
4581 if (p->error)
4583 // libtorrent won't delete the directory if it contains files not listed in the torrent,
4584 // so we remove the directory ourselves
4585 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
4587 LogMsg(tr("'%1' was removed from the transfer list but the files couldn't be deleted. Error: %2", "'xxx.avi' was removed...")
4588 .arg(removingTorrentDataIter->name, QString::fromLocal8Bit(p->error.message().c_str()))
4589 , Log::WARNING);
4591 else // torrent without metadata, hence no files on disk
4593 LogMsg(tr("'%1' was removed from the transfer list.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4595 m_removingTorrents.erase(removingTorrentDataIter);
4598 void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
4600 #ifdef QBT_USES_LIBTORRENT2
4601 const auto id = TorrentID::fromInfoHash(p->handle.info_hashes());
4602 #else
4603 const auto id = TorrentID::fromInfoHash(p->handle.info_hash());
4604 #endif
4606 const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
4608 if (downloadedMetadataIter != m_downloadedMetadata.end())
4610 const TorrentInfo metadata {p->handle.torrent_file()};
4612 m_downloadedMetadata.erase(downloadedMetadataIter);
4613 --m_extraLimit;
4614 adjustLimits();
4615 m_nativeSession->remove_torrent(p->handle, lt::session::delete_files);
4617 emit metadataDownloaded(metadata);
4621 void Session::handleFileErrorAlert(const lt::file_error_alert *p)
4623 TorrentImpl *const torrent = m_torrents.value(p->handle.info_hash());
4624 if (!torrent)
4625 return;
4627 torrent->handleAlert(p);
4629 const TorrentID id = torrent->id();
4630 if (!m_recentErroredTorrents.contains(id))
4632 m_recentErroredTorrents.insert(id);
4634 const QString msg = QString::fromStdString(p->message());
4635 LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: %3")
4636 .arg(torrent->name(), p->filename(), msg)
4637 , Log::WARNING);
4638 emit fullDiskError(torrent, msg);
4641 m_recentErroredTorrentsTimer->start();
4644 void Session::handlePortmapWarningAlert(const lt::portmap_error_alert *p)
4646 LogMsg(tr("UPnP/NAT-PMP: Port mapping failure, message: %1").arg(QString::fromStdString(p->message())), Log::CRITICAL);
4649 void Session::handlePortmapAlert(const lt::portmap_alert *p)
4651 qDebug("UPnP Success, msg: %s", p->message().c_str());
4652 LogMsg(tr("UPnP/NAT-PMP: Port mapping successful, message: %1").arg(QString::fromStdString(p->message())), Log::INFO);
4655 void Session::handlePeerBlockedAlert(const lt::peer_blocked_alert *p)
4657 QString reason;
4658 switch (p->reason)
4660 case lt::peer_blocked_alert::ip_filter:
4661 reason = tr("IP filter", "this peer was blocked. Reason: IP filter.");
4662 break;
4663 case lt::peer_blocked_alert::port_filter:
4664 reason = tr("port filter", "this peer was blocked. Reason: port filter.");
4665 break;
4666 case lt::peer_blocked_alert::i2p_mixed:
4667 reason = tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg("I2P"); // don't translate I2P
4668 break;
4669 case lt::peer_blocked_alert::privileged_ports:
4670 reason = tr("use of privileged port", "this peer was blocked. Reason: use of privileged port.");
4671 break;
4672 case lt::peer_blocked_alert::utp_disabled:
4673 reason = tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(QString::fromUtf8(C_UTP)); // don't translate μTP
4674 break;
4675 case lt::peer_blocked_alert::tcp_disabled:
4676 reason = tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg("TCP"); // don't translate TCP
4677 break;
4680 const QString ip {toString(p->endpoint.address())};
4681 if (!ip.isEmpty())
4682 Logger::instance()->addPeer(ip, true, reason);
4685 void Session::handlePeerBanAlert(const lt::peer_ban_alert *p)
4687 const QString ip {toString(p->endpoint.address())};
4688 if (!ip.isEmpty())
4689 Logger::instance()->addPeer(ip, false);
4692 void Session::handleUrlSeedAlert(const lt::url_seed_alert *p)
4694 const TorrentImpl *torrent = m_torrents.value(p->handle.info_hash());
4695 if (!torrent)
4696 return;
4698 if (p->error)
4700 LogMsg(tr("URL seed name lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
4701 .arg(torrent->name(), p->server_url(), QString::fromStdString(p->message()))
4702 , Log::WARNING);
4704 else
4706 LogMsg(tr("Received error message from a URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
4707 .arg(torrent->name(), p->server_url(), p->error_message())
4708 , Log::WARNING);
4712 void Session::handleListenSucceededAlert(const lt::listen_succeeded_alert *p)
4714 const QString proto {toString(p->socket_type)};
4715 LogMsg(tr("Successfully listening on IP: %1, port: %2/%3"
4716 , "e.g: Successfully listening on IP: 192.168.0.1, port: TCP/6881")
4717 .arg(toString(p->address), proto, QString::number(p->port)), Log::INFO);
4719 // Force reannounce on all torrents because some trackers blacklist some ports
4720 reannounceToAllTrackers();
4723 void Session::handleListenFailedAlert(const lt::listen_failed_alert *p)
4725 const QString proto {toString(p->socket_type)};
4726 LogMsg(tr("Failed to listen on IP: %1, port: %2/%3. Reason: %4"
4727 , "e.g: Failed to listen on IP: 192.168.0.1, port: TCP/6881. Reason: already in use")
4728 .arg(toString(p->address), proto, QString::number(p->port)
4729 , QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
4732 void Session::handleExternalIPAlert(const lt::external_ip_alert *p)
4734 const QString externalIP {toString(p->external_address)};
4735 LogMsg(tr("Detected external IP: %1", "e.g. Detected external IP: 1.1.1.1")
4736 .arg(externalIP), Log::INFO);
4738 if (m_lastExternalIP != externalIP)
4740 if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty())
4741 reannounceToAllTrackers();
4742 m_lastExternalIP = externalIP;
4746 void Session::handleSessionStatsAlert(const lt::session_stats_alert *p)
4748 const qreal interval = lt::total_milliseconds(p->timestamp() - m_statsLastTimestamp) / 1000.;
4749 m_statsLastTimestamp = p->timestamp();
4751 const auto stats = p->counters();
4753 m_status.hasIncomingConnections = static_cast<bool>(stats[m_metricIndices.net.hasIncomingConnections]);
4755 const int64_t ipOverheadDownload = stats[m_metricIndices.net.recvIPOverheadBytes];
4756 const int64_t ipOverheadUpload = stats[m_metricIndices.net.sentIPOverheadBytes];
4757 const int64_t totalDownload = stats[m_metricIndices.net.recvBytes] + ipOverheadDownload;
4758 const int64_t totalUpload = stats[m_metricIndices.net.sentBytes] + ipOverheadUpload;
4759 const int64_t totalPayloadDownload = stats[m_metricIndices.net.recvPayloadBytes];
4760 const int64_t totalPayloadUpload = stats[m_metricIndices.net.sentPayloadBytes];
4761 const int64_t trackerDownload = stats[m_metricIndices.net.recvTrackerBytes];
4762 const int64_t trackerUpload = stats[m_metricIndices.net.sentTrackerBytes];
4763 const int64_t dhtDownload = stats[m_metricIndices.dht.dhtBytesIn];
4764 const int64_t dhtUpload = stats[m_metricIndices.dht.dhtBytesOut];
4766 auto calcRate = [interval](const quint64 previous, const quint64 current)
4768 Q_ASSERT(current >= previous);
4769 return static_cast<quint64>((current - previous) / interval);
4772 m_status.payloadDownloadRate = calcRate(m_status.totalPayloadDownload, totalPayloadDownload);
4773 m_status.payloadUploadRate = calcRate(m_status.totalPayloadUpload, totalPayloadUpload);
4774 m_status.downloadRate = calcRate(m_status.totalDownload, totalDownload);
4775 m_status.uploadRate = calcRate(m_status.totalUpload, totalUpload);
4776 m_status.ipOverheadDownloadRate = calcRate(m_status.ipOverheadDownload, ipOverheadDownload);
4777 m_status.ipOverheadUploadRate = calcRate(m_status.ipOverheadUpload, ipOverheadUpload);
4778 m_status.dhtDownloadRate = calcRate(m_status.dhtDownload, dhtDownload);
4779 m_status.dhtUploadRate = calcRate(m_status.dhtUpload, dhtUpload);
4780 m_status.trackerDownloadRate = calcRate(m_status.trackerDownload, trackerDownload);
4781 m_status.trackerUploadRate = calcRate(m_status.trackerUpload, trackerUpload);
4783 m_status.totalDownload = totalDownload;
4784 m_status.totalUpload = totalUpload;
4785 m_status.totalPayloadDownload = totalPayloadDownload;
4786 m_status.totalPayloadUpload = totalPayloadUpload;
4787 m_status.ipOverheadDownload = ipOverheadDownload;
4788 m_status.ipOverheadUpload = ipOverheadUpload;
4789 m_status.trackerDownload = trackerDownload;
4790 m_status.trackerUpload = trackerUpload;
4791 m_status.dhtDownload = dhtDownload;
4792 m_status.dhtUpload = dhtUpload;
4793 m_status.totalWasted = stats[m_metricIndices.net.recvRedundantBytes]
4794 + stats[m_metricIndices.net.recvFailedBytes];
4795 m_status.dhtNodes = stats[m_metricIndices.dht.dhtNodes];
4796 m_status.diskReadQueue = stats[m_metricIndices.peer.numPeersUpDisk];
4797 m_status.diskWriteQueue = stats[m_metricIndices.peer.numPeersDownDisk];
4798 m_status.peersCount = stats[m_metricIndices.peer.numPeersConnected];
4800 m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
4801 m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
4803 #ifndef QBT_USES_LIBTORRENT2
4804 const int64_t numBlocksRead = stats[m_metricIndices.disk.numBlocksRead];
4805 const int64_t numBlocksCacheHits = stats[m_metricIndices.disk.numBlocksCacheHits];
4806 m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max<int64_t>((numBlocksCacheHits + numBlocksRead), 1);
4807 #endif
4809 const int64_t totalJobs = stats[m_metricIndices.disk.writeJobs] + stats[m_metricIndices.disk.readJobs]
4810 + stats[m_metricIndices.disk.hashJobs];
4811 m_cacheStatus.averageJobTime = (totalJobs > 0)
4812 ? (stats[m_metricIndices.disk.diskJobTime] / totalJobs) : 0;
4814 emit statsUpdated();
4816 if (m_refreshEnqueued)
4817 m_refreshEnqueued = false;
4818 else
4819 enqueueRefresh();
4822 void Session::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
4824 LogMsg(tr("Error: Internal alert queue full and alerts were dropped, you might see degraded performance. Dropped alert types: %1. Message: %2")
4825 .arg(QString::fromStdString(p->dropped_alerts.to_string()), QString::fromStdString(p->message())), Log::CRITICAL);
4828 void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
4830 Q_ASSERT(!m_moveStorageQueue.isEmpty());
4832 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
4833 Q_ASSERT(currentJob.torrentHandle == p->handle);
4835 const QString newPath {p->storage_path()};
4836 Q_ASSERT(newPath == currentJob.path);
4838 #ifdef QBT_USES_LIBTORRENT2
4839 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
4840 #else
4841 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
4842 #endif
4844 TorrentImpl *torrent = m_torrents.value(id);
4845 const QString torrentName = (torrent ? torrent->name() : id.toString());
4846 LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath));
4848 handleMoveTorrentStorageJobFinished();
4851 void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
4853 Q_ASSERT(!m_moveStorageQueue.isEmpty());
4855 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
4856 Q_ASSERT(currentJob.torrentHandle == p->handle);
4858 #ifdef QBT_USES_LIBTORRENT2
4859 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
4860 #else
4861 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
4862 #endif
4864 TorrentImpl *torrent = m_torrents.value(id);
4865 const QString torrentName = (torrent ? torrent->name() : id.toString());
4866 const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path);
4867 const QString errorMessage = QString::fromStdString(p->message());
4868 LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.")
4869 .arg(torrentName, currentLocation, currentJob.path, errorMessage), Log::CRITICAL);
4871 handleMoveTorrentStorageJobFinished();
4874 void Session::handleStateUpdateAlert(const lt::state_update_alert *p)
4876 QVector<Torrent *> updatedTorrents;
4877 updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(p->status.size()));
4879 for (const lt::torrent_status &status : p->status)
4881 #ifdef QBT_USES_LIBTORRENT2
4882 const auto id = TorrentID::fromInfoHash(status.info_hashes);
4883 #else
4884 const auto id = TorrentID::fromInfoHash(status.info_hash);
4885 #endif
4886 TorrentImpl *const torrent = m_torrents.value(id);
4887 if (!torrent)
4888 continue;
4890 torrent->handleStateUpdate(status);
4891 updatedTorrents.push_back(torrent);
4894 if (!updatedTorrents.isEmpty())
4895 emit torrentsUpdated(updatedTorrents);
4897 if (m_refreshEnqueued)
4898 m_refreshEnqueued = false;
4899 else
4900 enqueueRefresh();
4903 void Session::handleSocks5Alert(const lt::socks5_alert *p) const
4905 if (p->error)
4907 LogMsg(tr("SOCKS5 proxy error. Message: %1").arg(QString::fromStdString(p->message()))
4908 , Log::WARNING);