Try to detect system wake-up event
[qBittorrent.git] / src / base / bittorrent / sessionimpl.cpp
blobf23e43649187e2c1d9c5c8ae139c71446b70a2ed
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2023 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 <QFile>
64 #include <QHostAddress>
65 #include <QJsonArray>
66 #include <QJsonDocument>
67 #include <QJsonObject>
68 #include <QJsonValue>
69 #include <QNetworkAddressEntry>
70 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
71 #include <QNetworkConfigurationManager>
72 #endif
73 #include <QNetworkInterface>
74 #include <QRegularExpression>
75 #include <QString>
76 #include <QThread>
77 #include <QThreadPool>
78 #include <QTimer>
79 #include <QUuid>
81 #include "base/algorithm.h"
82 #include "base/global.h"
83 #include "base/logger.h"
84 #include "base/net/downloadmanager.h"
85 #include "base/net/proxyconfigurationmanager.h"
86 #include "base/preferences.h"
87 #include "base/profile.h"
88 #include "base/torrentfileguard.h"
89 #include "base/torrentfilter.h"
90 #include "base/unicodestrings.h"
91 #include "base/utils/bytearray.h"
92 #include "base/utils/fs.h"
93 #include "base/utils/io.h"
94 #include "base/utils/misc.h"
95 #include "base/utils/net.h"
96 #include "base/utils/random.h"
97 #include "base/version.h"
98 #include "bandwidthscheduler.h"
99 #include "bencoderesumedatastorage.h"
100 #include "common.h"
101 #include "customstorage.h"
102 #include "dbresumedatastorage.h"
103 #include "downloadpriority.h"
104 #include "extensiondata.h"
105 #include "filesearcher.h"
106 #include "filterparserthread.h"
107 #include "loadtorrentparams.h"
108 #include "lttypecast.h"
109 #include "magneturi.h"
110 #include "nativesessionextension.h"
111 #include "portforwarderimpl.h"
112 #include "resumedatastorage.h"
113 #include "torrentimpl.h"
114 #include "tracker.h"
116 using namespace std::chrono_literals;
117 using namespace BitTorrent;
119 const Path CATEGORIES_FILE_NAME {u"categories.json"_qs};
120 const int MAX_PROCESSING_RESUMEDATA_COUNT = 50;
121 const int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count();
123 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
124 namespace std
126 uint qHash(const std::string &key, uint seed = 0)
128 return ::qHash(std::hash<std::string> {}(key), seed);
132 namespace libtorrent
134 uint qHash(const libtorrent::torrent_handle &key)
136 return static_cast<uint>(libtorrent::hash_value(key));
139 #endif
141 namespace
143 const char PEER_ID[] = "qB";
144 const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2);
146 void torrentQueuePositionUp(const lt::torrent_handle &handle)
150 handle.queue_position_up();
152 catch (const std::exception &exc)
154 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
158 void torrentQueuePositionDown(const lt::torrent_handle &handle)
162 handle.queue_position_down();
164 catch (const std::exception &exc)
166 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
170 void torrentQueuePositionTop(const lt::torrent_handle &handle)
174 handle.queue_position_top();
176 catch (const std::exception &exc)
178 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
182 void torrentQueuePositionBottom(const lt::torrent_handle &handle)
186 handle.queue_position_bottom();
188 catch (const std::exception &exc)
190 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
194 QMap<QString, CategoryOptions> expandCategories(const QMap<QString, CategoryOptions> &categories)
196 QMap<QString, CategoryOptions> expanded = categories;
198 for (auto i = categories.cbegin(); i != categories.cend(); ++i)
200 const QString &category = i.key();
201 for (const QString &subcat : asConst(Session::expandCategory(category)))
203 if (!expanded.contains(subcat))
204 expanded[subcat] = {};
208 return expanded;
211 QString toString(const lt::socket_type_t socketType)
213 switch (socketType)
215 #ifdef QBT_USES_LIBTORRENT2
216 case lt::socket_type_t::http:
217 return u"HTTP"_qs;
218 case lt::socket_type_t::http_ssl:
219 return u"HTTP_SSL"_qs;
220 #endif
221 case lt::socket_type_t::i2p:
222 return u"I2P"_qs;
223 case lt::socket_type_t::socks5:
224 return u"SOCKS5"_qs;
225 #ifdef QBT_USES_LIBTORRENT2
226 case lt::socket_type_t::socks5_ssl:
227 return u"SOCKS5_SSL"_qs;
228 #endif
229 case lt::socket_type_t::tcp:
230 return u"TCP"_qs;
231 case lt::socket_type_t::tcp_ssl:
232 return u"TCP_SSL"_qs;
233 #ifdef QBT_USES_LIBTORRENT2
234 case lt::socket_type_t::utp:
235 return u"UTP"_qs;
236 #else
237 case lt::socket_type_t::udp:
238 return u"UDP"_qs;
239 #endif
240 case lt::socket_type_t::utp_ssl:
241 return u"UTP_SSL"_qs;
243 return u"INVALID"_qs;
246 QString toString(const lt::address &address)
250 return QString::fromLatin1(address.to_string().c_str());
252 catch (const std::exception &)
254 // suppress conversion error
256 return {};
259 template <typename T>
260 struct LowerLimited
262 LowerLimited(T limit, T ret)
263 : m_limit(limit)
264 , m_ret(ret)
268 explicit LowerLimited(T limit)
269 : LowerLimited(limit, limit)
273 T operator()(T val) const
275 return val <= m_limit ? m_ret : val;
278 private:
279 const T m_limit;
280 const T m_ret;
283 template <typename T>
284 LowerLimited<T> lowerLimited(T limit) { return LowerLimited<T>(limit); }
286 template <typename T>
287 LowerLimited<T> lowerLimited(T limit, T ret) { return LowerLimited<T>(limit, ret); }
289 template <typename T>
290 auto clampValue(const T lower, const T upper)
292 return [lower, upper](const T value) -> T
294 if (value < lower)
295 return lower;
296 if (value > upper)
297 return upper;
298 return value;
302 #ifdef Q_OS_WIN
303 QString convertIfaceNameToGuid(const QString &name)
305 // Under Windows XP or on Qt version <= 5.5 'name' will be a GUID already.
306 const QUuid uuid(name);
307 if (!uuid.isNull())
308 return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
310 const std::wstring nameWStr = name.toStdWString();
311 NET_LUID luid {};
312 const LONG res = ::ConvertInterfaceNameToLuidW(nameWStr.c_str(), &luid);
313 if (res == 0)
315 GUID guid;
316 if (::ConvertInterfaceLuidToGuid(&luid, &guid) == 0)
317 return QUuid(guid).toString().toUpper();
320 return {};
322 #endif
324 constexpr lt::move_flags_t toNative(const MoveStorageMode mode)
326 switch (mode)
328 default:
329 Q_ASSERT(false);
330 case MoveStorageMode::FailIfExist:
331 return lt::move_flags_t::fail_if_exist;
332 case MoveStorageMode::KeepExistingFiles:
333 return lt::move_flags_t::dont_replace;
334 case MoveStorageMode::Overwrite:
335 return lt::move_flags_t::always_replace_files;
340 struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject
342 using QObject::QObject;
344 ResumeDataStorage *startupStorage = nullptr;
345 ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy;
346 QList<LoadedResumeData> loadedResumeData;
347 int processingResumeDataCount = 0;
348 int64_t totalResumeDataCount = 0;
349 int64_t finishedResumeDataCount = 0;
350 bool isLoadFinished = false;
351 bool isLoadedResumeDataHandlingEnqueued = false;
352 QSet<QString> recoveredCategories;
353 #ifdef QBT_USES_LIBTORRENT2
354 QSet<TorrentID> indexedTorrents;
355 QSet<TorrentID> skippedIDs;
356 #endif
359 const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
361 Session *SessionImpl::m_instance = nullptr;
363 void Session::initInstance()
365 if (!SessionImpl::m_instance)
366 SessionImpl::m_instance = new SessionImpl;
369 void Session::freeInstance()
371 delete SessionImpl::m_instance;
372 SessionImpl::m_instance = nullptr;
375 Session *Session::instance()
377 return SessionImpl::m_instance;
380 bool Session::isValidCategoryName(const QString &name)
382 const QRegularExpression re {uR"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)"_qs};
383 return (name.isEmpty() || (name.indexOf(re) == 0));
386 bool Session::isValidTag(const QString &tag)
388 return (!tag.trimmed().isEmpty() && !tag.contains(u','));
391 QStringList Session::expandCategory(const QString &category)
393 QStringList result;
394 int index = 0;
395 while ((index = category.indexOf(u'/', index)) >= 0)
397 result << category.left(index);
398 ++index;
400 result << category;
402 return result;
405 #define BITTORRENT_KEY(name) u"BitTorrent/" name
406 #define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY(u"Session/") name
408 SessionImpl::SessionImpl(QObject *parent)
409 : Session(parent)
410 , m_isDHTEnabled(BITTORRENT_SESSION_KEY(u"DHTEnabled"_qs), true)
411 , m_isLSDEnabled(BITTORRENT_SESSION_KEY(u"LSDEnabled"_qs), true)
412 , m_isPeXEnabled(BITTORRENT_SESSION_KEY(u"PeXEnabled"_qs), true)
413 , m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY(u"IPFilteringEnabled"_qs), false)
414 , m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY(u"TrackerFilteringEnabled"_qs), false)
415 , m_IPFilterFile(BITTORRENT_SESSION_KEY(u"IPFilter"_qs))
416 , m_announceToAllTrackers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTrackers"_qs), false)
417 , m_announceToAllTiers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTiers"_qs), true)
418 , m_asyncIOThreads(BITTORRENT_SESSION_KEY(u"AsyncIOThreadsCount"_qs), 10)
419 , m_hashingThreads(BITTORRENT_SESSION_KEY(u"HashingThreadsCount"_qs), 1)
420 , m_filePoolSize(BITTORRENT_SESSION_KEY(u"FilePoolSize"_qs), 500)
421 , m_checkingMemUsage(BITTORRENT_SESSION_KEY(u"CheckingMemUsageSize"_qs), 32)
422 , m_diskCacheSize(BITTORRENT_SESSION_KEY(u"DiskCacheSize"_qs), -1)
423 , m_diskCacheTTL(BITTORRENT_SESSION_KEY(u"DiskCacheTTL"_qs), 60)
424 , m_diskQueueSize(BITTORRENT_SESSION_KEY(u"DiskQueueSize"_qs), (1024 * 1024))
425 , m_diskIOType(BITTORRENT_SESSION_KEY(u"DiskIOType"_qs), DiskIOType::Default)
426 , m_diskIOReadMode(BITTORRENT_SESSION_KEY(u"DiskIOReadMode"_qs), DiskIOReadMode::EnableOSCache)
427 , m_diskIOWriteMode(BITTORRENT_SESSION_KEY(u"DiskIOWriteMode"_qs), DiskIOWriteMode::EnableOSCache)
428 #ifdef Q_OS_WIN
429 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_qs), true)
430 #else
431 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_qs), false)
432 #endif
433 , m_usePieceExtentAffinity(BITTORRENT_SESSION_KEY(u"PieceExtentAffinity"_qs), false)
434 , m_isSuggestMode(BITTORRENT_SESSION_KEY(u"SuggestMode"_qs), false)
435 , m_sendBufferWatermark(BITTORRENT_SESSION_KEY(u"SendBufferWatermark"_qs), 500)
436 , m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY(u"SendBufferLowWatermark"_qs), 10)
437 , m_sendBufferWatermarkFactor(BITTORRENT_SESSION_KEY(u"SendBufferWatermarkFactor"_qs), 50)
438 , m_connectionSpeed(BITTORRENT_SESSION_KEY(u"ConnectionSpeed"_qs), 30)
439 , m_socketSendBufferSize(BITTORRENT_SESSION_KEY(u"SocketSendBufferSize"_qs), 0)
440 , m_socketReceiveBufferSize(BITTORRENT_SESSION_KEY(u"SocketReceiveBufferSize"_qs), 0)
441 , m_socketBacklogSize(BITTORRENT_SESSION_KEY(u"SocketBacklogSize"_qs), 30)
442 , m_isAnonymousModeEnabled(BITTORRENT_SESSION_KEY(u"AnonymousModeEnabled"_qs), false)
443 , m_isQueueingEnabled(BITTORRENT_SESSION_KEY(u"QueueingSystemEnabled"_qs), false)
444 , m_maxActiveDownloads(BITTORRENT_SESSION_KEY(u"MaxActiveDownloads"_qs), 3, lowerLimited(-1))
445 , m_maxActiveUploads(BITTORRENT_SESSION_KEY(u"MaxActiveUploads"_qs), 3, lowerLimited(-1))
446 , m_maxActiveTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveTorrents"_qs), 5, lowerLimited(-1))
447 , m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY(u"IgnoreSlowTorrentsForQueueing"_qs), false)
448 , m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsDownloadRate"_qs), 2)
449 , m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsUploadRate"_qs), 2)
450 , m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY(u"SlowTorrentsInactivityTimer"_qs), 60)
451 , m_outgoingPortsMin(BITTORRENT_SESSION_KEY(u"OutgoingPortsMin"_qs), 0)
452 , m_outgoingPortsMax(BITTORRENT_SESSION_KEY(u"OutgoingPortsMax"_qs), 0)
453 , m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY(u"UPnPLeaseDuration"_qs), 0)
454 , m_peerToS(BITTORRENT_SESSION_KEY(u"PeerToS"_qs), 0x04)
455 , m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY(u"IgnoreLimitsOnLAN"_qs), false)
456 , m_includeOverheadInLimits(BITTORRENT_SESSION_KEY(u"IncludeOverheadInLimits"_qs), false)
457 , m_announceIP(BITTORRENT_SESSION_KEY(u"AnnounceIP"_qs))
458 , m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY(u"MaxConcurrentHTTPAnnounces"_qs), 50)
459 , m_isReannounceWhenAddressChangedEnabled(BITTORRENT_SESSION_KEY(u"ReannounceWhenAddressChanged"_qs), false)
460 , m_stopTrackerTimeout(BITTORRENT_SESSION_KEY(u"StopTrackerTimeout"_qs), 5)
461 , m_maxConnections(BITTORRENT_SESSION_KEY(u"MaxConnections"_qs), 500, lowerLimited(0, -1))
462 , m_maxUploads(BITTORRENT_SESSION_KEY(u"MaxUploads"_qs), 20, lowerLimited(0, -1))
463 , m_maxConnectionsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxConnectionsPerTorrent"_qs), 100, lowerLimited(0, -1))
464 , m_maxUploadsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxUploadsPerTorrent"_qs), 4, lowerLimited(0, -1))
465 , m_btProtocol(BITTORRENT_SESSION_KEY(u"BTProtocol"_qs), BTProtocol::Both
466 , clampValue(BTProtocol::Both, BTProtocol::UTP))
467 , m_isUTPRateLimited(BITTORRENT_SESSION_KEY(u"uTPRateLimited"_qs), true)
468 , m_utpMixedMode(BITTORRENT_SESSION_KEY(u"uTPMixedMode"_qs), MixedModeAlgorithm::TCP
469 , clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
470 , m_IDNSupportEnabled(BITTORRENT_SESSION_KEY(u"IDNSupportEnabled"_qs), false)
471 , m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY(u"MultiConnectionsPerIp"_qs), false)
472 , m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY(u"ValidateHTTPSTrackerCertificate"_qs), true)
473 , m_SSRFMitigationEnabled(BITTORRENT_SESSION_KEY(u"SSRFMitigation"_qs), true)
474 , m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY(u"BlockPeersOnPrivilegedPorts"_qs), false)
475 , m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersEnabled"_qs), false)
476 , m_additionalTrackers(BITTORRENT_SESSION_KEY(u"AdditionalTrackers"_qs))
477 , m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_qs), -1, [](qreal r) { return r < 0 ? -1. : r;})
478 , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_qs), -1, lowerLimited(-1))
479 , m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_qs), false)
480 , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY(u"AddTorrentPaused"_qs), false)
481 , m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_qs), Torrent::StopCondition::None)
482 , m_torrentContentLayout(BITTORRENT_SESSION_KEY(u"TorrentContentLayout"_qs), TorrentContentLayout::Original)
483 , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY(u"AddExtensionToIncompleteFiles"_qs), false)
484 , m_refreshInterval(BITTORRENT_SESSION_KEY(u"RefreshInterval"_qs), 1500)
485 , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY(u"Preallocation"_qs), false)
486 , m_torrentExportDirectory(BITTORRENT_SESSION_KEY(u"TorrentExportDirectory"_qs))
487 , m_finishedTorrentExportDirectory(BITTORRENT_SESSION_KEY(u"FinishedTorrentExportDirectory"_qs))
488 , m_globalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalDLSpeedLimit"_qs), 0, lowerLimited(0))
489 , m_globalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalUPSpeedLimit"_qs), 0, lowerLimited(0))
490 , m_altGlobalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalDLSpeedLimit"_qs), 10, lowerLimited(0))
491 , m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalUPSpeedLimit"_qs), 10, lowerLimited(0))
492 , m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY(u"UseAlternativeGlobalSpeedLimit"_qs), false)
493 , m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY(u"BandwidthSchedulerEnabled"_qs), false)
494 , m_isPerformanceWarningEnabled(BITTORRENT_SESSION_KEY(u"PerformanceWarning"_qs), false)
495 , m_saveResumeDataInterval(BITTORRENT_SESSION_KEY(u"SaveResumeDataInterval"_qs), 60)
496 , m_port(BITTORRENT_SESSION_KEY(u"Port"_qs), -1)
497 , m_networkInterface(BITTORRENT_SESSION_KEY(u"Interface"_qs))
498 , m_networkInterfaceName(BITTORRENT_SESSION_KEY(u"InterfaceName"_qs))
499 , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY(u"InterfaceAddress"_qs))
500 , m_encryption(BITTORRENT_SESSION_KEY(u"Encryption"_qs), 0)
501 , m_maxActiveCheckingTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveCheckingTorrents"_qs), 1)
502 , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY(u"ProxyPeerConnections"_qs), false)
503 , m_chokingAlgorithm(BITTORRENT_SESSION_KEY(u"ChokingAlgorithm"_qs), ChokingAlgorithm::FixedSlots
504 , clampValue(ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased))
505 , m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY(u"SeedChokingAlgorithm"_qs), SeedChokingAlgorithm::FastestUpload
506 , clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
507 , m_storedTags(BITTORRENT_SESSION_KEY(u"Tags"_qs))
508 , m_maxRatioAction(BITTORRENT_SESSION_KEY(u"MaxRatioAction"_qs), Pause)
509 , m_savePath(BITTORRENT_SESSION_KEY(u"DefaultSavePath"_qs), specialFolderLocation(SpecialFolder::Downloads))
510 , m_downloadPath(BITTORRENT_SESSION_KEY(u"TempPath"_qs), (savePath() / Path(u"temp"_qs)))
511 , m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY(u"TempPathEnabled"_qs), false)
512 , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY(u"SubcategoriesEnabled"_qs), false)
513 , m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY(u"UseCategoryPathsInManualMode"_qs), false)
514 , m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY(u"DisableAutoTMMByDefault"_qs), true)
515 , m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategoryChanged"_qs), false)
516 , m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/DefaultSavePathChanged"_qs), true)
517 , m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategorySavePathChanged"_qs), true)
518 , m_isTrackerEnabled(BITTORRENT_KEY(u"TrackerEnabled"_qs), false)
519 , m_peerTurnover(BITTORRENT_SESSION_KEY(u"PeerTurnover"_qs), 4)
520 , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY(u"PeerTurnoverCutOff"_qs), 90)
521 , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY(u"PeerTurnoverInterval"_qs), 300)
522 , m_requestQueueSize(BITTORRENT_SESSION_KEY(u"RequestQueueSize"_qs), 500)
523 , m_isExcludedFileNamesEnabled(BITTORRENT_KEY(u"ExcludedFileNamesEnabled"_qs), false)
524 , m_excludedFileNames(BITTORRENT_SESSION_KEY(u"ExcludedFileNames"_qs))
525 , m_bannedIPs(u"State/BannedIPs"_qs, QStringList(), Algorithm::sorted<QStringList>)
526 , m_resumeDataStorageType(BITTORRENT_SESSION_KEY(u"ResumeDataStorageType"_qs), ResumeDataStorageType::Legacy)
527 , m_isI2PEnabled {BITTORRENT_SESSION_KEY(u"I2P/Enabled"_qs), false}
528 , m_I2PAddress {BITTORRENT_SESSION_KEY(u"I2P/Address"_qs), u"127.0.0.1"_qs}
529 , m_I2PPort {BITTORRENT_SESSION_KEY(u"I2P/Port"_qs), 7656}
530 , m_I2PMixedMode {BITTORRENT_SESSION_KEY(u"I2P/MixedMode"_qs), false}
531 , m_seedingLimitTimer {new QTimer(this)}
532 , m_resumeDataTimer {new QTimer(this)}
533 , m_ioThread {new QThread}
534 , m_asyncWorker {new QThreadPool(this)}
535 , m_recentErroredTorrentsTimer {new QTimer(this)}
536 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
537 , m_networkManager {new QNetworkConfigurationManager(this)}
538 #endif
540 // It is required to perform async access to libtorrent sequentially
541 m_asyncWorker->setMaxThreadCount(1);
543 if (port() < 0)
544 m_port = Utils::Random::rand(1024, 65535);
546 m_recentErroredTorrentsTimer->setSingleShot(true);
547 m_recentErroredTorrentsTimer->setInterval(1s);
548 connect(m_recentErroredTorrentsTimer, &QTimer::timeout
549 , this, [this]() { m_recentErroredTorrents.clear(); });
551 m_seedingLimitTimer->setInterval(10s);
552 connect(m_seedingLimitTimer, &QTimer::timeout, this, &SessionImpl::processShareLimits);
554 initializeNativeSession();
555 configureComponents();
557 if (isBandwidthSchedulerEnabled())
558 enableBandwidthScheduler();
560 loadCategories();
561 if (isSubcategoriesEnabled())
563 // if subcategories support changed manually
564 m_categories = expandCategories(m_categories);
567 const QStringList storedTags = m_storedTags.get();
568 m_tags = {storedTags.cbegin(), storedTags.cend()};
570 updateSeedingLimitTimer();
571 populateAdditionalTrackers();
572 if (isExcludedFileNamesEnabled())
573 populateExcludedFileNamesRegExpList();
575 connect(Net::ProxyConfigurationManager::instance()
576 , &Net::ProxyConfigurationManager::proxyConfigurationChanged
577 , this, &SessionImpl::configureDeferred);
579 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
580 // Network configuration monitor
581 connect(m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &SessionImpl::networkOnlineStateChanged);
582 connect(m_networkManager, &QNetworkConfigurationManager::configurationAdded, this, &SessionImpl::networkConfigurationChange);
583 connect(m_networkManager, &QNetworkConfigurationManager::configurationRemoved, this, &SessionImpl::networkConfigurationChange);
584 connect(m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &SessionImpl::networkConfigurationChange);
585 #endif
587 m_fileSearcher = new FileSearcher;
588 m_fileSearcher->moveToThread(m_ioThread.get());
589 connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
590 connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
592 m_ioThread->start();
594 initMetrics();
595 loadStatistics();
597 // initialize PortForwarder instance
598 new PortForwarderImpl(this);
600 // start embedded tracker
601 enableTracker(isTrackerEnabled());
603 prepareStartup();
606 SessionImpl::~SessionImpl()
608 m_nativeSession->pause();
610 if (m_torrentsQueueChanged)
612 m_nativeSession->post_torrent_updates({});
613 m_torrentsQueueChanged = false;
614 m_needSaveTorrentsQueue = true;
617 // Do some bittorrent related saving
618 // After this, (ideally) no more important alerts will be generated/handled
619 saveResumeData();
621 saveStatistics();
623 // We must delete FilterParserThread
624 // before we delete lt::session
625 delete m_filterParser;
627 // We must delete PortForwarderImpl before
628 // we delete lt::session
629 delete Net::PortForwarder::instance();
631 // We must stop "async worker" only after deletion
632 // of all the components that could potentially use it
633 m_asyncWorker->clear();
634 m_asyncWorker->waitForDone();
636 qDebug("Deleting libtorrent session...");
637 delete m_nativeSession;
640 bool SessionImpl::isDHTEnabled() const
642 return m_isDHTEnabled;
645 void SessionImpl::setDHTEnabled(bool enabled)
647 if (enabled != m_isDHTEnabled)
649 m_isDHTEnabled = enabled;
650 configureDeferred();
651 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(enabled ? tr("ON") : tr("OFF")), Log::INFO);
655 bool SessionImpl::isLSDEnabled() const
657 return m_isLSDEnabled;
660 void SessionImpl::setLSDEnabled(const bool enabled)
662 if (enabled != m_isLSDEnabled)
664 m_isLSDEnabled = enabled;
665 configureDeferred();
666 LogMsg(tr("Local Peer Discovery support: %1").arg(enabled ? tr("ON") : tr("OFF"))
667 , Log::INFO);
671 bool SessionImpl::isPeXEnabled() const
673 return m_isPeXEnabled;
676 void SessionImpl::setPeXEnabled(const bool enabled)
678 m_isPeXEnabled = enabled;
679 if (m_wasPexEnabled != enabled)
680 LogMsg(tr("Restart is required to toggle Peer Exchange (PeX) support"), Log::WARNING);
683 bool SessionImpl::isDownloadPathEnabled() const
685 return m_isDownloadPathEnabled;
688 void SessionImpl::setDownloadPathEnabled(const bool enabled)
690 if (enabled != isDownloadPathEnabled())
692 m_isDownloadPathEnabled = enabled;
693 for (TorrentImpl *const torrent : asConst(m_torrents))
694 torrent->handleCategoryOptionsChanged();
698 bool SessionImpl::isAppendExtensionEnabled() const
700 return m_isAppendExtensionEnabled;
703 void SessionImpl::setAppendExtensionEnabled(const bool enabled)
705 if (isAppendExtensionEnabled() != enabled)
707 m_isAppendExtensionEnabled = enabled;
709 // append or remove .!qB extension for incomplete files
710 for (TorrentImpl *const torrent : asConst(m_torrents))
711 torrent->handleAppendExtensionToggled();
715 int SessionImpl::refreshInterval() const
717 return m_refreshInterval;
720 void SessionImpl::setRefreshInterval(const int value)
722 if (value != refreshInterval())
724 m_refreshInterval = value;
728 bool SessionImpl::isPreallocationEnabled() const
730 return m_isPreallocationEnabled;
733 void SessionImpl::setPreallocationEnabled(const bool enabled)
735 m_isPreallocationEnabled = enabled;
738 Path SessionImpl::torrentExportDirectory() const
740 return m_torrentExportDirectory;
743 void SessionImpl::setTorrentExportDirectory(const Path &path)
745 if (path != torrentExportDirectory())
746 m_torrentExportDirectory = path;
749 Path SessionImpl::finishedTorrentExportDirectory() const
751 return m_finishedTorrentExportDirectory;
754 void SessionImpl::setFinishedTorrentExportDirectory(const Path &path)
756 if (path != finishedTorrentExportDirectory())
757 m_finishedTorrentExportDirectory = path;
760 Path SessionImpl::savePath() const
762 // TODO: Make sure it is always non-empty
763 return m_savePath;
766 Path SessionImpl::downloadPath() const
768 // TODO: Make sure it is always non-empty
769 return m_downloadPath;
772 QStringList SessionImpl::categories() const
774 return m_categories.keys();
777 CategoryOptions SessionImpl::categoryOptions(const QString &categoryName) const
779 return m_categories.value(categoryName);
782 Path SessionImpl::categorySavePath(const QString &categoryName) const
784 const Path basePath = savePath();
785 if (categoryName.isEmpty())
786 return basePath;
788 Path path = m_categories.value(categoryName).savePath;
789 if (path.isEmpty()) // use implicit save path
790 path = Utils::Fs::toValidPath(categoryName);
792 return (path.isAbsolute() ? path : (basePath / path));
795 Path SessionImpl::categoryDownloadPath(const QString &categoryName) const
797 const CategoryOptions categoryOptions = m_categories.value(categoryName);
798 const CategoryOptions::DownloadPathOption downloadPathOption =
799 categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
800 if (!downloadPathOption.enabled)
801 return {};
803 const Path basePath = downloadPath();
804 if (categoryName.isEmpty())
805 return basePath;
807 const Path path = (!downloadPathOption.path.isEmpty()
808 ? downloadPathOption.path
809 : Utils::Fs::toValidPath(categoryName)); // use implicit download path
811 return (path.isAbsolute() ? path : (basePath / path));
814 bool SessionImpl::addCategory(const QString &name, const CategoryOptions &options)
816 if (name.isEmpty())
817 return false;
819 if (!isValidCategoryName(name) || m_categories.contains(name))
820 return false;
822 if (isSubcategoriesEnabled())
824 for (const QString &parent : asConst(expandCategory(name)))
826 if ((parent != name) && !m_categories.contains(parent))
828 m_categories[parent] = {};
829 emit categoryAdded(parent);
834 m_categories[name] = options;
835 storeCategories();
836 emit categoryAdded(name);
838 return true;
841 bool SessionImpl::editCategory(const QString &name, const CategoryOptions &options)
843 const auto it = m_categories.find(name);
844 if (it == m_categories.end())
845 return false;
847 CategoryOptions &currentOptions = it.value();
848 if (options == currentOptions)
849 return false;
851 currentOptions = options;
852 storeCategories();
853 if (isDisableAutoTMMWhenCategorySavePathChanged())
855 for (TorrentImpl *const torrent : asConst(m_torrents))
857 if (torrent->category() == name)
858 torrent->setAutoTMMEnabled(false);
861 else
863 for (TorrentImpl *const torrent : asConst(m_torrents))
865 if (torrent->category() == name)
866 torrent->handleCategoryOptionsChanged();
870 emit categoryOptionsChanged(name);
871 return true;
874 bool SessionImpl::removeCategory(const QString &name)
876 for (TorrentImpl *const torrent : asConst(m_torrents))
878 if (torrent->belongsToCategory(name))
879 torrent->setCategory(u""_qs);
882 // remove stored category and its subcategories if exist
883 bool result = false;
884 if (isSubcategoriesEnabled())
886 // remove subcategories
887 const QString test = name + u'/';
888 Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const CategoryOptions &)
890 if (category.startsWith(test))
892 result = true;
893 emit categoryRemoved(category);
894 return true;
896 return false;
900 result = (m_categories.remove(name) > 0) || result;
902 if (result)
904 // update stored categories
905 storeCategories();
906 emit categoryRemoved(name);
909 return result;
912 bool SessionImpl::isSubcategoriesEnabled() const
914 return m_isSubcategoriesEnabled;
917 void SessionImpl::setSubcategoriesEnabled(const bool value)
919 if (isSubcategoriesEnabled() == value) return;
921 if (value)
923 // expand categories to include all parent categories
924 m_categories = expandCategories(m_categories);
925 // update stored categories
926 storeCategories();
928 else
930 // reload categories
931 loadCategories();
934 m_isSubcategoriesEnabled = value;
935 emit subcategoriesSupportChanged();
938 bool SessionImpl::useCategoryPathsInManualMode() const
940 return m_useCategoryPathsInManualMode;
943 void SessionImpl::setUseCategoryPathsInManualMode(const bool value)
945 m_useCategoryPathsInManualMode = value;
948 Path SessionImpl::suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const
950 const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
951 const auto path = (useCategoryPaths ? categorySavePath(categoryName) : savePath());
952 return path;
955 Path SessionImpl::suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const
957 const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
958 const auto categoryDownloadPath = this->categoryDownloadPath(categoryName);
959 const auto path = ((useCategoryPaths && !categoryDownloadPath.isEmpty()) ? categoryDownloadPath : downloadPath());
960 return path;
963 QSet<QString> SessionImpl::tags() const
965 return m_tags;
968 bool SessionImpl::hasTag(const QString &tag) const
970 return m_tags.contains(tag);
973 bool SessionImpl::addTag(const QString &tag)
975 if (!isValidTag(tag) || hasTag(tag))
976 return false;
978 m_tags.insert(tag);
979 m_storedTags = m_tags.values();
980 emit tagAdded(tag);
981 return true;
984 bool SessionImpl::removeTag(const QString &tag)
986 if (m_tags.remove(tag))
988 for (TorrentImpl *const torrent : asConst(m_torrents))
989 torrent->removeTag(tag);
990 m_storedTags = m_tags.values();
991 emit tagRemoved(tag);
992 return true;
994 return false;
997 bool SessionImpl::isAutoTMMDisabledByDefault() const
999 return m_isAutoTMMDisabledByDefault;
1002 void SessionImpl::setAutoTMMDisabledByDefault(const bool value)
1004 m_isAutoTMMDisabledByDefault = value;
1007 bool SessionImpl::isDisableAutoTMMWhenCategoryChanged() const
1009 return m_isDisableAutoTMMWhenCategoryChanged;
1012 void SessionImpl::setDisableAutoTMMWhenCategoryChanged(const bool value)
1014 m_isDisableAutoTMMWhenCategoryChanged = value;
1017 bool SessionImpl::isDisableAutoTMMWhenDefaultSavePathChanged() const
1019 return m_isDisableAutoTMMWhenDefaultSavePathChanged;
1022 void SessionImpl::setDisableAutoTMMWhenDefaultSavePathChanged(const bool value)
1024 m_isDisableAutoTMMWhenDefaultSavePathChanged = value;
1027 bool SessionImpl::isDisableAutoTMMWhenCategorySavePathChanged() const
1029 return m_isDisableAutoTMMWhenCategorySavePathChanged;
1032 void SessionImpl::setDisableAutoTMMWhenCategorySavePathChanged(const bool value)
1034 m_isDisableAutoTMMWhenCategorySavePathChanged = value;
1037 bool SessionImpl::isAddTorrentToQueueTop() const
1039 return m_isAddTorrentToQueueTop;
1042 void SessionImpl::setAddTorrentToQueueTop(bool value)
1044 m_isAddTorrentToQueueTop = value;
1047 bool SessionImpl::isAddTorrentPaused() const
1049 return m_isAddTorrentPaused;
1052 void SessionImpl::setAddTorrentPaused(const bool value)
1054 m_isAddTorrentPaused = value;
1057 Torrent::StopCondition SessionImpl::torrentStopCondition() const
1059 return m_torrentStopCondition;
1062 void SessionImpl::setTorrentStopCondition(const Torrent::StopCondition stopCondition)
1064 m_torrentStopCondition = stopCondition;
1067 bool SessionImpl::isTrackerEnabled() const
1069 return m_isTrackerEnabled;
1072 void SessionImpl::setTrackerEnabled(const bool enabled)
1074 if (m_isTrackerEnabled != enabled)
1075 m_isTrackerEnabled = enabled;
1077 // call enableTracker() unconditionally, otherwise port change won't trigger
1078 // tracker restart
1079 enableTracker(enabled);
1082 qreal SessionImpl::globalMaxRatio() const
1084 return m_globalMaxRatio;
1087 // Torrents with a ratio superior to the given value will
1088 // be automatically deleted
1089 void SessionImpl::setGlobalMaxRatio(qreal ratio)
1091 if (ratio < 0)
1092 ratio = -1.;
1094 if (ratio != globalMaxRatio())
1096 m_globalMaxRatio = ratio;
1097 updateSeedingLimitTimer();
1101 int SessionImpl::globalMaxSeedingMinutes() const
1103 return m_globalMaxSeedingMinutes;
1106 void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
1108 if (minutes < 0)
1109 minutes = -1;
1111 if (minutes != globalMaxSeedingMinutes())
1113 m_globalMaxSeedingMinutes = minutes;
1114 updateSeedingLimitTimer();
1118 void SessionImpl::applyBandwidthLimits()
1120 lt::settings_pack settingsPack;
1121 settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
1122 settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
1123 m_nativeSession->apply_settings(std::move(settingsPack));
1126 void SessionImpl::configure()
1128 m_nativeSession->apply_settings(loadLTSettings());
1129 configureComponents();
1131 m_deferredConfigureScheduled = false;
1134 void SessionImpl::configureComponents()
1136 // This function contains components/actions that:
1137 // 1. Need to be setup at start up
1138 // 2. When deferred configure is called
1140 configurePeerClasses();
1142 if (!m_IPFilteringConfigured)
1144 if (isIPFilteringEnabled())
1145 enableIPFilter();
1146 else
1147 disableIPFilter();
1148 m_IPFilteringConfigured = true;
1152 void SessionImpl::prepareStartup()
1154 qDebug("Initializing torrents resume data storage...");
1156 const Path dbPath = specialFolderLocation(SpecialFolder::Data) / Path(u"torrents.db"_qs);
1157 const bool dbStorageExists = dbPath.exists();
1159 auto *context = new ResumeSessionContext(this);
1160 context->currentStorageType = resumeDataStorageType();
1162 if (context->currentStorageType == ResumeDataStorageType::SQLite)
1164 m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
1166 if (!dbStorageExists)
1168 const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_qs);
1169 context->startupStorage = new BencodeResumeDataStorage(dataPath, this);
1172 else
1174 const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_qs);
1175 m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
1177 if (dbStorageExists)
1178 context->startupStorage = new DBResumeDataStorage(dbPath, this);
1181 if (!context->startupStorage)
1182 context->startupStorage = m_resumeDataStorage;
1184 connect(context->startupStorage, &ResumeDataStorage::loadStarted, context
1185 , [this, context](const QVector<TorrentID> &torrents)
1187 context->totalResumeDataCount = torrents.size();
1188 #ifdef QBT_USES_LIBTORRENT2
1189 context->indexedTorrents = QSet<TorrentID>(torrents.cbegin(), torrents.cend());
1190 #endif
1192 handleLoadedResumeData(context);
1195 connect(context->startupStorage, &ResumeDataStorage::loadFinished, context, [context]()
1197 context->isLoadFinished = true;
1200 connect(this, &SessionImpl::torrentsLoaded, context, [this, context](const QVector<Torrent *> &torrents)
1202 context->processingResumeDataCount -= torrents.count();
1203 context->finishedResumeDataCount += torrents.count();
1204 if (!context->isLoadedResumeDataHandlingEnqueued)
1206 QMetaObject::invokeMethod(this, [this, context]() { handleLoadedResumeData(context); }, Qt::QueuedConnection);
1207 context->isLoadedResumeDataHandlingEnqueued = true;
1210 if (!m_refreshEnqueued)
1212 m_nativeSession->post_torrent_updates();
1213 m_refreshEnqueued = true;
1216 emit startupProgressUpdated((context->finishedResumeDataCount * 100.) / context->totalResumeDataCount);
1219 context->startupStorage->loadAll();
1222 void SessionImpl::handleLoadedResumeData(ResumeSessionContext *context)
1224 context->isLoadedResumeDataHandlingEnqueued = false;
1226 int count = context->processingResumeDataCount;
1227 while (context->processingResumeDataCount < MAX_PROCESSING_RESUMEDATA_COUNT)
1229 if (context->loadedResumeData.isEmpty())
1230 context->loadedResumeData = context->startupStorage->fetchLoadedResumeData();
1232 if (context->loadedResumeData.isEmpty())
1234 if (context->processingResumeDataCount == 0)
1236 if (context->isLoadFinished)
1238 endStartup(context);
1240 else if (!context->isLoadedResumeDataHandlingEnqueued)
1242 QMetaObject::invokeMethod(this, [this, context]() { handleLoadedResumeData(context); }, Qt::QueuedConnection);
1243 context->isLoadedResumeDataHandlingEnqueued = true;
1247 break;
1250 processNextResumeData(context);
1251 ++count;
1254 context->finishedResumeDataCount += (count - context->processingResumeDataCount);
1257 void SessionImpl::processNextResumeData(ResumeSessionContext *context)
1259 const LoadedResumeData loadedResumeDataItem = context->loadedResumeData.takeFirst();
1261 TorrentID torrentID = loadedResumeDataItem.torrentID;
1262 #ifdef QBT_USES_LIBTORRENT2
1263 if (context->skippedIDs.contains(torrentID))
1264 return;
1265 #endif
1267 const nonstd::expected<LoadTorrentParams, QString> &loadResumeDataResult = loadedResumeDataItem.result;
1268 if (!loadResumeDataResult)
1270 LogMsg(tr("Failed to resume torrent. Torrent: \"%1\". Reason: \"%2\"")
1271 .arg(torrentID.toString(), loadResumeDataResult.error()), Log::CRITICAL);
1272 return;
1275 LoadTorrentParams resumeData = *loadResumeDataResult;
1276 bool needStore = false;
1278 #ifdef QBT_USES_LIBTORRENT2
1279 const InfoHash infoHash {(resumeData.ltAddTorrentParams.ti
1280 ? resumeData.ltAddTorrentParams.ti->info_hashes()
1281 : resumeData.ltAddTorrentParams.info_hashes)};
1282 const bool isHybrid = infoHash.isHybrid();
1283 const auto torrentIDv2 = TorrentID::fromInfoHash(infoHash);
1284 const auto torrentIDv1 = TorrentID::fromSHA1Hash(infoHash.v1());
1285 if (torrentID == torrentIDv2)
1287 if (isHybrid && context->indexedTorrents.contains(torrentIDv1))
1289 // if we don't have metadata, try to find it in alternative "resume data"
1290 if (!resumeData.ltAddTorrentParams.ti)
1292 const nonstd::expected<LoadTorrentParams, QString> loadAltResumeDataResult = context->startupStorage->load(torrentIDv1);
1293 if (loadAltResumeDataResult)
1294 resumeData.ltAddTorrentParams.ti = loadAltResumeDataResult->ltAddTorrentParams.ti;
1297 // remove alternative "resume data" and skip the attempt to load it
1298 m_resumeDataStorage->remove(torrentIDv1);
1299 context->skippedIDs.insert(torrentIDv1);
1302 else if (torrentID == torrentIDv1)
1304 torrentID = torrentIDv2;
1305 needStore = true;
1306 m_resumeDataStorage->remove(torrentIDv1);
1308 if (context->indexedTorrents.contains(torrentID))
1310 context->skippedIDs.insert(torrentID);
1312 const nonstd::expected<LoadTorrentParams, QString> loadPreferredResumeDataResult = context->startupStorage->load(torrentID);
1313 if (loadPreferredResumeDataResult)
1315 std::shared_ptr<lt::torrent_info> ti = resumeData.ltAddTorrentParams.ti;
1316 resumeData = *loadPreferredResumeDataResult;
1317 if (!resumeData.ltAddTorrentParams.ti)
1318 resumeData.ltAddTorrentParams.ti = ti;
1322 else
1324 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1325 .arg(torrentID.toString()), Log::WARNING);
1326 return;
1328 #else
1329 const lt::sha1_hash infoHash = (resumeData.ltAddTorrentParams.ti
1330 ? resumeData.ltAddTorrentParams.ti->info_hash()
1331 : resumeData.ltAddTorrentParams.info_hash);
1332 if (torrentID != TorrentID::fromInfoHash(infoHash))
1334 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1335 .arg(torrentID.toString()), Log::WARNING);
1336 return;
1338 #endif
1340 if (m_resumeDataStorage != context->startupStorage)
1341 needStore = true;
1343 // TODO: Remove the following upgrade code in v4.6
1344 // == BEGIN UPGRADE CODE ==
1345 if (!needStore)
1347 if (m_needUpgradeDownloadPath && isDownloadPathEnabled() && !resumeData.useAutoTMM)
1349 resumeData.downloadPath = downloadPath();
1350 needStore = true;
1353 // == END UPGRADE CODE ==
1355 if (needStore)
1356 m_resumeDataStorage->store(torrentID, resumeData);
1358 const QString category = resumeData.category;
1359 bool isCategoryRecovered = context->recoveredCategories.contains(category);
1360 if (!category.isEmpty() && (isCategoryRecovered || !m_categories.contains(category)))
1362 if (!isCategoryRecovered)
1364 if (addCategory(category))
1366 context->recoveredCategories.insert(category);
1367 isCategoryRecovered = true;
1368 LogMsg(tr("Detected inconsistent data: category is missing from the configuration file."
1369 " Category will be recovered but its settings will be reset to default."
1370 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
1372 else
1374 resumeData.category.clear();
1375 LogMsg(tr("Detected inconsistent data: invalid category. Torrent: \"%1\". Category: \"%2\"")
1376 .arg(torrentID.toString(), category), Log::WARNING);
1380 // We should check isCategoryRecovered again since the category
1381 // can be just recovered by the code above
1382 if (isCategoryRecovered && resumeData.useAutoTMM)
1384 const Path storageLocation {resumeData.ltAddTorrentParams.save_path};
1385 if ((storageLocation != categorySavePath(resumeData.category)) && (storageLocation != categoryDownloadPath(resumeData.category)))
1387 resumeData.useAutoTMM = false;
1388 resumeData.savePath = storageLocation;
1389 resumeData.downloadPath = {};
1390 LogMsg(tr("Detected mismatch between the save paths of the recovered category and the current save path of the torrent."
1391 " Torrent is now switched to Manual mode."
1392 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
1397 Algorithm::removeIf(resumeData.tags, [this, &torrentID](const QString &tag)
1399 if (hasTag(tag))
1400 return false;
1402 if (addTag(tag))
1404 LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file."
1405 " Tag will be recovered."
1406 " Torrent: \"%1\". Tag: \"%2\"").arg(torrentID.toString(), tag), Log::WARNING);
1407 return false;
1410 LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"")
1411 .arg(torrentID.toString(), tag), Log::WARNING);
1412 return true;
1415 resumeData.ltAddTorrentParams.userdata = LTClientData(new ExtensionData);
1416 #ifndef QBT_USES_LIBTORRENT2
1417 resumeData.ltAddTorrentParams.storage = customStorageConstructor;
1418 #endif
1420 qDebug() << "Starting up torrent" << torrentID.toString() << "...";
1421 m_loadingTorrents.insert(torrentID, resumeData);
1422 #ifdef QBT_USES_LIBTORRENT2
1423 if (infoHash.isHybrid())
1425 // this allows to know the being added hybrid torrent by its v1 info hash
1426 // without having yet another mapping table
1427 m_hybridTorrentsByAltID.insert(torrentIDv1, nullptr);
1429 #endif
1430 m_nativeSession->async_add_torrent(resumeData.ltAddTorrentParams);
1431 ++context->processingResumeDataCount;
1434 void SessionImpl::endStartup(ResumeSessionContext *context)
1436 if (m_resumeDataStorage != context->startupStorage)
1438 if (isQueueingSystemEnabled())
1439 saveTorrentsQueue();
1441 const Path dbPath = context->startupStorage->path();
1442 context->startupStorage->deleteLater();
1444 if (context->currentStorageType == ResumeDataStorageType::Legacy)
1446 connect(context->startupStorage, &QObject::destroyed, [dbPath]
1448 Utils::Fs::removeFile(dbPath);
1453 context->deleteLater();
1454 connect(context, &QObject::destroyed, this, [this]
1456 m_nativeSession->resume();
1457 if (m_refreshEnqueued)
1458 m_refreshEnqueued = false;
1459 else
1460 enqueueRefresh();
1462 m_statisticsLastUpdateTimer.start();
1464 // Regular saving of fastresume data
1465 connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
1466 const int saveInterval = saveResumeDataInterval();
1467 if (saveInterval > 0)
1469 m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
1470 m_resumeDataTimer->start();
1473 m_wakeupCheckTimer = new QTimer(this);
1474 connect(m_wakeupCheckTimer, &QTimer::timeout, this, [this]
1476 const auto now = QDateTime::currentDateTime();
1477 if (m_wakeupCheckTimestamp.secsTo(now) > 100)
1479 LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
1480 reannounceToAllTrackers();
1483 m_wakeupCheckTimestamp = QDateTime::currentDateTime();
1485 m_wakeupCheckTimestamp = QDateTime::currentDateTime();
1486 m_wakeupCheckTimer->start(30s);
1488 m_isRestored = true;
1489 emit startupProgressUpdated(100);
1490 emit restored();
1494 void SessionImpl::initializeNativeSession()
1496 lt::settings_pack pack = loadLTSettings();
1498 const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
1499 pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
1501 pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
1502 pack.set_str(lt::settings_pack::user_agent, USER_AGENT.toStdString());
1503 pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
1504 // Speed up exit
1505 pack.set_int(lt::settings_pack::auto_scrape_interval, 1200); // 20 minutes
1506 pack.set_int(lt::settings_pack::auto_scrape_min_interval, 900); // 15 minutes
1507 // libtorrent 1.1 enables UPnP & NAT-PMP by default
1508 // turn them off before `lt::session` ctor to avoid split second effects
1509 pack.set_bool(lt::settings_pack::enable_upnp, false);
1510 pack.set_bool(lt::settings_pack::enable_natpmp, false);
1512 #ifdef QBT_USES_LIBTORRENT2
1513 // preserve the same behavior as in earlier libtorrent versions
1514 pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
1515 #endif
1517 lt::session_params sessionParams {std::move(pack), {}};
1518 #ifdef QBT_USES_LIBTORRENT2
1519 switch (diskIOType())
1521 case DiskIOType::Posix:
1522 sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
1523 break;
1524 case DiskIOType::MMap:
1525 sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
1526 break;
1527 default:
1528 sessionParams.disk_io_constructor = customDiskIOConstructor;
1529 break;
1531 #endif
1533 #if LIBTORRENT_VERSION_NUM < 20100
1534 m_nativeSession = new lt::session(sessionParams, lt::session::paused);
1535 #else
1536 m_nativeSession = new lt::session(sessionParams);
1537 m_nativeSession->pause();
1538 #endif
1540 LogMsg(tr("Peer ID: \"%1\"").arg(QString::fromStdString(peerId)), Log::INFO);
1541 LogMsg(tr("HTTP User-Agent: \"%1\"").arg(USER_AGENT), Log::INFO);
1542 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1543 LogMsg(tr("Local Peer Discovery support: %1").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1544 LogMsg(tr("Peer Exchange (PeX) support: %1").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1545 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1546 LogMsg(tr("Encryption support: %1").arg((encryption() == 0) ? tr("ON") : ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO);
1548 m_nativeSession->set_alert_notify([this]()
1550 QMetaObject::invokeMethod(this, &SessionImpl::readAlerts, Qt::QueuedConnection);
1553 // Enabling plugins
1554 m_nativeSession->add_extension(&lt::create_smart_ban_plugin);
1555 m_nativeSession->add_extension(&lt::create_ut_metadata_plugin);
1556 if (isPeXEnabled())
1557 m_nativeSession->add_extension(&lt::create_ut_pex_plugin);
1559 auto nativeSessionExtension = std::make_shared<NativeSessionExtension>();
1560 m_nativeSession->add_extension(nativeSessionExtension);
1561 m_nativeSessionExtension = nativeSessionExtension.get();
1564 void SessionImpl::processBannedIPs(lt::ip_filter &filter)
1566 // First, import current filter
1567 for (const QString &ip : asConst(m_bannedIPs.get()))
1569 lt::error_code ec;
1570 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1571 Q_ASSERT(!ec);
1572 if (!ec)
1573 filter.add_rule(addr, addr, lt::ip_filter::blocked);
1577 void SessionImpl::initMetrics()
1579 const auto findMetricIndex = [](const char *name) -> int
1581 const int index = lt::find_metric_idx(name);
1582 Q_ASSERT(index >= 0);
1583 return index;
1586 // TODO: switch to "designated initializers" in C++20
1587 m_metricIndices.net.hasIncomingConnections = findMetricIndex("net.has_incoming_connections");
1588 m_metricIndices.net.sentPayloadBytes = findMetricIndex("net.sent_payload_bytes");
1589 m_metricIndices.net.recvPayloadBytes = findMetricIndex("net.recv_payload_bytes");
1590 m_metricIndices.net.sentBytes = findMetricIndex("net.sent_bytes");
1591 m_metricIndices.net.recvBytes = findMetricIndex("net.recv_bytes");
1592 m_metricIndices.net.sentIPOverheadBytes = findMetricIndex("net.sent_ip_overhead_bytes");
1593 m_metricIndices.net.recvIPOverheadBytes = findMetricIndex("net.recv_ip_overhead_bytes");
1594 m_metricIndices.net.sentTrackerBytes = findMetricIndex("net.sent_tracker_bytes");
1595 m_metricIndices.net.recvTrackerBytes = findMetricIndex("net.recv_tracker_bytes");
1596 m_metricIndices.net.recvRedundantBytes = findMetricIndex("net.recv_redundant_bytes");
1597 m_metricIndices.net.recvFailedBytes = findMetricIndex("net.recv_failed_bytes");
1599 m_metricIndices.peer.numPeersConnected = findMetricIndex("peer.num_peers_connected");
1600 m_metricIndices.peer.numPeersDownDisk = findMetricIndex("peer.num_peers_down_disk");
1601 m_metricIndices.peer.numPeersUpDisk = findMetricIndex("peer.num_peers_up_disk");
1603 m_metricIndices.dht.dhtBytesIn = findMetricIndex("dht.dht_bytes_in");
1604 m_metricIndices.dht.dhtBytesOut = findMetricIndex("dht.dht_bytes_out");
1605 m_metricIndices.dht.dhtNodes = findMetricIndex("dht.dht_nodes");
1607 m_metricIndices.disk.diskBlocksInUse = findMetricIndex("disk.disk_blocks_in_use");
1608 m_metricIndices.disk.numBlocksRead = findMetricIndex("disk.num_blocks_read");
1609 #ifndef QBT_USES_LIBTORRENT2
1610 m_metricIndices.disk.numBlocksCacheHits = findMetricIndex("disk.num_blocks_cache_hits");
1611 #endif
1612 m_metricIndices.disk.writeJobs = findMetricIndex("disk.num_write_ops");
1613 m_metricIndices.disk.readJobs = findMetricIndex("disk.num_read_ops");
1614 m_metricIndices.disk.hashJobs = findMetricIndex("disk.num_blocks_hashed");
1615 m_metricIndices.disk.queuedDiskJobs = findMetricIndex("disk.queued_disk_jobs");
1616 m_metricIndices.disk.diskJobTime = findMetricIndex("disk.disk_job_time");
1619 lt::settings_pack SessionImpl::loadLTSettings() const
1621 lt::settings_pack settingsPack;
1623 const lt::alert_category_t alertMask = lt::alert::error_notification
1624 | lt::alert::file_progress_notification
1625 | lt::alert::ip_block_notification
1626 | lt::alert::peer_notification
1627 | (isPerformanceWarningEnabled() ? lt::alert::performance_warning : lt::alert_category_t())
1628 | lt::alert::port_mapping_notification
1629 | lt::alert::status_notification
1630 | lt::alert::storage_notification
1631 | lt::alert::tracker_notification;
1632 settingsPack.set_int(lt::settings_pack::alert_mask, alertMask);
1634 settingsPack.set_int(lt::settings_pack::connection_speed, connectionSpeed());
1636 // from libtorrent doc:
1637 // It will not take affect until the listen_interfaces settings is updated
1638 settingsPack.set_int(lt::settings_pack::send_socket_buffer_size, socketSendBufferSize());
1639 settingsPack.set_int(lt::settings_pack::recv_socket_buffer_size, socketReceiveBufferSize());
1640 settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
1642 applyNetworkInterfacesSettings(settingsPack);
1644 settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
1645 settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
1647 // The most secure, rc4 only so that all streams are encrypted
1648 settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
1649 settingsPack.set_bool(lt::settings_pack::prefer_rc4, true);
1650 switch (encryption())
1652 case 0: // Enabled
1653 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_enabled);
1654 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_enabled);
1655 break;
1656 case 1: // Forced
1657 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_forced);
1658 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_forced);
1659 break;
1660 default: // Disabled
1661 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_disabled);
1662 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_disabled);
1665 settingsPack.set_int(lt::settings_pack::active_checking, maxActiveCheckingTorrents());
1667 // I2P
1668 if (isI2PEnabled())
1670 settingsPack.set_str(lt::settings_pack::i2p_hostname, I2PAddress().toStdString());
1671 settingsPack.set_int(lt::settings_pack::i2p_port, I2PPort());
1672 settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, I2PMixedMode());
1674 else
1676 settingsPack.set_str(lt::settings_pack::i2p_hostname, "");
1677 settingsPack.set_int(lt::settings_pack::i2p_port, 0);
1678 settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, false);
1681 // proxy
1682 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
1683 if (Preferences::instance()->useProxyForBT())
1685 const auto *proxyManager = Net::ProxyConfigurationManager::instance();
1686 const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
1688 switch (proxyConfig.type)
1690 case Net::ProxyType::SOCKS4:
1691 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks4);
1692 break;
1694 case Net::ProxyType::HTTP:
1695 if (proxyConfig.authEnabled)
1696 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http_pw);
1697 else
1698 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http);
1699 break;
1701 case Net::ProxyType::SOCKS5:
1702 if (proxyConfig.authEnabled)
1703 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5_pw);
1704 else
1705 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
1706 break;
1708 default:
1709 break;
1712 settingsPack.set_str(lt::settings_pack::proxy_hostname, proxyConfig.ip.toStdString());
1713 settingsPack.set_int(lt::settings_pack::proxy_port, proxyConfig.port);
1715 if (proxyConfig.authEnabled)
1717 settingsPack.set_str(lt::settings_pack::proxy_username, proxyConfig.username.toStdString());
1718 settingsPack.set_str(lt::settings_pack::proxy_password, proxyConfig.password.toStdString());
1721 settingsPack.set_bool(lt::settings_pack::proxy_peer_connections, isProxyPeerConnectionsEnabled());
1722 settingsPack.set_bool(lt::settings_pack::proxy_hostnames, proxyConfig.hostnameLookupEnabled);
1725 settingsPack.set_bool(lt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
1726 settingsPack.set_bool(lt::settings_pack::announce_to_all_tiers, announceToAllTiers());
1728 settingsPack.set_int(lt::settings_pack::peer_turnover, peerTurnover());
1729 settingsPack.set_int(lt::settings_pack::peer_turnover_cutoff, peerTurnoverCutoff());
1730 settingsPack.set_int(lt::settings_pack::peer_turnover_interval, peerTurnoverInterval());
1732 settingsPack.set_int(lt::settings_pack::max_out_request_queue, requestQueueSize());
1734 settingsPack.set_int(lt::settings_pack::aio_threads, asyncIOThreads());
1735 #ifdef QBT_USES_LIBTORRENT2
1736 settingsPack.set_int(lt::settings_pack::hashing_threads, hashingThreads());
1737 #endif
1738 settingsPack.set_int(lt::settings_pack::file_pool_size, filePoolSize());
1740 const int checkingMemUsageSize = checkingMemUsage() * 64;
1741 settingsPack.set_int(lt::settings_pack::checking_mem_usage, checkingMemUsageSize);
1743 #ifndef QBT_USES_LIBTORRENT2
1744 const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
1745 settingsPack.set_int(lt::settings_pack::cache_size, cacheSize);
1746 settingsPack.set_int(lt::settings_pack::cache_expiry, diskCacheTTL());
1747 #endif
1749 settingsPack.set_int(lt::settings_pack::max_queued_disk_bytes, diskQueueSize());
1751 switch (diskIOReadMode())
1753 case DiskIOReadMode::DisableOSCache:
1754 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::disable_os_cache);
1755 break;
1756 case DiskIOReadMode::EnableOSCache:
1757 default:
1758 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::enable_os_cache);
1759 break;
1762 switch (diskIOWriteMode())
1764 case DiskIOWriteMode::DisableOSCache:
1765 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::disable_os_cache);
1766 break;
1767 case DiskIOWriteMode::EnableOSCache:
1768 default:
1769 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::enable_os_cache);
1770 break;
1771 #ifdef QBT_USES_LIBTORRENT2
1772 case DiskIOWriteMode::WriteThrough:
1773 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::write_through);
1774 break;
1775 #endif
1778 #ifndef QBT_USES_LIBTORRENT2
1779 settingsPack.set_bool(lt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
1780 settingsPack.set_bool(lt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
1781 #endif
1783 settingsPack.set_bool(lt::settings_pack::piece_extent_affinity, usePieceExtentAffinity());
1785 settingsPack.set_int(lt::settings_pack::suggest_mode, isSuggestModeEnabled()
1786 ? lt::settings_pack::suggest_read_cache : lt::settings_pack::no_piece_suggestions);
1788 settingsPack.set_int(lt::settings_pack::send_buffer_watermark, sendBufferWatermark() * 1024);
1789 settingsPack.set_int(lt::settings_pack::send_buffer_low_watermark, sendBufferLowWatermark() * 1024);
1790 settingsPack.set_int(lt::settings_pack::send_buffer_watermark_factor, sendBufferWatermarkFactor());
1792 settingsPack.set_bool(lt::settings_pack::anonymous_mode, isAnonymousModeEnabled());
1794 // Queueing System
1795 if (isQueueingSystemEnabled())
1797 settingsPack.set_int(lt::settings_pack::active_downloads, maxActiveDownloads());
1798 settingsPack.set_int(lt::settings_pack::active_limit, maxActiveTorrents());
1799 settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
1800 settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
1801 settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
1802 settingsPack.set_int(lt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
1803 settingsPack.set_int(lt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
1805 else
1807 settingsPack.set_int(lt::settings_pack::active_downloads, -1);
1808 settingsPack.set_int(lt::settings_pack::active_seeds, -1);
1809 settingsPack.set_int(lt::settings_pack::active_limit, -1);
1811 settingsPack.set_int(lt::settings_pack::active_tracker_limit, -1);
1812 settingsPack.set_int(lt::settings_pack::active_dht_limit, -1);
1813 settingsPack.set_int(lt::settings_pack::active_lsd_limit, -1);
1814 settingsPack.set_int(lt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
1816 // Outgoing ports
1817 settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
1818 settingsPack.set_int(lt::settings_pack::num_outgoing_ports, (outgoingPortsMax() - outgoingPortsMin()));
1819 // UPnP lease duration
1820 settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
1821 // Type of service
1822 settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
1823 // Include overhead in transfer limits
1824 settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
1825 // IP address to announce to trackers
1826 settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
1827 // Max concurrent HTTP announces
1828 settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
1829 // Stop tracker timeout
1830 settingsPack.set_int(lt::settings_pack::stop_tracker_timeout, stopTrackerTimeout());
1831 // * Max connections limit
1832 settingsPack.set_int(lt::settings_pack::connections_limit, maxConnections());
1833 // * Global max upload slots
1834 settingsPack.set_int(lt::settings_pack::unchoke_slots_limit, maxUploads());
1835 // uTP
1836 switch (btProtocol())
1838 case BTProtocol::Both:
1839 default:
1840 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1841 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1842 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1843 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1844 break;
1846 case BTProtocol::TCP:
1847 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1848 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1849 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, false);
1850 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
1851 break;
1853 case BTProtocol::UTP:
1854 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, false);
1855 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, false);
1856 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1857 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1858 break;
1861 switch (utpMixedMode())
1863 case MixedModeAlgorithm::TCP:
1864 default:
1865 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::prefer_tcp);
1866 break;
1867 case MixedModeAlgorithm::Proportional:
1868 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::peer_proportional);
1869 break;
1872 settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
1874 settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
1876 settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
1878 settingsPack.set_bool(lt::settings_pack::ssrf_mitigation, isSSRFMitigationEnabled());
1880 settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
1882 settingsPack.set_bool(lt::settings_pack::apply_ip_filter_to_trackers, isTrackerFilteringEnabled());
1884 settingsPack.set_bool(lt::settings_pack::enable_dht, isDHTEnabled());
1885 if (isDHTEnabled())
1886 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");
1887 settingsPack.set_bool(lt::settings_pack::enable_lsd, isLSDEnabled());
1889 switch (chokingAlgorithm())
1891 case ChokingAlgorithm::FixedSlots:
1892 default:
1893 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::fixed_slots_choker);
1894 break;
1895 case ChokingAlgorithm::RateBased:
1896 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::rate_based_choker);
1897 break;
1900 switch (seedChokingAlgorithm())
1902 case SeedChokingAlgorithm::RoundRobin:
1903 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::round_robin);
1904 break;
1905 case SeedChokingAlgorithm::FastestUpload:
1906 default:
1907 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::fastest_upload);
1908 break;
1909 case SeedChokingAlgorithm::AntiLeech:
1910 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
1911 break;
1914 return settingsPack;
1917 void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const
1919 if (m_listenInterfaceConfigured)
1920 return;
1922 if (port() > 0) // user has specified port number
1923 settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);
1925 QStringList endpoints;
1926 QStringList outgoingInterfaces;
1927 const QString portString = u':' + QString::number(port());
1929 for (const QString &ip : asConst(getListeningIPs()))
1931 const QHostAddress addr {ip};
1932 if (!addr.isNull())
1934 const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
1935 const QString ip = isIPv6
1936 ? Utils::Net::canonicalIPv6Addr(addr).toString()
1937 : addr.toString();
1939 endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString);
1941 if ((ip != u"0.0.0.0") && (ip != u"::"))
1942 outgoingInterfaces << ip;
1944 else
1946 // ip holds an interface name
1947 #ifdef Q_OS_WIN
1948 // On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
1949 // the interface's LUID and not the GUID.
1950 // Libtorrent expects GUIDs for the 'listen_interfaces' setting.
1951 const QString guid = convertIfaceNameToGuid(ip);
1952 if (!guid.isEmpty())
1954 endpoints << (guid + portString);
1955 outgoingInterfaces << guid;
1957 else
1959 LogMsg(tr("Could not find GUID of network interface. Interface: \"%1\"").arg(ip), Log::WARNING);
1960 // Since we can't get the GUID, we'll pass the interface name instead.
1961 // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
1962 endpoints << (ip + portString);
1963 outgoingInterfaces << ip;
1965 #else
1966 endpoints << (ip + portString);
1967 outgoingInterfaces << ip;
1968 #endif
1972 const QString finalEndpoints = endpoints.join(u',');
1973 settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
1974 LogMsg(tr("Trying to listen on the following list of IP addresses: \"%1\"").arg(finalEndpoints));
1976 settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(u',').toStdString());
1977 m_listenInterfaceConfigured = true;
1980 void SessionImpl::configurePeerClasses()
1982 lt::ip_filter f;
1983 // lt::make_address("255.255.255.255") crashes on some people's systems
1984 // so instead we use address_v4::broadcast()
1985 // Proactively do the same for 0.0.0.0 and address_v4::any()
1986 f.add_rule(lt::address_v4::any()
1987 , lt::address_v4::broadcast()
1988 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
1990 // IPv6 may not be available on OS and the parsing
1991 // would result in an exception -> abnormal program termination
1992 // Affects Windows XP
1995 f.add_rule(lt::address_v6::any()
1996 , lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1997 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
1999 catch (const std::exception &) {}
2001 if (ignoreLimitsOnLAN())
2003 // local networks
2004 f.add_rule(lt::make_address("10.0.0.0")
2005 , lt::make_address("10.255.255.255")
2006 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2007 f.add_rule(lt::make_address("172.16.0.0")
2008 , lt::make_address("172.31.255.255")
2009 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2010 f.add_rule(lt::make_address("192.168.0.0")
2011 , lt::make_address("192.168.255.255")
2012 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2013 // link local
2014 f.add_rule(lt::make_address("169.254.0.0")
2015 , lt::make_address("169.254.255.255")
2016 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2017 // loopback
2018 f.add_rule(lt::make_address("127.0.0.0")
2019 , lt::make_address("127.255.255.255")
2020 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2022 // IPv6 may not be available on OS and the parsing
2023 // would result in an exception -> abnormal program termination
2024 // Affects Windows XP
2027 // link local
2028 f.add_rule(lt::make_address("fe80::")
2029 , lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2030 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2031 // unique local addresses
2032 f.add_rule(lt::make_address("fc00::")
2033 , lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2034 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2035 // loopback
2036 f.add_rule(lt::address_v6::loopback()
2037 , lt::address_v6::loopback()
2038 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2040 catch (const std::exception &) {}
2042 m_nativeSession->set_peer_class_filter(f);
2044 lt::peer_class_type_filter peerClassTypeFilter;
2045 peerClassTypeFilter.add(lt::peer_class_type_filter::tcp_socket, lt::session::tcp_peer_class_id);
2046 peerClassTypeFilter.add(lt::peer_class_type_filter::ssl_tcp_socket, lt::session::tcp_peer_class_id);
2047 peerClassTypeFilter.add(lt::peer_class_type_filter::i2p_socket, lt::session::tcp_peer_class_id);
2048 if (!isUTPRateLimited())
2050 peerClassTypeFilter.disallow(lt::peer_class_type_filter::utp_socket
2051 , lt::session::global_peer_class_id);
2052 peerClassTypeFilter.disallow(lt::peer_class_type_filter::ssl_utp_socket
2053 , lt::session::global_peer_class_id);
2055 m_nativeSession->set_peer_class_type_filter(peerClassTypeFilter);
2058 void SessionImpl::enableTracker(const bool enable)
2060 const QString profile = u"embeddedTracker"_qs;
2061 auto *portForwarder = Net::PortForwarder::instance();
2063 if (enable)
2065 if (!m_tracker)
2066 m_tracker = new Tracker(this);
2068 m_tracker->start();
2070 const auto *pref = Preferences::instance();
2071 if (pref->isTrackerPortForwardingEnabled())
2072 portForwarder->setPorts(profile, {static_cast<quint16>(pref->getTrackerPort())});
2073 else
2074 portForwarder->removePorts(profile);
2076 else
2078 delete m_tracker;
2080 portForwarder->removePorts(profile);
2084 void SessionImpl::enableBandwidthScheduler()
2086 if (!m_bwScheduler)
2088 m_bwScheduler = new BandwidthScheduler(this);
2089 connect(m_bwScheduler.data(), &BandwidthScheduler::bandwidthLimitRequested
2090 , this, &SessionImpl::setAltGlobalSpeedLimitEnabled);
2092 m_bwScheduler->start();
2095 void SessionImpl::populateAdditionalTrackers()
2097 m_additionalTrackerList.clear();
2099 const QString trackers = additionalTrackers();
2100 for (QStringView tracker : asConst(QStringView(trackers).split(u'\n')))
2102 tracker = tracker.trimmed();
2103 if (!tracker.isEmpty())
2104 m_additionalTrackerList.append({tracker.toString()});
2108 void SessionImpl::processShareLimits()
2110 qDebug("Processing share limits...");
2112 // We shouldn't iterate over `m_torrents` in the loop below
2113 // since `deleteTorrent()` modifies it indirectly
2114 const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
2115 for (TorrentImpl *const torrent : torrents)
2117 if (torrent->isFinished() && !torrent->isForced())
2119 if (torrent->ratioLimit() != Torrent::NO_RATIO_LIMIT)
2121 const qreal ratio = torrent->realRatio();
2122 qreal ratioLimit = torrent->ratioLimit();
2123 if (ratioLimit == Torrent::USE_GLOBAL_RATIO)
2124 // If Global Max Ratio is really set...
2125 ratioLimit = globalMaxRatio();
2127 if (ratioLimit >= 0)
2129 qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
2131 if ((ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
2133 const QString description = tr("Torrent reached the share ratio limit.");
2134 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2136 if (m_maxRatioAction == Remove)
2138 LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Removed torrent."), torrentName));
2139 deleteTorrent(torrent->id());
2141 else if (m_maxRatioAction == DeleteFiles)
2143 LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Removed torrent and deleted its content."), torrentName));
2144 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
2146 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
2148 torrent->pause();
2149 LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Torrent paused."), torrentName));
2151 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
2153 torrent->setSuperSeeding(true);
2154 LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Super seeding enabled."), torrentName));
2157 continue;
2162 if (torrent->seedingTimeLimit() != Torrent::NO_SEEDING_TIME_LIMIT)
2164 const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
2165 int seedingTimeLimit = torrent->seedingTimeLimit();
2166 if (seedingTimeLimit == Torrent::USE_GLOBAL_SEEDING_TIME)
2168 // If Global Seeding Time Limit is really set...
2169 seedingTimeLimit = globalMaxSeedingMinutes();
2172 if (seedingTimeLimit >= 0)
2174 if ((seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
2176 const QString description = tr("Torrent reached the seeding time limit.");
2177 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2179 if (m_maxRatioAction == Remove)
2181 LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Removed torrent."), torrentName));
2182 deleteTorrent(torrent->id());
2184 else if (m_maxRatioAction == DeleteFiles)
2186 LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Removed torrent and deleted its content."), torrentName));
2187 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
2189 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
2191 torrent->pause();
2192 LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Torrent paused."), torrentName));
2194 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
2196 torrent->setSuperSeeding(true);
2197 LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Super seeding enabled."), torrentName));
2206 // Add to BitTorrent session the downloaded torrent file
2207 void SessionImpl::handleDownloadFinished(const Net::DownloadResult &result)
2209 switch (result.status)
2211 case Net::DownloadStatus::Success:
2212 emit downloadFromUrlFinished(result.url);
2213 if (const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::load(result.data); loadResult)
2214 addTorrent(loadResult.value(), m_downloadedTorrents.take(result.url));
2215 else
2216 LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(loadResult.error()), Log::WARNING);
2217 break;
2218 case Net::DownloadStatus::RedirectedToMagnet:
2219 emit downloadFromUrlFinished(result.url);
2220 addTorrent(MagnetUri(result.magnet), m_downloadedTorrents.take(result.url));
2221 break;
2222 default:
2223 emit downloadFromUrlFailed(result.url, result.errorString);
2227 void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
2229 TorrentImpl *torrent = m_torrents.value(id);
2230 if (torrent)
2232 torrent->fileSearchFinished(savePath, fileNames);
2233 return;
2236 const auto loadingTorrentsIter = m_loadingTorrents.find(id);
2237 if (loadingTorrentsIter != m_loadingTorrents.end())
2239 LoadTorrentParams &params = loadingTorrentsIter.value();
2240 lt::add_torrent_params &p = params.ltAddTorrentParams;
2242 p.save_path = savePath.toString().toStdString();
2243 const TorrentInfo torrentInfo {*p.ti};
2244 const auto nativeIndexes = torrentInfo.nativeIndexes();
2245 for (int i = 0; i < fileNames.size(); ++i)
2246 p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString();
2248 m_nativeSession->async_add_torrent(p);
2252 Torrent *SessionImpl::getTorrent(const TorrentID &id) const
2254 return m_torrents.value(id);
2257 Torrent *SessionImpl::findTorrent(const InfoHash &infoHash) const
2259 const auto id = TorrentID::fromInfoHash(infoHash);
2260 if (Torrent *torrent = m_torrents.value(id); torrent)
2261 return torrent;
2263 if (!infoHash.isHybrid())
2264 return m_hybridTorrentsByAltID.value(id);
2266 // alternative ID can be useful to find existing torrent
2267 // in case if hybrid torrent was added by v1 info hash
2268 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
2269 return m_torrents.value(altID);
2272 void SessionImpl::banIP(const QString &ip)
2274 if (m_bannedIPs.get().contains(ip))
2275 return;
2277 lt::error_code ec;
2278 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
2279 Q_ASSERT(!ec);
2280 if (ec)
2281 return;
2283 invokeAsync([session = m_nativeSession, addr]
2285 lt::ip_filter filter = session->get_ip_filter();
2286 filter.add_rule(addr, addr, lt::ip_filter::blocked);
2287 session->set_ip_filter(std::move(filter));
2290 QStringList bannedIPs = m_bannedIPs;
2291 bannedIPs.append(ip);
2292 bannedIPs.sort();
2293 m_bannedIPs = bannedIPs;
2296 // Delete a torrent from the session, given its hash
2297 // and from the disk, if the corresponding deleteOption is chosen
2298 bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
2300 TorrentImpl *const torrent = m_torrents.take(id);
2301 if (!torrent) return false;
2303 qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
2304 emit torrentAboutToBeRemoved(torrent);
2306 if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
2307 m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
2309 // Remove it from session
2310 if (deleteOption == DeleteTorrent)
2312 m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
2314 const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
2315 const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
2316 , [&nativeHandle](const MoveStorageJob &job)
2318 return job.torrentHandle == nativeHandle;
2320 if (iter != m_moveStorageQueue.end())
2322 // We shouldn't actually remove torrent until existing "move storage jobs" are done
2323 torrentQueuePositionBottom(nativeHandle);
2324 nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
2325 nativeHandle.pause();
2327 else
2329 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
2332 else
2334 m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
2336 if (m_moveStorageQueue.size() > 1)
2338 // Delete "move storage job" for the deleted torrent
2339 // (note: we shouldn't delete active job)
2340 const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
2341 , [torrent](const MoveStorageJob &job)
2343 return job.torrentHandle == torrent->nativeHandle();
2345 if (iter != m_moveStorageQueue.end())
2346 m_moveStorageQueue.erase(iter);
2349 m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
2352 // Remove it from torrent resume directory
2353 m_resumeDataStorage->remove(torrent->id());
2355 delete torrent;
2356 return true;
2359 bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
2361 const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
2362 if (downloadedMetadataIter == m_downloadedMetadata.end())
2363 return false;
2365 const lt::torrent_handle nativeHandle = downloadedMetadataIter.value();
2366 #ifdef QBT_USES_LIBTORRENT2
2367 const InfoHash infoHash {nativeHandle.info_hashes()};
2368 if (infoHash.isHybrid())
2370 // if magnet link was hybrid initially then it is indexed also by v1 info hash
2371 // so we need to remove both entries
2372 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
2373 m_downloadedMetadata.remove((altID == downloadedMetadataIter.key()) ? id : altID);
2375 #endif
2376 m_downloadedMetadata.erase(downloadedMetadataIter);
2377 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
2378 return true;
2381 void SessionImpl::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
2383 using ElementType = std::pair<int, const TorrentImpl *>;
2384 std::priority_queue<ElementType
2385 , std::vector<ElementType>
2386 , std::greater<ElementType>> torrentQueue;
2388 // Sort torrents by queue position
2389 for (const TorrentID &id : ids)
2391 const TorrentImpl *torrent = m_torrents.value(id);
2392 if (!torrent) continue;
2393 if (const int position = torrent->queuePosition(); position >= 0)
2394 torrentQueue.emplace(position, torrent);
2397 // Increase torrents queue position (starting with the one in the highest queue position)
2398 while (!torrentQueue.empty())
2400 const TorrentImpl *torrent = torrentQueue.top().second;
2401 torrentQueuePositionUp(torrent->nativeHandle());
2402 torrentQueue.pop();
2405 m_torrentsQueueChanged = true;
2408 void SessionImpl::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
2410 using ElementType = std::pair<int, const TorrentImpl *>;
2411 std::priority_queue<ElementType> torrentQueue;
2413 // Sort torrents by queue position
2414 for (const TorrentID &id : ids)
2416 const TorrentImpl *torrent = m_torrents.value(id);
2417 if (!torrent) continue;
2418 if (const int position = torrent->queuePosition(); position >= 0)
2419 torrentQueue.emplace(position, torrent);
2422 // Decrease torrents queue position (starting with the one in the lowest queue position)
2423 while (!torrentQueue.empty())
2425 const TorrentImpl *torrent = torrentQueue.top().second;
2426 torrentQueuePositionDown(torrent->nativeHandle());
2427 torrentQueue.pop();
2430 for (const lt::torrent_handle &torrentHandle : asConst(m_downloadedMetadata))
2431 torrentQueuePositionBottom(torrentHandle);
2433 m_torrentsQueueChanged = true;
2436 void SessionImpl::topTorrentsQueuePos(const QVector<TorrentID> &ids)
2438 using ElementType = std::pair<int, const TorrentImpl *>;
2439 std::priority_queue<ElementType> torrentQueue;
2441 // Sort torrents by queue position
2442 for (const TorrentID &id : ids)
2444 const TorrentImpl *torrent = m_torrents.value(id);
2445 if (!torrent) continue;
2446 if (const int position = torrent->queuePosition(); position >= 0)
2447 torrentQueue.emplace(position, torrent);
2450 // Top torrents queue position (starting with the one in the lowest queue position)
2451 while (!torrentQueue.empty())
2453 const TorrentImpl *torrent = torrentQueue.top().second;
2454 torrentQueuePositionTop(torrent->nativeHandle());
2455 torrentQueue.pop();
2458 m_torrentsQueueChanged = true;
2461 void SessionImpl::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
2463 using ElementType = std::pair<int, const TorrentImpl *>;
2464 std::priority_queue<ElementType
2465 , std::vector<ElementType>
2466 , std::greater<ElementType>> torrentQueue;
2468 // Sort torrents by queue position
2469 for (const TorrentID &id : ids)
2471 const TorrentImpl *torrent = m_torrents.value(id);
2472 if (!torrent) continue;
2473 if (const int position = torrent->queuePosition(); position >= 0)
2474 torrentQueue.emplace(position, torrent);
2477 // Bottom torrents queue position (starting with the one in the highest queue position)
2478 while (!torrentQueue.empty())
2480 const TorrentImpl *torrent = torrentQueue.top().second;
2481 torrentQueuePositionBottom(torrent->nativeHandle());
2482 torrentQueue.pop();
2485 for (const lt::torrent_handle &torrentHandle : asConst(m_downloadedMetadata))
2486 torrentQueuePositionBottom(torrentHandle);
2488 m_torrentsQueueChanged = true;
2491 void SessionImpl::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
2493 if (m_needSaveResumeDataTorrents.empty())
2495 QMetaObject::invokeMethod(this, [this]()
2497 for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
2499 TorrentImpl *torrent = m_torrents.value(torrentID);
2500 if (torrent)
2501 torrent->saveResumeData();
2503 m_needSaveResumeDataTorrents.clear();
2504 }, Qt::QueuedConnection);
2507 m_needSaveResumeDataTorrents.insert(torrent->id());
2510 void SessionImpl::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
2512 qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
2513 ++m_numResumeData;
2516 void SessionImpl::handleTorrentSaveResumeDataFailed(const TorrentImpl *torrent)
2518 Q_UNUSED(torrent);
2519 --m_numResumeData;
2522 QVector<Torrent *> SessionImpl::torrents() const
2524 QVector<Torrent *> result;
2525 result.reserve(m_torrents.size());
2526 for (TorrentImpl *torrent : asConst(m_torrents))
2527 result << torrent;
2529 return result;
2532 qsizetype SessionImpl::torrentsCount() const
2534 return m_torrents.size();
2537 bool SessionImpl::addTorrent(const QString &source, const AddTorrentParams &params)
2539 // `source`: .torrent file path/url or magnet uri
2541 if (!isRestored())
2542 return false;
2544 if (Net::DownloadManager::hasSupportedScheme(source))
2546 LogMsg(tr("Downloading torrent, please wait... Source: \"%1\"").arg(source));
2547 // Launch downloader
2548 Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE)
2549 , Preferences::instance()->useProxyForGeneralPurposes(), this, &SessionImpl::handleDownloadFinished);
2550 m_downloadedTorrents[source] = params;
2551 return true;
2554 const MagnetUri magnetUri {source};
2555 if (magnetUri.isValid())
2556 return addTorrent(magnetUri, params);
2558 const Path path {source};
2559 TorrentFileGuard guard {path};
2560 const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(path);
2561 if (!loadResult)
2563 LogMsg(tr("Failed to load torrent. Source: \"%1\". Reason: \"%2\"").arg(source, loadResult.error()), Log::WARNING);
2564 return false;
2567 guard.markAsAddedToSession();
2568 return addTorrent(loadResult.value(), params);
2571 bool SessionImpl::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &params)
2573 if (!isRestored())
2574 return false;
2576 if (!magnetUri.isValid())
2577 return false;
2579 return addTorrent_impl(magnetUri, params);
2582 bool SessionImpl::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams &params)
2584 if (!isRestored())
2585 return false;
2587 return addTorrent_impl(torrentInfo, params);
2590 LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &addTorrentParams)
2592 LoadTorrentParams loadTorrentParams;
2594 loadTorrentParams.name = addTorrentParams.name;
2595 loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
2596 loadTorrentParams.hasFinishedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
2597 loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
2598 loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged);
2599 loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused());
2600 loadTorrentParams.stopCondition = addTorrentParams.stopCondition.value_or(torrentStopCondition());
2601 loadTorrentParams.addToQueueTop = addTorrentParams.addToQueueTop.value_or(isAddTorrentToQueueTop());
2602 loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
2603 loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
2605 const QString category = addTorrentParams.category;
2606 if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
2607 loadTorrentParams.category = u""_qs;
2608 else
2609 loadTorrentParams.category = category;
2611 const auto defaultSavePath = suggestedSavePath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
2612 const auto defaultDownloadPath = suggestedDownloadPath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
2614 loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(!isAutoTMMDisabledByDefault());
2616 if (!addTorrentParams.useAutoTMM.has_value())
2618 // Default TMM settings
2620 if (!loadTorrentParams.useAutoTMM)
2622 loadTorrentParams.savePath = defaultSavePath;
2623 if (isDownloadPathEnabled())
2624 loadTorrentParams.downloadPath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath());
2627 else
2629 // Overridden TMM settings
2631 if (!loadTorrentParams.useAutoTMM)
2633 if (addTorrentParams.savePath.isAbsolute())
2634 loadTorrentParams.savePath = addTorrentParams.savePath;
2635 else
2636 loadTorrentParams.savePath = defaultSavePath / addTorrentParams.savePath;
2638 if (!addTorrentParams.useDownloadPath.has_value())
2640 // Default "Download path" settings
2642 if (isDownloadPathEnabled())
2643 loadTorrentParams.downloadPath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath());
2645 else if (addTorrentParams.useDownloadPath.value())
2647 // Overridden "Download path" settings
2649 if (addTorrentParams.downloadPath.isAbsolute())
2651 loadTorrentParams.downloadPath = addTorrentParams.downloadPath;
2653 else
2655 const Path basePath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath());
2656 loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath;
2662 for (const QString &tag : addTorrentParams.tags)
2664 if (hasTag(tag) || addTag(tag))
2665 loadTorrentParams.tags.insert(tag);
2668 return loadTorrentParams;
2671 // Add a torrent to the BitTorrent session
2672 bool SessionImpl::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams)
2674 Q_ASSERT(isRestored());
2676 const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
2677 const auto infoHash = (hasMetadata ? std::get<TorrentInfo>(source).infoHash() : std::get<MagnetUri>(source).infoHash());
2678 const auto id = TorrentID::fromInfoHash(infoHash);
2680 // alternative ID can be useful to find existing torrent in case if hybrid torrent was added by v1 info hash
2681 const auto altID = (infoHash.isHybrid() ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
2683 // We should not add the torrent if it is already
2684 // processed or is pending to add to session
2685 if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
2686 return false;
2688 if (Torrent *torrent = findTorrent(infoHash); torrent)
2690 if (hasMetadata)
2692 // Trying to set metadata to existing torrent in case if it has none
2693 torrent->setMetadata(std::get<TorrentInfo>(source));
2696 return false;
2699 // It looks illogical that we don't just use an existing handle,
2700 // but as previous experience has shown, it actually creates unnecessary
2701 // problems and unwanted behavior due to the fact that it was originally
2702 // added with parameters other than those provided by the user.
2703 cancelDownloadMetadata(id);
2704 if (infoHash.isHybrid())
2705 cancelDownloadMetadata(altID);
2707 LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams);
2708 lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
2710 bool isFindingIncompleteFiles = false;
2712 const bool useAutoTMM = loadTorrentParams.useAutoTMM;
2713 const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
2715 if (hasMetadata)
2717 const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
2719 Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
2721 PathList filePaths = addTorrentParams.filePaths;
2722 if (filePaths.isEmpty())
2724 filePaths = torrentInfo.filePaths();
2725 if (loadTorrentParams.contentLayout != TorrentContentLayout::Original)
2727 const Path originalRootFolder = Path::findRootFolder(filePaths);
2728 const auto originalContentLayout = (originalRootFolder.isEmpty()
2729 ? TorrentContentLayout::NoSubfolder
2730 : TorrentContentLayout::Subfolder);
2731 if (loadTorrentParams.contentLayout != originalContentLayout)
2733 if (loadTorrentParams.contentLayout == TorrentContentLayout::NoSubfolder)
2734 Path::stripRootFolder(filePaths);
2735 else
2736 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2741 // if torrent name wasn't explicitly set we handle the case of
2742 // initial renaming of torrent content and rename torrent accordingly
2743 if (loadTorrentParams.name.isEmpty())
2745 QString contentName = Path::findRootFolder(filePaths).toString();
2746 if (contentName.isEmpty() && (filePaths.size() == 1))
2747 contentName = filePaths.at(0).filename();
2749 if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
2750 loadTorrentParams.name = contentName;
2753 if (!loadTorrentParams.hasFinishedStatus)
2755 const Path actualDownloadPath = useAutoTMM
2756 ? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
2757 findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
2758 isFindingIncompleteFiles = true;
2761 const auto nativeIndexes = torrentInfo.nativeIndexes();
2762 if (!isFindingIncompleteFiles)
2764 for (int index = 0; index < filePaths.size(); ++index)
2765 p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
2768 Q_ASSERT(p.file_priorities.empty());
2769 Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
2771 const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2772 // Use qBittorrent default priority rather than libtorrent's (4)
2773 p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2775 if (addTorrentParams.filePriorities.isEmpty())
2777 if (isExcludedFileNamesEnabled())
2779 // Check file name blacklist when priorities are not explicitly set
2780 for (int i = 0; i < filePaths.size(); ++i)
2782 if (isFilenameExcluded(filePaths.at(i).filename()))
2783 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = lt::dont_download;
2787 else
2789 for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
2790 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
2793 p.ti = torrentInfo.nativeInfo();
2795 else
2797 const MagnetUri &magnetUri = std::get<MagnetUri>(source);
2798 p = magnetUri.addTorrentParams();
2800 if (loadTorrentParams.name.isEmpty() && !p.name.empty())
2801 loadTorrentParams.name = QString::fromStdString(p.name);
2804 p.save_path = actualSavePath.toString().toStdString();
2806 if (isAddTrackersEnabled() && !(hasMetadata && p.ti->priv()))
2808 p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
2809 p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
2810 p.tracker_tiers.resize(p.trackers.size(), 0);
2811 for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
2813 p.trackers.push_back(trackerEntry.url.toStdString());
2814 p.tracker_tiers.push_back(trackerEntry.tier);
2818 p.upload_limit = addTorrentParams.uploadLimit;
2819 p.download_limit = addTorrentParams.downloadLimit;
2821 // Preallocation mode
2822 p.storage_mode = isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse;
2824 if (addTorrentParams.sequential)
2825 p.flags |= lt::torrent_flags::sequential_download;
2826 else
2827 p.flags &= ~lt::torrent_flags::sequential_download;
2829 // Seeding mode
2830 // Skip checking and directly start seeding
2831 if (addTorrentParams.skipChecking)
2832 p.flags |= lt::torrent_flags::seed_mode;
2833 else
2834 p.flags &= ~lt::torrent_flags::seed_mode;
2836 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::AutoManaged))
2837 p.flags |= lt::torrent_flags::paused;
2838 else
2839 p.flags &= ~lt::torrent_flags::paused;
2840 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::Forced))
2841 p.flags &= ~lt::torrent_flags::auto_managed;
2842 else
2843 p.flags |= lt::torrent_flags::auto_managed;
2845 p.flags |= lt::torrent_flags::duplicate_is_error;
2847 p.added_time = std::time(nullptr);
2849 // Limits
2850 p.max_connections = maxConnectionsPerTorrent();
2851 p.max_uploads = maxUploadsPerTorrent();
2853 p.userdata = LTClientData(new ExtensionData);
2854 #ifndef QBT_USES_LIBTORRENT2
2855 p.storage = customStorageConstructor;
2856 #endif
2858 m_loadingTorrents.insert(id, loadTorrentParams);
2859 if (infoHash.isHybrid())
2860 m_hybridTorrentsByAltID.insert(altID, nullptr);
2861 if (!isFindingIncompleteFiles)
2862 m_nativeSession->async_add_torrent(p);
2864 return true;
2867 void SessionImpl::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
2868 , const Path &downloadPath, const PathList &filePaths) const
2870 Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
2872 const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
2873 const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
2874 QMetaObject::invokeMethod(m_fileSearcher, [=]()
2876 m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath, isAppendExtensionEnabled());
2880 void SessionImpl::enablePortMapping()
2882 invokeAsync([this]
2884 if (m_isPortMappingEnabled)
2885 return;
2887 lt::settings_pack settingsPack;
2888 settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
2889 settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
2890 m_nativeSession->apply_settings(std::move(settingsPack));
2892 m_isPortMappingEnabled = true;
2894 LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
2898 void SessionImpl::disablePortMapping()
2900 invokeAsync([this]
2902 if (!m_isPortMappingEnabled)
2903 return;
2905 lt::settings_pack settingsPack;
2906 settingsPack.set_bool(lt::settings_pack::enable_upnp, false);
2907 settingsPack.set_bool(lt::settings_pack::enable_natpmp, false);
2908 m_nativeSession->apply_settings(std::move(settingsPack));
2910 m_mappedPorts.clear();
2911 m_isPortMappingEnabled = false;
2913 LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO);
2917 void SessionImpl::addMappedPorts(const QSet<quint16> &ports)
2919 invokeAsync([this, ports]
2921 if (!m_isPortMappingEnabled)
2922 return;
2924 for (const quint16 port : ports)
2926 if (!m_mappedPorts.contains(port))
2927 m_mappedPorts.insert(port, m_nativeSession->add_port_mapping(lt::session::tcp, port, port));
2932 void SessionImpl::removeMappedPorts(const QSet<quint16> &ports)
2934 invokeAsync([this, ports]
2936 if (!m_isPortMappingEnabled)
2937 return;
2939 Algorithm::removeIf(m_mappedPorts, [this, ports](const quint16 port, const std::vector<lt::port_mapping_t> &handles)
2941 if (!ports.contains(port))
2942 return false;
2944 for (const lt::port_mapping_t &handle : handles)
2945 m_nativeSession->delete_port_mapping(handle);
2947 return true;
2952 void SessionImpl::invokeAsync(std::function<void ()> func)
2954 m_asyncWorker->start(std::move(func));
2957 // Add a torrent to libtorrent session in hidden mode
2958 // and force it to download its metadata
2959 bool SessionImpl::downloadMetadata(const MagnetUri &magnetUri)
2961 if (!magnetUri.isValid())
2962 return false;
2964 const InfoHash infoHash = magnetUri.infoHash();
2966 // We should not add torrent if it's already
2967 // processed or adding to session
2968 if (isKnownTorrent(infoHash))
2969 return false;
2971 lt::add_torrent_params p = magnetUri.addTorrentParams();
2973 if (isAddTrackersEnabled())
2975 // Use "additional trackers" when metadata retrieving (this can help when the DHT nodes are few)
2976 p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
2977 p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
2978 p.tracker_tiers.resize(p.trackers.size(), 0);
2979 for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
2981 p.trackers.push_back(trackerEntry.url.toStdString());
2982 p.tracker_tiers.push_back(trackerEntry.tier);
2986 // Flags
2987 // Preallocation mode
2988 if (isPreallocationEnabled())
2989 p.storage_mode = lt::storage_mode_allocate;
2990 else
2991 p.storage_mode = lt::storage_mode_sparse;
2993 // Limits
2994 p.max_connections = maxConnectionsPerTorrent();
2995 p.max_uploads = maxUploadsPerTorrent();
2997 const auto id = TorrentID::fromInfoHash(infoHash);
2998 const Path savePath = Utils::Fs::tempPath() / Path(id.toString());
2999 p.save_path = savePath.toString().toStdString();
3001 // Forced start
3002 p.flags &= ~lt::torrent_flags::paused;
3003 p.flags &= ~lt::torrent_flags::auto_managed;
3005 // Solution to avoid accidental file writes
3006 p.flags |= lt::torrent_flags::upload_mode;
3008 #ifndef QBT_USES_LIBTORRENT2
3009 p.storage = customStorageConstructor;
3010 #endif
3012 // Adding torrent to libtorrent session
3013 m_nativeSession->async_add_torrent(p);
3014 m_downloadedMetadata.insert(id, {});
3016 return true;
3019 void SessionImpl::exportTorrentFile(const Torrent *torrent, const Path &folderPath)
3021 if (!folderPath.exists() && !Utils::Fs::mkpath(folderPath))
3022 return;
3024 const QString validName = Utils::Fs::toValidFileName(torrent->name());
3025 QString torrentExportFilename = u"%1.torrent"_qs.arg(validName);
3026 Path newTorrentPath = folderPath / Path(torrentExportFilename);
3027 int counter = 0;
3028 while (newTorrentPath.exists())
3030 // Append number to torrent name to make it unique
3031 torrentExportFilename = u"%1 %2.torrent"_qs.arg(validName).arg(++counter);
3032 newTorrentPath = folderPath / Path(torrentExportFilename);
3035 const nonstd::expected<void, QString> result = torrent->exportToFile(newTorrentPath);
3036 if (!result)
3038 LogMsg(tr("Failed to export torrent. Torrent: \"%1\". Destination: \"%2\". Reason: \"%3\"")
3039 .arg(torrent->name(), newTorrentPath.toString(), result.error()), Log::WARNING);
3043 void SessionImpl::generateResumeData()
3045 for (TorrentImpl *const torrent : asConst(m_torrents))
3047 if (!torrent->isValid()) continue;
3049 if (torrent->needSaveResumeData())
3051 torrent->saveResumeData();
3052 m_needSaveResumeDataTorrents.remove(torrent->id());
3057 // Called on exit
3058 void SessionImpl::saveResumeData()
3060 for (const TorrentImpl *torrent : asConst(m_torrents))
3061 torrent->nativeHandle().save_resume_data(lt::torrent_handle::only_if_modified);
3062 m_numResumeData += m_torrents.size();
3064 // clear queued storage move jobs except the current ongoing one
3065 if (m_moveStorageQueue.size() > 1)
3067 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
3068 m_moveStorageQueue = m_moveStorageQueue.mid(0, 1);
3069 #else
3070 m_moveStorageQueue.resize(1);
3071 #endif
3074 QElapsedTimer timer;
3075 timer.start();
3077 while ((m_numResumeData > 0) || !m_moveStorageQueue.isEmpty() || m_needSaveTorrentsQueue)
3079 const lt::seconds waitTime {5};
3080 const lt::seconds expireTime {30};
3082 // only terminate when no storage is moving
3083 if (timer.hasExpired(lt::total_milliseconds(expireTime)) && m_moveStorageQueue.isEmpty())
3085 LogMsg(tr("Aborted saving resume data. Number of outstanding torrents: %1").arg(QString::number(m_numResumeData))
3086 , Log::CRITICAL);
3087 break;
3090 const std::vector<lt::alert *> alerts = getPendingAlerts(waitTime);
3092 bool hasWantedAlert = false;
3093 for (const lt::alert *a : alerts)
3095 if (const int alertType = a->type();
3096 (alertType == lt::save_resume_data_alert::alert_type) || (alertType == lt::save_resume_data_failed_alert::alert_type)
3097 || (alertType == lt::storage_moved_alert::alert_type) || (alertType == lt::storage_moved_failed_alert::alert_type)
3098 || (alertType == lt::state_update_alert::alert_type))
3100 hasWantedAlert = true;
3103 handleAlert(a);
3106 if (hasWantedAlert)
3107 timer.start();
3111 void SessionImpl::saveTorrentsQueue()
3113 QVector<TorrentID> queue;
3114 for (const TorrentImpl *torrent : asConst(m_torrents))
3116 if (const int queuePos = torrent->queuePosition(); queuePos >= 0)
3118 if (queuePos >= queue.size())
3119 queue.resize(queuePos + 1);
3120 queue[queuePos] = torrent->id();
3124 m_resumeDataStorage->storeQueue(queue);
3125 m_needSaveTorrentsQueue = false;
3128 void SessionImpl::removeTorrentsQueue()
3130 m_resumeDataStorage->storeQueue({});
3131 m_torrentsQueueChanged = false;
3132 m_needSaveTorrentsQueue = false;
3135 void SessionImpl::setSavePath(const Path &path)
3137 const auto newPath = (path.isAbsolute() ? path : (specialFolderLocation(SpecialFolder::Downloads) / path));
3138 if (newPath == m_savePath)
3139 return;
3141 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3143 QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
3144 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
3146 const QString &categoryName = it.key();
3147 const CategoryOptions &categoryOptions = it.value();
3148 if (categoryOptions.savePath.isRelative())
3149 affectedCatogories.insert(categoryName);
3152 for (TorrentImpl *const torrent : asConst(m_torrents))
3154 if (affectedCatogories.contains(torrent->category()))
3155 torrent->setAutoTMMEnabled(false);
3159 m_savePath = newPath;
3160 for (TorrentImpl *const torrent : asConst(m_torrents))
3161 torrent->handleCategoryOptionsChanged();
3164 void SessionImpl::setDownloadPath(const Path &path)
3166 const Path newPath = (path.isAbsolute() ? path : (savePath() / Path(u"temp"_qs) / path));
3167 if (newPath == m_downloadPath)
3168 return;
3170 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3172 QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
3173 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
3175 const QString &categoryName = it.key();
3176 const CategoryOptions &categoryOptions = it.value();
3177 const CategoryOptions::DownloadPathOption downloadPathOption =
3178 categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
3179 if (downloadPathOption.enabled && downloadPathOption.path.isRelative())
3180 affectedCatogories.insert(categoryName);
3183 for (TorrentImpl *const torrent : asConst(m_torrents))
3185 if (affectedCatogories.contains(torrent->category()))
3186 torrent->setAutoTMMEnabled(false);
3190 m_downloadPath = newPath;
3191 for (TorrentImpl *const torrent : asConst(m_torrents))
3192 torrent->handleCategoryOptionsChanged();
3195 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
3196 void SessionImpl::networkOnlineStateChanged(const bool online)
3198 LogMsg(tr("System network status changed to %1", "e.g: System network status changed to ONLINE").arg(online ? tr("ONLINE") : tr("OFFLINE")), Log::INFO);
3201 void SessionImpl::networkConfigurationChange(const QNetworkConfiguration &cfg)
3203 const QString configuredInterfaceName = networkInterface();
3204 // Empty means "Any Interface". In this case libtorrent has binded to 0.0.0.0 so any change to any interface will
3205 // be automatically picked up. Otherwise we would rebinding here to 0.0.0.0 again.
3206 if (configuredInterfaceName.isEmpty()) return;
3208 const QString changedInterface = cfg.name();
3210 if (configuredInterfaceName == changedInterface)
3212 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);
3213 configureListeningInterface();
3216 #endif
3218 QStringList SessionImpl::getListeningIPs() const
3220 QStringList IPs;
3222 const QString ifaceName = networkInterface();
3223 const QString ifaceAddr = networkInterfaceAddress();
3224 const QHostAddress configuredAddr(ifaceAddr);
3225 const bool allIPv4 = (ifaceAddr == u"0.0.0.0"); // Means All IPv4 addresses
3226 const bool allIPv6 = (ifaceAddr == u"::"); // Means All IPv6 addresses
3228 if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull())
3230 LogMsg(tr("The configured network address is invalid. Address: \"%1\"").arg(ifaceAddr), Log::CRITICAL);
3231 // Pass the invalid user configured interface name/address to libtorrent
3232 // in hopes that it will come online later.
3233 // This will not cause IP leak but allow user to reconnect the interface
3234 // and re-establish connection without restarting the client.
3235 IPs.append(ifaceAddr);
3236 return IPs;
3239 if (ifaceName.isEmpty())
3241 if (ifaceAddr.isEmpty())
3242 return {u"0.0.0.0"_qs, u"::"_qs}; // Indicates all interfaces + all addresses (aka default)
3244 if (allIPv4)
3245 return {u"0.0.0.0"_qs};
3247 if (allIPv6)
3248 return {u"::"_qs};
3251 const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
3253 if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
3254 || (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
3255 return;
3257 if ((match == addr) || allIPv4 || allIPv6)
3258 IPs.append(addr.toString());
3261 if (ifaceName.isEmpty())
3263 const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
3264 for (const auto &addr : addresses)
3265 checkAndAddIP(addr, configuredAddr);
3267 // At this point ifaceAddr was non-empty
3268 // If IPs.isEmpty() it means the configured Address was not found
3269 if (IPs.isEmpty())
3271 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3272 .arg(ifaceAddr), Log::CRITICAL);
3273 IPs.append(ifaceAddr);
3276 return IPs;
3279 // Attempt to listen on provided interface
3280 const QNetworkInterface networkIFace = QNetworkInterface::interfaceFromName(ifaceName);
3281 if (!networkIFace.isValid())
3283 qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName));
3284 LogMsg(tr("The configured network interface is invalid. Interface: \"%1\"").arg(ifaceName), Log::CRITICAL);
3285 IPs.append(ifaceName);
3286 return IPs;
3289 if (ifaceAddr.isEmpty())
3291 IPs.append(ifaceName);
3292 return IPs; // On Windows calling code converts it to GUID
3295 const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
3296 qDebug() << "This network interface has " << addresses.size() << " IP addresses";
3297 for (const QNetworkAddressEntry &entry : addresses)
3298 checkAndAddIP(entry.ip(), configuredAddr);
3300 // Make sure there is at least one IP
3301 // At this point there was an explicit interface and an explicit address set
3302 // and the address should have been found
3303 if (IPs.isEmpty())
3305 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3306 .arg(ifaceAddr), Log::CRITICAL);
3307 IPs.append(ifaceAddr);
3310 return IPs;
3313 // Set the ports range in which is chosen the port
3314 // the BitTorrent session will listen to
3315 void SessionImpl::configureListeningInterface()
3317 m_listenInterfaceConfigured = false;
3318 configureDeferred();
3321 int SessionImpl::globalDownloadSpeedLimit() const
3323 // Unfortunately the value was saved as KiB instead of B.
3324 // But it is better to pass it around internally(+ webui) as Bytes.
3325 return m_globalDownloadSpeedLimit * 1024;
3328 void SessionImpl::setGlobalDownloadSpeedLimit(const int limit)
3330 // Unfortunately the value was saved as KiB instead of B.
3331 // But it is better to pass it around internally(+ webui) as Bytes.
3332 if (limit == globalDownloadSpeedLimit())
3333 return;
3335 if (limit <= 0)
3336 m_globalDownloadSpeedLimit = 0;
3337 else if (limit <= 1024)
3338 m_globalDownloadSpeedLimit = 1;
3339 else
3340 m_globalDownloadSpeedLimit = (limit / 1024);
3342 if (!isAltGlobalSpeedLimitEnabled())
3343 configureDeferred();
3346 int SessionImpl::globalUploadSpeedLimit() const
3348 // Unfortunately the value was saved as KiB instead of B.
3349 // But it is better to pass it around internally(+ webui) as Bytes.
3350 return m_globalUploadSpeedLimit * 1024;
3353 void SessionImpl::setGlobalUploadSpeedLimit(const int limit)
3355 // Unfortunately the value was saved as KiB instead of B.
3356 // But it is better to pass it around internally(+ webui) as Bytes.
3357 if (limit == globalUploadSpeedLimit())
3358 return;
3360 if (limit <= 0)
3361 m_globalUploadSpeedLimit = 0;
3362 else if (limit <= 1024)
3363 m_globalUploadSpeedLimit = 1;
3364 else
3365 m_globalUploadSpeedLimit = (limit / 1024);
3367 if (!isAltGlobalSpeedLimitEnabled())
3368 configureDeferred();
3371 int SessionImpl::altGlobalDownloadSpeedLimit() const
3373 // Unfortunately the value was saved as KiB instead of B.
3374 // But it is better to pass it around internally(+ webui) as Bytes.
3375 return m_altGlobalDownloadSpeedLimit * 1024;
3378 void SessionImpl::setAltGlobalDownloadSpeedLimit(const int limit)
3380 // Unfortunately the value was saved as KiB instead of B.
3381 // But it is better to pass it around internally(+ webui) as Bytes.
3382 if (limit == altGlobalDownloadSpeedLimit())
3383 return;
3385 if (limit <= 0)
3386 m_altGlobalDownloadSpeedLimit = 0;
3387 else if (limit <= 1024)
3388 m_altGlobalDownloadSpeedLimit = 1;
3389 else
3390 m_altGlobalDownloadSpeedLimit = (limit / 1024);
3392 if (isAltGlobalSpeedLimitEnabled())
3393 configureDeferred();
3396 int SessionImpl::altGlobalUploadSpeedLimit() const
3398 // Unfortunately the value was saved as KiB instead of B.
3399 // But it is better to pass it around internally(+ webui) as Bytes.
3400 return m_altGlobalUploadSpeedLimit * 1024;
3403 void SessionImpl::setAltGlobalUploadSpeedLimit(const int limit)
3405 // Unfortunately the value was saved as KiB instead of B.
3406 // But it is better to pass it around internally(+ webui) as Bytes.
3407 if (limit == altGlobalUploadSpeedLimit())
3408 return;
3410 if (limit <= 0)
3411 m_altGlobalUploadSpeedLimit = 0;
3412 else if (limit <= 1024)
3413 m_altGlobalUploadSpeedLimit = 1;
3414 else
3415 m_altGlobalUploadSpeedLimit = (limit / 1024);
3417 if (isAltGlobalSpeedLimitEnabled())
3418 configureDeferred();
3421 int SessionImpl::downloadSpeedLimit() const
3423 return isAltGlobalSpeedLimitEnabled()
3424 ? altGlobalDownloadSpeedLimit()
3425 : globalDownloadSpeedLimit();
3428 void SessionImpl::setDownloadSpeedLimit(const int limit)
3430 if (isAltGlobalSpeedLimitEnabled())
3431 setAltGlobalDownloadSpeedLimit(limit);
3432 else
3433 setGlobalDownloadSpeedLimit(limit);
3436 int SessionImpl::uploadSpeedLimit() const
3438 return isAltGlobalSpeedLimitEnabled()
3439 ? altGlobalUploadSpeedLimit()
3440 : globalUploadSpeedLimit();
3443 void SessionImpl::setUploadSpeedLimit(const int limit)
3445 if (isAltGlobalSpeedLimitEnabled())
3446 setAltGlobalUploadSpeedLimit(limit);
3447 else
3448 setGlobalUploadSpeedLimit(limit);
3451 bool SessionImpl::isAltGlobalSpeedLimitEnabled() const
3453 return m_isAltGlobalSpeedLimitEnabled;
3456 void SessionImpl::setAltGlobalSpeedLimitEnabled(const bool enabled)
3458 if (enabled == isAltGlobalSpeedLimitEnabled()) return;
3460 // Save new state to remember it on startup
3461 m_isAltGlobalSpeedLimitEnabled = enabled;
3462 applyBandwidthLimits();
3463 // Notify
3464 emit speedLimitModeChanged(m_isAltGlobalSpeedLimitEnabled);
3467 bool SessionImpl::isBandwidthSchedulerEnabled() const
3469 return m_isBandwidthSchedulerEnabled;
3472 void SessionImpl::setBandwidthSchedulerEnabled(const bool enabled)
3474 if (enabled != isBandwidthSchedulerEnabled())
3476 m_isBandwidthSchedulerEnabled = enabled;
3477 if (enabled)
3478 enableBandwidthScheduler();
3479 else
3480 delete m_bwScheduler;
3484 bool SessionImpl::isPerformanceWarningEnabled() const
3486 return m_isPerformanceWarningEnabled;
3489 void SessionImpl::setPerformanceWarningEnabled(const bool enable)
3491 if (enable == m_isPerformanceWarningEnabled)
3492 return;
3494 m_isPerformanceWarningEnabled = enable;
3495 configureDeferred();
3498 int SessionImpl::saveResumeDataInterval() const
3500 return m_saveResumeDataInterval;
3503 void SessionImpl::setSaveResumeDataInterval(const int value)
3505 if (value == m_saveResumeDataInterval)
3506 return;
3508 m_saveResumeDataInterval = value;
3510 if (value > 0)
3512 m_resumeDataTimer->setInterval(std::chrono::minutes(value));
3513 m_resumeDataTimer->start();
3515 else
3517 m_resumeDataTimer->stop();
3521 int SessionImpl::port() const
3523 return m_port;
3526 void SessionImpl::setPort(const int port)
3528 if (port != m_port)
3530 m_port = port;
3531 configureListeningInterface();
3533 if (isReannounceWhenAddressChangedEnabled())
3534 reannounceToAllTrackers();
3538 QString SessionImpl::networkInterface() const
3540 return m_networkInterface;
3543 void SessionImpl::setNetworkInterface(const QString &iface)
3545 if (iface != networkInterface())
3547 m_networkInterface = iface;
3548 configureListeningInterface();
3552 QString SessionImpl::networkInterfaceName() const
3554 return m_networkInterfaceName;
3557 void SessionImpl::setNetworkInterfaceName(const QString &name)
3559 m_networkInterfaceName = name;
3562 QString SessionImpl::networkInterfaceAddress() const
3564 return m_networkInterfaceAddress;
3567 void SessionImpl::setNetworkInterfaceAddress(const QString &address)
3569 if (address != networkInterfaceAddress())
3571 m_networkInterfaceAddress = address;
3572 configureListeningInterface();
3576 int SessionImpl::encryption() const
3578 return m_encryption;
3581 void SessionImpl::setEncryption(const int state)
3583 if (state != encryption())
3585 m_encryption = state;
3586 configureDeferred();
3587 LogMsg(tr("Encryption support: %1").arg(
3588 state == 0 ? tr("ON") : ((state == 1) ? tr("FORCED") : tr("OFF")))
3589 , Log::INFO);
3593 int SessionImpl::maxActiveCheckingTorrents() const
3595 return m_maxActiveCheckingTorrents;
3598 void SessionImpl::setMaxActiveCheckingTorrents(const int val)
3600 if (val == m_maxActiveCheckingTorrents)
3601 return;
3603 m_maxActiveCheckingTorrents = val;
3604 configureDeferred();
3607 bool SessionImpl::isI2PEnabled() const
3609 return m_isI2PEnabled;
3612 void SessionImpl::setI2PEnabled(const bool enabled)
3614 if (m_isI2PEnabled != enabled)
3616 m_isI2PEnabled = enabled;
3617 configureDeferred();
3621 QString SessionImpl::I2PAddress() const
3623 return m_I2PAddress;
3626 void SessionImpl::setI2PAddress(const QString &address)
3628 if (m_I2PAddress != address)
3630 m_I2PAddress = address;
3631 configureDeferred();
3635 int SessionImpl::I2PPort() const
3637 return m_I2PPort;
3640 void SessionImpl::setI2PPort(int port)
3642 if (m_I2PPort != port)
3644 m_I2PPort = port;
3645 configureDeferred();
3649 bool SessionImpl::I2PMixedMode() const
3651 return m_I2PMixedMode;
3654 void SessionImpl::setI2PMixedMode(const bool enabled)
3656 if (m_I2PMixedMode != enabled)
3658 m_I2PMixedMode = enabled;
3659 configureDeferred();
3663 bool SessionImpl::isProxyPeerConnectionsEnabled() const
3665 return m_isProxyPeerConnectionsEnabled;
3668 void SessionImpl::setProxyPeerConnectionsEnabled(const bool enabled)
3670 if (enabled != isProxyPeerConnectionsEnabled())
3672 m_isProxyPeerConnectionsEnabled = enabled;
3673 configureDeferred();
3677 ChokingAlgorithm SessionImpl::chokingAlgorithm() const
3679 return m_chokingAlgorithm;
3682 void SessionImpl::setChokingAlgorithm(const ChokingAlgorithm mode)
3684 if (mode == m_chokingAlgorithm) return;
3686 m_chokingAlgorithm = mode;
3687 configureDeferred();
3690 SeedChokingAlgorithm SessionImpl::seedChokingAlgorithm() const
3692 return m_seedChokingAlgorithm;
3695 void SessionImpl::setSeedChokingAlgorithm(const SeedChokingAlgorithm mode)
3697 if (mode == m_seedChokingAlgorithm) return;
3699 m_seedChokingAlgorithm = mode;
3700 configureDeferred();
3703 bool SessionImpl::isAddTrackersEnabled() const
3705 return m_isAddTrackersEnabled;
3708 void SessionImpl::setAddTrackersEnabled(const bool enabled)
3710 m_isAddTrackersEnabled = enabled;
3713 QString SessionImpl::additionalTrackers() const
3715 return m_additionalTrackers;
3718 void SessionImpl::setAdditionalTrackers(const QString &trackers)
3720 if (trackers != additionalTrackers())
3722 m_additionalTrackers = trackers;
3723 populateAdditionalTrackers();
3727 bool SessionImpl::isIPFilteringEnabled() const
3729 return m_isIPFilteringEnabled;
3732 void SessionImpl::setIPFilteringEnabled(const bool enabled)
3734 if (enabled != m_isIPFilteringEnabled)
3736 m_isIPFilteringEnabled = enabled;
3737 m_IPFilteringConfigured = false;
3738 configureDeferred();
3742 Path SessionImpl::IPFilterFile() const
3744 return m_IPFilterFile;
3747 void SessionImpl::setIPFilterFile(const Path &path)
3749 if (path != IPFilterFile())
3751 m_IPFilterFile = path;
3752 m_IPFilteringConfigured = false;
3753 configureDeferred();
3757 bool SessionImpl::isExcludedFileNamesEnabled() const
3759 return m_isExcludedFileNamesEnabled;
3762 void SessionImpl::setExcludedFileNamesEnabled(const bool enabled)
3764 if (m_isExcludedFileNamesEnabled == enabled)
3765 return;
3767 m_isExcludedFileNamesEnabled = enabled;
3769 if (enabled)
3770 populateExcludedFileNamesRegExpList();
3771 else
3772 m_excludedFileNamesRegExpList.clear();
3775 QStringList SessionImpl::excludedFileNames() const
3777 return m_excludedFileNames;
3780 void SessionImpl::setExcludedFileNames(const QStringList &excludedFileNames)
3782 if (excludedFileNames != m_excludedFileNames)
3784 m_excludedFileNames = excludedFileNames;
3785 populateExcludedFileNamesRegExpList();
3789 void SessionImpl::populateExcludedFileNamesRegExpList()
3791 const QStringList excludedNames = excludedFileNames();
3793 m_excludedFileNamesRegExpList.clear();
3794 m_excludedFileNamesRegExpList.reserve(excludedNames.size());
3796 for (const QString &str : excludedNames)
3798 const QString pattern = QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(str));
3799 const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
3800 m_excludedFileNamesRegExpList.append(re);
3804 bool SessionImpl::isFilenameExcluded(const QString &fileName) const
3806 if (!isExcludedFileNamesEnabled())
3807 return false;
3809 return std::any_of(m_excludedFileNamesRegExpList.begin(), m_excludedFileNamesRegExpList.end(), [&fileName](const QRegularExpression &re)
3811 return re.match(fileName).hasMatch();
3815 void SessionImpl::setBannedIPs(const QStringList &newList)
3817 if (newList == m_bannedIPs)
3818 return; // do nothing
3819 // here filter out incorrect IP
3820 QStringList filteredList;
3821 for (const QString &ip : newList)
3823 if (Utils::Net::isValidIP(ip))
3825 // the same IPv6 addresses could be written in different forms;
3826 // QHostAddress::toString() result format follows RFC5952;
3827 // thus we avoid duplicate entries pointing to the same address
3828 filteredList << QHostAddress(ip).toString();
3830 else
3832 LogMsg(tr("Rejected invalid IP address while applying the list of banned IP addresses. IP: \"%1\"")
3833 .arg(ip)
3834 , Log::WARNING);
3837 // now we have to sort IPs and make them unique
3838 filteredList.sort();
3839 filteredList.removeDuplicates();
3840 // Again ensure that the new list is different from the stored one.
3841 if (filteredList == m_bannedIPs)
3842 return; // do nothing
3843 // store to session settings
3844 // also here we have to recreate filter list including 3rd party ban file
3845 // and install it again into m_session
3846 m_bannedIPs = filteredList;
3847 m_IPFilteringConfigured = false;
3848 configureDeferred();
3851 ResumeDataStorageType SessionImpl::resumeDataStorageType() const
3853 return m_resumeDataStorageType;
3856 void SessionImpl::setResumeDataStorageType(const ResumeDataStorageType type)
3858 m_resumeDataStorageType = type;
3861 QStringList SessionImpl::bannedIPs() const
3863 return m_bannedIPs;
3866 bool SessionImpl::isRestored() const
3868 return m_isRestored;
3871 int SessionImpl::maxConnectionsPerTorrent() const
3873 return m_maxConnectionsPerTorrent;
3876 void SessionImpl::setMaxConnectionsPerTorrent(int max)
3878 max = (max > 0) ? max : -1;
3879 if (max != maxConnectionsPerTorrent())
3881 m_maxConnectionsPerTorrent = max;
3883 for (const TorrentImpl *torrent : asConst(m_torrents))
3887 torrent->nativeHandle().set_max_connections(max);
3889 catch (const std::exception &) {}
3894 int SessionImpl::maxUploadsPerTorrent() const
3896 return m_maxUploadsPerTorrent;
3899 void SessionImpl::setMaxUploadsPerTorrent(int max)
3901 max = (max > 0) ? max : -1;
3902 if (max != maxUploadsPerTorrent())
3904 m_maxUploadsPerTorrent = max;
3906 for (const TorrentImpl *torrent : asConst(m_torrents))
3910 torrent->nativeHandle().set_max_uploads(max);
3912 catch (const std::exception &) {}
3917 bool SessionImpl::announceToAllTrackers() const
3919 return m_announceToAllTrackers;
3922 void SessionImpl::setAnnounceToAllTrackers(const bool val)
3924 if (val != m_announceToAllTrackers)
3926 m_announceToAllTrackers = val;
3927 configureDeferred();
3931 bool SessionImpl::announceToAllTiers() const
3933 return m_announceToAllTiers;
3936 void SessionImpl::setAnnounceToAllTiers(const bool val)
3938 if (val != m_announceToAllTiers)
3940 m_announceToAllTiers = val;
3941 configureDeferred();
3945 int SessionImpl::peerTurnover() const
3947 return m_peerTurnover;
3950 void SessionImpl::setPeerTurnover(const int val)
3952 if (val == m_peerTurnover)
3953 return;
3955 m_peerTurnover = val;
3956 configureDeferred();
3959 int SessionImpl::peerTurnoverCutoff() const
3961 return m_peerTurnoverCutoff;
3964 void SessionImpl::setPeerTurnoverCutoff(const int val)
3966 if (val == m_peerTurnoverCutoff)
3967 return;
3969 m_peerTurnoverCutoff = val;
3970 configureDeferred();
3973 int SessionImpl::peerTurnoverInterval() const
3975 return m_peerTurnoverInterval;
3978 void SessionImpl::setPeerTurnoverInterval(const int val)
3980 if (val == m_peerTurnoverInterval)
3981 return;
3983 m_peerTurnoverInterval = val;
3984 configureDeferred();
3987 DiskIOType SessionImpl::diskIOType() const
3989 return m_diskIOType;
3992 void SessionImpl::setDiskIOType(const DiskIOType type)
3994 if (type != m_diskIOType)
3996 m_diskIOType = type;
4000 int SessionImpl::requestQueueSize() const
4002 return m_requestQueueSize;
4005 void SessionImpl::setRequestQueueSize(const int val)
4007 if (val == m_requestQueueSize)
4008 return;
4010 m_requestQueueSize = val;
4011 configureDeferred();
4014 int SessionImpl::asyncIOThreads() const
4016 return std::clamp(m_asyncIOThreads.get(), 1, 1024);
4019 void SessionImpl::setAsyncIOThreads(const int num)
4021 if (num == m_asyncIOThreads)
4022 return;
4024 m_asyncIOThreads = num;
4025 configureDeferred();
4028 int SessionImpl::hashingThreads() const
4030 return std::clamp(m_hashingThreads.get(), 1, 1024);
4033 void SessionImpl::setHashingThreads(const int num)
4035 if (num == m_hashingThreads)
4036 return;
4038 m_hashingThreads = num;
4039 configureDeferred();
4042 int SessionImpl::filePoolSize() const
4044 return m_filePoolSize;
4047 void SessionImpl::setFilePoolSize(const int size)
4049 if (size == m_filePoolSize)
4050 return;
4052 m_filePoolSize = size;
4053 configureDeferred();
4056 int SessionImpl::checkingMemUsage() const
4058 return std::max(1, m_checkingMemUsage.get());
4061 void SessionImpl::setCheckingMemUsage(int size)
4063 size = std::max(size, 1);
4065 if (size == m_checkingMemUsage)
4066 return;
4068 m_checkingMemUsage = size;
4069 configureDeferred();
4072 int SessionImpl::diskCacheSize() const
4074 #ifdef QBT_APP_64BIT
4075 return std::min(m_diskCacheSize.get(), 33554431); // 32768GiB
4076 #else
4077 // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
4078 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4079 return std::min(m_diskCacheSize.get(), 1536);
4080 #endif
4083 void SessionImpl::setDiskCacheSize(int size)
4085 #ifdef QBT_APP_64BIT
4086 size = std::min(size, 33554431); // 32768GiB
4087 #else
4088 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4089 size = std::min(size, 1536);
4090 #endif
4091 if (size != m_diskCacheSize)
4093 m_diskCacheSize = size;
4094 configureDeferred();
4098 int SessionImpl::diskCacheTTL() const
4100 return m_diskCacheTTL;
4103 void SessionImpl::setDiskCacheTTL(const int ttl)
4105 if (ttl != m_diskCacheTTL)
4107 m_diskCacheTTL = ttl;
4108 configureDeferred();
4112 qint64 SessionImpl::diskQueueSize() const
4114 return m_diskQueueSize;
4117 void SessionImpl::setDiskQueueSize(const qint64 size)
4119 if (size == m_diskQueueSize)
4120 return;
4122 m_diskQueueSize = size;
4123 configureDeferred();
4126 DiskIOReadMode SessionImpl::diskIOReadMode() const
4128 return m_diskIOReadMode;
4131 void SessionImpl::setDiskIOReadMode(const DiskIOReadMode mode)
4133 if (mode == m_diskIOReadMode)
4134 return;
4136 m_diskIOReadMode = mode;
4137 configureDeferred();
4140 DiskIOWriteMode SessionImpl::diskIOWriteMode() const
4142 return m_diskIOWriteMode;
4145 void SessionImpl::setDiskIOWriteMode(const DiskIOWriteMode mode)
4147 if (mode == m_diskIOWriteMode)
4148 return;
4150 m_diskIOWriteMode = mode;
4151 configureDeferred();
4154 bool SessionImpl::isCoalesceReadWriteEnabled() const
4156 return m_coalesceReadWriteEnabled;
4159 void SessionImpl::setCoalesceReadWriteEnabled(const bool enabled)
4161 if (enabled == m_coalesceReadWriteEnabled) return;
4163 m_coalesceReadWriteEnabled = enabled;
4164 configureDeferred();
4167 bool SessionImpl::isSuggestModeEnabled() const
4169 return m_isSuggestMode;
4172 bool SessionImpl::usePieceExtentAffinity() const
4174 return m_usePieceExtentAffinity;
4177 void SessionImpl::setPieceExtentAffinity(const bool enabled)
4179 if (enabled == m_usePieceExtentAffinity) return;
4181 m_usePieceExtentAffinity = enabled;
4182 configureDeferred();
4185 void SessionImpl::setSuggestMode(const bool mode)
4187 if (mode == m_isSuggestMode) return;
4189 m_isSuggestMode = mode;
4190 configureDeferred();
4193 int SessionImpl::sendBufferWatermark() const
4195 return m_sendBufferWatermark;
4198 void SessionImpl::setSendBufferWatermark(const int value)
4200 if (value == m_sendBufferWatermark) return;
4202 m_sendBufferWatermark = value;
4203 configureDeferred();
4206 int SessionImpl::sendBufferLowWatermark() const
4208 return m_sendBufferLowWatermark;
4211 void SessionImpl::setSendBufferLowWatermark(const int value)
4213 if (value == m_sendBufferLowWatermark) return;
4215 m_sendBufferLowWatermark = value;
4216 configureDeferred();
4219 int SessionImpl::sendBufferWatermarkFactor() const
4221 return m_sendBufferWatermarkFactor;
4224 void SessionImpl::setSendBufferWatermarkFactor(const int value)
4226 if (value == m_sendBufferWatermarkFactor) return;
4228 m_sendBufferWatermarkFactor = value;
4229 configureDeferred();
4232 int SessionImpl::connectionSpeed() const
4234 return m_connectionSpeed;
4237 void SessionImpl::setConnectionSpeed(const int value)
4239 if (value == m_connectionSpeed) return;
4241 m_connectionSpeed = value;
4242 configureDeferred();
4245 int SessionImpl::socketSendBufferSize() const
4247 return m_socketSendBufferSize;
4250 void SessionImpl::setSocketSendBufferSize(const int value)
4252 if (value == m_socketSendBufferSize)
4253 return;
4255 m_socketSendBufferSize = value;
4256 configureDeferred();
4259 int SessionImpl::socketReceiveBufferSize() const
4261 return m_socketReceiveBufferSize;
4264 void SessionImpl::setSocketReceiveBufferSize(const int value)
4266 if (value == m_socketReceiveBufferSize)
4267 return;
4269 m_socketReceiveBufferSize = value;
4270 configureDeferred();
4273 int SessionImpl::socketBacklogSize() const
4275 return m_socketBacklogSize;
4278 void SessionImpl::setSocketBacklogSize(const int value)
4280 if (value == m_socketBacklogSize) return;
4282 m_socketBacklogSize = value;
4283 configureDeferred();
4286 bool SessionImpl::isAnonymousModeEnabled() const
4288 return m_isAnonymousModeEnabled;
4291 void SessionImpl::setAnonymousModeEnabled(const bool enabled)
4293 if (enabled != m_isAnonymousModeEnabled)
4295 m_isAnonymousModeEnabled = enabled;
4296 configureDeferred();
4297 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
4298 , Log::INFO);
4302 bool SessionImpl::isQueueingSystemEnabled() const
4304 return m_isQueueingEnabled;
4307 void SessionImpl::setQueueingSystemEnabled(const bool enabled)
4309 if (enabled != m_isQueueingEnabled)
4311 m_isQueueingEnabled = enabled;
4312 configureDeferred();
4314 if (enabled)
4315 m_torrentsQueueChanged = true;
4316 else
4317 removeTorrentsQueue();
4321 int SessionImpl::maxActiveDownloads() const
4323 return m_maxActiveDownloads;
4326 void SessionImpl::setMaxActiveDownloads(int max)
4328 max = std::max(max, -1);
4329 if (max != m_maxActiveDownloads)
4331 m_maxActiveDownloads = max;
4332 configureDeferred();
4336 int SessionImpl::maxActiveUploads() const
4338 return m_maxActiveUploads;
4341 void SessionImpl::setMaxActiveUploads(int max)
4343 max = std::max(max, -1);
4344 if (max != m_maxActiveUploads)
4346 m_maxActiveUploads = max;
4347 configureDeferred();
4351 int SessionImpl::maxActiveTorrents() const
4353 return m_maxActiveTorrents;
4356 void SessionImpl::setMaxActiveTorrents(int max)
4358 max = std::max(max, -1);
4359 if (max != m_maxActiveTorrents)
4361 m_maxActiveTorrents = max;
4362 configureDeferred();
4366 bool SessionImpl::ignoreSlowTorrentsForQueueing() const
4368 return m_ignoreSlowTorrentsForQueueing;
4371 void SessionImpl::setIgnoreSlowTorrentsForQueueing(const bool ignore)
4373 if (ignore != m_ignoreSlowTorrentsForQueueing)
4375 m_ignoreSlowTorrentsForQueueing = ignore;
4376 configureDeferred();
4380 int SessionImpl::downloadRateForSlowTorrents() const
4382 return m_downloadRateForSlowTorrents;
4385 void SessionImpl::setDownloadRateForSlowTorrents(const int rateInKibiBytes)
4387 if (rateInKibiBytes == m_downloadRateForSlowTorrents)
4388 return;
4390 m_downloadRateForSlowTorrents = rateInKibiBytes;
4391 configureDeferred();
4394 int SessionImpl::uploadRateForSlowTorrents() const
4396 return m_uploadRateForSlowTorrents;
4399 void SessionImpl::setUploadRateForSlowTorrents(const int rateInKibiBytes)
4401 if (rateInKibiBytes == m_uploadRateForSlowTorrents)
4402 return;
4404 m_uploadRateForSlowTorrents = rateInKibiBytes;
4405 configureDeferred();
4408 int SessionImpl::slowTorrentsInactivityTimer() const
4410 return m_slowTorrentsInactivityTimer;
4413 void SessionImpl::setSlowTorrentsInactivityTimer(const int timeInSeconds)
4415 if (timeInSeconds == m_slowTorrentsInactivityTimer)
4416 return;
4418 m_slowTorrentsInactivityTimer = timeInSeconds;
4419 configureDeferred();
4422 int SessionImpl::outgoingPortsMin() const
4424 return m_outgoingPortsMin;
4427 void SessionImpl::setOutgoingPortsMin(const int min)
4429 if (min != m_outgoingPortsMin)
4431 m_outgoingPortsMin = min;
4432 configureDeferred();
4436 int SessionImpl::outgoingPortsMax() const
4438 return m_outgoingPortsMax;
4441 void SessionImpl::setOutgoingPortsMax(const int max)
4443 if (max != m_outgoingPortsMax)
4445 m_outgoingPortsMax = max;
4446 configureDeferred();
4450 int SessionImpl::UPnPLeaseDuration() const
4452 return m_UPnPLeaseDuration;
4455 void SessionImpl::setUPnPLeaseDuration(const int duration)
4457 if (duration != m_UPnPLeaseDuration)
4459 m_UPnPLeaseDuration = duration;
4460 configureDeferred();
4464 int SessionImpl::peerToS() const
4466 return m_peerToS;
4469 void SessionImpl::setPeerToS(const int value)
4471 if (value == m_peerToS)
4472 return;
4474 m_peerToS = value;
4475 configureDeferred();
4478 bool SessionImpl::ignoreLimitsOnLAN() const
4480 return m_ignoreLimitsOnLAN;
4483 void SessionImpl::setIgnoreLimitsOnLAN(const bool ignore)
4485 if (ignore != m_ignoreLimitsOnLAN)
4487 m_ignoreLimitsOnLAN = ignore;
4488 configureDeferred();
4492 bool SessionImpl::includeOverheadInLimits() const
4494 return m_includeOverheadInLimits;
4497 void SessionImpl::setIncludeOverheadInLimits(const bool include)
4499 if (include != m_includeOverheadInLimits)
4501 m_includeOverheadInLimits = include;
4502 configureDeferred();
4506 QString SessionImpl::announceIP() const
4508 return m_announceIP;
4511 void SessionImpl::setAnnounceIP(const QString &ip)
4513 if (ip != m_announceIP)
4515 m_announceIP = ip;
4516 configureDeferred();
4520 int SessionImpl::maxConcurrentHTTPAnnounces() const
4522 return m_maxConcurrentHTTPAnnounces;
4525 void SessionImpl::setMaxConcurrentHTTPAnnounces(const int value)
4527 if (value == m_maxConcurrentHTTPAnnounces)
4528 return;
4530 m_maxConcurrentHTTPAnnounces = value;
4531 configureDeferred();
4534 bool SessionImpl::isReannounceWhenAddressChangedEnabled() const
4536 return m_isReannounceWhenAddressChangedEnabled;
4539 void SessionImpl::setReannounceWhenAddressChangedEnabled(const bool enabled)
4541 if (enabled == m_isReannounceWhenAddressChangedEnabled)
4542 return;
4544 m_isReannounceWhenAddressChangedEnabled = enabled;
4547 void SessionImpl::reannounceToAllTrackers() const
4549 for (const TorrentImpl *torrent : asConst(m_torrents))
4553 torrent->nativeHandle().force_reannounce(0, -1, lt::torrent_handle::ignore_min_interval);
4555 catch (const std::exception &) {}
4559 int SessionImpl::stopTrackerTimeout() const
4561 return m_stopTrackerTimeout;
4564 void SessionImpl::setStopTrackerTimeout(const int value)
4566 if (value == m_stopTrackerTimeout)
4567 return;
4569 m_stopTrackerTimeout = value;
4570 configureDeferred();
4573 int SessionImpl::maxConnections() const
4575 return m_maxConnections;
4578 void SessionImpl::setMaxConnections(int max)
4580 max = (max > 0) ? max : -1;
4581 if (max != m_maxConnections)
4583 m_maxConnections = max;
4584 configureDeferred();
4588 int SessionImpl::maxUploads() const
4590 return m_maxUploads;
4593 void SessionImpl::setMaxUploads(int max)
4595 max = (max > 0) ? max : -1;
4596 if (max != m_maxUploads)
4598 m_maxUploads = max;
4599 configureDeferred();
4603 BTProtocol SessionImpl::btProtocol() const
4605 return m_btProtocol;
4608 void SessionImpl::setBTProtocol(const BTProtocol protocol)
4610 if ((protocol < BTProtocol::Both) || (BTProtocol::UTP < protocol))
4611 return;
4613 if (protocol == m_btProtocol) return;
4615 m_btProtocol = protocol;
4616 configureDeferred();
4619 bool SessionImpl::isUTPRateLimited() const
4621 return m_isUTPRateLimited;
4624 void SessionImpl::setUTPRateLimited(const bool limited)
4626 if (limited != m_isUTPRateLimited)
4628 m_isUTPRateLimited = limited;
4629 configureDeferred();
4633 MixedModeAlgorithm SessionImpl::utpMixedMode() const
4635 return m_utpMixedMode;
4638 void SessionImpl::setUtpMixedMode(const MixedModeAlgorithm mode)
4640 if (mode == m_utpMixedMode) return;
4642 m_utpMixedMode = mode;
4643 configureDeferred();
4646 bool SessionImpl::isIDNSupportEnabled() const
4648 return m_IDNSupportEnabled;
4651 void SessionImpl::setIDNSupportEnabled(const bool enabled)
4653 if (enabled == m_IDNSupportEnabled) return;
4655 m_IDNSupportEnabled = enabled;
4656 configureDeferred();
4659 bool SessionImpl::multiConnectionsPerIpEnabled() const
4661 return m_multiConnectionsPerIpEnabled;
4664 void SessionImpl::setMultiConnectionsPerIpEnabled(const bool enabled)
4666 if (enabled == m_multiConnectionsPerIpEnabled) return;
4668 m_multiConnectionsPerIpEnabled = enabled;
4669 configureDeferred();
4672 bool SessionImpl::validateHTTPSTrackerCertificate() const
4674 return m_validateHTTPSTrackerCertificate;
4677 void SessionImpl::setValidateHTTPSTrackerCertificate(const bool enabled)
4679 if (enabled == m_validateHTTPSTrackerCertificate) return;
4681 m_validateHTTPSTrackerCertificate = enabled;
4682 configureDeferred();
4685 bool SessionImpl::isSSRFMitigationEnabled() const
4687 return m_SSRFMitigationEnabled;
4690 void SessionImpl::setSSRFMitigationEnabled(const bool enabled)
4692 if (enabled == m_SSRFMitigationEnabled) return;
4694 m_SSRFMitigationEnabled = enabled;
4695 configureDeferred();
4698 bool SessionImpl::blockPeersOnPrivilegedPorts() const
4700 return m_blockPeersOnPrivilegedPorts;
4703 void SessionImpl::setBlockPeersOnPrivilegedPorts(const bool enabled)
4705 if (enabled == m_blockPeersOnPrivilegedPorts) return;
4707 m_blockPeersOnPrivilegedPorts = enabled;
4708 configureDeferred();
4711 bool SessionImpl::isTrackerFilteringEnabled() const
4713 return m_isTrackerFilteringEnabled;
4716 void SessionImpl::setTrackerFilteringEnabled(const bool enabled)
4718 if (enabled != m_isTrackerFilteringEnabled)
4720 m_isTrackerFilteringEnabled = enabled;
4721 configureDeferred();
4725 bool SessionImpl::isListening() const
4727 return m_nativeSessionExtension->isSessionListening();
4730 MaxRatioAction SessionImpl::maxRatioAction() const
4732 return static_cast<MaxRatioAction>(m_maxRatioAction.get());
4735 void SessionImpl::setMaxRatioAction(const MaxRatioAction act)
4737 m_maxRatioAction = static_cast<int>(act);
4740 bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const
4742 const bool isHybrid = infoHash.isHybrid();
4743 const auto id = TorrentID::fromInfoHash(infoHash);
4744 // alternative ID can be useful to find existing torrent
4745 // in case if hybrid torrent was added by v1 info hash
4746 const auto altID = (isHybrid ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
4748 if (m_loadingTorrents.contains(id) || (isHybrid && m_loadingTorrents.contains(altID)))
4749 return true;
4750 if (m_downloadedMetadata.contains(id) || (isHybrid && m_downloadedMetadata.contains(altID)))
4751 return true;
4752 return findTorrent(infoHash);
4755 void SessionImpl::updateSeedingLimitTimer()
4757 if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
4758 && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit())
4760 if (m_seedingLimitTimer->isActive())
4761 m_seedingLimitTimer->stop();
4763 else if (!m_seedingLimitTimer->isActive())
4765 m_seedingLimitTimer->start();
4769 void SessionImpl::handleTorrentShareLimitChanged(TorrentImpl *const)
4771 updateSeedingLimitTimer();
4774 void SessionImpl::handleTorrentNameChanged(TorrentImpl *const)
4778 void SessionImpl::handleTorrentSavePathChanged(TorrentImpl *const torrent)
4780 emit torrentSavePathChanged(torrent);
4783 void SessionImpl::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
4785 emit torrentCategoryChanged(torrent, oldCategory);
4788 void SessionImpl::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
4790 emit torrentTagAdded(torrent, tag);
4793 void SessionImpl::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
4795 emit torrentTagRemoved(torrent, tag);
4798 void SessionImpl::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
4800 emit torrentSavingModeChanged(torrent);
4803 void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
4805 for (const TrackerEntry &newTracker : newTrackers)
4806 LogMsg(tr("Added tracker to torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), newTracker.url));
4807 emit trackersAdded(torrent, newTrackers);
4808 if (torrent->trackers().size() == newTrackers.size())
4809 emit trackerlessStateChanged(torrent, false);
4810 emit trackersChanged(torrent);
4813 void SessionImpl::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers)
4815 for (const QString &deletedTracker : deletedTrackers)
4816 LogMsg(tr("Removed tracker from torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), deletedTracker));
4817 emit trackersRemoved(torrent, deletedTrackers);
4818 if (torrent->trackers().isEmpty())
4819 emit trackerlessStateChanged(torrent, true);
4820 emit trackersChanged(torrent);
4823 void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
4825 emit trackersChanged(torrent);
4828 void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
4830 for (const QUrl &newUrlSeed : newUrlSeeds)
4831 LogMsg(tr("Added URL seed to torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), newUrlSeed.toString()));
4834 void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
4836 for (const QUrl &urlSeed : urlSeeds)
4837 LogMsg(tr("Removed URL seed from torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), urlSeed.toString()));
4840 void SessionImpl::handleTorrentMetadataReceived(TorrentImpl *const torrent)
4842 if (!torrentExportDirectory().isEmpty())
4843 exportTorrentFile(torrent, torrentExportDirectory());
4845 emit torrentMetadataReceived(torrent);
4848 void SessionImpl::handleTorrentPaused(TorrentImpl *const torrent)
4850 LogMsg(tr("Torrent paused. Torrent: \"%1\"").arg(torrent->name()));
4851 emit torrentPaused(torrent);
4854 void SessionImpl::handleTorrentResumed(TorrentImpl *const torrent)
4856 LogMsg(tr("Torrent resumed. Torrent: \"%1\"").arg(torrent->name()));
4857 emit torrentResumed(torrent);
4860 void SessionImpl::handleTorrentChecked(TorrentImpl *const torrent)
4862 emit torrentFinishedChecking(torrent);
4865 void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
4867 LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
4868 emit torrentFinished(torrent);
4870 if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
4871 exportTorrentFile(torrent, exportPath);
4873 // Check whether it contains .torrent files
4874 for (const Path &torrentRelpath : asConst(torrent->filePaths()))
4876 if (torrentRelpath.hasExtension(u".torrent"_qs))
4878 emit recursiveTorrentDownloadPossible(torrent);
4879 break;
4883 const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
4885 return !(torrent->isFinished() || torrent->isPaused() || torrent->isErrored());
4887 if (!hasUnfinishedTorrents)
4888 emit allTorrentsFinished();
4891 void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
4893 --m_numResumeData;
4895 m_resumeDataStorage->store(torrent->id(), data);
4896 const auto iter = m_changedTorrentIDs.find(torrent->id());
4897 if (iter != m_changedTorrentIDs.end())
4899 m_resumeDataStorage->remove(iter.value());
4900 m_changedTorrentIDs.erase(iter);
4904 void SessionImpl::handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash)
4906 Q_ASSERT(torrent->infoHash().isHybrid());
4908 m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(torrent->infoHash().v1()), torrent);
4910 const auto prevID = TorrentID::fromInfoHash(prevInfoHash);
4911 const TorrentID currentID = torrent->id();
4912 if (currentID != prevID)
4914 m_torrents[torrent->id()] = m_torrents.take(prevID);
4915 m_changedTorrentIDs[torrent->id()] = prevID;
4919 bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode, const MoveStorageContext context)
4921 Q_ASSERT(torrent);
4923 const lt::torrent_handle torrentHandle = torrent->nativeHandle();
4924 const Path currentLocation = torrent->actualStorageLocation();
4925 const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle);
4927 if (m_moveStorageQueue.size() > 1)
4929 auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
4930 , [&torrentHandle](const MoveStorageJob &job)
4932 return job.torrentHandle == torrentHandle;
4935 if (iter != m_moveStorageQueue.end())
4937 // remove existing inactive job
4938 torrent->handleMoveStorageJobFinished(currentLocation, iter->context, torrentHasActiveJob);
4939 LogMsg(tr("Torrent move canceled. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), iter->path.toString()));
4940 m_moveStorageQueue.erase(iter);
4944 if (torrentHasActiveJob)
4946 // if there is active job for this torrent prevent creating meaningless
4947 // job that will move torrent to the same location as current one
4948 if (m_moveStorageQueue.first().path == newPath)
4950 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: torrent is currently moving to the destination")
4951 .arg(torrent->name(), currentLocation.toString(), newPath.toString()));
4952 return false;
4955 else
4957 if (currentLocation == newPath)
4959 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\" Destination: \"%3\". Reason: both paths point to the same location")
4960 .arg(torrent->name(), currentLocation.toString(), newPath.toString()));
4961 return false;
4965 const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode, context};
4966 m_moveStorageQueue << moveStorageJob;
4967 LogMsg(tr("Enqueued torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), newPath.toString()));
4969 if (m_moveStorageQueue.size() == 1)
4970 moveTorrentStorage(moveStorageJob);
4972 return true;
4975 void SessionImpl::moveTorrentStorage(const MoveStorageJob &job) const
4977 #ifdef QBT_USES_LIBTORRENT2
4978 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
4979 #else
4980 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
4981 #endif
4982 const TorrentImpl *torrent = m_torrents.value(id);
4983 const QString torrentName = (torrent ? torrent->name() : id.toString());
4984 LogMsg(tr("Start moving torrent. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, job.path.toString()));
4986 job.torrentHandle.move_storage(job.path.toString().toStdString(), toNative(job.mode));
4989 void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
4991 const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
4992 if (!m_moveStorageQueue.isEmpty())
4993 moveTorrentStorage(m_moveStorageQueue.first());
4995 const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
4996 , [&finishedJob](const MoveStorageJob &job)
4998 return job.torrentHandle == finishedJob.torrentHandle;
5001 const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
5003 TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
5004 if (torrent)
5006 torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
5008 else if (!torrentHasOutstandingJob)
5010 // Last job is completed for torrent that being removing, so actually remove it
5011 const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
5012 const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
5013 if (removingTorrentData.deleteOption == DeleteTorrent)
5014 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
5018 void SessionImpl::storeCategories() const
5020 QJsonObject jsonObj;
5021 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
5023 const QString &categoryName = it.key();
5024 const CategoryOptions &categoryOptions = it.value();
5025 jsonObj[categoryName] = categoryOptions.toJSON();
5028 const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
5029 const QByteArray data = QJsonDocument(jsonObj).toJson();
5030 const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
5031 if (!result)
5033 LogMsg(tr("Failed to save Categories configuration. File: \"%1\". Error: \"%2\"")
5034 .arg(path.toString(), result.error()), Log::WARNING);
5038 void SessionImpl::upgradeCategories()
5040 const auto legacyCategories = SettingValue<QVariantMap>(u"BitTorrent/Session/Categories"_qs).get();
5041 for (auto it = legacyCategories.cbegin(); it != legacyCategories.cend(); ++it)
5043 const QString &categoryName = it.key();
5044 CategoryOptions categoryOptions;
5045 categoryOptions.savePath = Path(it.value().toString());
5046 m_categories[categoryName] = categoryOptions;
5049 storeCategories();
5052 void SessionImpl::loadCategories()
5054 m_categories.clear();
5056 QFile confFile {(specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME).data()};
5057 if (!confFile.exists())
5059 // TODO: Remove the following upgrade code in v4.5
5060 // == BEGIN UPGRADE CODE ==
5061 upgradeCategories();
5062 m_needUpgradeDownloadPath = true;
5063 // == END UPGRADE CODE ==
5065 // return;
5068 if (!confFile.open(QFile::ReadOnly))
5070 LogMsg(tr("Failed to load Categories. File: \"%1\". Error: \"%2\"")
5071 .arg(confFile.fileName(), confFile.errorString()), Log::CRITICAL);
5072 return;
5075 QJsonParseError jsonError;
5076 const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
5077 if (jsonError.error != QJsonParseError::NoError)
5079 LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
5080 .arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
5081 return;
5084 if (!jsonDoc.isObject())
5086 LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Reason: invalid data format")
5087 .arg(confFile.fileName()), Log::WARNING);
5088 return;
5091 const QJsonObject jsonObj = jsonDoc.object();
5092 for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
5094 const QString &categoryName = it.key();
5095 const auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
5096 m_categories[categoryName] = categoryOptions;
5100 bool SessionImpl::hasPerTorrentRatioLimit() const
5102 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5104 return (torrent->ratioLimit() >= 0);
5108 bool SessionImpl::hasPerTorrentSeedingTimeLimit() const
5110 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5112 return (torrent->seedingTimeLimit() >= 0);
5116 void SessionImpl::configureDeferred()
5118 if (m_deferredConfigureScheduled)
5119 return;
5121 m_deferredConfigureScheduled = true;
5122 QMetaObject::invokeMethod(this, qOverload<>(&SessionImpl::configure), Qt::QueuedConnection);
5125 // Enable IP Filtering
5126 // this method creates ban list from scratch combining user ban list and 3rd party ban list file
5127 void SessionImpl::enableIPFilter()
5129 qDebug("Enabling IPFilter");
5130 // 1. Parse the IP filter
5131 // 2. In the slot add the manually banned IPs to the provided lt::ip_filter
5132 // 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
5133 // set between clearing the old one and setting the new one.
5134 if (!m_filterParser)
5136 m_filterParser = new FilterParserThread(this);
5137 connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &SessionImpl::handleIPFilterParsed);
5138 connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &SessionImpl::handleIPFilterError);
5140 m_filterParser->processFilterFile(IPFilterFile());
5143 // Disable IP Filtering
5144 void SessionImpl::disableIPFilter()
5146 qDebug("Disabling IPFilter");
5147 if (m_filterParser)
5149 disconnect(m_filterParser.data(), nullptr, this, nullptr);
5150 delete m_filterParser;
5153 // Add the banned IPs after the IPFilter disabling
5154 // which creates an empty filter and overrides all previously
5155 // applied bans.
5156 lt::ip_filter filter;
5157 processBannedIPs(filter);
5158 m_nativeSession->set_ip_filter(filter);
5161 void SessionImpl::recursiveTorrentDownload(const TorrentID &id)
5163 const TorrentImpl *torrent = m_torrents.value(id);
5164 if (!torrent)
5165 return;
5167 for (const Path &torrentRelpath : asConst(torrent->filePaths()))
5169 if (torrentRelpath.hasExtension(u".torrent"_qs))
5171 const Path torrentFullpath = torrent->savePath() / torrentRelpath;
5173 LogMsg(tr("Recursive download .torrent file within torrent. Source torrent: \"%1\". File: \"%2\"")
5174 .arg(torrent->name(), torrentFullpath.toString()));
5176 AddTorrentParams params;
5177 // Passing the save path along to the sub torrent file
5178 params.savePath = torrent->savePath();
5179 const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(torrentFullpath);
5180 if (loadResult)
5182 addTorrent(loadResult.value(), params);
5184 else
5186 LogMsg(tr("Failed to load .torrent file within torrent. Source torrent: \"%1\". File: \"%2\". Error: \"%3\"")
5187 .arg(torrent->name(), torrentFullpath.toString(), loadResult.error()), Log::WARNING);
5193 const SessionStatus &SessionImpl::status() const
5195 return m_status;
5198 const CacheStatus &SessionImpl::cacheStatus() const
5200 return m_cacheStatus;
5203 void SessionImpl::enqueueRefresh()
5205 Q_ASSERT(!m_refreshEnqueued);
5207 QTimer::singleShot(refreshInterval(), Qt::CoarseTimer, this, [this]
5209 m_nativeSession->post_torrent_updates();
5210 m_nativeSession->post_session_stats();
5212 if (m_torrentsQueueChanged)
5214 m_torrentsQueueChanged = false;
5215 m_needSaveTorrentsQueue = true;
5219 m_refreshEnqueued = true;
5222 void SessionImpl::handleIPFilterParsed(const int ruleCount)
5224 if (m_filterParser)
5226 lt::ip_filter filter = m_filterParser->IPfilter();
5227 processBannedIPs(filter);
5228 m_nativeSession->set_ip_filter(filter);
5230 LogMsg(tr("Successfully parsed the IP filter file. Number of rules applied: %1").arg(ruleCount));
5231 emit IPFilterParsed(false, ruleCount);
5234 void SessionImpl::handleIPFilterError()
5236 lt::ip_filter filter;
5237 processBannedIPs(filter);
5238 m_nativeSession->set_ip_filter(filter);
5240 LogMsg(tr("Failed to parse the IP filter file"), Log::WARNING);
5241 emit IPFilterParsed(true, 0);
5244 std::vector<lt::alert *> SessionImpl::getPendingAlerts(const lt::time_duration time) const
5246 if (time > lt::time_duration::zero())
5247 m_nativeSession->wait_for_alert(time);
5249 std::vector<lt::alert *> alerts;
5250 m_nativeSession->pop_alerts(&alerts);
5251 return alerts;
5254 TorrentContentLayout SessionImpl::torrentContentLayout() const
5256 return m_torrentContentLayout;
5259 void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value)
5261 m_torrentContentLayout = value;
5264 // Read alerts sent by the BitTorrent session
5265 void SessionImpl::readAlerts()
5267 const std::vector<lt::alert *> alerts = getPendingAlerts();
5268 handleAddTorrentAlerts(alerts);
5269 for (const lt::alert *a : alerts)
5270 handleAlert(a);
5272 processTrackerStatuses();
5275 void SessionImpl::handleAddTorrentAlerts(const std::vector<lt::alert *> &alerts)
5277 QVector<Torrent *> loadedTorrents;
5278 if (!isRestored())
5279 loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
5281 for (const lt::alert *a : alerts)
5283 if (a->type() != lt::add_torrent_alert::alert_type)
5284 continue;
5286 const auto *alert = static_cast<const lt::add_torrent_alert *>(a);
5287 if (alert->error)
5289 const QString msg = QString::fromStdString(alert->message());
5290 LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(msg), Log::WARNING);
5291 emit loadTorrentFailed(msg);
5293 const lt::add_torrent_params &params = alert->params;
5294 const bool hasMetadata = (params.ti && params.ti->is_valid());
5295 #ifdef QBT_USES_LIBTORRENT2
5296 const InfoHash infoHash {(hasMetadata ? params.ti->info_hashes() : params.info_hashes)};
5297 if (infoHash.isHybrid())
5298 m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
5299 #else
5300 const InfoHash infoHash {(hasMetadata ? params.ti->info_hash() : params.info_hash)};
5301 #endif
5302 if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
5303 ; loadingTorrentsIter != m_loadingTorrents.end())
5305 m_loadingTorrents.erase(loadingTorrentsIter);
5307 else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
5308 ; downloadedMetadataIter != m_downloadedMetadata.end())
5310 m_downloadedMetadata.erase(downloadedMetadataIter);
5311 if (infoHash.isHybrid())
5313 // index hybrid magnet links by both v1 and v2 info hashes
5314 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5315 m_downloadedMetadata.remove(altID);
5319 return;
5323 #ifdef QBT_USES_LIBTORRENT2
5324 const InfoHash infoHash {alert->handle.info_hashes()};
5325 #else
5326 const InfoHash infoHash {alert->handle.info_hash()};
5327 #endif
5328 const auto torrentID = TorrentID::fromInfoHash(infoHash);
5330 if (const auto loadingTorrentsIter = m_loadingTorrents.find(torrentID)
5331 ; loadingTorrentsIter != m_loadingTorrents.end())
5333 const LoadTorrentParams params = loadingTorrentsIter.value();
5334 m_loadingTorrents.erase(loadingTorrentsIter);
5336 Torrent *torrent = createTorrent(alert->handle, params);
5337 loadedTorrents.append(torrent);
5339 else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(torrentID)
5340 ; downloadedMetadataIter != m_downloadedMetadata.end())
5342 downloadedMetadataIter.value() = alert->handle;
5343 if (infoHash.isHybrid())
5345 // index hybrid magnet links by both v1 and v2 info hashes
5346 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5347 m_downloadedMetadata[altID] = alert->handle;
5352 if (!loadedTorrents.isEmpty())
5354 if (isRestored())
5355 m_torrentsQueueChanged = true;
5356 emit torrentsLoaded(loadedTorrents);
5360 void SessionImpl::handleAlert(const lt::alert *a)
5364 switch (a->type())
5366 #ifdef QBT_USES_LIBTORRENT2
5367 case lt::file_prio_alert::alert_type:
5368 #endif
5369 case lt::file_renamed_alert::alert_type:
5370 case lt::file_rename_failed_alert::alert_type:
5371 case lt::file_completed_alert::alert_type:
5372 case lt::torrent_finished_alert::alert_type:
5373 case lt::save_resume_data_alert::alert_type:
5374 case lt::save_resume_data_failed_alert::alert_type:
5375 case lt::torrent_paused_alert::alert_type:
5376 case lt::torrent_resumed_alert::alert_type:
5377 case lt::fastresume_rejected_alert::alert_type:
5378 case lt::torrent_checked_alert::alert_type:
5379 case lt::metadata_received_alert::alert_type:
5380 case lt::performance_alert::alert_type:
5381 dispatchTorrentAlert(static_cast<const lt::torrent_alert *>(a));
5382 break;
5383 case lt::state_update_alert::alert_type:
5384 handleStateUpdateAlert(static_cast<const lt::state_update_alert*>(a));
5385 break;
5386 case lt::session_stats_alert::alert_type:
5387 handleSessionStatsAlert(static_cast<const lt::session_stats_alert*>(a));
5388 break;
5389 case lt::tracker_announce_alert::alert_type:
5390 case lt::tracker_error_alert::alert_type:
5391 case lt::tracker_reply_alert::alert_type:
5392 case lt::tracker_warning_alert::alert_type:
5393 handleTrackerAlert(static_cast<const lt::tracker_alert *>(a));
5394 break;
5395 case lt::file_error_alert::alert_type:
5396 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
5397 break;
5398 case lt::add_torrent_alert::alert_type:
5399 // handled separately
5400 break;
5401 case lt::torrent_removed_alert::alert_type:
5402 handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert*>(a));
5403 break;
5404 case lt::torrent_deleted_alert::alert_type:
5405 handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert*>(a));
5406 break;
5407 case lt::torrent_delete_failed_alert::alert_type:
5408 handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert*>(a));
5409 break;
5410 case lt::portmap_error_alert::alert_type:
5411 handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert*>(a));
5412 break;
5413 case lt::portmap_alert::alert_type:
5414 handlePortmapAlert(static_cast<const lt::portmap_alert*>(a));
5415 break;
5416 case lt::peer_blocked_alert::alert_type:
5417 handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert*>(a));
5418 break;
5419 case lt::peer_ban_alert::alert_type:
5420 handlePeerBanAlert(static_cast<const lt::peer_ban_alert*>(a));
5421 break;
5422 case lt::url_seed_alert::alert_type:
5423 handleUrlSeedAlert(static_cast<const lt::url_seed_alert*>(a));
5424 break;
5425 case lt::listen_succeeded_alert::alert_type:
5426 handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert*>(a));
5427 break;
5428 case lt::listen_failed_alert::alert_type:
5429 handleListenFailedAlert(static_cast<const lt::listen_failed_alert*>(a));
5430 break;
5431 case lt::external_ip_alert::alert_type:
5432 handleExternalIPAlert(static_cast<const lt::external_ip_alert*>(a));
5433 break;
5434 case lt::alerts_dropped_alert::alert_type:
5435 handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(a));
5436 break;
5437 case lt::storage_moved_alert::alert_type:
5438 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert*>(a));
5439 break;
5440 case lt::storage_moved_failed_alert::alert_type:
5441 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert*>(a));
5442 break;
5443 case lt::socks5_alert::alert_type:
5444 handleSocks5Alert(static_cast<const lt::socks5_alert *>(a));
5445 break;
5446 #ifdef QBT_USES_LIBTORRENT2
5447 case lt::torrent_conflict_alert::alert_type:
5448 handleTorrentConflictAlert(static_cast<const lt::torrent_conflict_alert *>(a));
5449 break;
5450 #endif
5453 catch (const std::exception &exc)
5455 qWarning() << "Caught exception in " << Q_FUNC_INFO << ": " << QString::fromStdString(exc.what());
5459 void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert *a)
5461 const TorrentID torrentID {a->handle.info_hash()};
5462 TorrentImpl *torrent = m_torrents.value(torrentID);
5463 #ifdef QBT_USES_LIBTORRENT2
5464 if (!torrent && (a->type() == lt::metadata_received_alert::alert_type))
5466 const InfoHash infoHash {a->handle.info_hashes()};
5467 if (infoHash.isHybrid())
5468 torrent = m_torrents.value(TorrentID::fromSHA1Hash(infoHash.v1()));
5470 #endif
5472 if (torrent)
5474 torrent->handleAlert(a);
5475 return;
5478 switch (a->type())
5480 case lt::metadata_received_alert::alert_type:
5481 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert *>(a));
5482 break;
5486 TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
5488 auto *const torrent = new TorrentImpl(this, m_nativeSession, nativeHandle, params);
5489 m_torrents.insert(torrent->id(), torrent);
5490 if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
5491 m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent);
5493 if (isRestored())
5495 if (params.addToQueueTop)
5496 nativeHandle.queue_position_top();
5498 torrent->saveResumeData(lt::torrent_handle::save_info_dict);
5500 // The following is useless for newly added magnet
5501 if (torrent->hasMetadata())
5503 if (!torrentExportDirectory().isEmpty())
5504 exportTorrentFile(torrent, torrentExportDirectory());
5508 if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
5509 && !m_seedingLimitTimer->isActive())
5511 m_seedingLimitTimer->start();
5514 if (!isRestored())
5516 LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name()));
5518 else
5520 LogMsg(tr("Added new torrent. Torrent: \"%1\"").arg(torrent->name()));
5521 emit torrentAdded(torrent);
5524 // Torrent could have error just after adding to libtorrent
5525 if (torrent->hasError())
5526 LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: \"%2\"").arg(torrent->name(), torrent->error()), Log::WARNING);
5528 return torrent;
5531 void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
5533 #ifdef QBT_USES_LIBTORRENT2
5534 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5535 #else
5536 const auto id = TorrentID::fromInfoHash(p->info_hash);
5537 #endif
5539 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5540 if (removingTorrentDataIter != m_removingTorrents.end())
5542 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
5544 LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5545 m_removingTorrents.erase(removingTorrentDataIter);
5550 void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
5552 #ifdef QBT_USES_LIBTORRENT2
5553 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5554 #else
5555 const auto id = TorrentID::fromInfoHash(p->info_hash);
5556 #endif
5558 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5559 if (removingTorrentDataIter == m_removingTorrents.end())
5560 return;
5562 // torrent_deleted_alert can also be posted due to deletion of partfile. Ignore it in such a case.
5563 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
5564 return;
5566 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
5567 LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5568 m_removingTorrents.erase(removingTorrentDataIter);
5571 void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
5573 #ifdef QBT_USES_LIBTORRENT2
5574 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5575 #else
5576 const auto id = TorrentID::fromInfoHash(p->info_hash);
5577 #endif
5579 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5580 if (removingTorrentDataIter == m_removingTorrents.end())
5581 return;
5583 if (p->error)
5585 // libtorrent won't delete the directory if it contains files not listed in the torrent,
5586 // so we remove the directory ourselves
5587 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
5589 LogMsg(tr("Removed torrent but failed to delete its content and/or partfile. Torrent: \"%1\". Error: \"%2\"")
5590 .arg(removingTorrentDataIter->name, QString::fromLocal8Bit(p->error.message().c_str()))
5591 , Log::WARNING);
5593 else // torrent without metadata, hence no files on disk
5595 LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5598 m_removingTorrents.erase(removingTorrentDataIter);
5601 void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
5603 const TorrentID torrentID {p->handle.info_hash()};
5605 bool found = false;
5606 if (const auto iter = m_downloadedMetadata.find(torrentID); iter != m_downloadedMetadata.end())
5608 found = true;
5609 m_downloadedMetadata.erase(iter);
5611 #ifdef QBT_USES_LIBTORRENT2
5612 const InfoHash infoHash {p->handle.info_hashes()};
5613 if (infoHash.isHybrid())
5615 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5616 if (const auto iter = m_downloadedMetadata.find(altID); iter != m_downloadedMetadata.end())
5618 found = true;
5619 m_downloadedMetadata.erase(iter);
5622 #endif
5623 if (found)
5625 const TorrentInfo metadata {*p->handle.torrent_file()};
5626 m_nativeSession->remove_torrent(p->handle, lt::session::delete_files);
5628 emit metadataDownloaded(metadata);
5632 void SessionImpl::handleFileErrorAlert(const lt::file_error_alert *p)
5634 TorrentImpl *const torrent = m_torrents.value(p->handle.info_hash());
5635 if (!torrent)
5636 return;
5638 torrent->handleAlert(p);
5640 const TorrentID id = torrent->id();
5641 if (!m_recentErroredTorrents.contains(id))
5643 m_recentErroredTorrents.insert(id);
5645 const QString msg = QString::fromStdString(p->message());
5646 LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: \"%3\"")
5647 .arg(torrent->name(), QString::fromLocal8Bit(p->filename()), msg)
5648 , Log::WARNING);
5649 emit fullDiskError(torrent, msg);
5652 m_recentErroredTorrentsTimer->start();
5655 void SessionImpl::handlePortmapWarningAlert(const lt::portmap_error_alert *p)
5657 LogMsg(tr("UPnP/NAT-PMP port mapping failed. Message: \"%1\"").arg(QString::fromStdString(p->message())), Log::WARNING);
5660 void SessionImpl::handlePortmapAlert(const lt::portmap_alert *p)
5662 qDebug("UPnP Success, msg: %s", p->message().c_str());
5663 LogMsg(tr("UPnP/NAT-PMP port mapping succeeded. Message: \"%1\"").arg(QString::fromStdString(p->message())), Log::INFO);
5666 void SessionImpl::handlePeerBlockedAlert(const lt::peer_blocked_alert *p)
5668 QString reason;
5669 switch (p->reason)
5671 case lt::peer_blocked_alert::ip_filter:
5672 reason = tr("IP filter", "this peer was blocked. Reason: IP filter.");
5673 break;
5674 case lt::peer_blocked_alert::port_filter:
5675 reason = tr("filtered port (%1)", "this peer was blocked. Reason: filtered port (8899).").arg(QString::number(p->endpoint.port()));
5676 break;
5677 case lt::peer_blocked_alert::i2p_mixed:
5678 reason = tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg(u"I2P"_qs); // don't translate I2P
5679 break;
5680 case lt::peer_blocked_alert::privileged_ports:
5681 reason = tr("privileged port (%1)", "this peer was blocked. Reason: privileged port (80).").arg(QString::number(p->endpoint.port()));
5682 break;
5683 case lt::peer_blocked_alert::utp_disabled:
5684 reason = tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(C_UTP); // don't translate μTP
5685 break;
5686 case lt::peer_blocked_alert::tcp_disabled:
5687 reason = tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg(u"TCP"_qs); // don't translate TCP
5688 break;
5691 const QString ip {toString(p->endpoint.address())};
5692 if (!ip.isEmpty())
5693 Logger::instance()->addPeer(ip, true, reason);
5696 void SessionImpl::handlePeerBanAlert(const lt::peer_ban_alert *p)
5698 const QString ip {toString(p->endpoint.address())};
5699 if (!ip.isEmpty())
5700 Logger::instance()->addPeer(ip, false);
5703 void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert *p)
5705 const TorrentImpl *torrent = m_torrents.value(p->handle.info_hash());
5706 if (!torrent)
5707 return;
5709 if (p->error)
5711 LogMsg(tr("URL seed DNS lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
5712 .arg(torrent->name(), QString::fromUtf8(p->server_url()), QString::fromStdString(p->message()))
5713 , Log::WARNING);
5715 else
5717 LogMsg(tr("Received error message from URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
5718 .arg(torrent->name(), QString::fromUtf8(p->server_url()), QString::fromUtf8(p->error_message()))
5719 , Log::WARNING);
5723 void SessionImpl::handleListenSucceededAlert(const lt::listen_succeeded_alert *p)
5725 const QString proto {toString(p->socket_type)};
5726 LogMsg(tr("Successfully listening on IP. IP: \"%1\". Port: \"%2/%3\"")
5727 .arg(toString(p->address), proto, QString::number(p->port)), Log::INFO);
5730 void SessionImpl::handleListenFailedAlert(const lt::listen_failed_alert *p)
5732 const QString proto {toString(p->socket_type)};
5733 LogMsg(tr("Failed to listen on IP. IP: \"%1\". Port: \"%2/%3\". Reason: \"%4\"")
5734 .arg(toString(p->address), proto, QString::number(p->port)
5735 , QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
5738 void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *p)
5740 const QString externalIP {toString(p->external_address)};
5741 LogMsg(tr("Detected external IP. IP: \"%1\"")
5742 .arg(externalIP), Log::INFO);
5744 if (m_lastExternalIP != externalIP)
5746 if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty())
5747 reannounceToAllTrackers();
5748 m_lastExternalIP = externalIP;
5752 void SessionImpl::handleSessionStatsAlert(const lt::session_stats_alert *p)
5754 if (m_refreshEnqueued)
5755 m_refreshEnqueued = false;
5756 else
5757 enqueueRefresh();
5759 const int64_t interval = lt::total_microseconds(p->timestamp() - m_statsLastTimestamp);
5760 if (interval <= 0)
5761 return;
5763 m_statsLastTimestamp = p->timestamp();
5765 const auto stats = p->counters();
5767 m_status.hasIncomingConnections = static_cast<bool>(stats[m_metricIndices.net.hasIncomingConnections]);
5769 const int64_t ipOverheadDownload = stats[m_metricIndices.net.recvIPOverheadBytes];
5770 const int64_t ipOverheadUpload = stats[m_metricIndices.net.sentIPOverheadBytes];
5771 const int64_t totalDownload = stats[m_metricIndices.net.recvBytes] + ipOverheadDownload;
5772 const int64_t totalUpload = stats[m_metricIndices.net.sentBytes] + ipOverheadUpload;
5773 const int64_t totalPayloadDownload = stats[m_metricIndices.net.recvPayloadBytes];
5774 const int64_t totalPayloadUpload = stats[m_metricIndices.net.sentPayloadBytes];
5775 const int64_t trackerDownload = stats[m_metricIndices.net.recvTrackerBytes];
5776 const int64_t trackerUpload = stats[m_metricIndices.net.sentTrackerBytes];
5777 const int64_t dhtDownload = stats[m_metricIndices.dht.dhtBytesIn];
5778 const int64_t dhtUpload = stats[m_metricIndices.dht.dhtBytesOut];
5780 const auto calcRate = [interval](const qint64 previous, const qint64 current) -> qint64
5782 Q_ASSERT(current >= previous);
5783 Q_ASSERT(interval >= 0);
5784 return (((current - previous) * lt::microseconds(1s).count()) / interval);
5787 m_status.payloadDownloadRate = calcRate(m_status.totalPayloadDownload, totalPayloadDownload);
5788 m_status.payloadUploadRate = calcRate(m_status.totalPayloadUpload, totalPayloadUpload);
5789 m_status.downloadRate = calcRate(m_status.totalDownload, totalDownload);
5790 m_status.uploadRate = calcRate(m_status.totalUpload, totalUpload);
5791 m_status.ipOverheadDownloadRate = calcRate(m_status.ipOverheadDownload, ipOverheadDownload);
5792 m_status.ipOverheadUploadRate = calcRate(m_status.ipOverheadUpload, ipOverheadUpload);
5793 m_status.dhtDownloadRate = calcRate(m_status.dhtDownload, dhtDownload);
5794 m_status.dhtUploadRate = calcRate(m_status.dhtUpload, dhtUpload);
5795 m_status.trackerDownloadRate = calcRate(m_status.trackerDownload, trackerDownload);
5796 m_status.trackerUploadRate = calcRate(m_status.trackerUpload, trackerUpload);
5798 m_status.totalPayloadDownload = totalPayloadDownload;
5799 m_status.totalPayloadUpload = totalPayloadUpload;
5800 m_status.ipOverheadDownload = ipOverheadDownload;
5801 m_status.ipOverheadUpload = ipOverheadUpload;
5802 m_status.trackerDownload = trackerDownload;
5803 m_status.trackerUpload = trackerUpload;
5804 m_status.dhtDownload = dhtDownload;
5805 m_status.dhtUpload = dhtUpload;
5806 m_status.totalWasted = stats[m_metricIndices.net.recvRedundantBytes]
5807 + stats[m_metricIndices.net.recvFailedBytes];
5808 m_status.dhtNodes = stats[m_metricIndices.dht.dhtNodes];
5809 m_status.diskReadQueue = stats[m_metricIndices.peer.numPeersUpDisk];
5810 m_status.diskWriteQueue = stats[m_metricIndices.peer.numPeersDownDisk];
5811 m_status.peersCount = stats[m_metricIndices.peer.numPeersConnected];
5813 if (totalDownload > m_status.totalDownload)
5815 m_status.totalDownload = totalDownload;
5816 m_isStatisticsDirty = true;
5819 if (totalUpload > m_status.totalUpload)
5821 m_status.totalUpload = totalUpload;
5822 m_isStatisticsDirty = true;
5825 m_status.allTimeDownload = m_previouslyDownloaded + m_status.totalDownload;
5826 m_status.allTimeUpload = m_previouslyUploaded + m_status.totalUpload;
5828 if (m_statisticsLastUpdateTimer.hasExpired(STATISTICS_SAVE_INTERVAL))
5829 saveStatistics();
5831 m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
5832 m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
5834 #ifndef QBT_USES_LIBTORRENT2
5835 const int64_t numBlocksRead = stats[m_metricIndices.disk.numBlocksRead];
5836 const int64_t numBlocksCacheHits = stats[m_metricIndices.disk.numBlocksCacheHits];
5837 m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max<int64_t>((numBlocksCacheHits + numBlocksRead), 1);
5838 #endif
5840 const int64_t totalJobs = stats[m_metricIndices.disk.writeJobs] + stats[m_metricIndices.disk.readJobs]
5841 + stats[m_metricIndices.disk.hashJobs];
5842 m_cacheStatus.averageJobTime = (totalJobs > 0)
5843 ? (stats[m_metricIndices.disk.diskJobTime] / totalJobs) : 0;
5845 emit statsUpdated();
5848 void SessionImpl::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
5850 LogMsg(tr("Error: Internal alert queue is full and alerts are dropped, you might see degraded performance. Dropped alert type: \"%1\". Message: \"%2\"")
5851 .arg(QString::fromStdString(p->dropped_alerts.to_string()), QString::fromStdString(p->message())), Log::CRITICAL);
5854 void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *p)
5856 Q_ASSERT(!m_moveStorageQueue.isEmpty());
5858 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
5859 Q_ASSERT(currentJob.torrentHandle == p->handle);
5861 const Path newPath {QString::fromUtf8(p->storage_path())};
5862 Q_ASSERT(newPath == currentJob.path);
5864 #ifdef QBT_USES_LIBTORRENT2
5865 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
5866 #else
5867 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
5868 #endif
5870 TorrentImpl *torrent = m_torrents.value(id);
5871 const QString torrentName = (torrent ? torrent->name() : id.toString());
5872 LogMsg(tr("Moved torrent successfully. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, newPath.toString()));
5874 handleMoveTorrentStorageJobFinished(newPath);
5877 void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
5879 Q_ASSERT(!m_moveStorageQueue.isEmpty());
5881 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
5882 Q_ASSERT(currentJob.torrentHandle == p->handle);
5884 #ifdef QBT_USES_LIBTORRENT2
5885 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
5886 #else
5887 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
5888 #endif
5890 TorrentImpl *torrent = m_torrents.value(id);
5891 const QString torrentName = (torrent ? torrent->name() : id.toString());
5892 const Path currentLocation = (torrent ? torrent->actualStorageLocation()
5893 : Path(p->handle.status(lt::torrent_handle::query_save_path).save_path));
5894 const QString errorMessage = QString::fromStdString(p->message());
5895 LogMsg(tr("Failed to move torrent. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: \"%4\"")
5896 .arg(torrentName, currentLocation.toString(), currentJob.path.toString(), errorMessage), Log::WARNING);
5898 handleMoveTorrentStorageJobFinished(currentLocation);
5901 void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *p)
5903 QVector<Torrent *> updatedTorrents;
5904 updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(p->status.size()));
5906 for (const lt::torrent_status &status : p->status)
5908 #ifdef QBT_USES_LIBTORRENT2
5909 const auto id = TorrentID::fromInfoHash(status.info_hashes);
5910 #else
5911 const auto id = TorrentID::fromInfoHash(status.info_hash);
5912 #endif
5913 TorrentImpl *const torrent = m_torrents.value(id);
5914 if (!torrent)
5915 continue;
5917 torrent->handleStateUpdate(status);
5918 updatedTorrents.push_back(torrent);
5921 if (!updatedTorrents.isEmpty())
5922 emit torrentsUpdated(updatedTorrents);
5924 if (m_needSaveTorrentsQueue)
5925 saveTorrentsQueue();
5927 if (m_refreshEnqueued)
5928 m_refreshEnqueued = false;
5929 else
5930 enqueueRefresh();
5933 void SessionImpl::handleSocks5Alert(const lt::socks5_alert *p) const
5935 if (p->error)
5937 const auto addr = p->ip.address();
5938 const QString endpoint = (addr.is_v6() ? u"[%1]:%2"_qs : u"%1:%2"_qs)
5939 .arg(QString::fromStdString(addr.to_string()), QString::number(p->ip.port()));
5940 LogMsg(tr("SOCKS5 proxy error. Address: %1. Message: \"%2\".")
5941 .arg(endpoint, QString::fromLocal8Bit(p->error.message().c_str()))
5942 , Log::WARNING);
5946 void SessionImpl::handleTrackerAlert(const lt::tracker_alert *a)
5948 TorrentImpl *torrent = m_torrents.value(a->handle.info_hash());
5949 if (!torrent)
5950 return;
5952 QMap<TrackerEntry::Endpoint, int> &updateInfo = m_updatedTrackerEntries[torrent->nativeHandle()][std::string(a->tracker_url())];
5954 if (a->type() == lt::tracker_reply_alert::alert_type)
5956 const int numPeers = static_cast<const lt::tracker_reply_alert *>(a)->num_peers;
5957 updateInfo.insert(a->local_endpoint, numPeers);
5961 #ifdef QBT_USES_LIBTORRENT2
5962 void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a)
5964 const auto torrentIDv1 = TorrentID::fromSHA1Hash(a->metadata->info_hashes().v1);
5965 const auto torrentIDv2 = TorrentID::fromSHA256Hash(a->metadata->info_hashes().v2);
5966 TorrentImpl *torrent1 = m_torrents.value(torrentIDv1);
5967 TorrentImpl *torrent2 = m_torrents.value(torrentIDv2);
5968 if (torrent2)
5970 if (torrent1)
5971 deleteTorrent(torrentIDv1);
5972 else
5973 cancelDownloadMetadata(torrentIDv1);
5975 invokeAsync([torrentHandle = torrent2->nativeHandle(), metadata = a->metadata]
5979 torrentHandle.set_metadata(metadata->info_section());
5981 catch (const std::exception &) {}
5984 else if (torrent1)
5986 if (!torrent2)
5987 cancelDownloadMetadata(torrentIDv2);
5989 invokeAsync([torrentHandle = torrent1->nativeHandle(), metadata = a->metadata]
5993 torrentHandle.set_metadata(metadata->info_section());
5995 catch (const std::exception &) {}
5998 else
6000 cancelDownloadMetadata(torrentIDv1);
6001 cancelDownloadMetadata(torrentIDv2);
6004 if (!torrent1 || !torrent2)
6005 emit metadataDownloaded(TorrentInfo(*a->metadata));
6007 #endif
6009 void SessionImpl::processTrackerStatuses()
6011 if (m_updatedTrackerEntries.isEmpty())
6012 return;
6014 for (auto it = m_updatedTrackerEntries.cbegin(); it != m_updatedTrackerEntries.cend(); ++it)
6016 invokeAsync([this, torrentHandle = it.key(), updatedTrackers = it.value()]() mutable
6020 std::vector<lt::announce_entry> nativeTrackers = torrentHandle.trackers();
6021 invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers)
6022 , updatedTrackers = std::move(updatedTrackers)]
6024 TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
6025 if (!torrent)
6026 return;
6028 QHash<QString, TrackerEntry> updatedTrackerEntries;
6029 updatedTrackerEntries.reserve(updatedTrackers.size());
6030 for (const lt::announce_entry &announceEntry : nativeTrackers)
6032 const auto updatedTrackersIter = updatedTrackers.find(announceEntry.url);
6033 if (updatedTrackersIter == updatedTrackers.end())
6034 continue;
6036 const QMap<TrackerEntry::Endpoint, int> &updateInfo = updatedTrackersIter.value();
6037 TrackerEntry trackerEntry = torrent->updateTrackerEntry(announceEntry, updateInfo);
6038 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
6039 updatedTrackerEntries[trackerEntry.url] = std::move(trackerEntry);
6040 #else
6041 updatedTrackerEntries.emplace(trackerEntry.url, std::move(trackerEntry));
6042 #endif
6045 emit trackerEntriesUpdated(torrent, updatedTrackerEntries);
6048 catch (const std::exception &)
6054 m_updatedTrackerEntries.clear();
6057 void SessionImpl::saveStatistics() const
6059 if (!m_isStatisticsDirty)
6060 return;
6062 const QVariantHash stats {
6063 {u"AlltimeDL"_qs, m_status.allTimeDownload},
6064 {u"AlltimeUL"_qs, m_status.allTimeUpload}};
6065 std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_qs);
6066 settings->setValue(u"Stats/AllStats"_qs, stats);
6068 m_statisticsLastUpdateTimer.start();
6069 m_isStatisticsDirty = false;
6072 void SessionImpl::loadStatistics()
6074 const std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_qs);
6075 const QVariantHash value = settings->value(u"Stats/AllStats"_qs).toHash();
6077 m_previouslyDownloaded = value[u"AlltimeDL"_qs].toLongLong();
6078 m_previouslyUploaded = value[u"AlltimeUL"_qs].toLongLong();