Sync translations from Transifex and run lupdate
[qBittorrent.git] / src / base / bittorrent / sessionimpl.cpp
blob7ef150c0acbe05fc891ddee2ca563b3b989e3fb3
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 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 "sessionimpl.h"
32 #include <algorithm>
33 #include <chrono>
34 #include <cstdint>
35 #include <ctime>
36 #include <queue>
37 #include <string>
39 #ifdef Q_OS_WIN
40 #include <Windows.h>
41 #include <wincrypt.h>
42 #include <iphlpapi.h>
43 #endif
45 #include <boost/asio/ip/tcp.hpp>
47 #include <libtorrent/add_torrent_params.hpp>
48 #include <libtorrent/address.hpp>
49 #include <libtorrent/alert_types.hpp>
50 #include <libtorrent/error_code.hpp>
51 #include <libtorrent/extensions/smart_ban.hpp>
52 #include <libtorrent/extensions/ut_metadata.hpp>
53 #include <libtorrent/extensions/ut_pex.hpp>
54 #include <libtorrent/ip_filter.hpp>
55 #include <libtorrent/magnet_uri.hpp>
56 #include <libtorrent/session.hpp>
57 #include <libtorrent/session_stats.hpp>
58 #include <libtorrent/session_status.hpp>
59 #include <libtorrent/torrent_info.hpp>
61 #include <QDebug>
62 #include <QDir>
63 #include <QHostAddress>
64 #include <QJsonArray>
65 #include <QJsonDocument>
66 #include <QJsonObject>
67 #include <QJsonValue>
68 #include <QNetworkAddressEntry>
69 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
70 #include <QNetworkConfigurationManager>
71 #endif
72 #include <QNetworkInterface>
73 #include <QRegularExpression>
74 #include <QString>
75 #include <QThread>
76 #include <QThreadPool>
77 #include <QTimer>
78 #include <QUuid>
80 #include "base/algorithm.h"
81 #include "base/global.h"
82 #include "base/logger.h"
83 #include "base/net/downloadmanager.h"
84 #include "base/net/proxyconfigurationmanager.h"
85 #include "base/preferences.h"
86 #include "base/profile.h"
87 #include "base/torrentfileguard.h"
88 #include "base/torrentfilter.h"
89 #include "base/unicodestrings.h"
90 #include "base/utils/bytearray.h"
91 #include "base/utils/fs.h"
92 #include "base/utils/io.h"
93 #include "base/utils/misc.h"
94 #include "base/utils/net.h"
95 #include "base/utils/random.h"
96 #include "base/version.h"
97 #include "bandwidthscheduler.h"
98 #include "bencoderesumedatastorage.h"
99 #include "customstorage.h"
100 #include "dbresumedatastorage.h"
101 #include "downloadpriority.h"
102 #include "extensiondata.h"
103 #include "filesearcher.h"
104 #include "filterparserthread.h"
105 #include "loadtorrentparams.h"
106 #include "lttypecast.h"
107 #include "magneturi.h"
108 #include "nativesessionextension.h"
109 #include "portforwarderimpl.h"
110 #include "resumedatastorage.h"
111 #include "torrentimpl.h"
112 #include "tracker.h"
114 using namespace std::chrono_literals;
115 using namespace BitTorrent;
117 const Path CATEGORIES_FILE_NAME {u"categories.json"_s};
118 const int MAX_PROCESSING_RESUMEDATA_COUNT = 50;
119 const int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count();
121 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
122 namespace std
124 uint qHash(const std::string &key, uint seed = 0)
126 return ::qHash(std::hash<std::string> {}(key), seed);
130 namespace libtorrent
132 uint qHash(const libtorrent::torrent_handle &key)
134 return static_cast<uint>(libtorrent::hash_value(key));
137 #endif
139 namespace
141 const char PEER_ID[] = "qB";
142 const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2);
144 void torrentQueuePositionUp(const lt::torrent_handle &handle)
148 handle.queue_position_up();
150 catch (const std::exception &exc)
152 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
156 void torrentQueuePositionDown(const lt::torrent_handle &handle)
160 handle.queue_position_down();
162 catch (const std::exception &exc)
164 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
168 void torrentQueuePositionTop(const lt::torrent_handle &handle)
172 handle.queue_position_top();
174 catch (const std::exception &exc)
176 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
180 void torrentQueuePositionBottom(const lt::torrent_handle &handle)
184 handle.queue_position_bottom();
186 catch (const std::exception &exc)
188 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
192 QMap<QString, CategoryOptions> expandCategories(const QMap<QString, CategoryOptions> &categories)
194 QMap<QString, CategoryOptions> 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 u"HTTP"_s;
216 case lt::socket_type_t::http_ssl:
217 return u"HTTP_SSL"_s;
218 #endif
219 case lt::socket_type_t::i2p:
220 return u"I2P"_s;
221 case lt::socket_type_t::socks5:
222 return u"SOCKS5"_s;
223 #ifdef QBT_USES_LIBTORRENT2
224 case lt::socket_type_t::socks5_ssl:
225 return u"SOCKS5_SSL"_s;
226 #endif
227 case lt::socket_type_t::tcp:
228 return u"TCP"_s;
229 case lt::socket_type_t::tcp_ssl:
230 return u"TCP_SSL"_s;
231 #ifdef QBT_USES_LIBTORRENT2
232 case lt::socket_type_t::utp:
233 return u"UTP"_s;
234 #else
235 case lt::socket_type_t::udp:
236 return u"UDP"_s;
237 #endif
238 case lt::socket_type_t::utp_ssl:
239 return u"UTP_SSL"_s;
241 return u"INVALID"_s;
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 const std::wstring nameWStr = name.toStdWString();
309 NET_LUID luid {};
310 const LONG res = ::ConvertInterfaceNameToLuidW(nameWStr.c_str(), &luid);
311 if (res == 0)
313 GUID guid;
314 if (::ConvertInterfaceLuidToGuid(&luid, &guid) == 0)
315 return QUuid(guid).toString().toUpper();
318 return {};
320 #endif
322 constexpr lt::move_flags_t toNative(const MoveStorageMode mode)
324 switch (mode)
326 default:
327 Q_ASSERT(false);
328 case MoveStorageMode::FailIfExist:
329 return lt::move_flags_t::fail_if_exist;
330 case MoveStorageMode::KeepExistingFiles:
331 return lt::move_flags_t::dont_replace;
332 case MoveStorageMode::Overwrite:
333 return lt::move_flags_t::always_replace_files;
338 struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject
340 using QObject::QObject;
342 ResumeDataStorage *startupStorage = nullptr;
343 ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy;
344 QList<LoadedResumeData> loadedResumeData;
345 int processingResumeDataCount = 0;
346 int64_t totalResumeDataCount = 0;
347 int64_t finishedResumeDataCount = 0;
348 bool isLoadFinished = false;
349 bool isLoadedResumeDataHandlingEnqueued = false;
350 QSet<QString> recoveredCategories;
351 #ifdef QBT_USES_LIBTORRENT2
352 QSet<TorrentID> indexedTorrents;
353 QSet<TorrentID> skippedIDs;
354 #endif
357 const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
359 Session *SessionImpl::m_instance = nullptr;
361 void Session::initInstance()
363 if (!SessionImpl::m_instance)
364 SessionImpl::m_instance = new SessionImpl;
367 void Session::freeInstance()
369 delete SessionImpl::m_instance;
370 SessionImpl::m_instance = nullptr;
373 Session *Session::instance()
375 return SessionImpl::m_instance;
378 bool Session::isValidCategoryName(const QString &name)
380 const QRegularExpression re {uR"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)"_s};
381 return (name.isEmpty() || (name.indexOf(re) == 0));
384 bool Session::isValidTag(const QString &tag)
386 return (!tag.trimmed().isEmpty() && !tag.contains(u','));
389 QStringList Session::expandCategory(const QString &category)
391 QStringList result;
392 int index = 0;
393 while ((index = category.indexOf(u'/', index)) >= 0)
395 result << category.left(index);
396 ++index;
398 result << category;
400 return result;
403 #define BITTORRENT_KEY(name) u"BitTorrent/" name
404 #define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY(u"Session/") name
406 SessionImpl::SessionImpl(QObject *parent)
407 : Session(parent)
408 , m_isDHTEnabled(BITTORRENT_SESSION_KEY(u"DHTEnabled"_s), true)
409 , m_isLSDEnabled(BITTORRENT_SESSION_KEY(u"LSDEnabled"_s), true)
410 , m_isPeXEnabled(BITTORRENT_SESSION_KEY(u"PeXEnabled"_s), true)
411 , m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY(u"IPFilteringEnabled"_s), false)
412 , m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY(u"TrackerFilteringEnabled"_s), false)
413 , m_IPFilterFile(BITTORRENT_SESSION_KEY(u"IPFilter"_s))
414 , m_announceToAllTrackers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTrackers"_s), false)
415 , m_announceToAllTiers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTiers"_s), true)
416 , m_asyncIOThreads(BITTORRENT_SESSION_KEY(u"AsyncIOThreadsCount"_s), 10)
417 , m_hashingThreads(BITTORRENT_SESSION_KEY(u"HashingThreadsCount"_s), 1)
418 , m_filePoolSize(BITTORRENT_SESSION_KEY(u"FilePoolSize"_s), 100)
419 , m_checkingMemUsage(BITTORRENT_SESSION_KEY(u"CheckingMemUsageSize"_s), 32)
420 , m_diskCacheSize(BITTORRENT_SESSION_KEY(u"DiskCacheSize"_s), -1)
421 , m_diskCacheTTL(BITTORRENT_SESSION_KEY(u"DiskCacheTTL"_s), 60)
422 , m_diskQueueSize(BITTORRENT_SESSION_KEY(u"DiskQueueSize"_s), (1024 * 1024))
423 , m_diskIOType(BITTORRENT_SESSION_KEY(u"DiskIOType"_s), DiskIOType::Default)
424 , m_diskIOReadMode(BITTORRENT_SESSION_KEY(u"DiskIOReadMode"_s), DiskIOReadMode::EnableOSCache)
425 , m_diskIOWriteMode(BITTORRENT_SESSION_KEY(u"DiskIOWriteMode"_s), DiskIOWriteMode::EnableOSCache)
426 #ifdef Q_OS_WIN
427 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_s), true)
428 #else
429 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_s), false)
430 #endif
431 , m_usePieceExtentAffinity(BITTORRENT_SESSION_KEY(u"PieceExtentAffinity"_s), false)
432 , m_isSuggestMode(BITTORRENT_SESSION_KEY(u"SuggestMode"_s), false)
433 , m_sendBufferWatermark(BITTORRENT_SESSION_KEY(u"SendBufferWatermark"_s), 500)
434 , m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY(u"SendBufferLowWatermark"_s), 10)
435 , m_sendBufferWatermarkFactor(BITTORRENT_SESSION_KEY(u"SendBufferWatermarkFactor"_s), 50)
436 , m_connectionSpeed(BITTORRENT_SESSION_KEY(u"ConnectionSpeed"_s), 30)
437 , m_socketSendBufferSize(BITTORRENT_SESSION_KEY(u"SocketSendBufferSize"_s), 0)
438 , m_socketReceiveBufferSize(BITTORRENT_SESSION_KEY(u"SocketReceiveBufferSize"_s), 0)
439 , m_socketBacklogSize(BITTORRENT_SESSION_KEY(u"SocketBacklogSize"_s), 30)
440 , m_isAnonymousModeEnabled(BITTORRENT_SESSION_KEY(u"AnonymousModeEnabled"_s), false)
441 , m_isQueueingEnabled(BITTORRENT_SESSION_KEY(u"QueueingSystemEnabled"_s), false)
442 , m_maxActiveDownloads(BITTORRENT_SESSION_KEY(u"MaxActiveDownloads"_s), 3, lowerLimited(-1))
443 , m_maxActiveUploads(BITTORRENT_SESSION_KEY(u"MaxActiveUploads"_s), 3, lowerLimited(-1))
444 , m_maxActiveTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveTorrents"_s), 5, lowerLimited(-1))
445 , m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY(u"IgnoreSlowTorrentsForQueueing"_s), false)
446 , m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsDownloadRate"_s), 2)
447 , m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsUploadRate"_s), 2)
448 , m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY(u"SlowTorrentsInactivityTimer"_s), 60)
449 , m_outgoingPortsMin(BITTORRENT_SESSION_KEY(u"OutgoingPortsMin"_s), 0)
450 , m_outgoingPortsMax(BITTORRENT_SESSION_KEY(u"OutgoingPortsMax"_s), 0)
451 , m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY(u"UPnPLeaseDuration"_s), 0)
452 , m_peerToS(BITTORRENT_SESSION_KEY(u"PeerToS"_s), 0x04)
453 , m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY(u"IgnoreLimitsOnLAN"_s), false)
454 , m_includeOverheadInLimits(BITTORRENT_SESSION_KEY(u"IncludeOverheadInLimits"_s), false)
455 , m_announceIP(BITTORRENT_SESSION_KEY(u"AnnounceIP"_s))
456 , m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY(u"MaxConcurrentHTTPAnnounces"_s), 50)
457 , m_isReannounceWhenAddressChangedEnabled(BITTORRENT_SESSION_KEY(u"ReannounceWhenAddressChanged"_s), false)
458 , m_stopTrackerTimeout(BITTORRENT_SESSION_KEY(u"StopTrackerTimeout"_s), 2)
459 , m_maxConnections(BITTORRENT_SESSION_KEY(u"MaxConnections"_s), 500, lowerLimited(0, -1))
460 , m_maxUploads(BITTORRENT_SESSION_KEY(u"MaxUploads"_s), 20, lowerLimited(0, -1))
461 , m_maxConnectionsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxConnectionsPerTorrent"_s), 100, lowerLimited(0, -1))
462 , m_maxUploadsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxUploadsPerTorrent"_s), 4, lowerLimited(0, -1))
463 , m_btProtocol(BITTORRENT_SESSION_KEY(u"BTProtocol"_s), BTProtocol::Both
464 , clampValue(BTProtocol::Both, BTProtocol::UTP))
465 , m_isUTPRateLimited(BITTORRENT_SESSION_KEY(u"uTPRateLimited"_s), true)
466 , m_utpMixedMode(BITTORRENT_SESSION_KEY(u"uTPMixedMode"_s), MixedModeAlgorithm::TCP
467 , clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
468 , m_IDNSupportEnabled(BITTORRENT_SESSION_KEY(u"IDNSupportEnabled"_s), false)
469 , m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY(u"MultiConnectionsPerIp"_s), false)
470 , m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY(u"ValidateHTTPSTrackerCertificate"_s), true)
471 , m_SSRFMitigationEnabled(BITTORRENT_SESSION_KEY(u"SSRFMitigation"_s), true)
472 , m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY(u"BlockPeersOnPrivilegedPorts"_s), false)
473 , m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersEnabled"_s), false)
474 , m_additionalTrackers(BITTORRENT_SESSION_KEY(u"AdditionalTrackers"_s))
475 , m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_s), -1, [](qreal r) { return r < 0 ? -1. : r;})
476 , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s), -1, lowerLimited(-1))
477 , m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), -1, lowerLimited(-1))
478 , m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_s), false)
479 , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY(u"AddTorrentPaused"_s), false)
480 , m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_s), Torrent::StopCondition::None)
481 , m_torrentContentLayout(BITTORRENT_SESSION_KEY(u"TorrentContentLayout"_s), TorrentContentLayout::Original)
482 , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY(u"AddExtensionToIncompleteFiles"_s), false)
483 , m_refreshInterval(BITTORRENT_SESSION_KEY(u"RefreshInterval"_s), 1500)
484 , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY(u"Preallocation"_s), false)
485 , m_torrentExportDirectory(BITTORRENT_SESSION_KEY(u"TorrentExportDirectory"_s))
486 , m_finishedTorrentExportDirectory(BITTORRENT_SESSION_KEY(u"FinishedTorrentExportDirectory"_s))
487 , m_globalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalDLSpeedLimit"_s), 0, lowerLimited(0))
488 , m_globalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalUPSpeedLimit"_s), 0, lowerLimited(0))
489 , m_altGlobalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalDLSpeedLimit"_s), 10, lowerLimited(0))
490 , m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalUPSpeedLimit"_s), 10, lowerLimited(0))
491 , m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY(u"UseAlternativeGlobalSpeedLimit"_s), false)
492 , m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY(u"BandwidthSchedulerEnabled"_s), false)
493 , m_isPerformanceWarningEnabled(BITTORRENT_SESSION_KEY(u"PerformanceWarning"_s), false)
494 , m_saveResumeDataInterval(BITTORRENT_SESSION_KEY(u"SaveResumeDataInterval"_s), 60)
495 , m_port(BITTORRENT_SESSION_KEY(u"Port"_s), -1)
496 , m_networkInterface(BITTORRENT_SESSION_KEY(u"Interface"_s))
497 , m_networkInterfaceName(BITTORRENT_SESSION_KEY(u"InterfaceName"_s))
498 , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY(u"InterfaceAddress"_s))
499 , m_encryption(BITTORRENT_SESSION_KEY(u"Encryption"_s), 0)
500 , m_maxActiveCheckingTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveCheckingTorrents"_s), 1)
501 , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY(u"ProxyPeerConnections"_s), false)
502 , m_chokingAlgorithm(BITTORRENT_SESSION_KEY(u"ChokingAlgorithm"_s), ChokingAlgorithm::FixedSlots
503 , clampValue(ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased))
504 , m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY(u"SeedChokingAlgorithm"_s), SeedChokingAlgorithm::FastestUpload
505 , clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
506 , m_storedTags(BITTORRENT_SESSION_KEY(u"Tags"_s))
507 , m_maxRatioAction(BITTORRENT_SESSION_KEY(u"MaxRatioAction"_s), Pause)
508 , m_savePath(BITTORRENT_SESSION_KEY(u"DefaultSavePath"_s), specialFolderLocation(SpecialFolder::Downloads))
509 , m_downloadPath(BITTORRENT_SESSION_KEY(u"TempPath"_s), (savePath() / Path(u"temp"_s)))
510 , m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY(u"TempPathEnabled"_s), false)
511 , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY(u"SubcategoriesEnabled"_s), false)
512 , m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY(u"UseCategoryPathsInManualMode"_s), false)
513 , m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY(u"DisableAutoTMMByDefault"_s), true)
514 , m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategoryChanged"_s), false)
515 , m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/DefaultSavePathChanged"_s), true)
516 , m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategorySavePathChanged"_s), true)
517 , m_isTrackerEnabled(BITTORRENT_KEY(u"TrackerEnabled"_s), false)
518 , m_peerTurnover(BITTORRENT_SESSION_KEY(u"PeerTurnover"_s), 4)
519 , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY(u"PeerTurnoverCutOff"_s), 90)
520 , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY(u"PeerTurnoverInterval"_s), 300)
521 , m_requestQueueSize(BITTORRENT_SESSION_KEY(u"RequestQueueSize"_s), 500)
522 , m_isExcludedFileNamesEnabled(BITTORRENT_KEY(u"ExcludedFileNamesEnabled"_s), false)
523 , m_excludedFileNames(BITTORRENT_SESSION_KEY(u"ExcludedFileNames"_s))
524 , m_bannedIPs(u"State/BannedIPs"_s, QStringList(), Algorithm::sorted<QStringList>)
525 , m_resumeDataStorageType(BITTORRENT_SESSION_KEY(u"ResumeDataStorageType"_s), ResumeDataStorageType::Legacy)
526 , m_isMergeTrackersEnabled(BITTORRENT_KEY(u"MergeTrackersEnabled"_s), false)
527 , m_isI2PEnabled {BITTORRENT_SESSION_KEY(u"I2P/Enabled"_s), false}
528 , m_I2PAddress {BITTORRENT_SESSION_KEY(u"I2P/Address"_s), u"127.0.0.1"_s}
529 , m_I2PPort {BITTORRENT_SESSION_KEY(u"I2P/Port"_s), 7656}
530 , m_I2PMixedMode {BITTORRENT_SESSION_KEY(u"I2P/MixedMode"_s), false}
531 , m_I2PInboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/InboundQuantity"_s), 3}
532 , m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
533 , m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
534 , m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
535 , m_seedingLimitTimer {new QTimer(this)}
536 , m_resumeDataTimer {new QTimer(this)}
537 , m_ioThread {new QThread}
538 , m_asyncWorker {new QThreadPool(this)}
539 , m_recentErroredTorrentsTimer {new QTimer(this)}
540 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
541 , m_networkManager {new QNetworkConfigurationManager(this)}
542 #endif
544 // It is required to perform async access to libtorrent sequentially
545 m_asyncWorker->setMaxThreadCount(1);
547 if (port() < 0)
548 m_port = Utils::Random::rand(1024, 65535);
550 m_recentErroredTorrentsTimer->setSingleShot(true);
551 m_recentErroredTorrentsTimer->setInterval(1s);
552 connect(m_recentErroredTorrentsTimer, &QTimer::timeout
553 , this, [this]() { m_recentErroredTorrents.clear(); });
555 m_seedingLimitTimer->setInterval(10s);
556 connect(m_seedingLimitTimer, &QTimer::timeout, this, &SessionImpl::processShareLimits);
558 initializeNativeSession();
559 configureComponents();
561 if (isBandwidthSchedulerEnabled())
562 enableBandwidthScheduler();
564 loadCategories();
565 if (isSubcategoriesEnabled())
567 // if subcategories support changed manually
568 m_categories = expandCategories(m_categories);
571 const QStringList storedTags = m_storedTags.get();
572 m_tags = {storedTags.cbegin(), storedTags.cend()};
574 updateSeedingLimitTimer();
575 populateAdditionalTrackers();
576 if (isExcludedFileNamesEnabled())
577 populateExcludedFileNamesRegExpList();
579 connect(Net::ProxyConfigurationManager::instance()
580 , &Net::ProxyConfigurationManager::proxyConfigurationChanged
581 , this, &SessionImpl::configureDeferred);
583 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
584 // Network configuration monitor
585 connect(m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &SessionImpl::networkOnlineStateChanged);
586 connect(m_networkManager, &QNetworkConfigurationManager::configurationAdded, this, &SessionImpl::networkConfigurationChange);
587 connect(m_networkManager, &QNetworkConfigurationManager::configurationRemoved, this, &SessionImpl::networkConfigurationChange);
588 connect(m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &SessionImpl::networkConfigurationChange);
589 #endif
591 m_fileSearcher = new FileSearcher;
592 m_fileSearcher->moveToThread(m_ioThread.get());
593 connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
594 connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
596 m_ioThread->start();
598 initMetrics();
599 loadStatistics();
601 // initialize PortForwarder instance
602 new PortForwarderImpl(this);
604 // start embedded tracker
605 enableTracker(isTrackerEnabled());
607 prepareStartup();
610 SessionImpl::~SessionImpl()
612 m_nativeSession->pause();
614 if (m_torrentsQueueChanged)
616 m_nativeSession->post_torrent_updates({});
617 m_torrentsQueueChanged = false;
618 m_needSaveTorrentsQueue = true;
621 // Do some bittorrent related saving
622 // After this, (ideally) no more important alerts will be generated/handled
623 saveResumeData();
625 saveStatistics();
627 // We must delete FilterParserThread
628 // before we delete lt::session
629 delete m_filterParser;
631 // We must delete PortForwarderImpl before
632 // we delete lt::session
633 delete Net::PortForwarder::instance();
635 // We must stop "async worker" only after deletion
636 // of all the components that could potentially use it
637 m_asyncWorker->clear();
638 m_asyncWorker->waitForDone();
640 qDebug("Deleting libtorrent session...");
641 delete m_nativeSession;
644 bool SessionImpl::isDHTEnabled() const
646 return m_isDHTEnabled;
649 void SessionImpl::setDHTEnabled(bool enabled)
651 if (enabled != m_isDHTEnabled)
653 m_isDHTEnabled = enabled;
654 configureDeferred();
655 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(enabled ? tr("ON") : tr("OFF")), Log::INFO);
659 bool SessionImpl::isLSDEnabled() const
661 return m_isLSDEnabled;
664 void SessionImpl::setLSDEnabled(const bool enabled)
666 if (enabled != m_isLSDEnabled)
668 m_isLSDEnabled = enabled;
669 configureDeferred();
670 LogMsg(tr("Local Peer Discovery support: %1").arg(enabled ? tr("ON") : tr("OFF"))
671 , Log::INFO);
675 bool SessionImpl::isPeXEnabled() const
677 return m_isPeXEnabled;
680 void SessionImpl::setPeXEnabled(const bool enabled)
682 m_isPeXEnabled = enabled;
683 if (m_wasPexEnabled != enabled)
684 LogMsg(tr("Restart is required to toggle Peer Exchange (PeX) support"), Log::WARNING);
687 bool SessionImpl::isDownloadPathEnabled() const
689 return m_isDownloadPathEnabled;
692 void SessionImpl::setDownloadPathEnabled(const bool enabled)
694 if (enabled != isDownloadPathEnabled())
696 m_isDownloadPathEnabled = enabled;
697 for (TorrentImpl *const torrent : asConst(m_torrents))
698 torrent->handleCategoryOptionsChanged();
702 bool SessionImpl::isAppendExtensionEnabled() const
704 return m_isAppendExtensionEnabled;
707 void SessionImpl::setAppendExtensionEnabled(const bool enabled)
709 if (isAppendExtensionEnabled() != enabled)
711 m_isAppendExtensionEnabled = enabled;
713 // append or remove .!qB extension for incomplete files
714 for (TorrentImpl *const torrent : asConst(m_torrents))
715 torrent->handleAppendExtensionToggled();
719 int SessionImpl::refreshInterval() const
721 return m_refreshInterval;
724 void SessionImpl::setRefreshInterval(const int value)
726 if (value != refreshInterval())
728 m_refreshInterval = value;
732 bool SessionImpl::isPreallocationEnabled() const
734 return m_isPreallocationEnabled;
737 void SessionImpl::setPreallocationEnabled(const bool enabled)
739 m_isPreallocationEnabled = enabled;
742 Path SessionImpl::torrentExportDirectory() const
744 return m_torrentExportDirectory;
747 void SessionImpl::setTorrentExportDirectory(const Path &path)
749 if (path != torrentExportDirectory())
750 m_torrentExportDirectory = path;
753 Path SessionImpl::finishedTorrentExportDirectory() const
755 return m_finishedTorrentExportDirectory;
758 void SessionImpl::setFinishedTorrentExportDirectory(const Path &path)
760 if (path != finishedTorrentExportDirectory())
761 m_finishedTorrentExportDirectory = path;
764 Path SessionImpl::savePath() const
766 // TODO: Make sure it is always non-empty
767 return m_savePath;
770 Path SessionImpl::downloadPath() const
772 // TODO: Make sure it is always non-empty
773 return m_downloadPath;
776 QStringList SessionImpl::categories() const
778 return m_categories.keys();
781 CategoryOptions SessionImpl::categoryOptions(const QString &categoryName) const
783 return m_categories.value(categoryName);
786 Path SessionImpl::categorySavePath(const QString &categoryName) const
788 const Path basePath = savePath();
789 if (categoryName.isEmpty())
790 return basePath;
792 Path path = m_categories.value(categoryName).savePath;
793 if (path.isEmpty()) // use implicit save path
794 path = Utils::Fs::toValidPath(categoryName);
796 return (path.isAbsolute() ? path : (basePath / path));
799 Path SessionImpl::categoryDownloadPath(const QString &categoryName) const
801 const CategoryOptions categoryOptions = m_categories.value(categoryName);
802 const CategoryOptions::DownloadPathOption downloadPathOption =
803 categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
804 if (!downloadPathOption.enabled)
805 return {};
807 const Path basePath = downloadPath();
808 if (categoryName.isEmpty())
809 return basePath;
811 const Path path = (!downloadPathOption.path.isEmpty()
812 ? downloadPathOption.path
813 : Utils::Fs::toValidPath(categoryName)); // use implicit download path
815 return (path.isAbsolute() ? path : (basePath / path));
818 bool SessionImpl::addCategory(const QString &name, const CategoryOptions &options)
820 if (name.isEmpty())
821 return false;
823 if (!isValidCategoryName(name) || m_categories.contains(name))
824 return false;
826 if (isSubcategoriesEnabled())
828 for (const QString &parent : asConst(expandCategory(name)))
830 if ((parent != name) && !m_categories.contains(parent))
832 m_categories[parent] = {};
833 emit categoryAdded(parent);
838 m_categories[name] = options;
839 storeCategories();
840 emit categoryAdded(name);
842 return true;
845 bool SessionImpl::editCategory(const QString &name, const CategoryOptions &options)
847 const auto it = m_categories.find(name);
848 if (it == m_categories.end())
849 return false;
851 CategoryOptions &currentOptions = it.value();
852 if (options == currentOptions)
853 return false;
855 currentOptions = options;
856 storeCategories();
857 if (isDisableAutoTMMWhenCategorySavePathChanged())
859 for (TorrentImpl *const torrent : asConst(m_torrents))
861 if (torrent->category() == name)
862 torrent->setAutoTMMEnabled(false);
865 else
867 for (TorrentImpl *const torrent : asConst(m_torrents))
869 if (torrent->category() == name)
870 torrent->handleCategoryOptionsChanged();
874 emit categoryOptionsChanged(name);
875 return true;
878 bool SessionImpl::removeCategory(const QString &name)
880 for (TorrentImpl *const torrent : asConst(m_torrents))
882 if (torrent->belongsToCategory(name))
883 torrent->setCategory(u""_s);
886 // remove stored category and its subcategories if exist
887 bool result = false;
888 if (isSubcategoriesEnabled())
890 // remove subcategories
891 const QString test = name + u'/';
892 Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const CategoryOptions &)
894 if (category.startsWith(test))
896 result = true;
897 emit categoryRemoved(category);
898 return true;
900 return false;
904 result = (m_categories.remove(name) > 0) || result;
906 if (result)
908 // update stored categories
909 storeCategories();
910 emit categoryRemoved(name);
913 return result;
916 bool SessionImpl::isSubcategoriesEnabled() const
918 return m_isSubcategoriesEnabled;
921 void SessionImpl::setSubcategoriesEnabled(const bool value)
923 if (isSubcategoriesEnabled() == value) return;
925 if (value)
927 // expand categories to include all parent categories
928 m_categories = expandCategories(m_categories);
929 // update stored categories
930 storeCategories();
932 else
934 // reload categories
935 loadCategories();
938 m_isSubcategoriesEnabled = value;
939 emit subcategoriesSupportChanged();
942 bool SessionImpl::useCategoryPathsInManualMode() const
944 return m_useCategoryPathsInManualMode;
947 void SessionImpl::setUseCategoryPathsInManualMode(const bool value)
949 m_useCategoryPathsInManualMode = value;
952 Path SessionImpl::suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const
954 const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
955 const auto path = (useCategoryPaths ? categorySavePath(categoryName) : savePath());
956 return path;
959 Path SessionImpl::suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const
961 const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
962 const auto categoryDownloadPath = this->categoryDownloadPath(categoryName);
963 const auto path = ((useCategoryPaths && !categoryDownloadPath.isEmpty()) ? categoryDownloadPath : downloadPath());
964 return path;
967 QSet<QString> SessionImpl::tags() const
969 return m_tags;
972 bool SessionImpl::hasTag(const QString &tag) const
974 return m_tags.contains(tag);
977 bool SessionImpl::addTag(const QString &tag)
979 if (!isValidTag(tag) || hasTag(tag))
980 return false;
982 m_tags.insert(tag);
983 m_storedTags = m_tags.values();
984 emit tagAdded(tag);
985 return true;
988 bool SessionImpl::removeTag(const QString &tag)
990 if (m_tags.remove(tag))
992 for (TorrentImpl *const torrent : asConst(m_torrents))
993 torrent->removeTag(tag);
994 m_storedTags = m_tags.values();
995 emit tagRemoved(tag);
996 return true;
998 return false;
1001 bool SessionImpl::isAutoTMMDisabledByDefault() const
1003 return m_isAutoTMMDisabledByDefault;
1006 void SessionImpl::setAutoTMMDisabledByDefault(const bool value)
1008 m_isAutoTMMDisabledByDefault = value;
1011 bool SessionImpl::isDisableAutoTMMWhenCategoryChanged() const
1013 return m_isDisableAutoTMMWhenCategoryChanged;
1016 void SessionImpl::setDisableAutoTMMWhenCategoryChanged(const bool value)
1018 m_isDisableAutoTMMWhenCategoryChanged = value;
1021 bool SessionImpl::isDisableAutoTMMWhenDefaultSavePathChanged() const
1023 return m_isDisableAutoTMMWhenDefaultSavePathChanged;
1026 void SessionImpl::setDisableAutoTMMWhenDefaultSavePathChanged(const bool value)
1028 m_isDisableAutoTMMWhenDefaultSavePathChanged = value;
1031 bool SessionImpl::isDisableAutoTMMWhenCategorySavePathChanged() const
1033 return m_isDisableAutoTMMWhenCategorySavePathChanged;
1036 void SessionImpl::setDisableAutoTMMWhenCategorySavePathChanged(const bool value)
1038 m_isDisableAutoTMMWhenCategorySavePathChanged = value;
1041 bool SessionImpl::isAddTorrentToQueueTop() const
1043 return m_isAddTorrentToQueueTop;
1046 void SessionImpl::setAddTorrentToQueueTop(bool value)
1048 m_isAddTorrentToQueueTop = value;
1051 bool SessionImpl::isAddTorrentPaused() const
1053 return m_isAddTorrentPaused;
1056 void SessionImpl::setAddTorrentPaused(const bool value)
1058 m_isAddTorrentPaused = value;
1061 Torrent::StopCondition SessionImpl::torrentStopCondition() const
1063 return m_torrentStopCondition;
1066 void SessionImpl::setTorrentStopCondition(const Torrent::StopCondition stopCondition)
1068 m_torrentStopCondition = stopCondition;
1071 bool SessionImpl::isTrackerEnabled() const
1073 return m_isTrackerEnabled;
1076 void SessionImpl::setTrackerEnabled(const bool enabled)
1078 if (m_isTrackerEnabled != enabled)
1079 m_isTrackerEnabled = enabled;
1081 // call enableTracker() unconditionally, otherwise port change won't trigger
1082 // tracker restart
1083 enableTracker(enabled);
1086 qreal SessionImpl::globalMaxRatio() const
1088 return m_globalMaxRatio;
1091 // Torrents with a ratio superior to the given value will
1092 // be automatically deleted
1093 void SessionImpl::setGlobalMaxRatio(qreal ratio)
1095 if (ratio < 0)
1096 ratio = -1.;
1098 if (ratio != globalMaxRatio())
1100 m_globalMaxRatio = ratio;
1101 updateSeedingLimitTimer();
1105 int SessionImpl::globalMaxSeedingMinutes() const
1107 return m_globalMaxSeedingMinutes;
1110 void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
1112 if (minutes < 0)
1113 minutes = -1;
1115 if (minutes != globalMaxSeedingMinutes())
1117 m_globalMaxSeedingMinutes = minutes;
1118 updateSeedingLimitTimer();
1122 int SessionImpl::globalMaxInactiveSeedingMinutes() const
1124 return m_globalMaxInactiveSeedingMinutes;
1127 void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
1129 minutes = std::max(minutes, -1);
1131 if (minutes != globalMaxInactiveSeedingMinutes())
1133 m_globalMaxInactiveSeedingMinutes = minutes;
1134 updateSeedingLimitTimer();
1138 void SessionImpl::applyBandwidthLimits()
1140 lt::settings_pack settingsPack;
1141 settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
1142 settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
1143 m_nativeSession->apply_settings(std::move(settingsPack));
1146 void SessionImpl::configure()
1148 m_nativeSession->apply_settings(loadLTSettings());
1149 configureComponents();
1151 m_deferredConfigureScheduled = false;
1154 void SessionImpl::configureComponents()
1156 // This function contains components/actions that:
1157 // 1. Need to be setup at start up
1158 // 2. When deferred configure is called
1160 configurePeerClasses();
1162 if (!m_IPFilteringConfigured)
1164 if (isIPFilteringEnabled())
1165 enableIPFilter();
1166 else
1167 disableIPFilter();
1168 m_IPFilteringConfigured = true;
1172 void SessionImpl::prepareStartup()
1174 qDebug("Initializing torrents resume data storage...");
1176 const Path dbPath = specialFolderLocation(SpecialFolder::Data) / Path(u"torrents.db"_s);
1177 const bool dbStorageExists = dbPath.exists();
1179 auto *context = new ResumeSessionContext(this);
1180 context->currentStorageType = resumeDataStorageType();
1182 if (context->currentStorageType == ResumeDataStorageType::SQLite)
1184 m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
1186 if (!dbStorageExists)
1188 const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_s);
1189 context->startupStorage = new BencodeResumeDataStorage(dataPath, this);
1192 else
1194 const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_s);
1195 m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
1197 if (dbStorageExists)
1198 context->startupStorage = new DBResumeDataStorage(dbPath, this);
1201 if (!context->startupStorage)
1202 context->startupStorage = m_resumeDataStorage;
1204 connect(context->startupStorage, &ResumeDataStorage::loadStarted, context
1205 , [this, context](const QVector<TorrentID> &torrents)
1207 context->totalResumeDataCount = torrents.size();
1208 #ifdef QBT_USES_LIBTORRENT2
1209 context->indexedTorrents = QSet<TorrentID>(torrents.cbegin(), torrents.cend());
1210 #endif
1212 handleLoadedResumeData(context);
1215 connect(context->startupStorage, &ResumeDataStorage::loadFinished, context, [context]()
1217 context->isLoadFinished = true;
1220 connect(this, &SessionImpl::addTorrentAlertsReceived, context, [this, context](const qsizetype alertsCount)
1222 context->processingResumeDataCount -= alertsCount;
1223 context->finishedResumeDataCount += alertsCount;
1224 if (!context->isLoadedResumeDataHandlingEnqueued)
1226 QMetaObject::invokeMethod(this, [this, context] { handleLoadedResumeData(context); }, Qt::QueuedConnection);
1227 context->isLoadedResumeDataHandlingEnqueued = true;
1230 if (!m_refreshEnqueued)
1232 m_nativeSession->post_torrent_updates();
1233 m_refreshEnqueued = true;
1236 emit startupProgressUpdated((context->finishedResumeDataCount * 100.) / context->totalResumeDataCount);
1239 context->startupStorage->loadAll();
1242 void SessionImpl::handleLoadedResumeData(ResumeSessionContext *context)
1244 context->isLoadedResumeDataHandlingEnqueued = false;
1246 int count = context->processingResumeDataCount;
1247 while (context->processingResumeDataCount < MAX_PROCESSING_RESUMEDATA_COUNT)
1249 if (context->loadedResumeData.isEmpty())
1250 context->loadedResumeData = context->startupStorage->fetchLoadedResumeData();
1252 if (context->loadedResumeData.isEmpty())
1254 if (context->processingResumeDataCount == 0)
1256 if (context->isLoadFinished)
1258 endStartup(context);
1260 else if (!context->isLoadedResumeDataHandlingEnqueued)
1262 QMetaObject::invokeMethod(this, [this, context]() { handleLoadedResumeData(context); }, Qt::QueuedConnection);
1263 context->isLoadedResumeDataHandlingEnqueued = true;
1267 break;
1270 processNextResumeData(context);
1271 ++count;
1274 context->finishedResumeDataCount += (count - context->processingResumeDataCount);
1277 void SessionImpl::processNextResumeData(ResumeSessionContext *context)
1279 const LoadedResumeData loadedResumeDataItem = context->loadedResumeData.takeFirst();
1281 TorrentID torrentID = loadedResumeDataItem.torrentID;
1282 #ifdef QBT_USES_LIBTORRENT2
1283 if (context->skippedIDs.contains(torrentID))
1284 return;
1285 #endif
1287 const nonstd::expected<LoadTorrentParams, QString> &loadResumeDataResult = loadedResumeDataItem.result;
1288 if (!loadResumeDataResult)
1290 LogMsg(tr("Failed to resume torrent. Torrent: \"%1\". Reason: \"%2\"")
1291 .arg(torrentID.toString(), loadResumeDataResult.error()), Log::CRITICAL);
1292 return;
1295 LoadTorrentParams resumeData = *loadResumeDataResult;
1296 bool needStore = false;
1298 #ifdef QBT_USES_LIBTORRENT2
1299 const InfoHash infoHash {(resumeData.ltAddTorrentParams.ti
1300 ? resumeData.ltAddTorrentParams.ti->info_hashes()
1301 : resumeData.ltAddTorrentParams.info_hashes)};
1302 const bool isHybrid = infoHash.isHybrid();
1303 const auto torrentIDv2 = TorrentID::fromInfoHash(infoHash);
1304 const auto torrentIDv1 = TorrentID::fromSHA1Hash(infoHash.v1());
1305 if (torrentID == torrentIDv2)
1307 if (isHybrid && context->indexedTorrents.contains(torrentIDv1))
1309 // if we don't have metadata, try to find it in alternative "resume data"
1310 if (!resumeData.ltAddTorrentParams.ti)
1312 const nonstd::expected<LoadTorrentParams, QString> loadAltResumeDataResult = context->startupStorage->load(torrentIDv1);
1313 if (loadAltResumeDataResult)
1314 resumeData.ltAddTorrentParams.ti = loadAltResumeDataResult->ltAddTorrentParams.ti;
1317 // remove alternative "resume data" and skip the attempt to load it
1318 m_resumeDataStorage->remove(torrentIDv1);
1319 context->skippedIDs.insert(torrentIDv1);
1322 else if (torrentID == torrentIDv1)
1324 torrentID = torrentIDv2;
1325 needStore = true;
1326 m_resumeDataStorage->remove(torrentIDv1);
1328 if (context->indexedTorrents.contains(torrentID))
1330 context->skippedIDs.insert(torrentID);
1332 const nonstd::expected<LoadTorrentParams, QString> loadPreferredResumeDataResult = context->startupStorage->load(torrentID);
1333 if (loadPreferredResumeDataResult)
1335 std::shared_ptr<lt::torrent_info> ti = resumeData.ltAddTorrentParams.ti;
1336 resumeData = *loadPreferredResumeDataResult;
1337 if (!resumeData.ltAddTorrentParams.ti)
1338 resumeData.ltAddTorrentParams.ti = std::move(ti);
1342 else
1344 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1345 .arg(torrentID.toString()), Log::WARNING);
1346 return;
1348 #else
1349 const lt::sha1_hash infoHash = (resumeData.ltAddTorrentParams.ti
1350 ? resumeData.ltAddTorrentParams.ti->info_hash()
1351 : resumeData.ltAddTorrentParams.info_hash);
1352 if (torrentID != TorrentID::fromInfoHash(infoHash))
1354 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1355 .arg(torrentID.toString()), Log::WARNING);
1356 return;
1358 #endif
1360 if (m_resumeDataStorage != context->startupStorage)
1361 needStore = true;
1363 // TODO: Remove the following upgrade code in v4.6
1364 // == BEGIN UPGRADE CODE ==
1365 if (!needStore)
1367 if (m_needUpgradeDownloadPath && isDownloadPathEnabled() && !resumeData.useAutoTMM)
1369 resumeData.downloadPath = downloadPath();
1370 needStore = true;
1373 // == END UPGRADE CODE ==
1375 if (needStore)
1376 m_resumeDataStorage->store(torrentID, resumeData);
1378 const QString category = resumeData.category;
1379 bool isCategoryRecovered = context->recoveredCategories.contains(category);
1380 if (!category.isEmpty() && (isCategoryRecovered || !m_categories.contains(category)))
1382 if (!isCategoryRecovered)
1384 if (addCategory(category))
1386 context->recoveredCategories.insert(category);
1387 isCategoryRecovered = true;
1388 LogMsg(tr("Detected inconsistent data: category is missing from the configuration file."
1389 " Category will be recovered but its settings will be reset to default."
1390 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
1392 else
1394 resumeData.category.clear();
1395 LogMsg(tr("Detected inconsistent data: invalid category. Torrent: \"%1\". Category: \"%2\"")
1396 .arg(torrentID.toString(), category), Log::WARNING);
1400 // We should check isCategoryRecovered again since the category
1401 // can be just recovered by the code above
1402 if (isCategoryRecovered && resumeData.useAutoTMM)
1404 const Path storageLocation {resumeData.ltAddTorrentParams.save_path};
1405 if ((storageLocation != categorySavePath(resumeData.category)) && (storageLocation != categoryDownloadPath(resumeData.category)))
1407 resumeData.useAutoTMM = false;
1408 resumeData.savePath = storageLocation;
1409 resumeData.downloadPath = {};
1410 LogMsg(tr("Detected mismatch between the save paths of the recovered category and the current save path of the torrent."
1411 " Torrent is now switched to Manual mode."
1412 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
1417 Algorithm::removeIf(resumeData.tags, [this, &torrentID](const QString &tag)
1419 if (hasTag(tag))
1420 return false;
1422 if (addTag(tag))
1424 LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file."
1425 " Tag will be recovered."
1426 " Torrent: \"%1\". Tag: \"%2\"").arg(torrentID.toString(), tag), Log::WARNING);
1427 return false;
1430 LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"")
1431 .arg(torrentID.toString(), tag), Log::WARNING);
1432 return true;
1435 resumeData.ltAddTorrentParams.userdata = LTClientData(new ExtensionData);
1436 #ifndef QBT_USES_LIBTORRENT2
1437 resumeData.ltAddTorrentParams.storage = customStorageConstructor;
1438 #endif
1440 qDebug() << "Starting up torrent" << torrentID.toString() << "...";
1441 m_loadingTorrents.insert(torrentID, resumeData);
1442 #ifdef QBT_USES_LIBTORRENT2
1443 if (infoHash.isHybrid())
1445 // this allows to know the being added hybrid torrent by its v1 info hash
1446 // without having yet another mapping table
1447 m_hybridTorrentsByAltID.insert(torrentIDv1, nullptr);
1449 #endif
1450 m_nativeSession->async_add_torrent(resumeData.ltAddTorrentParams);
1451 ++context->processingResumeDataCount;
1454 void SessionImpl::endStartup(ResumeSessionContext *context)
1456 if (m_resumeDataStorage != context->startupStorage)
1458 if (isQueueingSystemEnabled())
1459 saveTorrentsQueue();
1461 const Path dbPath = context->startupStorage->path();
1462 context->startupStorage->deleteLater();
1464 if (context->currentStorageType == ResumeDataStorageType::Legacy)
1466 connect(context->startupStorage, &QObject::destroyed, [dbPath]
1468 Utils::Fs::removeFile(dbPath);
1473 context->deleteLater();
1474 connect(context, &QObject::destroyed, this, [this]
1476 m_nativeSession->resume();
1477 if (m_refreshEnqueued)
1478 m_refreshEnqueued = false;
1479 else
1480 enqueueRefresh();
1482 m_statisticsLastUpdateTimer.start();
1484 // Regular saving of fastresume data
1485 connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
1486 const int saveInterval = saveResumeDataInterval();
1487 if (saveInterval > 0)
1489 m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
1490 m_resumeDataTimer->start();
1493 m_wakeupCheckTimer = new QTimer(this);
1494 connect(m_wakeupCheckTimer, &QTimer::timeout, this, [this]
1496 const auto now = QDateTime::currentDateTime();
1497 if (m_wakeupCheckTimestamp.secsTo(now) > 100)
1499 LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
1500 reannounceToAllTrackers();
1503 m_wakeupCheckTimestamp = QDateTime::currentDateTime();
1505 m_wakeupCheckTimestamp = QDateTime::currentDateTime();
1506 m_wakeupCheckTimer->start(30s);
1508 m_isRestored = true;
1509 emit startupProgressUpdated(100);
1510 emit restored();
1514 void SessionImpl::initializeNativeSession()
1516 lt::settings_pack pack = loadLTSettings();
1518 const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
1519 pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
1521 pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
1522 pack.set_str(lt::settings_pack::user_agent, USER_AGENT.toStdString());
1523 pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
1524 // Speed up exit
1525 pack.set_int(lt::settings_pack::auto_scrape_interval, 1200); // 20 minutes
1526 pack.set_int(lt::settings_pack::auto_scrape_min_interval, 900); // 15 minutes
1527 // libtorrent 1.1 enables UPnP & NAT-PMP by default
1528 // turn them off before `lt::session` ctor to avoid split second effects
1529 pack.set_bool(lt::settings_pack::enable_upnp, false);
1530 pack.set_bool(lt::settings_pack::enable_natpmp, false);
1532 #ifdef QBT_USES_LIBTORRENT2
1533 // preserve the same behavior as in earlier libtorrent versions
1534 pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
1535 #endif
1537 lt::session_params sessionParams {std::move(pack), {}};
1538 #ifdef QBT_USES_LIBTORRENT2
1539 switch (diskIOType())
1541 case DiskIOType::Posix:
1542 sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
1543 break;
1544 case DiskIOType::MMap:
1545 sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
1546 break;
1547 default:
1548 sessionParams.disk_io_constructor = customDiskIOConstructor;
1549 break;
1551 #endif
1553 #if LIBTORRENT_VERSION_NUM < 20100
1554 m_nativeSession = new lt::session(sessionParams, lt::session::paused);
1555 #else
1556 m_nativeSession = new lt::session(sessionParams);
1557 m_nativeSession->pause();
1558 #endif
1560 LogMsg(tr("Peer ID: \"%1\"").arg(QString::fromStdString(peerId)), Log::INFO);
1561 LogMsg(tr("HTTP User-Agent: \"%1\"").arg(USER_AGENT), Log::INFO);
1562 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1563 LogMsg(tr("Local Peer Discovery support: %1").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1564 LogMsg(tr("Peer Exchange (PeX) support: %1").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1565 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1566 LogMsg(tr("Encryption support: %1").arg((encryption() == 0) ? tr("ON") : ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO);
1568 m_nativeSession->set_alert_notify([this]()
1570 QMetaObject::invokeMethod(this, &SessionImpl::readAlerts, Qt::QueuedConnection);
1573 // Enabling plugins
1574 m_nativeSession->add_extension(&lt::create_smart_ban_plugin);
1575 m_nativeSession->add_extension(&lt::create_ut_metadata_plugin);
1576 if (isPeXEnabled())
1577 m_nativeSession->add_extension(&lt::create_ut_pex_plugin);
1579 auto nativeSessionExtension = std::make_shared<NativeSessionExtension>();
1580 m_nativeSession->add_extension(nativeSessionExtension);
1581 m_nativeSessionExtension = nativeSessionExtension.get();
1584 void SessionImpl::processBannedIPs(lt::ip_filter &filter)
1586 // First, import current filter
1587 for (const QString &ip : asConst(m_bannedIPs.get()))
1589 lt::error_code ec;
1590 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1591 Q_ASSERT(!ec);
1592 if (!ec)
1593 filter.add_rule(addr, addr, lt::ip_filter::blocked);
1597 void SessionImpl::initMetrics()
1599 const auto findMetricIndex = [](const char *name) -> int
1601 const int index = lt::find_metric_idx(name);
1602 Q_ASSERT(index >= 0);
1603 return index;
1606 // TODO: switch to "designated initializers" in C++20
1607 m_metricIndices.net.hasIncomingConnections = findMetricIndex("net.has_incoming_connections");
1608 m_metricIndices.net.sentPayloadBytes = findMetricIndex("net.sent_payload_bytes");
1609 m_metricIndices.net.recvPayloadBytes = findMetricIndex("net.recv_payload_bytes");
1610 m_metricIndices.net.sentBytes = findMetricIndex("net.sent_bytes");
1611 m_metricIndices.net.recvBytes = findMetricIndex("net.recv_bytes");
1612 m_metricIndices.net.sentIPOverheadBytes = findMetricIndex("net.sent_ip_overhead_bytes");
1613 m_metricIndices.net.recvIPOverheadBytes = findMetricIndex("net.recv_ip_overhead_bytes");
1614 m_metricIndices.net.sentTrackerBytes = findMetricIndex("net.sent_tracker_bytes");
1615 m_metricIndices.net.recvTrackerBytes = findMetricIndex("net.recv_tracker_bytes");
1616 m_metricIndices.net.recvRedundantBytes = findMetricIndex("net.recv_redundant_bytes");
1617 m_metricIndices.net.recvFailedBytes = findMetricIndex("net.recv_failed_bytes");
1619 m_metricIndices.peer.numPeersConnected = findMetricIndex("peer.num_peers_connected");
1620 m_metricIndices.peer.numPeersDownDisk = findMetricIndex("peer.num_peers_down_disk");
1621 m_metricIndices.peer.numPeersUpDisk = findMetricIndex("peer.num_peers_up_disk");
1623 m_metricIndices.dht.dhtBytesIn = findMetricIndex("dht.dht_bytes_in");
1624 m_metricIndices.dht.dhtBytesOut = findMetricIndex("dht.dht_bytes_out");
1625 m_metricIndices.dht.dhtNodes = findMetricIndex("dht.dht_nodes");
1627 m_metricIndices.disk.diskBlocksInUse = findMetricIndex("disk.disk_blocks_in_use");
1628 m_metricIndices.disk.numBlocksRead = findMetricIndex("disk.num_blocks_read");
1629 #ifndef QBT_USES_LIBTORRENT2
1630 m_metricIndices.disk.numBlocksCacheHits = findMetricIndex("disk.num_blocks_cache_hits");
1631 #endif
1632 m_metricIndices.disk.writeJobs = findMetricIndex("disk.num_write_ops");
1633 m_metricIndices.disk.readJobs = findMetricIndex("disk.num_read_ops");
1634 m_metricIndices.disk.hashJobs = findMetricIndex("disk.num_blocks_hashed");
1635 m_metricIndices.disk.queuedDiskJobs = findMetricIndex("disk.queued_disk_jobs");
1636 m_metricIndices.disk.diskJobTime = findMetricIndex("disk.disk_job_time");
1639 lt::settings_pack SessionImpl::loadLTSettings() const
1641 lt::settings_pack settingsPack;
1643 const lt::alert_category_t alertMask = lt::alert::error_notification
1644 | lt::alert::file_progress_notification
1645 | lt::alert::ip_block_notification
1646 | lt::alert::peer_notification
1647 | (isPerformanceWarningEnabled() ? lt::alert::performance_warning : lt::alert_category_t())
1648 | lt::alert::port_mapping_notification
1649 | lt::alert::status_notification
1650 | lt::alert::storage_notification
1651 | lt::alert::tracker_notification;
1652 settingsPack.set_int(lt::settings_pack::alert_mask, alertMask);
1654 settingsPack.set_int(lt::settings_pack::connection_speed, connectionSpeed());
1656 // from libtorrent doc:
1657 // It will not take affect until the listen_interfaces settings is updated
1658 settingsPack.set_int(lt::settings_pack::send_socket_buffer_size, socketSendBufferSize());
1659 settingsPack.set_int(lt::settings_pack::recv_socket_buffer_size, socketReceiveBufferSize());
1660 settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
1662 applyNetworkInterfacesSettings(settingsPack);
1664 settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
1665 settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
1667 // The most secure, rc4 only so that all streams are encrypted
1668 settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
1669 settingsPack.set_bool(lt::settings_pack::prefer_rc4, true);
1670 switch (encryption())
1672 case 0: // Enabled
1673 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_enabled);
1674 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_enabled);
1675 break;
1676 case 1: // Forced
1677 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_forced);
1678 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_forced);
1679 break;
1680 default: // Disabled
1681 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_disabled);
1682 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_disabled);
1685 settingsPack.set_int(lt::settings_pack::active_checking, maxActiveCheckingTorrents());
1687 // I2P
1688 #if defined(QBT_USES_LIBTORRENT2) && TORRENT_USE_I2P
1689 if (isI2PEnabled())
1691 settingsPack.set_str(lt::settings_pack::i2p_hostname, I2PAddress().toStdString());
1692 settingsPack.set_int(lt::settings_pack::i2p_port, I2PPort());
1693 settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, I2PMixedMode());
1695 else
1697 settingsPack.set_str(lt::settings_pack::i2p_hostname, "");
1698 settingsPack.set_int(lt::settings_pack::i2p_port, 0);
1699 settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, false);
1702 // I2P session options
1703 settingsPack.set_int(lt::settings_pack::i2p_inbound_quantity, I2PInboundQuantity());
1704 settingsPack.set_int(lt::settings_pack::i2p_outbound_quantity, I2POutboundQuantity());
1705 settingsPack.set_int(lt::settings_pack::i2p_inbound_length, I2PInboundLength());
1706 settingsPack.set_int(lt::settings_pack::i2p_outbound_length, I2POutboundLength());
1707 #endif
1709 // proxy
1710 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
1711 const auto *proxyManager = Net::ProxyConfigurationManager::instance();
1712 const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
1713 if ((proxyConfig.type != Net::ProxyType::None) && Preferences::instance()->useProxyForBT())
1715 switch (proxyConfig.type)
1717 case Net::ProxyType::SOCKS4:
1718 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks4);
1719 break;
1721 case Net::ProxyType::HTTP:
1722 if (proxyConfig.authEnabled)
1723 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http_pw);
1724 else
1725 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http);
1726 break;
1728 case Net::ProxyType::SOCKS5:
1729 if (proxyConfig.authEnabled)
1730 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5_pw);
1731 else
1732 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
1733 break;
1735 default:
1736 break;
1739 settingsPack.set_str(lt::settings_pack::proxy_hostname, proxyConfig.ip.toStdString());
1740 settingsPack.set_int(lt::settings_pack::proxy_port, proxyConfig.port);
1742 if (proxyConfig.authEnabled)
1744 settingsPack.set_str(lt::settings_pack::proxy_username, proxyConfig.username.toStdString());
1745 settingsPack.set_str(lt::settings_pack::proxy_password, proxyConfig.password.toStdString());
1748 settingsPack.set_bool(lt::settings_pack::proxy_peer_connections, isProxyPeerConnectionsEnabled());
1749 settingsPack.set_bool(lt::settings_pack::proxy_hostnames, proxyConfig.hostnameLookupEnabled);
1752 settingsPack.set_bool(lt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
1753 settingsPack.set_bool(lt::settings_pack::announce_to_all_tiers, announceToAllTiers());
1755 settingsPack.set_int(lt::settings_pack::peer_turnover, peerTurnover());
1756 settingsPack.set_int(lt::settings_pack::peer_turnover_cutoff, peerTurnoverCutoff());
1757 settingsPack.set_int(lt::settings_pack::peer_turnover_interval, peerTurnoverInterval());
1759 settingsPack.set_int(lt::settings_pack::max_out_request_queue, requestQueueSize());
1761 #ifdef QBT_USES_LIBTORRENT2
1762 settingsPack.set_int(lt::settings_pack::metadata_token_limit, Preferences::instance()->getBdecodeTokenLimit());
1763 #endif
1765 settingsPack.set_int(lt::settings_pack::aio_threads, asyncIOThreads());
1766 #ifdef QBT_USES_LIBTORRENT2
1767 settingsPack.set_int(lt::settings_pack::hashing_threads, hashingThreads());
1768 #endif
1769 settingsPack.set_int(lt::settings_pack::file_pool_size, filePoolSize());
1771 const int checkingMemUsageSize = checkingMemUsage() * 64;
1772 settingsPack.set_int(lt::settings_pack::checking_mem_usage, checkingMemUsageSize);
1774 #ifndef QBT_USES_LIBTORRENT2
1775 const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
1776 settingsPack.set_int(lt::settings_pack::cache_size, cacheSize);
1777 settingsPack.set_int(lt::settings_pack::cache_expiry, diskCacheTTL());
1778 #endif
1780 settingsPack.set_int(lt::settings_pack::max_queued_disk_bytes, diskQueueSize());
1782 switch (diskIOReadMode())
1784 case DiskIOReadMode::DisableOSCache:
1785 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::disable_os_cache);
1786 break;
1787 case DiskIOReadMode::EnableOSCache:
1788 default:
1789 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::enable_os_cache);
1790 break;
1793 switch (diskIOWriteMode())
1795 case DiskIOWriteMode::DisableOSCache:
1796 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::disable_os_cache);
1797 break;
1798 case DiskIOWriteMode::EnableOSCache:
1799 default:
1800 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::enable_os_cache);
1801 break;
1802 #ifdef QBT_USES_LIBTORRENT2
1803 case DiskIOWriteMode::WriteThrough:
1804 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::write_through);
1805 break;
1806 #endif
1809 #ifndef QBT_USES_LIBTORRENT2
1810 settingsPack.set_bool(lt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
1811 settingsPack.set_bool(lt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
1812 #endif
1814 settingsPack.set_bool(lt::settings_pack::piece_extent_affinity, usePieceExtentAffinity());
1816 settingsPack.set_int(lt::settings_pack::suggest_mode, isSuggestModeEnabled()
1817 ? lt::settings_pack::suggest_read_cache : lt::settings_pack::no_piece_suggestions);
1819 settingsPack.set_int(lt::settings_pack::send_buffer_watermark, sendBufferWatermark() * 1024);
1820 settingsPack.set_int(lt::settings_pack::send_buffer_low_watermark, sendBufferLowWatermark() * 1024);
1821 settingsPack.set_int(lt::settings_pack::send_buffer_watermark_factor, sendBufferWatermarkFactor());
1823 settingsPack.set_bool(lt::settings_pack::anonymous_mode, isAnonymousModeEnabled());
1825 // Queueing System
1826 if (isQueueingSystemEnabled())
1828 settingsPack.set_int(lt::settings_pack::active_downloads, maxActiveDownloads());
1829 settingsPack.set_int(lt::settings_pack::active_limit, maxActiveTorrents());
1830 settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
1831 settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
1832 settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
1833 settingsPack.set_int(lt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
1834 settingsPack.set_int(lt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
1836 else
1838 settingsPack.set_int(lt::settings_pack::active_downloads, -1);
1839 settingsPack.set_int(lt::settings_pack::active_seeds, -1);
1840 settingsPack.set_int(lt::settings_pack::active_limit, -1);
1842 settingsPack.set_int(lt::settings_pack::active_tracker_limit, -1);
1843 settingsPack.set_int(lt::settings_pack::active_dht_limit, -1);
1844 settingsPack.set_int(lt::settings_pack::active_lsd_limit, -1);
1845 settingsPack.set_int(lt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
1847 // Outgoing ports
1848 settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
1849 settingsPack.set_int(lt::settings_pack::num_outgoing_ports, (outgoingPortsMax() - outgoingPortsMin()));
1850 // UPnP lease duration
1851 settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
1852 // Type of service
1853 settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
1854 // Include overhead in transfer limits
1855 settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
1856 // IP address to announce to trackers
1857 settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
1858 // Max concurrent HTTP announces
1859 settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
1860 // Stop tracker timeout
1861 settingsPack.set_int(lt::settings_pack::stop_tracker_timeout, stopTrackerTimeout());
1862 // * Max connections limit
1863 settingsPack.set_int(lt::settings_pack::connections_limit, maxConnections());
1864 // * Global max upload slots
1865 settingsPack.set_int(lt::settings_pack::unchoke_slots_limit, maxUploads());
1866 // uTP
1867 switch (btProtocol())
1869 case BTProtocol::Both:
1870 default:
1871 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1872 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1873 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1874 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1875 break;
1877 case BTProtocol::TCP:
1878 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1879 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1880 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, false);
1881 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
1882 break;
1884 case BTProtocol::UTP:
1885 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, false);
1886 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, false);
1887 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1888 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1889 break;
1892 switch (utpMixedMode())
1894 case MixedModeAlgorithm::TCP:
1895 default:
1896 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::prefer_tcp);
1897 break;
1898 case MixedModeAlgorithm::Proportional:
1899 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::peer_proportional);
1900 break;
1903 settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
1905 settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
1907 settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
1909 settingsPack.set_bool(lt::settings_pack::ssrf_mitigation, isSSRFMitigationEnabled());
1911 settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
1913 settingsPack.set_bool(lt::settings_pack::apply_ip_filter_to_trackers, isTrackerFilteringEnabled());
1915 settingsPack.set_bool(lt::settings_pack::enable_dht, isDHTEnabled());
1916 if (isDHTEnabled())
1917 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");
1918 settingsPack.set_bool(lt::settings_pack::enable_lsd, isLSDEnabled());
1920 switch (chokingAlgorithm())
1922 case ChokingAlgorithm::FixedSlots:
1923 default:
1924 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::fixed_slots_choker);
1925 break;
1926 case ChokingAlgorithm::RateBased:
1927 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::rate_based_choker);
1928 break;
1931 switch (seedChokingAlgorithm())
1933 case SeedChokingAlgorithm::RoundRobin:
1934 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::round_robin);
1935 break;
1936 case SeedChokingAlgorithm::FastestUpload:
1937 default:
1938 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::fastest_upload);
1939 break;
1940 case SeedChokingAlgorithm::AntiLeech:
1941 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
1942 break;
1945 return settingsPack;
1948 void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const
1950 if (m_listenInterfaceConfigured)
1951 return;
1953 if (port() > 0) // user has specified port number
1954 settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);
1956 QStringList endpoints;
1957 QStringList outgoingInterfaces;
1958 const QString portString = u':' + QString::number(port());
1960 for (const QString &ip : asConst(getListeningIPs()))
1962 const QHostAddress addr {ip};
1963 if (!addr.isNull())
1965 const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
1966 const QString ip = isIPv6
1967 ? Utils::Net::canonicalIPv6Addr(addr).toString()
1968 : addr.toString();
1970 endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString);
1972 if ((ip != u"0.0.0.0") && (ip != u"::"))
1973 outgoingInterfaces << ip;
1975 else
1977 // ip holds an interface name
1978 #ifdef Q_OS_WIN
1979 // On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
1980 // the interface's LUID and not the GUID.
1981 // Libtorrent expects GUIDs for the 'listen_interfaces' setting.
1982 const QString guid = convertIfaceNameToGuid(ip);
1983 if (!guid.isEmpty())
1985 endpoints << (guid + portString);
1986 outgoingInterfaces << guid;
1988 else
1990 LogMsg(tr("Could not find GUID of network interface. Interface: \"%1\"").arg(ip), Log::WARNING);
1991 // Since we can't get the GUID, we'll pass the interface name instead.
1992 // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
1993 endpoints << (ip + portString);
1994 outgoingInterfaces << ip;
1996 #else
1997 endpoints << (ip + portString);
1998 outgoingInterfaces << ip;
1999 #endif
2003 const QString finalEndpoints = endpoints.join(u',');
2004 settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
2005 LogMsg(tr("Trying to listen on the following list of IP addresses: \"%1\"").arg(finalEndpoints));
2007 settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(u',').toStdString());
2008 m_listenInterfaceConfigured = true;
2011 void SessionImpl::configurePeerClasses()
2013 lt::ip_filter f;
2014 // lt::make_address("255.255.255.255") crashes on some people's systems
2015 // so instead we use address_v4::broadcast()
2016 // Proactively do the same for 0.0.0.0 and address_v4::any()
2017 f.add_rule(lt::address_v4::any()
2018 , lt::address_v4::broadcast()
2019 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
2021 // IPv6 may not be available on OS and the parsing
2022 // would result in an exception -> abnormal program termination
2023 // Affects Windows XP
2026 f.add_rule(lt::address_v6::any()
2027 , lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2028 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
2030 catch (const std::exception &) {}
2032 if (ignoreLimitsOnLAN())
2034 // local networks
2035 f.add_rule(lt::make_address("10.0.0.0")
2036 , lt::make_address("10.255.255.255")
2037 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2038 f.add_rule(lt::make_address("172.16.0.0")
2039 , lt::make_address("172.31.255.255")
2040 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2041 f.add_rule(lt::make_address("192.168.0.0")
2042 , lt::make_address("192.168.255.255")
2043 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2044 // link local
2045 f.add_rule(lt::make_address("169.254.0.0")
2046 , lt::make_address("169.254.255.255")
2047 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2048 // loopback
2049 f.add_rule(lt::make_address("127.0.0.0")
2050 , lt::make_address("127.255.255.255")
2051 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2053 // IPv6 may not be available on OS and the parsing
2054 // would result in an exception -> abnormal program termination
2055 // Affects Windows XP
2058 // link local
2059 f.add_rule(lt::make_address("fe80::")
2060 , lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2061 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2062 // unique local addresses
2063 f.add_rule(lt::make_address("fc00::")
2064 , lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2065 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2066 // loopback
2067 f.add_rule(lt::address_v6::loopback()
2068 , lt::address_v6::loopback()
2069 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2071 catch (const std::exception &) {}
2073 m_nativeSession->set_peer_class_filter(f);
2075 lt::peer_class_type_filter peerClassTypeFilter;
2076 peerClassTypeFilter.add(lt::peer_class_type_filter::tcp_socket, lt::session::tcp_peer_class_id);
2077 peerClassTypeFilter.add(lt::peer_class_type_filter::ssl_tcp_socket, lt::session::tcp_peer_class_id);
2078 peerClassTypeFilter.add(lt::peer_class_type_filter::i2p_socket, lt::session::tcp_peer_class_id);
2079 if (!isUTPRateLimited())
2081 peerClassTypeFilter.disallow(lt::peer_class_type_filter::utp_socket
2082 , lt::session::global_peer_class_id);
2083 peerClassTypeFilter.disallow(lt::peer_class_type_filter::ssl_utp_socket
2084 , lt::session::global_peer_class_id);
2086 m_nativeSession->set_peer_class_type_filter(peerClassTypeFilter);
2089 void SessionImpl::enableTracker(const bool enable)
2091 const QString profile = u"embeddedTracker"_s;
2092 auto *portForwarder = Net::PortForwarder::instance();
2094 if (enable)
2096 if (!m_tracker)
2097 m_tracker = new Tracker(this);
2099 m_tracker->start();
2101 const auto *pref = Preferences::instance();
2102 if (pref->isTrackerPortForwardingEnabled())
2103 portForwarder->setPorts(profile, {static_cast<quint16>(pref->getTrackerPort())});
2104 else
2105 portForwarder->removePorts(profile);
2107 else
2109 delete m_tracker;
2111 portForwarder->removePorts(profile);
2115 void SessionImpl::enableBandwidthScheduler()
2117 if (!m_bwScheduler)
2119 m_bwScheduler = new BandwidthScheduler(this);
2120 connect(m_bwScheduler.data(), &BandwidthScheduler::bandwidthLimitRequested
2121 , this, &SessionImpl::setAltGlobalSpeedLimitEnabled);
2123 m_bwScheduler->start();
2126 void SessionImpl::populateAdditionalTrackers()
2128 m_additionalTrackerList.clear();
2130 const QString trackers = additionalTrackers();
2131 for (QStringView tracker : asConst(QStringView(trackers).split(u'\n')))
2133 tracker = tracker.trimmed();
2134 if (!tracker.isEmpty())
2135 m_additionalTrackerList.append({tracker.toString()});
2139 void SessionImpl::processShareLimits()
2141 qDebug("Processing share limits...");
2143 // We shouldn't iterate over `m_torrents` in the loop below
2144 // since `deleteTorrent()` modifies it indirectly
2145 const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
2146 for (TorrentImpl *const torrent : torrents)
2148 if (torrent->isFinished() && !torrent->isForced())
2150 if (torrent->ratioLimit() != Torrent::NO_RATIO_LIMIT)
2152 const qreal ratio = torrent->realRatio();
2153 qreal ratioLimit = torrent->ratioLimit();
2154 if (ratioLimit == Torrent::USE_GLOBAL_RATIO)
2155 // If Global Max Ratio is really set...
2156 ratioLimit = globalMaxRatio();
2158 if (ratioLimit >= 0)
2160 qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
2162 if ((ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
2164 const QString description = tr("Torrent reached the share ratio limit.");
2165 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2167 if (m_maxRatioAction == Remove)
2169 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent."), torrentName));
2170 deleteTorrent(torrent->id());
2172 else if (m_maxRatioAction == DeleteFiles)
2174 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent and deleted its content."), torrentName));
2175 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
2177 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
2179 torrent->pause();
2180 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent paused."), torrentName));
2182 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
2184 torrent->setSuperSeeding(true);
2185 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
2188 continue;
2193 if (torrent->seedingTimeLimit() != Torrent::NO_SEEDING_TIME_LIMIT)
2195 const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
2196 int seedingTimeLimit = torrent->seedingTimeLimit();
2197 if (seedingTimeLimit == Torrent::USE_GLOBAL_SEEDING_TIME)
2199 // If Global Seeding Time Limit is really set...
2200 seedingTimeLimit = globalMaxSeedingMinutes();
2203 if (seedingTimeLimit >= 0)
2205 if ((seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
2207 const QString description = tr("Torrent reached the seeding time limit.");
2208 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2210 if (m_maxRatioAction == Remove)
2212 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent."), torrentName));
2213 deleteTorrent(torrent->id());
2215 else if (m_maxRatioAction == DeleteFiles)
2217 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent and deleted its content."), torrentName));
2218 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
2220 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
2222 torrent->pause();
2223 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent paused."), torrentName));
2225 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
2227 torrent->setSuperSeeding(true);
2228 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
2231 continue;
2236 if (torrent->inactiveSeedingTimeLimit() != Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT)
2238 const qlonglong inactiveSeedingTimeInMinutes = torrent->timeSinceActivity() / 60;
2239 int inactiveSeedingTimeLimit = torrent->inactiveSeedingTimeLimit();
2240 if (inactiveSeedingTimeLimit == Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME)
2242 // If Global Seeding Time Limit is really set...
2243 inactiveSeedingTimeLimit = globalMaxInactiveSeedingMinutes();
2246 if (inactiveSeedingTimeLimit >= 0)
2248 if ((inactiveSeedingTimeInMinutes <= Torrent::MAX_INACTIVE_SEEDING_TIME) && (inactiveSeedingTimeInMinutes >= inactiveSeedingTimeLimit))
2250 const QString description = tr("Torrent reached the inactive seeding time limit.");
2251 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2253 if (m_maxRatioAction == Remove)
2255 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent."), torrentName));
2256 deleteTorrent(torrent->id());
2258 else if (m_maxRatioAction == DeleteFiles)
2260 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent and deleted its content."), torrentName));
2261 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
2263 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
2265 torrent->pause();
2266 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent paused."), torrentName));
2268 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
2270 torrent->setSuperSeeding(true);
2271 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
2280 // Add to BitTorrent session the downloaded torrent file
2281 void SessionImpl::handleDownloadFinished(const Net::DownloadResult &result)
2283 switch (result.status)
2285 case Net::DownloadStatus::Success:
2286 emit downloadFromUrlFinished(result.url);
2287 if (const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::load(result.data); loadResult)
2288 addTorrent(loadResult.value(), m_downloadedTorrents.take(result.url));
2289 else
2290 LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(loadResult.error()), Log::WARNING);
2291 break;
2292 case Net::DownloadStatus::RedirectedToMagnet:
2293 emit downloadFromUrlFinished(result.url);
2294 addTorrent(MagnetUri(result.magnet), m_downloadedTorrents.take(result.url));
2295 break;
2296 default:
2297 emit downloadFromUrlFailed(result.url, result.errorString);
2301 void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
2303 TorrentImpl *torrent = m_torrents.value(id);
2304 if (torrent)
2306 torrent->fileSearchFinished(savePath, fileNames);
2307 return;
2310 const auto loadingTorrentsIter = m_loadingTorrents.find(id);
2311 if (loadingTorrentsIter != m_loadingTorrents.end())
2313 LoadTorrentParams &params = loadingTorrentsIter.value();
2314 lt::add_torrent_params &p = params.ltAddTorrentParams;
2316 p.save_path = savePath.toString().toStdString();
2317 const TorrentInfo torrentInfo {*p.ti};
2318 const auto nativeIndexes = torrentInfo.nativeIndexes();
2319 for (int i = 0; i < fileNames.size(); ++i)
2320 p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString();
2322 m_nativeSession->async_add_torrent(p);
2326 Torrent *SessionImpl::getTorrent(const TorrentID &id) const
2328 return m_torrents.value(id);
2331 Torrent *SessionImpl::findTorrent(const InfoHash &infoHash) const
2333 const auto id = TorrentID::fromInfoHash(infoHash);
2334 if (Torrent *torrent = m_torrents.value(id); torrent)
2335 return torrent;
2337 if (!infoHash.isHybrid())
2338 return m_hybridTorrentsByAltID.value(id);
2340 // alternative ID can be useful to find existing torrent
2341 // in case if hybrid torrent was added by v1 info hash
2342 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
2343 return m_torrents.value(altID);
2346 void SessionImpl::banIP(const QString &ip)
2348 if (m_bannedIPs.get().contains(ip))
2349 return;
2351 lt::error_code ec;
2352 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
2353 Q_ASSERT(!ec);
2354 if (ec)
2355 return;
2357 invokeAsync([session = m_nativeSession, addr]
2359 lt::ip_filter filter = session->get_ip_filter();
2360 filter.add_rule(addr, addr, lt::ip_filter::blocked);
2361 session->set_ip_filter(std::move(filter));
2364 QStringList bannedIPs = m_bannedIPs;
2365 bannedIPs.append(ip);
2366 bannedIPs.sort();
2367 m_bannedIPs = bannedIPs;
2370 // Delete a torrent from the session, given its hash
2371 // and from the disk, if the corresponding deleteOption is chosen
2372 bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
2374 TorrentImpl *const torrent = m_torrents.take(id);
2375 if (!torrent) return false;
2377 qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
2378 emit torrentAboutToBeRemoved(torrent);
2380 if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
2381 m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
2383 // Remove it from session
2384 if (deleteOption == DeleteTorrent)
2386 m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
2388 const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
2389 const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
2390 , [&nativeHandle](const MoveStorageJob &job)
2392 return job.torrentHandle == nativeHandle;
2394 if (iter != m_moveStorageQueue.end())
2396 // We shouldn't actually remove torrent until existing "move storage jobs" are done
2397 torrentQueuePositionBottom(nativeHandle);
2398 nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
2399 nativeHandle.pause();
2401 else
2403 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
2406 else
2408 m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
2410 if (m_moveStorageQueue.size() > 1)
2412 // Delete "move storage job" for the deleted torrent
2413 // (note: we shouldn't delete active job)
2414 const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
2415 , [torrent](const MoveStorageJob &job)
2417 return job.torrentHandle == torrent->nativeHandle();
2419 if (iter != m_moveStorageQueue.end())
2420 m_moveStorageQueue.erase(iter);
2423 m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
2426 // Remove it from torrent resume directory
2427 m_resumeDataStorage->remove(torrent->id());
2429 delete torrent;
2430 return true;
2433 bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
2435 const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
2436 if (downloadedMetadataIter == m_downloadedMetadata.end())
2437 return false;
2439 const lt::torrent_handle nativeHandle = downloadedMetadataIter.value();
2440 m_downloadedMetadata.erase(downloadedMetadataIter);
2442 if (!nativeHandle.is_valid())
2443 return true;
2445 #ifdef QBT_USES_LIBTORRENT2
2446 const InfoHash infoHash {nativeHandle.info_hashes()};
2447 if (infoHash.isHybrid())
2449 // if magnet link was hybrid initially then it is indexed also by v1 info hash
2450 // so we need to remove both entries
2451 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
2452 m_downloadedMetadata.remove(altID);
2454 #endif
2456 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
2457 return true;
2460 void SessionImpl::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
2462 using ElementType = std::pair<int, const TorrentImpl *>;
2463 std::priority_queue<ElementType
2464 , std::vector<ElementType>
2465 , std::greater<ElementType>> torrentQueue;
2467 // Sort torrents by queue position
2468 for (const TorrentID &id : ids)
2470 const TorrentImpl *torrent = m_torrents.value(id);
2471 if (!torrent) continue;
2472 if (const int position = torrent->queuePosition(); position >= 0)
2473 torrentQueue.emplace(position, torrent);
2476 // Increase torrents queue position (starting with the one in the highest queue position)
2477 while (!torrentQueue.empty())
2479 const TorrentImpl *torrent = torrentQueue.top().second;
2480 torrentQueuePositionUp(torrent->nativeHandle());
2481 torrentQueue.pop();
2484 m_torrentsQueueChanged = true;
2487 void SessionImpl::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
2489 using ElementType = std::pair<int, const TorrentImpl *>;
2490 std::priority_queue<ElementType> torrentQueue;
2492 // Sort torrents by queue position
2493 for (const TorrentID &id : ids)
2495 const TorrentImpl *torrent = m_torrents.value(id);
2496 if (!torrent) continue;
2497 if (const int position = torrent->queuePosition(); position >= 0)
2498 torrentQueue.emplace(position, torrent);
2501 // Decrease torrents queue position (starting with the one in the lowest queue position)
2502 while (!torrentQueue.empty())
2504 const TorrentImpl *torrent = torrentQueue.top().second;
2505 torrentQueuePositionDown(torrent->nativeHandle());
2506 torrentQueue.pop();
2509 for (const lt::torrent_handle &torrentHandle : asConst(m_downloadedMetadata))
2510 torrentQueuePositionBottom(torrentHandle);
2512 m_torrentsQueueChanged = true;
2515 void SessionImpl::topTorrentsQueuePos(const QVector<TorrentID> &ids)
2517 using ElementType = std::pair<int, const TorrentImpl *>;
2518 std::priority_queue<ElementType> torrentQueue;
2520 // Sort torrents by queue position
2521 for (const TorrentID &id : ids)
2523 const TorrentImpl *torrent = m_torrents.value(id);
2524 if (!torrent) continue;
2525 if (const int position = torrent->queuePosition(); position >= 0)
2526 torrentQueue.emplace(position, torrent);
2529 // Top torrents queue position (starting with the one in the lowest queue position)
2530 while (!torrentQueue.empty())
2532 const TorrentImpl *torrent = torrentQueue.top().second;
2533 torrentQueuePositionTop(torrent->nativeHandle());
2534 torrentQueue.pop();
2537 m_torrentsQueueChanged = true;
2540 void SessionImpl::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
2542 using ElementType = std::pair<int, const TorrentImpl *>;
2543 std::priority_queue<ElementType
2544 , std::vector<ElementType>
2545 , std::greater<ElementType>> torrentQueue;
2547 // Sort torrents by queue position
2548 for (const TorrentID &id : ids)
2550 const TorrentImpl *torrent = m_torrents.value(id);
2551 if (!torrent) continue;
2552 if (const int position = torrent->queuePosition(); position >= 0)
2553 torrentQueue.emplace(position, torrent);
2556 // Bottom torrents queue position (starting with the one in the highest queue position)
2557 while (!torrentQueue.empty())
2559 const TorrentImpl *torrent = torrentQueue.top().second;
2560 torrentQueuePositionBottom(torrent->nativeHandle());
2561 torrentQueue.pop();
2564 for (const lt::torrent_handle &torrentHandle : asConst(m_downloadedMetadata))
2565 torrentQueuePositionBottom(torrentHandle);
2567 m_torrentsQueueChanged = true;
2570 void SessionImpl::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
2572 if (m_needSaveResumeDataTorrents.empty())
2574 QMetaObject::invokeMethod(this, [this]()
2576 for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
2578 TorrentImpl *torrent = m_torrents.value(torrentID);
2579 if (torrent)
2580 torrent->saveResumeData();
2582 m_needSaveResumeDataTorrents.clear();
2583 }, Qt::QueuedConnection);
2586 m_needSaveResumeDataTorrents.insert(torrent->id());
2589 void SessionImpl::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
2591 qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
2592 ++m_numResumeData;
2595 void SessionImpl::handleTorrentSaveResumeDataFailed(const TorrentImpl *torrent)
2597 Q_UNUSED(torrent);
2598 --m_numResumeData;
2601 QVector<Torrent *> SessionImpl::torrents() const
2603 QVector<Torrent *> result;
2604 result.reserve(m_torrents.size());
2605 for (TorrentImpl *torrent : asConst(m_torrents))
2606 result << torrent;
2608 return result;
2611 qsizetype SessionImpl::torrentsCount() const
2613 return m_torrents.size();
2616 bool SessionImpl::addTorrent(const QString &source, const AddTorrentParams &params)
2618 // `source`: .torrent file path/url or magnet uri
2620 if (!isRestored())
2621 return false;
2623 if (Net::DownloadManager::hasSupportedScheme(source))
2625 LogMsg(tr("Downloading torrent, please wait... Source: \"%1\"").arg(source));
2626 const auto *pref = Preferences::instance();
2627 // Launch downloader
2628 Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(pref->getTorrentFileSizeLimit())
2629 , pref->useProxyForGeneralPurposes(), this, &SessionImpl::handleDownloadFinished);
2630 m_downloadedTorrents[source] = params;
2631 return true;
2634 const MagnetUri magnetUri {source};
2635 if (magnetUri.isValid())
2636 return addTorrent(magnetUri, params);
2638 const Path path {source};
2639 TorrentFileGuard guard {path};
2640 const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(path);
2641 if (!loadResult)
2643 LogMsg(tr("Failed to load torrent. Source: \"%1\". Reason: \"%2\"").arg(source, loadResult.error()), Log::WARNING);
2644 return false;
2647 guard.markAsAddedToSession();
2648 return addTorrent(loadResult.value(), params);
2651 bool SessionImpl::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &params)
2653 if (!isRestored())
2654 return false;
2656 if (!magnetUri.isValid())
2657 return false;
2659 return addTorrent_impl(magnetUri, params);
2662 bool SessionImpl::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams &params)
2664 if (!isRestored())
2665 return false;
2667 return addTorrent_impl(torrentInfo, params);
2670 LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &addTorrentParams)
2672 LoadTorrentParams loadTorrentParams;
2674 loadTorrentParams.name = addTorrentParams.name;
2675 loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
2676 loadTorrentParams.hasFinishedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
2677 loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
2678 loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged);
2679 loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused());
2680 loadTorrentParams.stopCondition = addTorrentParams.stopCondition.value_or(torrentStopCondition());
2681 loadTorrentParams.addToQueueTop = addTorrentParams.addToQueueTop.value_or(isAddTorrentToQueueTop());
2682 loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
2683 loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
2684 loadTorrentParams.inactiveSeedingTimeLimit = addTorrentParams.inactiveSeedingTimeLimit;
2686 const QString category = addTorrentParams.category;
2687 if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
2688 loadTorrentParams.category = u""_s;
2689 else
2690 loadTorrentParams.category = category;
2692 const auto defaultSavePath = suggestedSavePath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
2693 const auto defaultDownloadPath = suggestedDownloadPath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
2695 loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(
2696 addTorrentParams.savePath.isEmpty() && addTorrentParams.downloadPath.isEmpty() && !isAutoTMMDisabledByDefault());
2698 if (!loadTorrentParams.useAutoTMM)
2700 if (addTorrentParams.savePath.isAbsolute())
2701 loadTorrentParams.savePath = addTorrentParams.savePath;
2702 else
2703 loadTorrentParams.savePath = defaultSavePath / addTorrentParams.savePath;
2705 // if useDownloadPath isn't specified but downloadPath is explicitly set we prefer to use it
2706 const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(!addTorrentParams.downloadPath.isEmpty() || isDownloadPathEnabled());
2707 if (useDownloadPath)
2709 // Overridden "Download path" settings
2711 if (addTorrentParams.downloadPath.isAbsolute())
2713 loadTorrentParams.downloadPath = addTorrentParams.downloadPath;
2715 else
2717 const Path basePath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath());
2718 loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath;
2723 for (const QString &tag : addTorrentParams.tags)
2725 if (hasTag(tag) || addTag(tag))
2726 loadTorrentParams.tags.insert(tag);
2729 return loadTorrentParams;
2732 // Add a torrent to the BitTorrent session
2733 bool SessionImpl::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams)
2735 Q_ASSERT(isRestored());
2737 const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
2738 const auto infoHash = (hasMetadata ? std::get<TorrentInfo>(source).infoHash() : std::get<MagnetUri>(source).infoHash());
2739 const auto id = TorrentID::fromInfoHash(infoHash);
2741 // alternative ID can be useful to find existing torrent in case if hybrid torrent was added by v1 info hash
2742 const auto altID = (infoHash.isHybrid() ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
2744 // We should not add the torrent if it is already
2745 // processed or is pending to add to session
2746 if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
2747 return false;
2749 if (Torrent *torrent = findTorrent(infoHash))
2751 // a duplicate torrent is being added
2752 if (hasMetadata)
2754 // Trying to set metadata to existing torrent in case if it has none
2755 torrent->setMetadata(std::get<TorrentInfo>(source));
2758 if (!isMergeTrackersEnabled())
2760 LogMsg(tr("Detected an attempt to add a duplicate torrent. Merging of trackers is disabled. Torrent: %1").arg(torrent->name()));
2761 return false;
2764 const bool isPrivate = torrent->isPrivate() || (hasMetadata && std::get<TorrentInfo>(source).isPrivate());
2765 if (isPrivate)
2767 LogMsg(tr("Detected an attempt to add a duplicate torrent. Trackers cannot be merged because it is a private torrent. Torrent: %1").arg(torrent->name()));
2768 return false;
2771 if (hasMetadata)
2773 const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
2775 // merge trackers and web seeds
2776 torrent->addTrackers(torrentInfo.trackers());
2777 torrent->addUrlSeeds(torrentInfo.urlSeeds());
2779 else
2781 const MagnetUri &magnetUri = std::get<MagnetUri>(source);
2783 // merge trackers and web seeds
2784 torrent->addTrackers(magnetUri.trackers());
2785 torrent->addUrlSeeds(magnetUri.urlSeeds());
2788 LogMsg(tr("Detected an attempt to add a duplicate torrent. Trackers are merged from new source. Torrent: %1").arg(torrent->name()));
2789 return false;
2792 // It looks illogical that we don't just use an existing handle,
2793 // but as previous experience has shown, it actually creates unnecessary
2794 // problems and unwanted behavior due to the fact that it was originally
2795 // added with parameters other than those provided by the user.
2796 cancelDownloadMetadata(id);
2797 if (infoHash.isHybrid())
2798 cancelDownloadMetadata(altID);
2800 LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams);
2801 lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
2803 bool isFindingIncompleteFiles = false;
2805 const bool useAutoTMM = loadTorrentParams.useAutoTMM;
2806 const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
2808 if (hasMetadata)
2810 // Torrent that is being added with metadata is considered to be added as stopped
2811 // if "metadata received" stop condition is set for it.
2812 if (loadTorrentParams.stopCondition == Torrent::StopCondition::MetadataReceived)
2814 loadTorrentParams.stopped = true;
2815 loadTorrentParams.stopCondition = Torrent::StopCondition::None;
2818 const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
2820 Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
2822 PathList filePaths = addTorrentParams.filePaths;
2823 if (filePaths.isEmpty())
2825 filePaths = torrentInfo.filePaths();
2826 if (loadTorrentParams.contentLayout != TorrentContentLayout::Original)
2828 const Path originalRootFolder = Path::findRootFolder(filePaths);
2829 const auto originalContentLayout = (originalRootFolder.isEmpty()
2830 ? TorrentContentLayout::NoSubfolder
2831 : TorrentContentLayout::Subfolder);
2832 if (loadTorrentParams.contentLayout != originalContentLayout)
2834 if (loadTorrentParams.contentLayout == TorrentContentLayout::NoSubfolder)
2835 Path::stripRootFolder(filePaths);
2836 else
2837 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2842 // if torrent name wasn't explicitly set we handle the case of
2843 // initial renaming of torrent content and rename torrent accordingly
2844 if (loadTorrentParams.name.isEmpty())
2846 QString contentName = Path::findRootFolder(filePaths).toString();
2847 if (contentName.isEmpty() && (filePaths.size() == 1))
2848 contentName = filePaths.at(0).filename();
2850 if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
2851 loadTorrentParams.name = contentName;
2854 if (!loadTorrentParams.hasFinishedStatus)
2856 const Path actualDownloadPath = useAutoTMM
2857 ? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
2858 findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
2859 isFindingIncompleteFiles = true;
2862 const auto nativeIndexes = torrentInfo.nativeIndexes();
2863 if (!isFindingIncompleteFiles)
2865 for (int index = 0; index < filePaths.size(); ++index)
2866 p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
2869 Q_ASSERT(p.file_priorities.empty());
2870 Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
2872 const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2873 // Use qBittorrent default priority rather than libtorrent's (4)
2874 p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2876 if (addTorrentParams.filePriorities.isEmpty())
2878 if (isExcludedFileNamesEnabled())
2880 // Check file name blacklist when priorities are not explicitly set
2881 for (int i = 0; i < filePaths.size(); ++i)
2883 if (isFilenameExcluded(filePaths.at(i).filename()))
2884 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = lt::dont_download;
2888 else
2890 for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
2891 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
2894 p.ti = torrentInfo.nativeInfo();
2896 else
2898 const MagnetUri &magnetUri = std::get<MagnetUri>(source);
2899 p = magnetUri.addTorrentParams();
2901 if (loadTorrentParams.name.isEmpty() && !p.name.empty())
2902 loadTorrentParams.name = QString::fromStdString(p.name);
2905 p.save_path = actualSavePath.toString().toStdString();
2907 if (isAddTrackersEnabled() && !(hasMetadata && p.ti->priv()))
2909 p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
2910 p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
2911 p.tracker_tiers.resize(p.trackers.size(), 0);
2912 for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
2914 p.trackers.push_back(trackerEntry.url.toStdString());
2915 p.tracker_tiers.push_back(trackerEntry.tier);
2919 p.upload_limit = addTorrentParams.uploadLimit;
2920 p.download_limit = addTorrentParams.downloadLimit;
2922 // Preallocation mode
2923 p.storage_mode = isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse;
2925 if (addTorrentParams.sequential)
2926 p.flags |= lt::torrent_flags::sequential_download;
2927 else
2928 p.flags &= ~lt::torrent_flags::sequential_download;
2930 // Seeding mode
2931 // Skip checking and directly start seeding
2932 if (addTorrentParams.skipChecking)
2933 p.flags |= lt::torrent_flags::seed_mode;
2934 else
2935 p.flags &= ~lt::torrent_flags::seed_mode;
2937 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::AutoManaged))
2938 p.flags |= lt::torrent_flags::paused;
2939 else
2940 p.flags &= ~lt::torrent_flags::paused;
2941 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::Forced))
2942 p.flags &= ~lt::torrent_flags::auto_managed;
2943 else
2944 p.flags |= lt::torrent_flags::auto_managed;
2946 p.flags |= lt::torrent_flags::duplicate_is_error;
2948 p.added_time = std::time(nullptr);
2950 // Limits
2951 p.max_connections = maxConnectionsPerTorrent();
2952 p.max_uploads = maxUploadsPerTorrent();
2954 p.userdata = LTClientData(new ExtensionData);
2955 #ifndef QBT_USES_LIBTORRENT2
2956 p.storage = customStorageConstructor;
2957 #endif
2959 m_loadingTorrents.insert(id, loadTorrentParams);
2960 if (infoHash.isHybrid())
2961 m_hybridTorrentsByAltID.insert(altID, nullptr);
2962 if (!isFindingIncompleteFiles)
2963 m_nativeSession->async_add_torrent(p);
2965 return true;
2968 void SessionImpl::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
2969 , const Path &downloadPath, const PathList &filePaths) const
2971 Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
2973 const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
2974 const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
2975 QMetaObject::invokeMethod(m_fileSearcher, [=]()
2977 m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath, isAppendExtensionEnabled());
2981 void SessionImpl::enablePortMapping()
2983 invokeAsync([this]
2985 if (m_isPortMappingEnabled)
2986 return;
2988 lt::settings_pack settingsPack;
2989 settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
2990 settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
2991 m_nativeSession->apply_settings(std::move(settingsPack));
2993 m_isPortMappingEnabled = true;
2995 LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
2999 void SessionImpl::disablePortMapping()
3001 invokeAsync([this]
3003 if (!m_isPortMappingEnabled)
3004 return;
3006 lt::settings_pack settingsPack;
3007 settingsPack.set_bool(lt::settings_pack::enable_upnp, false);
3008 settingsPack.set_bool(lt::settings_pack::enable_natpmp, false);
3009 m_nativeSession->apply_settings(std::move(settingsPack));
3011 m_mappedPorts.clear();
3012 m_isPortMappingEnabled = false;
3014 LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO);
3018 void SessionImpl::addMappedPorts(const QSet<quint16> &ports)
3020 invokeAsync([this, ports]
3022 if (!m_isPortMappingEnabled)
3023 return;
3025 for (const quint16 port : ports)
3027 if (!m_mappedPorts.contains(port))
3028 m_mappedPorts.insert(port, m_nativeSession->add_port_mapping(lt::session::tcp, port, port));
3033 void SessionImpl::removeMappedPorts(const QSet<quint16> &ports)
3035 invokeAsync([this, ports]
3037 if (!m_isPortMappingEnabled)
3038 return;
3040 Algorithm::removeIf(m_mappedPorts, [this, ports](const quint16 port, const std::vector<lt::port_mapping_t> &handles)
3042 if (!ports.contains(port))
3043 return false;
3045 for (const lt::port_mapping_t &handle : handles)
3046 m_nativeSession->delete_port_mapping(handle);
3048 return true;
3053 void SessionImpl::invokeAsync(std::function<void ()> func)
3055 m_asyncWorker->start(std::move(func));
3058 // Add a torrent to libtorrent session in hidden mode
3059 // and force it to download its metadata
3060 bool SessionImpl::downloadMetadata(const MagnetUri &magnetUri)
3062 if (!magnetUri.isValid())
3063 return false;
3065 const InfoHash infoHash = magnetUri.infoHash();
3067 // We should not add torrent if it's already
3068 // processed or adding to session
3069 if (isKnownTorrent(infoHash))
3070 return false;
3072 lt::add_torrent_params p = magnetUri.addTorrentParams();
3074 if (isAddTrackersEnabled())
3076 // Use "additional trackers" when metadata retrieving (this can help when the DHT nodes are few)
3077 p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
3078 p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
3079 p.tracker_tiers.resize(p.trackers.size(), 0);
3080 for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
3082 p.trackers.push_back(trackerEntry.url.toStdString());
3083 p.tracker_tiers.push_back(trackerEntry.tier);
3087 // Flags
3088 // Preallocation mode
3089 if (isPreallocationEnabled())
3090 p.storage_mode = lt::storage_mode_allocate;
3091 else
3092 p.storage_mode = lt::storage_mode_sparse;
3094 // Limits
3095 p.max_connections = maxConnectionsPerTorrent();
3096 p.max_uploads = maxUploadsPerTorrent();
3098 const auto id = TorrentID::fromInfoHash(infoHash);
3099 const Path savePath = Utils::Fs::tempPath() / Path(id.toString());
3100 p.save_path = savePath.toString().toStdString();
3102 // Forced start
3103 p.flags &= ~lt::torrent_flags::paused;
3104 p.flags &= ~lt::torrent_flags::auto_managed;
3106 // Solution to avoid accidental file writes
3107 p.flags |= lt::torrent_flags::upload_mode;
3109 #ifndef QBT_USES_LIBTORRENT2
3110 p.storage = customStorageConstructor;
3111 #endif
3113 // Adding torrent to libtorrent session
3114 m_nativeSession->async_add_torrent(p);
3115 m_downloadedMetadata.insert(id, {});
3117 return true;
3120 void SessionImpl::exportTorrentFile(const Torrent *torrent, const Path &folderPath)
3122 if (!folderPath.exists() && !Utils::Fs::mkpath(folderPath))
3123 return;
3125 const QString validName = Utils::Fs::toValidFileName(torrent->name());
3126 QString torrentExportFilename = u"%1.torrent"_s.arg(validName);
3127 Path newTorrentPath = folderPath / Path(torrentExportFilename);
3128 int counter = 0;
3129 while (newTorrentPath.exists())
3131 // Append number to torrent name to make it unique
3132 torrentExportFilename = u"%1 %2.torrent"_s.arg(validName).arg(++counter);
3133 newTorrentPath = folderPath / Path(torrentExportFilename);
3136 const nonstd::expected<void, QString> result = torrent->exportToFile(newTorrentPath);
3137 if (!result)
3139 LogMsg(tr("Failed to export torrent. Torrent: \"%1\". Destination: \"%2\". Reason: \"%3\"")
3140 .arg(torrent->name(), newTorrentPath.toString(), result.error()), Log::WARNING);
3144 void SessionImpl::generateResumeData()
3146 for (TorrentImpl *const torrent : asConst(m_torrents))
3148 if (!torrent->isValid()) continue;
3150 if (torrent->needSaveResumeData())
3152 torrent->saveResumeData();
3153 m_needSaveResumeDataTorrents.remove(torrent->id());
3158 // Called on exit
3159 void SessionImpl::saveResumeData()
3161 for (const TorrentImpl *torrent : asConst(m_torrents))
3163 // When the session is terminated due to unrecoverable error
3164 // some of the torrent handles can be corrupted
3167 torrent->nativeHandle().save_resume_data(lt::torrent_handle::only_if_modified);
3168 ++m_numResumeData;
3170 catch (const std::exception &) {}
3173 // clear queued storage move jobs except the current ongoing one
3174 if (m_moveStorageQueue.size() > 1)
3176 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
3177 m_moveStorageQueue = m_moveStorageQueue.mid(0, 1);
3178 #else
3179 m_moveStorageQueue.resize(1);
3180 #endif
3183 QElapsedTimer timer;
3184 timer.start();
3186 while ((m_numResumeData > 0) || !m_moveStorageQueue.isEmpty() || m_needSaveTorrentsQueue)
3188 const lt::seconds waitTime {5};
3189 const lt::seconds expireTime {30};
3191 // only terminate when no storage is moving
3192 if (timer.hasExpired(lt::total_milliseconds(expireTime)) && m_moveStorageQueue.isEmpty())
3194 LogMsg(tr("Aborted saving resume data. Number of outstanding torrents: %1").arg(QString::number(m_numResumeData))
3195 , Log::CRITICAL);
3196 break;
3199 const std::vector<lt::alert *> alerts = getPendingAlerts(waitTime);
3201 bool hasWantedAlert = false;
3202 for (const lt::alert *a : alerts)
3204 if (const int alertType = a->type();
3205 (alertType == lt::save_resume_data_alert::alert_type) || (alertType == lt::save_resume_data_failed_alert::alert_type)
3206 || (alertType == lt::storage_moved_alert::alert_type) || (alertType == lt::storage_moved_failed_alert::alert_type)
3207 || (alertType == lt::state_update_alert::alert_type))
3209 hasWantedAlert = true;
3212 handleAlert(a);
3215 if (hasWantedAlert)
3216 timer.start();
3220 void SessionImpl::saveTorrentsQueue()
3222 QVector<TorrentID> queue;
3223 for (const TorrentImpl *torrent : asConst(m_torrents))
3225 if (const int queuePos = torrent->queuePosition(); queuePos >= 0)
3227 if (queuePos >= queue.size())
3228 queue.resize(queuePos + 1);
3229 queue[queuePos] = torrent->id();
3233 m_resumeDataStorage->storeQueue(queue);
3234 m_needSaveTorrentsQueue = false;
3237 void SessionImpl::removeTorrentsQueue()
3239 m_resumeDataStorage->storeQueue({});
3240 m_torrentsQueueChanged = false;
3241 m_needSaveTorrentsQueue = false;
3244 void SessionImpl::setSavePath(const Path &path)
3246 const auto newPath = (path.isAbsolute() ? path : (specialFolderLocation(SpecialFolder::Downloads) / path));
3247 if (newPath == m_savePath)
3248 return;
3250 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3252 QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
3253 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
3255 const QString &categoryName = it.key();
3256 const CategoryOptions &categoryOptions = it.value();
3257 if (categoryOptions.savePath.isRelative())
3258 affectedCatogories.insert(categoryName);
3261 for (TorrentImpl *const torrent : asConst(m_torrents))
3263 if (affectedCatogories.contains(torrent->category()))
3264 torrent->setAutoTMMEnabled(false);
3268 m_savePath = newPath;
3269 for (TorrentImpl *const torrent : asConst(m_torrents))
3270 torrent->handleCategoryOptionsChanged();
3273 void SessionImpl::setDownloadPath(const Path &path)
3275 const Path newPath = (path.isAbsolute() ? path : (savePath() / Path(u"temp"_s) / path));
3276 if (newPath == m_downloadPath)
3277 return;
3279 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3281 QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
3282 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
3284 const QString &categoryName = it.key();
3285 const CategoryOptions &categoryOptions = it.value();
3286 const CategoryOptions::DownloadPathOption downloadPathOption =
3287 categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
3288 if (downloadPathOption.enabled && downloadPathOption.path.isRelative())
3289 affectedCatogories.insert(categoryName);
3292 for (TorrentImpl *const torrent : asConst(m_torrents))
3294 if (affectedCatogories.contains(torrent->category()))
3295 torrent->setAutoTMMEnabled(false);
3299 m_downloadPath = newPath;
3300 for (TorrentImpl *const torrent : asConst(m_torrents))
3301 torrent->handleCategoryOptionsChanged();
3304 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
3305 void SessionImpl::networkOnlineStateChanged(const bool online)
3307 LogMsg(tr("System network status changed to %1", "e.g: System network status changed to ONLINE").arg(online ? tr("ONLINE") : tr("OFFLINE")), Log::INFO);
3310 void SessionImpl::networkConfigurationChange(const QNetworkConfiguration &cfg)
3312 const QString configuredInterfaceName = networkInterface();
3313 // Empty means "Any Interface". In this case libtorrent has binded to 0.0.0.0 so any change to any interface will
3314 // be automatically picked up. Otherwise we would rebinding here to 0.0.0.0 again.
3315 if (configuredInterfaceName.isEmpty()) return;
3317 const QString changedInterface = cfg.name();
3319 if (configuredInterfaceName == changedInterface)
3321 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);
3322 configureListeningInterface();
3325 #endif
3327 QStringList SessionImpl::getListeningIPs() const
3329 QStringList IPs;
3331 const QString ifaceName = networkInterface();
3332 const QString ifaceAddr = networkInterfaceAddress();
3333 const QHostAddress configuredAddr(ifaceAddr);
3334 const bool allIPv4 = (ifaceAddr == u"0.0.0.0"); // Means All IPv4 addresses
3335 const bool allIPv6 = (ifaceAddr == u"::"); // Means All IPv6 addresses
3337 if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull())
3339 LogMsg(tr("The configured network address is invalid. Address: \"%1\"").arg(ifaceAddr), Log::CRITICAL);
3340 // Pass the invalid user configured interface name/address to libtorrent
3341 // in hopes that it will come online later.
3342 // This will not cause IP leak but allow user to reconnect the interface
3343 // and re-establish connection without restarting the client.
3344 IPs.append(ifaceAddr);
3345 return IPs;
3348 if (ifaceName.isEmpty())
3350 if (ifaceAddr.isEmpty())
3351 return {u"0.0.0.0"_s, u"::"_s}; // Indicates all interfaces + all addresses (aka default)
3353 if (allIPv4)
3354 return {u"0.0.0.0"_s};
3356 if (allIPv6)
3357 return {u"::"_s};
3360 const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
3362 if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
3363 || (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
3364 return;
3366 if ((match == addr) || allIPv4 || allIPv6)
3367 IPs.append(addr.toString());
3370 if (ifaceName.isEmpty())
3372 const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
3373 for (const auto &addr : addresses)
3374 checkAndAddIP(addr, configuredAddr);
3376 // At this point ifaceAddr was non-empty
3377 // If IPs.isEmpty() it means the configured Address was not found
3378 if (IPs.isEmpty())
3380 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3381 .arg(ifaceAddr), Log::CRITICAL);
3382 IPs.append(ifaceAddr);
3385 return IPs;
3388 // Attempt to listen on provided interface
3389 const QNetworkInterface networkIFace = QNetworkInterface::interfaceFromName(ifaceName);
3390 if (!networkIFace.isValid())
3392 qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName));
3393 LogMsg(tr("The configured network interface is invalid. Interface: \"%1\"").arg(ifaceName), Log::CRITICAL);
3394 IPs.append(ifaceName);
3395 return IPs;
3398 if (ifaceAddr.isEmpty())
3400 IPs.append(ifaceName);
3401 return IPs; // On Windows calling code converts it to GUID
3404 const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
3405 qDebug() << "This network interface has " << addresses.size() << " IP addresses";
3406 for (const QNetworkAddressEntry &entry : addresses)
3407 checkAndAddIP(entry.ip(), configuredAddr);
3409 // Make sure there is at least one IP
3410 // At this point there was an explicit interface and an explicit address set
3411 // and the address should have been found
3412 if (IPs.isEmpty())
3414 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3415 .arg(ifaceAddr), Log::CRITICAL);
3416 IPs.append(ifaceAddr);
3419 return IPs;
3422 // Set the ports range in which is chosen the port
3423 // the BitTorrent session will listen to
3424 void SessionImpl::configureListeningInterface()
3426 m_listenInterfaceConfigured = false;
3427 configureDeferred();
3430 int SessionImpl::globalDownloadSpeedLimit() const
3432 // Unfortunately the value was saved as KiB instead of B.
3433 // But it is better to pass it around internally(+ webui) as Bytes.
3434 return m_globalDownloadSpeedLimit * 1024;
3437 void SessionImpl::setGlobalDownloadSpeedLimit(const int limit)
3439 // Unfortunately the value was saved as KiB instead of B.
3440 // But it is better to pass it around internally(+ webui) as Bytes.
3441 if (limit == globalDownloadSpeedLimit())
3442 return;
3444 if (limit <= 0)
3445 m_globalDownloadSpeedLimit = 0;
3446 else if (limit <= 1024)
3447 m_globalDownloadSpeedLimit = 1;
3448 else
3449 m_globalDownloadSpeedLimit = (limit / 1024);
3451 if (!isAltGlobalSpeedLimitEnabled())
3452 configureDeferred();
3455 int SessionImpl::globalUploadSpeedLimit() const
3457 // Unfortunately the value was saved as KiB instead of B.
3458 // But it is better to pass it around internally(+ webui) as Bytes.
3459 return m_globalUploadSpeedLimit * 1024;
3462 void SessionImpl::setGlobalUploadSpeedLimit(const int limit)
3464 // Unfortunately the value was saved as KiB instead of B.
3465 // But it is better to pass it around internally(+ webui) as Bytes.
3466 if (limit == globalUploadSpeedLimit())
3467 return;
3469 if (limit <= 0)
3470 m_globalUploadSpeedLimit = 0;
3471 else if (limit <= 1024)
3472 m_globalUploadSpeedLimit = 1;
3473 else
3474 m_globalUploadSpeedLimit = (limit / 1024);
3476 if (!isAltGlobalSpeedLimitEnabled())
3477 configureDeferred();
3480 int SessionImpl::altGlobalDownloadSpeedLimit() const
3482 // Unfortunately the value was saved as KiB instead of B.
3483 // But it is better to pass it around internally(+ webui) as Bytes.
3484 return m_altGlobalDownloadSpeedLimit * 1024;
3487 void SessionImpl::setAltGlobalDownloadSpeedLimit(const int limit)
3489 // Unfortunately the value was saved as KiB instead of B.
3490 // But it is better to pass it around internally(+ webui) as Bytes.
3491 if (limit == altGlobalDownloadSpeedLimit())
3492 return;
3494 if (limit <= 0)
3495 m_altGlobalDownloadSpeedLimit = 0;
3496 else if (limit <= 1024)
3497 m_altGlobalDownloadSpeedLimit = 1;
3498 else
3499 m_altGlobalDownloadSpeedLimit = (limit / 1024);
3501 if (isAltGlobalSpeedLimitEnabled())
3502 configureDeferred();
3505 int SessionImpl::altGlobalUploadSpeedLimit() const
3507 // Unfortunately the value was saved as KiB instead of B.
3508 // But it is better to pass it around internally(+ webui) as Bytes.
3509 return m_altGlobalUploadSpeedLimit * 1024;
3512 void SessionImpl::setAltGlobalUploadSpeedLimit(const int limit)
3514 // Unfortunately the value was saved as KiB instead of B.
3515 // But it is better to pass it around internally(+ webui) as Bytes.
3516 if (limit == altGlobalUploadSpeedLimit())
3517 return;
3519 if (limit <= 0)
3520 m_altGlobalUploadSpeedLimit = 0;
3521 else if (limit <= 1024)
3522 m_altGlobalUploadSpeedLimit = 1;
3523 else
3524 m_altGlobalUploadSpeedLimit = (limit / 1024);
3526 if (isAltGlobalSpeedLimitEnabled())
3527 configureDeferred();
3530 int SessionImpl::downloadSpeedLimit() const
3532 return isAltGlobalSpeedLimitEnabled()
3533 ? altGlobalDownloadSpeedLimit()
3534 : globalDownloadSpeedLimit();
3537 void SessionImpl::setDownloadSpeedLimit(const int limit)
3539 if (isAltGlobalSpeedLimitEnabled())
3540 setAltGlobalDownloadSpeedLimit(limit);
3541 else
3542 setGlobalDownloadSpeedLimit(limit);
3545 int SessionImpl::uploadSpeedLimit() const
3547 return isAltGlobalSpeedLimitEnabled()
3548 ? altGlobalUploadSpeedLimit()
3549 : globalUploadSpeedLimit();
3552 void SessionImpl::setUploadSpeedLimit(const int limit)
3554 if (isAltGlobalSpeedLimitEnabled())
3555 setAltGlobalUploadSpeedLimit(limit);
3556 else
3557 setGlobalUploadSpeedLimit(limit);
3560 bool SessionImpl::isAltGlobalSpeedLimitEnabled() const
3562 return m_isAltGlobalSpeedLimitEnabled;
3565 void SessionImpl::setAltGlobalSpeedLimitEnabled(const bool enabled)
3567 if (enabled == isAltGlobalSpeedLimitEnabled()) return;
3569 // Save new state to remember it on startup
3570 m_isAltGlobalSpeedLimitEnabled = enabled;
3571 applyBandwidthLimits();
3572 // Notify
3573 emit speedLimitModeChanged(m_isAltGlobalSpeedLimitEnabled);
3576 bool SessionImpl::isBandwidthSchedulerEnabled() const
3578 return m_isBandwidthSchedulerEnabled;
3581 void SessionImpl::setBandwidthSchedulerEnabled(const bool enabled)
3583 if (enabled != isBandwidthSchedulerEnabled())
3585 m_isBandwidthSchedulerEnabled = enabled;
3586 if (enabled)
3587 enableBandwidthScheduler();
3588 else
3589 delete m_bwScheduler;
3593 bool SessionImpl::isPerformanceWarningEnabled() const
3595 return m_isPerformanceWarningEnabled;
3598 void SessionImpl::setPerformanceWarningEnabled(const bool enable)
3600 if (enable == m_isPerformanceWarningEnabled)
3601 return;
3603 m_isPerformanceWarningEnabled = enable;
3604 configureDeferred();
3607 int SessionImpl::saveResumeDataInterval() const
3609 return m_saveResumeDataInterval;
3612 void SessionImpl::setSaveResumeDataInterval(const int value)
3614 if (value == m_saveResumeDataInterval)
3615 return;
3617 m_saveResumeDataInterval = value;
3619 if (value > 0)
3621 m_resumeDataTimer->setInterval(std::chrono::minutes(value));
3622 m_resumeDataTimer->start();
3624 else
3626 m_resumeDataTimer->stop();
3630 int SessionImpl::port() const
3632 return m_port;
3635 void SessionImpl::setPort(const int port)
3637 if (port != m_port)
3639 m_port = port;
3640 configureListeningInterface();
3642 if (isReannounceWhenAddressChangedEnabled())
3643 reannounceToAllTrackers();
3647 QString SessionImpl::networkInterface() const
3649 return m_networkInterface;
3652 void SessionImpl::setNetworkInterface(const QString &iface)
3654 if (iface != networkInterface())
3656 m_networkInterface = iface;
3657 configureListeningInterface();
3661 QString SessionImpl::networkInterfaceName() const
3663 return m_networkInterfaceName;
3666 void SessionImpl::setNetworkInterfaceName(const QString &name)
3668 m_networkInterfaceName = name;
3671 QString SessionImpl::networkInterfaceAddress() const
3673 return m_networkInterfaceAddress;
3676 void SessionImpl::setNetworkInterfaceAddress(const QString &address)
3678 if (address != networkInterfaceAddress())
3680 m_networkInterfaceAddress = address;
3681 configureListeningInterface();
3685 int SessionImpl::encryption() const
3687 return m_encryption;
3690 void SessionImpl::setEncryption(const int state)
3692 if (state != encryption())
3694 m_encryption = state;
3695 configureDeferred();
3696 LogMsg(tr("Encryption support: %1").arg(
3697 state == 0 ? tr("ON") : ((state == 1) ? tr("FORCED") : tr("OFF")))
3698 , Log::INFO);
3702 int SessionImpl::maxActiveCheckingTorrents() const
3704 return m_maxActiveCheckingTorrents;
3707 void SessionImpl::setMaxActiveCheckingTorrents(const int val)
3709 if (val == m_maxActiveCheckingTorrents)
3710 return;
3712 m_maxActiveCheckingTorrents = val;
3713 configureDeferred();
3716 bool SessionImpl::isI2PEnabled() const
3718 return m_isI2PEnabled;
3721 void SessionImpl::setI2PEnabled(const bool enabled)
3723 if (m_isI2PEnabled != enabled)
3725 m_isI2PEnabled = enabled;
3726 configureDeferred();
3730 QString SessionImpl::I2PAddress() const
3732 return m_I2PAddress;
3735 void SessionImpl::setI2PAddress(const QString &address)
3737 if (m_I2PAddress != address)
3739 m_I2PAddress = address;
3740 configureDeferred();
3744 int SessionImpl::I2PPort() const
3746 return m_I2PPort;
3749 void SessionImpl::setI2PPort(int port)
3751 if (m_I2PPort != port)
3753 m_I2PPort = port;
3754 configureDeferred();
3758 bool SessionImpl::I2PMixedMode() const
3760 return m_I2PMixedMode;
3763 void SessionImpl::setI2PMixedMode(const bool enabled)
3765 if (m_I2PMixedMode != enabled)
3767 m_I2PMixedMode = enabled;
3768 configureDeferred();
3772 int SessionImpl::I2PInboundQuantity() const
3774 return m_I2PInboundQuantity;
3777 void SessionImpl::setI2PInboundQuantity(const int value)
3779 if (value == m_I2PInboundQuantity)
3780 return;
3782 m_I2PInboundQuantity = value;
3783 configureDeferred();
3786 int SessionImpl::I2POutboundQuantity() const
3788 return m_I2POutboundQuantity;
3791 void SessionImpl::setI2POutboundQuantity(const int value)
3793 if (value == m_I2POutboundQuantity)
3794 return;
3796 m_I2POutboundQuantity = value;
3797 configureDeferred();
3800 int SessionImpl::I2PInboundLength() const
3802 return m_I2PInboundLength;
3805 void SessionImpl::setI2PInboundLength(const int value)
3807 if (value == m_I2PInboundLength)
3808 return;
3810 m_I2PInboundLength = value;
3811 configureDeferred();
3814 int SessionImpl::I2POutboundLength() const
3816 return m_I2POutboundLength;
3819 void SessionImpl::setI2POutboundLength(const int value)
3821 if (value == m_I2POutboundLength)
3822 return;
3824 m_I2POutboundLength = value;
3825 configureDeferred();
3828 bool SessionImpl::isProxyPeerConnectionsEnabled() const
3830 return m_isProxyPeerConnectionsEnabled;
3833 void SessionImpl::setProxyPeerConnectionsEnabled(const bool enabled)
3835 if (enabled != isProxyPeerConnectionsEnabled())
3837 m_isProxyPeerConnectionsEnabled = enabled;
3838 configureDeferred();
3842 ChokingAlgorithm SessionImpl::chokingAlgorithm() const
3844 return m_chokingAlgorithm;
3847 void SessionImpl::setChokingAlgorithm(const ChokingAlgorithm mode)
3849 if (mode == m_chokingAlgorithm) return;
3851 m_chokingAlgorithm = mode;
3852 configureDeferred();
3855 SeedChokingAlgorithm SessionImpl::seedChokingAlgorithm() const
3857 return m_seedChokingAlgorithm;
3860 void SessionImpl::setSeedChokingAlgorithm(const SeedChokingAlgorithm mode)
3862 if (mode == m_seedChokingAlgorithm) return;
3864 m_seedChokingAlgorithm = mode;
3865 configureDeferred();
3868 bool SessionImpl::isAddTrackersEnabled() const
3870 return m_isAddTrackersEnabled;
3873 void SessionImpl::setAddTrackersEnabled(const bool enabled)
3875 m_isAddTrackersEnabled = enabled;
3878 QString SessionImpl::additionalTrackers() const
3880 return m_additionalTrackers;
3883 void SessionImpl::setAdditionalTrackers(const QString &trackers)
3885 if (trackers != additionalTrackers())
3887 m_additionalTrackers = trackers;
3888 populateAdditionalTrackers();
3892 bool SessionImpl::isIPFilteringEnabled() const
3894 return m_isIPFilteringEnabled;
3897 void SessionImpl::setIPFilteringEnabled(const bool enabled)
3899 if (enabled != m_isIPFilteringEnabled)
3901 m_isIPFilteringEnabled = enabled;
3902 m_IPFilteringConfigured = false;
3903 configureDeferred();
3907 Path SessionImpl::IPFilterFile() const
3909 return m_IPFilterFile;
3912 void SessionImpl::setIPFilterFile(const Path &path)
3914 if (path != IPFilterFile())
3916 m_IPFilterFile = path;
3917 m_IPFilteringConfigured = false;
3918 configureDeferred();
3922 bool SessionImpl::isExcludedFileNamesEnabled() const
3924 return m_isExcludedFileNamesEnabled;
3927 void SessionImpl::setExcludedFileNamesEnabled(const bool enabled)
3929 if (m_isExcludedFileNamesEnabled == enabled)
3930 return;
3932 m_isExcludedFileNamesEnabled = enabled;
3934 if (enabled)
3935 populateExcludedFileNamesRegExpList();
3936 else
3937 m_excludedFileNamesRegExpList.clear();
3940 QStringList SessionImpl::excludedFileNames() const
3942 return m_excludedFileNames;
3945 void SessionImpl::setExcludedFileNames(const QStringList &excludedFileNames)
3947 if (excludedFileNames != m_excludedFileNames)
3949 m_excludedFileNames = excludedFileNames;
3950 populateExcludedFileNamesRegExpList();
3954 void SessionImpl::populateExcludedFileNamesRegExpList()
3956 const QStringList excludedNames = excludedFileNames();
3958 m_excludedFileNamesRegExpList.clear();
3959 m_excludedFileNamesRegExpList.reserve(excludedNames.size());
3961 for (const QString &str : excludedNames)
3963 const QString pattern = QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(str));
3964 const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
3965 m_excludedFileNamesRegExpList.append(re);
3969 bool SessionImpl::isFilenameExcluded(const QString &fileName) const
3971 if (!isExcludedFileNamesEnabled())
3972 return false;
3974 return std::any_of(m_excludedFileNamesRegExpList.begin(), m_excludedFileNamesRegExpList.end(), [&fileName](const QRegularExpression &re)
3976 return re.match(fileName).hasMatch();
3980 void SessionImpl::setBannedIPs(const QStringList &newList)
3982 if (newList == m_bannedIPs)
3983 return; // do nothing
3984 // here filter out incorrect IP
3985 QStringList filteredList;
3986 for (const QString &ip : newList)
3988 if (Utils::Net::isValidIP(ip))
3990 // the same IPv6 addresses could be written in different forms;
3991 // QHostAddress::toString() result format follows RFC5952;
3992 // thus we avoid duplicate entries pointing to the same address
3993 filteredList << QHostAddress(ip).toString();
3995 else
3997 LogMsg(tr("Rejected invalid IP address while applying the list of banned IP addresses. IP: \"%1\"")
3998 .arg(ip)
3999 , Log::WARNING);
4002 // now we have to sort IPs and make them unique
4003 filteredList.sort();
4004 filteredList.removeDuplicates();
4005 // Again ensure that the new list is different from the stored one.
4006 if (filteredList == m_bannedIPs)
4007 return; // do nothing
4008 // store to session settings
4009 // also here we have to recreate filter list including 3rd party ban file
4010 // and install it again into m_session
4011 m_bannedIPs = filteredList;
4012 m_IPFilteringConfigured = false;
4013 configureDeferred();
4016 ResumeDataStorageType SessionImpl::resumeDataStorageType() const
4018 return m_resumeDataStorageType;
4021 void SessionImpl::setResumeDataStorageType(const ResumeDataStorageType type)
4023 m_resumeDataStorageType = type;
4026 bool SessionImpl::isMergeTrackersEnabled() const
4028 return m_isMergeTrackersEnabled;
4031 void SessionImpl::setMergeTrackersEnabled(const bool enabled)
4033 m_isMergeTrackersEnabled = enabled;
4036 QStringList SessionImpl::bannedIPs() const
4038 return m_bannedIPs;
4041 bool SessionImpl::isRestored() const
4043 return m_isRestored;
4046 int SessionImpl::maxConnectionsPerTorrent() const
4048 return m_maxConnectionsPerTorrent;
4051 void SessionImpl::setMaxConnectionsPerTorrent(int max)
4053 max = (max > 0) ? max : -1;
4054 if (max != maxConnectionsPerTorrent())
4056 m_maxConnectionsPerTorrent = max;
4058 for (const TorrentImpl *torrent : asConst(m_torrents))
4062 torrent->nativeHandle().set_max_connections(max);
4064 catch (const std::exception &) {}
4069 int SessionImpl::maxUploadsPerTorrent() const
4071 return m_maxUploadsPerTorrent;
4074 void SessionImpl::setMaxUploadsPerTorrent(int max)
4076 max = (max > 0) ? max : -1;
4077 if (max != maxUploadsPerTorrent())
4079 m_maxUploadsPerTorrent = max;
4081 for (const TorrentImpl *torrent : asConst(m_torrents))
4085 torrent->nativeHandle().set_max_uploads(max);
4087 catch (const std::exception &) {}
4092 bool SessionImpl::announceToAllTrackers() const
4094 return m_announceToAllTrackers;
4097 void SessionImpl::setAnnounceToAllTrackers(const bool val)
4099 if (val != m_announceToAllTrackers)
4101 m_announceToAllTrackers = val;
4102 configureDeferred();
4106 bool SessionImpl::announceToAllTiers() const
4108 return m_announceToAllTiers;
4111 void SessionImpl::setAnnounceToAllTiers(const bool val)
4113 if (val != m_announceToAllTiers)
4115 m_announceToAllTiers = val;
4116 configureDeferred();
4120 int SessionImpl::peerTurnover() const
4122 return m_peerTurnover;
4125 void SessionImpl::setPeerTurnover(const int val)
4127 if (val == m_peerTurnover)
4128 return;
4130 m_peerTurnover = val;
4131 configureDeferred();
4134 int SessionImpl::peerTurnoverCutoff() const
4136 return m_peerTurnoverCutoff;
4139 void SessionImpl::setPeerTurnoverCutoff(const int val)
4141 if (val == m_peerTurnoverCutoff)
4142 return;
4144 m_peerTurnoverCutoff = val;
4145 configureDeferred();
4148 int SessionImpl::peerTurnoverInterval() const
4150 return m_peerTurnoverInterval;
4153 void SessionImpl::setPeerTurnoverInterval(const int val)
4155 if (val == m_peerTurnoverInterval)
4156 return;
4158 m_peerTurnoverInterval = val;
4159 configureDeferred();
4162 DiskIOType SessionImpl::diskIOType() const
4164 return m_diskIOType;
4167 void SessionImpl::setDiskIOType(const DiskIOType type)
4169 if (type != m_diskIOType)
4171 m_diskIOType = type;
4175 int SessionImpl::requestQueueSize() const
4177 return m_requestQueueSize;
4180 void SessionImpl::setRequestQueueSize(const int val)
4182 if (val == m_requestQueueSize)
4183 return;
4185 m_requestQueueSize = val;
4186 configureDeferred();
4189 int SessionImpl::asyncIOThreads() const
4191 return std::clamp(m_asyncIOThreads.get(), 1, 1024);
4194 void SessionImpl::setAsyncIOThreads(const int num)
4196 if (num == m_asyncIOThreads)
4197 return;
4199 m_asyncIOThreads = num;
4200 configureDeferred();
4203 int SessionImpl::hashingThreads() const
4205 return std::clamp(m_hashingThreads.get(), 1, 1024);
4208 void SessionImpl::setHashingThreads(const int num)
4210 if (num == m_hashingThreads)
4211 return;
4213 m_hashingThreads = num;
4214 configureDeferred();
4217 int SessionImpl::filePoolSize() const
4219 return m_filePoolSize;
4222 void SessionImpl::setFilePoolSize(const int size)
4224 if (size == m_filePoolSize)
4225 return;
4227 m_filePoolSize = size;
4228 configureDeferred();
4231 int SessionImpl::checkingMemUsage() const
4233 return std::max(1, m_checkingMemUsage.get());
4236 void SessionImpl::setCheckingMemUsage(int size)
4238 size = std::max(size, 1);
4240 if (size == m_checkingMemUsage)
4241 return;
4243 m_checkingMemUsage = size;
4244 configureDeferred();
4247 int SessionImpl::diskCacheSize() const
4249 #ifdef QBT_APP_64BIT
4250 return std::min(m_diskCacheSize.get(), 33554431); // 32768GiB
4251 #else
4252 // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
4253 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4254 return std::min(m_diskCacheSize.get(), 1536);
4255 #endif
4258 void SessionImpl::setDiskCacheSize(int size)
4260 #ifdef QBT_APP_64BIT
4261 size = std::min(size, 33554431); // 32768GiB
4262 #else
4263 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4264 size = std::min(size, 1536);
4265 #endif
4266 if (size != m_diskCacheSize)
4268 m_diskCacheSize = size;
4269 configureDeferred();
4273 int SessionImpl::diskCacheTTL() const
4275 return m_diskCacheTTL;
4278 void SessionImpl::setDiskCacheTTL(const int ttl)
4280 if (ttl != m_diskCacheTTL)
4282 m_diskCacheTTL = ttl;
4283 configureDeferred();
4287 qint64 SessionImpl::diskQueueSize() const
4289 return m_diskQueueSize;
4292 void SessionImpl::setDiskQueueSize(const qint64 size)
4294 if (size == m_diskQueueSize)
4295 return;
4297 m_diskQueueSize = size;
4298 configureDeferred();
4301 DiskIOReadMode SessionImpl::diskIOReadMode() const
4303 return m_diskIOReadMode;
4306 void SessionImpl::setDiskIOReadMode(const DiskIOReadMode mode)
4308 if (mode == m_diskIOReadMode)
4309 return;
4311 m_diskIOReadMode = mode;
4312 configureDeferred();
4315 DiskIOWriteMode SessionImpl::diskIOWriteMode() const
4317 return m_diskIOWriteMode;
4320 void SessionImpl::setDiskIOWriteMode(const DiskIOWriteMode mode)
4322 if (mode == m_diskIOWriteMode)
4323 return;
4325 m_diskIOWriteMode = mode;
4326 configureDeferred();
4329 bool SessionImpl::isCoalesceReadWriteEnabled() const
4331 return m_coalesceReadWriteEnabled;
4334 void SessionImpl::setCoalesceReadWriteEnabled(const bool enabled)
4336 if (enabled == m_coalesceReadWriteEnabled) return;
4338 m_coalesceReadWriteEnabled = enabled;
4339 configureDeferred();
4342 bool SessionImpl::isSuggestModeEnabled() const
4344 return m_isSuggestMode;
4347 bool SessionImpl::usePieceExtentAffinity() const
4349 return m_usePieceExtentAffinity;
4352 void SessionImpl::setPieceExtentAffinity(const bool enabled)
4354 if (enabled == m_usePieceExtentAffinity) return;
4356 m_usePieceExtentAffinity = enabled;
4357 configureDeferred();
4360 void SessionImpl::setSuggestMode(const bool mode)
4362 if (mode == m_isSuggestMode) return;
4364 m_isSuggestMode = mode;
4365 configureDeferred();
4368 int SessionImpl::sendBufferWatermark() const
4370 return m_sendBufferWatermark;
4373 void SessionImpl::setSendBufferWatermark(const int value)
4375 if (value == m_sendBufferWatermark) return;
4377 m_sendBufferWatermark = value;
4378 configureDeferred();
4381 int SessionImpl::sendBufferLowWatermark() const
4383 return m_sendBufferLowWatermark;
4386 void SessionImpl::setSendBufferLowWatermark(const int value)
4388 if (value == m_sendBufferLowWatermark) return;
4390 m_sendBufferLowWatermark = value;
4391 configureDeferred();
4394 int SessionImpl::sendBufferWatermarkFactor() const
4396 return m_sendBufferWatermarkFactor;
4399 void SessionImpl::setSendBufferWatermarkFactor(const int value)
4401 if (value == m_sendBufferWatermarkFactor) return;
4403 m_sendBufferWatermarkFactor = value;
4404 configureDeferred();
4407 int SessionImpl::connectionSpeed() const
4409 return m_connectionSpeed;
4412 void SessionImpl::setConnectionSpeed(const int value)
4414 if (value == m_connectionSpeed) return;
4416 m_connectionSpeed = value;
4417 configureDeferred();
4420 int SessionImpl::socketSendBufferSize() const
4422 return m_socketSendBufferSize;
4425 void SessionImpl::setSocketSendBufferSize(const int value)
4427 if (value == m_socketSendBufferSize)
4428 return;
4430 m_socketSendBufferSize = value;
4431 configureDeferred();
4434 int SessionImpl::socketReceiveBufferSize() const
4436 return m_socketReceiveBufferSize;
4439 void SessionImpl::setSocketReceiveBufferSize(const int value)
4441 if (value == m_socketReceiveBufferSize)
4442 return;
4444 m_socketReceiveBufferSize = value;
4445 configureDeferred();
4448 int SessionImpl::socketBacklogSize() const
4450 return m_socketBacklogSize;
4453 void SessionImpl::setSocketBacklogSize(const int value)
4455 if (value == m_socketBacklogSize) return;
4457 m_socketBacklogSize = value;
4458 configureDeferred();
4461 bool SessionImpl::isAnonymousModeEnabled() const
4463 return m_isAnonymousModeEnabled;
4466 void SessionImpl::setAnonymousModeEnabled(const bool enabled)
4468 if (enabled != m_isAnonymousModeEnabled)
4470 m_isAnonymousModeEnabled = enabled;
4471 configureDeferred();
4472 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
4473 , Log::INFO);
4477 bool SessionImpl::isQueueingSystemEnabled() const
4479 return m_isQueueingEnabled;
4482 void SessionImpl::setQueueingSystemEnabled(const bool enabled)
4484 if (enabled != m_isQueueingEnabled)
4486 m_isQueueingEnabled = enabled;
4487 configureDeferred();
4489 if (enabled)
4490 m_torrentsQueueChanged = true;
4491 else
4492 removeTorrentsQueue();
4496 int SessionImpl::maxActiveDownloads() const
4498 return m_maxActiveDownloads;
4501 void SessionImpl::setMaxActiveDownloads(int max)
4503 max = std::max(max, -1);
4504 if (max != m_maxActiveDownloads)
4506 m_maxActiveDownloads = max;
4507 configureDeferred();
4511 int SessionImpl::maxActiveUploads() const
4513 return m_maxActiveUploads;
4516 void SessionImpl::setMaxActiveUploads(int max)
4518 max = std::max(max, -1);
4519 if (max != m_maxActiveUploads)
4521 m_maxActiveUploads = max;
4522 configureDeferred();
4526 int SessionImpl::maxActiveTorrents() const
4528 return m_maxActiveTorrents;
4531 void SessionImpl::setMaxActiveTorrents(int max)
4533 max = std::max(max, -1);
4534 if (max != m_maxActiveTorrents)
4536 m_maxActiveTorrents = max;
4537 configureDeferred();
4541 bool SessionImpl::ignoreSlowTorrentsForQueueing() const
4543 return m_ignoreSlowTorrentsForQueueing;
4546 void SessionImpl::setIgnoreSlowTorrentsForQueueing(const bool ignore)
4548 if (ignore != m_ignoreSlowTorrentsForQueueing)
4550 m_ignoreSlowTorrentsForQueueing = ignore;
4551 configureDeferred();
4555 int SessionImpl::downloadRateForSlowTorrents() const
4557 return m_downloadRateForSlowTorrents;
4560 void SessionImpl::setDownloadRateForSlowTorrents(const int rateInKibiBytes)
4562 if (rateInKibiBytes == m_downloadRateForSlowTorrents)
4563 return;
4565 m_downloadRateForSlowTorrents = rateInKibiBytes;
4566 configureDeferred();
4569 int SessionImpl::uploadRateForSlowTorrents() const
4571 return m_uploadRateForSlowTorrents;
4574 void SessionImpl::setUploadRateForSlowTorrents(const int rateInKibiBytes)
4576 if (rateInKibiBytes == m_uploadRateForSlowTorrents)
4577 return;
4579 m_uploadRateForSlowTorrents = rateInKibiBytes;
4580 configureDeferred();
4583 int SessionImpl::slowTorrentsInactivityTimer() const
4585 return m_slowTorrentsInactivityTimer;
4588 void SessionImpl::setSlowTorrentsInactivityTimer(const int timeInSeconds)
4590 if (timeInSeconds == m_slowTorrentsInactivityTimer)
4591 return;
4593 m_slowTorrentsInactivityTimer = timeInSeconds;
4594 configureDeferred();
4597 int SessionImpl::outgoingPortsMin() const
4599 return m_outgoingPortsMin;
4602 void SessionImpl::setOutgoingPortsMin(const int min)
4604 if (min != m_outgoingPortsMin)
4606 m_outgoingPortsMin = min;
4607 configureDeferred();
4611 int SessionImpl::outgoingPortsMax() const
4613 return m_outgoingPortsMax;
4616 void SessionImpl::setOutgoingPortsMax(const int max)
4618 if (max != m_outgoingPortsMax)
4620 m_outgoingPortsMax = max;
4621 configureDeferred();
4625 int SessionImpl::UPnPLeaseDuration() const
4627 return m_UPnPLeaseDuration;
4630 void SessionImpl::setUPnPLeaseDuration(const int duration)
4632 if (duration != m_UPnPLeaseDuration)
4634 m_UPnPLeaseDuration = duration;
4635 configureDeferred();
4639 int SessionImpl::peerToS() const
4641 return m_peerToS;
4644 void SessionImpl::setPeerToS(const int value)
4646 if (value == m_peerToS)
4647 return;
4649 m_peerToS = value;
4650 configureDeferred();
4653 bool SessionImpl::ignoreLimitsOnLAN() const
4655 return m_ignoreLimitsOnLAN;
4658 void SessionImpl::setIgnoreLimitsOnLAN(const bool ignore)
4660 if (ignore != m_ignoreLimitsOnLAN)
4662 m_ignoreLimitsOnLAN = ignore;
4663 configureDeferred();
4667 bool SessionImpl::includeOverheadInLimits() const
4669 return m_includeOverheadInLimits;
4672 void SessionImpl::setIncludeOverheadInLimits(const bool include)
4674 if (include != m_includeOverheadInLimits)
4676 m_includeOverheadInLimits = include;
4677 configureDeferred();
4681 QString SessionImpl::announceIP() const
4683 return m_announceIP;
4686 void SessionImpl::setAnnounceIP(const QString &ip)
4688 if (ip != m_announceIP)
4690 m_announceIP = ip;
4691 configureDeferred();
4695 int SessionImpl::maxConcurrentHTTPAnnounces() const
4697 return m_maxConcurrentHTTPAnnounces;
4700 void SessionImpl::setMaxConcurrentHTTPAnnounces(const int value)
4702 if (value == m_maxConcurrentHTTPAnnounces)
4703 return;
4705 m_maxConcurrentHTTPAnnounces = value;
4706 configureDeferred();
4709 bool SessionImpl::isReannounceWhenAddressChangedEnabled() const
4711 return m_isReannounceWhenAddressChangedEnabled;
4714 void SessionImpl::setReannounceWhenAddressChangedEnabled(const bool enabled)
4716 if (enabled == m_isReannounceWhenAddressChangedEnabled)
4717 return;
4719 m_isReannounceWhenAddressChangedEnabled = enabled;
4722 void SessionImpl::reannounceToAllTrackers() const
4724 for (const TorrentImpl *torrent : asConst(m_torrents))
4728 torrent->nativeHandle().force_reannounce(0, -1, lt::torrent_handle::ignore_min_interval);
4730 catch (const std::exception &) {}
4734 int SessionImpl::stopTrackerTimeout() const
4736 return m_stopTrackerTimeout;
4739 void SessionImpl::setStopTrackerTimeout(const int value)
4741 if (value == m_stopTrackerTimeout)
4742 return;
4744 m_stopTrackerTimeout = value;
4745 configureDeferred();
4748 int SessionImpl::maxConnections() const
4750 return m_maxConnections;
4753 void SessionImpl::setMaxConnections(int max)
4755 max = (max > 0) ? max : -1;
4756 if (max != m_maxConnections)
4758 m_maxConnections = max;
4759 configureDeferred();
4763 int SessionImpl::maxUploads() const
4765 return m_maxUploads;
4768 void SessionImpl::setMaxUploads(int max)
4770 max = (max > 0) ? max : -1;
4771 if (max != m_maxUploads)
4773 m_maxUploads = max;
4774 configureDeferred();
4778 BTProtocol SessionImpl::btProtocol() const
4780 return m_btProtocol;
4783 void SessionImpl::setBTProtocol(const BTProtocol protocol)
4785 if ((protocol < BTProtocol::Both) || (BTProtocol::UTP < protocol))
4786 return;
4788 if (protocol == m_btProtocol) return;
4790 m_btProtocol = protocol;
4791 configureDeferred();
4794 bool SessionImpl::isUTPRateLimited() const
4796 return m_isUTPRateLimited;
4799 void SessionImpl::setUTPRateLimited(const bool limited)
4801 if (limited != m_isUTPRateLimited)
4803 m_isUTPRateLimited = limited;
4804 configureDeferred();
4808 MixedModeAlgorithm SessionImpl::utpMixedMode() const
4810 return m_utpMixedMode;
4813 void SessionImpl::setUtpMixedMode(const MixedModeAlgorithm mode)
4815 if (mode == m_utpMixedMode) return;
4817 m_utpMixedMode = mode;
4818 configureDeferred();
4821 bool SessionImpl::isIDNSupportEnabled() const
4823 return m_IDNSupportEnabled;
4826 void SessionImpl::setIDNSupportEnabled(const bool enabled)
4828 if (enabled == m_IDNSupportEnabled) return;
4830 m_IDNSupportEnabled = enabled;
4831 configureDeferred();
4834 bool SessionImpl::multiConnectionsPerIpEnabled() const
4836 return m_multiConnectionsPerIpEnabled;
4839 void SessionImpl::setMultiConnectionsPerIpEnabled(const bool enabled)
4841 if (enabled == m_multiConnectionsPerIpEnabled) return;
4843 m_multiConnectionsPerIpEnabled = enabled;
4844 configureDeferred();
4847 bool SessionImpl::validateHTTPSTrackerCertificate() const
4849 return m_validateHTTPSTrackerCertificate;
4852 void SessionImpl::setValidateHTTPSTrackerCertificate(const bool enabled)
4854 if (enabled == m_validateHTTPSTrackerCertificate) return;
4856 m_validateHTTPSTrackerCertificate = enabled;
4857 configureDeferred();
4860 bool SessionImpl::isSSRFMitigationEnabled() const
4862 return m_SSRFMitigationEnabled;
4865 void SessionImpl::setSSRFMitigationEnabled(const bool enabled)
4867 if (enabled == m_SSRFMitigationEnabled) return;
4869 m_SSRFMitigationEnabled = enabled;
4870 configureDeferred();
4873 bool SessionImpl::blockPeersOnPrivilegedPorts() const
4875 return m_blockPeersOnPrivilegedPorts;
4878 void SessionImpl::setBlockPeersOnPrivilegedPorts(const bool enabled)
4880 if (enabled == m_blockPeersOnPrivilegedPorts) return;
4882 m_blockPeersOnPrivilegedPorts = enabled;
4883 configureDeferred();
4886 bool SessionImpl::isTrackerFilteringEnabled() const
4888 return m_isTrackerFilteringEnabled;
4891 void SessionImpl::setTrackerFilteringEnabled(const bool enabled)
4893 if (enabled != m_isTrackerFilteringEnabled)
4895 m_isTrackerFilteringEnabled = enabled;
4896 configureDeferred();
4900 bool SessionImpl::isListening() const
4902 return m_nativeSessionExtension->isSessionListening();
4905 MaxRatioAction SessionImpl::maxRatioAction() const
4907 return static_cast<MaxRatioAction>(m_maxRatioAction.get());
4910 void SessionImpl::setMaxRatioAction(const MaxRatioAction act)
4912 m_maxRatioAction = static_cast<int>(act);
4915 bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const
4917 const bool isHybrid = infoHash.isHybrid();
4918 const auto id = TorrentID::fromInfoHash(infoHash);
4919 // alternative ID can be useful to find existing torrent
4920 // in case if hybrid torrent was added by v1 info hash
4921 const auto altID = (isHybrid ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
4923 if (m_loadingTorrents.contains(id) || (isHybrid && m_loadingTorrents.contains(altID)))
4924 return true;
4925 if (m_downloadedMetadata.contains(id) || (isHybrid && m_downloadedMetadata.contains(altID)))
4926 return true;
4927 return findTorrent(infoHash);
4930 void SessionImpl::updateSeedingLimitTimer()
4932 if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
4933 && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit()
4934 && (globalMaxInactiveSeedingMinutes() == Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT) && !hasPerTorrentInactiveSeedingTimeLimit())
4936 if (m_seedingLimitTimer->isActive())
4937 m_seedingLimitTimer->stop();
4939 else if (!m_seedingLimitTimer->isActive())
4941 m_seedingLimitTimer->start();
4945 void SessionImpl::handleTorrentShareLimitChanged(TorrentImpl *const)
4947 updateSeedingLimitTimer();
4950 void SessionImpl::handleTorrentNameChanged(TorrentImpl *const)
4954 void SessionImpl::handleTorrentSavePathChanged(TorrentImpl *const torrent)
4956 emit torrentSavePathChanged(torrent);
4959 void SessionImpl::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
4961 emit torrentCategoryChanged(torrent, oldCategory);
4964 void SessionImpl::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
4966 emit torrentTagAdded(torrent, tag);
4969 void SessionImpl::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
4971 emit torrentTagRemoved(torrent, tag);
4974 void SessionImpl::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
4976 emit torrentSavingModeChanged(torrent);
4979 void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
4981 for (const TrackerEntry &newTracker : newTrackers)
4982 LogMsg(tr("Added tracker to torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), newTracker.url));
4983 emit trackersAdded(torrent, newTrackers);
4984 if (torrent->trackers().size() == newTrackers.size())
4985 emit trackerlessStateChanged(torrent, false);
4986 emit trackersChanged(torrent);
4989 void SessionImpl::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers)
4991 for (const QString &deletedTracker : deletedTrackers)
4992 LogMsg(tr("Removed tracker from torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), deletedTracker));
4993 emit trackersRemoved(torrent, deletedTrackers);
4994 if (torrent->trackers().isEmpty())
4995 emit trackerlessStateChanged(torrent, true);
4996 emit trackersChanged(torrent);
4999 void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
5001 emit trackersChanged(torrent);
5004 void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
5006 for (const QUrl &newUrlSeed : newUrlSeeds)
5007 LogMsg(tr("Added URL seed to torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), newUrlSeed.toString()));
5010 void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
5012 for (const QUrl &urlSeed : urlSeeds)
5013 LogMsg(tr("Removed URL seed from torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), urlSeed.toString()));
5016 void SessionImpl::handleTorrentMetadataReceived(TorrentImpl *const torrent)
5018 if (!torrentExportDirectory().isEmpty())
5019 exportTorrentFile(torrent, torrentExportDirectory());
5021 emit torrentMetadataReceived(torrent);
5024 void SessionImpl::handleTorrentPaused(TorrentImpl *const torrent)
5026 LogMsg(tr("Torrent paused. Torrent: \"%1\"").arg(torrent->name()));
5027 emit torrentPaused(torrent);
5030 void SessionImpl::handleTorrentResumed(TorrentImpl *const torrent)
5032 LogMsg(tr("Torrent resumed. Torrent: \"%1\"").arg(torrent->name()));
5033 emit torrentResumed(torrent);
5036 void SessionImpl::handleTorrentChecked(TorrentImpl *const torrent)
5038 emit torrentFinishedChecking(torrent);
5041 void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
5043 LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
5044 emit torrentFinished(torrent);
5046 if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
5047 exportTorrentFile(torrent, exportPath);
5049 // Check whether it contains .torrent files
5050 for (const Path &torrentRelpath : asConst(torrent->filePaths()))
5052 if (torrentRelpath.hasExtension(u".torrent"_s))
5054 emit recursiveTorrentDownloadPossible(torrent);
5055 break;
5059 const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5061 return !(torrent->isFinished() || torrent->isPaused() || torrent->isErrored());
5063 if (!hasUnfinishedTorrents)
5064 emit allTorrentsFinished();
5067 void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
5069 --m_numResumeData;
5071 m_resumeDataStorage->store(torrent->id(), data);
5072 const auto iter = m_changedTorrentIDs.find(torrent->id());
5073 if (iter != m_changedTorrentIDs.end())
5075 m_resumeDataStorage->remove(iter.value());
5076 m_changedTorrentIDs.erase(iter);
5080 void SessionImpl::handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash)
5082 Q_ASSERT(torrent->infoHash().isHybrid());
5084 m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(torrent->infoHash().v1()), torrent);
5086 const auto prevID = TorrentID::fromInfoHash(prevInfoHash);
5087 const TorrentID currentID = torrent->id();
5088 if (currentID != prevID)
5090 m_torrents[torrent->id()] = m_torrents.take(prevID);
5091 m_changedTorrentIDs[torrent->id()] = prevID;
5095 void SessionImpl::handleTorrentStorageMovingStateChanged(TorrentImpl *torrent)
5097 emit torrentsUpdated({torrent});
5100 bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode, const MoveStorageContext context)
5102 Q_ASSERT(torrent);
5104 const lt::torrent_handle torrentHandle = torrent->nativeHandle();
5105 const Path currentLocation = torrent->actualStorageLocation();
5106 const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle);
5108 if (m_moveStorageQueue.size() > 1)
5110 auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
5111 , [&torrentHandle](const MoveStorageJob &job)
5113 return job.torrentHandle == torrentHandle;
5116 if (iter != m_moveStorageQueue.end())
5118 // remove existing inactive job
5119 torrent->handleMoveStorageJobFinished(currentLocation, iter->context, torrentHasActiveJob);
5120 LogMsg(tr("Torrent move canceled. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), iter->path.toString()));
5121 m_moveStorageQueue.erase(iter);
5125 if (torrentHasActiveJob)
5127 // if there is active job for this torrent prevent creating meaningless
5128 // job that will move torrent to the same location as current one
5129 if (m_moveStorageQueue.first().path == newPath)
5131 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: torrent is currently moving to the destination")
5132 .arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5133 return false;
5136 else
5138 if (currentLocation == newPath)
5140 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\" Destination: \"%3\". Reason: both paths point to the same location")
5141 .arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5142 return false;
5146 const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode, context};
5147 m_moveStorageQueue << moveStorageJob;
5148 LogMsg(tr("Enqueued torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5150 if (m_moveStorageQueue.size() == 1)
5151 moveTorrentStorage(moveStorageJob);
5153 return true;
5156 void SessionImpl::moveTorrentStorage(const MoveStorageJob &job) const
5158 #ifdef QBT_USES_LIBTORRENT2
5159 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
5160 #else
5161 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
5162 #endif
5163 const TorrentImpl *torrent = m_torrents.value(id);
5164 const QString torrentName = (torrent ? torrent->name() : id.toString());
5165 LogMsg(tr("Start moving torrent. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, job.path.toString()));
5167 job.torrentHandle.move_storage(job.path.toString().toStdString(), toNative(job.mode));
5170 void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
5172 const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
5173 if (!m_moveStorageQueue.isEmpty())
5174 moveTorrentStorage(m_moveStorageQueue.first());
5176 const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
5177 , [&finishedJob](const MoveStorageJob &job)
5179 return job.torrentHandle == finishedJob.torrentHandle;
5182 const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
5184 TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
5185 if (torrent)
5187 torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
5189 else if (!torrentHasOutstandingJob)
5191 // Last job is completed for torrent that being removing, so actually remove it
5192 const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
5193 const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
5194 if (removingTorrentData.deleteOption == DeleteTorrent)
5195 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
5199 void SessionImpl::storeCategories() const
5201 QJsonObject jsonObj;
5202 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
5204 const QString &categoryName = it.key();
5205 const CategoryOptions &categoryOptions = it.value();
5206 jsonObj[categoryName] = categoryOptions.toJSON();
5209 const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
5210 const QByteArray data = QJsonDocument(jsonObj).toJson();
5211 const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
5212 if (!result)
5214 LogMsg(tr("Failed to save Categories configuration. File: \"%1\". Error: \"%2\"")
5215 .arg(path.toString(), result.error()), Log::WARNING);
5219 void SessionImpl::upgradeCategories()
5221 const auto legacyCategories = SettingValue<QVariantMap>(u"BitTorrent/Session/Categories"_s).get();
5222 for (auto it = legacyCategories.cbegin(); it != legacyCategories.cend(); ++it)
5224 const QString &categoryName = it.key();
5225 CategoryOptions categoryOptions;
5226 categoryOptions.savePath = Path(it.value().toString());
5227 m_categories[categoryName] = categoryOptions;
5230 storeCategories();
5233 void SessionImpl::loadCategories()
5235 m_categories.clear();
5237 const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
5238 if (!path.exists())
5240 // TODO: Remove the following upgrade code in v4.5
5241 // == BEGIN UPGRADE CODE ==
5242 upgradeCategories();
5243 m_needUpgradeDownloadPath = true;
5244 // == END UPGRADE CODE ==
5246 // return;
5249 const int fileMaxSize = 1024 * 1024;
5250 const auto readResult = Utils::IO::readFile(path, fileMaxSize);
5251 if (!readResult)
5253 LogMsg(tr("Failed to load Categories. %1").arg(readResult.error().message), Log::WARNING);
5254 return;
5257 QJsonParseError jsonError;
5258 const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
5259 if (jsonError.error != QJsonParseError::NoError)
5261 LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
5262 .arg(path.toString(), jsonError.errorString()), Log::WARNING);
5263 return;
5266 if (!jsonDoc.isObject())
5268 LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Error: \"Invalid data format\"")
5269 .arg(path.toString()), Log::WARNING);
5270 return;
5273 const QJsonObject jsonObj = jsonDoc.object();
5274 for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
5276 const QString &categoryName = it.key();
5277 const auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
5278 m_categories[categoryName] = categoryOptions;
5282 bool SessionImpl::hasPerTorrentRatioLimit() const
5284 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5286 return (torrent->ratioLimit() >= 0);
5290 bool SessionImpl::hasPerTorrentSeedingTimeLimit() const
5292 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5294 return (torrent->seedingTimeLimit() >= 0);
5298 bool SessionImpl::hasPerTorrentInactiveSeedingTimeLimit() const
5300 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5302 return (torrent->inactiveSeedingTimeLimit() >= 0);
5306 void SessionImpl::configureDeferred()
5308 if (m_deferredConfigureScheduled)
5309 return;
5311 m_deferredConfigureScheduled = true;
5312 QMetaObject::invokeMethod(this, qOverload<>(&SessionImpl::configure), Qt::QueuedConnection);
5315 // Enable IP Filtering
5316 // this method creates ban list from scratch combining user ban list and 3rd party ban list file
5317 void SessionImpl::enableIPFilter()
5319 qDebug("Enabling IPFilter");
5320 // 1. Parse the IP filter
5321 // 2. In the slot add the manually banned IPs to the provided lt::ip_filter
5322 // 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
5323 // set between clearing the old one and setting the new one.
5324 if (!m_filterParser)
5326 m_filterParser = new FilterParserThread(this);
5327 connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &SessionImpl::handleIPFilterParsed);
5328 connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &SessionImpl::handleIPFilterError);
5330 m_filterParser->processFilterFile(IPFilterFile());
5333 // Disable IP Filtering
5334 void SessionImpl::disableIPFilter()
5336 qDebug("Disabling IPFilter");
5337 if (m_filterParser)
5339 disconnect(m_filterParser.data(), nullptr, this, nullptr);
5340 delete m_filterParser;
5343 // Add the banned IPs after the IPFilter disabling
5344 // which creates an empty filter and overrides all previously
5345 // applied bans.
5346 lt::ip_filter filter;
5347 processBannedIPs(filter);
5348 m_nativeSession->set_ip_filter(filter);
5351 void SessionImpl::recursiveTorrentDownload(const TorrentID &id)
5353 const TorrentImpl *torrent = m_torrents.value(id);
5354 if (!torrent)
5355 return;
5357 for (const Path &torrentRelpath : asConst(torrent->filePaths()))
5359 if (torrentRelpath.hasExtension(u".torrent"_s))
5361 const Path torrentFullpath = torrent->savePath() / torrentRelpath;
5363 LogMsg(tr("Recursive download .torrent file within torrent. Source torrent: \"%1\". File: \"%2\"")
5364 .arg(torrent->name(), torrentFullpath.toString()));
5366 AddTorrentParams params;
5367 // Passing the save path along to the sub torrent file
5368 params.savePath = torrent->savePath();
5369 const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(torrentFullpath);
5370 if (loadResult)
5372 addTorrent(loadResult.value(), params);
5374 else
5376 LogMsg(tr("Failed to load .torrent file within torrent. Source torrent: \"%1\". File: \"%2\". Error: \"%3\"")
5377 .arg(torrent->name(), torrentFullpath.toString(), loadResult.error()), Log::WARNING);
5383 const SessionStatus &SessionImpl::status() const
5385 return m_status;
5388 const CacheStatus &SessionImpl::cacheStatus() const
5390 return m_cacheStatus;
5393 void SessionImpl::enqueueRefresh()
5395 Q_ASSERT(!m_refreshEnqueued);
5397 QTimer::singleShot(refreshInterval(), Qt::CoarseTimer, this, [this]
5399 m_nativeSession->post_torrent_updates();
5400 m_nativeSession->post_session_stats();
5402 if (m_torrentsQueueChanged)
5404 m_torrentsQueueChanged = false;
5405 m_needSaveTorrentsQueue = true;
5409 m_refreshEnqueued = true;
5412 void SessionImpl::handleIPFilterParsed(const int ruleCount)
5414 if (m_filterParser)
5416 lt::ip_filter filter = m_filterParser->IPfilter();
5417 processBannedIPs(filter);
5418 m_nativeSession->set_ip_filter(filter);
5420 LogMsg(tr("Successfully parsed the IP filter file. Number of rules applied: %1").arg(ruleCount));
5421 emit IPFilterParsed(false, ruleCount);
5424 void SessionImpl::handleIPFilterError()
5426 lt::ip_filter filter;
5427 processBannedIPs(filter);
5428 m_nativeSession->set_ip_filter(filter);
5430 LogMsg(tr("Failed to parse the IP filter file"), Log::WARNING);
5431 emit IPFilterParsed(true, 0);
5434 std::vector<lt::alert *> SessionImpl::getPendingAlerts(const lt::time_duration time) const
5436 if (time > lt::time_duration::zero())
5437 m_nativeSession->wait_for_alert(time);
5439 std::vector<lt::alert *> alerts;
5440 m_nativeSession->pop_alerts(&alerts);
5441 return alerts;
5444 TorrentContentLayout SessionImpl::torrentContentLayout() const
5446 return m_torrentContentLayout;
5449 void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value)
5451 m_torrentContentLayout = value;
5454 // Read alerts sent by the BitTorrent session
5455 void SessionImpl::readAlerts()
5457 const std::vector<lt::alert *> alerts = getPendingAlerts();
5458 handleAddTorrentAlerts(alerts);
5459 for (const lt::alert *a : alerts)
5460 handleAlert(a);
5462 processTrackerStatuses();
5465 void SessionImpl::handleAddTorrentAlerts(const std::vector<lt::alert *> &alerts)
5467 QVector<Torrent *> loadedTorrents;
5468 if (!isRestored())
5469 loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
5471 qsizetype alertsCount = 0;
5472 for (const lt::alert *a : alerts)
5474 if (a->type() != lt::add_torrent_alert::alert_type)
5475 continue;
5477 ++alertsCount;
5479 const auto *alert = static_cast<const lt::add_torrent_alert *>(a);
5480 if (alert->error)
5482 const QString msg = QString::fromStdString(alert->message());
5483 LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(msg), Log::WARNING);
5484 emit loadTorrentFailed(msg);
5486 const lt::add_torrent_params &params = alert->params;
5487 const bool hasMetadata = (params.ti && params.ti->is_valid());
5489 #ifdef QBT_USES_LIBTORRENT2
5490 const InfoHash infoHash {(hasMetadata ? params.ti->info_hashes() : params.info_hashes)};
5491 if (infoHash.isHybrid())
5492 m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
5493 #else
5494 const InfoHash infoHash {(hasMetadata ? params.ti->info_hash() : params.info_hash)};
5495 #endif
5496 if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
5497 ; loadingTorrentsIter != m_loadingTorrents.end())
5499 m_loadingTorrents.erase(loadingTorrentsIter);
5501 else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
5502 ; downloadedMetadataIter != m_downloadedMetadata.end())
5504 m_downloadedMetadata.erase(downloadedMetadataIter);
5505 if (infoHash.isHybrid())
5507 // index hybrid magnet links by both v1 and v2 info hashes
5508 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5509 m_downloadedMetadata.remove(altID);
5513 continue;
5516 #ifdef QBT_USES_LIBTORRENT2
5517 const InfoHash infoHash {alert->handle.info_hashes()};
5518 #else
5519 const InfoHash infoHash {alert->handle.info_hash()};
5520 #endif
5521 const auto torrentID = TorrentID::fromInfoHash(infoHash);
5523 if (const auto loadingTorrentsIter = m_loadingTorrents.find(torrentID)
5524 ; loadingTorrentsIter != m_loadingTorrents.end())
5526 const LoadTorrentParams params = loadingTorrentsIter.value();
5527 m_loadingTorrents.erase(loadingTorrentsIter);
5529 Torrent *torrent = createTorrent(alert->handle, params);
5530 loadedTorrents.append(torrent);
5532 else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(torrentID)
5533 ; downloadedMetadataIter != m_downloadedMetadata.end())
5535 downloadedMetadataIter.value() = alert->handle;
5536 if (infoHash.isHybrid())
5538 // index hybrid magnet links by both v1 and v2 info hashes
5539 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5540 m_downloadedMetadata[altID] = alert->handle;
5545 if (alertsCount > 0)
5547 emit addTorrentAlertsReceived(alertsCount);
5549 if (!loadedTorrents.isEmpty())
5551 if (isRestored())
5552 m_torrentsQueueChanged = true;
5553 emit torrentsLoaded(loadedTorrents);
5558 void SessionImpl::handleAlert(const lt::alert *a)
5562 switch (a->type())
5564 #ifdef QBT_USES_LIBTORRENT2
5565 case lt::file_prio_alert::alert_type:
5566 #endif
5567 case lt::file_renamed_alert::alert_type:
5568 case lt::file_rename_failed_alert::alert_type:
5569 case lt::file_completed_alert::alert_type:
5570 case lt::torrent_finished_alert::alert_type:
5571 case lt::save_resume_data_alert::alert_type:
5572 case lt::save_resume_data_failed_alert::alert_type:
5573 case lt::torrent_paused_alert::alert_type:
5574 case lt::torrent_resumed_alert::alert_type:
5575 case lt::fastresume_rejected_alert::alert_type:
5576 case lt::torrent_checked_alert::alert_type:
5577 case lt::metadata_received_alert::alert_type:
5578 case lt::performance_alert::alert_type:
5579 dispatchTorrentAlert(static_cast<const lt::torrent_alert *>(a));
5580 break;
5581 case lt::state_update_alert::alert_type:
5582 handleStateUpdateAlert(static_cast<const lt::state_update_alert *>(a));
5583 break;
5584 case lt::session_error_alert::alert_type:
5585 handleSessionErrorAlert(static_cast<const lt::session_error_alert *>(a));
5586 break;
5587 case lt::session_stats_alert::alert_type:
5588 handleSessionStatsAlert(static_cast<const lt::session_stats_alert *>(a));
5589 break;
5590 case lt::tracker_announce_alert::alert_type:
5591 case lt::tracker_error_alert::alert_type:
5592 case lt::tracker_reply_alert::alert_type:
5593 case lt::tracker_warning_alert::alert_type:
5594 handleTrackerAlert(static_cast<const lt::tracker_alert *>(a));
5595 break;
5596 case lt::file_error_alert::alert_type:
5597 handleFileErrorAlert(static_cast<const lt::file_error_alert *>(a));
5598 break;
5599 case lt::add_torrent_alert::alert_type:
5600 // handled separately
5601 break;
5602 case lt::torrent_removed_alert::alert_type:
5603 handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert *>(a));
5604 break;
5605 case lt::torrent_deleted_alert::alert_type:
5606 handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert *>(a));
5607 break;
5608 case lt::torrent_delete_failed_alert::alert_type:
5609 handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert *>(a));
5610 break;
5611 case lt::portmap_error_alert::alert_type:
5612 handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert *>(a));
5613 break;
5614 case lt::portmap_alert::alert_type:
5615 handlePortmapAlert(static_cast<const lt::portmap_alert *>(a));
5616 break;
5617 case lt::peer_blocked_alert::alert_type:
5618 handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert *>(a));
5619 break;
5620 case lt::peer_ban_alert::alert_type:
5621 handlePeerBanAlert(static_cast<const lt::peer_ban_alert *>(a));
5622 break;
5623 case lt::url_seed_alert::alert_type:
5624 handleUrlSeedAlert(static_cast<const lt::url_seed_alert *>(a));
5625 break;
5626 case lt::listen_succeeded_alert::alert_type:
5627 handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert *>(a));
5628 break;
5629 case lt::listen_failed_alert::alert_type:
5630 handleListenFailedAlert(static_cast<const lt::listen_failed_alert *>(a));
5631 break;
5632 case lt::external_ip_alert::alert_type:
5633 handleExternalIPAlert(static_cast<const lt::external_ip_alert *>(a));
5634 break;
5635 case lt::alerts_dropped_alert::alert_type:
5636 handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(a));
5637 break;
5638 case lt::storage_moved_alert::alert_type:
5639 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert *>(a));
5640 break;
5641 case lt::storage_moved_failed_alert::alert_type:
5642 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert *>(a));
5643 break;
5644 case lt::socks5_alert::alert_type:
5645 handleSocks5Alert(static_cast<const lt::socks5_alert *>(a));
5646 break;
5647 case lt::i2p_alert::alert_type:
5648 handleI2PAlert(static_cast<const lt::i2p_alert *>(a));
5649 break;
5650 #ifdef QBT_USES_LIBTORRENT2
5651 case lt::torrent_conflict_alert::alert_type:
5652 handleTorrentConflictAlert(static_cast<const lt::torrent_conflict_alert *>(a));
5653 break;
5654 #endif
5657 catch (const std::exception &exc)
5659 qWarning() << "Caught exception in " << Q_FUNC_INFO << ": " << QString::fromStdString(exc.what());
5663 void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert *a)
5665 const TorrentID torrentID {a->handle.info_hash()};
5666 TorrentImpl *torrent = m_torrents.value(torrentID);
5667 #ifdef QBT_USES_LIBTORRENT2
5668 if (!torrent && (a->type() == lt::metadata_received_alert::alert_type))
5670 const InfoHash infoHash {a->handle.info_hashes()};
5671 if (infoHash.isHybrid())
5672 torrent = m_torrents.value(TorrentID::fromSHA1Hash(infoHash.v1()));
5674 #endif
5676 if (torrent)
5678 torrent->handleAlert(a);
5679 return;
5682 switch (a->type())
5684 case lt::metadata_received_alert::alert_type:
5685 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert *>(a));
5686 break;
5690 TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
5692 auto *const torrent = new TorrentImpl(this, m_nativeSession, nativeHandle, params);
5693 m_torrents.insert(torrent->id(), torrent);
5694 if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
5695 m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent);
5697 if (isRestored())
5699 if (params.addToQueueTop)
5700 nativeHandle.queue_position_top();
5702 torrent->saveResumeData(lt::torrent_handle::save_info_dict);
5704 // The following is useless for newly added magnet
5705 if (torrent->hasMetadata())
5707 if (!torrentExportDirectory().isEmpty())
5708 exportTorrentFile(torrent, torrentExportDirectory());
5712 if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
5713 && !m_seedingLimitTimer->isActive())
5715 m_seedingLimitTimer->start();
5718 if (!isRestored())
5720 LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name()));
5722 else
5724 LogMsg(tr("Added new torrent. Torrent: \"%1\"").arg(torrent->name()));
5725 emit torrentAdded(torrent);
5728 // Torrent could have error just after adding to libtorrent
5729 if (torrent->hasError())
5730 LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: \"%2\"").arg(torrent->name(), torrent->error()), Log::WARNING);
5732 return torrent;
5735 void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
5737 #ifdef QBT_USES_LIBTORRENT2
5738 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5739 #else
5740 const auto id = TorrentID::fromInfoHash(p->info_hash);
5741 #endif
5743 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5744 if (removingTorrentDataIter != m_removingTorrents.end())
5746 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
5748 LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5749 m_removingTorrents.erase(removingTorrentDataIter);
5754 void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
5756 #ifdef QBT_USES_LIBTORRENT2
5757 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5758 #else
5759 const auto id = TorrentID::fromInfoHash(p->info_hash);
5760 #endif
5762 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5763 if (removingTorrentDataIter == m_removingTorrents.end())
5764 return;
5766 // torrent_deleted_alert can also be posted due to deletion of partfile. Ignore it in such a case.
5767 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
5768 return;
5770 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
5771 LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5772 m_removingTorrents.erase(removingTorrentDataIter);
5775 void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
5777 #ifdef QBT_USES_LIBTORRENT2
5778 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5779 #else
5780 const auto id = TorrentID::fromInfoHash(p->info_hash);
5781 #endif
5783 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5784 if (removingTorrentDataIter == m_removingTorrents.end())
5785 return;
5787 if (p->error)
5789 // libtorrent won't delete the directory if it contains files not listed in the torrent,
5790 // so we remove the directory ourselves
5791 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
5793 LogMsg(tr("Removed torrent but failed to delete its content and/or partfile. Torrent: \"%1\". Error: \"%2\"")
5794 .arg(removingTorrentDataIter->name, QString::fromLocal8Bit(p->error.message().c_str()))
5795 , Log::WARNING);
5797 else // torrent without metadata, hence no files on disk
5799 LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5802 m_removingTorrents.erase(removingTorrentDataIter);
5805 void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
5807 const TorrentID torrentID {p->handle.info_hash()};
5809 bool found = false;
5810 if (const auto iter = m_downloadedMetadata.find(torrentID); iter != m_downloadedMetadata.end())
5812 found = true;
5813 m_downloadedMetadata.erase(iter);
5815 #ifdef QBT_USES_LIBTORRENT2
5816 const InfoHash infoHash {p->handle.info_hashes()};
5817 if (infoHash.isHybrid())
5819 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5820 if (const auto iter = m_downloadedMetadata.find(altID); iter != m_downloadedMetadata.end())
5822 found = true;
5823 m_downloadedMetadata.erase(iter);
5826 #endif
5827 if (found)
5829 const TorrentInfo metadata {*p->handle.torrent_file()};
5830 m_nativeSession->remove_torrent(p->handle, lt::session::delete_files);
5832 emit metadataDownloaded(metadata);
5836 void SessionImpl::handleFileErrorAlert(const lt::file_error_alert *p)
5838 TorrentImpl *const torrent = m_torrents.value(p->handle.info_hash());
5839 if (!torrent)
5840 return;
5842 torrent->handleAlert(p);
5844 const TorrentID id = torrent->id();
5845 if (!m_recentErroredTorrents.contains(id))
5847 m_recentErroredTorrents.insert(id);
5849 const QString msg = QString::fromStdString(p->message());
5850 LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: \"%3\"")
5851 .arg(torrent->name(), QString::fromUtf8(p->filename()), msg)
5852 , Log::WARNING);
5853 emit fullDiskError(torrent, msg);
5856 m_recentErroredTorrentsTimer->start();
5859 void SessionImpl::handlePortmapWarningAlert(const lt::portmap_error_alert *p)
5861 LogMsg(tr("UPnP/NAT-PMP port mapping failed. Message: \"%1\"").arg(QString::fromStdString(p->message())), Log::WARNING);
5864 void SessionImpl::handlePortmapAlert(const lt::portmap_alert *p)
5866 qDebug("UPnP Success, msg: %s", p->message().c_str());
5867 LogMsg(tr("UPnP/NAT-PMP port mapping succeeded. Message: \"%1\"").arg(QString::fromStdString(p->message())), Log::INFO);
5870 void SessionImpl::handlePeerBlockedAlert(const lt::peer_blocked_alert *p)
5872 QString reason;
5873 switch (p->reason)
5875 case lt::peer_blocked_alert::ip_filter:
5876 reason = tr("IP filter", "this peer was blocked. Reason: IP filter.");
5877 break;
5878 case lt::peer_blocked_alert::port_filter:
5879 reason = tr("filtered port (%1)", "this peer was blocked. Reason: filtered port (8899).").arg(QString::number(p->endpoint.port()));
5880 break;
5881 case lt::peer_blocked_alert::i2p_mixed:
5882 reason = tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg(u"I2P"_s); // don't translate I2P
5883 break;
5884 case lt::peer_blocked_alert::privileged_ports:
5885 reason = tr("privileged port (%1)", "this peer was blocked. Reason: privileged port (80).").arg(QString::number(p->endpoint.port()));
5886 break;
5887 case lt::peer_blocked_alert::utp_disabled:
5888 reason = tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(C_UTP); // don't translate μTP
5889 break;
5890 case lt::peer_blocked_alert::tcp_disabled:
5891 reason = tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg(u"TCP"_s); // don't translate TCP
5892 break;
5895 const QString ip {toString(p->endpoint.address())};
5896 if (!ip.isEmpty())
5897 Logger::instance()->addPeer(ip, true, reason);
5900 void SessionImpl::handlePeerBanAlert(const lt::peer_ban_alert *p)
5902 const QString ip {toString(p->endpoint.address())};
5903 if (!ip.isEmpty())
5904 Logger::instance()->addPeer(ip, false);
5907 void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert *p)
5909 const TorrentImpl *torrent = m_torrents.value(p->handle.info_hash());
5910 if (!torrent)
5911 return;
5913 if (p->error)
5915 LogMsg(tr("URL seed DNS lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
5916 .arg(torrent->name(), QString::fromUtf8(p->server_url()), QString::fromStdString(p->message()))
5917 , Log::WARNING);
5919 else
5921 LogMsg(tr("Received error message from URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
5922 .arg(torrent->name(), QString::fromUtf8(p->server_url()), QString::fromUtf8(p->error_message()))
5923 , Log::WARNING);
5927 void SessionImpl::handleListenSucceededAlert(const lt::listen_succeeded_alert *p)
5929 const QString proto {toString(p->socket_type)};
5930 LogMsg(tr("Successfully listening on IP. IP: \"%1\". Port: \"%2/%3\"")
5931 .arg(toString(p->address), proto, QString::number(p->port)), Log::INFO);
5934 void SessionImpl::handleListenFailedAlert(const lt::listen_failed_alert *p)
5936 const QString proto {toString(p->socket_type)};
5937 LogMsg(tr("Failed to listen on IP. IP: \"%1\". Port: \"%2/%3\". Reason: \"%4\"")
5938 .arg(toString(p->address), proto, QString::number(p->port)
5939 , QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
5942 void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *p)
5944 const QString externalIP {toString(p->external_address)};
5945 LogMsg(tr("Detected external IP. IP: \"%1\"")
5946 .arg(externalIP), Log::INFO);
5948 if (m_lastExternalIP != externalIP)
5950 if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty())
5951 reannounceToAllTrackers();
5952 m_lastExternalIP = externalIP;
5956 void SessionImpl::handleSessionErrorAlert(const lt::session_error_alert *p) const
5958 LogMsg(tr("BitTorrent session encountered a serious error. Reason: \"%1\"")
5959 .arg(QString::fromStdString(p->message())), Log::CRITICAL);
5962 void SessionImpl::handleSessionStatsAlert(const lt::session_stats_alert *p)
5964 if (m_refreshEnqueued)
5965 m_refreshEnqueued = false;
5966 else
5967 enqueueRefresh();
5969 const int64_t interval = lt::total_microseconds(p->timestamp() - m_statsLastTimestamp);
5970 if (interval <= 0)
5971 return;
5973 m_statsLastTimestamp = p->timestamp();
5975 const auto stats = p->counters();
5977 m_status.hasIncomingConnections = static_cast<bool>(stats[m_metricIndices.net.hasIncomingConnections]);
5979 const int64_t ipOverheadDownload = stats[m_metricIndices.net.recvIPOverheadBytes];
5980 const int64_t ipOverheadUpload = stats[m_metricIndices.net.sentIPOverheadBytes];
5981 const int64_t totalDownload = stats[m_metricIndices.net.recvBytes] + ipOverheadDownload;
5982 const int64_t totalUpload = stats[m_metricIndices.net.sentBytes] + ipOverheadUpload;
5983 const int64_t totalPayloadDownload = stats[m_metricIndices.net.recvPayloadBytes];
5984 const int64_t totalPayloadUpload = stats[m_metricIndices.net.sentPayloadBytes];
5985 const int64_t trackerDownload = stats[m_metricIndices.net.recvTrackerBytes];
5986 const int64_t trackerUpload = stats[m_metricIndices.net.sentTrackerBytes];
5987 const int64_t dhtDownload = stats[m_metricIndices.dht.dhtBytesIn];
5988 const int64_t dhtUpload = stats[m_metricIndices.dht.dhtBytesOut];
5990 const auto calcRate = [interval](const qint64 previous, const qint64 current) -> qint64
5992 Q_ASSERT(current >= previous);
5993 Q_ASSERT(interval >= 0);
5994 return (((current - previous) * lt::microseconds(1s).count()) / interval);
5997 m_status.payloadDownloadRate = calcRate(m_status.totalPayloadDownload, totalPayloadDownload);
5998 m_status.payloadUploadRate = calcRate(m_status.totalPayloadUpload, totalPayloadUpload);
5999 m_status.downloadRate = calcRate(m_status.totalDownload, totalDownload);
6000 m_status.uploadRate = calcRate(m_status.totalUpload, totalUpload);
6001 m_status.ipOverheadDownloadRate = calcRate(m_status.ipOverheadDownload, ipOverheadDownload);
6002 m_status.ipOverheadUploadRate = calcRate(m_status.ipOverheadUpload, ipOverheadUpload);
6003 m_status.dhtDownloadRate = calcRate(m_status.dhtDownload, dhtDownload);
6004 m_status.dhtUploadRate = calcRate(m_status.dhtUpload, dhtUpload);
6005 m_status.trackerDownloadRate = calcRate(m_status.trackerDownload, trackerDownload);
6006 m_status.trackerUploadRate = calcRate(m_status.trackerUpload, trackerUpload);
6008 m_status.totalPayloadDownload = totalPayloadDownload;
6009 m_status.totalPayloadUpload = totalPayloadUpload;
6010 m_status.ipOverheadDownload = ipOverheadDownload;
6011 m_status.ipOverheadUpload = ipOverheadUpload;
6012 m_status.trackerDownload = trackerDownload;
6013 m_status.trackerUpload = trackerUpload;
6014 m_status.dhtDownload = dhtDownload;
6015 m_status.dhtUpload = dhtUpload;
6016 m_status.totalWasted = stats[m_metricIndices.net.recvRedundantBytes]
6017 + stats[m_metricIndices.net.recvFailedBytes];
6018 m_status.dhtNodes = stats[m_metricIndices.dht.dhtNodes];
6019 m_status.diskReadQueue = stats[m_metricIndices.peer.numPeersUpDisk];
6020 m_status.diskWriteQueue = stats[m_metricIndices.peer.numPeersDownDisk];
6021 m_status.peersCount = stats[m_metricIndices.peer.numPeersConnected];
6023 if (totalDownload > m_status.totalDownload)
6025 m_status.totalDownload = totalDownload;
6026 m_isStatisticsDirty = true;
6029 if (totalUpload > m_status.totalUpload)
6031 m_status.totalUpload = totalUpload;
6032 m_isStatisticsDirty = true;
6035 m_status.allTimeDownload = m_previouslyDownloaded + m_status.totalDownload;
6036 m_status.allTimeUpload = m_previouslyUploaded + m_status.totalUpload;
6038 if (m_statisticsLastUpdateTimer.hasExpired(STATISTICS_SAVE_INTERVAL))
6039 saveStatistics();
6041 m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
6042 m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
6044 #ifndef QBT_USES_LIBTORRENT2
6045 const int64_t numBlocksRead = stats[m_metricIndices.disk.numBlocksRead];
6046 const int64_t numBlocksCacheHits = stats[m_metricIndices.disk.numBlocksCacheHits];
6047 m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max<int64_t>((numBlocksCacheHits + numBlocksRead), 1);
6048 #endif
6050 const int64_t totalJobs = stats[m_metricIndices.disk.writeJobs] + stats[m_metricIndices.disk.readJobs]
6051 + stats[m_metricIndices.disk.hashJobs];
6052 m_cacheStatus.averageJobTime = (totalJobs > 0)
6053 ? (stats[m_metricIndices.disk.diskJobTime] / totalJobs) : 0;
6055 emit statsUpdated();
6058 void SessionImpl::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
6060 LogMsg(tr("Error: Internal alert queue is full and alerts are dropped, you might see degraded performance. Dropped alert type: \"%1\". Message: \"%2\"")
6061 .arg(QString::fromStdString(p->dropped_alerts.to_string()), QString::fromStdString(p->message())), Log::CRITICAL);
6064 void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *p)
6066 Q_ASSERT(!m_moveStorageQueue.isEmpty());
6068 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
6069 Q_ASSERT(currentJob.torrentHandle == p->handle);
6071 const Path newPath {QString::fromUtf8(p->storage_path())};
6072 Q_ASSERT(newPath == currentJob.path);
6074 #ifdef QBT_USES_LIBTORRENT2
6075 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
6076 #else
6077 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
6078 #endif
6080 TorrentImpl *torrent = m_torrents.value(id);
6081 const QString torrentName = (torrent ? torrent->name() : id.toString());
6082 LogMsg(tr("Moved torrent successfully. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, newPath.toString()));
6084 handleMoveTorrentStorageJobFinished(newPath);
6087 void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
6089 Q_ASSERT(!m_moveStorageQueue.isEmpty());
6091 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
6092 Q_ASSERT(currentJob.torrentHandle == p->handle);
6094 #ifdef QBT_USES_LIBTORRENT2
6095 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
6096 #else
6097 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
6098 #endif
6100 TorrentImpl *torrent = m_torrents.value(id);
6101 const QString torrentName = (torrent ? torrent->name() : id.toString());
6102 const Path currentLocation = (torrent ? torrent->actualStorageLocation()
6103 : Path(p->handle.status(lt::torrent_handle::query_save_path).save_path));
6104 const QString errorMessage = QString::fromStdString(p->message());
6105 LogMsg(tr("Failed to move torrent. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: \"%4\"")
6106 .arg(torrentName, currentLocation.toString(), currentJob.path.toString(), errorMessage), Log::WARNING);
6108 handleMoveTorrentStorageJobFinished(currentLocation);
6111 void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *p)
6113 QVector<Torrent *> updatedTorrents;
6114 updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(p->status.size()));
6116 for (const lt::torrent_status &status : p->status)
6118 #ifdef QBT_USES_LIBTORRENT2
6119 const auto id = TorrentID::fromInfoHash(status.info_hashes);
6120 #else
6121 const auto id = TorrentID::fromInfoHash(status.info_hash);
6122 #endif
6123 TorrentImpl *const torrent = m_torrents.value(id);
6124 if (!torrent)
6125 continue;
6127 torrent->handleStateUpdate(status);
6128 updatedTorrents.push_back(torrent);
6131 if (!updatedTorrents.isEmpty())
6132 emit torrentsUpdated(updatedTorrents);
6134 if (m_needSaveTorrentsQueue)
6135 saveTorrentsQueue();
6137 if (m_refreshEnqueued)
6138 m_refreshEnqueued = false;
6139 else
6140 enqueueRefresh();
6143 void SessionImpl::handleSocks5Alert(const lt::socks5_alert *p) const
6145 if (p->error)
6147 const auto addr = p->ip.address();
6148 const QString endpoint = (addr.is_v6() ? u"[%1]:%2"_s : u"%1:%2"_s)
6149 .arg(QString::fromStdString(addr.to_string()), QString::number(p->ip.port()));
6150 LogMsg(tr("SOCKS5 proxy error. Address: %1. Message: \"%2\".")
6151 .arg(endpoint, QString::fromLocal8Bit(p->error.message().c_str()))
6152 , Log::WARNING);
6156 void SessionImpl::handleI2PAlert(const lt::i2p_alert *p) const
6158 if (p->error)
6160 LogMsg(tr("I2P error. Message: \"%1\".")
6161 .arg(QString::fromStdString(p->message())), Log::WARNING);
6165 void SessionImpl::handleTrackerAlert(const lt::tracker_alert *a)
6167 TorrentImpl *torrent = m_torrents.value(a->handle.info_hash());
6168 if (!torrent)
6169 return;
6171 QMap<TrackerEntry::Endpoint, int> &updateInfo = m_updatedTrackerEntries[torrent->nativeHandle()][std::string(a->tracker_url())];
6173 if (a->type() == lt::tracker_reply_alert::alert_type)
6175 const int numPeers = static_cast<const lt::tracker_reply_alert *>(a)->num_peers;
6176 updateInfo.insert(a->local_endpoint, numPeers);
6180 #ifdef QBT_USES_LIBTORRENT2
6181 void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a)
6183 const auto torrentIDv1 = TorrentID::fromSHA1Hash(a->metadata->info_hashes().v1);
6184 const auto torrentIDv2 = TorrentID::fromSHA256Hash(a->metadata->info_hashes().v2);
6185 TorrentImpl *torrent1 = m_torrents.value(torrentIDv1);
6186 TorrentImpl *torrent2 = m_torrents.value(torrentIDv2);
6187 if (torrent2)
6189 if (torrent1)
6190 deleteTorrent(torrentIDv1);
6191 else
6192 cancelDownloadMetadata(torrentIDv1);
6194 invokeAsync([torrentHandle = torrent2->nativeHandle(), metadata = a->metadata]
6198 torrentHandle.set_metadata(metadata->info_section());
6200 catch (const std::exception &) {}
6203 else if (torrent1)
6205 if (!torrent2)
6206 cancelDownloadMetadata(torrentIDv2);
6208 invokeAsync([torrentHandle = torrent1->nativeHandle(), metadata = a->metadata]
6212 torrentHandle.set_metadata(metadata->info_section());
6214 catch (const std::exception &) {}
6217 else
6219 cancelDownloadMetadata(torrentIDv1);
6220 cancelDownloadMetadata(torrentIDv2);
6223 if (!torrent1 || !torrent2)
6224 emit metadataDownloaded(TorrentInfo(*a->metadata));
6226 #endif
6228 void SessionImpl::processTrackerStatuses()
6230 if (m_updatedTrackerEntries.isEmpty())
6231 return;
6233 for (auto it = m_updatedTrackerEntries.cbegin(); it != m_updatedTrackerEntries.cend(); ++it)
6235 invokeAsync([this, torrentHandle = it.key(), updatedTrackers = it.value()]() mutable
6239 std::vector<lt::announce_entry> nativeTrackers = torrentHandle.trackers();
6240 invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers)
6241 , updatedTrackers = std::move(updatedTrackers)]
6243 TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
6244 if (!torrent)
6245 return;
6247 QHash<QString, TrackerEntry> updatedTrackerEntries;
6248 updatedTrackerEntries.reserve(updatedTrackers.size());
6249 for (const lt::announce_entry &announceEntry : nativeTrackers)
6251 const auto updatedTrackersIter = updatedTrackers.find(announceEntry.url);
6252 if (updatedTrackersIter == updatedTrackers.end())
6253 continue;
6255 const QMap<TrackerEntry::Endpoint, int> &updateInfo = updatedTrackersIter.value();
6256 TrackerEntry trackerEntry = torrent->updateTrackerEntry(announceEntry, updateInfo);
6257 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
6258 updatedTrackerEntries[trackerEntry.url] = std::move(trackerEntry);
6259 #else
6260 const QString url = trackerEntry.url;
6261 updatedTrackerEntries.emplace(url, std::move(trackerEntry));
6262 #endif
6265 emit trackerEntriesUpdated(torrent, updatedTrackerEntries);
6268 catch (const std::exception &)
6274 m_updatedTrackerEntries.clear();
6277 void SessionImpl::saveStatistics() const
6279 if (!m_isStatisticsDirty)
6280 return;
6282 const QVariantHash stats {
6283 {u"AlltimeDL"_s, m_status.allTimeDownload},
6284 {u"AlltimeUL"_s, m_status.allTimeUpload}};
6285 std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_s);
6286 settings->setValue(u"Stats/AllStats"_s, stats);
6288 m_statisticsLastUpdateTimer.start();
6289 m_isStatisticsDirty = false;
6292 void SessionImpl::loadStatistics()
6294 const std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_s);
6295 const QVariantHash value = settings->value(u"Stats/AllStats"_s).toHash();
6297 m_previouslyDownloaded = value[u"AlltimeDL"_s].toLongLong();
6298 m_previouslyUploaded = value[u"AlltimeUL"_s].toLongLong();