Apply filename filter to subfolder names as well
[qBittorrent.git] / src / base / bittorrent / sessionimpl.cpp
blobe34c93acb015017be1426c443694e8c0f01bba78
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 <QDeadlineTimer>
62 #include <QDebug>
63 #include <QDir>
64 #include <QHostAddress>
65 #include <QJsonArray>
66 #include <QJsonDocument>
67 #include <QJsonObject>
68 #include <QJsonValue>
69 #include <QNetworkAddressEntry>
70 #include <QNetworkInterface>
71 #include <QRegularExpression>
72 #include <QString>
73 #include <QThread>
74 #include <QThreadPool>
75 #include <QTimer>
76 #include <QUuid>
78 #include "base/algorithm.h"
79 #include "base/global.h"
80 #include "base/logger.h"
81 #include "base/net/proxyconfigurationmanager.h"
82 #include "base/preferences.h"
83 #include "base/profile.h"
84 #include "base/unicodestrings.h"
85 #include "base/utils/fs.h"
86 #include "base/utils/io.h"
87 #include "base/utils/net.h"
88 #include "base/utils/number.h"
89 #include "base/utils/random.h"
90 #include "base/version.h"
91 #include "bandwidthscheduler.h"
92 #include "bencoderesumedatastorage.h"
93 #include "customstorage.h"
94 #include "dbresumedatastorage.h"
95 #include "downloadpriority.h"
96 #include "extensiondata.h"
97 #include "filesearcher.h"
98 #include "filterparserthread.h"
99 #include "loadtorrentparams.h"
100 #include "lttypecast.h"
101 #include "nativesessionextension.h"
102 #include "portforwarderimpl.h"
103 #include "resumedatastorage.h"
104 #include "torrentdescriptor.h"
105 #include "torrentimpl.h"
106 #include "tracker.h"
107 #include "trackerentry.h"
109 using namespace std::chrono_literals;
110 using namespace BitTorrent;
112 const Path CATEGORIES_FILE_NAME {u"categories.json"_s};
113 const int MAX_PROCESSING_RESUMEDATA_COUNT = 50;
114 const int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count();
116 namespace
118 const char PEER_ID[] = "qB";
119 const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2);
120 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;
122 void torrentQueuePositionUp(const lt::torrent_handle &handle)
126 handle.queue_position_up();
128 catch (const std::exception &exc)
130 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
134 void torrentQueuePositionDown(const lt::torrent_handle &handle)
138 handle.queue_position_down();
140 catch (const std::exception &exc)
142 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
146 void torrentQueuePositionTop(const lt::torrent_handle &handle)
150 handle.queue_position_top();
152 catch (const std::exception &exc)
154 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
158 void torrentQueuePositionBottom(const lt::torrent_handle &handle)
162 handle.queue_position_bottom();
164 catch (const std::exception &exc)
166 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
170 QMap<QString, CategoryOptions> expandCategories(const QMap<QString, CategoryOptions> &categories)
172 QMap<QString, CategoryOptions> expanded = categories;
174 for (auto i = categories.cbegin(); i != categories.cend(); ++i)
176 const QString &category = i.key();
177 for (const QString &subcat : asConst(Session::expandCategory(category)))
179 if (!expanded.contains(subcat))
180 expanded[subcat] = {};
184 return expanded;
187 QString toString(const lt::socket_type_t socketType)
189 switch (socketType)
191 #ifdef QBT_USES_LIBTORRENT2
192 case lt::socket_type_t::http:
193 return u"HTTP"_s;
194 case lt::socket_type_t::http_ssl:
195 return u"HTTP_SSL"_s;
196 #endif
197 case lt::socket_type_t::i2p:
198 return u"I2P"_s;
199 case lt::socket_type_t::socks5:
200 return u"SOCKS5"_s;
201 #ifdef QBT_USES_LIBTORRENT2
202 case lt::socket_type_t::socks5_ssl:
203 return u"SOCKS5_SSL"_s;
204 #endif
205 case lt::socket_type_t::tcp:
206 return u"TCP"_s;
207 case lt::socket_type_t::tcp_ssl:
208 return u"TCP_SSL"_s;
209 #ifdef QBT_USES_LIBTORRENT2
210 case lt::socket_type_t::utp:
211 return u"UTP"_s;
212 #else
213 case lt::socket_type_t::udp:
214 return u"UDP"_s;
215 #endif
216 case lt::socket_type_t::utp_ssl:
217 return u"UTP_SSL"_s;
219 return u"INVALID"_s;
222 QString toString(const lt::address &address)
226 return QString::fromLatin1(address.to_string().c_str());
228 catch (const std::exception &)
230 // suppress conversion error
232 return {};
235 template <typename T>
236 struct LowerLimited
238 LowerLimited(T limit, T ret)
239 : m_limit(limit)
240 , m_ret(ret)
244 explicit LowerLimited(T limit)
245 : LowerLimited(limit, limit)
249 T operator()(T val) const
251 return val <= m_limit ? m_ret : val;
254 private:
255 const T m_limit;
256 const T m_ret;
259 template <typename T>
260 LowerLimited<T> lowerLimited(T limit) { return LowerLimited<T>(limit); }
262 template <typename T>
263 LowerLimited<T> lowerLimited(T limit, T ret) { return LowerLimited<T>(limit, ret); }
265 template <typename T>
266 auto clampValue(const T lower, const T upper)
268 return [lower, upper](const T value) -> T
270 return std::clamp(value, lower, upper);
274 #ifdef Q_OS_WIN
275 QString convertIfaceNameToGuid(const QString &name)
277 // Under Windows XP or on Qt version <= 5.5 'name' will be a GUID already.
278 const QUuid uuid(name);
279 if (!uuid.isNull())
280 return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
282 const std::wstring nameWStr = name.toStdWString();
283 NET_LUID luid {};
284 const LONG res = ::ConvertInterfaceNameToLuidW(nameWStr.c_str(), &luid);
285 if (res == 0)
287 GUID guid;
288 if (::ConvertInterfaceLuidToGuid(&luid, &guid) == 0)
289 return QUuid(guid).toString().toUpper();
292 return {};
294 #endif
296 constexpr lt::move_flags_t toNative(const MoveStorageMode mode)
298 switch (mode)
300 default:
301 Q_ASSERT(false);
302 case MoveStorageMode::FailIfExist:
303 return lt::move_flags_t::fail_if_exist;
304 case MoveStorageMode::KeepExistingFiles:
305 return lt::move_flags_t::dont_replace;
306 case MoveStorageMode::Overwrite:
307 return lt::move_flags_t::always_replace_files;
312 struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject
314 using QObject::QObject;
316 ResumeDataStorage *startupStorage = nullptr;
317 ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy;
318 QList<LoadedResumeData> loadedResumeData;
319 int processingResumeDataCount = 0;
320 int64_t totalResumeDataCount = 0;
321 int64_t finishedResumeDataCount = 0;
322 bool isLoadFinished = false;
323 bool isLoadedResumeDataHandlingEnqueued = false;
324 QSet<QString> recoveredCategories;
325 #ifdef QBT_USES_LIBTORRENT2
326 QSet<TorrentID> indexedTorrents;
327 QSet<TorrentID> skippedIDs;
328 #endif
331 const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
333 Session *SessionImpl::m_instance = nullptr;
335 void Session::initInstance()
337 if (!SessionImpl::m_instance)
338 SessionImpl::m_instance = new SessionImpl;
341 void Session::freeInstance()
343 delete SessionImpl::m_instance;
344 SessionImpl::m_instance = nullptr;
347 Session *Session::instance()
349 return SessionImpl::m_instance;
352 bool Session::isValidCategoryName(const QString &name)
354 const QRegularExpression re {uR"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)"_s};
355 return (name.isEmpty() || (name.indexOf(re) == 0));
358 QString Session::subcategoryName(const QString &category)
360 const int sepIndex = category.lastIndexOf(u'/');
361 if (sepIndex >= 0)
362 return category.mid(sepIndex + 1);
364 return category;
367 QString Session::parentCategoryName(const QString &category)
369 const int sepIndex = category.lastIndexOf(u'/');
370 if (sepIndex >= 0)
371 return category.left(sepIndex);
373 return {};
376 QStringList Session::expandCategory(const QString &category)
378 QStringList result;
379 int index = 0;
380 while ((index = category.indexOf(u'/', index)) >= 0)
382 result << category.left(index);
383 ++index;
385 result << category;
387 return result;
390 #define BITTORRENT_KEY(name) u"BitTorrent/" name
391 #define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY(u"Session/") name
393 SessionImpl::SessionImpl(QObject *parent)
394 : Session(parent)
395 , m_DHTBootstrapNodes(BITTORRENT_SESSION_KEY(u"DHTBootstrapNodes"_s), DEFAULT_DHT_BOOTSTRAP_NODES)
396 , m_isDHTEnabled(BITTORRENT_SESSION_KEY(u"DHTEnabled"_s), true)
397 , m_isLSDEnabled(BITTORRENT_SESSION_KEY(u"LSDEnabled"_s), true)
398 , m_isPeXEnabled(BITTORRENT_SESSION_KEY(u"PeXEnabled"_s), true)
399 , m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY(u"IPFilteringEnabled"_s), false)
400 , m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY(u"TrackerFilteringEnabled"_s), false)
401 , m_IPFilterFile(BITTORRENT_SESSION_KEY(u"IPFilter"_s))
402 , m_announceToAllTrackers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTrackers"_s), false)
403 , m_announceToAllTiers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTiers"_s), true)
404 , m_asyncIOThreads(BITTORRENT_SESSION_KEY(u"AsyncIOThreadsCount"_s), 10)
405 , m_hashingThreads(BITTORRENT_SESSION_KEY(u"HashingThreadsCount"_s), 1)
406 , m_filePoolSize(BITTORRENT_SESSION_KEY(u"FilePoolSize"_s), 100)
407 , m_checkingMemUsage(BITTORRENT_SESSION_KEY(u"CheckingMemUsageSize"_s), 32)
408 , m_diskCacheSize(BITTORRENT_SESSION_KEY(u"DiskCacheSize"_s), -1)
409 , m_diskCacheTTL(BITTORRENT_SESSION_KEY(u"DiskCacheTTL"_s), 60)
410 , m_diskQueueSize(BITTORRENT_SESSION_KEY(u"DiskQueueSize"_s), (1024 * 1024))
411 , m_diskIOType(BITTORRENT_SESSION_KEY(u"DiskIOType"_s), DiskIOType::Default)
412 , m_diskIOReadMode(BITTORRENT_SESSION_KEY(u"DiskIOReadMode"_s), DiskIOReadMode::EnableOSCache)
413 , m_diskIOWriteMode(BITTORRENT_SESSION_KEY(u"DiskIOWriteMode"_s), DiskIOWriteMode::EnableOSCache)
414 #ifdef Q_OS_WIN
415 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_s), true)
416 #else
417 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_s), false)
418 #endif
419 , m_usePieceExtentAffinity(BITTORRENT_SESSION_KEY(u"PieceExtentAffinity"_s), false)
420 , m_isSuggestMode(BITTORRENT_SESSION_KEY(u"SuggestMode"_s), false)
421 , m_sendBufferWatermark(BITTORRENT_SESSION_KEY(u"SendBufferWatermark"_s), 500)
422 , m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY(u"SendBufferLowWatermark"_s), 10)
423 , m_sendBufferWatermarkFactor(BITTORRENT_SESSION_KEY(u"SendBufferWatermarkFactor"_s), 50)
424 , m_connectionSpeed(BITTORRENT_SESSION_KEY(u"ConnectionSpeed"_s), 30)
425 , m_socketSendBufferSize(BITTORRENT_SESSION_KEY(u"SocketSendBufferSize"_s), 0)
426 , m_socketReceiveBufferSize(BITTORRENT_SESSION_KEY(u"SocketReceiveBufferSize"_s), 0)
427 , m_socketBacklogSize(BITTORRENT_SESSION_KEY(u"SocketBacklogSize"_s), 30)
428 , m_isAnonymousModeEnabled(BITTORRENT_SESSION_KEY(u"AnonymousModeEnabled"_s), false)
429 , m_isQueueingEnabled(BITTORRENT_SESSION_KEY(u"QueueingSystemEnabled"_s), false)
430 , m_maxActiveDownloads(BITTORRENT_SESSION_KEY(u"MaxActiveDownloads"_s), 3, lowerLimited(-1))
431 , m_maxActiveUploads(BITTORRENT_SESSION_KEY(u"MaxActiveUploads"_s), 3, lowerLimited(-1))
432 , m_maxActiveTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveTorrents"_s), 5, lowerLimited(-1))
433 , m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY(u"IgnoreSlowTorrentsForQueueing"_s), false)
434 , m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsDownloadRate"_s), 2)
435 , m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsUploadRate"_s), 2)
436 , m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY(u"SlowTorrentsInactivityTimer"_s), 60)
437 , m_outgoingPortsMin(BITTORRENT_SESSION_KEY(u"OutgoingPortsMin"_s), 0)
438 , m_outgoingPortsMax(BITTORRENT_SESSION_KEY(u"OutgoingPortsMax"_s), 0)
439 , m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY(u"UPnPLeaseDuration"_s), 0)
440 , m_peerToS(BITTORRENT_SESSION_KEY(u"PeerToS"_s), 0x04)
441 , m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY(u"IgnoreLimitsOnLAN"_s), false)
442 , m_includeOverheadInLimits(BITTORRENT_SESSION_KEY(u"IncludeOverheadInLimits"_s), false)
443 , m_announceIP(BITTORRENT_SESSION_KEY(u"AnnounceIP"_s))
444 , m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY(u"MaxConcurrentHTTPAnnounces"_s), 50)
445 , m_isReannounceWhenAddressChangedEnabled(BITTORRENT_SESSION_KEY(u"ReannounceWhenAddressChanged"_s), false)
446 , m_stopTrackerTimeout(BITTORRENT_SESSION_KEY(u"StopTrackerTimeout"_s), 2)
447 , m_maxConnections(BITTORRENT_SESSION_KEY(u"MaxConnections"_s), 500, lowerLimited(0, -1))
448 , m_maxUploads(BITTORRENT_SESSION_KEY(u"MaxUploads"_s), 20, lowerLimited(0, -1))
449 , m_maxConnectionsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxConnectionsPerTorrent"_s), 100, lowerLimited(0, -1))
450 , m_maxUploadsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxUploadsPerTorrent"_s), 4, lowerLimited(0, -1))
451 , m_btProtocol(BITTORRENT_SESSION_KEY(u"BTProtocol"_s), BTProtocol::Both
452 , clampValue(BTProtocol::Both, BTProtocol::UTP))
453 , m_isUTPRateLimited(BITTORRENT_SESSION_KEY(u"uTPRateLimited"_s), true)
454 , m_utpMixedMode(BITTORRENT_SESSION_KEY(u"uTPMixedMode"_s), MixedModeAlgorithm::TCP
455 , clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
456 , m_IDNSupportEnabled(BITTORRENT_SESSION_KEY(u"IDNSupportEnabled"_s), false)
457 , m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY(u"MultiConnectionsPerIp"_s), false)
458 , m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY(u"ValidateHTTPSTrackerCertificate"_s), true)
459 , m_SSRFMitigationEnabled(BITTORRENT_SESSION_KEY(u"SSRFMitigation"_s), true)
460 , m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY(u"BlockPeersOnPrivilegedPorts"_s), false)
461 , m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersEnabled"_s), false)
462 , m_additionalTrackers(BITTORRENT_SESSION_KEY(u"AdditionalTrackers"_s))
463 , m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_s), -1, [](qreal r) { return r < 0 ? -1. : r;})
464 , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s), -1, lowerLimited(-1))
465 , m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), -1, lowerLimited(-1))
466 , m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_s), false)
467 , m_isAddTorrentStopped(BITTORRENT_SESSION_KEY(u"AddTorrentStopped"_s), false)
468 , m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_s), Torrent::StopCondition::None)
469 , m_torrentContentLayout(BITTORRENT_SESSION_KEY(u"TorrentContentLayout"_s), TorrentContentLayout::Original)
470 , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY(u"AddExtensionToIncompleteFiles"_s), false)
471 , m_isUnwantedFolderEnabled(BITTORRENT_SESSION_KEY(u"UseUnwantedFolder"_s), false)
472 , m_refreshInterval(BITTORRENT_SESSION_KEY(u"RefreshInterval"_s), 1500)
473 , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY(u"Preallocation"_s), false)
474 , m_torrentExportDirectory(BITTORRENT_SESSION_KEY(u"TorrentExportDirectory"_s))
475 , m_finishedTorrentExportDirectory(BITTORRENT_SESSION_KEY(u"FinishedTorrentExportDirectory"_s))
476 , m_globalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalDLSpeedLimit"_s), 0, lowerLimited(0))
477 , m_globalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalUPSpeedLimit"_s), 0, lowerLimited(0))
478 , m_altGlobalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalDLSpeedLimit"_s), 10, lowerLimited(0))
479 , m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalUPSpeedLimit"_s), 10, lowerLimited(0))
480 , m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY(u"UseAlternativeGlobalSpeedLimit"_s), false)
481 , m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY(u"BandwidthSchedulerEnabled"_s), false)
482 , m_isPerformanceWarningEnabled(BITTORRENT_SESSION_KEY(u"PerformanceWarning"_s), false)
483 , m_saveResumeDataInterval(BITTORRENT_SESSION_KEY(u"SaveResumeDataInterval"_s), 60)
484 , m_shutdownTimeout(BITTORRENT_SESSION_KEY(u"ShutdownTimeout"_s), -1)
485 , m_port(BITTORRENT_SESSION_KEY(u"Port"_s), -1)
486 , m_sslEnabled(BITTORRENT_SESSION_KEY(u"SSL/Enabled"_s), false)
487 , m_sslPort(BITTORRENT_SESSION_KEY(u"SSL/Port"_s), -1)
488 , m_networkInterface(BITTORRENT_SESSION_KEY(u"Interface"_s))
489 , m_networkInterfaceName(BITTORRENT_SESSION_KEY(u"InterfaceName"_s))
490 , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY(u"InterfaceAddress"_s))
491 , m_encryption(BITTORRENT_SESSION_KEY(u"Encryption"_s), 0)
492 , m_maxActiveCheckingTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveCheckingTorrents"_s), 1)
493 , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY(u"ProxyPeerConnections"_s), false)
494 , m_chokingAlgorithm(BITTORRENT_SESSION_KEY(u"ChokingAlgorithm"_s), ChokingAlgorithm::FixedSlots
495 , clampValue(ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased))
496 , m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY(u"SeedChokingAlgorithm"_s), SeedChokingAlgorithm::FastestUpload
497 , clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
498 , m_storedTags(BITTORRENT_SESSION_KEY(u"Tags"_s))
499 , m_shareLimitAction(BITTORRENT_SESSION_KEY(u"ShareLimitAction"_s), ShareLimitAction::Stop
500 , [](const ShareLimitAction action) { return (action == ShareLimitAction::Default) ? ShareLimitAction::Stop : action; })
501 , m_savePath(BITTORRENT_SESSION_KEY(u"DefaultSavePath"_s), specialFolderLocation(SpecialFolder::Downloads))
502 , m_downloadPath(BITTORRENT_SESSION_KEY(u"TempPath"_s), (savePath() / Path(u"temp"_s)))
503 , m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY(u"TempPathEnabled"_s), false)
504 , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY(u"SubcategoriesEnabled"_s), false)
505 , m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY(u"UseCategoryPathsInManualMode"_s), false)
506 , m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY(u"DisableAutoTMMByDefault"_s), true)
507 , m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategoryChanged"_s), false)
508 , m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/DefaultSavePathChanged"_s), true)
509 , m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategorySavePathChanged"_s), true)
510 , m_isTrackerEnabled(BITTORRENT_KEY(u"TrackerEnabled"_s), false)
511 , m_peerTurnover(BITTORRENT_SESSION_KEY(u"PeerTurnover"_s), 4)
512 , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY(u"PeerTurnoverCutOff"_s), 90)
513 , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY(u"PeerTurnoverInterval"_s), 300)
514 , m_requestQueueSize(BITTORRENT_SESSION_KEY(u"RequestQueueSize"_s), 500)
515 , m_isExcludedFileNamesEnabled(BITTORRENT_KEY(u"ExcludedFileNamesEnabled"_s), false)
516 , m_excludedFileNames(BITTORRENT_SESSION_KEY(u"ExcludedFileNames"_s))
517 , m_bannedIPs(u"State/BannedIPs"_s, QStringList(), Algorithm::sorted<QStringList>)
518 , m_resumeDataStorageType(BITTORRENT_SESSION_KEY(u"ResumeDataStorageType"_s), ResumeDataStorageType::Legacy)
519 , m_isMergeTrackersEnabled(BITTORRENT_KEY(u"MergeTrackersEnabled"_s), false)
520 , m_isI2PEnabled {BITTORRENT_SESSION_KEY(u"I2P/Enabled"_s), false}
521 , m_I2PAddress {BITTORRENT_SESSION_KEY(u"I2P/Address"_s), u"127.0.0.1"_s}
522 , m_I2PPort {BITTORRENT_SESSION_KEY(u"I2P/Port"_s), 7656}
523 , m_I2PMixedMode {BITTORRENT_SESSION_KEY(u"I2P/MixedMode"_s), false}
524 , m_I2PInboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/InboundQuantity"_s), 3}
525 , m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
526 , m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
527 , m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
528 , m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
529 , m_seedingLimitTimer {new QTimer(this)}
530 , m_resumeDataTimer {new QTimer(this)}
531 , m_ioThread {new QThread}
532 , m_asyncWorker {new QThreadPool(this)}
533 , m_recentErroredTorrentsTimer {new QTimer(this)}
535 // It is required to perform async access to libtorrent sequentially
536 m_asyncWorker->setMaxThreadCount(1);
538 if (port() < 0)
539 m_port = Utils::Random::rand(1024, 65535);
540 if (sslPort() < 0)
542 m_sslPort = Utils::Random::rand(1024, 65535);
543 while (m_sslPort == port())
544 m_sslPort = Utils::Random::rand(1024, 65535);
547 m_recentErroredTorrentsTimer->setSingleShot(true);
548 m_recentErroredTorrentsTimer->setInterval(1s);
549 connect(m_recentErroredTorrentsTimer, &QTimer::timeout
550 , this, [this]() { m_recentErroredTorrents.clear(); });
552 m_seedingLimitTimer->setInterval(10s);
553 connect(m_seedingLimitTimer, &QTimer::timeout, this, &SessionImpl::processShareLimits);
555 initializeNativeSession();
556 configureComponents();
558 if (isBandwidthSchedulerEnabled())
559 enableBandwidthScheduler();
561 loadCategories();
562 if (isSubcategoriesEnabled())
564 // if subcategories support changed manually
565 m_categories = expandCategories(m_categories);
568 const QStringList storedTags = m_storedTags.get();
569 for (const QString &tagStr : storedTags)
571 if (const Tag tag {tagStr}; tag.isValid())
572 m_tags.insert(tag);
575 updateSeedingLimitTimer();
576 populateAdditionalTrackers();
577 if (isExcludedFileNamesEnabled())
578 populateExcludedFileNamesRegExpList();
580 connect(Net::ProxyConfigurationManager::instance()
581 , &Net::ProxyConfigurationManager::proxyConfigurationChanged
582 , this, &SessionImpl::configureDeferred);
584 m_fileSearcher = new FileSearcher;
585 m_fileSearcher->moveToThread(m_ioThread.get());
586 connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
587 connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
589 m_ioThread->start();
591 initMetrics();
592 loadStatistics();
594 // initialize PortForwarder instance
595 new PortForwarderImpl(this);
597 // start embedded tracker
598 enableTracker(isTrackerEnabled());
600 prepareStartup();
603 SessionImpl::~SessionImpl()
605 m_nativeSession->pause();
607 const qint64 timeout = (m_shutdownTimeout >= 0) ? (m_shutdownTimeout * 1000) : -1;
608 const QDeadlineTimer shutdownDeadlineTimer {timeout};
610 if (m_torrentsQueueChanged)
612 m_nativeSession->post_torrent_updates({});
613 m_torrentsQueueChanged = false;
614 m_needSaveTorrentsQueue = true;
617 // Do some bittorrent related saving
618 // After this, (ideally) no more important alerts will be generated/handled
619 saveResumeData();
621 saveStatistics();
623 // We must delete FilterParserThread
624 // before we delete lt::session
625 delete m_filterParser;
627 // We must delete PortForwarderImpl before
628 // we delete lt::session
629 delete Net::PortForwarder::instance();
631 // We must stop "async worker" only after deletion
632 // of all the components that could potentially use it
633 m_asyncWorker->clear();
634 m_asyncWorker->waitForDone();
636 auto *nativeSessionProxy = new lt::session_proxy(m_nativeSession->abort());
637 delete m_nativeSession;
639 qDebug("Deleting resume data storage...");
640 delete m_resumeDataStorage;
641 LogMsg(tr("Saving resume data completed."));
643 auto *sessionTerminateThread = QThread::create([nativeSessionProxy]()
645 qDebug("Deleting libtorrent session...");
646 delete nativeSessionProxy;
648 connect(sessionTerminateThread, &QThread::finished, sessionTerminateThread, &QObject::deleteLater);
649 sessionTerminateThread->start();
650 if (sessionTerminateThread->wait(shutdownDeadlineTimer))
651 LogMsg(tr("BitTorrent session successfully finished."));
652 else
653 LogMsg(tr("Session shutdown timed out."));
656 QString SessionImpl::getDHTBootstrapNodes() const
658 const QString nodes = m_DHTBootstrapNodes;
659 return !nodes.isEmpty() ? nodes : DEFAULT_DHT_BOOTSTRAP_NODES;
662 void SessionImpl::setDHTBootstrapNodes(const QString &nodes)
664 if (nodes == m_DHTBootstrapNodes)
665 return;
667 m_DHTBootstrapNodes = nodes;
668 configureDeferred();
671 bool SessionImpl::isDHTEnabled() const
673 return m_isDHTEnabled;
676 void SessionImpl::setDHTEnabled(bool enabled)
678 if (enabled != m_isDHTEnabled)
680 m_isDHTEnabled = enabled;
681 configureDeferred();
682 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(enabled ? tr("ON") : tr("OFF")), Log::INFO);
686 bool SessionImpl::isLSDEnabled() const
688 return m_isLSDEnabled;
691 void SessionImpl::setLSDEnabled(const bool enabled)
693 if (enabled != m_isLSDEnabled)
695 m_isLSDEnabled = enabled;
696 configureDeferred();
697 LogMsg(tr("Local Peer Discovery support: %1").arg(enabled ? tr("ON") : tr("OFF"))
698 , Log::INFO);
702 bool SessionImpl::isPeXEnabled() const
704 return m_isPeXEnabled;
707 void SessionImpl::setPeXEnabled(const bool enabled)
709 m_isPeXEnabled = enabled;
710 if (m_wasPexEnabled != enabled)
711 LogMsg(tr("Restart is required to toggle Peer Exchange (PeX) support"), Log::WARNING);
714 bool SessionImpl::isDownloadPathEnabled() const
716 return m_isDownloadPathEnabled;
719 void SessionImpl::setDownloadPathEnabled(const bool enabled)
721 if (enabled != isDownloadPathEnabled())
723 m_isDownloadPathEnabled = enabled;
724 for (TorrentImpl *const torrent : asConst(m_torrents))
725 torrent->handleCategoryOptionsChanged();
729 bool SessionImpl::isAppendExtensionEnabled() const
731 return m_isAppendExtensionEnabled;
734 void SessionImpl::setAppendExtensionEnabled(const bool enabled)
736 if (isAppendExtensionEnabled() != enabled)
738 m_isAppendExtensionEnabled = enabled;
740 // append or remove .!qB extension for incomplete files
741 for (TorrentImpl *const torrent : asConst(m_torrents))
742 torrent->handleAppendExtensionToggled();
746 bool SessionImpl::isUnwantedFolderEnabled() const
748 return m_isUnwantedFolderEnabled;
751 void SessionImpl::setUnwantedFolderEnabled(const bool enabled)
753 if (isUnwantedFolderEnabled() != enabled)
755 m_isUnwantedFolderEnabled = enabled;
757 // append or remove .!qB extension for incomplete files
758 for (TorrentImpl *const torrent : asConst(m_torrents))
759 torrent->handleUnwantedFolderToggled();
763 int SessionImpl::refreshInterval() const
765 return m_refreshInterval;
768 void SessionImpl::setRefreshInterval(const int value)
770 if (value != refreshInterval())
772 m_refreshInterval = value;
776 bool SessionImpl::isPreallocationEnabled() const
778 return m_isPreallocationEnabled;
781 void SessionImpl::setPreallocationEnabled(const bool enabled)
783 m_isPreallocationEnabled = enabled;
786 Path SessionImpl::torrentExportDirectory() const
788 return m_torrentExportDirectory;
791 void SessionImpl::setTorrentExportDirectory(const Path &path)
793 if (path != torrentExportDirectory())
794 m_torrentExportDirectory = path;
797 Path SessionImpl::finishedTorrentExportDirectory() const
799 return m_finishedTorrentExportDirectory;
802 void SessionImpl::setFinishedTorrentExportDirectory(const Path &path)
804 if (path != finishedTorrentExportDirectory())
805 m_finishedTorrentExportDirectory = path;
808 Path SessionImpl::savePath() const
810 // TODO: Make sure it is always non-empty
811 return m_savePath;
814 Path SessionImpl::downloadPath() const
816 // TODO: Make sure it is always non-empty
817 return m_downloadPath;
820 QStringList SessionImpl::categories() const
822 return m_categories.keys();
825 CategoryOptions SessionImpl::categoryOptions(const QString &categoryName) const
827 return m_categories.value(categoryName);
830 Path SessionImpl::categorySavePath(const QString &categoryName) const
832 return categorySavePath(categoryName, categoryOptions(categoryName));
835 Path SessionImpl::categorySavePath(const QString &categoryName, const CategoryOptions &options) const
837 Path basePath = savePath();
838 if (categoryName.isEmpty())
839 return basePath;
841 Path path = options.savePath;
842 if (path.isEmpty())
844 // use implicit save path
845 if (isSubcategoriesEnabled())
847 path = Utils::Fs::toValidPath(subcategoryName(categoryName));
848 basePath = categorySavePath(parentCategoryName(categoryName));
850 else
852 path = Utils::Fs::toValidPath(categoryName);
856 return (path.isAbsolute() ? path : (basePath / path));
859 Path SessionImpl::categoryDownloadPath(const QString &categoryName) const
861 return categoryDownloadPath(categoryName, categoryOptions(categoryName));
864 Path SessionImpl::categoryDownloadPath(const QString &categoryName, const CategoryOptions &options) const
866 const DownloadPathOption downloadPathOption = resolveCategoryDownloadPathOption(categoryName, options.downloadPath);
867 if (!downloadPathOption.enabled)
868 return {};
870 if (categoryName.isEmpty())
871 return downloadPath();
873 const bool useSubcategories = isSubcategoriesEnabled();
874 const QString name = useSubcategories ? subcategoryName(categoryName) : categoryName;
875 const Path path = !downloadPathOption.path.isEmpty()
876 ? downloadPathOption.path
877 : Utils::Fs::toValidPath(name); // use implicit download path
879 if (path.isAbsolute())
880 return path;
882 const QString parentName = useSubcategories ? parentCategoryName(categoryName) : QString();
883 CategoryOptions parentOptions = categoryOptions(parentName);
884 // Even if download path of parent category is disabled (directly or by inheritance)
885 // we need to construct the one as if it would be enabled.
886 if (!parentOptions.downloadPath || !parentOptions.downloadPath->enabled)
887 parentOptions.downloadPath = {true, {}};
888 const Path parentDownloadPath = categoryDownloadPath(parentName, parentOptions);
889 const Path basePath = parentDownloadPath.isEmpty() ? downloadPath() : parentDownloadPath;
890 return (basePath / path);
893 DownloadPathOption SessionImpl::resolveCategoryDownloadPathOption(const QString &categoryName, const std::optional<DownloadPathOption> &option) const
895 if (categoryName.isEmpty())
896 return {isDownloadPathEnabled(), Path()};
898 if (option.has_value())
899 return *option;
901 const QString parentName = isSubcategoriesEnabled() ? parentCategoryName(categoryName) : QString();
902 return resolveCategoryDownloadPathOption(parentName, categoryOptions(parentName).downloadPath);
905 bool SessionImpl::addCategory(const QString &name, const CategoryOptions &options)
907 if (name.isEmpty())
908 return false;
910 if (!isValidCategoryName(name) || m_categories.contains(name))
911 return false;
913 if (isSubcategoriesEnabled())
915 for (const QString &parent : asConst(expandCategory(name)))
917 if ((parent != name) && !m_categories.contains(parent))
919 m_categories[parent] = {};
920 emit categoryAdded(parent);
925 m_categories[name] = options;
926 storeCategories();
927 emit categoryAdded(name);
929 return true;
932 bool SessionImpl::editCategory(const QString &name, const CategoryOptions &options)
934 const auto it = m_categories.find(name);
935 if (it == m_categories.end())
936 return false;
938 CategoryOptions &currentOptions = it.value();
939 if (options == currentOptions)
940 return false;
942 currentOptions = options;
943 storeCategories();
944 if (isDisableAutoTMMWhenCategorySavePathChanged())
946 for (TorrentImpl *const torrent : asConst(m_torrents))
948 if (torrent->category() == name)
949 torrent->setAutoTMMEnabled(false);
952 else
954 for (TorrentImpl *const torrent : asConst(m_torrents))
956 if (torrent->category() == name)
957 torrent->handleCategoryOptionsChanged();
961 emit categoryOptionsChanged(name);
962 return true;
965 bool SessionImpl::removeCategory(const QString &name)
967 for (TorrentImpl *const torrent : asConst(m_torrents))
969 if (torrent->belongsToCategory(name))
970 torrent->setCategory(u""_s);
973 // remove stored category and its subcategories if exist
974 bool result = false;
975 if (isSubcategoriesEnabled())
977 // remove subcategories
978 const QString test = name + u'/';
979 Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const CategoryOptions &)
981 if (category.startsWith(test))
983 result = true;
984 emit categoryRemoved(category);
985 return true;
987 return false;
991 result = (m_categories.remove(name) > 0) || result;
993 if (result)
995 // update stored categories
996 storeCategories();
997 emit categoryRemoved(name);
1000 return result;
1003 bool SessionImpl::isSubcategoriesEnabled() const
1005 return m_isSubcategoriesEnabled;
1008 void SessionImpl::setSubcategoriesEnabled(const bool value)
1010 if (isSubcategoriesEnabled() == value) return;
1012 if (value)
1014 // expand categories to include all parent categories
1015 m_categories = expandCategories(m_categories);
1016 // update stored categories
1017 storeCategories();
1019 else
1021 // reload categories
1022 loadCategories();
1025 m_isSubcategoriesEnabled = value;
1026 emit subcategoriesSupportChanged();
1029 bool SessionImpl::useCategoryPathsInManualMode() const
1031 return m_useCategoryPathsInManualMode;
1034 void SessionImpl::setUseCategoryPathsInManualMode(const bool value)
1036 m_useCategoryPathsInManualMode = value;
1039 Path SessionImpl::suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const
1041 const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
1042 const auto path = (useCategoryPaths ? categorySavePath(categoryName) : savePath());
1043 return path;
1046 Path SessionImpl::suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const
1048 const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
1049 const auto categoryDownloadPath = this->categoryDownloadPath(categoryName);
1050 const auto path = ((useCategoryPaths && !categoryDownloadPath.isEmpty()) ? categoryDownloadPath : downloadPath());
1051 return path;
1054 TagSet SessionImpl::tags() const
1056 return m_tags;
1059 bool SessionImpl::hasTag(const Tag &tag) const
1061 return m_tags.contains(tag);
1064 bool SessionImpl::addTag(const Tag &tag)
1066 if (!tag.isValid() || hasTag(tag))
1067 return false;
1069 m_tags.insert(tag);
1070 m_storedTags = QStringList(m_tags.cbegin(), m_tags.cend());
1072 emit tagAdded(tag);
1073 return true;
1076 bool SessionImpl::removeTag(const Tag &tag)
1078 if (m_tags.remove(tag))
1080 for (TorrentImpl *const torrent : asConst(m_torrents))
1081 torrent->removeTag(tag);
1083 m_storedTags = QStringList(m_tags.cbegin(), m_tags.cend());
1085 emit tagRemoved(tag);
1086 return true;
1088 return false;
1091 bool SessionImpl::isAutoTMMDisabledByDefault() const
1093 return m_isAutoTMMDisabledByDefault;
1096 void SessionImpl::setAutoTMMDisabledByDefault(const bool value)
1098 m_isAutoTMMDisabledByDefault = value;
1101 bool SessionImpl::isDisableAutoTMMWhenCategoryChanged() const
1103 return m_isDisableAutoTMMWhenCategoryChanged;
1106 void SessionImpl::setDisableAutoTMMWhenCategoryChanged(const bool value)
1108 m_isDisableAutoTMMWhenCategoryChanged = value;
1111 bool SessionImpl::isDisableAutoTMMWhenDefaultSavePathChanged() const
1113 return m_isDisableAutoTMMWhenDefaultSavePathChanged;
1116 void SessionImpl::setDisableAutoTMMWhenDefaultSavePathChanged(const bool value)
1118 m_isDisableAutoTMMWhenDefaultSavePathChanged = value;
1121 bool SessionImpl::isDisableAutoTMMWhenCategorySavePathChanged() const
1123 return m_isDisableAutoTMMWhenCategorySavePathChanged;
1126 void SessionImpl::setDisableAutoTMMWhenCategorySavePathChanged(const bool value)
1128 m_isDisableAutoTMMWhenCategorySavePathChanged = value;
1131 bool SessionImpl::isAddTorrentToQueueTop() const
1133 return m_isAddTorrentToQueueTop;
1136 void SessionImpl::setAddTorrentToQueueTop(bool value)
1138 m_isAddTorrentToQueueTop = value;
1141 bool SessionImpl::isAddTorrentStopped() const
1143 return m_isAddTorrentStopped;
1146 void SessionImpl::setAddTorrentStopped(const bool value)
1148 m_isAddTorrentStopped = value;
1151 Torrent::StopCondition SessionImpl::torrentStopCondition() const
1153 return m_torrentStopCondition;
1156 void SessionImpl::setTorrentStopCondition(const Torrent::StopCondition stopCondition)
1158 m_torrentStopCondition = stopCondition;
1161 bool SessionImpl::isTrackerEnabled() const
1163 return m_isTrackerEnabled;
1166 void SessionImpl::setTrackerEnabled(const bool enabled)
1168 if (m_isTrackerEnabled != enabled)
1169 m_isTrackerEnabled = enabled;
1171 // call enableTracker() unconditionally, otherwise port change won't trigger
1172 // tracker restart
1173 enableTracker(enabled);
1176 qreal SessionImpl::globalMaxRatio() const
1178 return m_globalMaxRatio;
1181 // Torrents with a ratio superior to the given value will
1182 // be automatically deleted
1183 void SessionImpl::setGlobalMaxRatio(qreal ratio)
1185 if (ratio < 0)
1186 ratio = -1.;
1188 if (ratio != globalMaxRatio())
1190 m_globalMaxRatio = ratio;
1191 updateSeedingLimitTimer();
1195 int SessionImpl::globalMaxSeedingMinutes() const
1197 return m_globalMaxSeedingMinutes;
1200 void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
1202 if (minutes < 0)
1203 minutes = -1;
1205 if (minutes != globalMaxSeedingMinutes())
1207 m_globalMaxSeedingMinutes = minutes;
1208 updateSeedingLimitTimer();
1212 int SessionImpl::globalMaxInactiveSeedingMinutes() const
1214 return m_globalMaxInactiveSeedingMinutes;
1217 void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
1219 minutes = std::max(minutes, -1);
1221 if (minutes != globalMaxInactiveSeedingMinutes())
1223 m_globalMaxInactiveSeedingMinutes = minutes;
1224 updateSeedingLimitTimer();
1228 void SessionImpl::applyBandwidthLimits()
1230 lt::settings_pack settingsPack;
1231 settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
1232 settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
1233 m_nativeSession->apply_settings(std::move(settingsPack));
1236 void SessionImpl::configure()
1238 m_nativeSession->apply_settings(loadLTSettings());
1239 configureComponents();
1241 m_deferredConfigureScheduled = false;
1244 void SessionImpl::configureComponents()
1246 // This function contains components/actions that:
1247 // 1. Need to be setup at start up
1248 // 2. When deferred configure is called
1250 configurePeerClasses();
1252 if (!m_IPFilteringConfigured)
1254 if (isIPFilteringEnabled())
1255 enableIPFilter();
1256 else
1257 disableIPFilter();
1258 m_IPFilteringConfigured = true;
1262 void SessionImpl::prepareStartup()
1264 qDebug("Initializing torrents resume data storage...");
1266 const Path dbPath = specialFolderLocation(SpecialFolder::Data) / Path(u"torrents.db"_s);
1267 const bool dbStorageExists = dbPath.exists();
1269 auto *context = new ResumeSessionContext(this);
1270 context->currentStorageType = resumeDataStorageType();
1272 if (context->currentStorageType == ResumeDataStorageType::SQLite)
1274 m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
1276 if (!dbStorageExists)
1278 const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_s);
1279 context->startupStorage = new BencodeResumeDataStorage(dataPath, this);
1282 else
1284 const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_s);
1285 m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
1287 if (dbStorageExists)
1288 context->startupStorage = new DBResumeDataStorage(dbPath, this);
1291 if (!context->startupStorage)
1292 context->startupStorage = m_resumeDataStorage;
1294 connect(context->startupStorage, &ResumeDataStorage::loadStarted, context
1295 , [this, context](const QVector<TorrentID> &torrents)
1297 context->totalResumeDataCount = torrents.size();
1298 #ifdef QBT_USES_LIBTORRENT2
1299 context->indexedTorrents = QSet<TorrentID>(torrents.cbegin(), torrents.cend());
1300 #endif
1302 handleLoadedResumeData(context);
1305 connect(context->startupStorage, &ResumeDataStorage::loadFinished, context, [context]()
1307 context->isLoadFinished = true;
1310 connect(this, &SessionImpl::addTorrentAlertsReceived, context, [this, context](const qsizetype alertsCount)
1312 context->processingResumeDataCount -= alertsCount;
1313 context->finishedResumeDataCount += alertsCount;
1314 if (!context->isLoadedResumeDataHandlingEnqueued)
1316 QMetaObject::invokeMethod(this, [this, context] { handleLoadedResumeData(context); }, Qt::QueuedConnection);
1317 context->isLoadedResumeDataHandlingEnqueued = true;
1320 if (!m_refreshEnqueued)
1322 m_nativeSession->post_torrent_updates();
1323 m_refreshEnqueued = true;
1326 emit startupProgressUpdated((context->finishedResumeDataCount * 100.) / context->totalResumeDataCount);
1329 context->startupStorage->loadAll();
1332 void SessionImpl::handleLoadedResumeData(ResumeSessionContext *context)
1334 context->isLoadedResumeDataHandlingEnqueued = false;
1336 int count = context->processingResumeDataCount;
1337 while (context->processingResumeDataCount < MAX_PROCESSING_RESUMEDATA_COUNT)
1339 if (context->loadedResumeData.isEmpty())
1340 context->loadedResumeData = context->startupStorage->fetchLoadedResumeData();
1342 if (context->loadedResumeData.isEmpty())
1344 if (context->processingResumeDataCount == 0)
1346 if (context->isLoadFinished)
1348 endStartup(context);
1350 else if (!context->isLoadedResumeDataHandlingEnqueued)
1352 QMetaObject::invokeMethod(this, [this, context]() { handleLoadedResumeData(context); }, Qt::QueuedConnection);
1353 context->isLoadedResumeDataHandlingEnqueued = true;
1357 break;
1360 processNextResumeData(context);
1361 ++count;
1364 context->finishedResumeDataCount += (count - context->processingResumeDataCount);
1367 void SessionImpl::processNextResumeData(ResumeSessionContext *context)
1369 const LoadedResumeData loadedResumeDataItem = context->loadedResumeData.takeFirst();
1371 TorrentID torrentID = loadedResumeDataItem.torrentID;
1372 #ifdef QBT_USES_LIBTORRENT2
1373 if (context->skippedIDs.contains(torrentID))
1374 return;
1375 #endif
1377 const nonstd::expected<LoadTorrentParams, QString> &loadResumeDataResult = loadedResumeDataItem.result;
1378 if (!loadResumeDataResult)
1380 LogMsg(tr("Failed to resume torrent. Torrent: \"%1\". Reason: \"%2\"")
1381 .arg(torrentID.toString(), loadResumeDataResult.error()), Log::CRITICAL);
1382 return;
1385 LoadTorrentParams resumeData = *loadResumeDataResult;
1386 bool needStore = false;
1388 #ifdef QBT_USES_LIBTORRENT2
1389 const InfoHash infoHash {(resumeData.ltAddTorrentParams.ti
1390 ? resumeData.ltAddTorrentParams.ti->info_hashes()
1391 : resumeData.ltAddTorrentParams.info_hashes)};
1392 const bool isHybrid = infoHash.isHybrid();
1393 const auto torrentIDv2 = TorrentID::fromInfoHash(infoHash);
1394 const auto torrentIDv1 = TorrentID::fromSHA1Hash(infoHash.v1());
1395 if (torrentID == torrentIDv2)
1397 if (isHybrid && context->indexedTorrents.contains(torrentIDv1))
1399 // if we don't have metadata, try to find it in alternative "resume data"
1400 if (!resumeData.ltAddTorrentParams.ti)
1402 const nonstd::expected<LoadTorrentParams, QString> loadAltResumeDataResult = context->startupStorage->load(torrentIDv1);
1403 if (loadAltResumeDataResult)
1404 resumeData.ltAddTorrentParams.ti = loadAltResumeDataResult->ltAddTorrentParams.ti;
1407 // remove alternative "resume data" and skip the attempt to load it
1408 m_resumeDataStorage->remove(torrentIDv1);
1409 context->skippedIDs.insert(torrentIDv1);
1412 else if (torrentID == torrentIDv1)
1414 torrentID = torrentIDv2;
1415 needStore = true;
1416 m_resumeDataStorage->remove(torrentIDv1);
1418 if (context->indexedTorrents.contains(torrentID))
1420 context->skippedIDs.insert(torrentID);
1422 const nonstd::expected<LoadTorrentParams, QString> loadPreferredResumeDataResult = context->startupStorage->load(torrentID);
1423 if (loadPreferredResumeDataResult)
1425 std::shared_ptr<lt::torrent_info> ti = resumeData.ltAddTorrentParams.ti;
1426 resumeData = *loadPreferredResumeDataResult;
1427 if (!resumeData.ltAddTorrentParams.ti)
1428 resumeData.ltAddTorrentParams.ti = std::move(ti);
1432 else
1434 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1435 .arg(torrentID.toString()), Log::WARNING);
1436 return;
1438 #else
1439 const lt::sha1_hash infoHash = (resumeData.ltAddTorrentParams.ti
1440 ? resumeData.ltAddTorrentParams.ti->info_hash()
1441 : resumeData.ltAddTorrentParams.info_hash);
1442 if (torrentID != TorrentID::fromInfoHash(infoHash))
1444 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1445 .arg(torrentID.toString()), Log::WARNING);
1446 return;
1448 #endif
1450 if (m_resumeDataStorage != context->startupStorage)
1451 needStore = true;
1453 // TODO: Remove the following upgrade code in v4.6
1454 // == BEGIN UPGRADE CODE ==
1455 if (!needStore)
1457 if (m_needUpgradeDownloadPath && isDownloadPathEnabled() && !resumeData.useAutoTMM)
1459 resumeData.downloadPath = downloadPath();
1460 needStore = true;
1463 // == END UPGRADE CODE ==
1465 if (needStore)
1466 m_resumeDataStorage->store(torrentID, resumeData);
1468 const QString category = resumeData.category;
1469 bool isCategoryRecovered = context->recoveredCategories.contains(category);
1470 if (!category.isEmpty() && (isCategoryRecovered || !m_categories.contains(category)))
1472 if (!isCategoryRecovered)
1474 if (addCategory(category))
1476 context->recoveredCategories.insert(category);
1477 isCategoryRecovered = true;
1478 LogMsg(tr("Detected inconsistent data: category is missing from the configuration file."
1479 " Category will be recovered but its settings will be reset to default."
1480 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
1482 else
1484 resumeData.category.clear();
1485 LogMsg(tr("Detected inconsistent data: invalid category. Torrent: \"%1\". Category: \"%2\"")
1486 .arg(torrentID.toString(), category), Log::WARNING);
1490 // We should check isCategoryRecovered again since the category
1491 // can be just recovered by the code above
1492 if (isCategoryRecovered && resumeData.useAutoTMM)
1494 const Path storageLocation {resumeData.ltAddTorrentParams.save_path};
1495 if ((storageLocation != categorySavePath(resumeData.category)) && (storageLocation != categoryDownloadPath(resumeData.category)))
1497 resumeData.useAutoTMM = false;
1498 resumeData.savePath = storageLocation;
1499 resumeData.downloadPath = {};
1500 LogMsg(tr("Detected mismatch between the save paths of the recovered category and the current save path of the torrent."
1501 " Torrent is now switched to Manual mode."
1502 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
1507 std::erase_if(resumeData.tags, [this, &torrentID](const Tag &tag)
1509 if (hasTag(tag))
1510 return false;
1512 if (addTag(tag))
1514 LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file."
1515 " Tag will be recovered."
1516 " Torrent: \"%1\". Tag: \"%2\"").arg(torrentID.toString(), tag.toString()), Log::WARNING);
1517 return false;
1520 LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"")
1521 .arg(torrentID.toString(), tag.toString()), Log::WARNING);
1522 return true;
1525 resumeData.ltAddTorrentParams.userdata = LTClientData(new ExtensionData);
1526 #ifndef QBT_USES_LIBTORRENT2
1527 resumeData.ltAddTorrentParams.storage = customStorageConstructor;
1528 #endif
1530 qDebug() << "Starting up torrent" << torrentID.toString() << "...";
1531 m_loadingTorrents.insert(torrentID, resumeData);
1532 #ifdef QBT_USES_LIBTORRENT2
1533 if (infoHash.isHybrid())
1535 // this allows to know the being added hybrid torrent by its v1 info hash
1536 // without having yet another mapping table
1537 m_hybridTorrentsByAltID.insert(torrentIDv1, nullptr);
1539 #endif
1540 m_nativeSession->async_add_torrent(resumeData.ltAddTorrentParams);
1541 ++context->processingResumeDataCount;
1544 void SessionImpl::endStartup(ResumeSessionContext *context)
1546 if (m_resumeDataStorage != context->startupStorage)
1548 if (isQueueingSystemEnabled())
1549 saveTorrentsQueue();
1551 const Path dbPath = context->startupStorage->path();
1552 context->startupStorage->deleteLater();
1554 if (context->currentStorageType == ResumeDataStorageType::Legacy)
1556 connect(context->startupStorage, &QObject::destroyed, [dbPath]
1558 Utils::Fs::removeFile(dbPath);
1563 context->deleteLater();
1564 connect(context, &QObject::destroyed, this, [this]
1566 if (!m_isPaused)
1567 m_nativeSession->resume();
1569 if (m_refreshEnqueued)
1570 m_refreshEnqueued = false;
1571 else
1572 enqueueRefresh();
1574 m_statisticsLastUpdateTimer.start();
1576 // Regular saving of fastresume data
1577 connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
1578 const int saveInterval = saveResumeDataInterval();
1579 if (saveInterval > 0)
1581 m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
1582 m_resumeDataTimer->start();
1585 m_wakeupCheckTimer = new QTimer(this);
1586 connect(m_wakeupCheckTimer, &QTimer::timeout, this, [this]
1588 const auto now = QDateTime::currentDateTime();
1589 if (m_wakeupCheckTimestamp.secsTo(now) > 100)
1591 LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
1592 reannounceToAllTrackers();
1595 m_wakeupCheckTimestamp = QDateTime::currentDateTime();
1597 m_wakeupCheckTimestamp = QDateTime::currentDateTime();
1598 m_wakeupCheckTimer->start(30s);
1600 m_isRestored = true;
1601 emit startupProgressUpdated(100);
1602 emit restored();
1606 void SessionImpl::initializeNativeSession()
1608 lt::settings_pack pack = loadLTSettings();
1610 const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
1611 pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
1613 pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
1614 pack.set_str(lt::settings_pack::user_agent, USER_AGENT.toStdString());
1615 pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
1616 // Speed up exit
1617 pack.set_int(lt::settings_pack::auto_scrape_interval, 1200); // 20 minutes
1618 pack.set_int(lt::settings_pack::auto_scrape_min_interval, 900); // 15 minutes
1619 // libtorrent 1.1 enables UPnP & NAT-PMP by default
1620 // turn them off before `lt::session` ctor to avoid split second effects
1621 pack.set_bool(lt::settings_pack::enable_upnp, false);
1622 pack.set_bool(lt::settings_pack::enable_natpmp, false);
1624 #ifdef QBT_USES_LIBTORRENT2
1625 // preserve the same behavior as in earlier libtorrent versions
1626 pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
1627 #endif
1629 lt::session_params sessionParams {std::move(pack), {}};
1630 #ifdef QBT_USES_LIBTORRENT2
1631 switch (diskIOType())
1633 case DiskIOType::Posix:
1634 sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
1635 break;
1636 case DiskIOType::MMap:
1637 sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
1638 break;
1639 default:
1640 sessionParams.disk_io_constructor = customDiskIOConstructor;
1641 break;
1643 #endif
1645 #if LIBTORRENT_VERSION_NUM < 20100
1646 m_nativeSession = new lt::session(sessionParams, lt::session::paused);
1647 #else
1648 m_nativeSession = new lt::session(sessionParams);
1649 m_nativeSession->pause();
1650 #endif
1652 LogMsg(tr("Peer ID: \"%1\"").arg(QString::fromStdString(peerId)), Log::INFO);
1653 LogMsg(tr("HTTP User-Agent: \"%1\"").arg(USER_AGENT), Log::INFO);
1654 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1655 LogMsg(tr("Local Peer Discovery support: %1").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1656 LogMsg(tr("Peer Exchange (PeX) support: %1").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1657 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1658 LogMsg(tr("Encryption support: %1").arg((encryption() == 0) ? tr("ON") : ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO);
1660 m_nativeSession->set_alert_notify([this]()
1662 QMetaObject::invokeMethod(this, &SessionImpl::readAlerts, Qt::QueuedConnection);
1665 // Enabling plugins
1666 m_nativeSession->add_extension(&lt::create_smart_ban_plugin);
1667 m_nativeSession->add_extension(&lt::create_ut_metadata_plugin);
1668 if (isPeXEnabled())
1669 m_nativeSession->add_extension(&lt::create_ut_pex_plugin);
1671 auto nativeSessionExtension = std::make_shared<NativeSessionExtension>();
1672 m_nativeSession->add_extension(nativeSessionExtension);
1673 m_nativeSessionExtension = nativeSessionExtension.get();
1676 void SessionImpl::processBannedIPs(lt::ip_filter &filter)
1678 // First, import current filter
1679 for (const QString &ip : asConst(m_bannedIPs.get()))
1681 lt::error_code ec;
1682 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1683 Q_ASSERT(!ec);
1684 if (!ec)
1685 filter.add_rule(addr, addr, lt::ip_filter::blocked);
1689 void SessionImpl::initMetrics()
1691 const auto findMetricIndex = [](const char *name) -> int
1693 const int index = lt::find_metric_idx(name);
1694 Q_ASSERT(index >= 0);
1695 return index;
1698 m_metricIndices =
1700 .net =
1702 .hasIncomingConnections = findMetricIndex("net.has_incoming_connections"),
1703 .sentPayloadBytes = findMetricIndex("net.sent_payload_bytes"),
1704 .recvPayloadBytes = findMetricIndex("net.recv_payload_bytes"),
1705 .sentBytes = findMetricIndex("net.sent_bytes"),
1706 .recvBytes = findMetricIndex("net.recv_bytes"),
1707 .sentIPOverheadBytes = findMetricIndex("net.sent_ip_overhead_bytes"),
1708 .recvIPOverheadBytes = findMetricIndex("net.recv_ip_overhead_bytes"),
1709 .sentTrackerBytes = findMetricIndex("net.sent_tracker_bytes"),
1710 .recvTrackerBytes = findMetricIndex("net.recv_tracker_bytes"),
1711 .recvRedundantBytes = findMetricIndex("net.recv_redundant_bytes"),
1712 .recvFailedBytes = findMetricIndex("net.recv_failed_bytes")
1714 .peer =
1716 .numPeersConnected = findMetricIndex("peer.num_peers_connected"),
1717 .numPeersUpDisk = findMetricIndex("peer.num_peers_up_disk"),
1718 .numPeersDownDisk = findMetricIndex("peer.num_peers_down_disk")
1720 .dht =
1722 .dhtBytesIn = findMetricIndex("dht.dht_bytes_in"),
1723 .dhtBytesOut = findMetricIndex("dht.dht_bytes_out"),
1724 .dhtNodes = findMetricIndex("dht.dht_nodes")
1726 .disk =
1728 .diskBlocksInUse = findMetricIndex("disk.disk_blocks_in_use"),
1729 .numBlocksRead = findMetricIndex("disk.num_blocks_read"),
1730 #ifndef QBT_USES_LIBTORRENT2
1731 .numBlocksCacheHits = findMetricIndex("disk.num_blocks_cache_hits"),
1732 #endif
1733 .writeJobs = findMetricIndex("disk.num_write_ops"),
1734 .readJobs = findMetricIndex("disk.num_read_ops"),
1735 .hashJobs = findMetricIndex("disk.num_blocks_hashed"),
1736 .queuedDiskJobs = findMetricIndex("disk.queued_disk_jobs"),
1737 .diskJobTime = findMetricIndex("disk.disk_job_time")
1742 lt::settings_pack SessionImpl::loadLTSettings() const
1744 lt::settings_pack settingsPack;
1746 const lt::alert_category_t alertMask = lt::alert::error_notification
1747 | lt::alert::file_progress_notification
1748 | lt::alert::ip_block_notification
1749 | lt::alert::peer_notification
1750 | (isPerformanceWarningEnabled() ? lt::alert::performance_warning : lt::alert_category_t())
1751 | lt::alert::port_mapping_notification
1752 | lt::alert::status_notification
1753 | lt::alert::storage_notification
1754 | lt::alert::tracker_notification;
1755 settingsPack.set_int(lt::settings_pack::alert_mask, alertMask);
1757 settingsPack.set_int(lt::settings_pack::connection_speed, connectionSpeed());
1759 // from libtorrent doc:
1760 // It will not take affect until the listen_interfaces settings is updated
1761 settingsPack.set_int(lt::settings_pack::send_socket_buffer_size, socketSendBufferSize());
1762 settingsPack.set_int(lt::settings_pack::recv_socket_buffer_size, socketReceiveBufferSize());
1763 settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
1765 applyNetworkInterfacesSettings(settingsPack);
1767 settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
1768 settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
1770 // The most secure, rc4 only so that all streams are encrypted
1771 settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
1772 settingsPack.set_bool(lt::settings_pack::prefer_rc4, true);
1773 switch (encryption())
1775 case 0: // Enabled
1776 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_enabled);
1777 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_enabled);
1778 break;
1779 case 1: // Forced
1780 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_forced);
1781 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_forced);
1782 break;
1783 default: // Disabled
1784 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_disabled);
1785 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_disabled);
1788 settingsPack.set_int(lt::settings_pack::active_checking, maxActiveCheckingTorrents());
1790 // I2P
1791 #if defined(QBT_USES_LIBTORRENT2) && TORRENT_USE_I2P
1792 if (isI2PEnabled())
1794 settingsPack.set_str(lt::settings_pack::i2p_hostname, I2PAddress().toStdString());
1795 settingsPack.set_int(lt::settings_pack::i2p_port, I2PPort());
1796 settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, I2PMixedMode());
1798 else
1800 settingsPack.set_str(lt::settings_pack::i2p_hostname, "");
1801 settingsPack.set_int(lt::settings_pack::i2p_port, 0);
1802 settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, false);
1805 // I2P session options
1806 settingsPack.set_int(lt::settings_pack::i2p_inbound_quantity, I2PInboundQuantity());
1807 settingsPack.set_int(lt::settings_pack::i2p_outbound_quantity, I2POutboundQuantity());
1808 settingsPack.set_int(lt::settings_pack::i2p_inbound_length, I2PInboundLength());
1809 settingsPack.set_int(lt::settings_pack::i2p_outbound_length, I2POutboundLength());
1810 #endif
1812 // proxy
1813 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
1814 const auto *proxyManager = Net::ProxyConfigurationManager::instance();
1815 const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
1816 if ((proxyConfig.type != Net::ProxyType::None) && Preferences::instance()->useProxyForBT())
1818 switch (proxyConfig.type)
1820 case Net::ProxyType::SOCKS4:
1821 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks4);
1822 break;
1824 case Net::ProxyType::HTTP:
1825 if (proxyConfig.authEnabled)
1826 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http_pw);
1827 else
1828 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http);
1829 break;
1831 case Net::ProxyType::SOCKS5:
1832 if (proxyConfig.authEnabled)
1833 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5_pw);
1834 else
1835 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
1836 break;
1838 default:
1839 break;
1842 settingsPack.set_str(lt::settings_pack::proxy_hostname, proxyConfig.ip.toStdString());
1843 settingsPack.set_int(lt::settings_pack::proxy_port, proxyConfig.port);
1845 if (proxyConfig.authEnabled)
1847 settingsPack.set_str(lt::settings_pack::proxy_username, proxyConfig.username.toStdString());
1848 settingsPack.set_str(lt::settings_pack::proxy_password, proxyConfig.password.toStdString());
1851 settingsPack.set_bool(lt::settings_pack::proxy_peer_connections, isProxyPeerConnectionsEnabled());
1852 settingsPack.set_bool(lt::settings_pack::proxy_hostnames, proxyConfig.hostnameLookupEnabled);
1855 settingsPack.set_bool(lt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
1856 settingsPack.set_bool(lt::settings_pack::announce_to_all_tiers, announceToAllTiers());
1858 settingsPack.set_int(lt::settings_pack::peer_turnover, peerTurnover());
1859 settingsPack.set_int(lt::settings_pack::peer_turnover_cutoff, peerTurnoverCutoff());
1860 settingsPack.set_int(lt::settings_pack::peer_turnover_interval, peerTurnoverInterval());
1862 settingsPack.set_int(lt::settings_pack::max_out_request_queue, requestQueueSize());
1864 #ifdef QBT_USES_LIBTORRENT2
1865 settingsPack.set_int(lt::settings_pack::metadata_token_limit, Preferences::instance()->getBdecodeTokenLimit());
1866 #endif
1868 settingsPack.set_int(lt::settings_pack::aio_threads, asyncIOThreads());
1869 #ifdef QBT_USES_LIBTORRENT2
1870 settingsPack.set_int(lt::settings_pack::hashing_threads, hashingThreads());
1871 #endif
1872 settingsPack.set_int(lt::settings_pack::file_pool_size, filePoolSize());
1874 const int checkingMemUsageSize = checkingMemUsage() * 64;
1875 settingsPack.set_int(lt::settings_pack::checking_mem_usage, checkingMemUsageSize);
1877 #ifndef QBT_USES_LIBTORRENT2
1878 const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
1879 settingsPack.set_int(lt::settings_pack::cache_size, cacheSize);
1880 settingsPack.set_int(lt::settings_pack::cache_expiry, diskCacheTTL());
1881 #endif
1883 settingsPack.set_int(lt::settings_pack::max_queued_disk_bytes, diskQueueSize());
1885 switch (diskIOReadMode())
1887 case DiskIOReadMode::DisableOSCache:
1888 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::disable_os_cache);
1889 break;
1890 case DiskIOReadMode::EnableOSCache:
1891 default:
1892 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::enable_os_cache);
1893 break;
1896 switch (diskIOWriteMode())
1898 case DiskIOWriteMode::DisableOSCache:
1899 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::disable_os_cache);
1900 break;
1901 case DiskIOWriteMode::EnableOSCache:
1902 default:
1903 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::enable_os_cache);
1904 break;
1905 #ifdef QBT_USES_LIBTORRENT2
1906 case DiskIOWriteMode::WriteThrough:
1907 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::write_through);
1908 break;
1909 #endif
1912 #ifndef QBT_USES_LIBTORRENT2
1913 settingsPack.set_bool(lt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
1914 settingsPack.set_bool(lt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
1915 #endif
1917 settingsPack.set_bool(lt::settings_pack::piece_extent_affinity, usePieceExtentAffinity());
1919 settingsPack.set_int(lt::settings_pack::suggest_mode, isSuggestModeEnabled()
1920 ? lt::settings_pack::suggest_read_cache : lt::settings_pack::no_piece_suggestions);
1922 settingsPack.set_int(lt::settings_pack::send_buffer_watermark, sendBufferWatermark() * 1024);
1923 settingsPack.set_int(lt::settings_pack::send_buffer_low_watermark, sendBufferLowWatermark() * 1024);
1924 settingsPack.set_int(lt::settings_pack::send_buffer_watermark_factor, sendBufferWatermarkFactor());
1926 settingsPack.set_bool(lt::settings_pack::anonymous_mode, isAnonymousModeEnabled());
1928 // Queueing System
1929 if (isQueueingSystemEnabled())
1931 settingsPack.set_int(lt::settings_pack::active_downloads, maxActiveDownloads());
1932 settingsPack.set_int(lt::settings_pack::active_limit, maxActiveTorrents());
1933 settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
1934 settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
1935 settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
1936 settingsPack.set_int(lt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
1937 settingsPack.set_int(lt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
1939 else
1941 settingsPack.set_int(lt::settings_pack::active_downloads, -1);
1942 settingsPack.set_int(lt::settings_pack::active_seeds, -1);
1943 settingsPack.set_int(lt::settings_pack::active_limit, -1);
1945 settingsPack.set_int(lt::settings_pack::active_tracker_limit, -1);
1946 settingsPack.set_int(lt::settings_pack::active_dht_limit, -1);
1947 settingsPack.set_int(lt::settings_pack::active_lsd_limit, -1);
1948 settingsPack.set_int(lt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
1950 // Outgoing ports
1951 settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
1952 settingsPack.set_int(lt::settings_pack::num_outgoing_ports, (outgoingPortsMax() - outgoingPortsMin()));
1953 // UPnP lease duration
1954 settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
1955 // Type of service
1956 settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
1957 // Include overhead in transfer limits
1958 settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
1959 // IP address to announce to trackers
1960 settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
1961 // Max concurrent HTTP announces
1962 settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
1963 // Stop tracker timeout
1964 settingsPack.set_int(lt::settings_pack::stop_tracker_timeout, stopTrackerTimeout());
1965 // * Max connections limit
1966 settingsPack.set_int(lt::settings_pack::connections_limit, maxConnections());
1967 // * Global max upload slots
1968 settingsPack.set_int(lt::settings_pack::unchoke_slots_limit, maxUploads());
1969 // uTP
1970 switch (btProtocol())
1972 case BTProtocol::Both:
1973 default:
1974 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1975 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1976 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1977 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1978 break;
1980 case BTProtocol::TCP:
1981 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1982 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1983 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, false);
1984 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
1985 break;
1987 case BTProtocol::UTP:
1988 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, false);
1989 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, false);
1990 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1991 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1992 break;
1995 switch (utpMixedMode())
1997 case MixedModeAlgorithm::TCP:
1998 default:
1999 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::prefer_tcp);
2000 break;
2001 case MixedModeAlgorithm::Proportional:
2002 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::peer_proportional);
2003 break;
2006 settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
2008 settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
2010 settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
2012 settingsPack.set_bool(lt::settings_pack::ssrf_mitigation, isSSRFMitigationEnabled());
2014 settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
2016 settingsPack.set_bool(lt::settings_pack::apply_ip_filter_to_trackers, isTrackerFilteringEnabled());
2018 settingsPack.set_str(lt::settings_pack::dht_bootstrap_nodes, getDHTBootstrapNodes().toStdString());
2019 settingsPack.set_bool(lt::settings_pack::enable_dht, isDHTEnabled());
2020 settingsPack.set_bool(lt::settings_pack::enable_lsd, isLSDEnabled());
2022 switch (chokingAlgorithm())
2024 case ChokingAlgorithm::FixedSlots:
2025 default:
2026 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::fixed_slots_choker);
2027 break;
2028 case ChokingAlgorithm::RateBased:
2029 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::rate_based_choker);
2030 break;
2033 switch (seedChokingAlgorithm())
2035 case SeedChokingAlgorithm::RoundRobin:
2036 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::round_robin);
2037 break;
2038 case SeedChokingAlgorithm::FastestUpload:
2039 default:
2040 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::fastest_upload);
2041 break;
2042 case SeedChokingAlgorithm::AntiLeech:
2043 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
2044 break;
2047 return settingsPack;
2050 void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const
2052 if (m_listenInterfaceConfigured)
2053 return;
2055 if (port() > 0) // user has specified port number
2056 settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);
2058 QStringList endpoints;
2059 QStringList outgoingInterfaces;
2060 QStringList portStrings = {u':' + QString::number(port())};
2061 if (isSSLEnabled())
2062 portStrings.append(u':' + QString::number(sslPort()) + u's');
2064 for (const QString &ip : asConst(getListeningIPs()))
2066 const QHostAddress addr {ip};
2067 if (!addr.isNull())
2069 const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
2070 const QString ip = isIPv6
2071 ? Utils::Net::canonicalIPv6Addr(addr).toString()
2072 : addr.toString();
2074 for (const QString &portString : asConst(portStrings))
2075 endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString);
2077 if ((ip != u"0.0.0.0") && (ip != u"::"))
2078 outgoingInterfaces << ip;
2080 else
2082 // ip holds an interface name
2083 #ifdef Q_OS_WIN
2084 // On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
2085 // the interface's LUID and not the GUID.
2086 // Libtorrent expects GUIDs for the 'listen_interfaces' setting.
2087 const QString guid = convertIfaceNameToGuid(ip);
2088 if (!guid.isEmpty())
2090 for (const QString &portString : asConst(portStrings))
2091 endpoints << (guid + portString);
2092 outgoingInterfaces << guid;
2094 else
2096 LogMsg(tr("Could not find GUID of network interface. Interface: \"%1\"").arg(ip), Log::WARNING);
2097 // Since we can't get the GUID, we'll pass the interface name instead.
2098 // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
2099 for (const QString &portString : asConst(portStrings))
2100 endpoints << (ip + portString);
2101 outgoingInterfaces << ip;
2103 #else
2104 for (const QString &portString : asConst(portStrings))
2105 endpoints << (ip + portString);
2106 outgoingInterfaces << ip;
2107 #endif
2111 const QString finalEndpoints = endpoints.join(u',');
2112 settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
2113 LogMsg(tr("Trying to listen on the following list of IP addresses: \"%1\"").arg(finalEndpoints));
2115 settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(u',').toStdString());
2116 m_listenInterfaceConfigured = true;
2119 void SessionImpl::configurePeerClasses()
2121 lt::ip_filter f;
2122 // lt::make_address("255.255.255.255") crashes on some people's systems
2123 // so instead we use address_v4::broadcast()
2124 // Proactively do the same for 0.0.0.0 and address_v4::any()
2125 f.add_rule(lt::address_v4::any()
2126 , lt::address_v4::broadcast()
2127 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
2129 // IPv6 may not be available on OS and the parsing
2130 // would result in an exception -> abnormal program termination
2131 // Affects Windows XP
2134 f.add_rule(lt::address_v6::any()
2135 , lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2136 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
2138 catch (const std::exception &) {}
2140 if (ignoreLimitsOnLAN())
2142 // local networks
2143 f.add_rule(lt::make_address("10.0.0.0")
2144 , lt::make_address("10.255.255.255")
2145 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2146 f.add_rule(lt::make_address("172.16.0.0")
2147 , lt::make_address("172.31.255.255")
2148 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2149 f.add_rule(lt::make_address("192.168.0.0")
2150 , lt::make_address("192.168.255.255")
2151 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2152 // link local
2153 f.add_rule(lt::make_address("169.254.0.0")
2154 , lt::make_address("169.254.255.255")
2155 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2156 // loopback
2157 f.add_rule(lt::make_address("127.0.0.0")
2158 , lt::make_address("127.255.255.255")
2159 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2161 // IPv6 may not be available on OS and the parsing
2162 // would result in an exception -> abnormal program termination
2163 // Affects Windows XP
2166 // link local
2167 f.add_rule(lt::make_address("fe80::")
2168 , lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2169 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2170 // unique local addresses
2171 f.add_rule(lt::make_address("fc00::")
2172 , lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2173 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2174 // loopback
2175 f.add_rule(lt::address_v6::loopback()
2176 , lt::address_v6::loopback()
2177 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
2179 catch (const std::exception &) {}
2181 m_nativeSession->set_peer_class_filter(f);
2183 lt::peer_class_type_filter peerClassTypeFilter;
2184 peerClassTypeFilter.add(lt::peer_class_type_filter::tcp_socket, lt::session::tcp_peer_class_id);
2185 peerClassTypeFilter.add(lt::peer_class_type_filter::ssl_tcp_socket, lt::session::tcp_peer_class_id);
2186 peerClassTypeFilter.add(lt::peer_class_type_filter::i2p_socket, lt::session::tcp_peer_class_id);
2187 if (!isUTPRateLimited())
2189 peerClassTypeFilter.disallow(lt::peer_class_type_filter::utp_socket
2190 , lt::session::global_peer_class_id);
2191 peerClassTypeFilter.disallow(lt::peer_class_type_filter::ssl_utp_socket
2192 , lt::session::global_peer_class_id);
2194 m_nativeSession->set_peer_class_type_filter(peerClassTypeFilter);
2197 void SessionImpl::enableTracker(const bool enable)
2199 const QString profile = u"embeddedTracker"_s;
2200 auto *portForwarder = Net::PortForwarder::instance();
2202 if (enable)
2204 if (!m_tracker)
2205 m_tracker = new Tracker(this);
2207 m_tracker->start();
2209 const auto *pref = Preferences::instance();
2210 if (pref->isTrackerPortForwardingEnabled())
2211 portForwarder->setPorts(profile, {static_cast<quint16>(pref->getTrackerPort())});
2212 else
2213 portForwarder->removePorts(profile);
2215 else
2217 delete m_tracker;
2219 portForwarder->removePorts(profile);
2223 void SessionImpl::enableBandwidthScheduler()
2225 if (!m_bwScheduler)
2227 m_bwScheduler = new BandwidthScheduler(this);
2228 connect(m_bwScheduler.data(), &BandwidthScheduler::bandwidthLimitRequested
2229 , this, &SessionImpl::setAltGlobalSpeedLimitEnabled);
2231 m_bwScheduler->start();
2234 void SessionImpl::populateAdditionalTrackers()
2236 m_additionalTrackerEntries = parseTrackerEntries(additionalTrackers());
2239 void SessionImpl::processShareLimits()
2241 const auto effectiveLimit = []<typename T>(const T limit, const T useGlobalLimit, const T globalLimit) -> T
2243 return (limit == useGlobalLimit) ? globalLimit : limit;
2246 // We shouldn't iterate over `m_torrents` in the loop below
2247 // since `deleteTorrent()` modifies it indirectly
2248 const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
2249 for (const auto &[torrentID, torrent] : torrents.asKeyValueRange())
2251 if (!torrent->isFinished() || torrent->isForced())
2252 continue;
2254 const qreal ratioLimit = effectiveLimit(torrent->ratioLimit(), Torrent::USE_GLOBAL_RATIO, globalMaxRatio());
2255 const int seedingTimeLimit = effectiveLimit(torrent->seedingTimeLimit(), Torrent::USE_GLOBAL_SEEDING_TIME, globalMaxSeedingMinutes());
2256 const int inactiveSeedingTimeLimit = effectiveLimit(torrent->inactiveSeedingTimeLimit(), Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME, globalMaxInactiveSeedingMinutes());
2258 bool reached = false;
2259 QString description;
2261 if (const qreal ratio = torrent->realRatio();
2262 (ratioLimit >= 0) && (ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
2264 reached = true;
2265 description = tr("Torrent reached the share ratio limit.");
2267 else if (const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
2268 (seedingTimeLimit >= 0) && (seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
2270 reached = true;
2271 description = tr("Torrent reached the seeding time limit.");
2273 else if (const qlonglong inactiveSeedingTimeInMinutes = torrent->timeSinceActivity() / 60;
2274 (inactiveSeedingTimeLimit >= 0) && (inactiveSeedingTimeInMinutes <= Torrent::MAX_INACTIVE_SEEDING_TIME) && (inactiveSeedingTimeInMinutes >= inactiveSeedingTimeLimit))
2276 reached = true;
2277 description = tr("Torrent reached the inactive seeding time limit.");
2280 if (reached)
2282 const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
2283 const ShareLimitAction shareLimitAction = (torrent->shareLimitAction() == ShareLimitAction::Default) ? m_shareLimitAction : torrent->shareLimitAction();
2285 if (shareLimitAction == ShareLimitAction::Remove)
2287 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent."), torrentName));
2288 deleteTorrent(torrentID);
2290 else if (shareLimitAction == ShareLimitAction::RemoveWithContent)
2292 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent and deleting its content."), torrentName));
2293 deleteTorrent(torrentID, DeleteTorrentAndFiles);
2295 else if ((shareLimitAction == ShareLimitAction::Stop) && !torrent->isStopped())
2297 torrent->stop();
2298 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent stopped."), torrentName));
2300 else if ((shareLimitAction == ShareLimitAction::EnableSuperSeeding) && !torrent->isStopped() && !torrent->superSeeding())
2302 torrent->setSuperSeeding(true);
2303 LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
2309 void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
2311 TorrentImpl *torrent = m_torrents.value(id);
2312 if (torrent)
2314 torrent->fileSearchFinished(savePath, fileNames);
2315 return;
2318 const auto loadingTorrentsIter = m_loadingTorrents.find(id);
2319 if (loadingTorrentsIter != m_loadingTorrents.end())
2321 LoadTorrentParams &params = loadingTorrentsIter.value();
2322 lt::add_torrent_params &p = params.ltAddTorrentParams;
2324 p.save_path = savePath.toString().toStdString();
2325 const TorrentInfo torrentInfo {*p.ti};
2326 const auto nativeIndexes = torrentInfo.nativeIndexes();
2327 for (int i = 0; i < fileNames.size(); ++i)
2328 p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString();
2330 m_nativeSession->async_add_torrent(p);
2334 Torrent *SessionImpl::getTorrent(const TorrentID &id) const
2336 return m_torrents.value(id);
2339 Torrent *SessionImpl::findTorrent(const InfoHash &infoHash) const
2341 const auto id = TorrentID::fromInfoHash(infoHash);
2342 if (Torrent *torrent = m_torrents.value(id); torrent)
2343 return torrent;
2345 if (!infoHash.isHybrid())
2346 return m_hybridTorrentsByAltID.value(id);
2348 // alternative ID can be useful to find existing torrent
2349 // in case if hybrid torrent was added by v1 info hash
2350 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
2351 return m_torrents.value(altID);
2354 void SessionImpl::banIP(const QString &ip)
2356 if (m_bannedIPs.get().contains(ip))
2357 return;
2359 lt::error_code ec;
2360 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
2361 Q_ASSERT(!ec);
2362 if (ec)
2363 return;
2365 invokeAsync([session = m_nativeSession, addr]
2367 lt::ip_filter filter = session->get_ip_filter();
2368 filter.add_rule(addr, addr, lt::ip_filter::blocked);
2369 session->set_ip_filter(std::move(filter));
2372 QStringList bannedIPs = m_bannedIPs;
2373 bannedIPs.append(ip);
2374 bannedIPs.sort();
2375 m_bannedIPs = bannedIPs;
2378 // Delete a torrent from the session, given its hash
2379 // and from the disk, if the corresponding deleteOption is chosen
2380 bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
2382 TorrentImpl *const torrent = m_torrents.take(id);
2383 if (!torrent)
2384 return false;
2386 qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
2387 emit torrentAboutToBeRemoved(torrent);
2389 if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
2390 m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
2392 // Remove it from session
2393 if (deleteOption == DeleteTorrent)
2395 m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
2397 const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
2398 const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
2399 , [&nativeHandle](const MoveStorageJob &job)
2401 return job.torrentHandle == nativeHandle;
2403 if (iter != m_moveStorageQueue.end())
2405 // We shouldn't actually remove torrent until existing "move storage jobs" are done
2406 torrentQueuePositionBottom(nativeHandle);
2407 nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
2408 nativeHandle.pause();
2410 else
2412 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
2415 else
2417 m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
2419 if (m_moveStorageQueue.size() > 1)
2421 // Delete "move storage job" for the deleted torrent
2422 // (note: we shouldn't delete active job)
2423 const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
2424 , [torrent](const MoveStorageJob &job)
2426 return job.torrentHandle == torrent->nativeHandle();
2428 if (iter != m_moveStorageQueue.end())
2429 m_moveStorageQueue.erase(iter);
2432 m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
2435 // Remove it from torrent resume directory
2436 m_resumeDataStorage->remove(torrent->id());
2438 delete torrent;
2439 return true;
2442 bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
2444 const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
2445 if (downloadedMetadataIter == m_downloadedMetadata.end())
2446 return false;
2448 const lt::torrent_handle nativeHandle = downloadedMetadataIter.value();
2449 m_downloadedMetadata.erase(downloadedMetadataIter);
2451 if (!nativeHandle.is_valid())
2452 return true;
2454 #ifdef QBT_USES_LIBTORRENT2
2455 const InfoHash infoHash {nativeHandle.info_hashes()};
2456 if (infoHash.isHybrid())
2458 // if magnet link was hybrid initially then it is indexed also by v1 info hash
2459 // so we need to remove both entries
2460 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
2461 m_downloadedMetadata.remove(altID);
2463 #endif
2465 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
2466 return true;
2469 void SessionImpl::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
2471 using ElementType = std::pair<int, const TorrentImpl *>;
2472 std::priority_queue<ElementType
2473 , std::vector<ElementType>
2474 , std::greater<ElementType>> torrentQueue;
2476 // Sort torrents by queue position
2477 for (const TorrentID &id : ids)
2479 const TorrentImpl *torrent = m_torrents.value(id);
2480 if (!torrent) continue;
2481 if (const int position = torrent->queuePosition(); position >= 0)
2482 torrentQueue.emplace(position, torrent);
2485 // Increase torrents queue position (starting with the one in the highest queue position)
2486 while (!torrentQueue.empty())
2488 const TorrentImpl *torrent = torrentQueue.top().second;
2489 torrentQueuePositionUp(torrent->nativeHandle());
2490 torrentQueue.pop();
2493 m_torrentsQueueChanged = true;
2496 void SessionImpl::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
2498 using ElementType = std::pair<int, const TorrentImpl *>;
2499 std::priority_queue<ElementType> torrentQueue;
2501 // Sort torrents by queue position
2502 for (const TorrentID &id : ids)
2504 const TorrentImpl *torrent = m_torrents.value(id);
2505 if (!torrent) continue;
2506 if (const int position = torrent->queuePosition(); position >= 0)
2507 torrentQueue.emplace(position, torrent);
2510 // Decrease torrents queue position (starting with the one in the lowest queue position)
2511 while (!torrentQueue.empty())
2513 const TorrentImpl *torrent = torrentQueue.top().second;
2514 torrentQueuePositionDown(torrent->nativeHandle());
2515 torrentQueue.pop();
2518 for (const lt::torrent_handle &torrentHandle : asConst(m_downloadedMetadata))
2519 torrentQueuePositionBottom(torrentHandle);
2521 m_torrentsQueueChanged = true;
2524 void SessionImpl::topTorrentsQueuePos(const QVector<TorrentID> &ids)
2526 using ElementType = std::pair<int, const TorrentImpl *>;
2527 std::priority_queue<ElementType> torrentQueue;
2529 // Sort torrents by queue position
2530 for (const TorrentID &id : ids)
2532 const TorrentImpl *torrent = m_torrents.value(id);
2533 if (!torrent) continue;
2534 if (const int position = torrent->queuePosition(); position >= 0)
2535 torrentQueue.emplace(position, torrent);
2538 // Top torrents queue position (starting with the one in the lowest queue position)
2539 while (!torrentQueue.empty())
2541 const TorrentImpl *torrent = torrentQueue.top().second;
2542 torrentQueuePositionTop(torrent->nativeHandle());
2543 torrentQueue.pop();
2546 m_torrentsQueueChanged = true;
2549 void SessionImpl::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
2551 using ElementType = std::pair<int, const TorrentImpl *>;
2552 std::priority_queue<ElementType
2553 , std::vector<ElementType>
2554 , std::greater<ElementType>> torrentQueue;
2556 // Sort torrents by queue position
2557 for (const TorrentID &id : ids)
2559 const TorrentImpl *torrent = m_torrents.value(id);
2560 if (!torrent) continue;
2561 if (const int position = torrent->queuePosition(); position >= 0)
2562 torrentQueue.emplace(position, torrent);
2565 // Bottom torrents queue position (starting with the one in the highest queue position)
2566 while (!torrentQueue.empty())
2568 const TorrentImpl *torrent = torrentQueue.top().second;
2569 torrentQueuePositionBottom(torrent->nativeHandle());
2570 torrentQueue.pop();
2573 for (const lt::torrent_handle &torrentHandle : asConst(m_downloadedMetadata))
2574 torrentQueuePositionBottom(torrentHandle);
2576 m_torrentsQueueChanged = true;
2579 void SessionImpl::handleTorrentResumeDataRequested(const TorrentImpl *torrent)
2581 qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
2582 ++m_numResumeData;
2585 QVector<Torrent *> SessionImpl::torrents() const
2587 QVector<Torrent *> result;
2588 result.reserve(m_torrents.size());
2589 for (TorrentImpl *torrent : asConst(m_torrents))
2590 result << torrent;
2592 return result;
2595 qsizetype SessionImpl::torrentsCount() const
2597 return m_torrents.size();
2600 bool SessionImpl::addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams &params)
2602 if (!isRestored())
2603 return false;
2605 return addTorrent_impl(torrentDescr, params);
2608 LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &addTorrentParams)
2610 LoadTorrentParams loadTorrentParams;
2612 loadTorrentParams.name = addTorrentParams.name;
2613 loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
2614 loadTorrentParams.hasFinishedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
2615 loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
2616 loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged);
2617 loadTorrentParams.stopped = addTorrentParams.addStopped.value_or(isAddTorrentStopped());
2618 loadTorrentParams.stopCondition = addTorrentParams.stopCondition.value_or(torrentStopCondition());
2619 loadTorrentParams.addToQueueTop = addTorrentParams.addToQueueTop.value_or(isAddTorrentToQueueTop());
2620 loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
2621 loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
2622 loadTorrentParams.inactiveSeedingTimeLimit = addTorrentParams.inactiveSeedingTimeLimit;
2623 loadTorrentParams.shareLimitAction = addTorrentParams.shareLimitAction;
2624 loadTorrentParams.sslParameters = addTorrentParams.sslParameters;
2626 const QString category = addTorrentParams.category;
2627 if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
2628 loadTorrentParams.category = u""_s;
2629 else
2630 loadTorrentParams.category = category;
2632 const auto defaultSavePath = suggestedSavePath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
2633 const auto defaultDownloadPath = suggestedDownloadPath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
2635 loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(
2636 addTorrentParams.savePath.isEmpty() && addTorrentParams.downloadPath.isEmpty() && !isAutoTMMDisabledByDefault());
2638 if (!loadTorrentParams.useAutoTMM)
2640 if (addTorrentParams.savePath.isAbsolute())
2641 loadTorrentParams.savePath = addTorrentParams.savePath;
2642 else
2643 loadTorrentParams.savePath = defaultSavePath / addTorrentParams.savePath;
2645 // if useDownloadPath isn't specified but downloadPath is explicitly set we prefer to use it
2646 const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(!addTorrentParams.downloadPath.isEmpty() || isDownloadPathEnabled());
2647 if (useDownloadPath)
2649 // Overridden "Download path" settings
2651 if (addTorrentParams.downloadPath.isAbsolute())
2653 loadTorrentParams.downloadPath = addTorrentParams.downloadPath;
2655 else
2657 const Path basePath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath());
2658 loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath;
2663 for (const Tag &tag : addTorrentParams.tags)
2665 if (hasTag(tag) || addTag(tag))
2666 loadTorrentParams.tags.insert(tag);
2669 return loadTorrentParams;
2672 // Add a torrent to the BitTorrent session
2673 bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorrentParams &addTorrentParams)
2675 Q_ASSERT(isRestored());
2677 const bool hasMetadata = (source.info().has_value());
2678 const auto infoHash = source.infoHash();
2679 const auto id = TorrentID::fromInfoHash(infoHash);
2681 // alternative ID can be useful to find existing torrent in case if hybrid torrent was added by v1 info hash
2682 const auto altID = (infoHash.isHybrid() ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
2684 // We should not add the torrent if it is already
2685 // processed or is pending to add to session
2686 if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
2687 return false;
2689 if (findTorrent(infoHash))
2690 return false;
2692 // It looks illogical that we don't just use an existing handle,
2693 // but as previous experience has shown, it actually creates unnecessary
2694 // problems and unwanted behavior due to the fact that it was originally
2695 // added with parameters other than those provided by the user.
2696 cancelDownloadMetadata(id);
2697 if (infoHash.isHybrid())
2698 cancelDownloadMetadata(altID);
2700 LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams);
2701 lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
2702 p = source.ltAddTorrentParams();
2704 bool isFindingIncompleteFiles = false;
2706 const bool useAutoTMM = loadTorrentParams.useAutoTMM;
2707 const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
2709 if (hasMetadata)
2711 // Torrent that is being added with metadata is considered to be added as stopped
2712 // if "metadata received" stop condition is set for it.
2713 if (loadTorrentParams.stopCondition == Torrent::StopCondition::MetadataReceived)
2715 loadTorrentParams.stopped = true;
2716 loadTorrentParams.stopCondition = Torrent::StopCondition::None;
2719 const TorrentInfo &torrentInfo = *source.info();
2721 Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
2723 PathList filePaths = addTorrentParams.filePaths;
2724 if (filePaths.isEmpty())
2726 filePaths = torrentInfo.filePaths();
2727 if (loadTorrentParams.contentLayout != TorrentContentLayout::Original)
2729 const Path originalRootFolder = Path::findRootFolder(filePaths);
2730 const auto originalContentLayout = (originalRootFolder.isEmpty()
2731 ? TorrentContentLayout::NoSubfolder : TorrentContentLayout::Subfolder);
2732 if (loadTorrentParams.contentLayout != originalContentLayout)
2734 if (loadTorrentParams.contentLayout == TorrentContentLayout::NoSubfolder)
2735 Path::stripRootFolder(filePaths);
2736 else
2737 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2742 // if torrent name wasn't explicitly set we handle the case of
2743 // initial renaming of torrent content and rename torrent accordingly
2744 if (loadTorrentParams.name.isEmpty())
2746 QString contentName = Path::findRootFolder(filePaths).toString();
2747 if (contentName.isEmpty() && (filePaths.size() == 1))
2748 contentName = filePaths.at(0).filename();
2750 if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
2751 loadTorrentParams.name = contentName;
2754 if (!loadTorrentParams.hasFinishedStatus)
2756 const Path actualDownloadPath = useAutoTMM
2757 ? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
2758 findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
2759 isFindingIncompleteFiles = true;
2762 const auto nativeIndexes = torrentInfo.nativeIndexes();
2763 if (!isFindingIncompleteFiles)
2765 for (int index = 0; index < filePaths.size(); ++index)
2766 p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
2769 Q_ASSERT(p.file_priorities.empty());
2770 Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
2772 QList<DownloadPriority> filePriorities = addTorrentParams.filePriorities;
2774 if (filePriorities.isEmpty() && isExcludedFileNamesEnabled())
2776 // Check file name blacklist when priorities are not explicitly set
2777 applyFilenameFilter(filePaths, filePriorities);
2780 const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2781 // Use qBittorrent default priority rather than libtorrent's (4)
2782 p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2784 if (!filePriorities.isEmpty())
2786 for (int i = 0; i < filePriorities.size(); ++i)
2787 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(filePriorities[i]);
2790 Q_ASSERT(p.ti);
2792 else
2794 if (loadTorrentParams.name.isEmpty() && !p.name.empty())
2795 loadTorrentParams.name = QString::fromStdString(p.name);
2798 p.save_path = actualSavePath.toString().toStdString();
2800 if (isAddTrackersEnabled() && !(hasMetadata && p.ti->priv()))
2802 const auto maxTierIter = std::max_element(p.tracker_tiers.cbegin(), p.tracker_tiers.cend());
2803 const int baseTier = (maxTierIter != p.tracker_tiers.cend()) ? (*maxTierIter + 1) : 0;
2805 p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerEntries.size()));
2806 p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerEntries.size()));
2807 p.tracker_tiers.resize(p.trackers.size(), 0);
2808 for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerEntries))
2810 p.trackers.emplace_back(trackerEntry.url.toStdString());
2811 p.tracker_tiers.emplace_back(Utils::Number::clampingAdd(trackerEntry.tier, baseTier));
2815 p.upload_limit = addTorrentParams.uploadLimit;
2816 p.download_limit = addTorrentParams.downloadLimit;
2818 // Preallocation mode
2819 p.storage_mode = isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse;
2821 if (addTorrentParams.sequential)
2822 p.flags |= lt::torrent_flags::sequential_download;
2823 else
2824 p.flags &= ~lt::torrent_flags::sequential_download;
2826 // Seeding mode
2827 // Skip checking and directly start seeding
2828 if (addTorrentParams.skipChecking)
2829 p.flags |= lt::torrent_flags::seed_mode;
2830 else
2831 p.flags &= ~lt::torrent_flags::seed_mode;
2833 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::AutoManaged))
2834 p.flags |= lt::torrent_flags::paused;
2835 else
2836 p.flags &= ~lt::torrent_flags::paused;
2837 if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::Forced))
2838 p.flags &= ~lt::torrent_flags::auto_managed;
2839 else
2840 p.flags |= lt::torrent_flags::auto_managed;
2842 p.flags |= lt::torrent_flags::duplicate_is_error;
2844 p.added_time = std::time(nullptr);
2846 // Limits
2847 p.max_connections = maxConnectionsPerTorrent();
2848 p.max_uploads = maxUploadsPerTorrent();
2850 p.userdata = LTClientData(new ExtensionData);
2851 #ifndef QBT_USES_LIBTORRENT2
2852 p.storage = customStorageConstructor;
2853 #endif
2855 m_loadingTorrents.insert(id, loadTorrentParams);
2856 if (infoHash.isHybrid())
2857 m_hybridTorrentsByAltID.insert(altID, nullptr);
2858 if (!isFindingIncompleteFiles)
2859 m_nativeSession->async_add_torrent(p);
2861 return true;
2864 void SessionImpl::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
2865 , const Path &downloadPath, const PathList &filePaths) const
2867 Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
2869 const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
2870 const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
2871 QMetaObject::invokeMethod(m_fileSearcher, [=, this]
2873 m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath, isAppendExtensionEnabled());
2877 void SessionImpl::enablePortMapping()
2879 invokeAsync([this]
2881 if (m_isPortMappingEnabled)
2882 return;
2884 lt::settings_pack settingsPack;
2885 settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
2886 settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
2887 m_nativeSession->apply_settings(std::move(settingsPack));
2889 m_isPortMappingEnabled = true;
2891 LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
2895 void SessionImpl::disablePortMapping()
2897 invokeAsync([this]
2899 if (!m_isPortMappingEnabled)
2900 return;
2902 lt::settings_pack settingsPack;
2903 settingsPack.set_bool(lt::settings_pack::enable_upnp, false);
2904 settingsPack.set_bool(lt::settings_pack::enable_natpmp, false);
2905 m_nativeSession->apply_settings(std::move(settingsPack));
2907 m_mappedPorts.clear();
2908 m_isPortMappingEnabled = false;
2910 LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO);
2914 void SessionImpl::addMappedPorts(const QSet<quint16> &ports)
2916 invokeAsync([this, ports]
2918 if (!m_isPortMappingEnabled)
2919 return;
2921 for (const quint16 port : ports)
2923 if (!m_mappedPorts.contains(port))
2924 m_mappedPorts.insert(port, m_nativeSession->add_port_mapping(lt::session::tcp, port, port));
2929 void SessionImpl::removeMappedPorts(const QSet<quint16> &ports)
2931 invokeAsync([this, ports]
2933 if (!m_isPortMappingEnabled)
2934 return;
2936 Algorithm::removeIf(m_mappedPorts, [this, ports](const quint16 port, const std::vector<lt::port_mapping_t> &handles)
2938 if (!ports.contains(port))
2939 return false;
2941 for (const lt::port_mapping_t &handle : handles)
2942 m_nativeSession->delete_port_mapping(handle);
2944 return true;
2949 void SessionImpl::invokeAsync(std::function<void ()> func)
2951 m_asyncWorker->start(std::move(func));
2954 // Add a torrent to libtorrent session in hidden mode
2955 // and force it to download its metadata
2956 bool SessionImpl::downloadMetadata(const TorrentDescriptor &torrentDescr)
2958 Q_ASSERT(!torrentDescr.info().has_value());
2959 if (torrentDescr.info().has_value()) [[unlikely]]
2960 return false;
2962 const InfoHash infoHash = torrentDescr.infoHash();
2964 // We should not add torrent if it's already
2965 // processed or adding to session
2966 if (isKnownTorrent(infoHash))
2967 return false;
2969 lt::add_torrent_params p = torrentDescr.ltAddTorrentParams();
2971 if (isAddTrackersEnabled())
2973 // Use "additional trackers" when metadata retrieving (this can help when the DHT nodes are few)
2975 const auto maxTierIter = std::max_element(p.tracker_tiers.cbegin(), p.tracker_tiers.cend());
2976 const int baseTier = (maxTierIter != p.tracker_tiers.cend()) ? (*maxTierIter + 1) : 0;
2978 p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerEntries.size()));
2979 p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerEntries.size()));
2980 p.tracker_tiers.resize(p.trackers.size(), 0);
2981 for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerEntries))
2983 p.trackers.emplace_back(trackerEntry.url.toStdString());
2984 p.tracker_tiers.emplace_back(Utils::Number::clampingAdd(trackerEntry.tier, baseTier));
2988 // Flags
2989 // Preallocation mode
2990 if (isPreallocationEnabled())
2991 p.storage_mode = lt::storage_mode_allocate;
2992 else
2993 p.storage_mode = lt::storage_mode_sparse;
2995 // Limits
2996 p.max_connections = maxConnectionsPerTorrent();
2997 p.max_uploads = maxUploadsPerTorrent();
2999 const auto id = TorrentID::fromInfoHash(infoHash);
3000 const Path savePath = Utils::Fs::tempPath() / Path(id.toString());
3001 p.save_path = savePath.toString().toStdString();
3003 // Forced start
3004 p.flags &= ~lt::torrent_flags::paused;
3005 p.flags &= ~lt::torrent_flags::auto_managed;
3007 // Solution to avoid accidental file writes
3008 p.flags |= lt::torrent_flags::upload_mode;
3010 #ifndef QBT_USES_LIBTORRENT2
3011 p.storage = customStorageConstructor;
3012 #endif
3014 // Adding torrent to libtorrent session
3015 m_nativeSession->async_add_torrent(p);
3016 m_downloadedMetadata.insert(id, {});
3018 return true;
3021 void SessionImpl::exportTorrentFile(const Torrent *torrent, const Path &folderPath)
3023 if (!folderPath.exists() && !Utils::Fs::mkpath(folderPath))
3024 return;
3026 const QString validName = Utils::Fs::toValidFileName(torrent->name());
3027 QString torrentExportFilename = u"%1.torrent"_s.arg(validName);
3028 Path newTorrentPath = folderPath / Path(torrentExportFilename);
3029 int counter = 0;
3030 while (newTorrentPath.exists())
3032 // Append number to torrent name to make it unique
3033 torrentExportFilename = u"%1 %2.torrent"_s.arg(validName).arg(++counter);
3034 newTorrentPath = folderPath / Path(torrentExportFilename);
3037 const nonstd::expected<void, QString> result = torrent->exportToFile(newTorrentPath);
3038 if (!result)
3040 LogMsg(tr("Failed to export torrent. Torrent: \"%1\". Destination: \"%2\". Reason: \"%3\"")
3041 .arg(torrent->name(), newTorrentPath.toString(), result.error()), Log::WARNING);
3045 void SessionImpl::generateResumeData()
3047 for (TorrentImpl *const torrent : asConst(m_torrents))
3049 if (torrent->needSaveResumeData())
3050 torrent->requestResumeData();
3054 // Called on exit
3055 void SessionImpl::saveResumeData()
3057 for (TorrentImpl *torrent : asConst(m_torrents))
3059 // When the session is terminated due to unrecoverable error
3060 // some of the torrent handles can be corrupted
3063 torrent->requestResumeData(lt::torrent_handle::only_if_modified);
3065 catch (const std::exception &) {}
3068 // clear queued storage move jobs except the current ongoing one
3069 if (m_moveStorageQueue.size() > 1)
3070 m_moveStorageQueue.resize(1);
3072 QElapsedTimer timer;
3073 timer.start();
3075 while ((m_numResumeData > 0) || !m_moveStorageQueue.isEmpty() || m_needSaveTorrentsQueue)
3077 const lt::seconds waitTime {5};
3078 const lt::seconds expireTime {30};
3080 // only terminate when no storage is moving
3081 if (timer.hasExpired(lt::total_milliseconds(expireTime)) && m_moveStorageQueue.isEmpty())
3083 LogMsg(tr("Aborted saving resume data. Number of outstanding torrents: %1").arg(QString::number(m_numResumeData))
3084 , Log::CRITICAL);
3085 break;
3088 const std::vector<lt::alert *> alerts = getPendingAlerts(waitTime);
3090 bool hasWantedAlert = false;
3091 for (const lt::alert *alert : alerts)
3093 if (const int alertType = alert->type();
3094 (alertType == lt::save_resume_data_alert::alert_type) || (alertType == lt::save_resume_data_failed_alert::alert_type)
3095 || (alertType == lt::storage_moved_alert::alert_type) || (alertType == lt::storage_moved_failed_alert::alert_type)
3096 || (alertType == lt::state_update_alert::alert_type))
3098 hasWantedAlert = true;
3101 handleAlert(alert);
3104 if (hasWantedAlert)
3105 timer.start();
3109 void SessionImpl::saveTorrentsQueue()
3111 QVector<TorrentID> queue;
3112 for (const TorrentImpl *torrent : asConst(m_torrents))
3114 if (const int queuePos = torrent->queuePosition(); queuePos >= 0)
3116 if (queuePos >= queue.size())
3117 queue.resize(queuePos + 1);
3118 queue[queuePos] = torrent->id();
3122 m_resumeDataStorage->storeQueue(queue);
3123 m_needSaveTorrentsQueue = false;
3126 void SessionImpl::removeTorrentsQueue()
3128 m_resumeDataStorage->storeQueue({});
3129 m_torrentsQueueChanged = false;
3130 m_needSaveTorrentsQueue = false;
3133 void SessionImpl::setSavePath(const Path &path)
3135 const auto newPath = (path.isAbsolute() ? path : (specialFolderLocation(SpecialFolder::Downloads) / path));
3136 if (newPath == m_savePath)
3137 return;
3139 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3141 QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
3142 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
3144 const QString &categoryName = it.key();
3145 const CategoryOptions &categoryOptions = it.value();
3146 if (categoryOptions.savePath.isRelative())
3147 affectedCatogories.insert(categoryName);
3150 for (TorrentImpl *const torrent : asConst(m_torrents))
3152 if (affectedCatogories.contains(torrent->category()))
3153 torrent->setAutoTMMEnabled(false);
3157 m_savePath = newPath;
3158 for (TorrentImpl *const torrent : asConst(m_torrents))
3159 torrent->handleCategoryOptionsChanged();
3162 void SessionImpl::setDownloadPath(const Path &path)
3164 const Path newPath = (path.isAbsolute() ? path : (savePath() / Path(u"temp"_s) / path));
3165 if (newPath == m_downloadPath)
3166 return;
3168 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3170 QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
3171 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
3173 const QString &categoryName = it.key();
3174 const CategoryOptions &categoryOptions = it.value();
3175 const DownloadPathOption downloadPathOption =
3176 categoryOptions.downloadPath.value_or(DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
3177 if (downloadPathOption.enabled && downloadPathOption.path.isRelative())
3178 affectedCatogories.insert(categoryName);
3181 for (TorrentImpl *const torrent : asConst(m_torrents))
3183 if (affectedCatogories.contains(torrent->category()))
3184 torrent->setAutoTMMEnabled(false);
3188 m_downloadPath = newPath;
3189 for (TorrentImpl *const torrent : asConst(m_torrents))
3190 torrent->handleCategoryOptionsChanged();
3193 QStringList SessionImpl::getListeningIPs() const
3195 QStringList IPs;
3197 const QString ifaceName = networkInterface();
3198 const QString ifaceAddr = networkInterfaceAddress();
3199 const QHostAddress configuredAddr(ifaceAddr);
3200 const bool allIPv4 = (ifaceAddr == u"0.0.0.0"); // Means All IPv4 addresses
3201 const bool allIPv6 = (ifaceAddr == u"::"); // Means All IPv6 addresses
3203 if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull())
3205 LogMsg(tr("The configured network address is invalid. Address: \"%1\"").arg(ifaceAddr), Log::CRITICAL);
3206 // Pass the invalid user configured interface name/address to libtorrent
3207 // in hopes that it will come online later.
3208 // This will not cause IP leak but allow user to reconnect the interface
3209 // and re-establish connection without restarting the client.
3210 IPs.append(ifaceAddr);
3211 return IPs;
3214 if (ifaceName.isEmpty())
3216 if (ifaceAddr.isEmpty())
3217 return {u"0.0.0.0"_s, u"::"_s}; // Indicates all interfaces + all addresses (aka default)
3219 if (allIPv4)
3220 return {u"0.0.0.0"_s};
3222 if (allIPv6)
3223 return {u"::"_s};
3226 const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
3228 if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
3229 || (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
3230 return;
3232 if ((match == addr) || allIPv4 || allIPv6)
3233 IPs.append(addr.toString());
3236 if (ifaceName.isEmpty())
3238 const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
3239 for (const auto &addr : addresses)
3240 checkAndAddIP(addr, configuredAddr);
3242 // At this point ifaceAddr was non-empty
3243 // If IPs.isEmpty() it means the configured Address was not found
3244 if (IPs.isEmpty())
3246 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3247 .arg(ifaceAddr), Log::CRITICAL);
3248 IPs.append(ifaceAddr);
3251 return IPs;
3254 // Attempt to listen on provided interface
3255 const QNetworkInterface networkIFace = QNetworkInterface::interfaceFromName(ifaceName);
3256 if (!networkIFace.isValid())
3258 qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName));
3259 LogMsg(tr("The configured network interface is invalid. Interface: \"%1\"").arg(ifaceName), Log::CRITICAL);
3260 IPs.append(ifaceName);
3261 return IPs;
3264 if (ifaceAddr.isEmpty())
3266 IPs.append(ifaceName);
3267 return IPs; // On Windows calling code converts it to GUID
3270 const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
3271 qDebug() << "This network interface has " << addresses.size() << " IP addresses";
3272 for (const QNetworkAddressEntry &entry : addresses)
3273 checkAndAddIP(entry.ip(), configuredAddr);
3275 // Make sure there is at least one IP
3276 // At this point there was an explicit interface and an explicit address set
3277 // and the address should have been found
3278 if (IPs.isEmpty())
3280 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3281 .arg(ifaceAddr), Log::CRITICAL);
3282 IPs.append(ifaceAddr);
3285 return IPs;
3288 // Set the ports range in which is chosen the port
3289 // the BitTorrent session will listen to
3290 void SessionImpl::configureListeningInterface()
3292 m_listenInterfaceConfigured = false;
3293 configureDeferred();
3296 int SessionImpl::globalDownloadSpeedLimit() const
3298 // Unfortunately the value was saved as KiB instead of B.
3299 // But it is better to pass it around internally(+ webui) as Bytes.
3300 return m_globalDownloadSpeedLimit * 1024;
3303 void SessionImpl::setGlobalDownloadSpeedLimit(const int limit)
3305 // Unfortunately the value was saved as KiB instead of B.
3306 // But it is better to pass it around internally(+ webui) as Bytes.
3307 if (limit == globalDownloadSpeedLimit())
3308 return;
3310 if (limit <= 0)
3311 m_globalDownloadSpeedLimit = 0;
3312 else if (limit <= 1024)
3313 m_globalDownloadSpeedLimit = 1;
3314 else
3315 m_globalDownloadSpeedLimit = (limit / 1024);
3317 if (!isAltGlobalSpeedLimitEnabled())
3318 configureDeferred();
3321 int SessionImpl::globalUploadSpeedLimit() const
3323 // Unfortunately the value was saved as KiB instead of B.
3324 // But it is better to pass it around internally(+ webui) as Bytes.
3325 return m_globalUploadSpeedLimit * 1024;
3328 void SessionImpl::setGlobalUploadSpeedLimit(const int limit)
3330 // Unfortunately the value was saved as KiB instead of B.
3331 // But it is better to pass it around internally(+ webui) as Bytes.
3332 if (limit == globalUploadSpeedLimit())
3333 return;
3335 if (limit <= 0)
3336 m_globalUploadSpeedLimit = 0;
3337 else if (limit <= 1024)
3338 m_globalUploadSpeedLimit = 1;
3339 else
3340 m_globalUploadSpeedLimit = (limit / 1024);
3342 if (!isAltGlobalSpeedLimitEnabled())
3343 configureDeferred();
3346 int SessionImpl::altGlobalDownloadSpeedLimit() const
3348 // Unfortunately the value was saved as KiB instead of B.
3349 // But it is better to pass it around internally(+ webui) as Bytes.
3350 return m_altGlobalDownloadSpeedLimit * 1024;
3353 void SessionImpl::setAltGlobalDownloadSpeedLimit(const int limit)
3355 // Unfortunately the value was saved as KiB instead of B.
3356 // But it is better to pass it around internally(+ webui) as Bytes.
3357 if (limit == altGlobalDownloadSpeedLimit())
3358 return;
3360 if (limit <= 0)
3361 m_altGlobalDownloadSpeedLimit = 0;
3362 else if (limit <= 1024)
3363 m_altGlobalDownloadSpeedLimit = 1;
3364 else
3365 m_altGlobalDownloadSpeedLimit = (limit / 1024);
3367 if (isAltGlobalSpeedLimitEnabled())
3368 configureDeferred();
3371 int SessionImpl::altGlobalUploadSpeedLimit() const
3373 // Unfortunately the value was saved as KiB instead of B.
3374 // But it is better to pass it around internally(+ webui) as Bytes.
3375 return m_altGlobalUploadSpeedLimit * 1024;
3378 void SessionImpl::setAltGlobalUploadSpeedLimit(const int limit)
3380 // Unfortunately the value was saved as KiB instead of B.
3381 // But it is better to pass it around internally(+ webui) as Bytes.
3382 if (limit == altGlobalUploadSpeedLimit())
3383 return;
3385 if (limit <= 0)
3386 m_altGlobalUploadSpeedLimit = 0;
3387 else if (limit <= 1024)
3388 m_altGlobalUploadSpeedLimit = 1;
3389 else
3390 m_altGlobalUploadSpeedLimit = (limit / 1024);
3392 if (isAltGlobalSpeedLimitEnabled())
3393 configureDeferred();
3396 int SessionImpl::downloadSpeedLimit() const
3398 return isAltGlobalSpeedLimitEnabled()
3399 ? altGlobalDownloadSpeedLimit()
3400 : globalDownloadSpeedLimit();
3403 void SessionImpl::setDownloadSpeedLimit(const int limit)
3405 if (isAltGlobalSpeedLimitEnabled())
3406 setAltGlobalDownloadSpeedLimit(limit);
3407 else
3408 setGlobalDownloadSpeedLimit(limit);
3411 int SessionImpl::uploadSpeedLimit() const
3413 return isAltGlobalSpeedLimitEnabled()
3414 ? altGlobalUploadSpeedLimit()
3415 : globalUploadSpeedLimit();
3418 void SessionImpl::setUploadSpeedLimit(const int limit)
3420 if (isAltGlobalSpeedLimitEnabled())
3421 setAltGlobalUploadSpeedLimit(limit);
3422 else
3423 setGlobalUploadSpeedLimit(limit);
3426 bool SessionImpl::isAltGlobalSpeedLimitEnabled() const
3428 return m_isAltGlobalSpeedLimitEnabled;
3431 void SessionImpl::setAltGlobalSpeedLimitEnabled(const bool enabled)
3433 if (enabled == isAltGlobalSpeedLimitEnabled()) return;
3435 // Save new state to remember it on startup
3436 m_isAltGlobalSpeedLimitEnabled = enabled;
3437 applyBandwidthLimits();
3438 // Notify
3439 emit speedLimitModeChanged(m_isAltGlobalSpeedLimitEnabled);
3442 bool SessionImpl::isBandwidthSchedulerEnabled() const
3444 return m_isBandwidthSchedulerEnabled;
3447 void SessionImpl::setBandwidthSchedulerEnabled(const bool enabled)
3449 if (enabled != isBandwidthSchedulerEnabled())
3451 m_isBandwidthSchedulerEnabled = enabled;
3452 if (enabled)
3453 enableBandwidthScheduler();
3454 else
3455 delete m_bwScheduler;
3459 bool SessionImpl::isPerformanceWarningEnabled() const
3461 return m_isPerformanceWarningEnabled;
3464 void SessionImpl::setPerformanceWarningEnabled(const bool enable)
3466 if (enable == m_isPerformanceWarningEnabled)
3467 return;
3469 m_isPerformanceWarningEnabled = enable;
3470 configureDeferred();
3473 int SessionImpl::saveResumeDataInterval() const
3475 return m_saveResumeDataInterval;
3478 void SessionImpl::setSaveResumeDataInterval(const int value)
3480 if (value == m_saveResumeDataInterval)
3481 return;
3483 m_saveResumeDataInterval = value;
3485 if (value > 0)
3487 m_resumeDataTimer->setInterval(std::chrono::minutes(value));
3488 m_resumeDataTimer->start();
3490 else
3492 m_resumeDataTimer->stop();
3496 int SessionImpl::shutdownTimeout() const
3498 return m_shutdownTimeout;
3501 void SessionImpl::setShutdownTimeout(const int value)
3503 m_shutdownTimeout = value;
3506 int SessionImpl::port() const
3508 return m_port;
3511 void SessionImpl::setPort(const int port)
3513 if (port != m_port)
3515 m_port = port;
3516 configureListeningInterface();
3518 if (isReannounceWhenAddressChangedEnabled())
3519 reannounceToAllTrackers();
3523 bool SessionImpl::isSSLEnabled() const
3525 return m_sslEnabled;
3528 void SessionImpl::setSSLEnabled(const bool enabled)
3530 if (enabled == isSSLEnabled())
3531 return;
3533 m_sslEnabled = enabled;
3534 configureListeningInterface();
3536 if (isReannounceWhenAddressChangedEnabled())
3537 reannounceToAllTrackers();
3540 int SessionImpl::sslPort() const
3542 return m_sslPort;
3545 void SessionImpl::setSSLPort(const int port)
3547 if (port == sslPort())
3548 return;
3550 m_sslPort = port;
3551 configureListeningInterface();
3553 if (isReannounceWhenAddressChangedEnabled())
3554 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())
3796 return;
3798 m_additionalTrackers = trackers;
3799 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::wildcardToRegularExpression(str);
3874 const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
3875 m_excludedFileNamesRegExpList.append(re);
3879 void SessionImpl::applyFilenameFilter(const PathList &files, QList<DownloadPriority> &priorities)
3881 if (!isExcludedFileNamesEnabled())
3882 return;
3884 const auto isFilenameExcluded = [patterns = m_excludedFileNamesRegExpList](const Path &fileName)
3886 return std::any_of(patterns.begin(), patterns.end(), [&fileName](const QRegularExpression &re)
3888 Path path = fileName;
3889 while (!re.match(path.filename()).hasMatch())
3891 path = path.parentPath();
3892 if (path.isEmpty())
3893 return false;
3895 return true;
3899 priorities.resize(files.count(), DownloadPriority::Normal);
3900 for (int i = 0; i < priorities.size(); ++i)
3902 if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
3903 continue;
3905 if (isFilenameExcluded(files.at(i)))
3906 priorities[i] = BitTorrent::DownloadPriority::Ignored;
3910 void SessionImpl::setBannedIPs(const QStringList &newList)
3912 if (newList == m_bannedIPs)
3913 return; // do nothing
3914 // here filter out incorrect IP
3915 QStringList filteredList;
3916 for (const QString &ip : newList)
3918 if (Utils::Net::isValidIP(ip))
3920 // the same IPv6 addresses could be written in different forms;
3921 // QHostAddress::toString() result format follows RFC5952;
3922 // thus we avoid duplicate entries pointing to the same address
3923 filteredList << QHostAddress(ip).toString();
3925 else
3927 LogMsg(tr("Rejected invalid IP address while applying the list of banned IP addresses. IP: \"%1\"")
3928 .arg(ip)
3929 , Log::WARNING);
3932 // now we have to sort IPs and make them unique
3933 filteredList.sort();
3934 filteredList.removeDuplicates();
3935 // Again ensure that the new list is different from the stored one.
3936 if (filteredList == m_bannedIPs)
3937 return; // do nothing
3938 // store to session settings
3939 // also here we have to recreate filter list including 3rd party ban file
3940 // and install it again into m_session
3941 m_bannedIPs = filteredList;
3942 m_IPFilteringConfigured = false;
3943 configureDeferred();
3946 ResumeDataStorageType SessionImpl::resumeDataStorageType() const
3948 return m_resumeDataStorageType;
3951 void SessionImpl::setResumeDataStorageType(const ResumeDataStorageType type)
3953 m_resumeDataStorageType = type;
3956 bool SessionImpl::isMergeTrackersEnabled() const
3958 return m_isMergeTrackersEnabled;
3961 void SessionImpl::setMergeTrackersEnabled(const bool enabled)
3963 m_isMergeTrackersEnabled = enabled;
3966 bool SessionImpl::isStartPaused() const
3968 return m_startPaused.get(false);
3971 void SessionImpl::setStartPaused(const bool value)
3973 m_startPaused = value;
3976 QStringList SessionImpl::bannedIPs() const
3978 return m_bannedIPs;
3981 bool SessionImpl::isRestored() const
3983 return m_isRestored;
3986 bool SessionImpl::isPaused() const
3988 return m_isPaused;
3991 void SessionImpl::pause()
3993 if (!m_isPaused)
3995 if (isRestored())
3996 m_nativeSession->pause();
3998 m_isPaused = true;
3999 emit paused();
4003 void SessionImpl::resume()
4005 if (m_isPaused)
4007 if (isRestored())
4008 m_nativeSession->resume();
4010 m_isPaused = false;
4011 emit resumed();
4015 int SessionImpl::maxConnectionsPerTorrent() const
4017 return m_maxConnectionsPerTorrent;
4020 void SessionImpl::setMaxConnectionsPerTorrent(int max)
4022 max = (max > 0) ? max : -1;
4023 if (max != maxConnectionsPerTorrent())
4025 m_maxConnectionsPerTorrent = max;
4027 for (const TorrentImpl *torrent : asConst(m_torrents))
4031 torrent->nativeHandle().set_max_connections(max);
4033 catch (const std::exception &) {}
4038 int SessionImpl::maxUploadsPerTorrent() const
4040 return m_maxUploadsPerTorrent;
4043 void SessionImpl::setMaxUploadsPerTorrent(int max)
4045 max = (max > 0) ? max : -1;
4046 if (max != maxUploadsPerTorrent())
4048 m_maxUploadsPerTorrent = max;
4050 for (const TorrentImpl *torrent : asConst(m_torrents))
4054 torrent->nativeHandle().set_max_uploads(max);
4056 catch (const std::exception &) {}
4061 bool SessionImpl::announceToAllTrackers() const
4063 return m_announceToAllTrackers;
4066 void SessionImpl::setAnnounceToAllTrackers(const bool val)
4068 if (val != m_announceToAllTrackers)
4070 m_announceToAllTrackers = val;
4071 configureDeferred();
4075 bool SessionImpl::announceToAllTiers() const
4077 return m_announceToAllTiers;
4080 void SessionImpl::setAnnounceToAllTiers(const bool val)
4082 if (val != m_announceToAllTiers)
4084 m_announceToAllTiers = val;
4085 configureDeferred();
4089 int SessionImpl::peerTurnover() const
4091 return m_peerTurnover;
4094 void SessionImpl::setPeerTurnover(const int val)
4096 if (val == m_peerTurnover)
4097 return;
4099 m_peerTurnover = val;
4100 configureDeferred();
4103 int SessionImpl::peerTurnoverCutoff() const
4105 return m_peerTurnoverCutoff;
4108 void SessionImpl::setPeerTurnoverCutoff(const int val)
4110 if (val == m_peerTurnoverCutoff)
4111 return;
4113 m_peerTurnoverCutoff = val;
4114 configureDeferred();
4117 int SessionImpl::peerTurnoverInterval() const
4119 return m_peerTurnoverInterval;
4122 void SessionImpl::setPeerTurnoverInterval(const int val)
4124 if (val == m_peerTurnoverInterval)
4125 return;
4127 m_peerTurnoverInterval = val;
4128 configureDeferred();
4131 DiskIOType SessionImpl::diskIOType() const
4133 return m_diskIOType;
4136 void SessionImpl::setDiskIOType(const DiskIOType type)
4138 if (type != m_diskIOType)
4140 m_diskIOType = type;
4144 int SessionImpl::requestQueueSize() const
4146 return m_requestQueueSize;
4149 void SessionImpl::setRequestQueueSize(const int val)
4151 if (val == m_requestQueueSize)
4152 return;
4154 m_requestQueueSize = val;
4155 configureDeferred();
4158 int SessionImpl::asyncIOThreads() const
4160 return std::clamp(m_asyncIOThreads.get(), 1, 1024);
4163 void SessionImpl::setAsyncIOThreads(const int num)
4165 if (num == m_asyncIOThreads)
4166 return;
4168 m_asyncIOThreads = num;
4169 configureDeferred();
4172 int SessionImpl::hashingThreads() const
4174 return std::clamp(m_hashingThreads.get(), 1, 1024);
4177 void SessionImpl::setHashingThreads(const int num)
4179 if (num == m_hashingThreads)
4180 return;
4182 m_hashingThreads = num;
4183 configureDeferred();
4186 int SessionImpl::filePoolSize() const
4188 return m_filePoolSize;
4191 void SessionImpl::setFilePoolSize(const int size)
4193 if (size == m_filePoolSize)
4194 return;
4196 m_filePoolSize = size;
4197 configureDeferred();
4200 int SessionImpl::checkingMemUsage() const
4202 return std::max(1, m_checkingMemUsage.get());
4205 void SessionImpl::setCheckingMemUsage(int size)
4207 size = std::max(size, 1);
4209 if (size == m_checkingMemUsage)
4210 return;
4212 m_checkingMemUsage = size;
4213 configureDeferred();
4216 int SessionImpl::diskCacheSize() const
4218 #ifdef QBT_APP_64BIT
4219 return std::min(m_diskCacheSize.get(), 33554431); // 32768GiB
4220 #else
4221 // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
4222 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4223 return std::min(m_diskCacheSize.get(), 1536);
4224 #endif
4227 void SessionImpl::setDiskCacheSize(int size)
4229 #ifdef QBT_APP_64BIT
4230 size = std::min(size, 33554431); // 32768GiB
4231 #else
4232 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4233 size = std::min(size, 1536);
4234 #endif
4235 if (size != m_diskCacheSize)
4237 m_diskCacheSize = size;
4238 configureDeferred();
4242 int SessionImpl::diskCacheTTL() const
4244 return m_diskCacheTTL;
4247 void SessionImpl::setDiskCacheTTL(const int ttl)
4249 if (ttl != m_diskCacheTTL)
4251 m_diskCacheTTL = ttl;
4252 configureDeferred();
4256 qint64 SessionImpl::diskQueueSize() const
4258 return m_diskQueueSize;
4261 void SessionImpl::setDiskQueueSize(const qint64 size)
4263 if (size == m_diskQueueSize)
4264 return;
4266 m_diskQueueSize = size;
4267 configureDeferred();
4270 DiskIOReadMode SessionImpl::diskIOReadMode() const
4272 return m_diskIOReadMode;
4275 void SessionImpl::setDiskIOReadMode(const DiskIOReadMode mode)
4277 if (mode == m_diskIOReadMode)
4278 return;
4280 m_diskIOReadMode = mode;
4281 configureDeferred();
4284 DiskIOWriteMode SessionImpl::diskIOWriteMode() const
4286 return m_diskIOWriteMode;
4289 void SessionImpl::setDiskIOWriteMode(const DiskIOWriteMode mode)
4291 if (mode == m_diskIOWriteMode)
4292 return;
4294 m_diskIOWriteMode = mode;
4295 configureDeferred();
4298 bool SessionImpl::isCoalesceReadWriteEnabled() const
4300 return m_coalesceReadWriteEnabled;
4303 void SessionImpl::setCoalesceReadWriteEnabled(const bool enabled)
4305 if (enabled == m_coalesceReadWriteEnabled) return;
4307 m_coalesceReadWriteEnabled = enabled;
4308 configureDeferred();
4311 bool SessionImpl::isSuggestModeEnabled() const
4313 return m_isSuggestMode;
4316 bool SessionImpl::usePieceExtentAffinity() const
4318 return m_usePieceExtentAffinity;
4321 void SessionImpl::setPieceExtentAffinity(const bool enabled)
4323 if (enabled == m_usePieceExtentAffinity) return;
4325 m_usePieceExtentAffinity = enabled;
4326 configureDeferred();
4329 void SessionImpl::setSuggestMode(const bool mode)
4331 if (mode == m_isSuggestMode) return;
4333 m_isSuggestMode = mode;
4334 configureDeferred();
4337 int SessionImpl::sendBufferWatermark() const
4339 return m_sendBufferWatermark;
4342 void SessionImpl::setSendBufferWatermark(const int value)
4344 if (value == m_sendBufferWatermark) return;
4346 m_sendBufferWatermark = value;
4347 configureDeferred();
4350 int SessionImpl::sendBufferLowWatermark() const
4352 return m_sendBufferLowWatermark;
4355 void SessionImpl::setSendBufferLowWatermark(const int value)
4357 if (value == m_sendBufferLowWatermark) return;
4359 m_sendBufferLowWatermark = value;
4360 configureDeferred();
4363 int SessionImpl::sendBufferWatermarkFactor() const
4365 return m_sendBufferWatermarkFactor;
4368 void SessionImpl::setSendBufferWatermarkFactor(const int value)
4370 if (value == m_sendBufferWatermarkFactor) return;
4372 m_sendBufferWatermarkFactor = value;
4373 configureDeferred();
4376 int SessionImpl::connectionSpeed() const
4378 return m_connectionSpeed;
4381 void SessionImpl::setConnectionSpeed(const int value)
4383 if (value == m_connectionSpeed) return;
4385 m_connectionSpeed = value;
4386 configureDeferred();
4389 int SessionImpl::socketSendBufferSize() const
4391 return m_socketSendBufferSize;
4394 void SessionImpl::setSocketSendBufferSize(const int value)
4396 if (value == m_socketSendBufferSize)
4397 return;
4399 m_socketSendBufferSize = value;
4400 configureDeferred();
4403 int SessionImpl::socketReceiveBufferSize() const
4405 return m_socketReceiveBufferSize;
4408 void SessionImpl::setSocketReceiveBufferSize(const int value)
4410 if (value == m_socketReceiveBufferSize)
4411 return;
4413 m_socketReceiveBufferSize = value;
4414 configureDeferred();
4417 int SessionImpl::socketBacklogSize() const
4419 return m_socketBacklogSize;
4422 void SessionImpl::setSocketBacklogSize(const int value)
4424 if (value == m_socketBacklogSize) return;
4426 m_socketBacklogSize = value;
4427 configureDeferred();
4430 bool SessionImpl::isAnonymousModeEnabled() const
4432 return m_isAnonymousModeEnabled;
4435 void SessionImpl::setAnonymousModeEnabled(const bool enabled)
4437 if (enabled != m_isAnonymousModeEnabled)
4439 m_isAnonymousModeEnabled = enabled;
4440 configureDeferred();
4441 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
4442 , Log::INFO);
4446 bool SessionImpl::isQueueingSystemEnabled() const
4448 return m_isQueueingEnabled;
4451 void SessionImpl::setQueueingSystemEnabled(const bool enabled)
4453 if (enabled != m_isQueueingEnabled)
4455 m_isQueueingEnabled = enabled;
4456 configureDeferred();
4458 if (enabled)
4459 m_torrentsQueueChanged = true;
4460 else
4461 removeTorrentsQueue();
4463 for (TorrentImpl *torrent : asConst(m_torrents))
4464 torrent->handleQueueingModeChanged();
4468 int SessionImpl::maxActiveDownloads() const
4470 return m_maxActiveDownloads;
4473 void SessionImpl::setMaxActiveDownloads(int max)
4475 max = std::max(max, -1);
4476 if (max != m_maxActiveDownloads)
4478 m_maxActiveDownloads = max;
4479 configureDeferred();
4483 int SessionImpl::maxActiveUploads() const
4485 return m_maxActiveUploads;
4488 void SessionImpl::setMaxActiveUploads(int max)
4490 max = std::max(max, -1);
4491 if (max != m_maxActiveUploads)
4493 m_maxActiveUploads = max;
4494 configureDeferred();
4498 int SessionImpl::maxActiveTorrents() const
4500 return m_maxActiveTorrents;
4503 void SessionImpl::setMaxActiveTorrents(int max)
4505 max = std::max(max, -1);
4506 if (max != m_maxActiveTorrents)
4508 m_maxActiveTorrents = max;
4509 configureDeferred();
4513 bool SessionImpl::ignoreSlowTorrentsForQueueing() const
4515 return m_ignoreSlowTorrentsForQueueing;
4518 void SessionImpl::setIgnoreSlowTorrentsForQueueing(const bool ignore)
4520 if (ignore != m_ignoreSlowTorrentsForQueueing)
4522 m_ignoreSlowTorrentsForQueueing = ignore;
4523 configureDeferred();
4527 int SessionImpl::downloadRateForSlowTorrents() const
4529 return m_downloadRateForSlowTorrents;
4532 void SessionImpl::setDownloadRateForSlowTorrents(const int rateInKibiBytes)
4534 if (rateInKibiBytes == m_downloadRateForSlowTorrents)
4535 return;
4537 m_downloadRateForSlowTorrents = rateInKibiBytes;
4538 configureDeferred();
4541 int SessionImpl::uploadRateForSlowTorrents() const
4543 return m_uploadRateForSlowTorrents;
4546 void SessionImpl::setUploadRateForSlowTorrents(const int rateInKibiBytes)
4548 if (rateInKibiBytes == m_uploadRateForSlowTorrents)
4549 return;
4551 m_uploadRateForSlowTorrents = rateInKibiBytes;
4552 configureDeferred();
4555 int SessionImpl::slowTorrentsInactivityTimer() const
4557 return m_slowTorrentsInactivityTimer;
4560 void SessionImpl::setSlowTorrentsInactivityTimer(const int timeInSeconds)
4562 if (timeInSeconds == m_slowTorrentsInactivityTimer)
4563 return;
4565 m_slowTorrentsInactivityTimer = timeInSeconds;
4566 configureDeferred();
4569 int SessionImpl::outgoingPortsMin() const
4571 return m_outgoingPortsMin;
4574 void SessionImpl::setOutgoingPortsMin(const int min)
4576 if (min != m_outgoingPortsMin)
4578 m_outgoingPortsMin = min;
4579 configureDeferred();
4583 int SessionImpl::outgoingPortsMax() const
4585 return m_outgoingPortsMax;
4588 void SessionImpl::setOutgoingPortsMax(const int max)
4590 if (max != m_outgoingPortsMax)
4592 m_outgoingPortsMax = max;
4593 configureDeferred();
4597 int SessionImpl::UPnPLeaseDuration() const
4599 return m_UPnPLeaseDuration;
4602 void SessionImpl::setUPnPLeaseDuration(const int duration)
4604 if (duration != m_UPnPLeaseDuration)
4606 m_UPnPLeaseDuration = duration;
4607 configureDeferred();
4611 int SessionImpl::peerToS() const
4613 return m_peerToS;
4616 void SessionImpl::setPeerToS(const int value)
4618 if (value == m_peerToS)
4619 return;
4621 m_peerToS = value;
4622 configureDeferred();
4625 bool SessionImpl::ignoreLimitsOnLAN() const
4627 return m_ignoreLimitsOnLAN;
4630 void SessionImpl::setIgnoreLimitsOnLAN(const bool ignore)
4632 if (ignore != m_ignoreLimitsOnLAN)
4634 m_ignoreLimitsOnLAN = ignore;
4635 configureDeferred();
4639 bool SessionImpl::includeOverheadInLimits() const
4641 return m_includeOverheadInLimits;
4644 void SessionImpl::setIncludeOverheadInLimits(const bool include)
4646 if (include != m_includeOverheadInLimits)
4648 m_includeOverheadInLimits = include;
4649 configureDeferred();
4653 QString SessionImpl::announceIP() const
4655 return m_announceIP;
4658 void SessionImpl::setAnnounceIP(const QString &ip)
4660 if (ip != m_announceIP)
4662 m_announceIP = ip;
4663 configureDeferred();
4667 int SessionImpl::maxConcurrentHTTPAnnounces() const
4669 return m_maxConcurrentHTTPAnnounces;
4672 void SessionImpl::setMaxConcurrentHTTPAnnounces(const int value)
4674 if (value == m_maxConcurrentHTTPAnnounces)
4675 return;
4677 m_maxConcurrentHTTPAnnounces = value;
4678 configureDeferred();
4681 bool SessionImpl::isReannounceWhenAddressChangedEnabled() const
4683 return m_isReannounceWhenAddressChangedEnabled;
4686 void SessionImpl::setReannounceWhenAddressChangedEnabled(const bool enabled)
4688 if (enabled == m_isReannounceWhenAddressChangedEnabled)
4689 return;
4691 m_isReannounceWhenAddressChangedEnabled = enabled;
4694 void SessionImpl::reannounceToAllTrackers() const
4696 for (const TorrentImpl *torrent : asConst(m_torrents))
4700 torrent->nativeHandle().force_reannounce(0, -1, lt::torrent_handle::ignore_min_interval);
4702 catch (const std::exception &) {}
4706 int SessionImpl::stopTrackerTimeout() const
4708 return m_stopTrackerTimeout;
4711 void SessionImpl::setStopTrackerTimeout(const int value)
4713 if (value == m_stopTrackerTimeout)
4714 return;
4716 m_stopTrackerTimeout = value;
4717 configureDeferred();
4720 int SessionImpl::maxConnections() const
4722 return m_maxConnections;
4725 void SessionImpl::setMaxConnections(int max)
4727 max = (max > 0) ? max : -1;
4728 if (max != m_maxConnections)
4730 m_maxConnections = max;
4731 configureDeferred();
4735 int SessionImpl::maxUploads() const
4737 return m_maxUploads;
4740 void SessionImpl::setMaxUploads(int max)
4742 max = (max > 0) ? max : -1;
4743 if (max != m_maxUploads)
4745 m_maxUploads = max;
4746 configureDeferred();
4750 BTProtocol SessionImpl::btProtocol() const
4752 return m_btProtocol;
4755 void SessionImpl::setBTProtocol(const BTProtocol protocol)
4757 if ((protocol < BTProtocol::Both) || (BTProtocol::UTP < protocol))
4758 return;
4760 if (protocol == m_btProtocol) return;
4762 m_btProtocol = protocol;
4763 configureDeferred();
4766 bool SessionImpl::isUTPRateLimited() const
4768 return m_isUTPRateLimited;
4771 void SessionImpl::setUTPRateLimited(const bool limited)
4773 if (limited != m_isUTPRateLimited)
4775 m_isUTPRateLimited = limited;
4776 configureDeferred();
4780 MixedModeAlgorithm SessionImpl::utpMixedMode() const
4782 return m_utpMixedMode;
4785 void SessionImpl::setUtpMixedMode(const MixedModeAlgorithm mode)
4787 if (mode == m_utpMixedMode) return;
4789 m_utpMixedMode = mode;
4790 configureDeferred();
4793 bool SessionImpl::isIDNSupportEnabled() const
4795 return m_IDNSupportEnabled;
4798 void SessionImpl::setIDNSupportEnabled(const bool enabled)
4800 if (enabled == m_IDNSupportEnabled) return;
4802 m_IDNSupportEnabled = enabled;
4803 configureDeferred();
4806 bool SessionImpl::multiConnectionsPerIpEnabled() const
4808 return m_multiConnectionsPerIpEnabled;
4811 void SessionImpl::setMultiConnectionsPerIpEnabled(const bool enabled)
4813 if (enabled == m_multiConnectionsPerIpEnabled) return;
4815 m_multiConnectionsPerIpEnabled = enabled;
4816 configureDeferred();
4819 bool SessionImpl::validateHTTPSTrackerCertificate() const
4821 return m_validateHTTPSTrackerCertificate;
4824 void SessionImpl::setValidateHTTPSTrackerCertificate(const bool enabled)
4826 if (enabled == m_validateHTTPSTrackerCertificate) return;
4828 m_validateHTTPSTrackerCertificate = enabled;
4829 configureDeferred();
4832 bool SessionImpl::isSSRFMitigationEnabled() const
4834 return m_SSRFMitigationEnabled;
4837 void SessionImpl::setSSRFMitigationEnabled(const bool enabled)
4839 if (enabled == m_SSRFMitigationEnabled) return;
4841 m_SSRFMitigationEnabled = enabled;
4842 configureDeferred();
4845 bool SessionImpl::blockPeersOnPrivilegedPorts() const
4847 return m_blockPeersOnPrivilegedPorts;
4850 void SessionImpl::setBlockPeersOnPrivilegedPorts(const bool enabled)
4852 if (enabled == m_blockPeersOnPrivilegedPorts) return;
4854 m_blockPeersOnPrivilegedPorts = enabled;
4855 configureDeferred();
4858 bool SessionImpl::isTrackerFilteringEnabled() const
4860 return m_isTrackerFilteringEnabled;
4863 void SessionImpl::setTrackerFilteringEnabled(const bool enabled)
4865 if (enabled != m_isTrackerFilteringEnabled)
4867 m_isTrackerFilteringEnabled = enabled;
4868 configureDeferred();
4872 bool SessionImpl::isListening() const
4874 return m_nativeSessionExtension->isSessionListening();
4877 ShareLimitAction SessionImpl::shareLimitAction() const
4879 return m_shareLimitAction;
4882 void SessionImpl::setShareLimitAction(const ShareLimitAction act)
4884 Q_ASSERT(act != ShareLimitAction::Default);
4886 m_shareLimitAction = act;
4889 bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const
4891 const bool isHybrid = infoHash.isHybrid();
4892 const auto id = TorrentID::fromInfoHash(infoHash);
4893 // alternative ID can be useful to find existing torrent
4894 // in case if hybrid torrent was added by v1 info hash
4895 const auto altID = (isHybrid ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
4897 if (m_loadingTorrents.contains(id) || (isHybrid && m_loadingTorrents.contains(altID)))
4898 return true;
4899 if (m_downloadedMetadata.contains(id) || (isHybrid && m_downloadedMetadata.contains(altID)))
4900 return true;
4901 return findTorrent(infoHash);
4904 void SessionImpl::updateSeedingLimitTimer()
4906 if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
4907 && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit()
4908 && (globalMaxInactiveSeedingMinutes() == Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT) && !hasPerTorrentInactiveSeedingTimeLimit())
4910 if (m_seedingLimitTimer->isActive())
4911 m_seedingLimitTimer->stop();
4913 else if (!m_seedingLimitTimer->isActive())
4915 m_seedingLimitTimer->start();
4919 void SessionImpl::handleTorrentShareLimitChanged(TorrentImpl *const)
4921 updateSeedingLimitTimer();
4924 void SessionImpl::handleTorrentNameChanged(TorrentImpl *const)
4928 void SessionImpl::handleTorrentSavePathChanged(TorrentImpl *const torrent)
4930 emit torrentSavePathChanged(torrent);
4933 void SessionImpl::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
4935 emit torrentCategoryChanged(torrent, oldCategory);
4938 void SessionImpl::handleTorrentTagAdded(TorrentImpl *const torrent, const Tag &tag)
4940 emit torrentTagAdded(torrent, tag);
4943 void SessionImpl::handleTorrentTagRemoved(TorrentImpl *const torrent, const Tag &tag)
4945 emit torrentTagRemoved(torrent, tag);
4948 void SessionImpl::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
4950 emit torrentSavingModeChanged(torrent);
4953 void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
4955 for (const TrackerEntry &newTracker : newTrackers)
4956 LogMsg(tr("Added tracker to torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), newTracker.url));
4957 emit trackersAdded(torrent, newTrackers);
4960 void SessionImpl::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers)
4962 for (const QString &deletedTracker : deletedTrackers)
4963 LogMsg(tr("Removed tracker from torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), deletedTracker));
4964 emit trackersRemoved(torrent, deletedTrackers);
4967 void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
4969 emit trackersChanged(torrent);
4972 void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
4974 for (const QUrl &newUrlSeed : newUrlSeeds)
4975 LogMsg(tr("Added URL seed to torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), newUrlSeed.toString()));
4978 void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
4980 for (const QUrl &urlSeed : urlSeeds)
4981 LogMsg(tr("Removed URL seed from torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), urlSeed.toString()));
4984 void SessionImpl::handleTorrentMetadataReceived(TorrentImpl *const torrent)
4986 if (!torrentExportDirectory().isEmpty())
4987 exportTorrentFile(torrent, torrentExportDirectory());
4989 emit torrentMetadataReceived(torrent);
4992 void SessionImpl::handleTorrentStopped(TorrentImpl *const torrent)
4994 torrent->resetTrackerEntryStatuses();
4996 const QVector<TrackerEntryStatus> trackers = torrent->trackers();
4997 QHash<QString, TrackerEntryStatus> updatedTrackers;
4998 updatedTrackers.reserve(trackers.size());
5000 for (const TrackerEntryStatus &status : trackers)
5001 updatedTrackers.emplace(status.url, status);
5002 emit trackerEntryStatusesUpdated(torrent, updatedTrackers);
5004 LogMsg(tr("Torrent stopped. Torrent: \"%1\"").arg(torrent->name()));
5005 emit torrentStopped(torrent);
5008 void SessionImpl::handleTorrentStarted(TorrentImpl *const torrent)
5010 LogMsg(tr("Torrent resumed. Torrent: \"%1\"").arg(torrent->name()));
5011 emit torrentStarted(torrent);
5014 void SessionImpl::handleTorrentChecked(TorrentImpl *const torrent)
5016 emit torrentFinishedChecking(torrent);
5019 void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
5021 LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
5022 emit torrentFinished(torrent);
5024 if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
5025 exportTorrentFile(torrent, exportPath);
5027 const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5029 return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
5031 if (!hasUnfinishedTorrents)
5032 emit allTorrentsFinished();
5035 void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
5037 m_resumeDataStorage->store(torrent->id(), data);
5038 const auto iter = m_changedTorrentIDs.find(torrent->id());
5039 if (iter != m_changedTorrentIDs.end())
5041 m_resumeDataStorage->remove(iter.value());
5042 m_changedTorrentIDs.erase(iter);
5046 void SessionImpl::handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash)
5048 Q_ASSERT(torrent->infoHash().isHybrid());
5050 m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(torrent->infoHash().v1()), torrent);
5052 const auto prevID = TorrentID::fromInfoHash(prevInfoHash);
5053 const TorrentID currentID = torrent->id();
5054 if (currentID != prevID)
5056 m_torrents[torrent->id()] = m_torrents.take(prevID);
5057 m_changedTorrentIDs[torrent->id()] = prevID;
5061 void SessionImpl::handleTorrentStorageMovingStateChanged(TorrentImpl *torrent)
5063 emit torrentsUpdated({torrent});
5066 bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode, const MoveStorageContext context)
5068 Q_ASSERT(torrent);
5070 const lt::torrent_handle torrentHandle = torrent->nativeHandle();
5071 const Path currentLocation = torrent->actualStorageLocation();
5072 const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle);
5074 if (m_moveStorageQueue.size() > 1)
5076 auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
5077 , [&torrentHandle](const MoveStorageJob &job)
5079 return job.torrentHandle == torrentHandle;
5082 if (iter != m_moveStorageQueue.end())
5084 // remove existing inactive job
5085 torrent->handleMoveStorageJobFinished(currentLocation, iter->context, torrentHasActiveJob);
5086 LogMsg(tr("Torrent move canceled. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), iter->path.toString()));
5087 m_moveStorageQueue.erase(iter);
5091 if (torrentHasActiveJob)
5093 // if there is active job for this torrent prevent creating meaningless
5094 // job that will move torrent to the same location as current one
5095 if (m_moveStorageQueue.first().path == newPath)
5097 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: torrent is currently moving to the destination")
5098 .arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5099 return false;
5102 else
5104 if (currentLocation == newPath)
5106 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\" Destination: \"%3\". Reason: both paths point to the same location")
5107 .arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5108 return false;
5112 const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode, context};
5113 m_moveStorageQueue << moveStorageJob;
5114 LogMsg(tr("Enqueued torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), newPath.toString()));
5116 if (m_moveStorageQueue.size() == 1)
5117 moveTorrentStorage(moveStorageJob);
5119 return true;
5122 void SessionImpl::moveTorrentStorage(const MoveStorageJob &job) const
5124 #ifdef QBT_USES_LIBTORRENT2
5125 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
5126 #else
5127 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
5128 #endif
5129 const TorrentImpl *torrent = m_torrents.value(id);
5130 const QString torrentName = (torrent ? torrent->name() : id.toString());
5131 LogMsg(tr("Start moving torrent. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, job.path.toString()));
5133 job.torrentHandle.move_storage(job.path.toString().toStdString(), toNative(job.mode));
5136 void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
5138 const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
5139 if (!m_moveStorageQueue.isEmpty())
5140 moveTorrentStorage(m_moveStorageQueue.first());
5142 const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
5143 , [&finishedJob](const MoveStorageJob &job)
5145 return job.torrentHandle == finishedJob.torrentHandle;
5148 const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
5150 TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
5151 if (torrent)
5153 torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
5155 else if (!torrentHasOutstandingJob)
5157 // Last job is completed for torrent that being removing, so actually remove it
5158 const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
5159 const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
5160 if (removingTorrentData.deleteOption == DeleteTorrent)
5161 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
5165 void SessionImpl::storeCategories() const
5167 QJsonObject jsonObj;
5168 for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
5170 const QString &categoryName = it.key();
5171 const CategoryOptions &categoryOptions = it.value();
5172 jsonObj[categoryName] = categoryOptions.toJSON();
5175 const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
5176 const QByteArray data = QJsonDocument(jsonObj).toJson();
5177 const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
5178 if (!result)
5180 LogMsg(tr("Failed to save Categories configuration. File: \"%1\". Error: \"%2\"")
5181 .arg(path.toString(), result.error()), Log::WARNING);
5185 void SessionImpl::upgradeCategories()
5187 const auto legacyCategories = SettingValue<QVariantMap>(u"BitTorrent/Session/Categories"_s).get();
5188 for (auto it = legacyCategories.cbegin(); it != legacyCategories.cend(); ++it)
5190 const QString &categoryName = it.key();
5191 CategoryOptions categoryOptions;
5192 categoryOptions.savePath = Path(it.value().toString());
5193 m_categories[categoryName] = categoryOptions;
5196 storeCategories();
5199 void SessionImpl::loadCategories()
5201 m_categories.clear();
5203 const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
5204 if (!path.exists())
5206 // TODO: Remove the following upgrade code in v4.5
5207 // == BEGIN UPGRADE CODE ==
5208 upgradeCategories();
5209 m_needUpgradeDownloadPath = true;
5210 // == END UPGRADE CODE ==
5212 // return;
5215 const int fileMaxSize = 1024 * 1024;
5216 const auto readResult = Utils::IO::readFile(path, fileMaxSize);
5217 if (!readResult)
5219 LogMsg(tr("Failed to load Categories. %1").arg(readResult.error().message), Log::WARNING);
5220 return;
5223 QJsonParseError jsonError;
5224 const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
5225 if (jsonError.error != QJsonParseError::NoError)
5227 LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
5228 .arg(path.toString(), jsonError.errorString()), Log::WARNING);
5229 return;
5232 if (!jsonDoc.isObject())
5234 LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Error: \"Invalid data format\"")
5235 .arg(path.toString()), Log::WARNING);
5236 return;
5239 const QJsonObject jsonObj = jsonDoc.object();
5240 for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
5242 const QString &categoryName = it.key();
5243 const auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
5244 m_categories[categoryName] = categoryOptions;
5248 bool SessionImpl::hasPerTorrentRatioLimit() const
5250 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5252 return (torrent->ratioLimit() >= 0);
5256 bool SessionImpl::hasPerTorrentSeedingTimeLimit() const
5258 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5260 return (torrent->seedingTimeLimit() >= 0);
5264 bool SessionImpl::hasPerTorrentInactiveSeedingTimeLimit() const
5266 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
5268 return (torrent->inactiveSeedingTimeLimit() >= 0);
5272 void SessionImpl::configureDeferred()
5274 if (m_deferredConfigureScheduled)
5275 return;
5277 m_deferredConfigureScheduled = true;
5278 QMetaObject::invokeMethod(this, qOverload<>(&SessionImpl::configure), Qt::QueuedConnection);
5281 // Enable IP Filtering
5282 // this method creates ban list from scratch combining user ban list and 3rd party ban list file
5283 void SessionImpl::enableIPFilter()
5285 qDebug("Enabling IPFilter");
5286 // 1. Parse the IP filter
5287 // 2. In the slot add the manually banned IPs to the provided lt::ip_filter
5288 // 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
5289 // set between clearing the old one and setting the new one.
5290 if (!m_filterParser)
5292 m_filterParser = new FilterParserThread(this);
5293 connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &SessionImpl::handleIPFilterParsed);
5294 connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &SessionImpl::handleIPFilterError);
5296 m_filterParser->processFilterFile(IPFilterFile());
5299 // Disable IP Filtering
5300 void SessionImpl::disableIPFilter()
5302 qDebug("Disabling IPFilter");
5303 if (m_filterParser)
5305 disconnect(m_filterParser.data(), nullptr, this, nullptr);
5306 delete m_filterParser;
5309 // Add the banned IPs after the IPFilter disabling
5310 // which creates an empty filter and overrides all previously
5311 // applied bans.
5312 lt::ip_filter filter;
5313 processBannedIPs(filter);
5314 m_nativeSession->set_ip_filter(filter);
5317 const SessionStatus &SessionImpl::status() const
5319 return m_status;
5322 const CacheStatus &SessionImpl::cacheStatus() const
5324 return m_cacheStatus;
5327 void SessionImpl::enqueueRefresh()
5329 Q_ASSERT(!m_refreshEnqueued);
5331 QTimer::singleShot(refreshInterval(), Qt::CoarseTimer, this, [this]
5333 m_nativeSession->post_torrent_updates();
5334 m_nativeSession->post_session_stats();
5336 if (m_torrentsQueueChanged)
5338 m_torrentsQueueChanged = false;
5339 m_needSaveTorrentsQueue = true;
5343 m_refreshEnqueued = true;
5346 void SessionImpl::handleIPFilterParsed(const int ruleCount)
5348 if (m_filterParser)
5350 lt::ip_filter filter = m_filterParser->IPfilter();
5351 processBannedIPs(filter);
5352 m_nativeSession->set_ip_filter(filter);
5354 LogMsg(tr("Successfully parsed the IP filter file. Number of rules applied: %1").arg(ruleCount));
5355 emit IPFilterParsed(false, ruleCount);
5358 void SessionImpl::handleIPFilterError()
5360 lt::ip_filter filter;
5361 processBannedIPs(filter);
5362 m_nativeSession->set_ip_filter(filter);
5364 LogMsg(tr("Failed to parse the IP filter file"), Log::WARNING);
5365 emit IPFilterParsed(true, 0);
5368 std::vector<lt::alert *> SessionImpl::getPendingAlerts(const lt::time_duration time) const
5370 if (time > lt::time_duration::zero())
5371 m_nativeSession->wait_for_alert(time);
5373 std::vector<lt::alert *> alerts;
5374 m_nativeSession->pop_alerts(&alerts);
5375 return alerts;
5378 TorrentContentLayout SessionImpl::torrentContentLayout() const
5380 return m_torrentContentLayout;
5383 void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value)
5385 m_torrentContentLayout = value;
5388 // Read alerts sent by libtorrent session
5389 void SessionImpl::readAlerts()
5391 const std::vector<lt::alert *> alerts = getPendingAlerts();
5393 Q_ASSERT(m_loadedTorrents.isEmpty());
5394 Q_ASSERT(m_receivedAddTorrentAlertsCount == 0);
5396 if (!isRestored())
5397 m_loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
5399 for (const lt::alert *a : alerts)
5400 handleAlert(a);
5402 if (m_receivedAddTorrentAlertsCount > 0)
5404 emit addTorrentAlertsReceived(m_receivedAddTorrentAlertsCount);
5405 m_receivedAddTorrentAlertsCount = 0;
5407 if (!m_loadedTorrents.isEmpty())
5409 if (isRestored())
5410 m_torrentsQueueChanged = true;
5412 emit torrentsLoaded(m_loadedTorrents);
5413 m_loadedTorrents.clear();
5417 processTrackerStatuses();
5420 void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
5422 ++m_receivedAddTorrentAlertsCount;
5424 if (alert->error)
5426 const QString msg = QString::fromStdString(alert->message());
5427 LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(msg), Log::WARNING);
5428 emit loadTorrentFailed(msg);
5430 const lt::add_torrent_params &params = alert->params;
5431 const bool hasMetadata = (params.ti && params.ti->is_valid());
5433 #ifdef QBT_USES_LIBTORRENT2
5434 const InfoHash infoHash {(hasMetadata ? params.ti->info_hashes() : params.info_hashes)};
5435 if (infoHash.isHybrid())
5436 m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
5437 #else
5438 const InfoHash infoHash {(hasMetadata ? params.ti->info_hash() : params.info_hash)};
5439 #endif
5440 if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
5441 ; loadingTorrentsIter != m_loadingTorrents.end())
5443 emit addTorrentFailed(infoHash, msg);
5444 m_loadingTorrents.erase(loadingTorrentsIter);
5446 else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
5447 ; downloadedMetadataIter != m_downloadedMetadata.end())
5449 m_downloadedMetadata.erase(downloadedMetadataIter);
5450 if (infoHash.isHybrid())
5452 // index hybrid magnet links by both v1 and v2 info hashes
5453 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5454 m_downloadedMetadata.remove(altID);
5458 return;
5461 #ifdef QBT_USES_LIBTORRENT2
5462 const InfoHash infoHash {alert->handle.info_hashes()};
5463 #else
5464 const InfoHash infoHash {alert->handle.info_hash()};
5465 #endif
5466 const auto torrentID = TorrentID::fromInfoHash(infoHash);
5468 if (const auto loadingTorrentsIter = m_loadingTorrents.find(torrentID)
5469 ; loadingTorrentsIter != m_loadingTorrents.end())
5471 const LoadTorrentParams params = loadingTorrentsIter.value();
5472 m_loadingTorrents.erase(loadingTorrentsIter);
5474 Torrent *torrent = createTorrent(alert->handle, params);
5475 m_loadedTorrents.append(torrent);
5477 else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(torrentID)
5478 ; downloadedMetadataIter != m_downloadedMetadata.end())
5480 downloadedMetadataIter.value() = alert->handle;
5481 if (infoHash.isHybrid())
5483 // index hybrid magnet links by both v1 and v2 info hashes
5484 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5485 m_downloadedMetadata[altID] = alert->handle;
5490 void SessionImpl::handleAlert(const lt::alert *alert)
5494 switch (alert->type())
5496 #ifdef QBT_USES_LIBTORRENT2
5497 case lt::file_prio_alert::alert_type:
5498 #endif
5499 case lt::file_renamed_alert::alert_type:
5500 case lt::file_rename_failed_alert::alert_type:
5501 case lt::file_completed_alert::alert_type:
5502 case lt::torrent_finished_alert::alert_type:
5503 case lt::save_resume_data_alert::alert_type:
5504 case lt::save_resume_data_failed_alert::alert_type:
5505 case lt::torrent_paused_alert::alert_type:
5506 case lt::torrent_resumed_alert::alert_type:
5507 case lt::fastresume_rejected_alert::alert_type:
5508 case lt::torrent_checked_alert::alert_type:
5509 case lt::metadata_received_alert::alert_type:
5510 case lt::performance_alert::alert_type:
5511 dispatchTorrentAlert(static_cast<const lt::torrent_alert *>(alert));
5512 break;
5513 case lt::state_update_alert::alert_type:
5514 handleStateUpdateAlert(static_cast<const lt::state_update_alert *>(alert));
5515 break;
5516 case lt::session_error_alert::alert_type:
5517 handleSessionErrorAlert(static_cast<const lt::session_error_alert *>(alert));
5518 break;
5519 case lt::session_stats_alert::alert_type:
5520 handleSessionStatsAlert(static_cast<const lt::session_stats_alert *>(alert));
5521 break;
5522 case lt::tracker_announce_alert::alert_type:
5523 case lt::tracker_error_alert::alert_type:
5524 case lt::tracker_reply_alert::alert_type:
5525 case lt::tracker_warning_alert::alert_type:
5526 handleTrackerAlert(static_cast<const lt::tracker_alert *>(alert));
5527 break;
5528 case lt::file_error_alert::alert_type:
5529 handleFileErrorAlert(static_cast<const lt::file_error_alert *>(alert));
5530 break;
5531 case lt::add_torrent_alert::alert_type:
5532 handleAddTorrentAlert(static_cast<const lt::add_torrent_alert *>(alert));
5533 break;
5534 case lt::torrent_removed_alert::alert_type:
5535 handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert *>(alert));
5536 break;
5537 case lt::torrent_deleted_alert::alert_type:
5538 handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert *>(alert));
5539 break;
5540 case lt::torrent_delete_failed_alert::alert_type:
5541 handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert *>(alert));
5542 break;
5543 case lt::torrent_need_cert_alert::alert_type:
5544 handleTorrentNeedCertAlert(static_cast<const lt::torrent_need_cert_alert *>(alert));
5545 break;
5546 case lt::portmap_error_alert::alert_type:
5547 handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert *>(alert));
5548 break;
5549 case lt::portmap_alert::alert_type:
5550 handlePortmapAlert(static_cast<const lt::portmap_alert *>(alert));
5551 break;
5552 case lt::peer_blocked_alert::alert_type:
5553 handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert *>(alert));
5554 break;
5555 case lt::peer_ban_alert::alert_type:
5556 handlePeerBanAlert(static_cast<const lt::peer_ban_alert *>(alert));
5557 break;
5558 case lt::url_seed_alert::alert_type:
5559 handleUrlSeedAlert(static_cast<const lt::url_seed_alert *>(alert));
5560 break;
5561 case lt::listen_succeeded_alert::alert_type:
5562 handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert *>(alert));
5563 break;
5564 case lt::listen_failed_alert::alert_type:
5565 handleListenFailedAlert(static_cast<const lt::listen_failed_alert *>(alert));
5566 break;
5567 case lt::external_ip_alert::alert_type:
5568 handleExternalIPAlert(static_cast<const lt::external_ip_alert *>(alert));
5569 break;
5570 case lt::alerts_dropped_alert::alert_type:
5571 handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(alert));
5572 break;
5573 case lt::storage_moved_alert::alert_type:
5574 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert *>(alert));
5575 break;
5576 case lt::storage_moved_failed_alert::alert_type:
5577 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert *>(alert));
5578 break;
5579 case lt::socks5_alert::alert_type:
5580 handleSocks5Alert(static_cast<const lt::socks5_alert *>(alert));
5581 break;
5582 case lt::i2p_alert::alert_type:
5583 handleI2PAlert(static_cast<const lt::i2p_alert *>(alert));
5584 break;
5585 #ifdef QBT_USES_LIBTORRENT2
5586 case lt::torrent_conflict_alert::alert_type:
5587 handleTorrentConflictAlert(static_cast<const lt::torrent_conflict_alert *>(alert));
5588 break;
5589 #endif
5592 catch (const std::exception &exc)
5594 qWarning() << "Caught exception in " << Q_FUNC_INFO << ": " << QString::fromStdString(exc.what());
5598 void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert *alert)
5600 // The torrent can be deleted between the time the resume data was requested and
5601 // the time we received the appropriate alert. We have to decrease `m_numResumeData` anyway,
5602 // so we do this before checking for an existing torrent.
5603 if ((alert->type() == lt::save_resume_data_alert::alert_type)
5604 || (alert->type() == lt::save_resume_data_failed_alert::alert_type))
5606 --m_numResumeData;
5609 const TorrentID torrentID {alert->handle.info_hash()};
5610 TorrentImpl *torrent = m_torrents.value(torrentID);
5611 #ifdef QBT_USES_LIBTORRENT2
5612 if (!torrent && (alert->type() == lt::metadata_received_alert::alert_type))
5614 const InfoHash infoHash {alert->handle.info_hashes()};
5615 if (infoHash.isHybrid())
5616 torrent = m_torrents.value(TorrentID::fromSHA1Hash(infoHash.v1()));
5618 #endif
5620 if (torrent)
5622 torrent->handleAlert(alert);
5623 return;
5626 switch (alert->type())
5628 case lt::metadata_received_alert::alert_type:
5629 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert *>(alert));
5630 break;
5634 TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
5636 auto *const torrent = new TorrentImpl(this, m_nativeSession, nativeHandle, params);
5637 m_torrents.insert(torrent->id(), torrent);
5638 if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
5639 m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent);
5641 if (isRestored())
5643 if (params.addToQueueTop)
5644 nativeHandle.queue_position_top();
5646 torrent->requestResumeData(lt::torrent_handle::save_info_dict);
5648 // The following is useless for newly added magnet
5649 if (torrent->hasMetadata())
5651 if (!torrentExportDirectory().isEmpty())
5652 exportTorrentFile(torrent, torrentExportDirectory());
5656 if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
5657 && !m_seedingLimitTimer->isActive())
5659 m_seedingLimitTimer->start();
5662 if (!isRestored())
5664 LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name()));
5666 else
5668 LogMsg(tr("Added new torrent. Torrent: \"%1\"").arg(torrent->name()));
5669 emit torrentAdded(torrent);
5672 // Torrent could have error just after adding to libtorrent
5673 if (torrent->hasError())
5674 LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: \"%2\"").arg(torrent->name(), torrent->error()), Log::WARNING);
5676 return torrent;
5679 void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *alert)
5681 #ifdef QBT_USES_LIBTORRENT2
5682 const auto id = TorrentID::fromInfoHash(alert->info_hashes);
5683 #else
5684 const auto id = TorrentID::fromInfoHash(alert->info_hash);
5685 #endif
5687 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5688 if (removingTorrentDataIter != m_removingTorrents.end())
5690 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
5692 LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5693 m_removingTorrents.erase(removingTorrentDataIter);
5698 void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert)
5700 #ifdef QBT_USES_LIBTORRENT2
5701 const auto id = TorrentID::fromInfoHash(alert->info_hashes);
5702 #else
5703 const auto id = TorrentID::fromInfoHash(alert->info_hash);
5704 #endif
5706 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5707 if (removingTorrentDataIter == m_removingTorrents.end())
5708 return;
5710 // torrent_deleted_alert can also be posted due to deletion of partfile. Ignore it in such a case.
5711 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
5712 return;
5714 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
5715 LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5716 m_removingTorrents.erase(removingTorrentDataIter);
5719 void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert)
5721 #ifdef QBT_USES_LIBTORRENT2
5722 const auto id = TorrentID::fromInfoHash(alert->info_hashes);
5723 #else
5724 const auto id = TorrentID::fromInfoHash(alert->info_hash);
5725 #endif
5727 const auto removingTorrentDataIter = m_removingTorrents.find(id);
5728 if (removingTorrentDataIter == m_removingTorrents.end())
5729 return;
5731 if (alert->error)
5733 // libtorrent won't delete the directory if it contains files not listed in the torrent,
5734 // so we remove the directory ourselves
5735 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
5737 LogMsg(tr("Removed torrent but failed to delete its content and/or partfile. Torrent: \"%1\". Error: \"%2\"")
5738 .arg(removingTorrentDataIter->name, QString::fromLocal8Bit(alert->error.message().c_str()))
5739 , Log::WARNING);
5741 else // torrent without metadata, hence no files on disk
5743 LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
5746 m_removingTorrents.erase(removingTorrentDataIter);
5749 void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *alert)
5751 #ifdef QBT_USES_LIBTORRENT2
5752 const InfoHash infoHash {alert->handle.info_hashes()};
5753 #else
5754 const InfoHash infoHash {alert->handle.info_hash()};
5755 #endif
5756 const auto torrentID = TorrentID::fromInfoHash(infoHash);
5758 TorrentImpl *const torrent = m_torrents.value(torrentID);
5759 if (!torrent) [[unlikely]]
5760 return;
5762 if (!torrent->applySSLParameters())
5764 LogMsg(tr("Torrent is missing SSL parameters. Torrent: \"%1\". Message: \"%2\"").arg(torrent->name(), QString::fromStdString(alert->message()))
5765 , Log::WARNING);
5769 void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *alert)
5771 const TorrentID torrentID {alert->handle.info_hash()};
5773 bool found = false;
5774 if (const auto iter = m_downloadedMetadata.find(torrentID); iter != m_downloadedMetadata.end())
5776 found = true;
5777 m_downloadedMetadata.erase(iter);
5779 #ifdef QBT_USES_LIBTORRENT2
5780 const InfoHash infoHash {alert->handle.info_hashes()};
5781 if (infoHash.isHybrid())
5783 const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
5784 if (const auto iter = m_downloadedMetadata.find(altID); iter != m_downloadedMetadata.end())
5786 found = true;
5787 m_downloadedMetadata.erase(iter);
5790 #endif
5791 if (found)
5793 const TorrentInfo metadata {*alert->handle.torrent_file()};
5794 m_nativeSession->remove_torrent(alert->handle, lt::session::delete_files);
5796 emit metadataDownloaded(metadata);
5800 void SessionImpl::handleFileErrorAlert(const lt::file_error_alert *alert)
5802 TorrentImpl *const torrent = m_torrents.value(alert->handle.info_hash());
5803 if (!torrent)
5804 return;
5806 torrent->handleAlert(alert);
5808 const TorrentID id = torrent->id();
5809 if (!m_recentErroredTorrents.contains(id))
5811 m_recentErroredTorrents.insert(id);
5813 const QString msg = QString::fromStdString(alert->message());
5814 LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: \"%3\"")
5815 .arg(torrent->name(), QString::fromUtf8(alert->filename()), msg)
5816 , Log::WARNING);
5817 emit fullDiskError(torrent, msg);
5820 m_recentErroredTorrentsTimer->start();
5823 void SessionImpl::handlePortmapWarningAlert(const lt::portmap_error_alert *alert)
5825 LogMsg(tr("UPnP/NAT-PMP port mapping failed. Message: \"%1\"").arg(QString::fromStdString(alert->message())), Log::WARNING);
5828 void SessionImpl::handlePortmapAlert(const lt::portmap_alert *alert)
5830 qDebug("UPnP Success, msg: %s", alert->message().c_str());
5831 LogMsg(tr("UPnP/NAT-PMP port mapping succeeded. Message: \"%1\"").arg(QString::fromStdString(alert->message())), Log::INFO);
5834 void SessionImpl::handlePeerBlockedAlert(const lt::peer_blocked_alert *alert)
5836 QString reason;
5837 switch (alert->reason)
5839 case lt::peer_blocked_alert::ip_filter:
5840 reason = tr("IP filter", "this peer was blocked. Reason: IP filter.");
5841 break;
5842 case lt::peer_blocked_alert::port_filter:
5843 reason = tr("filtered port (%1)", "this peer was blocked. Reason: filtered port (8899).").arg(QString::number(alert->endpoint.port()));
5844 break;
5845 case lt::peer_blocked_alert::i2p_mixed:
5846 reason = tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg(u"I2P"_s); // don't translate I2P
5847 break;
5848 case lt::peer_blocked_alert::privileged_ports:
5849 reason = tr("privileged port (%1)", "this peer was blocked. Reason: privileged port (80).").arg(QString::number(alert->endpoint.port()));
5850 break;
5851 case lt::peer_blocked_alert::utp_disabled:
5852 reason = tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(C_UTP); // don't translate μTP
5853 break;
5854 case lt::peer_blocked_alert::tcp_disabled:
5855 reason = tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg(u"TCP"_s); // don't translate TCP
5856 break;
5859 const QString ip {toString(alert->endpoint.address())};
5860 if (!ip.isEmpty())
5861 Logger::instance()->addPeer(ip, true, reason);
5864 void SessionImpl::handlePeerBanAlert(const lt::peer_ban_alert *alert)
5866 const QString ip {toString(alert->endpoint.address())};
5867 if (!ip.isEmpty())
5868 Logger::instance()->addPeer(ip, false);
5871 void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert *alert)
5873 const TorrentImpl *torrent = m_torrents.value(alert->handle.info_hash());
5874 if (!torrent)
5875 return;
5877 if (alert->error)
5879 LogMsg(tr("URL seed DNS lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
5880 .arg(torrent->name(), QString::fromUtf8(alert->server_url()), QString::fromStdString(alert->message()))
5881 , Log::WARNING);
5883 else
5885 LogMsg(tr("Received error message from URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
5886 .arg(torrent->name(), QString::fromUtf8(alert->server_url()), QString::fromUtf8(alert->error_message()))
5887 , Log::WARNING);
5891 void SessionImpl::handleListenSucceededAlert(const lt::listen_succeeded_alert *alert)
5893 const QString proto {toString(alert->socket_type)};
5894 LogMsg(tr("Successfully listening on IP. IP: \"%1\". Port: \"%2/%3\"")
5895 .arg(toString(alert->address), proto, QString::number(alert->port)), Log::INFO);
5898 void SessionImpl::handleListenFailedAlert(const lt::listen_failed_alert *alert)
5900 const QString proto {toString(alert->socket_type)};
5901 LogMsg(tr("Failed to listen on IP. IP: \"%1\". Port: \"%2/%3\". Reason: \"%4\"")
5902 .arg(toString(alert->address), proto, QString::number(alert->port)
5903 , QString::fromLocal8Bit(alert->error.message().c_str())), Log::CRITICAL);
5906 void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *alert)
5908 const QString externalIP {toString(alert->external_address)};
5909 LogMsg(tr("Detected external IP. IP: \"%1\"")
5910 .arg(externalIP), Log::INFO);
5912 if (m_lastExternalIP != externalIP)
5914 if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty())
5915 reannounceToAllTrackers();
5916 m_lastExternalIP = externalIP;
5920 void SessionImpl::handleSessionErrorAlert(const lt::session_error_alert *alert) const
5922 LogMsg(tr("BitTorrent session encountered a serious error. Reason: \"%1\"")
5923 .arg(QString::fromStdString(alert->message())), Log::CRITICAL);
5926 void SessionImpl::handleSessionStatsAlert(const lt::session_stats_alert *alert)
5928 if (m_refreshEnqueued)
5929 m_refreshEnqueued = false;
5930 else
5931 enqueueRefresh();
5933 const int64_t interval = lt::total_microseconds(alert->timestamp() - m_statsLastTimestamp);
5934 if (interval <= 0)
5935 return;
5937 m_statsLastTimestamp = alert->timestamp();
5939 const auto stats = alert->counters();
5941 m_status.hasIncomingConnections = static_cast<bool>(stats[m_metricIndices.net.hasIncomingConnections]);
5943 const int64_t ipOverheadDownload = stats[m_metricIndices.net.recvIPOverheadBytes];
5944 const int64_t ipOverheadUpload = stats[m_metricIndices.net.sentIPOverheadBytes];
5945 const int64_t totalDownload = stats[m_metricIndices.net.recvBytes] + ipOverheadDownload;
5946 const int64_t totalUpload = stats[m_metricIndices.net.sentBytes] + ipOverheadUpload;
5947 const int64_t totalPayloadDownload = stats[m_metricIndices.net.recvPayloadBytes];
5948 const int64_t totalPayloadUpload = stats[m_metricIndices.net.sentPayloadBytes];
5949 const int64_t trackerDownload = stats[m_metricIndices.net.recvTrackerBytes];
5950 const int64_t trackerUpload = stats[m_metricIndices.net.sentTrackerBytes];
5951 const int64_t dhtDownload = stats[m_metricIndices.dht.dhtBytesIn];
5952 const int64_t dhtUpload = stats[m_metricIndices.dht.dhtBytesOut];
5954 const auto calcRate = [interval](const qint64 previous, const qint64 current) -> qint64
5956 Q_ASSERT(current >= previous);
5957 Q_ASSERT(interval >= 0);
5958 return (((current - previous) * lt::microseconds(1s).count()) / interval);
5961 m_status.payloadDownloadRate = calcRate(m_status.totalPayloadDownload, totalPayloadDownload);
5962 m_status.payloadUploadRate = calcRate(m_status.totalPayloadUpload, totalPayloadUpload);
5963 m_status.downloadRate = calcRate(m_status.totalDownload, totalDownload);
5964 m_status.uploadRate = calcRate(m_status.totalUpload, totalUpload);
5965 m_status.ipOverheadDownloadRate = calcRate(m_status.ipOverheadDownload, ipOverheadDownload);
5966 m_status.ipOverheadUploadRate = calcRate(m_status.ipOverheadUpload, ipOverheadUpload);
5967 m_status.dhtDownloadRate = calcRate(m_status.dhtDownload, dhtDownload);
5968 m_status.dhtUploadRate = calcRate(m_status.dhtUpload, dhtUpload);
5969 m_status.trackerDownloadRate = calcRate(m_status.trackerDownload, trackerDownload);
5970 m_status.trackerUploadRate = calcRate(m_status.trackerUpload, trackerUpload);
5972 m_status.totalPayloadDownload = totalPayloadDownload;
5973 m_status.totalPayloadUpload = totalPayloadUpload;
5974 m_status.ipOverheadDownload = ipOverheadDownload;
5975 m_status.ipOverheadUpload = ipOverheadUpload;
5976 m_status.trackerDownload = trackerDownload;
5977 m_status.trackerUpload = trackerUpload;
5978 m_status.dhtDownload = dhtDownload;
5979 m_status.dhtUpload = dhtUpload;
5980 m_status.totalWasted = stats[m_metricIndices.net.recvRedundantBytes]
5981 + stats[m_metricIndices.net.recvFailedBytes];
5982 m_status.dhtNodes = stats[m_metricIndices.dht.dhtNodes];
5983 m_status.diskReadQueue = stats[m_metricIndices.peer.numPeersUpDisk];
5984 m_status.diskWriteQueue = stats[m_metricIndices.peer.numPeersDownDisk];
5985 m_status.peersCount = stats[m_metricIndices.peer.numPeersConnected];
5987 if (totalDownload > m_status.totalDownload)
5989 m_status.totalDownload = totalDownload;
5990 m_isStatisticsDirty = true;
5993 if (totalUpload > m_status.totalUpload)
5995 m_status.totalUpload = totalUpload;
5996 m_isStatisticsDirty = true;
5999 m_status.allTimeDownload = m_previouslyDownloaded + m_status.totalDownload;
6000 m_status.allTimeUpload = m_previouslyUploaded + m_status.totalUpload;
6002 if (m_statisticsLastUpdateTimer.hasExpired(STATISTICS_SAVE_INTERVAL))
6003 saveStatistics();
6005 m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
6006 m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
6008 #ifndef QBT_USES_LIBTORRENT2
6009 const int64_t numBlocksRead = stats[m_metricIndices.disk.numBlocksRead];
6010 const int64_t numBlocksCacheHits = stats[m_metricIndices.disk.numBlocksCacheHits];
6011 m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max<int64_t>((numBlocksCacheHits + numBlocksRead), 1);
6012 #endif
6014 const int64_t totalJobs = stats[m_metricIndices.disk.writeJobs] + stats[m_metricIndices.disk.readJobs]
6015 + stats[m_metricIndices.disk.hashJobs];
6016 m_cacheStatus.averageJobTime = (totalJobs > 0)
6017 ? (stats[m_metricIndices.disk.diskJobTime] / totalJobs) : 0;
6019 emit statsUpdated();
6022 void SessionImpl::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *alert) const
6024 LogMsg(tr("Error: Internal alert queue is full and alerts are dropped, you might see degraded performance. Dropped alert type: \"%1\". Message: \"%2\"")
6025 .arg(QString::fromStdString(alert->dropped_alerts.to_string()), QString::fromStdString(alert->message())), Log::CRITICAL);
6028 void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *alert)
6030 Q_ASSERT(!m_moveStorageQueue.isEmpty());
6032 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
6033 Q_ASSERT(currentJob.torrentHandle == alert->handle);
6035 const Path newPath {QString::fromUtf8(alert->storage_path())};
6036 Q_ASSERT(newPath == currentJob.path);
6038 #ifdef QBT_USES_LIBTORRENT2
6039 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
6040 #else
6041 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
6042 #endif
6044 TorrentImpl *torrent = m_torrents.value(id);
6045 const QString torrentName = (torrent ? torrent->name() : id.toString());
6046 LogMsg(tr("Moved torrent successfully. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, newPath.toString()));
6048 handleMoveTorrentStorageJobFinished(newPath);
6051 void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *alert)
6053 Q_ASSERT(!m_moveStorageQueue.isEmpty());
6055 const MoveStorageJob &currentJob = m_moveStorageQueue.first();
6056 Q_ASSERT(currentJob.torrentHandle == alert->handle);
6058 #ifdef QBT_USES_LIBTORRENT2
6059 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
6060 #else
6061 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
6062 #endif
6064 TorrentImpl *torrent = m_torrents.value(id);
6065 const QString torrentName = (torrent ? torrent->name() : id.toString());
6066 const Path currentLocation = (torrent ? torrent->actualStorageLocation()
6067 : Path(alert->handle.status(lt::torrent_handle::query_save_path).save_path));
6068 const QString errorMessage = QString::fromStdString(alert->message());
6069 LogMsg(tr("Failed to move torrent. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: \"%4\"")
6070 .arg(torrentName, currentLocation.toString(), currentJob.path.toString(), errorMessage), Log::WARNING);
6072 handleMoveTorrentStorageJobFinished(currentLocation);
6075 void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert)
6077 QVector<Torrent *> updatedTorrents;
6078 updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(alert->status.size()));
6080 for (const lt::torrent_status &status : alert->status)
6082 #ifdef QBT_USES_LIBTORRENT2
6083 const auto id = TorrentID::fromInfoHash(status.info_hashes);
6084 #else
6085 const auto id = TorrentID::fromInfoHash(status.info_hash);
6086 #endif
6087 TorrentImpl *const torrent = m_torrents.value(id);
6088 if (!torrent)
6089 continue;
6091 torrent->handleStateUpdate(status);
6092 updatedTorrents.push_back(torrent);
6095 if (!updatedTorrents.isEmpty())
6096 emit torrentsUpdated(updatedTorrents);
6098 if (m_needSaveTorrentsQueue)
6099 saveTorrentsQueue();
6101 if (m_refreshEnqueued)
6102 m_refreshEnqueued = false;
6103 else
6104 enqueueRefresh();
6107 void SessionImpl::handleSocks5Alert(const lt::socks5_alert *alert) const
6109 if (alert->error)
6111 const auto addr = alert->ip.address();
6112 const QString endpoint = (addr.is_v6() ? u"[%1]:%2"_s : u"%1:%2"_s)
6113 .arg(QString::fromStdString(addr.to_string()), QString::number(alert->ip.port()));
6114 LogMsg(tr("SOCKS5 proxy error. Address: %1. Message: \"%2\".")
6115 .arg(endpoint, QString::fromLocal8Bit(alert->error.message().c_str()))
6116 , Log::WARNING);
6120 void SessionImpl::handleI2PAlert(const lt::i2p_alert *alert) const
6122 if (alert->error)
6124 LogMsg(tr("I2P error. Message: \"%1\".")
6125 .arg(QString::fromStdString(alert->message())), Log::WARNING);
6129 void SessionImpl::handleTrackerAlert(const lt::tracker_alert *alert)
6131 TorrentImpl *torrent = m_torrents.value(alert->handle.info_hash());
6132 if (!torrent)
6133 return;
6135 QMap<int, int> &updateInfo = m_updatedTrackerStatuses[torrent->nativeHandle()][std::string(alert->tracker_url())][alert->local_endpoint];
6137 if (alert->type() == lt::tracker_reply_alert::alert_type)
6139 const int numPeers = static_cast<const lt::tracker_reply_alert *>(alert)->num_peers;
6140 #ifdef QBT_USES_LIBTORRENT2
6141 const int protocolVersionNum = (static_cast<const lt::tracker_reply_alert *>(alert)->version == lt::protocol_version::V1) ? 1 : 2;
6142 #else
6143 const int protocolVersionNum = 1;
6144 #endif
6145 updateInfo.insert(protocolVersionNum, numPeers);
6149 #ifdef QBT_USES_LIBTORRENT2
6150 void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *alert)
6152 const auto torrentIDv1 = TorrentID::fromSHA1Hash(alert->metadata->info_hashes().v1);
6153 const auto torrentIDv2 = TorrentID::fromSHA256Hash(alert->metadata->info_hashes().v2);
6154 TorrentImpl *torrent1 = m_torrents.value(torrentIDv1);
6155 TorrentImpl *torrent2 = m_torrents.value(torrentIDv2);
6156 if (torrent2)
6158 if (torrent1)
6159 deleteTorrent(torrentIDv1);
6160 else
6161 cancelDownloadMetadata(torrentIDv1);
6163 invokeAsync([torrentHandle = torrent2->nativeHandle(), metadata = alert->metadata]
6167 torrentHandle.set_metadata(metadata->info_section());
6169 catch (const std::exception &) {}
6172 else if (torrent1)
6174 if (!torrent2)
6175 cancelDownloadMetadata(torrentIDv2);
6177 invokeAsync([torrentHandle = torrent1->nativeHandle(), metadata = alert->metadata]
6181 torrentHandle.set_metadata(metadata->info_section());
6183 catch (const std::exception &) {}
6186 else
6188 cancelDownloadMetadata(torrentIDv1);
6189 cancelDownloadMetadata(torrentIDv2);
6192 if (!torrent1 || !torrent2)
6193 emit metadataDownloaded(TorrentInfo(*alert->metadata));
6195 #endif
6197 void SessionImpl::processTrackerStatuses()
6199 if (m_updatedTrackerStatuses.isEmpty())
6200 return;
6202 for (auto it = m_updatedTrackerStatuses.cbegin(); it != m_updatedTrackerStatuses.cend(); ++it)
6203 updateTrackerEntryStatuses(it.key(), it.value());
6205 m_updatedTrackerStatuses.clear();
6208 void SessionImpl::saveStatistics() const
6210 if (!m_isStatisticsDirty)
6211 return;
6213 const QVariantHash stats {
6214 {u"AlltimeDL"_s, m_status.allTimeDownload},
6215 {u"AlltimeUL"_s, m_status.allTimeUpload}};
6216 std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_s);
6217 settings->setValue(u"Stats/AllStats"_s, stats);
6219 m_statisticsLastUpdateTimer.start();
6220 m_isStatisticsDirty = false;
6223 void SessionImpl::loadStatistics()
6225 const std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_s);
6226 const QVariantHash value = settings->value(u"Stats/AllStats"_s).toHash();
6228 m_previouslyDownloaded = value[u"AlltimeDL"_s].toLongLong();
6229 m_previouslyUploaded = value[u"AlltimeUL"_s].toLongLong();
6232 void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers)
6234 invokeAsync([this, torrentHandle = std::move(torrentHandle), updatedTrackers = std::move(updatedTrackers)]() mutable
6238 std::vector<lt::announce_entry> nativeTrackers = torrentHandle.trackers();
6239 invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers)
6240 , updatedTrackers = std::move(updatedTrackers)]
6242 TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
6243 if (!torrent || torrent->isStopped())
6244 return;
6246 QHash<QString, TrackerEntryStatus> trackers;
6247 trackers.reserve(updatedTrackers.size());
6248 for (const lt::announce_entry &announceEntry : nativeTrackers)
6250 const auto updatedTrackersIter = updatedTrackers.find(announceEntry.url);
6251 if (updatedTrackersIter == updatedTrackers.end())
6252 continue;
6254 const auto &updateInfo = updatedTrackersIter.value();
6255 TrackerEntryStatus status = torrent->updateTrackerEntryStatus(announceEntry, updateInfo);
6256 const QString url = status.url;
6257 trackers.emplace(url, std::move(status));
6260 emit trackerEntryStatusesUpdated(torrent, trackers);
6263 catch (const std::exception &)