Change "metadata received" stop condition behavior
[qBittorrent.git] / src / base / bittorrent / sessionimpl.cpp
blobff57f6646ddb71a39df6cbcb2e9b164a1c0324b6
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "sessionimpl.h"
32 #include <algorithm>
33 #include <chrono>
34 #include <cstdint>
35 #include <ctime>
36 #include <queue>
37 #include <string>
39 #ifdef Q_OS_WIN
40 #include <windows.h>
41 #include <wincrypt.h>
42 #include <iphlpapi.h>
43 #endif
45 #include <boost/asio/ip/tcp.hpp>
47 #include <libtorrent/add_torrent_params.hpp>
48 #include <libtorrent/address.hpp>
49 #include <libtorrent/alert_types.hpp>
50 #include <libtorrent/error_code.hpp>
51 #include <libtorrent/extensions/smart_ban.hpp>
52 #include <libtorrent/extensions/ut_metadata.hpp>
53 #include <libtorrent/extensions/ut_pex.hpp>
54 #include <libtorrent/ip_filter.hpp>
55 #include <libtorrent/magnet_uri.hpp>
56 #include <libtorrent/session.hpp>
57 #include <libtorrent/session_stats.hpp>
58 #include <libtorrent/session_status.hpp>
59 #include <libtorrent/torrent_info.hpp>
61 #include <QDebug>
62 #include <QDir>
63 #include <QHostAddress>
64 #include <QJsonArray>
65 #include <QJsonDocument>
66 #include <QJsonObject>
67 #include <QJsonValue>
68 #include <QNetworkAddressEntry>
69 #include <QNetworkInterface>
70 #include <QRegularExpression>
71 #include <QString>
72 #include <QThread>
73 #include <QThreadPool>
74 #include <QTimer>
75 #include <QUuid>
77 #include "base/algorithm.h"
78 #include "base/global.h"
79 #include "base/logger.h"
80 #include "base/net/proxyconfigurationmanager.h"
81 #include "base/preferences.h"
82 #include "base/profile.h"
83 #include "base/unicodestrings.h"
84 #include "base/utils/fs.h"
85 #include "base/utils/io.h"
86 #include "base/utils/net.h"
87 #include "base/utils/random.h"
88 #include "base/version.h"
89 #include "bandwidthscheduler.h"
90 #include "bencoderesumedatastorage.h"
91 #include "customstorage.h"
92 #include "dbresumedatastorage.h"
93 #include "downloadpriority.h"
94 #include "extensiondata.h"
95 #include "filesearcher.h"
96 #include "filterparserthread.h"
97 #include "loadtorrentparams.h"
98 #include "lttypecast.h"
99 #include "nativesessionextension.h"
100 #include "portforwarderimpl.h"
101 #include "resumedatastorage.h"
102 #include "torrentdescriptor.h"
103 #include "torrentimpl.h"
104 #include "tracker.h"
106 using namespace std::chrono_literals;
107 using namespace BitTorrent;
109 const Path CATEGORIES_FILE_NAME {u"categories.json"_s};
110 const int MAX_PROCESSING_RESUMEDATA_COUNT = 50;
111 const int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count();
113 namespace
115 const char PEER_ID[] = "qB";
116 const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2);
117 const QString DEFAULT_DHT_BOOTSTRAP_NODES = u"dht.libtorrent.org:25401, dht.transmissionbt.com:6881, router.bittorrent.com:6881, router.utorrent.com:6881, dht.aelitis.com:6881"_s;
119 void torrentQueuePositionUp(const lt::torrent_handle &handle)
123 handle.queue_position_up();
125 catch (const std::exception &exc)
127 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
131 void torrentQueuePositionDown(const lt::torrent_handle &handle)
135 handle.queue_position_down();
137 catch (const std::exception &exc)
139 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
143 void torrentQueuePositionTop(const lt::torrent_handle &handle)
147 handle.queue_position_top();
149 catch (const std::exception &exc)
151 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
155 void torrentQueuePositionBottom(const lt::torrent_handle &handle)
159 handle.queue_position_bottom();
161 catch (const std::exception &exc)
163 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
167 QMap<QString, CategoryOptions> expandCategories(const QMap<QString, CategoryOptions> &categories)
169 QMap<QString, CategoryOptions> expanded = categories;
171 for (auto i = categories.cbegin(); i != categories.cend(); ++i)
173 const QString &category = i.key();
174 for (const QString &subcat : asConst(Session::expandCategory(category)))
176 if (!expanded.contains(subcat))
177 expanded[subcat] = {};
181 return expanded;
184 QString toString(const lt::socket_type_t socketType)
186 switch (socketType)
188 #ifdef QBT_USES_LIBTORRENT2
189 case lt::socket_type_t::http:
190 return u"HTTP"_s;
191 case lt::socket_type_t::http_ssl:
192 return u"HTTP_SSL"_s;
193 #endif
194 case lt::socket_type_t::i2p:
195 return u"I2P"_s;
196 case lt::socket_type_t::socks5:
197 return u"SOCKS5"_s;
198 #ifdef QBT_USES_LIBTORRENT2
199 case lt::socket_type_t::socks5_ssl:
200 return u"SOCKS5_SSL"_s;
201 #endif
202 case lt::socket_type_t::tcp:
203 return u"TCP"_s;
204 case lt::socket_type_t::tcp_ssl:
205 return u"TCP_SSL"_s;
206 #ifdef QBT_USES_LIBTORRENT2
207 case lt::socket_type_t::utp:
208 return u"UTP"_s;
209 #else
210 case lt::socket_type_t::udp:
211 return u"UDP"_s;
212 #endif
213 case lt::socket_type_t::utp_ssl:
214 return u"UTP_SSL"_s;
216 return u"INVALID"_s;
219 QString toString(const lt::address &address)
223 return QString::fromLatin1(address.to_string().c_str());
225 catch (const std::exception &)
227 // suppress conversion error
229 return {};
232 template <typename T>
233 struct LowerLimited
235 LowerLimited(T limit, T ret)
236 : m_limit(limit)
237 , m_ret(ret)
241 explicit LowerLimited(T limit)
242 : LowerLimited(limit, limit)
246 T operator()(T val) const
248 return val <= m_limit ? m_ret : val;
251 private:
252 const T m_limit;
253 const T m_ret;
256 template <typename T>
257 LowerLimited<T> lowerLimited(T limit) { return LowerLimited<T>(limit); }
259 template <typename T>
260 LowerLimited<T> lowerLimited(T limit, T ret) { return LowerLimited<T>(limit, ret); }
262 template <typename T>
263 auto clampValue(const T lower, const T upper)
265 return [lower, upper](const T value) -> T
267 return std::clamp(value, lower, upper);
271 #ifdef Q_OS_WIN
272 QString convertIfaceNameToGuid(const QString &name)
274 // Under Windows XP or on Qt version <= 5.5 'name' will be a GUID already.
275 const QUuid uuid(name);
276 if (!uuid.isNull())
277 return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
279 const std::wstring nameWStr = name.toStdWString();
280 NET_LUID luid {};
281 const LONG res = ::ConvertInterfaceNameToLuidW(nameWStr.c_str(), &luid);
282 if (res == 0)
284 GUID guid;
285 if (::ConvertInterfaceLuidToGuid(&luid, &guid) == 0)
286 return QUuid(guid).toString().toUpper();
289 return {};
291 #endif
293 constexpr lt::move_flags_t toNative(const MoveStorageMode mode)
295 switch (mode)
297 default:
298 Q_ASSERT(false);
299 case MoveStorageMode::FailIfExist:
300 return lt::move_flags_t::fail_if_exist;
301 case MoveStorageMode::KeepExistingFiles:
302 return lt::move_flags_t::dont_replace;
303 case MoveStorageMode::Overwrite:
304 return lt::move_flags_t::always_replace_files;
309 struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject
311 using QObject::QObject;
313 ResumeDataStorage *startupStorage = nullptr;
314 ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy;
315 QList<LoadedResumeData> loadedResumeData;
316 int processingResumeDataCount = 0;
317 int64_t totalResumeDataCount = 0;
318 int64_t finishedResumeDataCount = 0;
319 bool isLoadFinished = false;
320 bool isLoadedResumeDataHandlingEnqueued = false;
321 QSet<QString> recoveredCategories;
322 #ifdef QBT_USES_LIBTORRENT2
323 QSet<TorrentID> indexedTorrents;
324 QSet<TorrentID> skippedIDs;
325 #endif
328 const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
330 Session *SessionImpl::m_instance = nullptr;
332 void Session::initInstance()
334 if (!SessionImpl::m_instance)
335 SessionImpl::m_instance = new SessionImpl;
338 void Session::freeInstance()
340 delete SessionImpl::m_instance;
341 SessionImpl::m_instance = nullptr;
344 Session *Session::instance()
346 return SessionImpl::m_instance;
349 bool Session::isValidCategoryName(const QString &name)
351 const QRegularExpression re {uR"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)"_s};
352 return (name.isEmpty() || (name.indexOf(re) == 0));
355 QString Session::subcategoryName(const QString &category)
357 const int sepIndex = category.lastIndexOf(u'/');
358 if (sepIndex >= 0)
359 return category.mid(sepIndex + 1);
361 return category;
364 QString Session::parentCategoryName(const QString &category)
366 const int sepIndex = category.lastIndexOf(u'/');
367 if (sepIndex >= 0)
368 return category.left(sepIndex);
370 return {};
373 QStringList Session::expandCategory(const QString &category)
375 QStringList result;
376 int index = 0;
377 while ((index = category.indexOf(u'/', index)) >= 0)
379 result << category.left(index);
380 ++index;
382 result << category;
384 return result;
387 #define BITTORRENT_KEY(name) u"BitTorrent/" name
388 #define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY(u"Session/") name
390 SessionImpl::SessionImpl(QObject *parent)
391 : Session(parent)
392 , m_DHTBootstrapNodes(BITTORRENT_SESSION_KEY(u"DHTBootstrapNodes"_s), DEFAULT_DHT_BOOTSTRAP_NODES)
393 , m_isDHTEnabled(BITTORRENT_SESSION_KEY(u"DHTEnabled"_s), true)
394 , m_isLSDEnabled(BITTORRENT_SESSION_KEY(u"LSDEnabled"_s), true)
395 , m_isPeXEnabled(BITTORRENT_SESSION_KEY(u"PeXEnabled"_s), true)
396 , m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY(u"IPFilteringEnabled"_s), false)
397 , m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY(u"TrackerFilteringEnabled"_s), false)
398 , m_IPFilterFile(BITTORRENT_SESSION_KEY(u"IPFilter"_s))
399 , m_announceToAllTrackers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTrackers"_s), false)
400 , m_announceToAllTiers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTiers"_s), true)
401 , m_asyncIOThreads(BITTORRENT_SESSION_KEY(u"AsyncIOThreadsCount"_s), 10)
402 , m_hashingThreads(BITTORRENT_SESSION_KEY(u"HashingThreadsCount"_s), 1)
403 , m_filePoolSize(BITTORRENT_SESSION_KEY(u"FilePoolSize"_s), 100)
404 , m_checkingMemUsage(BITTORRENT_SESSION_KEY(u"CheckingMemUsageSize"_s), 32)
405 , m_diskCacheSize(BITTORRENT_SESSION_KEY(u"DiskCacheSize"_s), -1)
406 , m_diskCacheTTL(BITTORRENT_SESSION_KEY(u"DiskCacheTTL"_s), 60)
407 , m_diskQueueSize(BITTORRENT_SESSION_KEY(u"DiskQueueSize"_s), (1024 * 1024))
408 , m_diskIOType(BITTORRENT_SESSION_KEY(u"DiskIOType"_s), DiskIOType::Default)
409 , m_diskIOReadMode(BITTORRENT_SESSION_KEY(u"DiskIOReadMode"_s), DiskIOReadMode::EnableOSCache)
410 , m_diskIOWriteMode(BITTORRENT_SESSION_KEY(u"DiskIOWriteMode"_s), DiskIOWriteMode::EnableOSCache)
411 #ifdef Q_OS_WIN
412 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_s), true)
413 #else
414 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_s), false)
415 #endif
416 , m_usePieceExtentAffinity(BITTORRENT_SESSION_KEY(u"PieceExtentAffinity"_s), false)
417 , m_isSuggestMode(BITTORRENT_SESSION_KEY(u"SuggestMode"_s), false)
418 , m_sendBufferWatermark(BITTORRENT_SESSION_KEY(u"SendBufferWatermark"_s), 500)
419 , m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY(u"SendBufferLowWatermark"_s), 10)
420 , m_sendBufferWatermarkFactor(BITTORRENT_SESSION_KEY(u"SendBufferWatermarkFactor"_s), 50)
421 , m_connectionSpeed(BITTORRENT_SESSION_KEY(u"ConnectionSpeed"_s), 30)
422 , m_socketSendBufferSize(BITTORRENT_SESSION_KEY(u"SocketSendBufferSize"_s), 0)
423 , m_socketReceiveBufferSize(BITTORRENT_SESSION_KEY(u"SocketReceiveBufferSize"_s), 0)
424 , m_socketBacklogSize(BITTORRENT_SESSION_KEY(u"SocketBacklogSize"_s), 30)
425 , m_isAnonymousModeEnabled(BITTORRENT_SESSION_KEY(u"AnonymousModeEnabled"_s), false)
426 , m_isQueueingEnabled(BITTORRENT_SESSION_KEY(u"QueueingSystemEnabled"_s), false)
427 , m_maxActiveDownloads(BITTORRENT_SESSION_KEY(u"MaxActiveDownloads"_s), 3, lowerLimited(-1))
428 , m_maxActiveUploads(BITTORRENT_SESSION_KEY(u"MaxActiveUploads"_s), 3, lowerLimited(-1))
429 , m_maxActiveTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveTorrents"_s), 5, lowerLimited(-1))
430 , m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY(u"IgnoreSlowTorrentsForQueueing"_s), false)
431 , m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsDownloadRate"_s), 2)
432 , m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsUploadRate"_s), 2)
433 , m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY(u"SlowTorrentsInactivityTimer"_s), 60)
434 , m_outgoingPortsMin(BITTORRENT_SESSION_KEY(u"OutgoingPortsMin"_s), 0)
435 , m_outgoingPortsMax(BITTORRENT_SESSION_KEY(u"OutgoingPortsMax"_s), 0)
436 , m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY(u"UPnPLeaseDuration"_s), 0)
437 , m_peerToS(BITTORRENT_SESSION_KEY(u"PeerToS"_s), 0x04)
438 , m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY(u"IgnoreLimitsOnLAN"_s), false)
439 , m_includeOverheadInLimits(BITTORRENT_SESSION_KEY(u"IncludeOverheadInLimits"_s), false)
440 , m_announceIP(BITTORRENT_SESSION_KEY(u"AnnounceIP"_s))
441 , m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY(u"MaxConcurrentHTTPAnnounces"_s), 50)
442 , m_isReannounceWhenAddressChangedEnabled(BITTORRENT_SESSION_KEY(u"ReannounceWhenAddressChanged"_s), false)
443 , m_stopTrackerTimeout(BITTORRENT_SESSION_KEY(u"StopTrackerTimeout"_s), 2)
444 , m_maxConnections(BITTORRENT_SESSION_KEY(u"MaxConnections"_s), 500, lowerLimited(0, -1))
445 , m_maxUploads(BITTORRENT_SESSION_KEY(u"MaxUploads"_s), 20, lowerLimited(0, -1))
446 , m_maxConnectionsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxConnectionsPerTorrent"_s), 100, lowerLimited(0, -1))
447 , m_maxUploadsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxUploadsPerTorrent"_s), 4, lowerLimited(0, -1))
448 , m_btProtocol(BITTORRENT_SESSION_KEY(u"BTProtocol"_s), BTProtocol::Both
449 , clampValue(BTProtocol::Both, BTProtocol::UTP))
450 , m_isUTPRateLimited(BITTORRENT_SESSION_KEY(u"uTPRateLimited"_s), true)
451 , m_utpMixedMode(BITTORRENT_SESSION_KEY(u"uTPMixedMode"_s), MixedModeAlgorithm::TCP
452 , clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
453 , m_IDNSupportEnabled(BITTORRENT_SESSION_KEY(u"IDNSupportEnabled"_s), false)
454 , m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY(u"MultiConnectionsPerIp"_s), false)
455 , m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY(u"ValidateHTTPSTrackerCertificate"_s), true)
456 , m_SSRFMitigationEnabled(BITTORRENT_SESSION_KEY(u"SSRFMitigation"_s), true)
457 , m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY(u"BlockPeersOnPrivilegedPorts"_s), false)
458 , m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersEnabled"_s), false)
459 , m_additionalTrackers(BITTORRENT_SESSION_KEY(u"AdditionalTrackers"_s))
460 , m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_s), -1, [](qreal r) { return r < 0 ? -1. : r;})
461 , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s), -1, lowerLimited(-1))
462 , m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), -1, lowerLimited(-1))
463 , m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_s), false)
464 , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY(u"AddTorrentPaused"_s), false)
465 , m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_s), Torrent::StopCondition::None)
466 , m_torrentContentLayout(BITTORRENT_SESSION_KEY(u"TorrentContentLayout"_s), TorrentContentLayout::Original)
467 , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY(u"AddExtensionToIncompleteFiles"_s), false)
468 , m_isUnwantedFolderEnabled(BITTORRENT_SESSION_KEY(u"UseUnwantedFolder"_s), false)
469 , m_refreshInterval(BITTORRENT_SESSION_KEY(u"RefreshInterval"_s), 1500)
470 , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY(u"Preallocation"_s), false)
471 , m_torrentExportDirectory(BITTORRENT_SESSION_KEY(u"TorrentExportDirectory"_s))
472 , m_finishedTorrentExportDirectory(BITTORRENT_SESSION_KEY(u"FinishedTorrentExportDirectory"_s))
473 , m_globalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalDLSpeedLimit"_s), 0, lowerLimited(0))
474 , m_globalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalUPSpeedLimit"_s), 0, lowerLimited(0))
475 , m_altGlobalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalDLSpeedLimit"_s), 10, lowerLimited(0))
476 , m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalUPSpeedLimit"_s), 10, lowerLimited(0))
477 , m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY(u"UseAlternativeGlobalSpeedLimit"_s), false)
478 , m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY(u"BandwidthSchedulerEnabled"_s), false)
479 , m_isPerformanceWarningEnabled(BITTORRENT_SESSION_KEY(u"PerformanceWarning"_s), false)
480 , m_saveResumeDataInterval(BITTORRENT_SESSION_KEY(u"SaveResumeDataInterval"_s), 60)
481 , m_port(BITTORRENT_SESSION_KEY(u"Port"_s), -1)
482 , m_networkInterface(BITTORRENT_SESSION_KEY(u"Interface"_s))
483 , m_networkInterfaceName(BITTORRENT_SESSION_KEY(u"InterfaceName"_s))
484 , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY(u"InterfaceAddress"_s))
485 , m_encryption(BITTORRENT_SESSION_KEY(u"Encryption"_s), 0)
486 , m_maxActiveCheckingTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveCheckingTorrents"_s), 1)
487 , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY(u"ProxyPeerConnections"_s), false)
488 , m_chokingAlgorithm(BITTORRENT_SESSION_KEY(u"ChokingAlgorithm"_s), ChokingAlgorithm::FixedSlots
489 , clampValue(ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased))
490 , m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY(u"SeedChokingAlgorithm"_s), SeedChokingAlgorithm::FastestUpload
491 , clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
492 , m_storedTags(BITTORRENT_SESSION_KEY(u"Tags"_s))
493 , m_maxRatioAction(BITTORRENT_SESSION_KEY(u"MaxRatioAction"_s), Pause)
494 , m_savePath(BITTORRENT_SESSION_KEY(u"DefaultSavePath"_s), specialFolderLocation(SpecialFolder::Downloads))
495 , m_downloadPath(BITTORRENT_SESSION_KEY(u"TempPath"_s), (savePath() / Path(u"temp"_s)))
496 , m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY(u"TempPathEnabled"_s), false)
497 , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY(u"SubcategoriesEnabled"_s), false)
498 , m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY(u"UseCategoryPathsInManualMode"_s), false)
499 , m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY(u"DisableAutoTMMByDefault"_s), true)
500 , m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategoryChanged"_s), false)
501 , m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/DefaultSavePathChanged"_s), true)
502 , m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategorySavePathChanged"_s), true)
503 , m_isTrackerEnabled(BITTORRENT_KEY(u"TrackerEnabled"_s), false)
504 , m_peerTurnover(BITTORRENT_SESSION_KEY(u"PeerTurnover"_s), 4)
505 , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY(u"PeerTurnoverCutOff"_s), 90)
506 , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY(u"PeerTurnoverInterval"_s), 300)
507 , m_requestQueueSize(BITTORRENT_SESSION_KEY(u"RequestQueueSize"_s), 500)
508 , m_isExcludedFileNamesEnabled(BITTORRENT_KEY(u"ExcludedFileNamesEnabled"_s), false)
509 , m_excludedFileNames(BITTORRENT_SESSION_KEY(u"ExcludedFileNames"_s))
510 , m_bannedIPs(u"State/BannedIPs"_s, QStringList(), Algorithm::sorted<QStringList>)
511 , m_resumeDataStorageType(BITTORRENT_SESSION_KEY(u"ResumeDataStorageType"_s), ResumeDataStorageType::Legacy)
512 , m_isMergeTrackersEnabled(BITTORRENT_KEY(u"MergeTrackersEnabled"_s), false)
513 , m_isI2PEnabled {BITTORRENT_SESSION_KEY(u"I2P/Enabled"_s), false}
514 , m_I2PAddress {BITTORRENT_SESSION_KEY(u"I2P/Address"_s), u"127.0.0.1"_s}
515 , m_I2PPort {BITTORRENT_SESSION_KEY(u"I2P/Port"_s), 7656}
516 , m_I2PMixedMode {BITTORRENT_SESSION_KEY(u"I2P/MixedMode"_s), false}
517 , m_I2PInboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/InboundQuantity"_s), 3}
518 , m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
519 , m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
520 , m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
521 , m_seedingLimitTimer {new QTimer(this)}
522 , m_resumeDataTimer {new QTimer(this)}
523 , m_ioThread {new QThread}
524 , m_asyncWorker {new QThreadPool(this)}
525 , m_recentErroredTorrentsTimer {new QTimer(this)}
527 // It is required to perform async access to libtorrent sequentially
528 m_asyncWorker->setMaxThreadCount(1);
530 if (port() < 0)
531 m_port = Utils::Random::rand(1024, 65535);
533 m_recentErroredTorrentsTimer->setSingleShot(true);
534 m_recentErroredTorrentsTimer->setInterval(1s);
535 connect(m_recentErroredTorrentsTimer, &QTimer::timeout
536 , this, [this]() { m_recentErroredTorrents.clear(); });
538 m_seedingLimitTimer->setInterval(10s);
539 connect(m_seedingLimitTimer, &QTimer::timeout, this, &SessionImpl::processShareLimits);
541 initializeNativeSession();
542 configureComponents();
544 if (isBandwidthSchedulerEnabled())
545 enableBandwidthScheduler();
547 loadCategories();
548 if (isSubcategoriesEnabled())
550 // if subcategories support changed manually
551 m_categories = expandCategories(m_categories);
554 const QStringList storedTags = m_storedTags.get();
555 for (const QString &tagStr : storedTags)
557 if (const Tag tag {tagStr}; tag.isValid())
558 m_tags.insert(tag);
561 updateSeedingLimitTimer();
562 populateAdditionalTrackers();
563 if (isExcludedFileNamesEnabled())
564 populateExcludedFileNamesRegExpList();
566 connect(Net::ProxyConfigurationManager::instance()
567 , &Net::ProxyConfigurationManager::proxyConfigurationChanged
568 , this, &SessionImpl::configureDeferred);
570 m_fileSearcher = new FileSearcher;
571 m_fileSearcher->moveToThread(m_ioThread.get());
572 connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
573 connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
575 m_ioThread->start();
577 initMetrics();
578 loadStatistics();
580 // initialize PortForwarder instance
581 new PortForwarderImpl(this);
583 // start embedded tracker
584 enableTracker(isTrackerEnabled());
586 prepareStartup();
589 SessionImpl::~SessionImpl()
591 m_nativeSession->pause();
593 if (m_torrentsQueueChanged)
595 m_nativeSession->post_torrent_updates({});
596 m_torrentsQueueChanged = false;
597 m_needSaveTorrentsQueue = true;
600 // Do some bittorrent related saving
601 // After this, (ideally) no more important alerts will be generated/handled
602 saveResumeData();
604 saveStatistics();
606 // We must delete FilterParserThread
607 // before we delete lt::session
608 delete m_filterParser;
610 // We must delete PortForwarderImpl before
611 // we delete lt::session
612 delete Net::PortForwarder::instance();
614 // We must stop "async worker" only after deletion
615 // of all the components that could potentially use it
616 m_asyncWorker->clear();
617 m_asyncWorker->waitForDone();
619 qDebug("Deleting libtorrent session...");
620 delete m_nativeSession;
623 QString SessionImpl::getDHTBootstrapNodes() const
625 const QString nodes = m_DHTBootstrapNodes;
626 return !nodes.isEmpty() ? nodes : DEFAULT_DHT_BOOTSTRAP_NODES;
629 void SessionImpl::setDHTBootstrapNodes(const QString &nodes)
631 if (nodes == m_DHTBootstrapNodes)
632 return;
634 m_DHTBootstrapNodes = nodes;
635 configureDeferred();
638 bool SessionImpl::isDHTEnabled() const
640 return m_isDHTEnabled;
643 void SessionImpl::setDHTEnabled(bool enabled)
645 if (enabled != m_isDHTEnabled)
647 m_isDHTEnabled = enabled;
648 configureDeferred();
649 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(enabled ? tr("ON") : tr("OFF")), Log::INFO);
653 bool SessionImpl::isLSDEnabled() const
655 return m_isLSDEnabled;
658 void SessionImpl::setLSDEnabled(const bool enabled)
660 if (enabled != m_isLSDEnabled)
662 m_isLSDEnabled = enabled;
663 configureDeferred();
664 LogMsg(tr("Local Peer Discovery support: %1").arg(enabled ? tr("ON") : tr("OFF"))
665 , Log::INFO);
669 bool SessionImpl::isPeXEnabled() const
671 return m_isPeXEnabled;
674 void SessionImpl::setPeXEnabled(const bool enabled)
676 m_isPeXEnabled = enabled;
677 if (m_wasPexEnabled != enabled)
678 LogMsg(tr("Restart is required to toggle Peer Exchange (PeX) support"), Log::WARNING);
681 bool SessionImpl::isDownloadPathEnabled() const
683 return m_isDownloadPathEnabled;
686 void SessionImpl::setDownloadPathEnabled(const bool enabled)
688 if (enabled != isDownloadPathEnabled())
690 m_isDownloadPathEnabled = enabled;
691 for (TorrentImpl *const torrent : asConst(m_torrents))
692 torrent->handleCategoryOptionsChanged();
696 bool SessionImpl::isAppendExtensionEnabled() const
698 return m_isAppendExtensionEnabled;
701 void SessionImpl::setAppendExtensionEnabled(const bool enabled)
703 if (isAppendExtensionEnabled() != enabled)
705 m_isAppendExtensionEnabled = enabled;
707 // append or remove .!qB extension for incomplete files
708 for (TorrentImpl *const torrent : asConst(m_torrents))
709 torrent->handleAppendExtensionToggled();
713 bool SessionImpl::isUnwantedFolderEnabled() const
715 return m_isUnwantedFolderEnabled;
718 void SessionImpl::setUnwantedFolderEnabled(const bool enabled)
720 if (isUnwantedFolderEnabled() != enabled)
722 m_isUnwantedFolderEnabled = enabled;
724 // append or remove .!qB extension for incomplete files
725 for (TorrentImpl *const torrent : asConst(m_torrents))
726 torrent->handleUnwantedFolderToggled();
730 int SessionImpl::refreshInterval() const
732 return m_refreshInterval;
735 void SessionImpl::setRefreshInterval(const int value)
737 if (value != refreshInterval())
739 m_refreshInterval = value;
743 bool SessionImpl::isPreallocationEnabled() const
745 return m_isPreallocationEnabled;
748 void SessionImpl::setPreallocationEnabled(const bool enabled)
750 m_isPreallocationEnabled = enabled;
753 Path SessionImpl::torrentExportDirectory() const
755 return m_torrentExportDirectory;
758 void SessionImpl::setTorrentExportDirectory(const Path &path)
760 if (path != torrentExportDirectory())
761 m_torrentExportDirectory = path;
764 Path SessionImpl::finishedTorrentExportDirectory() const
766 return m_finishedTorrentExportDirectory;
769 void SessionImpl::setFinishedTorrentExportDirectory(const Path &path)
771 if (path != finishedTorrentExportDirectory())
772 m_finishedTorrentExportDirectory = path;
775 Path SessionImpl::savePath() const
777 // TODO: Make sure it is always non-empty
778 return m_savePath;
781 Path SessionImpl::downloadPath() const
783 // TODO: Make sure it is always non-empty
784 return m_downloadPath;
787 QStringList SessionImpl::categories() const
789 return m_categories.keys();
792 CategoryOptions SessionImpl::categoryOptions(const QString &categoryName) const
794 return m_categories.value(categoryName);
797 Path SessionImpl::categorySavePath(const QString &categoryName) const
799 return categorySavePath(categoryName, categoryOptions(categoryName));
802 Path SessionImpl::categorySavePath(const QString &categoryName, const CategoryOptions &options) const
804 Path basePath = savePath();
805 if (categoryName.isEmpty())
806 return basePath;
808 Path path = options.savePath;
809 if (path.isEmpty())
811 // use implicit save path
812 if (isSubcategoriesEnabled())
814 path = Utils::Fs::toValidPath(subcategoryName(categoryName));
815 basePath = categorySavePath(parentCategoryName(categoryName));
817 else
819 path = Utils::Fs::toValidPath(categoryName);
823 return (path.isAbsolute() ? path : (basePath / path));
826 Path SessionImpl::categoryDownloadPath(const QString &categoryName) const
828 return categoryDownloadPath(categoryName, categoryOptions(categoryName));
831 Path SessionImpl::categoryDownloadPath(const QString &categoryName, const CategoryOptions &options) const
833 const DownloadPathOption downloadPathOption = resolveCategoryDownloadPathOption(categoryName, options.downloadPath);
834 if (!downloadPathOption.enabled)
835 return {};
837 if (categoryName.isEmpty())
838 return downloadPath();
840 const bool useSubcategories = isSubcategoriesEnabled();
841 const QString name = useSubcategories ? subcategoryName(categoryName) : categoryName;
842 const Path path = !downloadPathOption.path.isEmpty()
843 ? downloadPathOption.path
844 : Utils::Fs::toValidPath(name); // use implicit download path
846 if (path.isAbsolute())
847 return path;
849 const QString parentName = useSubcategories ? parentCategoryName(categoryName) : QString();
850 CategoryOptions parentOptions = categoryOptions(parentName);
851 // Even if download path of parent category is disabled (directly or by inheritance)
852 // we need to construct the one as if it would be enabled.
853 if (!parentOptions.downloadPath || !parentOptions.downloadPath->enabled)
854 parentOptions.downloadPath = {true, {}};
855 const Path parentDownloadPath = categoryDownloadPath(parentName, parentOptions);
856 const Path basePath = parentDownloadPath.isEmpty() ? downloadPath() : parentDownloadPath;
857 return (basePath / path);
860 DownloadPathOption SessionImpl::resolveCategoryDownloadPathOption(const QString &categoryName, const std::optional<DownloadPathOption> &option) const
862 if (categoryName.isEmpty())
863 return {isDownloadPathEnabled(), Path()};
865 if (option.has_value())
866 return *option;
868 const QString parentName = isSubcategoriesEnabled() ? parentCategoryName(categoryName) : QString();
869 return resolveCategoryDownloadPathOption(parentName, categoryOptions(parentName).downloadPath);
872 bool SessionImpl::addCategory(const QString &name, const CategoryOptions &options)
874 if (name.isEmpty())
875 return false;
877 if (!isValidCategoryName(name) || m_categories.contains(name))
878 return false;
880 if (isSubcategoriesEnabled())
882 for (const QString &parent : asConst(expandCategory(name)))
884 if ((parent != name) && !m_categories.contains(parent))
886 m_categories[parent] = {};
887 emit categoryAdded(parent);
892 m_categories[name] = options;
893 storeCategories();
894 emit categoryAdded(name);
896 return true;
899 bool SessionImpl::editCategory(const QString &name, const CategoryOptions &options)
901 const auto it = m_categories.find(name);
902 if (it == m_categories.end())
903 return false;
905 CategoryOptions &currentOptions = it.value();
906 if (options == currentOptions)
907 return false;
909 currentOptions = options;
910 storeCategories();
911 if (isDisableAutoTMMWhenCategorySavePathChanged())
913 for (TorrentImpl *const torrent : asConst(m_torrents))
915 if (torrent->category() == name)
916 torrent->setAutoTMMEnabled(false);
919 else
921 for (TorrentImpl *const torrent : asConst(m_torrents))
923 if (torrent->category() == name)
924 torrent->handleCategoryOptionsChanged();
928 emit categoryOptionsChanged(name);
929 return true;
932 bool SessionImpl::removeCategory(const QString &name)
934 for (TorrentImpl *const torrent : asConst(m_torrents))
936 if (torrent->belongsToCategory(name))
937 torrent->setCategory(u""_s);
940 // remove stored category and its subcategories if exist
941 bool result = false;
942 if (isSubcategoriesEnabled())
944 // remove subcategories
945 const QString test = name + u'/';
946 Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const CategoryOptions &)
948 if (category.startsWith(test))
950 result = true;
951 emit categoryRemoved(category);
952 return true;
954 return false;
958 result = (m_categories.remove(name) > 0) || result;
960 if (result)
962 // update stored categories
963 storeCategories();
964 emit categoryRemoved(name);
967 return result;
970 bool SessionImpl::isSubcategoriesEnabled() const
972 return m_isSubcategoriesEnabled;
975 void SessionImpl::setSubcategoriesEnabled(const bool value)
977 if (isSubcategoriesEnabled() == value) return;
979 if (value)
981 // expand categories to include all parent categories
982 m_categories = expandCategories(m_categories);
983 // update stored categories
984 storeCategories();
986 else
988 // reload categories
989 loadCategories();
992 m_isSubcategoriesEnabled = value;
993 emit subcategoriesSupportChanged();
996 bool SessionImpl::useCategoryPathsInManualMode() const
998 return m_useCategoryPathsInManualMode;
1001 void SessionImpl::setUseCategoryPathsInManualMode(const bool value)
1003 m_useCategoryPathsInManualMode = value;
1006 Path SessionImpl::suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const
1008 const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
1009 const auto path = (useCategoryPaths ? categorySavePath(categoryName) : savePath());
1010 return path;
1013 Path SessionImpl::suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const
1015 const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
1016 const auto categoryDownloadPath = this->categoryDownloadPath(categoryName);
1017 const auto path = ((useCategoryPaths && !categoryDownloadPath.isEmpty()) ? categoryDownloadPath : downloadPath());
1018 return path;
1021 TagSet SessionImpl::tags() const
1023 return m_tags;
1026 bool SessionImpl::hasTag(const Tag &tag) const
1028 return m_tags.contains(tag);
1031 bool SessionImpl::addTag(const Tag &tag)
1033 if (!tag.isValid() || hasTag(tag))
1034 return false;
1036 m_tags.insert(tag);
1037 m_storedTags = QStringList(m_tags.cbegin(), m_tags.cend());
1039 emit tagAdded(tag);
1040 return true;
1043 bool SessionImpl::removeTag(const Tag &tag)
1045 if (m_tags.remove(tag))
1047 for (TorrentImpl *const torrent : asConst(m_torrents))
1048 torrent->removeTag(tag);
1050 m_storedTags = QStringList(m_tags.cbegin(), m_tags.cend());
1052 emit tagRemoved(tag);
1053 return true;
1055 return false;
1058 bool SessionImpl::isAutoTMMDisabledByDefault() const
1060 return m_isAutoTMMDisabledByDefault;
1063 void SessionImpl::setAutoTMMDisabledByDefault(const bool value)
1065 m_isAutoTMMDisabledByDefault = value;
1068 bool SessionImpl::isDisableAutoTMMWhenCategoryChanged() const
1070 return m_isDisableAutoTMMWhenCategoryChanged;
1073 void SessionImpl::setDisableAutoTMMWhenCategoryChanged(const bool value)
1075 m_isDisableAutoTMMWhenCategoryChanged = value;
1078 bool SessionImpl::isDisableAutoTMMWhenDefaultSavePathChanged() const
1080 return m_isDisableAutoTMMWhenDefaultSavePathChanged;
1083 void SessionImpl::setDisableAutoTMMWhenDefaultSavePathChanged(const bool value)
1085 m_isDisableAutoTMMWhenDefaultSavePathChanged = value;
1088 bool SessionImpl::isDisableAutoTMMWhenCategorySavePathChanged() const
1090 return m_isDisableAutoTMMWhenCategorySavePathChanged;
1093 void SessionImpl::setDisableAutoTMMWhenCategorySavePathChanged(const bool value)
1095 m_isDisableAutoTMMWhenCategorySavePathChanged = value;
1098 bool SessionImpl::isAddTorrentToQueueTop() const
1100 return m_isAddTorrentToQueueTop;
1103 void SessionImpl::setAddTorrentToQueueTop(bool value)
1105 m_isAddTorrentToQueueTop = value;
1108 bool SessionImpl::isAddTorrentPaused() const
1110 return m_isAddTorrentPaused;
1113 void SessionImpl::setAddTorrentPaused(const bool value)
1115 m_isAddTorrentPaused = value;
1118 Torrent::StopCondition SessionImpl::torrentStopCondition() const
1120 return m_torrentStopCondition;
1123 void SessionImpl::setTorrentStopCondition(const Torrent::StopCondition stopCondition)
1125 m_torrentStopCondition = stopCondition;
1128 bool SessionImpl::isTrackerEnabled() const
1130 return m_isTrackerEnabled;
1133 void SessionImpl::setTrackerEnabled(const bool enabled)
1135 if (m_isTrackerEnabled != enabled)
1136 m_isTrackerEnabled = enabled;
1138 // call enableTracker() unconditionally, otherwise port change won't trigger
1139 // tracker restart
1140 enableTracker(enabled);
1143 qreal SessionImpl::globalMaxRatio() const
1145 return m_globalMaxRatio;
1148 // Torrents with a ratio superior to the given value will
1149 // be automatically deleted
1150 void SessionImpl::setGlobalMaxRatio(qreal ratio)
1152 if (ratio < 0)
1153 ratio = -1.;
1155 if (ratio != globalMaxRatio())
1157 m_globalMaxRatio = ratio;
1158 updateSeedingLimitTimer();
1162 int SessionImpl::globalMaxSeedingMinutes() const
1164 return m_globalMaxSeedingMinutes;
1167 void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
1169 if (minutes < 0)
1170 minutes = -1;
1172 if (minutes != globalMaxSeedingMinutes())
1174 m_globalMaxSeedingMinutes = minutes;
1175 updateSeedingLimitTimer();
1179 int SessionImpl::globalMaxInactiveSeedingMinutes() const
1181 return m_globalMaxInactiveSeedingMinutes;
1184 void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
1186 minutes = std::max(minutes, -1);
1188 if (minutes != globalMaxInactiveSeedingMinutes())
1190 m_globalMaxInactiveSeedingMinutes = minutes;
1191 updateSeedingLimitTimer();
1195 void SessionImpl::applyBandwidthLimits()
1197 lt::settings_pack settingsPack;
1198 settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
1199 settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
1200 m_nativeSession->apply_settings(std::move(settingsPack));
1203 void SessionImpl::configure()
1205 m_nativeSession->apply_settings(loadLTSettings());
1206 configureComponents();
1208 m_deferredConfigureScheduled = false;
1211 void SessionImpl::configureComponents()
1213 // This function contains components/actions that:
1214 // 1. Need to be setup at start up
1215 // 2. When deferred configure is called
1217 configurePeerClasses();
1219 if (!m_IPFilteringConfigured)
1221 if (isIPFilteringEnabled())
1222 enableIPFilter();
1223 else
1224 disableIPFilter();
1225 m_IPFilteringConfigured = true;
1229 void SessionImpl::prepareStartup()
1231 qDebug("Initializing torrents resume data storage...");
1233 const Path dbPath = specialFolderLocation(SpecialFolder::Data) / Path(u"torrents.db"_s);
1234 const bool dbStorageExists = dbPath.exists();
1236 auto *context = new ResumeSessionContext(this);
1237 context->currentStorageType = resumeDataStorageType();
1239 if (context->currentStorageType == ResumeDataStorageType::SQLite)
1241 m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
1243 if (!dbStorageExists)
1245 const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_s);
1246 context->startupStorage = new BencodeResumeDataStorage(dataPath, this);
1249 else
1251 const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_s);
1252 m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
1254 if (dbStorageExists)
1255 context->startupStorage = new DBResumeDataStorage(dbPath, this);
1258 if (!context->startupStorage)
1259 context->startupStorage = m_resumeDataStorage;
1261 connect(context->startupStorage, &ResumeDataStorage::loadStarted, context
1262 , [this, context](const QVector<TorrentID> &torrents)
1264 context->totalResumeDataCount = torrents.size();
1265 #ifdef QBT_USES_LIBTORRENT2
1266 context->indexedTorrents = QSet<TorrentID>(torrents.cbegin(), torrents.cend());
1267 #endif
1269 handleLoadedResumeData(context);
1272 connect(context->startupStorage, &ResumeDataStorage::loadFinished, context, [context]()
1274 context->isLoadFinished = true;
1277 connect(this, &SessionImpl::addTorrentAlertsReceived, context, [this, context](const qsizetype alertsCount)
1279 context->processingResumeDataCount -= alertsCount;
1280 context->finishedResumeDataCount += alertsCount;
1281 if (!context->isLoadedResumeDataHandlingEnqueued)
1283 QMetaObject::invokeMethod(this, [this, context] { handleLoadedResumeData(context); }, Qt::QueuedConnection);
1284 context->isLoadedResumeDataHandlingEnqueued = true;
1287 if (!m_refreshEnqueued)
1289 m_nativeSession->post_torrent_updates();
1290 m_refreshEnqueued = true;
1293 emit startupProgressUpdated((context->finishedResumeDataCount * 100.) / context->totalResumeDataCount);
1296 context->startupStorage->loadAll();
1299 void SessionImpl::handleLoadedResumeData(ResumeSessionContext *context)
1301 context->isLoadedResumeDataHandlingEnqueued = false;
1303 int count = context->processingResumeDataCount;
1304 while (context->processingResumeDataCount < MAX_PROCESSING_RESUMEDATA_COUNT)
1306 if (context->loadedResumeData.isEmpty())
1307 context->loadedResumeData = context->startupStorage->fetchLoadedResumeData();
1309 if (context->loadedResumeData.isEmpty())
1311 if (context->processingResumeDataCount == 0)
1313 if (context->isLoadFinished)
1315 endStartup(context);
1317 else if (!context->isLoadedResumeDataHandlingEnqueued)
1319 QMetaObject::invokeMethod(this, [this, context]() { handleLoadedResumeData(context); }, Qt::QueuedConnection);
1320 context->isLoadedResumeDataHandlingEnqueued = true;
1324 break;
1327 processNextResumeData(context);
1328 ++count;
1331 context->finishedResumeDataCount += (count - context->processingResumeDataCount);
1334 void SessionImpl::processNextResumeData(ResumeSessionContext *context)
1336 const LoadedResumeData loadedResumeDataItem = context->loadedResumeData.takeFirst();
1338 TorrentID torrentID = loadedResumeDataItem.torrentID;
1339 #ifdef QBT_USES_LIBTORRENT2
1340 if (context->skippedIDs.contains(torrentID))
1341 return;
1342 #endif
1344 const nonstd::expected<LoadTorrentParams, QString> &loadResumeDataResult = loadedResumeDataItem.result;
1345 if (!loadResumeDataResult)
1347 LogMsg(tr("Failed to resume torrent. Torrent: \"%1\". Reason: \"%2\"")
1348 .arg(torrentID.toString(), loadResumeDataResult.error()), Log::CRITICAL);
1349 return;
1352 LoadTorrentParams resumeData = *loadResumeDataResult;
1353 bool needStore = false;
1355 #ifdef QBT_USES_LIBTORRENT2
1356 const InfoHash infoHash {(resumeData.ltAddTorrentParams.ti
1357 ? resumeData.ltAddTorrentParams.ti->info_hashes()
1358 : resumeData.ltAddTorrentParams.info_hashes)};
1359 const bool isHybrid = infoHash.isHybrid();
1360 const auto torrentIDv2 = TorrentID::fromInfoHash(infoHash);
1361 const auto torrentIDv1 = TorrentID::fromSHA1Hash(infoHash.v1());
1362 if (torrentID == torrentIDv2)
1364 if (isHybrid && context->indexedTorrents.contains(torrentIDv1))
1366 // if we don't have metadata, try to find it in alternative "resume data"
1367 if (!resumeData.ltAddTorrentParams.ti)
1369 const nonstd::expected<LoadTorrentParams, QString> loadAltResumeDataResult = context->startupStorage->load(torrentIDv1);
1370 if (loadAltResumeDataResult)
1371 resumeData.ltAddTorrentParams.ti = loadAltResumeDataResult->ltAddTorrentParams.ti;
1374 // remove alternative "resume data" and skip the attempt to load it
1375 m_resumeDataStorage->remove(torrentIDv1);
1376 context->skippedIDs.insert(torrentIDv1);
1379 else if (torrentID == torrentIDv1)
1381 torrentID = torrentIDv2;
1382 needStore = true;
1383 m_resumeDataStorage->remove(torrentIDv1);
1385 if (context->indexedTorrents.contains(torrentID))
1387 context->skippedIDs.insert(torrentID);
1389 const nonstd::expected<LoadTorrentParams, QString> loadPreferredResumeDataResult = context->startupStorage->load(torrentID);
1390 if (loadPreferredResumeDataResult)
1392 std::shared_ptr<lt::torrent_info> ti = resumeData.ltAddTorrentParams.ti;
1393 resumeData = *loadPreferredResumeDataResult;
1394 if (!resumeData.ltAddTorrentParams.ti)
1395 resumeData.ltAddTorrentParams.ti = std::move(ti);
1399 else
1401 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1402 .arg(torrentID.toString()), Log::WARNING);
1403 return;
1405 #else
1406 const lt::sha1_hash infoHash = (resumeData.ltAddTorrentParams.ti
1407 ? resumeData.ltAddTorrentParams.ti->info_hash()
1408 : resumeData.ltAddTorrentParams.info_hash);
1409 if (torrentID != TorrentID::fromInfoHash(infoHash))
1411 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1412 .arg(torrentID.toString()), Log::WARNING);
1413 return;
1415 #endif
1417 if (m_resumeDataStorage != context->startupStorage)
1418 needStore = true;
1420 // TODO: Remove the following upgrade code in v4.6
1421 // == BEGIN UPGRADE CODE ==
1422 if (!needStore)
1424 if (m_needUpgradeDownloadPath && isDownloadPathEnabled() && !resumeData.useAutoTMM)
1426 resumeData.downloadPath = downloadPath();
1427 needStore = true;
1430 // == END UPGRADE CODE ==
1432 if (needStore)
1433 m_resumeDataStorage->store(torrentID, resumeData);
1435 const QString category = resumeData.category;
1436 bool isCategoryRecovered = context->recoveredCategories.contains(category);
1437 if (!category.isEmpty() && (isCategoryRecovered || !m_categories.contains(category)))
1439 if (!isCategoryRecovered)
1441 if (addCategory(category))
1443 context->recoveredCategories.insert(category);
1444 isCategoryRecovered = true;
1445 LogMsg(tr("Detected inconsistent data: category is missing from the configuration file."
1446 " Category will be recovered but its settings will be reset to default."
1447 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
1449 else
1451 resumeData.category.clear();
1452 LogMsg(tr("Detected inconsistent data: invalid category. Torrent: \"%1\". Category: \"%2\"")
1453 .arg(torrentID.toString(), category), Log::WARNING);
1457 // We should check isCategoryRecovered again since the category
1458 // can be just recovered by the code above
1459 if (isCategoryRecovered && resumeData.useAutoTMM)
1461 const Path storageLocation {resumeData.ltAddTorrentParams.save_path};
1462 if ((storageLocation != categorySavePath(resumeData.category)) && (storageLocation != categoryDownloadPath(resumeData.category)))
1464 resumeData.useAutoTMM = false;
1465 resumeData.savePath = storageLocation;
1466 resumeData.downloadPath = {};
1467 LogMsg(tr("Detected mismatch between the save paths of the recovered category and the current save path of the torrent."
1468 " Torrent is now switched to Manual mode."
1469 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
1474 std::erase_if(resumeData.tags, [this, &torrentID](const Tag &tag)
1476 if (hasTag(tag))
1477 return false;
1479 if (addTag(tag))
1481 LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file."
1482 " Tag will be recovered."
1483 " Torrent: \"%1\". Tag: \"%2\"").arg(torrentID.toString(), tag.toString()), Log::WARNING);
1484 return false;
1487 LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"")
1488 .arg(torrentID.toString(), tag.toString()), Log::WARNING);
1489 return true;
1492 resumeData.ltAddTorrentParams.userdata = LTClientData(new ExtensionData);
1493 #ifndef QBT_USES_LIBTORRENT2
1494 resumeData.ltAddTorrentParams.storage = customStorageConstructor;
1495 #endif
1497 qDebug() << "Starting up torrent" << torrentID.toString() << "...";
1498 m_loadingTorrents.insert(torrentID, resumeData);
1499 #ifdef QBT_USES_LIBTORRENT2
1500 if (infoHash.isHybrid())
1502 // this allows to know the being added hybrid torrent by its v1 info hash
1503 // without having yet another mapping table
1504 m_hybridTorrentsByAltID.insert(torrentIDv1, nullptr);
1506 #endif
1507 m_nativeSession->async_add_torrent(resumeData.ltAddTorrentParams);
1508 ++context->processingResumeDataCount;
1511 void SessionImpl::endStartup(ResumeSessionContext *context)
1513 if (m_resumeDataStorage != context->startupStorage)
1515 if (isQueueingSystemEnabled())
1516 saveTorrentsQueue();
1518 const Path dbPath = context->startupStorage->path();
1519 context->startupStorage->deleteLater();
1521 if (context->currentStorageType == ResumeDataStorageType::Legacy)
1523 connect(context->startupStorage, &QObject::destroyed, [dbPath]
1525 Utils::Fs::removeFile(dbPath);
1530 context->deleteLater();
1531 connect(context, &QObject::destroyed, this, [this]
1533 m_nativeSession->resume();
1534 if (m_refreshEnqueued)
1535 m_refreshEnqueued = false;
1536 else
1537 enqueueRefresh();
1539 m_statisticsLastUpdateTimer.start();
1541 // Regular saving of fastresume data
1542 connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
1543 const int saveInterval = saveResumeDataInterval();
1544 if (saveInterval > 0)
1546 m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
1547 m_resumeDataTimer->start();
1550 m_wakeupCheckTimer = new QTimer(this);
1551 connect(m_wakeupCheckTimer, &QTimer::timeout, this, [this]
1553 const auto now = QDateTime::currentDateTime();
1554 if (m_wakeupCheckTimestamp.secsTo(now) > 100)
1556 LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
1557 reannounceToAllTrackers();
1560 m_wakeupCheckTimestamp = QDateTime::currentDateTime();
1562 m_wakeupCheckTimestamp = QDateTime::currentDateTime();
1563 m_wakeupCheckTimer->start(30s);
1565 m_isRestored = true;
1566 emit startupProgressUpdated(100);
1567 emit restored();
1571 void SessionImpl::initializeNativeSession()
1573 lt::settings_pack pack = loadLTSettings();
1575 const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
1576 pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
1578 pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
1579 pack.set_str(lt::settings_pack::user_agent, USER_AGENT.toStdString());
1580 pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
1581 // Speed up exit
1582 pack.set_int(lt::settings_pack::auto_scrape_interval, 1200); // 20 minutes
1583 pack.set_int(lt::settings_pack::auto_scrape_min_interval, 900); // 15 minutes
1584 // libtorrent 1.1 enables UPnP & NAT-PMP by default
1585 // turn them off before `lt::session` ctor to avoid split second effects
1586 pack.set_bool(lt::settings_pack::enable_upnp, false);
1587 pack.set_bool(lt::settings_pack::enable_natpmp, false);
1589 #ifdef QBT_USES_LIBTORRENT2
1590 // preserve the same behavior as in earlier libtorrent versions
1591 pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
1592 #endif
1594 lt::session_params sessionParams {std::move(pack), {}};
1595 #ifdef QBT_USES_LIBTORRENT2
1596 switch (diskIOType())
1598 case DiskIOType::Posix:
1599 sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
1600 break;
1601 case DiskIOType::MMap:
1602 sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
1603 break;
1604 default:
1605 sessionParams.disk_io_constructor = customDiskIOConstructor;
1606 break;
1608 #endif
1610 #if LIBTORRENT_VERSION_NUM < 20100
1611 m_nativeSession = new lt::session(sessionParams, lt::session::paused);
1612 #else
1613 m_nativeSession = new lt::session(sessionParams);
1614 m_nativeSession->pause();
1615 #endif
1617 LogMsg(tr("Peer ID: \"%1\"").arg(QString::fromStdString(peerId)), Log::INFO);
1618 LogMsg(tr("HTTP User-Agent: \"%1\"").arg(USER_AGENT), Log::INFO);
1619 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1620 LogMsg(tr("Local Peer Discovery support: %1").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1621 LogMsg(tr("Peer Exchange (PeX) support: %1").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1622 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1623 LogMsg(tr("Encryption support: %1").arg((encryption() == 0) ? tr("ON") : ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO);
1625 m_nativeSession->set_alert_notify([this]()
1627 QMetaObject::invokeMethod(this, &SessionImpl::readAlerts, Qt::QueuedConnection);
1630 // Enabling plugins
1631 m_nativeSession->add_extension(&lt::create_smart_ban_plugin);
1632 m_nativeSession->add_extension(&lt::create_ut_metadata_plugin);
1633 if (isPeXEnabled())
1634 m_nativeSession->add_extension(&lt::create_ut_pex_plugin);
1636 auto nativeSessionExtension = std::make_shared<NativeSessionExtension>();
1637 m_nativeSession->add_extension(nativeSessionExtension);
1638 m_nativeSessionExtension = nativeSessionExtension.get();
1641 void SessionImpl::processBannedIPs(lt::ip_filter &filter)
1643 // First, import current filter
1644 for (const QString &ip : asConst(m_bannedIPs.get()))
1646 lt::error_code ec;
1647 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1648 Q_ASSERT(!ec);
1649 if (!ec)
1650 filter.add_rule(addr, addr, lt::ip_filter::blocked);
1654 void SessionImpl::initMetrics()
1656 const auto findMetricIndex = [](const char *name) -> int
1658 const int index = lt::find_metric_idx(name);
1659 Q_ASSERT(index >= 0);
1660 return index;
1663 m_metricIndices =
1665 .net =
1667 .hasIncomingConnections = findMetricIndex("net.has_incoming_connections"),
1668 .sentPayloadBytes = findMetricIndex("net.sent_payload_bytes"),
1669 .recvPayloadBytes = findMetricIndex("net.recv_payload_bytes"),
1670 .sentBytes = findMetricIndex("net.sent_bytes"),
1671 .recvBytes = findMetricIndex("net.recv_bytes"),
1672 .sentIPOverheadBytes = findMetricIndex("net.sent_ip_overhead_bytes"),
1673 .recvIPOverheadBytes = findMetricIndex("net.recv_ip_overhead_bytes"),
1674 .sentTrackerBytes = findMetricIndex("net.sent_tracker_bytes"),
1675 .recvTrackerBytes = findMetricIndex("net.recv_tracker_bytes"),
1676 .recvRedundantBytes = findMetricIndex("net.recv_redundant_bytes"),
1677 .recvFailedBytes = findMetricIndex("net.recv_failed_bytes")
1679 .peer =
1681 .numPeersConnected = findMetricIndex("peer.num_peers_connected"),
1682 .numPeersUpDisk = findMetricIndex("peer.num_peers_up_disk"),
1683 .numPeersDownDisk = findMetricIndex("peer.num_peers_down_disk")
1685 .dht =
1687 .dhtBytesIn = findMetricIndex("dht.dht_bytes_in"),
1688 .dhtBytesOut = findMetricIndex("dht.dht_bytes_out"),
1689 .dhtNodes = findMetricIndex("dht.dht_nodes")
1691 .disk =
1693 .diskBlocksInUse = findMetricIndex("disk.disk_blocks_in_use"),
1694 .numBlocksRead = findMetricIndex("disk.num_blocks_read"),
1695 #ifndef QBT_USES_LIBTORRENT2
1696 .numBlocksCacheHits = findMetricIndex("disk.num_blocks_cache_hits"),
1697 #endif
1698 .writeJobs = findMetricIndex("disk.num_write_ops"),
1699 .readJobs = findMetricIndex("disk.num_read_ops"),
1700 .hashJobs = findMetricIndex("disk.num_blocks_hashed"),
1701 .queuedDiskJobs = findMetricIndex("disk.queued_disk_jobs"),
1702 .diskJobTime = findMetricIndex("disk.disk_job_time")
1707 lt::settings_pack SessionImpl::loadLTSettings() const
1709 lt::settings_pack settingsPack;
1711 const lt::alert_category_t alertMask = lt::alert::error_notification
1712 | lt::alert::file_progress_notification
1713 | lt::alert::ip_block_notification
1714 | lt::alert::peer_notification
1715 | (isPerformanceWarningEnabled() ? lt::alert::performance_warning : lt::alert_category_t())
1716 | lt::alert::port_mapping_notification
1717 | lt::alert::status_notification
1718 | lt::alert::storage_notification
1719 | lt::alert::tracker_notification;
1720 settingsPack.set_int(lt::settings_pack::alert_mask, alertMask);
1722 settingsPack.set_int(lt::settings_pack::connection_speed, connectionSpeed());
1724 // from libtorrent doc:
1725 // It will not take affect until the listen_interfaces settings is updated
1726 settingsPack.set_int(lt::settings_pack::send_socket_buffer_size, socketSendBufferSize());
1727 settingsPack.set_int(lt::settings_pack::recv_socket_buffer_size, socketReceiveBufferSize());
1728 settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
1730 applyNetworkInterfacesSettings(settingsPack);
1732 settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
1733 settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
1735 // The most secure, rc4 only so that all streams are encrypted
1736 settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
1737 settingsPack.set_bool(lt::settings_pack::prefer_rc4, true);
1738 switch (encryption())
1740 case 0: // Enabled
1741 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_enabled);
1742 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_enabled);
1743 break;
1744 case 1: // Forced
1745 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_forced);
1746 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_forced);
1747 break;
1748 default: // Disabled
1749 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_disabled);
1750 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_disabled);
1753 settingsPack.set_int(lt::settings_pack::active_checking, maxActiveCheckingTorrents());
1755 // I2P
1756 #if defined(QBT_USES_LIBTORRENT2) && TORRENT_USE_I2P
1757 if (isI2PEnabled())
1759 settingsPack.set_str(lt::settings_pack::i2p_hostname, I2PAddress().toStdString());
1760 settingsPack.set_int(lt::settings_pack::i2p_port, I2PPort());
1761 settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, I2PMixedMode());
1763 else
1765 settingsPack.set_str(lt::settings_pack::i2p_hostname, "");
1766 settingsPack.set_int(lt::settings_pack::i2p_port, 0);
1767 settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, false);
1770 // I2P session options
1771 settingsPack.set_int(lt::settings_pack::i2p_inbound_quantity, I2PInboundQuantity());
1772 settingsPack.set_int(lt::settings_pack::i2p_outbound_quantity, I2POutboundQuantity());
1773 settingsPack.set_int(lt::settings_pack::i2p_inbound_length, I2PInboundLength());
1774 settingsPack.set_int(lt::settings_pack::i2p_outbound_length, I2POutboundLength());
1775 #endif
1777 // proxy
1778 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
1779 const auto *proxyManager = Net::ProxyConfigurationManager::instance();
1780 const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
1781 if ((proxyConfig.type != Net::ProxyType::None) && Preferences::instance()->useProxyForBT())
1783 switch (proxyConfig.type)
1785 case Net::ProxyType::SOCKS4:
1786 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks4);
1787 break;
1789 case Net::ProxyType::HTTP:
1790 if (proxyConfig.authEnabled)
1791 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http_pw);
1792 else
1793 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http);
1794 break;
1796 case Net::ProxyType::SOCKS5:
1797 if (proxyConfig.authEnabled)
1798 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5_pw);
1799 else
1800 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
1801 break;
1803 default:
1804 break;
1807 settingsPack.set_str(lt::settings_pack::proxy_hostname, proxyConfig.ip.toStdString());
1808 settingsPack.set_int(lt::settings_pack::proxy_port, proxyConfig.port);
1810 if (proxyConfig.authEnabled)
1812 settingsPack.set_str(lt::settings_pack::proxy_username, proxyConfig.username.toStdString());
1813 settingsPack.set_str(lt::settings_pack::proxy_password, proxyConfig.password.toStdString());
1816 settingsPack.set_bool(lt::settings_pack::proxy_peer_connections, isProxyPeerConnectionsEnabled());
1817 settingsPack.set_bool(lt::settings_pack::proxy_hostnames, proxyConfig.hostnameLookupEnabled);
1820 settingsPack.set_bool(lt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
1821 settingsPack.set_bool(lt::settings_pack::announce_to_all_tiers, announceToAllTiers());
1823 settingsPack.set_int(lt::settings_pack::peer_turnover, peerTurnover());
1824 settingsPack.set_int(lt::settings_pack::peer_turnover_cutoff, peerTurnoverCutoff());
1825 settingsPack.set_int(lt::settings_pack::peer_turnover_interval, peerTurnoverInterval());
1827 settingsPack.set_int(lt::settings_pack::max_out_request_queue, requestQueueSize());
1829 #ifdef QBT_USES_LIBTORRENT2
1830 settingsPack.set_int(lt::settings_pack::metadata_token_limit, Preferences::instance()->getBdecodeTokenLimit());
1831 #endif
1833 settingsPack.set_int(lt::settings_pack::aio_threads, asyncIOThreads());
1834 #ifdef QBT_USES_LIBTORRENT2
1835 settingsPack.set_int(lt::settings_pack::hashing_threads, hashingThreads());
1836 #endif
1837 settingsPack.set_int(lt::settings_pack::file_pool_size, filePoolSize());
1839 const int checkingMemUsageSize = checkingMemUsage() * 64;
1840 settingsPack.set_int(lt::settings_pack::checking_mem_usage, checkingMemUsageSize);
1842 #ifndef QBT_USES_LIBTORRENT2
1843 const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
1844 settingsPack.set_int(lt::settings_pack::cache_size, cacheSize);
1845 settingsPack.set_int(lt::settings_pack::cache_expiry, diskCacheTTL());
1846 #endif
1848 settingsPack.set_int(lt::settings_pack::max_queued_disk_bytes, diskQueueSize());
1850 switch (diskIOReadMode())
1852 case DiskIOReadMode::DisableOSCache:
1853 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::disable_os_cache);
1854 break;
1855 case DiskIOReadMode::EnableOSCache:
1856 default:
1857 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::enable_os_cache);
1858 break;
1861 switch (diskIOWriteMode())
1863 case DiskIOWriteMode::DisableOSCache:
1864 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::disable_os_cache);
1865 break;
1866 case DiskIOWriteMode::EnableOSCache:
1867 default:
1868 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::enable_os_cache);
1869 break;
1870 #ifdef QBT_USES_LIBTORRENT2
1871 case DiskIOWriteMode::WriteThrough:
1872 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::write_through);
1873 break;
1874 #endif
1877 #ifndef QBT_USES_LIBTORRENT2
1878 settingsPack.set_bool(lt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
1879 settingsPack.set_bool(lt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
1880 #endif
1882 settingsPack.set_bool(lt::settings_pack::piece_extent_affinity, usePieceExtentAffinity());
1884 settingsPack.set_int(lt::settings_pack::suggest_mode, isSuggestModeEnabled()
1885 ? lt::settings_pack::suggest_read_cache : lt::settings_pack::no_piece_suggestions);
1887 settingsPack.set_int(lt::settings_pack::send_buffer_watermark, sendBufferWatermark() * 1024);
1888 settingsPack.set_int(lt::settings_pack::send_buffer_low_watermark, sendBufferLowWatermark() * 1024);
1889 settingsPack.set_int(lt::settings_pack::send_buffer_watermark_factor, sendBufferWatermarkFactor());
1891 settingsPack.set_bool(lt::settings_pack::anonymous_mode, isAnonymousModeEnabled());
1893 // Queueing System
1894 if (isQueueingSystemEnabled())
1896 settingsPack.set_int(lt::settings_pack::active_downloads, maxActiveDownloads());
1897 settingsPack.set_int(lt::settings_pack::active_limit, maxActiveTorrents());
1898 settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
1899 settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
1900 settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
1901 settingsPack.set_int(lt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
1902 settingsPack.set_int(lt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
1904 else
1906 settingsPack.set_int(lt::settings_pack::active_downloads, -1);
1907 settingsPack.set_int(lt::settings_pack::active_seeds, -1);
1908 settingsPack.set_int(lt::settings_pack::active_limit, -1);
1910 settingsPack.set_int(lt::settings_pack::active_tracker_limit, -1);
1911 settingsPack.set_int(lt::settings_pack::active_dht_limit, -1);
1912 settingsPack.set_int(lt::settings_pack::active_lsd_limit, -1);
1913 settingsPack.set_int(lt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
1915 // Outgoing ports
1916 settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
1917 settingsPack.set_int(lt::settings_pack::num_outgoing_ports, (outgoingPortsMax() - outgoingPortsMin()));
1918 // UPnP lease duration
1919 settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
1920 // Type of service
1921 settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
1922 // Include overhead in transfer limits
1923 settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
1924 // IP address to announce to trackers
1925 settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
1926 // Max concurrent HTTP announces
1927 settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
1928 // Stop tracker timeout
1929 settingsPack.set_int(lt::settings_pack::stop_tracker_timeout, stopTrackerTimeout());
1930 // * Max connections limit
1931 settingsPack.set_int(lt::settings_pack::connections_limit, maxConnections());
1932 // * Global max upload slots
1933 settingsPack.set_int(lt::settings_pack::unchoke_slots_limit, maxUploads());
1934 // uTP
1935 switch (btProtocol())
1937 case BTProtocol::Both:
1938 default:
1939 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1940 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1941 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1942 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1943 break;
1945 case BTProtocol::TCP:
1946 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1947 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1948 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, false);
1949 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
1950 break;
1952 case BTProtocol::UTP:
1953 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, false);
1954 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, false);
1955 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1956 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1957 break;
1960 switch (utpMixedMode())
1962 case MixedModeAlgorithm::TCP:
1963 default:
1964 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::prefer_tcp);
1965 break;
1966 case MixedModeAlgorithm::Proportional:
1967 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::peer_proportional);
1968 break;
1971 settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
1973 settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
1975 settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
1977 settingsPack.set_bool(lt::settings_pack::ssrf_mitigation, isSSRFMitigationEnabled());
1979 settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
1981 settingsPack.set_bool(lt::settings_pack::apply_ip_filter_to_trackers, isTrackerFilteringEnabled());
1983 settingsPack.set_str(lt::settings_pack::dht_bootstrap_nodes, getDHTBootstrapNodes().toStdString());
1984 settingsPack.set_bool(lt::settings_pack::enable_dht, isDHTEnabled());
1985 settingsPack.set_bool(lt::settings_pack::enable_lsd, isLSDEnabled());
1987 switch (chokingAlgorithm())
1989 case ChokingAlgorithm::FixedSlots:
1990 default:
1991 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::fixed_slots_choker);
1992 break;
1993 case ChokingAlgorithm::RateBased:
1994 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::rate_based_choker);
1995 break;
1998 switch (seedChokingAlgorithm())
2000 case SeedChokingAlgorithm::RoundRobin:
2001 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::round_robin);
2002 break;
2003 case SeedChokingAlgorithm::FastestUpload:
2004 default:
2005 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::fastest_upload);
2006 break;
2007 case SeedChokingAlgorithm::AntiLeech:
2008 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
2009 break;
2012 return settingsPack;
2015 void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const
2017 if (m_listenInterfaceConfigured)
2018 return;
2020 if (port() > 0) // user has specified port number
2021 settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);
2023 QStringList endpoints;
2024 QStringList outgoingInterfaces;
2025 const QString portString = u':' + QString::number(port());
2027 for (const QString &ip : asConst(getListeningIPs()))
2029 const QHostAddress addr {ip};
2030 if (!addr.isNull())
2032 const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
2033 const QString ip = isIPv6
2034 ? Utils::Net::canonicalIPv6Addr(addr).toString()
2035 : addr.toString();
2037 endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString);
2039 if ((ip != u"0.0.0.0") && (ip != u"::"))
2040 outgoingInterfaces << ip;
2042 else
2044 // ip holds an interface name
2045 #ifdef Q_OS_WIN
2046 // On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
2047 // the interface's LUID and not the GUID.
2048 // Libtorrent expects GUIDs for the 'listen_interfaces' setting.
2049 const QString guid = convertIfaceNameToGuid(ip);
2050 if (!guid.isEmpty())
2052 endpoints << (guid + portString);
2053 outgoingInterfaces << guid;
2055 else
2057 LogMsg(tr("Could not find GUID of network interface. Interface: \"%1\"").arg(ip), Log::WARNING);
2058 // Since we can't get the GUID, we'll pass the interface name instead.
2059 // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
2060 endpoints << (ip + portString);
2061 outgoingInterfaces << ip;
2063 #else
2064 endpoints << (ip + portString);
2065 outgoingInterfaces << ip;
2066 #endif
2070 const QString finalEndpoints = endpoints.join(u',');
2071 settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
2072 LogMsg(tr("Trying to listen on the following list of IP addresses: \"%1\"").arg(finalEndpoints));
2074 settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(u',').toStdString());
2075 m_listenInterfaceConfigured = true;
2078 void SessionImpl::configurePeerClasses()
2080 lt::ip_filter f;
2081 // lt::make_address("255.255.255.255") crashes on some people's systems
2082 // so instead we use address_v4::broadcast()
2083 // Proactively do the same for 0.0.0.0 and address_v4::any()
2084 f.add_rule(lt::address_v4::any()
2085 , lt::address_v4::broadcast()
2086 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
2088 // IPv6 may not be available on OS and the parsing
2089 // would result in an exception -> abnormal program termination
2090 // Affects Windows XP
2093 f.add_rule(lt::address_v6::any()
2094 , lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2095 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
2097 catch (const std::exception &) {}
2099 if (ignoreLimitsOnLAN())
2101 // local networks
2102 f.add_rule(lt::make_address("10.0.0.0")
2103 , lt::make_address("10.255.255.255")
2104 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2105 f.add_rule(lt::make_address("172.16.0.0")
2106 , lt::make_address("172.31.255.255")
2107 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2108 f.add_rule(lt::make_address("192.168.0.0")
2109 , lt::make_address("192.168.255.255")
2110 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2111 // link local
2112 f.add_rule(lt::make_address("169.254.0.0")
2113 , lt::make_address("169.254.255.255")
2114 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2115 // loopback
2116 f.add_rule(lt::make_address("127.0.0.0")
2117 , lt::make_address("127.255.255.255")
2118 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2120 // IPv6 may not be available on OS and the parsing
2121 // would result in an exception -> abnormal program termination
2122 // Affects Windows XP
2125 // link local
2126 f.add_rule(lt::make_address("fe80::")
2127 , lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2128 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2129 // unique local addresses
2130 f.add_rule(lt::make_address("fc00::")
2131 , lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2132 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2133 // loopback
2134 f.add_rule(lt::address_v6::loopback()
2135 , lt::address_v6::loopback()
2136 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2138 catch (const std::exception &) {}
2140 m_nativeSession->set_peer_class_filter(f);
2142 lt::peer_class_type_filter peerClassTypeFilter;
2143 peerClassTypeFilter.add(lt::peer_class_type_filter::tcp_socket, lt::session::tcp_peer_class_id);
2144 peerClassTypeFilter.add(lt::peer_class_type_filter::ssl_tcp_socket, lt::session::tcp_peer_class_id);
2145 peerClassTypeFilter.add(lt::peer_class_type_filter::i2p_socket, lt::session::tcp_peer_class_id);
2146 if (!isUTPRateLimited())
2148 peerClassTypeFilter.disallow(lt::peer_class_type_filter::utp_socket
2149 , lt::session::global_peer_class_id);
2150 peerClassTypeFilter.disallow(lt::peer_class_type_filter::ssl_utp_socket
2151 , lt::session::global_peer_class_id);
2153 m_nativeSession->set_peer_class_type_filter(peerClassTypeFilter);
2156 void SessionImpl::enableTracker(const bool enable)
2158 const QString profile = u"embeddedTracker"_s;
2159 auto *portForwarder = Net::PortForwarder::instance();
2161 if (enable)
2163 if (!m_tracker)
2164 m_tracker = new Tracker(this);
2166 m_tracker->start();
2168 const auto *pref = Preferences::instance();
2169 if (pref->isTrackerPortForwardingEnabled())
2170 portForwarder->setPorts(profile, {static_cast<quint16>(pref->getTrackerPort())});
2171 else
2172 portForwarder->removePorts(profile);
2174 else
2176 delete m_tracker;
2178 portForwarder->removePorts(profile);
2182 void SessionImpl::enableBandwidthScheduler()
2184 if (!m_bwScheduler)
2186 m_bwScheduler = new BandwidthScheduler(this);
2187 connect(m_bwScheduler.data(), &BandwidthScheduler::bandwidthLimitRequested
2188 , this, &SessionImpl::setAltGlobalSpeedLimitEnabled);
2190 m_bwScheduler->start();
2193 void SessionImpl::populateAdditionalTrackers()
2195 m_additionalTrackerList.clear();
2197 const QString trackers = additionalTrackers();
2198 for (QStringView tracker : asConst(QStringView(trackers).split(u'\n')))
2200 tracker = tracker.trimmed();
2201 if (!tracker.isEmpty())
2202 m_additionalTrackerList.append({tracker.toString()});
2206 void SessionImpl::processShareLimits()
2208 qDebug("Processing share limits...");
2210 const auto resolveLimitValue = []<typename T>(const T limit, const T useGlobalLimit, const T globalLimit) -> T
2212 return (limit == useGlobalLimit) ? globalLimit : limit;
2215 // We shouldn't iterate over `m_torrents` in the loop below
2216 // since `deleteTorrent()` modifies it indirectly
2217 const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
2218 for (const auto &[torrentID, torrent] : torrents.asKeyValueRange())
2220 if (!torrent->isFinished() || torrent->isForced())
2221 continue;
2223 if (const qreal ratioLimit = resolveLimitValue(torrent->ratioLimit(), Torrent::USE_GLOBAL_RATIO, globalMaxRatio());
2224 ratioLimit >= 0)
2226 const qreal ratio = torrent->realRatio();
2227 qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
2229 if ((ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
2231 const QString description = tr("Torrent reached the share ratio limit.");
2232 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2234 if (m_maxRatioAction == Remove)
2236 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent."), torrentName));
2237 deleteTorrent(torrentID);
2239 else if (m_maxRatioAction == DeleteFiles)
2241 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent and deleted its content."), torrentName));
2242 deleteTorrent(torrentID, DeleteTorrentAndFiles);
2244 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
2246 torrent->pause();
2247 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent paused."), torrentName));
2249 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
2251 torrent->setSuperSeeding(true);
2252 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
2255 continue;
2259 if (const int seedingTimeLimit = resolveLimitValue(torrent->seedingTimeLimit(), Torrent::USE_GLOBAL_SEEDING_TIME, globalMaxSeedingMinutes());
2260 seedingTimeLimit >= 0)
2262 const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
2264 if ((seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
2266 const QString description = tr("Torrent reached the seeding time limit.");
2267 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2269 if (m_maxRatioAction == Remove)
2271 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent."), torrentName));
2272 deleteTorrent(torrentID);
2274 else if (m_maxRatioAction == DeleteFiles)
2276 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent and deleted its content."), torrentName));
2277 deleteTorrent(torrentID, DeleteTorrentAndFiles);
2279 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
2281 torrent->pause();
2282 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent paused."), torrentName));
2284 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
2286 torrent->setSuperSeeding(true);
2287 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
2290 continue;
2294 if (const int inactiveSeedingTimeLimit = resolveLimitValue(torrent->inactiveSeedingTimeLimit(), Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME, globalMaxInactiveSeedingMinutes());
2295 inactiveSeedingTimeLimit >= 0)
2297 const qlonglong inactiveSeedingTimeInMinutes = torrent->timeSinceActivity() / 60;
2299 if ((inactiveSeedingTimeInMinutes <= Torrent::MAX_INACTIVE_SEEDING_TIME) && (inactiveSeedingTimeInMinutes >= inactiveSeedingTimeLimit))
2301 const QString description = tr("Torrent reached the inactive seeding time limit.");
2302 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2304 if (m_maxRatioAction == Remove)
2306 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent."), torrentName));
2307 deleteTorrent(torrentID);
2309 else if (m_maxRatioAction == DeleteFiles)
2311 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent and deleted its content."), torrentName));
2312 deleteTorrent(torrentID, DeleteTorrentAndFiles);
2314 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
2316 torrent->pause();
2317 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent paused."), torrentName));
2319 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
2321 torrent->setSuperSeeding(true);
2322 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
2329 void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
2331 TorrentImpl *torrent = m_torrents.value(id);
2332 if (torrent)
2334 torrent->fileSearchFinished(savePath, fileNames);
2335 return;
2338 const auto loadingTorrentsIter = m_loadingTorrents.find(id);
2339 if (loadingTorrentsIter != m_loadingTorrents.end())
2341 LoadTorrentParams &params = loadingTorrentsIter.value();
2342 lt::add_torrent_params &p = params.ltAddTorrentParams;
2344 p.save_path = savePath.toString().toStdString();
2345 const TorrentInfo torrentInfo {*p.ti};
2346 const auto nativeIndexes = torrentInfo.nativeIndexes();
2347 for (int i = 0; i < fileNames.size(); ++i)
2348 p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString();
2350 m_nativeSession->async_add_torrent(p);
2354 Torrent *SessionImpl::getTorrent(const TorrentID &id) const
2356 return m_torrents.value(id);
2359 Torrent *SessionImpl::findTorrent(const InfoHash &infoHash) const
2361 const auto id = TorrentID::fromInfoHash(infoHash);
2362 if (Torrent *torrent = m_torrents.value(id); torrent)
2363 return torrent;
2365 if (!infoHash.isHybrid())
2366 return m_hybridTorrentsByAltID.value(id);
2368 // alternative ID can be useful to find existing torrent
2369 // in case if hybrid torrent was added by v1 info hash
2370 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
2371 return m_torrents.value(altID);
2374 void SessionImpl::banIP(const QString &ip)
2376 if (m_bannedIPs.get().contains(ip))
2377 return;
2379 lt::error_code ec;
2380 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
2381 Q_ASSERT(!ec);
2382 if (ec)
2383 return;
2385 invokeAsync([session = m_nativeSession, addr]
2387 lt::ip_filter filter = session->get_ip_filter();
2388 filter.add_rule(addr, addr, lt::ip_filter::blocked);
2389 session->set_ip_filter(std::move(filter));
2392 QStringList bannedIPs = m_bannedIPs;
2393 bannedIPs.append(ip);
2394 bannedIPs.sort();
2395 m_bannedIPs = bannedIPs;
2398 // Delete a torrent from the session, given its hash
2399 // and from the disk, if the corresponding deleteOption is chosen
2400 bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
2402 TorrentImpl *const torrent = m_torrents.take(id);
2403 if (!torrent) return false;
2405 qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
2406 emit torrentAboutToBeRemoved(torrent);
2408 if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
2409 m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
2411 // Remove it from session
2412 if (deleteOption == DeleteTorrent)
2414 m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
2416 const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
2417 const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
2418 , [&nativeHandle](const MoveStorageJob &job)
2420 return job.torrentHandle == nativeHandle;
2422 if (iter != m_moveStorageQueue.end())
2424 // We shouldn't actually remove torrent until existing "move storage jobs" are done
2425 torrentQueuePositionBottom(nativeHandle);
2426 nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
2427 nativeHandle.pause();
2429 else
2431 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
2434 else
2436 m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
2438 if (m_moveStorageQueue.size() > 1)
2440 // Delete "move storage job" for the deleted torrent
2441 // (note: we shouldn't delete active job)
2442 const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
2443 , [torrent](const MoveStorageJob &job)
2445 return job.torrentHandle == torrent->nativeHandle();
2447 if (iter != m_moveStorageQueue.end())
2448 m_moveStorageQueue.erase(iter);
2451 m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
2454 // Remove it from torrent resume directory
2455 m_resumeDataStorage->remove(torrent->id());
2457 delete torrent;
2458 return true;
2461 bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
2463 const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
2464 if (downloadedMetadataIter == m_downloadedMetadata.end())
2465 return false;
2467 const lt::torrent_handle nativeHandle = downloadedMetadataIter.value();
2468 m_downloadedMetadata.erase(downloadedMetadataIter);
2470 if (!nativeHandle.is_valid())
2471 return true;
2473 #ifdef QBT_USES_LIBTORRENT2
2474 const InfoHash infoHash {nativeHandle.info_hashes()};
2475 if (infoHash.isHybrid())
2477 // if magnet link was hybrid initially then it is indexed also by v1 info hash
2478 // so we need to remove both entries
2479 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
2480 m_downloadedMetadata.remove((altID == downloadedMetadataIter.key()) ? id : altID);
2482 #endif
2484 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
2485 return true;
2488 void SessionImpl::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
2490 using ElementType = std::pair<int, const TorrentImpl *>;
2491 std::priority_queue<ElementType
2492 , std::vector<ElementType>
2493 , std::greater<ElementType>> torrentQueue;
2495 // Sort torrents by queue position
2496 for (const TorrentID &id : ids)
2498 const TorrentImpl *torrent = m_torrents.value(id);
2499 if (!torrent) continue;
2500 if (const int position = torrent->queuePosition(); position >= 0)
2501 torrentQueue.emplace(position, torrent);
2504 // Increase torrents queue position (starting with the one in the highest queue position)
2505 while (!torrentQueue.empty())
2507 const TorrentImpl *torrent = torrentQueue.top().second;
2508 torrentQueuePositionUp(torrent->nativeHandle());
2509 torrentQueue.pop();
2512 m_torrentsQueueChanged = true;
2515 void SessionImpl::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
2517 using ElementType = std::pair<int, const TorrentImpl *>;
2518 std::priority_queue<ElementType> torrentQueue;
2520 // Sort torrents by queue position
2521 for (const TorrentID &id : ids)
2523 const TorrentImpl *torrent = m_torrents.value(id);
2524 if (!torrent) continue;
2525 if (const int position = torrent->queuePosition(); position >= 0)
2526 torrentQueue.emplace(position, torrent);
2529 // Decrease torrents queue position (starting with the one in the lowest queue position)
2530 while (!torrentQueue.empty())
2532 const TorrentImpl *torrent = torrentQueue.top().second;
2533 torrentQueuePositionDown(torrent->nativeHandle());
2534 torrentQueue.pop();
2537 for (const lt::torrent_handle &torrentHandle : asConst(m_downloadedMetadata))
2538 torrentQueuePositionBottom(torrentHandle);
2540 m_torrentsQueueChanged = true;
2543 void SessionImpl::topTorrentsQueuePos(const QVector<TorrentID> &ids)
2545 using ElementType = std::pair<int, const TorrentImpl *>;
2546 std::priority_queue<ElementType> torrentQueue;
2548 // Sort torrents by queue position
2549 for (const TorrentID &id : ids)
2551 const TorrentImpl *torrent = m_torrents.value(id);
2552 if (!torrent) continue;
2553 if (const int position = torrent->queuePosition(); position >= 0)
2554 torrentQueue.emplace(position, torrent);
2557 // Top torrents queue position (starting with the one in the lowest queue position)
2558 while (!torrentQueue.empty())
2560 const TorrentImpl *torrent = torrentQueue.top().second;
2561 torrentQueuePositionTop(torrent->nativeHandle());
2562 torrentQueue.pop();
2565 m_torrentsQueueChanged = true;
2568 void SessionImpl::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
2570 using ElementType = std::pair<int, const TorrentImpl *>;
2571 std::priority_queue<ElementType
2572 , std::vector<ElementType>
2573 , std::greater<ElementType>> torrentQueue;
2575 // Sort torrents by queue position
2576 for (const TorrentID &id : ids)
2578 const TorrentImpl *torrent = m_torrents.value(id);
2579 if (!torrent) continue;
2580 if (const int position = torrent->queuePosition(); position >= 0)
2581 torrentQueue.emplace(position, torrent);
2584 // Bottom torrents queue position (starting with the one in the highest queue position)
2585 while (!torrentQueue.empty())
2587 const TorrentImpl *torrent = torrentQueue.top().second;
2588 torrentQueuePositionBottom(torrent->nativeHandle());
2589 torrentQueue.pop();
2592 for (const lt::torrent_handle &torrentHandle : asConst(m_downloadedMetadata))
2593 torrentQueuePositionBottom(torrentHandle);
2595 m_torrentsQueueChanged = true;
2598 void SessionImpl::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
2600 if (m_needSaveResumeDataTorrents.empty())
2602 QMetaObject::invokeMethod(this, [this]()
2604 for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
2606 TorrentImpl *torrent = m_torrents.value(torrentID);
2607 if (torrent)
2608 torrent->saveResumeData();
2610 m_needSaveResumeDataTorrents.clear();
2611 }, Qt::QueuedConnection);
2614 m_needSaveResumeDataTorrents.insert(torrent->id());
2617 void SessionImpl::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
2619 qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
2620 ++m_numResumeData;
2623 void SessionImpl::handleTorrentSaveResumeDataFailed([[maybe_unused]] const TorrentImpl *torrent)
2625 --m_numResumeData;
2628 QVector<Torrent *> SessionImpl::torrents() const
2630 QVector<Torrent *> result;
2631 result.reserve(m_torrents.size());
2632 for (TorrentImpl *torrent : asConst(m_torrents))
2633 result << torrent;
2635 return result;
2638 qsizetype SessionImpl::torrentsCount() const
2640 return m_torrents.size();
2643 bool SessionImpl::addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams &params)
2645 if (!isRestored())
2646 return false;
2648 return addTorrent_impl(torrentDescr, params);
2651 LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &addTorrentParams)
2653 LoadTorrentParams loadTorrentParams;
2655 loadTorrentParams.name = addTorrentParams.name;
2656 loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
2657 loadTorrentParams.hasFinishedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
2658 loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
2659 loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged);
2660 loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused());
2661 loadTorrentParams.stopCondition = addTorrentParams.stopCondition.value_or(torrentStopCondition());
2662 loadTorrentParams.addToQueueTop = addTorrentParams.addToQueueTop.value_or(isAddTorrentToQueueTop());
2663 loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
2664 loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
2665 loadTorrentParams.inactiveSeedingTimeLimit = addTorrentParams.inactiveSeedingTimeLimit;
2667 const QString category = addTorrentParams.category;
2668 if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
2669 loadTorrentParams.category = u""_s;
2670 else
2671 loadTorrentParams.category = category;
2673 const auto defaultSavePath = suggestedSavePath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
2674 const auto defaultDownloadPath = suggestedDownloadPath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
2676 loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(
2677 addTorrentParams.savePath.isEmpty() && addTorrentParams.downloadPath.isEmpty() && !isAutoTMMDisabledByDefault());
2679 if (!loadTorrentParams.useAutoTMM)
2681 if (addTorrentParams.savePath.isAbsolute())
2682 loadTorrentParams.savePath = addTorrentParams.savePath;
2683 else
2684 loadTorrentParams.savePath = defaultSavePath / addTorrentParams.savePath;
2686 // if useDownloadPath isn't specified but downloadPath is explicitly set we prefer to use it
2687 const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(!addTorrentParams.downloadPath.isEmpty() || isDownloadPathEnabled());
2688 if (useDownloadPath)
2690 // Overridden "Download path" settings
2692 if (addTorrentParams.downloadPath.isAbsolute())
2694 loadTorrentParams.downloadPath = addTorrentParams.downloadPath;
2696 else
2698 const Path basePath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath());
2699 loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath;
2704 for (const Tag &tag : addTorrentParams.tags)
2706 if (hasTag(tag) || addTag(tag))
2707 loadTorrentParams.tags.insert(tag);
2710 return loadTorrentParams;
2713 // Add a torrent to the BitTorrent session
2714 bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorrentParams &addTorrentParams)
2716 Q_ASSERT(isRestored());
2718 const bool hasMetadata = (source.info().has_value());
2719 const auto infoHash = source.infoHash();
2720 const auto id = TorrentID::fromInfoHash(infoHash);
2722 // alternative ID can be useful to find existing torrent in case if hybrid torrent was added by v1 info hash
2723 const auto altID = (infoHash.isHybrid() ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
2725 // We should not add the torrent if it is already
2726 // processed or is pending to add to session
2727 if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
2728 return false;
2730 if (findTorrent(infoHash))
2731 return false;
2733 // It looks illogical that we don't just use an existing handle,
2734 // but as previous experience has shown, it actually creates unnecessary
2735 // problems and unwanted behavior due to the fact that it was originally
2736 // added with parameters other than those provided by the user.
2737 cancelDownloadMetadata(id);
2738 if (infoHash.isHybrid())
2739 cancelDownloadMetadata(altID);
2741 LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams);
2742 lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
2743 p = source.ltAddTorrentParams();
2745 bool isFindingIncompleteFiles = false;
2747 const bool useAutoTMM = loadTorrentParams.useAutoTMM;
2748 const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
2750 if (hasMetadata)
2752 // Torrent that is being added with metadata is considered to be added as stopped
2753 // if "metadata received" stop condition is set for it.
2754 if (loadTorrentParams.stopCondition == Torrent::StopCondition::MetadataReceived)
2756 loadTorrentParams.stopped = true;
2757 loadTorrentParams.stopCondition = Torrent::StopCondition::None;
2760 const TorrentInfo &torrentInfo = *source.info();
2762 Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
2764 PathList filePaths = addTorrentParams.filePaths;
2765 if (filePaths.isEmpty())
2767 filePaths = torrentInfo.filePaths();
2768 if (loadTorrentParams.contentLayout != TorrentContentLayout::Original)
2770 const Path originalRootFolder = Path::findRootFolder(filePaths);
2771 const auto originalContentLayout = (originalRootFolder.isEmpty()
2772 ? TorrentContentLayout::NoSubfolder : TorrentContentLayout::Subfolder);
2773 if (loadTorrentParams.contentLayout != originalContentLayout)
2775 if (loadTorrentParams.contentLayout == TorrentContentLayout::NoSubfolder)
2776 Path::stripRootFolder(filePaths);
2777 else
2778 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2783 // if torrent name wasn't explicitly set we handle the case of
2784 // initial renaming of torrent content and rename torrent accordingly
2785 if (loadTorrentParams.name.isEmpty())
2787 QString contentName = Path::findRootFolder(filePaths).toString();
2788 if (contentName.isEmpty() && (filePaths.size() == 1))
2789 contentName = filePaths.at(0).filename();
2791 if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
2792 loadTorrentParams.name = contentName;
2795 if (!loadTorrentParams.hasFinishedStatus)
2797 const Path actualDownloadPath = useAutoTMM
2798 ? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
2799 findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
2800 isFindingIncompleteFiles = true;
2803 const auto nativeIndexes = torrentInfo.nativeIndexes();
2804 if (!isFindingIncompleteFiles)
2806 for (int index = 0; index < filePaths.size(); ++index)
2807 p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
2810 Q_ASSERT(p.file_priorities.empty());
2811 Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
2813 const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2814 // Use qBittorrent default priority rather than libtorrent's (4)
2815 p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2817 if (addTorrentParams.filePriorities.isEmpty())
2819 if (isExcludedFileNamesEnabled())
2821 // Check file name blacklist when priorities are not explicitly set
2822 for (int i = 0; i < filePaths.size(); ++i)
2824 if (isFilenameExcluded(filePaths.at(i).filename()))
2825 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = lt::dont_download;
2829 else
2831 for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
2832 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
2835 Q_ASSERT(p.ti);
2837 else
2839 if (loadTorrentParams.name.isEmpty() && !p.name.empty())
2840 loadTorrentParams.name = QString::fromStdString(p.name);
2843 p.save_path = actualSavePath.toString().toStdString();
2845 if (isAddTrackersEnabled() && !(hasMetadata && p.ti->priv()))
2847 p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
2848 p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
2849 p.tracker_tiers.resize(p.trackers.size(), 0);
2850 for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
2852 p.trackers.push_back(trackerEntry.url.toStdString());
2853 p.tracker_tiers.push_back(trackerEntry.tier);
2857 p.upload_limit = addTorrentParams.uploadLimit;
2858 p.download_limit = addTorrentParams.downloadLimit;
2860 // Preallocation mode
2861 p.storage_mode = isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse;
2863 if (addTorrentParams.sequential)
2864 p.flags |= lt::torrent_flags::sequential_download;
2865 else
2866 p.flags &= ~lt::torrent_flags::sequential_download;
2868 // Seeding mode
2869 // Skip checking and directly start seeding
2870 if (addTorrentParams.skipChecking)
2871 p.flags |= lt::torrent_flags::seed_mode;
2872 else
2873 p.flags &= ~lt::torrent_flags::seed_mode;
2875 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::AutoManaged))
2876 p.flags |= lt::torrent_flags::paused;
2877 else
2878 p.flags &= ~lt::torrent_flags::paused;
2879 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::Forced))
2880 p.flags &= ~lt::torrent_flags::auto_managed;
2881 else
2882 p.flags |= lt::torrent_flags::auto_managed;
2884 p.flags |= lt::torrent_flags::duplicate_is_error;
2886 p.added_time = std::time(nullptr);
2888 // Limits
2889 p.max_connections = maxConnectionsPerTorrent();
2890 p.max_uploads = maxUploadsPerTorrent();
2892 p.userdata = LTClientData(new ExtensionData);
2893 #ifndef QBT_USES_LIBTORRENT2
2894 p.storage = customStorageConstructor;
2895 #endif
2897 m_loadingTorrents.insert(id, loadTorrentParams);
2898 if (infoHash.isHybrid())
2899 m_hybridTorrentsByAltID.insert(altID, nullptr);
2900 if (!isFindingIncompleteFiles)
2901 m_nativeSession->async_add_torrent(p);
2903 return true;
2906 void SessionImpl::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
2907 , const Path &downloadPath, const PathList &filePaths) const
2909 Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
2911 const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
2912 const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
2913 QMetaObject::invokeMethod(m_fileSearcher, [=, this]
2915 m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath, isAppendExtensionEnabled());
2919 void SessionImpl::enablePortMapping()
2921 invokeAsync([this]
2923 if (m_isPortMappingEnabled)
2924 return;
2926 lt::settings_pack settingsPack;
2927 settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
2928 settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
2929 m_nativeSession->apply_settings(std::move(settingsPack));
2931 m_isPortMappingEnabled = true;
2933 LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
2937 void SessionImpl::disablePortMapping()
2939 invokeAsync([this]
2941 if (!m_isPortMappingEnabled)
2942 return;
2944 lt::settings_pack settingsPack;
2945 settingsPack.set_bool(lt::settings_pack::enable_upnp, false);
2946 settingsPack.set_bool(lt::settings_pack::enable_natpmp, false);
2947 m_nativeSession->apply_settings(std::move(settingsPack));
2949 m_mappedPorts.clear();
2950 m_isPortMappingEnabled = false;
2952 LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO);
2956 void SessionImpl::addMappedPorts(const QSet<quint16> &ports)
2958 invokeAsync([this, ports]
2960 if (!m_isPortMappingEnabled)
2961 return;
2963 for (const quint16 port : ports)
2965 if (!m_mappedPorts.contains(port))
2966 m_mappedPorts.insert(port, m_nativeSession->add_port_mapping(lt::session::tcp, port, port));
2971 void SessionImpl::removeMappedPorts(const QSet<quint16> &ports)
2973 invokeAsync([this, ports]
2975 if (!m_isPortMappingEnabled)
2976 return;
2978 Algorithm::removeIf(m_mappedPorts, [this, ports](const quint16 port, const std::vector<lt::port_mapping_t> &handles)
2980 if (!ports.contains(port))
2981 return false;
2983 for (const lt::port_mapping_t &handle : handles)
2984 m_nativeSession->delete_port_mapping(handle);
2986 return true;
2991 void SessionImpl::invokeAsync(std::function<void ()> func)
2993 m_asyncWorker->start(std::move(func));
2996 // Add a torrent to libtorrent session in hidden mode
2997 // and force it to download its metadata
2998 bool SessionImpl::downloadMetadata(const TorrentDescriptor &torrentDescr)
3000 Q_ASSERT(!torrentDescr.info().has_value());
3001 if (torrentDescr.info().has_value()) [[unlikely]]
3002 return false;
3004 const InfoHash infoHash = torrentDescr.infoHash();
3006 // We should not add torrent if it's already
3007 // processed or adding to session
3008 if (isKnownTorrent(infoHash))
3009 return false;
3011 lt::add_torrent_params p = torrentDescr.ltAddTorrentParams();
3013 if (isAddTrackersEnabled())
3015 // Use "additional trackers" when metadata retrieving (this can help when the DHT nodes are few)
3016 p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
3017 p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
3018 p.tracker_tiers.resize(p.trackers.size(), 0);
3019 for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
3021 p.trackers.push_back(trackerEntry.url.toStdString());
3022 p.tracker_tiers.push_back(trackerEntry.tier);
3026 // Flags
3027 // Preallocation mode
3028 if (isPreallocationEnabled())
3029 p.storage_mode = lt::storage_mode_allocate;
3030 else
3031 p.storage_mode = lt::storage_mode_sparse;
3033 // Limits
3034 p.max_connections = maxConnectionsPerTorrent();
3035 p.max_uploads = maxUploadsPerTorrent();
3037 const auto id = TorrentID::fromInfoHash(infoHash);
3038 const Path savePath = Utils::Fs::tempPath() / Path(id.toString());
3039 p.save_path = savePath.toString().toStdString();
3041 // Forced start
3042 p.flags &= ~lt::torrent_flags::paused;
3043 p.flags &= ~lt::torrent_flags::auto_managed;
3045 // Solution to avoid accidental file writes
3046 p.flags |= lt::torrent_flags::upload_mode;
3048 #ifndef QBT_USES_LIBTORRENT2
3049 p.storage = customStorageConstructor;
3050 #endif
3052 // Adding torrent to libtorrent session
3053 m_nativeSession->async_add_torrent(p);
3054 m_downloadedMetadata.insert(id, {});
3056 return true;
3059 void SessionImpl::exportTorrentFile(const Torrent *torrent, const Path &folderPath)
3061 if (!folderPath.exists() && !Utils::Fs::mkpath(folderPath))
3062 return;
3064 const QString validName = Utils::Fs::toValidFileName(torrent->name());
3065 QString torrentExportFilename = u"%1.torrent"_s.arg(validName);
3066 Path newTorrentPath = folderPath / Path(torrentExportFilename);
3067 int counter = 0;
3068 while (newTorrentPath.exists())
3070 // Append number to torrent name to make it unique
3071 torrentExportFilename = u"%1 %2.torrent"_s.arg(validName).arg(++counter);
3072 newTorrentPath = folderPath / Path(torrentExportFilename);
3075 const nonstd::expected<void, QString> result = torrent->exportToFile(newTorrentPath);
3076 if (!result)
3078 LogMsg(tr("Failed to export torrent. Torrent: \"%1\". Destination: \"%2\". Reason: \"%3\"")
3079 .arg(torrent->name(), newTorrentPath.toString(), result.error()), Log::WARNING);
3083 void SessionImpl::generateResumeData()
3085 for (TorrentImpl *const torrent : asConst(m_torrents))
3087 if (!torrent->isValid()) continue;
3089 if (torrent->needSaveResumeData())
3091 torrent->saveResumeData();
3092 m_needSaveResumeDataTorrents.remove(torrent->id());
3097 // Called on exit
3098 void SessionImpl::saveResumeData()
3100 for (const TorrentImpl *torrent : asConst(m_torrents))
3102 // When the session is terminated due to unrecoverable error
3103 // some of the torrent handles can be corrupted
3106 torrent->nativeHandle().save_resume_data(lt::torrent_handle::only_if_modified);
3107 ++m_numResumeData;
3109 catch (const std::exception &) {}
3112 // clear queued storage move jobs except the current ongoing one
3113 if (m_moveStorageQueue.size() > 1)
3114 m_moveStorageQueue.resize(1);
3116 QElapsedTimer timer;
3117 timer.start();
3119 while ((m_numResumeData > 0) || !m_moveStorageQueue.isEmpty() || m_needSaveTorrentsQueue)
3121 const lt::seconds waitTime {5};
3122 const lt::seconds expireTime {30};
3124 // only terminate when no storage is moving
3125 if (timer.hasExpired(lt::total_milliseconds(expireTime)) && m_moveStorageQueue.isEmpty())
3127 LogMsg(tr("Aborted saving resume data. Number of outstanding torrents: %1").arg(QString::number(m_numResumeData))
3128 , Log::CRITICAL);
3129 break;
3132 const std::vector<lt::alert *> alerts = getPendingAlerts(waitTime);
3134 bool hasWantedAlert = false;
3135 for (const lt::alert *a : alerts)
3137 if (const int alertType = a->type();
3138 (alertType == lt::save_resume_data_alert::alert_type) || (alertType == lt::save_resume_data_failed_alert::alert_type)
3139 || (alertType == lt::storage_moved_alert::alert_type) || (alertType == lt::storage_moved_failed_alert::alert_type)
3140 || (alertType == lt::state_update_alert::alert_type))
3142 hasWantedAlert = true;
3145 handleAlert(a);
3148 if (hasWantedAlert)
3149 timer.start();
3153 void SessionImpl::saveTorrentsQueue()
3155 QVector<TorrentID> queue;
3156 for (const TorrentImpl *torrent : asConst(m_torrents))
3158 if (const int queuePos = torrent->queuePosition(); queuePos >= 0)
3160 if (queuePos >= queue.size())
3161 queue.resize(queuePos + 1);
3162 queue[queuePos] = torrent->id();
3166 m_resumeDataStorage->storeQueue(queue);
3167 m_needSaveTorrentsQueue = false;
3170 void SessionImpl::removeTorrentsQueue()
3172 m_resumeDataStorage->storeQueue({});
3173 m_torrentsQueueChanged = false;
3174 m_needSaveTorrentsQueue = false;
3177 void SessionImpl::setSavePath(const Path &path)
3179 const auto newPath = (path.isAbsolute() ? path : (specialFolderLocation(SpecialFolder::Downloads) / path));
3180 if (newPath == m_savePath)
3181 return;
3183 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3185 QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
3186 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
3188 const QString &categoryName = it.key();
3189 const CategoryOptions &categoryOptions = it.value();
3190 if (categoryOptions.savePath.isRelative())
3191 affectedCatogories.insert(categoryName);
3194 for (TorrentImpl *const torrent : asConst(m_torrents))
3196 if (affectedCatogories.contains(torrent->category()))
3197 torrent->setAutoTMMEnabled(false);
3201 m_savePath = newPath;
3202 for (TorrentImpl *const torrent : asConst(m_torrents))
3203 torrent->handleCategoryOptionsChanged();
3206 void SessionImpl::setDownloadPath(const Path &path)
3208 const Path newPath = (path.isAbsolute() ? path : (savePath() / Path(u"temp"_s) / path));
3209 if (newPath == m_downloadPath)
3210 return;
3212 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3214 QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
3215 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
3217 const QString &categoryName = it.key();
3218 const CategoryOptions &categoryOptions = it.value();
3219 const DownloadPathOption downloadPathOption =
3220 categoryOptions.downloadPath.value_or(DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
3221 if (downloadPathOption.enabled && downloadPathOption.path.isRelative())
3222 affectedCatogories.insert(categoryName);
3225 for (TorrentImpl *const torrent : asConst(m_torrents))
3227 if (affectedCatogories.contains(torrent->category()))
3228 torrent->setAutoTMMEnabled(false);
3232 m_downloadPath = newPath;
3233 for (TorrentImpl *const torrent : asConst(m_torrents))
3234 torrent->handleCategoryOptionsChanged();
3237 QStringList SessionImpl::getListeningIPs() const
3239 QStringList IPs;
3241 const QString ifaceName = networkInterface();
3242 const QString ifaceAddr = networkInterfaceAddress();
3243 const QHostAddress configuredAddr(ifaceAddr);
3244 const bool allIPv4 = (ifaceAddr == u"0.0.0.0"); // Means All IPv4 addresses
3245 const bool allIPv6 = (ifaceAddr == u"::"); // Means All IPv6 addresses
3247 if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull())
3249 LogMsg(tr("The configured network address is invalid. Address: \"%1\"").arg(ifaceAddr), Log::CRITICAL);
3250 // Pass the invalid user configured interface name/address to libtorrent
3251 // in hopes that it will come online later.
3252 // This will not cause IP leak but allow user to reconnect the interface
3253 // and re-establish connection without restarting the client.
3254 IPs.append(ifaceAddr);
3255 return IPs;
3258 if (ifaceName.isEmpty())
3260 if (ifaceAddr.isEmpty())
3261 return {u"0.0.0.0"_s, u"::"_s}; // Indicates all interfaces + all addresses (aka default)
3263 if (allIPv4)
3264 return {u"0.0.0.0"_s};
3266 if (allIPv6)
3267 return {u"::"_s};
3270 const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
3272 if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
3273 || (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
3274 return;
3276 if ((match == addr) || allIPv4 || allIPv6)
3277 IPs.append(addr.toString());
3280 if (ifaceName.isEmpty())
3282 const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
3283 for (const auto &addr : addresses)
3284 checkAndAddIP(addr, configuredAddr);
3286 // At this point ifaceAddr was non-empty
3287 // If IPs.isEmpty() it means the configured Address was not found
3288 if (IPs.isEmpty())
3290 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3291 .arg(ifaceAddr), Log::CRITICAL);
3292 IPs.append(ifaceAddr);
3295 return IPs;
3298 // Attempt to listen on provided interface
3299 const QNetworkInterface networkIFace = QNetworkInterface::interfaceFromName(ifaceName);
3300 if (!networkIFace.isValid())
3302 qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName));
3303 LogMsg(tr("The configured network interface is invalid. Interface: \"%1\"").arg(ifaceName), Log::CRITICAL);
3304 IPs.append(ifaceName);
3305 return IPs;
3308 if (ifaceAddr.isEmpty())
3310 IPs.append(ifaceName);
3311 return IPs; // On Windows calling code converts it to GUID
3314 const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
3315 qDebug() << "This network interface has " << addresses.size() << " IP addresses";
3316 for (const QNetworkAddressEntry &entry : addresses)
3317 checkAndAddIP(entry.ip(), configuredAddr);
3319 // Make sure there is at least one IP
3320 // At this point there was an explicit interface and an explicit address set
3321 // and the address should have been found
3322 if (IPs.isEmpty())
3324 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3325 .arg(ifaceAddr), Log::CRITICAL);
3326 IPs.append(ifaceAddr);
3329 return IPs;
3332 // Set the ports range in which is chosen the port
3333 // the BitTorrent session will listen to
3334 void SessionImpl::configureListeningInterface()
3336 m_listenInterfaceConfigured = false;
3337 configureDeferred();
3340 int SessionImpl::globalDownloadSpeedLimit() const
3342 // Unfortunately the value was saved as KiB instead of B.
3343 // But it is better to pass it around internally(+ webui) as Bytes.
3344 return m_globalDownloadSpeedLimit * 1024;
3347 void SessionImpl::setGlobalDownloadSpeedLimit(const int limit)
3349 // Unfortunately the value was saved as KiB instead of B.
3350 // But it is better to pass it around internally(+ webui) as Bytes.
3351 if (limit == globalDownloadSpeedLimit())
3352 return;
3354 if (limit <= 0)
3355 m_globalDownloadSpeedLimit = 0;
3356 else if (limit <= 1024)
3357 m_globalDownloadSpeedLimit = 1;
3358 else
3359 m_globalDownloadSpeedLimit = (limit / 1024);
3361 if (!isAltGlobalSpeedLimitEnabled())
3362 configureDeferred();
3365 int SessionImpl::globalUploadSpeedLimit() const
3367 // Unfortunately the value was saved as KiB instead of B.
3368 // But it is better to pass it around internally(+ webui) as Bytes.
3369 return m_globalUploadSpeedLimit * 1024;
3372 void SessionImpl::setGlobalUploadSpeedLimit(const int limit)
3374 // Unfortunately the value was saved as KiB instead of B.
3375 // But it is better to pass it around internally(+ webui) as Bytes.
3376 if (limit == globalUploadSpeedLimit())
3377 return;
3379 if (limit <= 0)
3380 m_globalUploadSpeedLimit = 0;
3381 else if (limit <= 1024)
3382 m_globalUploadSpeedLimit = 1;
3383 else
3384 m_globalUploadSpeedLimit = (limit / 1024);
3386 if (!isAltGlobalSpeedLimitEnabled())
3387 configureDeferred();
3390 int SessionImpl::altGlobalDownloadSpeedLimit() const
3392 // Unfortunately the value was saved as KiB instead of B.
3393 // But it is better to pass it around internally(+ webui) as Bytes.
3394 return m_altGlobalDownloadSpeedLimit * 1024;
3397 void SessionImpl::setAltGlobalDownloadSpeedLimit(const int limit)
3399 // Unfortunately the value was saved as KiB instead of B.
3400 // But it is better to pass it around internally(+ webui) as Bytes.
3401 if (limit == altGlobalDownloadSpeedLimit())
3402 return;
3404 if (limit <= 0)
3405 m_altGlobalDownloadSpeedLimit = 0;
3406 else if (limit <= 1024)
3407 m_altGlobalDownloadSpeedLimit = 1;
3408 else
3409 m_altGlobalDownloadSpeedLimit = (limit / 1024);
3411 if (isAltGlobalSpeedLimitEnabled())
3412 configureDeferred();
3415 int SessionImpl::altGlobalUploadSpeedLimit() const
3417 // Unfortunately the value was saved as KiB instead of B.
3418 // But it is better to pass it around internally(+ webui) as Bytes.
3419 return m_altGlobalUploadSpeedLimit * 1024;
3422 void SessionImpl::setAltGlobalUploadSpeedLimit(const int limit)
3424 // Unfortunately the value was saved as KiB instead of B.
3425 // But it is better to pass it around internally(+ webui) as Bytes.
3426 if (limit == altGlobalUploadSpeedLimit())
3427 return;
3429 if (limit <= 0)
3430 m_altGlobalUploadSpeedLimit = 0;
3431 else if (limit <= 1024)
3432 m_altGlobalUploadSpeedLimit = 1;
3433 else
3434 m_altGlobalUploadSpeedLimit = (limit / 1024);
3436 if (isAltGlobalSpeedLimitEnabled())
3437 configureDeferred();
3440 int SessionImpl::downloadSpeedLimit() const
3442 return isAltGlobalSpeedLimitEnabled()
3443 ? altGlobalDownloadSpeedLimit()
3444 : globalDownloadSpeedLimit();
3447 void SessionImpl::setDownloadSpeedLimit(const int limit)
3449 if (isAltGlobalSpeedLimitEnabled())
3450 setAltGlobalDownloadSpeedLimit(limit);
3451 else
3452 setGlobalDownloadSpeedLimit(limit);
3455 int SessionImpl::uploadSpeedLimit() const
3457 return isAltGlobalSpeedLimitEnabled()
3458 ? altGlobalUploadSpeedLimit()
3459 : globalUploadSpeedLimit();
3462 void SessionImpl::setUploadSpeedLimit(const int limit)
3464 if (isAltGlobalSpeedLimitEnabled())
3465 setAltGlobalUploadSpeedLimit(limit);
3466 else
3467 setGlobalUploadSpeedLimit(limit);
3470 bool SessionImpl::isAltGlobalSpeedLimitEnabled() const
3472 return m_isAltGlobalSpeedLimitEnabled;
3475 void SessionImpl::setAltGlobalSpeedLimitEnabled(const bool enabled)
3477 if (enabled == isAltGlobalSpeedLimitEnabled()) return;
3479 // Save new state to remember it on startup
3480 m_isAltGlobalSpeedLimitEnabled = enabled;
3481 applyBandwidthLimits();
3482 // Notify
3483 emit speedLimitModeChanged(m_isAltGlobalSpeedLimitEnabled);
3486 bool SessionImpl::isBandwidthSchedulerEnabled() const
3488 return m_isBandwidthSchedulerEnabled;
3491 void SessionImpl::setBandwidthSchedulerEnabled(const bool enabled)
3493 if (enabled != isBandwidthSchedulerEnabled())
3495 m_isBandwidthSchedulerEnabled = enabled;
3496 if (enabled)
3497 enableBandwidthScheduler();
3498 else
3499 delete m_bwScheduler;
3503 bool SessionImpl::isPerformanceWarningEnabled() const
3505 return m_isPerformanceWarningEnabled;
3508 void SessionImpl::setPerformanceWarningEnabled(const bool enable)
3510 if (enable == m_isPerformanceWarningEnabled)
3511 return;
3513 m_isPerformanceWarningEnabled = enable;
3514 configureDeferred();
3517 int SessionImpl::saveResumeDataInterval() const
3519 return m_saveResumeDataInterval;
3522 void SessionImpl::setSaveResumeDataInterval(const int value)
3524 if (value == m_saveResumeDataInterval)
3525 return;
3527 m_saveResumeDataInterval = value;
3529 if (value > 0)
3531 m_resumeDataTimer->setInterval(std::chrono::minutes(value));
3532 m_resumeDataTimer->start();
3534 else
3536 m_resumeDataTimer->stop();
3540 int SessionImpl::port() const
3542 return m_port;
3545 void SessionImpl::setPort(const int port)
3547 if (port != m_port)
3549 m_port = port;
3550 configureListeningInterface();
3552 if (isReannounceWhenAddressChangedEnabled())
3553 reannounceToAllTrackers();
3557 QString SessionImpl::networkInterface() const
3559 return m_networkInterface;
3562 void SessionImpl::setNetworkInterface(const QString &iface)
3564 if (iface != networkInterface())
3566 m_networkInterface = iface;
3567 configureListeningInterface();
3571 QString SessionImpl::networkInterfaceName() const
3573 return m_networkInterfaceName;
3576 void SessionImpl::setNetworkInterfaceName(const QString &name)
3578 m_networkInterfaceName = name;
3581 QString SessionImpl::networkInterfaceAddress() const
3583 return m_networkInterfaceAddress;
3586 void SessionImpl::setNetworkInterfaceAddress(const QString &address)
3588 if (address != networkInterfaceAddress())
3590 m_networkInterfaceAddress = address;
3591 configureListeningInterface();
3595 int SessionImpl::encryption() const
3597 return m_encryption;
3600 void SessionImpl::setEncryption(const int state)
3602 if (state != encryption())
3604 m_encryption = state;
3605 configureDeferred();
3606 LogMsg(tr("Encryption support: %1").arg(
3607 state == 0 ? tr("ON") : ((state == 1) ? tr("FORCED") : tr("OFF")))
3608 , Log::INFO);
3612 int SessionImpl::maxActiveCheckingTorrents() const
3614 return m_maxActiveCheckingTorrents;
3617 void SessionImpl::setMaxActiveCheckingTorrents(const int val)
3619 if (val == m_maxActiveCheckingTorrents)
3620 return;
3622 m_maxActiveCheckingTorrents = val;
3623 configureDeferred();
3626 bool SessionImpl::isI2PEnabled() const
3628 return m_isI2PEnabled;
3631 void SessionImpl::setI2PEnabled(const bool enabled)
3633 if (m_isI2PEnabled != enabled)
3635 m_isI2PEnabled = enabled;
3636 configureDeferred();
3640 QString SessionImpl::I2PAddress() const
3642 return m_I2PAddress;
3645 void SessionImpl::setI2PAddress(const QString &address)
3647 if (m_I2PAddress != address)
3649 m_I2PAddress = address;
3650 configureDeferred();
3654 int SessionImpl::I2PPort() const
3656 return m_I2PPort;
3659 void SessionImpl::setI2PPort(int port)
3661 if (m_I2PPort != port)
3663 m_I2PPort = port;
3664 configureDeferred();
3668 bool SessionImpl::I2PMixedMode() const
3670 return m_I2PMixedMode;
3673 void SessionImpl::setI2PMixedMode(const bool enabled)
3675 if (m_I2PMixedMode != enabled)
3677 m_I2PMixedMode = enabled;
3678 configureDeferred();
3682 int SessionImpl::I2PInboundQuantity() const
3684 return m_I2PInboundQuantity;
3687 void SessionImpl::setI2PInboundQuantity(const int value)
3689 if (value == m_I2PInboundQuantity)
3690 return;
3692 m_I2PInboundQuantity = value;
3693 configureDeferred();
3696 int SessionImpl::I2POutboundQuantity() const
3698 return m_I2POutboundQuantity;
3701 void SessionImpl::setI2POutboundQuantity(const int value)
3703 if (value == m_I2POutboundQuantity)
3704 return;
3706 m_I2POutboundQuantity = value;
3707 configureDeferred();
3710 int SessionImpl::I2PInboundLength() const
3712 return m_I2PInboundLength;
3715 void SessionImpl::setI2PInboundLength(const int value)
3717 if (value == m_I2PInboundLength)
3718 return;
3720 m_I2PInboundLength = value;
3721 configureDeferred();
3724 int SessionImpl::I2POutboundLength() const
3726 return m_I2POutboundLength;
3729 void SessionImpl::setI2POutboundLength(const int value)
3731 if (value == m_I2POutboundLength)
3732 return;
3734 m_I2POutboundLength = value;
3735 configureDeferred();
3738 bool SessionImpl::isProxyPeerConnectionsEnabled() const
3740 return m_isProxyPeerConnectionsEnabled;
3743 void SessionImpl::setProxyPeerConnectionsEnabled(const bool enabled)
3745 if (enabled != isProxyPeerConnectionsEnabled())
3747 m_isProxyPeerConnectionsEnabled = enabled;
3748 configureDeferred();
3752 ChokingAlgorithm SessionImpl::chokingAlgorithm() const
3754 return m_chokingAlgorithm;
3757 void SessionImpl::setChokingAlgorithm(const ChokingAlgorithm mode)
3759 if (mode == m_chokingAlgorithm) return;
3761 m_chokingAlgorithm = mode;
3762 configureDeferred();
3765 SeedChokingAlgorithm SessionImpl::seedChokingAlgorithm() const
3767 return m_seedChokingAlgorithm;
3770 void SessionImpl::setSeedChokingAlgorithm(const SeedChokingAlgorithm mode)
3772 if (mode == m_seedChokingAlgorithm) return;
3774 m_seedChokingAlgorithm = mode;
3775 configureDeferred();
3778 bool SessionImpl::isAddTrackersEnabled() const
3780 return m_isAddTrackersEnabled;
3783 void SessionImpl::setAddTrackersEnabled(const bool enabled)
3785 m_isAddTrackersEnabled = enabled;
3788 QString SessionImpl::additionalTrackers() const
3790 return m_additionalTrackers;
3793 void SessionImpl::setAdditionalTrackers(const QString &trackers)
3795 if (trackers != additionalTrackers())
3797 m_additionalTrackers = trackers;
3798 populateAdditionalTrackers();
3802 bool SessionImpl::isIPFilteringEnabled() const
3804 return m_isIPFilteringEnabled;
3807 void SessionImpl::setIPFilteringEnabled(const bool enabled)
3809 if (enabled != m_isIPFilteringEnabled)
3811 m_isIPFilteringEnabled = enabled;
3812 m_IPFilteringConfigured = false;
3813 configureDeferred();
3817 Path SessionImpl::IPFilterFile() const
3819 return m_IPFilterFile;
3822 void SessionImpl::setIPFilterFile(const Path &path)
3824 if (path != IPFilterFile())
3826 m_IPFilterFile = path;
3827 m_IPFilteringConfigured = false;
3828 configureDeferred();
3832 bool SessionImpl::isExcludedFileNamesEnabled() const
3834 return m_isExcludedFileNamesEnabled;
3837 void SessionImpl::setExcludedFileNamesEnabled(const bool enabled)
3839 if (m_isExcludedFileNamesEnabled == enabled)
3840 return;
3842 m_isExcludedFileNamesEnabled = enabled;
3844 if (enabled)
3845 populateExcludedFileNamesRegExpList();
3846 else
3847 m_excludedFileNamesRegExpList.clear();
3850 QStringList SessionImpl::excludedFileNames() const
3852 return m_excludedFileNames;
3855 void SessionImpl::setExcludedFileNames(const QStringList &excludedFileNames)
3857 if (excludedFileNames != m_excludedFileNames)
3859 m_excludedFileNames = excludedFileNames;
3860 populateExcludedFileNamesRegExpList();
3864 void SessionImpl::populateExcludedFileNamesRegExpList()
3866 const QStringList excludedNames = excludedFileNames();
3868 m_excludedFileNamesRegExpList.clear();
3869 m_excludedFileNamesRegExpList.reserve(excludedNames.size());
3871 for (const QString &str : excludedNames)
3873 const QString pattern = QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(str));
3874 const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
3875 m_excludedFileNamesRegExpList.append(re);
3879 bool SessionImpl::isFilenameExcluded(const QString &fileName) const
3881 if (!isExcludedFileNamesEnabled())
3882 return false;
3884 return std::any_of(m_excludedFileNamesRegExpList.begin(), m_excludedFileNamesRegExpList.end(), [&fileName](const QRegularExpression &re)
3886 return re.match(fileName).hasMatch();
3890 void SessionImpl::setBannedIPs(const QStringList &newList)
3892 if (newList == m_bannedIPs)
3893 return; // do nothing
3894 // here filter out incorrect IP
3895 QStringList filteredList;
3896 for (const QString &ip : newList)
3898 if (Utils::Net::isValidIP(ip))
3900 // the same IPv6 addresses could be written in different forms;
3901 // QHostAddress::toString() result format follows RFC5952;
3902 // thus we avoid duplicate entries pointing to the same address
3903 filteredList << QHostAddress(ip).toString();
3905 else
3907 LogMsg(tr("Rejected invalid IP address while applying the list of banned IP addresses. IP: \"%1\"")
3908 .arg(ip)
3909 , Log::WARNING);
3912 // now we have to sort IPs and make them unique
3913 filteredList.sort();
3914 filteredList.removeDuplicates();
3915 // Again ensure that the new list is different from the stored one.
3916 if (filteredList == m_bannedIPs)
3917 return; // do nothing
3918 // store to session settings
3919 // also here we have to recreate filter list including 3rd party ban file
3920 // and install it again into m_session
3921 m_bannedIPs = filteredList;
3922 m_IPFilteringConfigured = false;
3923 configureDeferred();
3926 ResumeDataStorageType SessionImpl::resumeDataStorageType() const
3928 return m_resumeDataStorageType;
3931 void SessionImpl::setResumeDataStorageType(const ResumeDataStorageType type)
3933 m_resumeDataStorageType = type;
3936 bool SessionImpl::isMergeTrackersEnabled() const
3938 return m_isMergeTrackersEnabled;
3941 void SessionImpl::setMergeTrackersEnabled(const bool enabled)
3943 m_isMergeTrackersEnabled = enabled;
3946 QStringList SessionImpl::bannedIPs() const
3948 return m_bannedIPs;
3951 bool SessionImpl::isRestored() const
3953 return m_isRestored;
3956 int SessionImpl::maxConnectionsPerTorrent() const
3958 return m_maxConnectionsPerTorrent;
3961 void SessionImpl::setMaxConnectionsPerTorrent(int max)
3963 max = (max > 0) ? max : -1;
3964 if (max != maxConnectionsPerTorrent())
3966 m_maxConnectionsPerTorrent = max;
3968 for (const TorrentImpl *torrent : asConst(m_torrents))
3972 torrent->nativeHandle().set_max_connections(max);
3974 catch (const std::exception &) {}
3979 int SessionImpl::maxUploadsPerTorrent() const
3981 return m_maxUploadsPerTorrent;
3984 void SessionImpl::setMaxUploadsPerTorrent(int max)
3986 max = (max > 0) ? max : -1;
3987 if (max != maxUploadsPerTorrent())
3989 m_maxUploadsPerTorrent = max;
3991 for (const TorrentImpl *torrent : asConst(m_torrents))
3995 torrent->nativeHandle().set_max_uploads(max);
3997 catch (const std::exception &) {}
4002 bool SessionImpl::announceToAllTrackers() const
4004 return m_announceToAllTrackers;
4007 void SessionImpl::setAnnounceToAllTrackers(const bool val)
4009 if (val != m_announceToAllTrackers)
4011 m_announceToAllTrackers = val;
4012 configureDeferred();
4016 bool SessionImpl::announceToAllTiers() const
4018 return m_announceToAllTiers;
4021 void SessionImpl::setAnnounceToAllTiers(const bool val)
4023 if (val != m_announceToAllTiers)
4025 m_announceToAllTiers = val;
4026 configureDeferred();
4030 int SessionImpl::peerTurnover() const
4032 return m_peerTurnover;
4035 void SessionImpl::setPeerTurnover(const int val)
4037 if (val == m_peerTurnover)
4038 return;
4040 m_peerTurnover = val;
4041 configureDeferred();
4044 int SessionImpl::peerTurnoverCutoff() const
4046 return m_peerTurnoverCutoff;
4049 void SessionImpl::setPeerTurnoverCutoff(const int val)
4051 if (val == m_peerTurnoverCutoff)
4052 return;
4054 m_peerTurnoverCutoff = val;
4055 configureDeferred();
4058 int SessionImpl::peerTurnoverInterval() const
4060 return m_peerTurnoverInterval;
4063 void SessionImpl::setPeerTurnoverInterval(const int val)
4065 if (val == m_peerTurnoverInterval)
4066 return;
4068 m_peerTurnoverInterval = val;
4069 configureDeferred();
4072 DiskIOType SessionImpl::diskIOType() const
4074 return m_diskIOType;
4077 void SessionImpl::setDiskIOType(const DiskIOType type)
4079 if (type != m_diskIOType)
4081 m_diskIOType = type;
4085 int SessionImpl::requestQueueSize() const
4087 return m_requestQueueSize;
4090 void SessionImpl::setRequestQueueSize(const int val)
4092 if (val == m_requestQueueSize)
4093 return;
4095 m_requestQueueSize = val;
4096 configureDeferred();
4099 int SessionImpl::asyncIOThreads() const
4101 return std::clamp(m_asyncIOThreads.get(), 1, 1024);
4104 void SessionImpl::setAsyncIOThreads(const int num)
4106 if (num == m_asyncIOThreads)
4107 return;
4109 m_asyncIOThreads = num;
4110 configureDeferred();
4113 int SessionImpl::hashingThreads() const
4115 return std::clamp(m_hashingThreads.get(), 1, 1024);
4118 void SessionImpl::setHashingThreads(const int num)
4120 if (num == m_hashingThreads)
4121 return;
4123 m_hashingThreads = num;
4124 configureDeferred();
4127 int SessionImpl::filePoolSize() const
4129 return m_filePoolSize;
4132 void SessionImpl::setFilePoolSize(const int size)
4134 if (size == m_filePoolSize)
4135 return;
4137 m_filePoolSize = size;
4138 configureDeferred();
4141 int SessionImpl::checkingMemUsage() const
4143 return std::max(1, m_checkingMemUsage.get());
4146 void SessionImpl::setCheckingMemUsage(int size)
4148 size = std::max(size, 1);
4150 if (size == m_checkingMemUsage)
4151 return;
4153 m_checkingMemUsage = size;
4154 configureDeferred();
4157 int SessionImpl::diskCacheSize() const
4159 #ifdef QBT_APP_64BIT
4160 return std::min(m_diskCacheSize.get(), 33554431); // 32768GiB
4161 #else
4162 // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
4163 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4164 return std::min(m_diskCacheSize.get(), 1536);
4165 #endif
4168 void SessionImpl::setDiskCacheSize(int size)
4170 #ifdef QBT_APP_64BIT
4171 size = std::min(size, 33554431); // 32768GiB
4172 #else
4173 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4174 size = std::min(size, 1536);
4175 #endif
4176 if (size != m_diskCacheSize)
4178 m_diskCacheSize = size;
4179 configureDeferred();
4183 int SessionImpl::diskCacheTTL() const
4185 return m_diskCacheTTL;
4188 void SessionImpl::setDiskCacheTTL(const int ttl)
4190 if (ttl != m_diskCacheTTL)
4192 m_diskCacheTTL = ttl;
4193 configureDeferred();
4197 qint64 SessionImpl::diskQueueSize() const
4199 return m_diskQueueSize;
4202 void SessionImpl::setDiskQueueSize(const qint64 size)
4204 if (size == m_diskQueueSize)
4205 return;
4207 m_diskQueueSize = size;
4208 configureDeferred();
4211 DiskIOReadMode SessionImpl::diskIOReadMode() const
4213 return m_diskIOReadMode;
4216 void SessionImpl::setDiskIOReadMode(const DiskIOReadMode mode)
4218 if (mode == m_diskIOReadMode)
4219 return;
4221 m_diskIOReadMode = mode;
4222 configureDeferred();
4225 DiskIOWriteMode SessionImpl::diskIOWriteMode() const
4227 return m_diskIOWriteMode;
4230 void SessionImpl::setDiskIOWriteMode(const DiskIOWriteMode mode)
4232 if (mode == m_diskIOWriteMode)
4233 return;
4235 m_diskIOWriteMode = mode;
4236 configureDeferred();
4239 bool SessionImpl::isCoalesceReadWriteEnabled() const
4241 return m_coalesceReadWriteEnabled;
4244 void SessionImpl::setCoalesceReadWriteEnabled(const bool enabled)
4246 if (enabled == m_coalesceReadWriteEnabled) return;
4248 m_coalesceReadWriteEnabled = enabled;
4249 configureDeferred();
4252 bool SessionImpl::isSuggestModeEnabled() const
4254 return m_isSuggestMode;
4257 bool SessionImpl::usePieceExtentAffinity() const
4259 return m_usePieceExtentAffinity;
4262 void SessionImpl::setPieceExtentAffinity(const bool enabled)
4264 if (enabled == m_usePieceExtentAffinity) return;
4266 m_usePieceExtentAffinity = enabled;
4267 configureDeferred();
4270 void SessionImpl::setSuggestMode(const bool mode)
4272 if (mode == m_isSuggestMode) return;
4274 m_isSuggestMode = mode;
4275 configureDeferred();
4278 int SessionImpl::sendBufferWatermark() const
4280 return m_sendBufferWatermark;
4283 void SessionImpl::setSendBufferWatermark(const int value)
4285 if (value == m_sendBufferWatermark) return;
4287 m_sendBufferWatermark = value;
4288 configureDeferred();
4291 int SessionImpl::sendBufferLowWatermark() const
4293 return m_sendBufferLowWatermark;
4296 void SessionImpl::setSendBufferLowWatermark(const int value)
4298 if (value == m_sendBufferLowWatermark) return;
4300 m_sendBufferLowWatermark = value;
4301 configureDeferred();
4304 int SessionImpl::sendBufferWatermarkFactor() const
4306 return m_sendBufferWatermarkFactor;
4309 void SessionImpl::setSendBufferWatermarkFactor(const int value)
4311 if (value == m_sendBufferWatermarkFactor) return;
4313 m_sendBufferWatermarkFactor = value;
4314 configureDeferred();
4317 int SessionImpl::connectionSpeed() const
4319 return m_connectionSpeed;
4322 void SessionImpl::setConnectionSpeed(const int value)
4324 if (value == m_connectionSpeed) return;
4326 m_connectionSpeed = value;
4327 configureDeferred();
4330 int SessionImpl::socketSendBufferSize() const
4332 return m_socketSendBufferSize;
4335 void SessionImpl::setSocketSendBufferSize(const int value)
4337 if (value == m_socketSendBufferSize)
4338 return;
4340 m_socketSendBufferSize = value;
4341 configureDeferred();
4344 int SessionImpl::socketReceiveBufferSize() const
4346 return m_socketReceiveBufferSize;
4349 void SessionImpl::setSocketReceiveBufferSize(const int value)
4351 if (value == m_socketReceiveBufferSize)
4352 return;
4354 m_socketReceiveBufferSize = value;
4355 configureDeferred();
4358 int SessionImpl::socketBacklogSize() const
4360 return m_socketBacklogSize;
4363 void SessionImpl::setSocketBacklogSize(const int value)
4365 if (value == m_socketBacklogSize) return;
4367 m_socketBacklogSize = value;
4368 configureDeferred();
4371 bool SessionImpl::isAnonymousModeEnabled() const
4373 return m_isAnonymousModeEnabled;
4376 void SessionImpl::setAnonymousModeEnabled(const bool enabled)
4378 if (enabled != m_isAnonymousModeEnabled)
4380 m_isAnonymousModeEnabled = enabled;
4381 configureDeferred();
4382 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
4383 , Log::INFO);
4387 bool SessionImpl::isQueueingSystemEnabled() const
4389 return m_isQueueingEnabled;
4392 void SessionImpl::setQueueingSystemEnabled(const bool enabled)
4394 if (enabled != m_isQueueingEnabled)
4396 m_isQueueingEnabled = enabled;
4397 configureDeferred();
4399 if (enabled)
4400 m_torrentsQueueChanged = true;
4401 else
4402 removeTorrentsQueue();
4406 int SessionImpl::maxActiveDownloads() const
4408 return m_maxActiveDownloads;
4411 void SessionImpl::setMaxActiveDownloads(int max)
4413 max = std::max(max, -1);
4414 if (max != m_maxActiveDownloads)
4416 m_maxActiveDownloads = max;
4417 configureDeferred();
4421 int SessionImpl::maxActiveUploads() const
4423 return m_maxActiveUploads;
4426 void SessionImpl::setMaxActiveUploads(int max)
4428 max = std::max(max, -1);
4429 if (max != m_maxActiveUploads)
4431 m_maxActiveUploads = max;
4432 configureDeferred();
4436 int SessionImpl::maxActiveTorrents() const
4438 return m_maxActiveTorrents;
4441 void SessionImpl::setMaxActiveTorrents(int max)
4443 max = std::max(max, -1);
4444 if (max != m_maxActiveTorrents)
4446 m_maxActiveTorrents = max;
4447 configureDeferred();
4451 bool SessionImpl::ignoreSlowTorrentsForQueueing() const
4453 return m_ignoreSlowTorrentsForQueueing;
4456 void SessionImpl::setIgnoreSlowTorrentsForQueueing(const bool ignore)
4458 if (ignore != m_ignoreSlowTorrentsForQueueing)
4460 m_ignoreSlowTorrentsForQueueing = ignore;
4461 configureDeferred();
4465 int SessionImpl::downloadRateForSlowTorrents() const
4467 return m_downloadRateForSlowTorrents;
4470 void SessionImpl::setDownloadRateForSlowTorrents(const int rateInKibiBytes)
4472 if (rateInKibiBytes == m_downloadRateForSlowTorrents)
4473 return;
4475 m_downloadRateForSlowTorrents = rateInKibiBytes;
4476 configureDeferred();
4479 int SessionImpl::uploadRateForSlowTorrents() const
4481 return m_uploadRateForSlowTorrents;
4484 void SessionImpl::setUploadRateForSlowTorrents(const int rateInKibiBytes)
4486 if (rateInKibiBytes == m_uploadRateForSlowTorrents)
4487 return;
4489 m_uploadRateForSlowTorrents = rateInKibiBytes;
4490 configureDeferred();
4493 int SessionImpl::slowTorrentsInactivityTimer() const
4495 return m_slowTorrentsInactivityTimer;
4498 void SessionImpl::setSlowTorrentsInactivityTimer(const int timeInSeconds)
4500 if (timeInSeconds == m_slowTorrentsInactivityTimer)
4501 return;
4503 m_slowTorrentsInactivityTimer = timeInSeconds;
4504 configureDeferred();
4507 int SessionImpl::outgoingPortsMin() const
4509 return m_outgoingPortsMin;
4512 void SessionImpl::setOutgoingPortsMin(const int min)
4514 if (min != m_outgoingPortsMin)
4516 m_outgoingPortsMin = min;
4517 configureDeferred();
4521 int SessionImpl::outgoingPortsMax() const
4523 return m_outgoingPortsMax;
4526 void SessionImpl::setOutgoingPortsMax(const int max)
4528 if (max != m_outgoingPortsMax)
4530 m_outgoingPortsMax = max;
4531 configureDeferred();
4535 int SessionImpl::UPnPLeaseDuration() const
4537 return m_UPnPLeaseDuration;
4540 void SessionImpl::setUPnPLeaseDuration(const int duration)
4542 if (duration != m_UPnPLeaseDuration)
4544 m_UPnPLeaseDuration = duration;
4545 configureDeferred();
4549 int SessionImpl::peerToS() const
4551 return m_peerToS;
4554 void SessionImpl::setPeerToS(const int value)
4556 if (value == m_peerToS)
4557 return;
4559 m_peerToS = value;
4560 configureDeferred();
4563 bool SessionImpl::ignoreLimitsOnLAN() const
4565 return m_ignoreLimitsOnLAN;
4568 void SessionImpl::setIgnoreLimitsOnLAN(const bool ignore)
4570 if (ignore != m_ignoreLimitsOnLAN)
4572 m_ignoreLimitsOnLAN = ignore;
4573 configureDeferred();
4577 bool SessionImpl::includeOverheadInLimits() const
4579 return m_includeOverheadInLimits;
4582 void SessionImpl::setIncludeOverheadInLimits(const bool include)
4584 if (include != m_includeOverheadInLimits)
4586 m_includeOverheadInLimits = include;
4587 configureDeferred();
4591 QString SessionImpl::announceIP() const
4593 return m_announceIP;
4596 void SessionImpl::setAnnounceIP(const QString &ip)
4598 if (ip != m_announceIP)
4600 m_announceIP = ip;
4601 configureDeferred();
4605 int SessionImpl::maxConcurrentHTTPAnnounces() const
4607 return m_maxConcurrentHTTPAnnounces;
4610 void SessionImpl::setMaxConcurrentHTTPAnnounces(const int value)
4612 if (value == m_maxConcurrentHTTPAnnounces)
4613 return;
4615 m_maxConcurrentHTTPAnnounces = value;
4616 configureDeferred();
4619 bool SessionImpl::isReannounceWhenAddressChangedEnabled() const
4621 return m_isReannounceWhenAddressChangedEnabled;
4624 void SessionImpl::setReannounceWhenAddressChangedEnabled(const bool enabled)
4626 if (enabled == m_isReannounceWhenAddressChangedEnabled)
4627 return;
4629 m_isReannounceWhenAddressChangedEnabled = enabled;
4632 void SessionImpl::reannounceToAllTrackers() const
4634 for (const TorrentImpl *torrent : asConst(m_torrents))
4638 torrent->nativeHandle().force_reannounce(0, -1, lt::torrent_handle::ignore_min_interval);
4640 catch (const std::exception &) {}
4644 int SessionImpl::stopTrackerTimeout() const
4646 return m_stopTrackerTimeout;
4649 void SessionImpl::setStopTrackerTimeout(const int value)
4651 if (value == m_stopTrackerTimeout)
4652 return;
4654 m_stopTrackerTimeout = value;
4655 configureDeferred();
4658 int SessionImpl::maxConnections() const
4660 return m_maxConnections;
4663 void SessionImpl::setMaxConnections(int max)
4665 max = (max > 0) ? max : -1;
4666 if (max != m_maxConnections)
4668 m_maxConnections = max;
4669 configureDeferred();
4673 int SessionImpl::maxUploads() const
4675 return m_maxUploads;
4678 void SessionImpl::setMaxUploads(int max)
4680 max = (max > 0) ? max : -1;
4681 if (max != m_maxUploads)
4683 m_maxUploads = max;
4684 configureDeferred();
4688 BTProtocol SessionImpl::btProtocol() const
4690 return m_btProtocol;
4693 void SessionImpl::setBTProtocol(const BTProtocol protocol)
4695 if ((protocol < BTProtocol::Both) || (BTProtocol::UTP < protocol))
4696 return;
4698 if (protocol == m_btProtocol) return;
4700 m_btProtocol = protocol;
4701 configureDeferred();
4704 bool SessionImpl::isUTPRateLimited() const
4706 return m_isUTPRateLimited;
4709 void SessionImpl::setUTPRateLimited(const bool limited)
4711 if (limited != m_isUTPRateLimited)
4713 m_isUTPRateLimited = limited;
4714 configureDeferred();
4718 MixedModeAlgorithm SessionImpl::utpMixedMode() const
4720 return m_utpMixedMode;
4723 void SessionImpl::setUtpMixedMode(const MixedModeAlgorithm mode)
4725 if (mode == m_utpMixedMode) return;
4727 m_utpMixedMode = mode;
4728 configureDeferred();
4731 bool SessionImpl::isIDNSupportEnabled() const
4733 return m_IDNSupportEnabled;
4736 void SessionImpl::setIDNSupportEnabled(const bool enabled)
4738 if (enabled == m_IDNSupportEnabled) return;
4740 m_IDNSupportEnabled = enabled;
4741 configureDeferred();
4744 bool SessionImpl::multiConnectionsPerIpEnabled() const
4746 return m_multiConnectionsPerIpEnabled;
4749 void SessionImpl::setMultiConnectionsPerIpEnabled(const bool enabled)
4751 if (enabled == m_multiConnectionsPerIpEnabled) return;
4753 m_multiConnectionsPerIpEnabled = enabled;
4754 configureDeferred();
4757 bool SessionImpl::validateHTTPSTrackerCertificate() const
4759 return m_validateHTTPSTrackerCertificate;
4762 void SessionImpl::setValidateHTTPSTrackerCertificate(const bool enabled)
4764 if (enabled == m_validateHTTPSTrackerCertificate) return;
4766 m_validateHTTPSTrackerCertificate = enabled;
4767 configureDeferred();
4770 bool SessionImpl::isSSRFMitigationEnabled() const
4772 return m_SSRFMitigationEnabled;
4775 void SessionImpl::setSSRFMitigationEnabled(const bool enabled)
4777 if (enabled == m_SSRFMitigationEnabled) return;
4779 m_SSRFMitigationEnabled = enabled;
4780 configureDeferred();
4783 bool SessionImpl::blockPeersOnPrivilegedPorts() const
4785 return m_blockPeersOnPrivilegedPorts;
4788 void SessionImpl::setBlockPeersOnPrivilegedPorts(const bool enabled)
4790 if (enabled == m_blockPeersOnPrivilegedPorts) return;
4792 m_blockPeersOnPrivilegedPorts = enabled;
4793 configureDeferred();
4796 bool SessionImpl::isTrackerFilteringEnabled() const
4798 return m_isTrackerFilteringEnabled;
4801 void SessionImpl::setTrackerFilteringEnabled(const bool enabled)
4803 if (enabled != m_isTrackerFilteringEnabled)
4805 m_isTrackerFilteringEnabled = enabled;
4806 configureDeferred();
4810 bool SessionImpl::isListening() const
4812 return m_nativeSessionExtension->isSessionListening();
4815 MaxRatioAction SessionImpl::maxRatioAction() const
4817 return static_cast<MaxRatioAction>(m_maxRatioAction.get());
4820 void SessionImpl::setMaxRatioAction(const MaxRatioAction act)
4822 m_maxRatioAction = static_cast<int>(act);
4825 bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const
4827 const bool isHybrid = infoHash.isHybrid();
4828 const auto id = TorrentID::fromInfoHash(infoHash);
4829 // alternative ID can be useful to find existing torrent
4830 // in case if hybrid torrent was added by v1 info hash
4831 const auto altID = (isHybrid ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
4833 if (m_loadingTorrents.contains(id) || (isHybrid && m_loadingTorrents.contains(altID)))
4834 return true;
4835 if (m_downloadedMetadata.contains(id) || (isHybrid && m_downloadedMetadata.contains(altID)))
4836 return true;
4837 return findTorrent(infoHash);
4840 void SessionImpl::updateSeedingLimitTimer()
4842 if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
4843 && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit()
4844 && (globalMaxInactiveSeedingMinutes() == Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT) && !hasPerTorrentInactiveSeedingTimeLimit())
4846 if (m_seedingLimitTimer->isActive())
4847 m_seedingLimitTimer->stop();
4849 else if (!m_seedingLimitTimer->isActive())
4851 m_seedingLimitTimer->start();
4855 void SessionImpl::handleTorrentShareLimitChanged(TorrentImpl *const)
4857 updateSeedingLimitTimer();
4860 void SessionImpl::handleTorrentNameChanged(TorrentImpl *const)
4864 void SessionImpl::handleTorrentSavePathChanged(TorrentImpl *const torrent)
4866 emit torrentSavePathChanged(torrent);
4869 void SessionImpl::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
4871 emit torrentCategoryChanged(torrent, oldCategory);
4874 void SessionImpl::handleTorrentTagAdded(TorrentImpl *const torrent, const Tag &tag)
4876 emit torrentTagAdded(torrent, tag);
4879 void SessionImpl::handleTorrentTagRemoved(TorrentImpl *const torrent, const Tag &tag)
4881 emit torrentTagRemoved(torrent, tag);
4884 void SessionImpl::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
4886 emit torrentSavingModeChanged(torrent);
4889 void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
4891 for (const TrackerEntry &newTracker : newTrackers)
4892 LogMsg(tr("Added tracker to torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), newTracker.url));
4893 emit trackersAdded(torrent, newTrackers);
4896 void SessionImpl::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers)
4898 for (const QString &deletedTracker : deletedTrackers)
4899 LogMsg(tr("Removed tracker from torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), deletedTracker));
4900 emit trackersRemoved(torrent, deletedTrackers);
4903 void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
4905 emit trackersChanged(torrent);
4908 void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
4910 for (const QUrl &newUrlSeed : newUrlSeeds)
4911 LogMsg(tr("Added URL seed to torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), newUrlSeed.toString()));
4914 void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
4916 for (const QUrl &urlSeed : urlSeeds)
4917 LogMsg(tr("Removed URL seed from torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), urlSeed.toString()));
4920 void SessionImpl::handleTorrentMetadataReceived(TorrentImpl *const torrent)
4922 if (!torrentExportDirectory().isEmpty())
4923 exportTorrentFile(torrent, torrentExportDirectory());
4925 emit torrentMetadataReceived(torrent);
4928 void SessionImpl::handleTorrentPaused(TorrentImpl *const torrent)
4930 torrent->resetTrackerEntries();
4932 const auto &trackerEntries = torrent->trackers();
4933 QHash<QString, TrackerEntry> updatedTrackerEntries;
4934 updatedTrackerEntries.reserve(trackerEntries.size());
4935 for (const auto &trackerEntry : trackerEntries)
4936 updatedTrackerEntries.emplace(trackerEntry.url, trackerEntry);
4937 emit trackerEntriesUpdated(torrent, updatedTrackerEntries);
4939 LogMsg(tr("Torrent paused. Torrent: \"%1\"").arg(torrent->name()));
4940 emit torrentPaused(torrent);
4943 void SessionImpl::handleTorrentResumed(TorrentImpl *const torrent)
4945 LogMsg(tr("Torrent resumed. Torrent: \"%1\"").arg(torrent->name()));
4946 emit torrentResumed(torrent);
4949 void SessionImpl::handleTorrentChecked(TorrentImpl *const torrent)
4951 emit torrentFinishedChecking(torrent);
4954 void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
4956 LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
4957 emit torrentFinished(torrent);
4959 if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
4960 exportTorrentFile(torrent, exportPath);
4962 const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
4964 return !(torrent->isFinished() || torrent->isPaused() || torrent->isErrored());
4966 if (!hasUnfinishedTorrents)
4967 emit allTorrentsFinished();
4970 void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
4972 --m_numResumeData;
4974 m_resumeDataStorage->store(torrent->id(), data);
4975 const auto iter = m_changedTorrentIDs.find(torrent->id());
4976 if (iter != m_changedTorrentIDs.end())
4978 m_resumeDataStorage->remove(iter.value());
4979 m_changedTorrentIDs.erase(iter);
4983 void SessionImpl::handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash)
4985 Q_ASSERT(torrent->infoHash().isHybrid());
4987 m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(torrent->infoHash().v1()), torrent);
4989 const auto prevID = TorrentID::fromInfoHash(prevInfoHash);
4990 const TorrentID currentID = torrent->id();
4991 if (currentID != prevID)
4993 m_torrents[torrent->id()] = m_torrents.take(prevID);
4994 m_changedTorrentIDs[torrent->id()] = prevID;
4998 void SessionImpl::handleTorrentStorageMovingStateChanged(TorrentImpl *torrent)
5000 emit torrentsUpdated({torrent});
5003 bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode, const MoveStorageContext context)
5005 Q_ASSERT(torrent);
5007 const lt::torrent_handle torrentHandle = torrent->nativeHandle();
5008 const Path currentLocation = torrent->actualStorageLocation();
5009 const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle);
5011 if (m_moveStorageQueue.size() > 1)
5013 auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
5014 , [&torrentHandle](const MoveStorageJob &job)
5016 return job.torrentHandle == torrentHandle;
5019 if (iter != m_moveStorageQueue.end())
5021 // remove existing inactive job
5022 torrent->handleMoveStorageJobFinished(currentLocation, iter->context, torrentHasActiveJob);
5023 LogMsg(tr("Torrent move canceled. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), iter->path.toString()));
5024 m_moveStorageQueue.erase(iter);
5028 if (torrentHasActiveJob)
5030 // if there is active job for this torrent prevent creating meaningless
5031 // job that will move torrent to the same location as current one
5032 if (m_moveStorageQueue.first().path == newPath)
5034 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: torrent is currently moving to the destination")
5035 .arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5036 return false;
5039 else
5041 if (currentLocation == newPath)
5043 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\" Destination: \"%3\". Reason: both paths point to the same location")
5044 .arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5045 return false;
5049 const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode, context};
5050 m_moveStorageQueue << moveStorageJob;
5051 LogMsg(tr("Enqueued torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5053 if (m_moveStorageQueue.size() == 1)
5054 moveTorrentStorage(moveStorageJob);
5056 return true;
5059 void SessionImpl::moveTorrentStorage(const MoveStorageJob &job) const
5061 #ifdef QBT_USES_LIBTORRENT2
5062 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
5063 #else
5064 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
5065 #endif
5066 const TorrentImpl *torrent = m_torrents.value(id);
5067 const QString torrentName = (torrent ? torrent->name() : id.toString());
5068 LogMsg(tr("Start moving torrent. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, job.path.toString()));
5070 job.torrentHandle.move_storage(job.path.toString().toStdString(), toNative(job.mode));
5073 void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
5075 const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
5076 if (!m_moveStorageQueue.isEmpty())
5077 moveTorrentStorage(m_moveStorageQueue.first());
5079 const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
5080 , [&finishedJob](const MoveStorageJob &job)
5082 return job.torrentHandle == finishedJob.torrentHandle;
5085 const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
5087 TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
5088 if (torrent)
5090 torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
5092 else if (!torrentHasOutstandingJob)
5094 // Last job is completed for torrent that being removing, so actually remove it
5095 const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
5096 const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
5097 if (removingTorrentData.deleteOption == DeleteTorrent)
5098 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
5102 void SessionImpl::storeCategories() const
5104 QJsonObject jsonObj;
5105 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
5107 const QString &categoryName = it.key();
5108 const CategoryOptions &categoryOptions = it.value();
5109 jsonObj[categoryName] = categoryOptions.toJSON();
5112 const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
5113 const QByteArray data = QJsonDocument(jsonObj).toJson();
5114 const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
5115 if (!result)
5117 LogMsg(tr("Failed to save Categories configuration. File: \"%1\". Error: \"%2\"")
5118 .arg(path.toString(), result.error()), Log::WARNING);
5122 void SessionImpl::upgradeCategories()
5124 const auto legacyCategories = SettingValue<QVariantMap>(u"BitTorrent/Session/Categories"_s).get();
5125 for (auto it = legacyCategories.cbegin(); it != legacyCategories.cend(); ++it)
5127 const QString &categoryName = it.key();
5128 CategoryOptions categoryOptions;
5129 categoryOptions.savePath = Path(it.value().toString());
5130 m_categories[categoryName] = categoryOptions;
5133 storeCategories();
5136 void SessionImpl::loadCategories()
5138 m_categories.clear();
5140 const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
5141 if (!path.exists())
5143 // TODO: Remove the following upgrade code in v4.5
5144 // == BEGIN UPGRADE CODE ==
5145 upgradeCategories();
5146 m_needUpgradeDownloadPath = true;
5147 // == END UPGRADE CODE ==
5149 // return;
5152 const int fileMaxSize = 1024 * 1024;
5153 const auto readResult = Utils::IO::readFile(path, fileMaxSize);
5154 if (!readResult)
5156 LogMsg(tr("Failed to load Categories. %1").arg(readResult.error().message), Log::WARNING);
5157 return;
5160 QJsonParseError jsonError;
5161 const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
5162 if (jsonError.error != QJsonParseError::NoError)
5164 LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
5165 .arg(path.toString(), jsonError.errorString()), Log::WARNING);
5166 return;
5169 if (!jsonDoc.isObject())
5171 LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Error: \"Invalid data format\"")
5172 .arg(path.toString()), Log::WARNING);
5173 return;
5176 const QJsonObject jsonObj = jsonDoc.object();
5177 for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
5179 const QString &categoryName = it.key();
5180 const auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
5181 m_categories[categoryName] = categoryOptions;
5185 bool SessionImpl::hasPerTorrentRatioLimit() const
5187 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5189 return (torrent->ratioLimit() >= 0);
5193 bool SessionImpl::hasPerTorrentSeedingTimeLimit() const
5195 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5197 return (torrent->seedingTimeLimit() >= 0);
5201 bool SessionImpl::hasPerTorrentInactiveSeedingTimeLimit() const
5203 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5205 return (torrent->inactiveSeedingTimeLimit() >= 0);
5209 void SessionImpl::configureDeferred()
5211 if (m_deferredConfigureScheduled)
5212 return;
5214 m_deferredConfigureScheduled = true;
5215 QMetaObject::invokeMethod(this, qOverload<>(&SessionImpl::configure), Qt::QueuedConnection);
5218 // Enable IP Filtering
5219 // this method creates ban list from scratch combining user ban list and 3rd party ban list file
5220 void SessionImpl::enableIPFilter()
5222 qDebug("Enabling IPFilter");
5223 // 1. Parse the IP filter
5224 // 2. In the slot add the manually banned IPs to the provided lt::ip_filter
5225 // 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
5226 // set between clearing the old one and setting the new one.
5227 if (!m_filterParser)
5229 m_filterParser = new FilterParserThread(this);
5230 connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &SessionImpl::handleIPFilterParsed);
5231 connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &SessionImpl::handleIPFilterError);
5233 m_filterParser->processFilterFile(IPFilterFile());
5236 // Disable IP Filtering
5237 void SessionImpl::disableIPFilter()
5239 qDebug("Disabling IPFilter");
5240 if (m_filterParser)
5242 disconnect(m_filterParser.data(), nullptr, this, nullptr);
5243 delete m_filterParser;
5246 // Add the banned IPs after the IPFilter disabling
5247 // which creates an empty filter and overrides all previously
5248 // applied bans.
5249 lt::ip_filter filter;
5250 processBannedIPs(filter);
5251 m_nativeSession->set_ip_filter(filter);
5254 const SessionStatus &SessionImpl::status() const
5256 return m_status;
5259 const CacheStatus &SessionImpl::cacheStatus() const
5261 return m_cacheStatus;
5264 void SessionImpl::enqueueRefresh()
5266 Q_ASSERT(!m_refreshEnqueued);
5268 QTimer::singleShot(refreshInterval(), Qt::CoarseTimer, this, [this]
5270 m_nativeSession->post_torrent_updates();
5271 m_nativeSession->post_session_stats();
5273 if (m_torrentsQueueChanged)
5275 m_torrentsQueueChanged = false;
5276 m_needSaveTorrentsQueue = true;
5280 m_refreshEnqueued = true;
5283 void SessionImpl::handleIPFilterParsed(const int ruleCount)
5285 if (m_filterParser)
5287 lt::ip_filter filter = m_filterParser->IPfilter();
5288 processBannedIPs(filter);
5289 m_nativeSession->set_ip_filter(filter);
5291 LogMsg(tr("Successfully parsed the IP filter file. Number of rules applied: %1").arg(ruleCount));
5292 emit IPFilterParsed(false, ruleCount);
5295 void SessionImpl::handleIPFilterError()
5297 lt::ip_filter filter;
5298 processBannedIPs(filter);
5299 m_nativeSession->set_ip_filter(filter);
5301 LogMsg(tr("Failed to parse the IP filter file"), Log::WARNING);
5302 emit IPFilterParsed(true, 0);
5305 std::vector<lt::alert *> SessionImpl::getPendingAlerts(const lt::time_duration time) const
5307 if (time > lt::time_duration::zero())
5308 m_nativeSession->wait_for_alert(time);
5310 std::vector<lt::alert *> alerts;
5311 m_nativeSession->pop_alerts(&alerts);
5312 return alerts;
5315 TorrentContentLayout SessionImpl::torrentContentLayout() const
5317 return m_torrentContentLayout;
5320 void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value)
5322 m_torrentContentLayout = value;
5325 // Read alerts sent by the BitTorrent session
5326 void SessionImpl::readAlerts()
5328 const std::vector<lt::alert *> alerts = getPendingAlerts();
5329 handleAddTorrentAlerts(alerts);
5330 for (const lt::alert *a : alerts)
5331 handleAlert(a);
5333 processTrackerStatuses();
5336 void SessionImpl::handleAddTorrentAlerts(const std::vector<lt::alert *> &alerts)
5338 QVector<Torrent *> loadedTorrents;
5339 if (!isRestored())
5340 loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
5342 qsizetype alertsCount = 0;
5343 for (const lt::alert *a : alerts)
5345 if (a->type() != lt::add_torrent_alert::alert_type)
5346 continue;
5348 ++alertsCount;
5350 const auto *alert = static_cast<const lt::add_torrent_alert *>(a);
5351 if (alert->error)
5353 const QString msg = QString::fromStdString(alert->message());
5354 LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(msg), Log::WARNING);
5355 emit loadTorrentFailed(msg);
5357 const lt::add_torrent_params &params = alert->params;
5358 const bool hasMetadata = (params.ti && params.ti->is_valid());
5360 #ifdef QBT_USES_LIBTORRENT2
5361 const InfoHash infoHash {(hasMetadata ? params.ti->info_hashes() : params.info_hashes)};
5362 if (infoHash.isHybrid())
5363 m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
5364 #else
5365 const InfoHash infoHash {(hasMetadata ? params.ti->info_hash() : params.info_hash)};
5366 #endif
5367 if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
5368 ; loadingTorrentsIter != m_loadingTorrents.end())
5370 emit addTorrentFailed(infoHash, msg);
5371 m_loadingTorrents.erase(loadingTorrentsIter);
5373 else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
5374 ; downloadedMetadataIter != m_downloadedMetadata.end())
5376 m_downloadedMetadata.erase(downloadedMetadataIter);
5377 if (infoHash.isHybrid())
5379 // index hybrid magnet links by both v1 and v2 info hashes
5380 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5381 m_downloadedMetadata.remove(altID);
5385 continue;
5388 #ifdef QBT_USES_LIBTORRENT2
5389 const InfoHash infoHash {alert->handle.info_hashes()};
5390 #else
5391 const InfoHash infoHash {alert->handle.info_hash()};
5392 #endif
5393 const auto torrentID = TorrentID::fromInfoHash(infoHash);
5395 if (const auto loadingTorrentsIter = m_loadingTorrents.find(torrentID)
5396 ; loadingTorrentsIter != m_loadingTorrents.end())
5398 const LoadTorrentParams params = loadingTorrentsIter.value();
5399 m_loadingTorrents.erase(loadingTorrentsIter);
5401 Torrent *torrent = createTorrent(alert->handle, params);
5402 loadedTorrents.append(torrent);
5404 else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(torrentID)
5405 ; downloadedMetadataIter != m_downloadedMetadata.end())
5407 downloadedMetadataIter.value() = alert->handle;
5408 if (infoHash.isHybrid())
5410 // index hybrid magnet links by both v1 and v2 info hashes
5411 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5412 m_downloadedMetadata[altID] = alert->handle;
5417 if (alertsCount > 0)
5419 emit addTorrentAlertsReceived(alertsCount);
5421 if (!loadedTorrents.isEmpty())
5423 if (isRestored())
5424 m_torrentsQueueChanged = true;
5425 emit torrentsLoaded(loadedTorrents);
5430 void SessionImpl::handleAlert(const lt::alert *a)
5434 switch (a->type())
5436 #ifdef QBT_USES_LIBTORRENT2
5437 case lt::file_prio_alert::alert_type:
5438 #endif
5439 case lt::file_renamed_alert::alert_type:
5440 case lt::file_rename_failed_alert::alert_type:
5441 case lt::file_completed_alert::alert_type:
5442 case lt::torrent_finished_alert::alert_type:
5443 case lt::save_resume_data_alert::alert_type:
5444 case lt::save_resume_data_failed_alert::alert_type:
5445 case lt::torrent_paused_alert::alert_type:
5446 case lt::torrent_resumed_alert::alert_type:
5447 case lt::fastresume_rejected_alert::alert_type:
5448 case lt::torrent_checked_alert::alert_type:
5449 case lt::metadata_received_alert::alert_type:
5450 case lt::performance_alert::alert_type:
5451 dispatchTorrentAlert(static_cast<const lt::torrent_alert *>(a));
5452 break;
5453 case lt::state_update_alert::alert_type:
5454 handleStateUpdateAlert(static_cast<const lt::state_update_alert *>(a));
5455 break;
5456 case lt::session_error_alert::alert_type:
5457 handleSessionErrorAlert(static_cast<const lt::session_error_alert *>(a));
5458 break;
5459 case lt::session_stats_alert::alert_type:
5460 handleSessionStatsAlert(static_cast<const lt::session_stats_alert *>(a));
5461 break;
5462 case lt::tracker_announce_alert::alert_type:
5463 case lt::tracker_error_alert::alert_type:
5464 case lt::tracker_reply_alert::alert_type:
5465 case lt::tracker_warning_alert::alert_type:
5466 handleTrackerAlert(static_cast<const lt::tracker_alert *>(a));
5467 break;
5468 case lt::file_error_alert::alert_type:
5469 handleFileErrorAlert(static_cast<const lt::file_error_alert *>(a));
5470 break;
5471 case lt::add_torrent_alert::alert_type:
5472 // handled separately
5473 break;
5474 case lt::torrent_removed_alert::alert_type:
5475 handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert *>(a));
5476 break;
5477 case lt::torrent_deleted_alert::alert_type:
5478 handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert *>(a));
5479 break;
5480 case lt::torrent_delete_failed_alert::alert_type:
5481 handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert *>(a));
5482 break;
5483 case lt::portmap_error_alert::alert_type:
5484 handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert *>(a));
5485 break;
5486 case lt::portmap_alert::alert_type:
5487 handlePortmapAlert(static_cast<const lt::portmap_alert *>(a));
5488 break;
5489 case lt::peer_blocked_alert::alert_type:
5490 handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert *>(a));
5491 break;
5492 case lt::peer_ban_alert::alert_type:
5493 handlePeerBanAlert(static_cast<const lt::peer_ban_alert *>(a));
5494 break;
5495 case lt::url_seed_alert::alert_type:
5496 handleUrlSeedAlert(static_cast<const lt::url_seed_alert *>(a));
5497 break;
5498 case lt::listen_succeeded_alert::alert_type:
5499 handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert *>(a));
5500 break;
5501 case lt::listen_failed_alert::alert_type:
5502 handleListenFailedAlert(static_cast<const lt::listen_failed_alert *>(a));
5503 break;
5504 case lt::external_ip_alert::alert_type:
5505 handleExternalIPAlert(static_cast<const lt::external_ip_alert *>(a));
5506 break;
5507 case lt::alerts_dropped_alert::alert_type:
5508 handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(a));
5509 break;
5510 case lt::storage_moved_alert::alert_type:
5511 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert *>(a));
5512 break;
5513 case lt::storage_moved_failed_alert::alert_type:
5514 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert *>(a));
5515 break;
5516 case lt::socks5_alert::alert_type:
5517 handleSocks5Alert(static_cast<const lt::socks5_alert *>(a));
5518 break;
5519 case lt::i2p_alert::alert_type:
5520 handleI2PAlert(static_cast<const lt::i2p_alert *>(a));
5521 break;
5522 #ifdef QBT_USES_LIBTORRENT2
5523 case lt::torrent_conflict_alert::alert_type:
5524 handleTorrentConflictAlert(static_cast<const lt::torrent_conflict_alert *>(a));
5525 break;
5526 #endif
5529 catch (const std::exception &exc)
5531 qWarning() << "Caught exception in " << Q_FUNC_INFO << ": " << QString::fromStdString(exc.what());
5535 void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert *a)
5537 const TorrentID torrentID {a->handle.info_hash()};
5538 TorrentImpl *torrent = m_torrents.value(torrentID);
5539 #ifdef QBT_USES_LIBTORRENT2
5540 if (!torrent && (a->type() == lt::metadata_received_alert::alert_type))
5542 const InfoHash infoHash {a->handle.info_hashes()};
5543 if (infoHash.isHybrid())
5544 torrent = m_torrents.value(TorrentID::fromSHA1Hash(infoHash.v1()));
5546 #endif
5548 if (torrent)
5550 torrent->handleAlert(a);
5551 return;
5554 switch (a->type())
5556 case lt::metadata_received_alert::alert_type:
5557 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert *>(a));
5558 break;
5562 TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
5564 auto *const torrent = new TorrentImpl(this, m_nativeSession, nativeHandle, params);
5565 m_torrents.insert(torrent->id(), torrent);
5566 if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
5567 m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent);
5569 if (isRestored())
5571 if (params.addToQueueTop)
5572 nativeHandle.queue_position_top();
5574 torrent->saveResumeData(lt::torrent_handle::save_info_dict);
5576 // The following is useless for newly added magnet
5577 if (torrent->hasMetadata())
5579 if (!torrentExportDirectory().isEmpty())
5580 exportTorrentFile(torrent, torrentExportDirectory());
5584 if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
5585 && !m_seedingLimitTimer->isActive())
5587 m_seedingLimitTimer->start();
5590 if (!isRestored())
5592 LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name()));
5594 else
5596 LogMsg(tr("Added new torrent. Torrent: \"%1\"").arg(torrent->name()));
5597 emit torrentAdded(torrent);
5600 // Torrent could have error just after adding to libtorrent
5601 if (torrent->hasError())
5602 LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: \"%2\"").arg(torrent->name(), torrent->error()), Log::WARNING);
5604 return torrent;
5607 void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
5609 #ifdef QBT_USES_LIBTORRENT2
5610 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5611 #else
5612 const auto id = TorrentID::fromInfoHash(p->info_hash);
5613 #endif
5615 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5616 if (removingTorrentDataIter != m_removingTorrents.end())
5618 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
5620 LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5621 m_removingTorrents.erase(removingTorrentDataIter);
5626 void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
5628 #ifdef QBT_USES_LIBTORRENT2
5629 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5630 #else
5631 const auto id = TorrentID::fromInfoHash(p->info_hash);
5632 #endif
5634 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5635 if (removingTorrentDataIter == m_removingTorrents.end())
5636 return;
5638 // torrent_deleted_alert can also be posted due to deletion of partfile. Ignore it in such a case.
5639 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
5640 return;
5642 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
5643 LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5644 m_removingTorrents.erase(removingTorrentDataIter);
5647 void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
5649 #ifdef QBT_USES_LIBTORRENT2
5650 const auto id = TorrentID::fromInfoHash(p->info_hashes);
5651 #else
5652 const auto id = TorrentID::fromInfoHash(p->info_hash);
5653 #endif
5655 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5656 if (removingTorrentDataIter == m_removingTorrents.end())
5657 return;
5659 if (p->error)
5661 // libtorrent won't delete the directory if it contains files not listed in the torrent,
5662 // so we remove the directory ourselves
5663 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
5665 LogMsg(tr("Removed torrent but failed to delete its content and/or partfile. Torrent: \"%1\". Error: \"%2\"")
5666 .arg(removingTorrentDataIter->name, QString::fromLocal8Bit(p->error.message().c_str()))
5667 , Log::WARNING);
5669 else // torrent without metadata, hence no files on disk
5671 LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5674 m_removingTorrents.erase(removingTorrentDataIter);
5677 void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
5679 const TorrentID torrentID {p->handle.info_hash()};
5681 bool found = false;
5682 if (const auto iter = m_downloadedMetadata.find(torrentID); iter != m_downloadedMetadata.end())
5684 found = true;
5685 m_downloadedMetadata.erase(iter);
5687 #ifdef QBT_USES_LIBTORRENT2
5688 const InfoHash infoHash {p->handle.info_hashes()};
5689 if (infoHash.isHybrid())
5691 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5692 if (const auto iter = m_downloadedMetadata.find(altID); iter != m_downloadedMetadata.end())
5694 found = true;
5695 m_downloadedMetadata.erase(iter);
5698 #endif
5699 if (found)
5701 const TorrentInfo metadata {*p->handle.torrent_file()};
5702 m_nativeSession->remove_torrent(p->handle, lt::session::delete_files);
5704 emit metadataDownloaded(metadata);
5708 void SessionImpl::handleFileErrorAlert(const lt::file_error_alert *p)
5710 TorrentImpl *const torrent = m_torrents.value(p->handle.info_hash());
5711 if (!torrent)
5712 return;
5714 torrent->handleAlert(p);
5716 const TorrentID id = torrent->id();
5717 if (!m_recentErroredTorrents.contains(id))
5719 m_recentErroredTorrents.insert(id);
5721 const QString msg = QString::fromStdString(p->message());
5722 LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: \"%3\"")
5723 .arg(torrent->name(), QString::fromUtf8(p->filename()), msg)
5724 , Log::WARNING);
5725 emit fullDiskError(torrent, msg);
5728 m_recentErroredTorrentsTimer->start();
5731 void SessionImpl::handlePortmapWarningAlert(const lt::portmap_error_alert *p)
5733 LogMsg(tr("UPnP/NAT-PMP port mapping failed. Message: \"%1\"").arg(QString::fromStdString(p->message())), Log::WARNING);
5736 void SessionImpl::handlePortmapAlert(const lt::portmap_alert *p)
5738 qDebug("UPnP Success, msg: %s", p->message().c_str());
5739 LogMsg(tr("UPnP/NAT-PMP port mapping succeeded. Message: \"%1\"").arg(QString::fromStdString(p->message())), Log::INFO);
5742 void SessionImpl::handlePeerBlockedAlert(const lt::peer_blocked_alert *p)
5744 QString reason;
5745 switch (p->reason)
5747 case lt::peer_blocked_alert::ip_filter:
5748 reason = tr("IP filter", "this peer was blocked. Reason: IP filter.");
5749 break;
5750 case lt::peer_blocked_alert::port_filter:
5751 reason = tr("filtered port (%1)", "this peer was blocked. Reason: filtered port (8899).").arg(QString::number(p->endpoint.port()));
5752 break;
5753 case lt::peer_blocked_alert::i2p_mixed:
5754 reason = tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg(u"I2P"_s); // don't translate I2P
5755 break;
5756 case lt::peer_blocked_alert::privileged_ports:
5757 reason = tr("privileged port (%1)", "this peer was blocked. Reason: privileged port (80).").arg(QString::number(p->endpoint.port()));
5758 break;
5759 case lt::peer_blocked_alert::utp_disabled:
5760 reason = tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(C_UTP); // don't translate μTP
5761 break;
5762 case lt::peer_blocked_alert::tcp_disabled:
5763 reason = tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg(u"TCP"_s); // don't translate TCP
5764 break;
5767 const QString ip {toString(p->endpoint.address())};
5768 if (!ip.isEmpty())
5769 Logger::instance()->addPeer(ip, true, reason);
5772 void SessionImpl::handlePeerBanAlert(const lt::peer_ban_alert *p)
5774 const QString ip {toString(p->endpoint.address())};
5775 if (!ip.isEmpty())
5776 Logger::instance()->addPeer(ip, false);
5779 void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert *p)
5781 const TorrentImpl *torrent = m_torrents.value(p->handle.info_hash());
5782 if (!torrent)
5783 return;
5785 if (p->error)
5787 LogMsg(tr("URL seed DNS lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
5788 .arg(torrent->name(), QString::fromUtf8(p->server_url()), QString::fromStdString(p->message()))
5789 , Log::WARNING);
5791 else
5793 LogMsg(tr("Received error message from URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
5794 .arg(torrent->name(), QString::fromUtf8(p->server_url()), QString::fromUtf8(p->error_message()))
5795 , Log::WARNING);
5799 void SessionImpl::handleListenSucceededAlert(const lt::listen_succeeded_alert *p)
5801 const QString proto {toString(p->socket_type)};
5802 LogMsg(tr("Successfully listening on IP. IP: \"%1\". Port: \"%2/%3\"")
5803 .arg(toString(p->address), proto, QString::number(p->port)), Log::INFO);
5806 void SessionImpl::handleListenFailedAlert(const lt::listen_failed_alert *p)
5808 const QString proto {toString(p->socket_type)};
5809 LogMsg(tr("Failed to listen on IP. IP: \"%1\". Port: \"%2/%3\". Reason: \"%4\"")
5810 .arg(toString(p->address), proto, QString::number(p->port)
5811 , QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
5814 void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *p)
5816 const QString externalIP {toString(p->external_address)};
5817 LogMsg(tr("Detected external IP. IP: \"%1\"")
5818 .arg(externalIP), Log::INFO);
5820 if (m_lastExternalIP != externalIP)
5822 if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty())
5823 reannounceToAllTrackers();
5824 m_lastExternalIP = externalIP;
5828 void SessionImpl::handleSessionErrorAlert(const lt::session_error_alert *p) const
5830 LogMsg(tr("BitTorrent session encountered a serious error. Reason: \"%1\"")
5831 .arg(QString::fromStdString(p->message())), Log::CRITICAL);
5834 void SessionImpl::handleSessionStatsAlert(const lt::session_stats_alert *p)
5836 if (m_refreshEnqueued)
5837 m_refreshEnqueued = false;
5838 else
5839 enqueueRefresh();
5841 const int64_t interval = lt::total_microseconds(p->timestamp() - m_statsLastTimestamp);
5842 if (interval <= 0)
5843 return;
5845 m_statsLastTimestamp = p->timestamp();
5847 const auto stats = p->counters();
5849 m_status.hasIncomingConnections = static_cast<bool>(stats[m_metricIndices.net.hasIncomingConnections]);
5851 const int64_t ipOverheadDownload = stats[m_metricIndices.net.recvIPOverheadBytes];
5852 const int64_t ipOverheadUpload = stats[m_metricIndices.net.sentIPOverheadBytes];
5853 const int64_t totalDownload = stats[m_metricIndices.net.recvBytes] + ipOverheadDownload;
5854 const int64_t totalUpload = stats[m_metricIndices.net.sentBytes] + ipOverheadUpload;
5855 const int64_t totalPayloadDownload = stats[m_metricIndices.net.recvPayloadBytes];
5856 const int64_t totalPayloadUpload = stats[m_metricIndices.net.sentPayloadBytes];
5857 const int64_t trackerDownload = stats[m_metricIndices.net.recvTrackerBytes];
5858 const int64_t trackerUpload = stats[m_metricIndices.net.sentTrackerBytes];
5859 const int64_t dhtDownload = stats[m_metricIndices.dht.dhtBytesIn];
5860 const int64_t dhtUpload = stats[m_metricIndices.dht.dhtBytesOut];
5862 const auto calcRate = [interval](const qint64 previous, const qint64 current) -> qint64
5864 Q_ASSERT(current >= previous);
5865 Q_ASSERT(interval >= 0);
5866 return (((current - previous) * lt::microseconds(1s).count()) / interval);
5869 m_status.payloadDownloadRate = calcRate(m_status.totalPayloadDownload, totalPayloadDownload);
5870 m_status.payloadUploadRate = calcRate(m_status.totalPayloadUpload, totalPayloadUpload);
5871 m_status.downloadRate = calcRate(m_status.totalDownload, totalDownload);
5872 m_status.uploadRate = calcRate(m_status.totalUpload, totalUpload);
5873 m_status.ipOverheadDownloadRate = calcRate(m_status.ipOverheadDownload, ipOverheadDownload);
5874 m_status.ipOverheadUploadRate = calcRate(m_status.ipOverheadUpload, ipOverheadUpload);
5875 m_status.dhtDownloadRate = calcRate(m_status.dhtDownload, dhtDownload);
5876 m_status.dhtUploadRate = calcRate(m_status.dhtUpload, dhtUpload);
5877 m_status.trackerDownloadRate = calcRate(m_status.trackerDownload, trackerDownload);
5878 m_status.trackerUploadRate = calcRate(m_status.trackerUpload, trackerUpload);
5880 m_status.totalPayloadDownload = totalPayloadDownload;
5881 m_status.totalPayloadUpload = totalPayloadUpload;
5882 m_status.ipOverheadDownload = ipOverheadDownload;
5883 m_status.ipOverheadUpload = ipOverheadUpload;
5884 m_status.trackerDownload = trackerDownload;
5885 m_status.trackerUpload = trackerUpload;
5886 m_status.dhtDownload = dhtDownload;
5887 m_status.dhtUpload = dhtUpload;
5888 m_status.totalWasted = stats[m_metricIndices.net.recvRedundantBytes]
5889 + stats[m_metricIndices.net.recvFailedBytes];
5890 m_status.dhtNodes = stats[m_metricIndices.dht.dhtNodes];
5891 m_status.diskReadQueue = stats[m_metricIndices.peer.numPeersUpDisk];
5892 m_status.diskWriteQueue = stats[m_metricIndices.peer.numPeersDownDisk];
5893 m_status.peersCount = stats[m_metricIndices.peer.numPeersConnected];
5895 if (totalDownload > m_status.totalDownload)
5897 m_status.totalDownload = totalDownload;
5898 m_isStatisticsDirty = true;
5901 if (totalUpload > m_status.totalUpload)
5903 m_status.totalUpload = totalUpload;
5904 m_isStatisticsDirty = true;
5907 m_status.allTimeDownload = m_previouslyDownloaded + m_status.totalDownload;
5908 m_status.allTimeUpload = m_previouslyUploaded + m_status.totalUpload;
5910 if (m_statisticsLastUpdateTimer.hasExpired(STATISTICS_SAVE_INTERVAL))
5911 saveStatistics();
5913 m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
5914 m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
5916 #ifndef QBT_USES_LIBTORRENT2
5917 const int64_t numBlocksRead = stats[m_metricIndices.disk.numBlocksRead];
5918 const int64_t numBlocksCacheHits = stats[m_metricIndices.disk.numBlocksCacheHits];
5919 m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max<int64_t>((numBlocksCacheHits + numBlocksRead), 1);
5920 #endif
5922 const int64_t totalJobs = stats[m_metricIndices.disk.writeJobs] + stats[m_metricIndices.disk.readJobs]
5923 + stats[m_metricIndices.disk.hashJobs];
5924 m_cacheStatus.averageJobTime = (totalJobs > 0)
5925 ? (stats[m_metricIndices.disk.diskJobTime] / totalJobs) : 0;
5927 emit statsUpdated();
5930 void SessionImpl::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
5932 LogMsg(tr("Error: Internal alert queue is full and alerts are dropped, you might see degraded performance. Dropped alert type: \"%1\". Message: \"%2\"")
5933 .arg(QString::fromStdString(p->dropped_alerts.to_string()), QString::fromStdString(p->message())), Log::CRITICAL);
5936 void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *p)
5938 Q_ASSERT(!m_moveStorageQueue.isEmpty());
5940 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
5941 Q_ASSERT(currentJob.torrentHandle == p->handle);
5943 const Path newPath {QString::fromUtf8(p->storage_path())};
5944 Q_ASSERT(newPath == currentJob.path);
5946 #ifdef QBT_USES_LIBTORRENT2
5947 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
5948 #else
5949 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
5950 #endif
5952 TorrentImpl *torrent = m_torrents.value(id);
5953 const QString torrentName = (torrent ? torrent->name() : id.toString());
5954 LogMsg(tr("Moved torrent successfully. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, newPath.toString()));
5956 handleMoveTorrentStorageJobFinished(newPath);
5959 void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
5961 Q_ASSERT(!m_moveStorageQueue.isEmpty());
5963 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
5964 Q_ASSERT(currentJob.torrentHandle == p->handle);
5966 #ifdef QBT_USES_LIBTORRENT2
5967 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
5968 #else
5969 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
5970 #endif
5972 TorrentImpl *torrent = m_torrents.value(id);
5973 const QString torrentName = (torrent ? torrent->name() : id.toString());
5974 const Path currentLocation = (torrent ? torrent->actualStorageLocation()
5975 : Path(p->handle.status(lt::torrent_handle::query_save_path).save_path));
5976 const QString errorMessage = QString::fromStdString(p->message());
5977 LogMsg(tr("Failed to move torrent. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: \"%4\"")
5978 .arg(torrentName, currentLocation.toString(), currentJob.path.toString(), errorMessage), Log::WARNING);
5980 handleMoveTorrentStorageJobFinished(currentLocation);
5983 void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *p)
5985 QVector<Torrent *> updatedTorrents;
5986 updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(p->status.size()));
5988 for (const lt::torrent_status &status : p->status)
5990 #ifdef QBT_USES_LIBTORRENT2
5991 const auto id = TorrentID::fromInfoHash(status.info_hashes);
5992 #else
5993 const auto id = TorrentID::fromInfoHash(status.info_hash);
5994 #endif
5995 TorrentImpl *const torrent = m_torrents.value(id);
5996 if (!torrent)
5997 continue;
5999 torrent->handleStateUpdate(status);
6000 updatedTorrents.push_back(torrent);
6003 if (!updatedTorrents.isEmpty())
6004 emit torrentsUpdated(updatedTorrents);
6006 if (m_needSaveTorrentsQueue)
6007 saveTorrentsQueue();
6009 if (m_refreshEnqueued)
6010 m_refreshEnqueued = false;
6011 else
6012 enqueueRefresh();
6015 void SessionImpl::handleSocks5Alert(const lt::socks5_alert *p) const
6017 if (p->error)
6019 const auto addr = p->ip.address();
6020 const QString endpoint = (addr.is_v6() ? u"[%1]:%2"_s : u"%1:%2"_s)
6021 .arg(QString::fromStdString(addr.to_string()), QString::number(p->ip.port()));
6022 LogMsg(tr("SOCKS5 proxy error. Address: %1. Message: \"%2\".")
6023 .arg(endpoint, QString::fromLocal8Bit(p->error.message().c_str()))
6024 , Log::WARNING);
6028 void SessionImpl::handleI2PAlert(const lt::i2p_alert *p) const
6030 if (p->error)
6032 LogMsg(tr("I2P error. Message: \"%1\".")
6033 .arg(QString::fromStdString(p->message())), Log::WARNING);
6037 void SessionImpl::handleTrackerAlert(const lt::tracker_alert *a)
6039 TorrentImpl *torrent = m_torrents.value(a->handle.info_hash());
6040 if (!torrent)
6041 return;
6043 QMap<int, int> &updateInfo = m_updatedTrackerEntries[torrent->nativeHandle()][std::string(a->tracker_url())][a->local_endpoint];
6045 if (a->type() == lt::tracker_reply_alert::alert_type)
6047 const int numPeers = static_cast<const lt::tracker_reply_alert *>(a)->num_peers;
6048 #ifdef QBT_USES_LIBTORRENT2
6049 const int protocolVersionNum = (static_cast<const lt::tracker_reply_alert *>(a)->version == lt::protocol_version::V1) ? 1 : 2;
6050 #else
6051 const int protocolVersionNum = 1;
6052 #endif
6053 updateInfo.insert(protocolVersionNum, numPeers);
6057 #ifdef QBT_USES_LIBTORRENT2
6058 void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a)
6060 const auto torrentIDv1 = TorrentID::fromSHA1Hash(a->metadata->info_hashes().v1);
6061 const auto torrentIDv2 = TorrentID::fromSHA256Hash(a->metadata->info_hashes().v2);
6062 TorrentImpl *torrent1 = m_torrents.value(torrentIDv1);
6063 TorrentImpl *torrent2 = m_torrents.value(torrentIDv2);
6064 if (torrent2)
6066 if (torrent1)
6067 deleteTorrent(torrentIDv1);
6068 else
6069 cancelDownloadMetadata(torrentIDv1);
6071 invokeAsync([torrentHandle = torrent2->nativeHandle(), metadata = a->metadata]
6075 torrentHandle.set_metadata(metadata->info_section());
6077 catch (const std::exception &) {}
6080 else if (torrent1)
6082 if (!torrent2)
6083 cancelDownloadMetadata(torrentIDv2);
6085 invokeAsync([torrentHandle = torrent1->nativeHandle(), metadata = a->metadata]
6089 torrentHandle.set_metadata(metadata->info_section());
6091 catch (const std::exception &) {}
6094 else
6096 cancelDownloadMetadata(torrentIDv1);
6097 cancelDownloadMetadata(torrentIDv2);
6100 if (!torrent1 || !torrent2)
6101 emit metadataDownloaded(TorrentInfo(*a->metadata));
6103 #endif
6105 void SessionImpl::processTrackerStatuses()
6107 if (m_updatedTrackerEntries.isEmpty())
6108 return;
6110 for (auto it = m_updatedTrackerEntries.cbegin(); it != m_updatedTrackerEntries.cend(); ++it)
6112 updateTrackerEntries(it.key(), it.value());
6115 m_updatedTrackerEntries.clear();
6118 void SessionImpl::saveStatistics() const
6120 if (!m_isStatisticsDirty)
6121 return;
6123 const QVariantHash stats {
6124 {u"AlltimeDL"_s, m_status.allTimeDownload},
6125 {u"AlltimeUL"_s, m_status.allTimeUpload}};
6126 std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_s);
6127 settings->setValue(u"Stats/AllStats"_s, stats);
6129 m_statisticsLastUpdateTimer.start();
6130 m_isStatisticsDirty = false;
6133 void SessionImpl::loadStatistics()
6135 const std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_s);
6136 const QVariantHash value = settings->value(u"Stats/AllStats"_s).toHash();
6138 m_previouslyDownloaded = value[u"AlltimeDL"_s].toLongLong();
6139 m_previouslyUploaded = value[u"AlltimeUL"_s].toLongLong();
6142 void SessionImpl::updateTrackerEntries(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers)
6144 invokeAsync([this, torrentHandle = std::move(torrentHandle), updatedTrackers = std::move(updatedTrackers)]() mutable
6148 std::vector<lt::announce_entry> nativeTrackers = torrentHandle.trackers();
6149 invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers)
6150 , updatedTrackers = std::move(updatedTrackers)]
6152 TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
6153 if (!torrent || torrent->isPaused())
6154 return;
6156 QHash<QString, TrackerEntry> updatedTrackerEntries;
6157 updatedTrackerEntries.reserve(updatedTrackers.size());
6158 for (const lt::announce_entry &announceEntry : nativeTrackers)
6160 const auto updatedTrackersIter = updatedTrackers.find(announceEntry.url);
6161 if (updatedTrackersIter == updatedTrackers.end())
6162 continue;
6164 const auto &updateInfo = updatedTrackersIter.value();
6165 TrackerEntry trackerEntry = torrent->updateTrackerEntry(announceEntry, updateInfo);
6166 const QString url = trackerEntry.url;
6167 updatedTrackerEntries.emplace(url, std::move(trackerEntry));
6170 emit trackerEntriesUpdated(torrent, updatedTrackerEntries);
6173 catch (const std::exception &)