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"
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>
64 #include <QHostAddress>
66 #include <QJsonDocument>
67 #include <QJsonObject>
69 #include <QNetworkAddressEntry>
70 #include <QNetworkInterface>
71 #include <QRegularExpression>
77 #include "base/algorithm.h"
78 #include "base/global.h"
79 #include "base/logger.h"
80 #include "base/net/proxyconfigurationmanager.h"
81 #include "base/preferences.h"
82 #include "base/profile.h"
83 #include "base/unicodestrings.h"
84 #include "base/utils/fs.h"
85 #include "base/utils/io.h"
86 #include "base/utils/net.h"
87 #include "base/utils/number.h"
88 #include "base/utils/random.h"
89 #include "base/version.h"
90 #include "bandwidthscheduler.h"
91 #include "bencoderesumedatastorage.h"
92 #include "customstorage.h"
93 #include "dbresumedatastorage.h"
94 #include "downloadpriority.h"
95 #include "extensiondata.h"
96 #include "filesearcher.h"
97 #include "filterparserthread.h"
98 #include "loadtorrentparams.h"
99 #include "lttypecast.h"
100 #include "nativesessionextension.h"
101 #include "portforwarderimpl.h"
102 #include "resumedatastorage.h"
103 #include "torrentcontentremover.h"
104 #include "torrentdescriptor.h"
105 #include "torrentimpl.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;
117 const char PEER_ID
[] = "qB";
118 const auto USER_AGENT
= QStringLiteral("qBittorrent/" QBT_VERSION_2
);
119 const QString DEFAULT_DHT_BOOTSTRAP_NODES
= u
"dht.libtorrent.org:25401, dht.transmissionbt.com:6881, router.silotis.us:6881"_s
;
121 void torrentQueuePositionUp(const lt::torrent_handle
&handle
)
125 handle
.queue_position_up();
127 catch (const std::exception
&exc
)
129 qDebug() << Q_FUNC_INFO
<< " fails: " << exc
.what();
133 void torrentQueuePositionDown(const lt::torrent_handle
&handle
)
137 handle
.queue_position_down();
139 catch (const std::exception
&exc
)
141 qDebug() << Q_FUNC_INFO
<< " fails: " << exc
.what();
145 void torrentQueuePositionTop(const lt::torrent_handle
&handle
)
149 handle
.queue_position_top();
151 catch (const std::exception
&exc
)
153 qDebug() << Q_FUNC_INFO
<< " fails: " << exc
.what();
157 void torrentQueuePositionBottom(const lt::torrent_handle
&handle
)
161 handle
.queue_position_bottom();
163 catch (const std::exception
&exc
)
165 qDebug() << Q_FUNC_INFO
<< " fails: " << exc
.what();
169 QMap
<QString
, CategoryOptions
> expandCategories(const QMap
<QString
, CategoryOptions
> &categories
)
171 QMap
<QString
, CategoryOptions
> expanded
= categories
;
173 for (auto i
= categories
.cbegin(); i
!= categories
.cend(); ++i
)
175 const QString
&category
= i
.key();
176 for (const QString
&subcat
: asConst(Session::expandCategory(category
)))
178 if (!expanded
.contains(subcat
))
179 expanded
[subcat
] = {};
186 QString
toString(const lt::socket_type_t socketType
)
190 #ifdef QBT_USES_LIBTORRENT2
191 case lt::socket_type_t::http
:
193 case lt::socket_type_t::http_ssl
:
194 return u
"HTTP_SSL"_s
;
196 case lt::socket_type_t::i2p
:
198 case lt::socket_type_t::socks5
:
200 #ifdef QBT_USES_LIBTORRENT2
201 case lt::socket_type_t::socks5_ssl
:
202 return u
"SOCKS5_SSL"_s
;
204 case lt::socket_type_t::tcp
:
206 case lt::socket_type_t::tcp_ssl
:
208 #ifdef QBT_USES_LIBTORRENT2
209 case lt::socket_type_t::utp
:
212 case lt::socket_type_t::udp
:
215 case lt::socket_type_t::utp_ssl
:
221 QString
toString(const lt::address
&address
)
225 return QString::fromLatin1(address
.to_string().c_str());
227 catch (const std::exception
&)
229 // suppress conversion error
234 template <typename T
>
237 LowerLimited(T limit
, T ret
)
243 explicit LowerLimited(T limit
)
244 : LowerLimited(limit
, limit
)
248 T
operator()(T val
) const
250 return val
<= m_limit
? m_ret
: val
;
258 template <typename T
>
259 LowerLimited
<T
> lowerLimited(T limit
) { return LowerLimited
<T
>(limit
); }
261 template <typename T
>
262 LowerLimited
<T
> lowerLimited(T limit
, T ret
) { return LowerLimited
<T
>(limit
, ret
); }
264 template <typename T
>
265 auto clampValue(const T lower
, const T upper
)
267 return [lower
, upper
](const T value
) -> T
269 return std::clamp(value
, lower
, upper
);
274 QString
convertIfaceNameToGuid(const QString
&name
)
276 // Under Windows XP or on Qt version <= 5.5 'name' will be a GUID already.
277 const QUuid
uuid(name
);
279 return uuid
.toString().toUpper(); // Libtorrent expects the GUID in uppercase
281 const std::wstring nameWStr
= name
.toStdWString();
283 const LONG res
= ::ConvertInterfaceNameToLuidW(nameWStr
.c_str(), &luid
);
287 if (::ConvertInterfaceLuidToGuid(&luid
, &guid
) == 0)
288 return QUuid(guid
).toString().toUpper();
295 constexpr lt::move_flags_t
toNative(const MoveStorageMode mode
)
299 case MoveStorageMode::FailIfExist
:
300 return lt::move_flags_t::fail_if_exist
;
301 case MoveStorageMode::KeepExistingFiles
:
302 return lt::move_flags_t::dont_replace
;
303 case MoveStorageMode::Overwrite
:
304 return lt::move_flags_t::always_replace_files
;
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
;
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
'/');
362 return category
.mid(sepIndex
+ 1);
367 QString
Session::parentCategoryName(const QString
&category
)
369 const int sepIndex
= category
.lastIndexOf(u
'/');
371 return category
.left(sepIndex
);
376 QStringList
Session::expandCategory(const QString
&category
)
380 while ((index
= category
.indexOf(u
'/', index
)) >= 0)
382 result
<< category
.left(index
);
390 #define BITTORRENT_KEY(name) u"BitTorrent/" name
391 #define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY(u"Session/") name
393 SessionImpl::SessionImpl(QObject
*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
)
415 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u
"CoalesceReadWrite"_s
), true)
417 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u
"CoalesceReadWrite"_s
), false)
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_saveStatisticsInterval(BITTORRENT_SESSION_KEY(u
"SaveStatisticsInterval"_s
), 15)
485 , m_shutdownTimeout(BITTORRENT_SESSION_KEY(u
"ShutdownTimeout"_s
), -1)
486 , m_port(BITTORRENT_SESSION_KEY(u
"Port"_s
), -1)
487 , m_sslEnabled(BITTORRENT_SESSION_KEY(u
"SSL/Enabled"_s
), false)
488 , m_sslPort(BITTORRENT_SESSION_KEY(u
"SSL/Port"_s
), -1)
489 , m_networkInterface(BITTORRENT_SESSION_KEY(u
"Interface"_s
))
490 , m_networkInterfaceName(BITTORRENT_SESSION_KEY(u
"InterfaceName"_s
))
491 , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY(u
"InterfaceAddress"_s
))
492 , m_encryption(BITTORRENT_SESSION_KEY(u
"Encryption"_s
), 0)
493 , m_maxActiveCheckingTorrents(BITTORRENT_SESSION_KEY(u
"MaxActiveCheckingTorrents"_s
), 1)
494 , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY(u
"ProxyPeerConnections"_s
), false)
495 , m_chokingAlgorithm(BITTORRENT_SESSION_KEY(u
"ChokingAlgorithm"_s
), ChokingAlgorithm::FixedSlots
496 , clampValue(ChokingAlgorithm::FixedSlots
, ChokingAlgorithm::RateBased
))
497 , m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY(u
"SeedChokingAlgorithm"_s
), SeedChokingAlgorithm::FastestUpload
498 , clampValue(SeedChokingAlgorithm::RoundRobin
, SeedChokingAlgorithm::AntiLeech
))
499 , m_storedTags(BITTORRENT_SESSION_KEY(u
"Tags"_s
))
500 , m_shareLimitAction(BITTORRENT_SESSION_KEY(u
"ShareLimitAction"_s
), ShareLimitAction::Stop
501 , [](const ShareLimitAction action
) { return (action
== ShareLimitAction::Default
) ? ShareLimitAction::Stop
: action
; })
502 , m_savePath(BITTORRENT_SESSION_KEY(u
"DefaultSavePath"_s
), specialFolderLocation(SpecialFolder::Downloads
))
503 , m_downloadPath(BITTORRENT_SESSION_KEY(u
"TempPath"_s
), (savePath() / Path(u
"temp"_s
)))
504 , m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY(u
"TempPathEnabled"_s
), false)
505 , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY(u
"SubcategoriesEnabled"_s
), false)
506 , m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY(u
"UseCategoryPathsInManualMode"_s
), false)
507 , m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY(u
"DisableAutoTMMByDefault"_s
), true)
508 , m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY(u
"DisableAutoTMMTriggers/CategoryChanged"_s
), false)
509 , m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY(u
"DisableAutoTMMTriggers/DefaultSavePathChanged"_s
), true)
510 , m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY(u
"DisableAutoTMMTriggers/CategorySavePathChanged"_s
), true)
511 , m_isTrackerEnabled(BITTORRENT_KEY(u
"TrackerEnabled"_s
), false)
512 , m_peerTurnover(BITTORRENT_SESSION_KEY(u
"PeerTurnover"_s
), 4)
513 , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY(u
"PeerTurnoverCutOff"_s
), 90)
514 , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY(u
"PeerTurnoverInterval"_s
), 300)
515 , m_requestQueueSize(BITTORRENT_SESSION_KEY(u
"RequestQueueSize"_s
), 500)
516 , m_isExcludedFileNamesEnabled(BITTORRENT_KEY(u
"ExcludedFileNamesEnabled"_s
), false)
517 , m_excludedFileNames(BITTORRENT_SESSION_KEY(u
"ExcludedFileNames"_s
))
518 , m_bannedIPs(u
"State/BannedIPs"_s
, QStringList(), Algorithm::sorted
<QStringList
>)
519 , m_resumeDataStorageType(BITTORRENT_SESSION_KEY(u
"ResumeDataStorageType"_s
), ResumeDataStorageType::Legacy
)
520 , m_isMergeTrackersEnabled(BITTORRENT_KEY(u
"MergeTrackersEnabled"_s
), false)
521 , m_isI2PEnabled
{BITTORRENT_SESSION_KEY(u
"I2P/Enabled"_s
), false}
522 , m_I2PAddress
{BITTORRENT_SESSION_KEY(u
"I2P/Address"_s
), u
"127.0.0.1"_s
}
523 , m_I2PPort
{BITTORRENT_SESSION_KEY(u
"I2P/Port"_s
), 7656}
524 , m_I2PMixedMode
{BITTORRENT_SESSION_KEY(u
"I2P/MixedMode"_s
), false}
525 , m_I2PInboundQuantity
{BITTORRENT_SESSION_KEY(u
"I2P/InboundQuantity"_s
), 3}
526 , m_I2POutboundQuantity
{BITTORRENT_SESSION_KEY(u
"I2P/OutboundQuantity"_s
), 3}
527 , m_I2PInboundLength
{BITTORRENT_SESSION_KEY(u
"I2P/InboundLength"_s
), 3}
528 , m_I2POutboundLength
{BITTORRENT_SESSION_KEY(u
"I2P/OutboundLength"_s
), 3}
529 , m_torrentContentRemoveOption
{BITTORRENT_SESSION_KEY(u
"TorrentContentRemoveOption"_s
), TorrentContentRemoveOption::Delete
}
530 , m_startPaused
{BITTORRENT_SESSION_KEY(u
"StartPaused"_s
)}
531 , m_seedingLimitTimer
{new QTimer(this)}
532 , m_resumeDataTimer
{new QTimer(this)}
533 , m_ioThread
{new QThread
}
534 , m_asyncWorker
{new QThreadPool(this)}
535 , m_recentErroredTorrentsTimer
{new QTimer(this)}
537 // It is required to perform async access to libtorrent sequentially
538 m_asyncWorker
->setMaxThreadCount(1);
539 m_asyncWorker
->setObjectName("SessionImpl m_asyncWorker");
542 m_port
= Utils::Random::rand(1024, 65535);
545 m_sslPort
= Utils::Random::rand(1024, 65535);
546 while (m_sslPort
== port())
547 m_sslPort
= Utils::Random::rand(1024, 65535);
550 m_recentErroredTorrentsTimer
->setSingleShot(true);
551 m_recentErroredTorrentsTimer
->setInterval(1s
);
552 connect(m_recentErroredTorrentsTimer
, &QTimer::timeout
553 , this, [this]() { m_recentErroredTorrents
.clear(); });
555 m_seedingLimitTimer
->setInterval(10s
);
556 connect(m_seedingLimitTimer
, &QTimer::timeout
, this, [this]
558 // We shouldn't iterate over `m_torrents` in the loop below
559 // since `deleteTorrent()` modifies it indirectly
560 const QHash
<TorrentID
, TorrentImpl
*> torrents
{m_torrents
};
561 for (TorrentImpl
*torrent
: torrents
)
562 processTorrentShareLimits(torrent
);
565 initializeNativeSession();
566 configureComponents();
568 if (isBandwidthSchedulerEnabled())
569 enableBandwidthScheduler();
572 if (isSubcategoriesEnabled())
574 // if subcategories support changed manually
575 m_categories
= expandCategories(m_categories
);
578 const QStringList storedTags
= m_storedTags
.get();
579 for (const QString
&tagStr
: storedTags
)
581 if (const Tag tag
{tagStr
}; tag
.isValid())
585 updateSeedingLimitTimer();
586 populateAdditionalTrackers();
587 if (isExcludedFileNamesEnabled())
588 populateExcludedFileNamesRegExpList();
590 connect(Net::ProxyConfigurationManager::instance()
591 , &Net::ProxyConfigurationManager::proxyConfigurationChanged
592 , this, &SessionImpl::configureDeferred
);
594 m_fileSearcher
= new FileSearcher
;
595 m_fileSearcher
->moveToThread(m_ioThread
.get());
596 connect(m_ioThread
.get(), &QThread::finished
, m_fileSearcher
, &QObject::deleteLater
);
597 connect(m_fileSearcher
, &FileSearcher::searchFinished
, this, &SessionImpl::fileSearchFinished
);
599 m_torrentContentRemover
= new TorrentContentRemover
;
600 m_torrentContentRemover
->moveToThread(m_ioThread
.get());
601 connect(m_ioThread
.get(), &QThread::finished
, m_torrentContentRemover
, &QObject::deleteLater
);
602 connect(m_torrentContentRemover
, &TorrentContentRemover::jobFinished
, this, &SessionImpl::torrentContentRemovingFinished
);
604 m_ioThread
->setObjectName("SessionImpl m_ioThread");
610 // initialize PortForwarder instance
611 new PortForwarderImpl(this);
613 // start embedded tracker
614 enableTracker(isTrackerEnabled());
619 SessionImpl::~SessionImpl()
621 m_nativeSession
->pause();
623 const auto timeout
= (m_shutdownTimeout
>= 0) ? (static_cast<qint64
>(m_shutdownTimeout
) * 1000) : -1;
624 const QDeadlineTimer shutdownDeadlineTimer
{timeout
};
626 if (m_torrentsQueueChanged
)
628 m_nativeSession
->post_torrent_updates({});
629 m_torrentsQueueChanged
= false;
630 m_needSaveTorrentsQueue
= true;
633 // Do some bittorrent related saving
634 // After this, (ideally) no more important alerts will be generated/handled
639 // We must delete FilterParserThread
640 // before we delete lt::session
641 delete m_filterParser
;
643 // We must delete PortForwarderImpl before
644 // we delete lt::session
645 delete Net::PortForwarder::instance();
647 // We must stop "async worker" only after deletion
648 // of all the components that could potentially use it
649 m_asyncWorker
->clear();
650 m_asyncWorker
->waitForDone();
652 auto *nativeSessionProxy
= new lt::session_proxy(m_nativeSession
->abort());
653 delete m_nativeSession
;
655 qDebug("Deleting resume data storage...");
656 delete m_resumeDataStorage
;
657 LogMsg(tr("Saving resume data completed."));
659 auto *sessionTerminateThread
= QThread::create([nativeSessionProxy
]()
661 qDebug("Deleting libtorrent session...");
662 delete nativeSessionProxy
;
664 sessionTerminateThread
->setObjectName("~SessionImpl sessionTerminateThread");
665 connect(sessionTerminateThread
, &QThread::finished
, sessionTerminateThread
, &QObject::deleteLater
);
666 sessionTerminateThread
->start();
667 if (sessionTerminateThread
->wait(shutdownDeadlineTimer
))
668 LogMsg(tr("BitTorrent session successfully finished."));
670 LogMsg(tr("Session shutdown timed out."));
673 QString
SessionImpl::getDHTBootstrapNodes() const
675 const QString nodes
= m_DHTBootstrapNodes
;
676 return !nodes
.isEmpty() ? nodes
: DEFAULT_DHT_BOOTSTRAP_NODES
;
679 void SessionImpl::setDHTBootstrapNodes(const QString
&nodes
)
681 if (nodes
== m_DHTBootstrapNodes
)
684 m_DHTBootstrapNodes
= nodes
;
688 bool SessionImpl::isDHTEnabled() const
690 return m_isDHTEnabled
;
693 void SessionImpl::setDHTEnabled(bool enabled
)
695 if (enabled
!= m_isDHTEnabled
)
697 m_isDHTEnabled
= enabled
;
699 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(enabled
? tr("ON") : tr("OFF")), Log::INFO
);
703 bool SessionImpl::isLSDEnabled() const
705 return m_isLSDEnabled
;
708 void SessionImpl::setLSDEnabled(const bool enabled
)
710 if (enabled
!= m_isLSDEnabled
)
712 m_isLSDEnabled
= enabled
;
714 LogMsg(tr("Local Peer Discovery support: %1").arg(enabled
? tr("ON") : tr("OFF"))
719 bool SessionImpl::isPeXEnabled() const
721 return m_isPeXEnabled
;
724 void SessionImpl::setPeXEnabled(const bool enabled
)
726 m_isPeXEnabled
= enabled
;
727 if (m_wasPexEnabled
!= enabled
)
728 LogMsg(tr("Restart is required to toggle Peer Exchange (PeX) support"), Log::WARNING
);
731 bool SessionImpl::isDownloadPathEnabled() const
733 return m_isDownloadPathEnabled
;
736 void SessionImpl::setDownloadPathEnabled(const bool enabled
)
738 if (enabled
!= isDownloadPathEnabled())
740 m_isDownloadPathEnabled
= enabled
;
741 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
742 torrent
->handleCategoryOptionsChanged();
746 bool SessionImpl::isAppendExtensionEnabled() const
748 return m_isAppendExtensionEnabled
;
751 void SessionImpl::setAppendExtensionEnabled(const bool enabled
)
753 if (isAppendExtensionEnabled() != enabled
)
755 m_isAppendExtensionEnabled
= enabled
;
757 // append or remove .!qB extension for incomplete files
758 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
759 torrent
->handleAppendExtensionToggled();
763 bool SessionImpl::isUnwantedFolderEnabled() const
765 return m_isUnwantedFolderEnabled
;
768 void SessionImpl::setUnwantedFolderEnabled(const bool enabled
)
770 if (isUnwantedFolderEnabled() != enabled
)
772 m_isUnwantedFolderEnabled
= enabled
;
774 // append or remove .!qB extension for incomplete files
775 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
776 torrent
->handleUnwantedFolderToggled();
780 int SessionImpl::refreshInterval() const
782 return m_refreshInterval
;
785 void SessionImpl::setRefreshInterval(const int value
)
787 if (value
!= refreshInterval())
789 m_refreshInterval
= value
;
793 bool SessionImpl::isPreallocationEnabled() const
795 return m_isPreallocationEnabled
;
798 void SessionImpl::setPreallocationEnabled(const bool enabled
)
800 m_isPreallocationEnabled
= enabled
;
803 Path
SessionImpl::torrentExportDirectory() const
805 return m_torrentExportDirectory
;
808 void SessionImpl::setTorrentExportDirectory(const Path
&path
)
810 if (path
!= torrentExportDirectory())
811 m_torrentExportDirectory
= path
;
814 Path
SessionImpl::finishedTorrentExportDirectory() const
816 return m_finishedTorrentExportDirectory
;
819 void SessionImpl::setFinishedTorrentExportDirectory(const Path
&path
)
821 if (path
!= finishedTorrentExportDirectory())
822 m_finishedTorrentExportDirectory
= path
;
825 Path
SessionImpl::savePath() const
827 // TODO: Make sure it is always non-empty
831 Path
SessionImpl::downloadPath() const
833 // TODO: Make sure it is always non-empty
834 return m_downloadPath
;
837 QStringList
SessionImpl::categories() const
839 return m_categories
.keys();
842 CategoryOptions
SessionImpl::categoryOptions(const QString
&categoryName
) const
844 return m_categories
.value(categoryName
);
847 Path
SessionImpl::categorySavePath(const QString
&categoryName
) const
849 return categorySavePath(categoryName
, categoryOptions(categoryName
));
852 Path
SessionImpl::categorySavePath(const QString
&categoryName
, const CategoryOptions
&options
) const
854 Path basePath
= savePath();
855 if (categoryName
.isEmpty())
858 Path path
= options
.savePath
;
861 // use implicit save path
862 if (isSubcategoriesEnabled())
864 path
= Utils::Fs::toValidPath(subcategoryName(categoryName
));
865 basePath
= categorySavePath(parentCategoryName(categoryName
));
869 path
= Utils::Fs::toValidPath(categoryName
);
873 return (path
.isAbsolute() ? path
: (basePath
/ path
));
876 Path
SessionImpl::categoryDownloadPath(const QString
&categoryName
) const
878 return categoryDownloadPath(categoryName
, categoryOptions(categoryName
));
881 Path
SessionImpl::categoryDownloadPath(const QString
&categoryName
, const CategoryOptions
&options
) const
883 const DownloadPathOption downloadPathOption
= resolveCategoryDownloadPathOption(categoryName
, options
.downloadPath
);
884 if (!downloadPathOption
.enabled
)
887 if (categoryName
.isEmpty())
888 return downloadPath();
890 const bool useSubcategories
= isSubcategoriesEnabled();
891 const QString name
= useSubcategories
? subcategoryName(categoryName
) : categoryName
;
892 const Path path
= !downloadPathOption
.path
.isEmpty()
893 ? downloadPathOption
.path
894 : Utils::Fs::toValidPath(name
); // use implicit download path
896 if (path
.isAbsolute())
899 const QString parentName
= useSubcategories
? parentCategoryName(categoryName
) : QString();
900 CategoryOptions parentOptions
= categoryOptions(parentName
);
901 // Even if download path of parent category is disabled (directly or by inheritance)
902 // we need to construct the one as if it would be enabled.
903 if (!parentOptions
.downloadPath
|| !parentOptions
.downloadPath
->enabled
)
904 parentOptions
.downloadPath
= {true, {}};
905 const Path parentDownloadPath
= categoryDownloadPath(parentName
, parentOptions
);
906 const Path basePath
= parentDownloadPath
.isEmpty() ? downloadPath() : parentDownloadPath
;
907 return (basePath
/ path
);
910 DownloadPathOption
SessionImpl::resolveCategoryDownloadPathOption(const QString
&categoryName
, const std::optional
<DownloadPathOption
> &option
) const
912 if (categoryName
.isEmpty())
913 return {isDownloadPathEnabled(), Path()};
915 if (option
.has_value())
918 const QString parentName
= isSubcategoriesEnabled() ? parentCategoryName(categoryName
) : QString();
919 return resolveCategoryDownloadPathOption(parentName
, categoryOptions(parentName
).downloadPath
);
922 bool SessionImpl::addCategory(const QString
&name
, const CategoryOptions
&options
)
927 if (!isValidCategoryName(name
) || m_categories
.contains(name
))
930 if (isSubcategoriesEnabled())
932 for (const QString
&parent
: asConst(expandCategory(name
)))
934 if ((parent
!= name
) && !m_categories
.contains(parent
))
936 m_categories
[parent
] = {};
937 emit
categoryAdded(parent
);
942 m_categories
[name
] = options
;
944 emit
categoryAdded(name
);
949 bool SessionImpl::editCategory(const QString
&name
, const CategoryOptions
&options
)
951 const auto it
= m_categories
.find(name
);
952 if (it
== m_categories
.end())
955 CategoryOptions
¤tOptions
= it
.value();
956 if (options
== currentOptions
)
959 currentOptions
= options
;
961 if (isDisableAutoTMMWhenCategorySavePathChanged())
963 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
965 if (torrent
->category() == name
)
966 torrent
->setAutoTMMEnabled(false);
971 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
973 if (torrent
->category() == name
)
974 torrent
->handleCategoryOptionsChanged();
978 emit
categoryOptionsChanged(name
);
982 bool SessionImpl::removeCategory(const QString
&name
)
984 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
986 if (torrent
->belongsToCategory(name
))
987 torrent
->setCategory(u
""_s
);
990 // remove stored category and its subcategories if exist
992 if (isSubcategoriesEnabled())
994 // remove subcategories
995 const QString test
= name
+ u
'/';
996 Algorithm::removeIf(m_categories
, [this, &test
, &result
](const QString
&category
, const CategoryOptions
&)
998 if (category
.startsWith(test
))
1001 emit
categoryRemoved(category
);
1008 result
= (m_categories
.remove(name
) > 0) || result
;
1012 // update stored categories
1014 emit
categoryRemoved(name
);
1020 bool SessionImpl::isSubcategoriesEnabled() const
1022 return m_isSubcategoriesEnabled
;
1025 void SessionImpl::setSubcategoriesEnabled(const bool value
)
1027 if (isSubcategoriesEnabled() == value
) return;
1031 // expand categories to include all parent categories
1032 m_categories
= expandCategories(m_categories
);
1033 // update stored categories
1038 // reload categories
1042 m_isSubcategoriesEnabled
= value
;
1043 emit
subcategoriesSupportChanged();
1046 bool SessionImpl::useCategoryPathsInManualMode() const
1048 return m_useCategoryPathsInManualMode
;
1051 void SessionImpl::setUseCategoryPathsInManualMode(const bool value
)
1053 m_useCategoryPathsInManualMode
= value
;
1056 Path
SessionImpl::suggestedSavePath(const QString
&categoryName
, std::optional
<bool> useAutoTMM
) const
1058 const bool useCategoryPaths
= useAutoTMM
.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
1059 const auto path
= (useCategoryPaths
? categorySavePath(categoryName
) : savePath());
1063 Path
SessionImpl::suggestedDownloadPath(const QString
&categoryName
, std::optional
<bool> useAutoTMM
) const
1065 const bool useCategoryPaths
= useAutoTMM
.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
1066 const auto categoryDownloadPath
= this->categoryDownloadPath(categoryName
);
1067 const auto path
= ((useCategoryPaths
&& !categoryDownloadPath
.isEmpty()) ? categoryDownloadPath
: downloadPath());
1071 TagSet
SessionImpl::tags() const
1076 bool SessionImpl::hasTag(const Tag
&tag
) const
1078 return m_tags
.contains(tag
);
1081 bool SessionImpl::addTag(const Tag
&tag
)
1083 if (!tag
.isValid() || hasTag(tag
))
1087 m_storedTags
= QStringList(m_tags
.cbegin(), m_tags
.cend());
1093 bool SessionImpl::removeTag(const Tag
&tag
)
1095 if (m_tags
.remove(tag
))
1097 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
1098 torrent
->removeTag(tag
);
1100 m_storedTags
= QStringList(m_tags
.cbegin(), m_tags
.cend());
1102 emit
tagRemoved(tag
);
1108 bool SessionImpl::isAutoTMMDisabledByDefault() const
1110 return m_isAutoTMMDisabledByDefault
;
1113 void SessionImpl::setAutoTMMDisabledByDefault(const bool value
)
1115 m_isAutoTMMDisabledByDefault
= value
;
1118 bool SessionImpl::isDisableAutoTMMWhenCategoryChanged() const
1120 return m_isDisableAutoTMMWhenCategoryChanged
;
1123 void SessionImpl::setDisableAutoTMMWhenCategoryChanged(const bool value
)
1125 m_isDisableAutoTMMWhenCategoryChanged
= value
;
1128 bool SessionImpl::isDisableAutoTMMWhenDefaultSavePathChanged() const
1130 return m_isDisableAutoTMMWhenDefaultSavePathChanged
;
1133 void SessionImpl::setDisableAutoTMMWhenDefaultSavePathChanged(const bool value
)
1135 m_isDisableAutoTMMWhenDefaultSavePathChanged
= value
;
1138 bool SessionImpl::isDisableAutoTMMWhenCategorySavePathChanged() const
1140 return m_isDisableAutoTMMWhenCategorySavePathChanged
;
1143 void SessionImpl::setDisableAutoTMMWhenCategorySavePathChanged(const bool value
)
1145 m_isDisableAutoTMMWhenCategorySavePathChanged
= value
;
1148 bool SessionImpl::isAddTorrentToQueueTop() const
1150 return m_isAddTorrentToQueueTop
;
1153 void SessionImpl::setAddTorrentToQueueTop(bool value
)
1155 m_isAddTorrentToQueueTop
= value
;
1158 bool SessionImpl::isAddTorrentStopped() const
1160 return m_isAddTorrentStopped
;
1163 void SessionImpl::setAddTorrentStopped(const bool value
)
1165 m_isAddTorrentStopped
= value
;
1168 Torrent::StopCondition
SessionImpl::torrentStopCondition() const
1170 return m_torrentStopCondition
;
1173 void SessionImpl::setTorrentStopCondition(const Torrent::StopCondition stopCondition
)
1175 m_torrentStopCondition
= stopCondition
;
1178 bool SessionImpl::isTrackerEnabled() const
1180 return m_isTrackerEnabled
;
1183 void SessionImpl::setTrackerEnabled(const bool enabled
)
1185 if (m_isTrackerEnabled
!= enabled
)
1186 m_isTrackerEnabled
= enabled
;
1188 // call enableTracker() unconditionally, otherwise port change won't trigger
1190 enableTracker(enabled
);
1193 qreal
SessionImpl::globalMaxRatio() const
1195 return m_globalMaxRatio
;
1198 // Torrents with a ratio superior to the given value will
1199 // be automatically deleted
1200 void SessionImpl::setGlobalMaxRatio(qreal ratio
)
1205 if (ratio
!= globalMaxRatio())
1207 m_globalMaxRatio
= ratio
;
1208 updateSeedingLimitTimer();
1212 int SessionImpl::globalMaxSeedingMinutes() const
1214 return m_globalMaxSeedingMinutes
;
1217 void SessionImpl::setGlobalMaxSeedingMinutes(int minutes
)
1222 if (minutes
!= globalMaxSeedingMinutes())
1224 m_globalMaxSeedingMinutes
= minutes
;
1225 updateSeedingLimitTimer();
1229 int SessionImpl::globalMaxInactiveSeedingMinutes() const
1231 return m_globalMaxInactiveSeedingMinutes
;
1234 void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes
)
1236 minutes
= std::max(minutes
, -1);
1238 if (minutes
!= globalMaxInactiveSeedingMinutes())
1240 m_globalMaxInactiveSeedingMinutes
= minutes
;
1241 updateSeedingLimitTimer();
1245 void SessionImpl::applyBandwidthLimits()
1247 lt::settings_pack settingsPack
;
1248 settingsPack
.set_int(lt::settings_pack::download_rate_limit
, downloadSpeedLimit());
1249 settingsPack
.set_int(lt::settings_pack::upload_rate_limit
, uploadSpeedLimit());
1250 m_nativeSession
->apply_settings(std::move(settingsPack
));
1253 void SessionImpl::configure()
1255 m_nativeSession
->apply_settings(loadLTSettings());
1256 configureComponents();
1258 m_deferredConfigureScheduled
= false;
1261 void SessionImpl::configureComponents()
1263 // This function contains components/actions that:
1264 // 1. Need to be setup at start up
1265 // 2. When deferred configure is called
1267 configurePeerClasses();
1269 if (!m_IPFilteringConfigured
)
1271 if (isIPFilteringEnabled())
1275 m_IPFilteringConfigured
= true;
1279 void SessionImpl::prepareStartup()
1281 qDebug("Initializing torrents resume data storage...");
1283 const Path dbPath
= specialFolderLocation(SpecialFolder::Data
) / Path(u
"torrents.db"_s
);
1284 const bool dbStorageExists
= dbPath
.exists();
1286 auto *context
= new ResumeSessionContext(this);
1287 context
->currentStorageType
= resumeDataStorageType();
1289 if (context
->currentStorageType
== ResumeDataStorageType::SQLite
)
1291 m_resumeDataStorage
= new DBResumeDataStorage(dbPath
, this);
1293 if (!dbStorageExists
)
1295 const Path dataPath
= specialFolderLocation(SpecialFolder::Data
) / Path(u
"BT_backup"_s
);
1296 context
->startupStorage
= new BencodeResumeDataStorage(dataPath
, this);
1301 const Path dataPath
= specialFolderLocation(SpecialFolder::Data
) / Path(u
"BT_backup"_s
);
1302 m_resumeDataStorage
= new BencodeResumeDataStorage(dataPath
, this);
1304 if (dbStorageExists
)
1305 context
->startupStorage
= new DBResumeDataStorage(dbPath
, this);
1308 if (!context
->startupStorage
)
1309 context
->startupStorage
= m_resumeDataStorage
;
1311 connect(context
->startupStorage
, &ResumeDataStorage::loadStarted
, context
1312 , [this, context
](const QList
<TorrentID
> &torrents
)
1314 context
->totalResumeDataCount
= torrents
.size();
1315 #ifdef QBT_USES_LIBTORRENT2
1316 context
->indexedTorrents
= QSet
<TorrentID
>(torrents
.cbegin(), torrents
.cend());
1319 handleLoadedResumeData(context
);
1322 connect(context
->startupStorage
, &ResumeDataStorage::loadFinished
, context
, [context
]()
1324 context
->isLoadFinished
= true;
1327 connect(this, &SessionImpl::addTorrentAlertsReceived
, context
, [this, context
](const qsizetype alertsCount
)
1329 context
->processingResumeDataCount
-= alertsCount
;
1330 context
->finishedResumeDataCount
+= alertsCount
;
1331 if (!context
->isLoadedResumeDataHandlingEnqueued
)
1333 QMetaObject::invokeMethod(this, [this, context
] { handleLoadedResumeData(context
); }, Qt::QueuedConnection
);
1334 context
->isLoadedResumeDataHandlingEnqueued
= true;
1337 if (!m_refreshEnqueued
)
1339 m_nativeSession
->post_torrent_updates();
1340 m_refreshEnqueued
= true;
1343 emit
startupProgressUpdated((context
->finishedResumeDataCount
* 100.) / context
->totalResumeDataCount
);
1346 context
->startupStorage
->loadAll();
1349 void SessionImpl::handleLoadedResumeData(ResumeSessionContext
*context
)
1351 context
->isLoadedResumeDataHandlingEnqueued
= false;
1353 int count
= context
->processingResumeDataCount
;
1354 while (context
->processingResumeDataCount
< MAX_PROCESSING_RESUMEDATA_COUNT
)
1356 if (context
->loadedResumeData
.isEmpty())
1357 context
->loadedResumeData
= context
->startupStorage
->fetchLoadedResumeData();
1359 if (context
->loadedResumeData
.isEmpty())
1361 if (context
->processingResumeDataCount
== 0)
1363 if (context
->isLoadFinished
)
1365 endStartup(context
);
1367 else if (!context
->isLoadedResumeDataHandlingEnqueued
)
1369 QMetaObject::invokeMethod(this, [this, context
]() { handleLoadedResumeData(context
); }, Qt::QueuedConnection
);
1370 context
->isLoadedResumeDataHandlingEnqueued
= true;
1377 processNextResumeData(context
);
1381 context
->finishedResumeDataCount
+= (count
- context
->processingResumeDataCount
);
1384 void SessionImpl::processNextResumeData(ResumeSessionContext
*context
)
1386 const LoadedResumeData loadedResumeDataItem
= context
->loadedResumeData
.takeFirst();
1388 TorrentID torrentID
= loadedResumeDataItem
.torrentID
;
1389 #ifdef QBT_USES_LIBTORRENT2
1390 if (context
->skippedIDs
.contains(torrentID
))
1394 const nonstd::expected
<LoadTorrentParams
, QString
> &loadResumeDataResult
= loadedResumeDataItem
.result
;
1395 if (!loadResumeDataResult
)
1397 LogMsg(tr("Failed to resume torrent. Torrent: \"%1\". Reason: \"%2\"")
1398 .arg(torrentID
.toString(), loadResumeDataResult
.error()), Log::CRITICAL
);
1402 LoadTorrentParams resumeData
= *loadResumeDataResult
;
1403 bool needStore
= false;
1405 #ifdef QBT_USES_LIBTORRENT2
1406 const InfoHash infoHash
{(resumeData
.ltAddTorrentParams
.ti
1407 ? resumeData
.ltAddTorrentParams
.ti
->info_hashes()
1408 : resumeData
.ltAddTorrentParams
.info_hashes
)};
1409 const bool isHybrid
= infoHash
.isHybrid();
1410 const auto torrentIDv2
= TorrentID::fromInfoHash(infoHash
);
1411 const auto torrentIDv1
= TorrentID::fromSHA1Hash(infoHash
.v1());
1412 if (torrentID
== torrentIDv2
)
1414 if (isHybrid
&& context
->indexedTorrents
.contains(torrentIDv1
))
1416 // if we don't have metadata, try to find it in alternative "resume data"
1417 if (!resumeData
.ltAddTorrentParams
.ti
)
1419 const nonstd::expected
<LoadTorrentParams
, QString
> loadAltResumeDataResult
= context
->startupStorage
->load(torrentIDv1
);
1420 if (loadAltResumeDataResult
)
1421 resumeData
.ltAddTorrentParams
.ti
= loadAltResumeDataResult
->ltAddTorrentParams
.ti
;
1424 // remove alternative "resume data" and skip the attempt to load it
1425 m_resumeDataStorage
->remove(torrentIDv1
);
1426 context
->skippedIDs
.insert(torrentIDv1
);
1429 else if (torrentID
== torrentIDv1
)
1431 torrentID
= torrentIDv2
;
1433 m_resumeDataStorage
->remove(torrentIDv1
);
1435 if (context
->indexedTorrents
.contains(torrentID
))
1437 context
->skippedIDs
.insert(torrentID
);
1439 const nonstd::expected
<LoadTorrentParams
, QString
> loadPreferredResumeDataResult
= context
->startupStorage
->load(torrentID
);
1440 if (loadPreferredResumeDataResult
)
1442 std::shared_ptr
<lt::torrent_info
> ti
= resumeData
.ltAddTorrentParams
.ti
;
1443 resumeData
= *loadPreferredResumeDataResult
;
1444 if (!resumeData
.ltAddTorrentParams
.ti
)
1445 resumeData
.ltAddTorrentParams
.ti
= std::move(ti
);
1451 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1452 .arg(torrentID
.toString()), Log::WARNING
);
1456 const lt::sha1_hash infoHash
= (resumeData
.ltAddTorrentParams
.ti
1457 ? resumeData
.ltAddTorrentParams
.ti
->info_hash()
1458 : resumeData
.ltAddTorrentParams
.info_hash
);
1459 if (torrentID
!= TorrentID::fromInfoHash(infoHash
))
1461 LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
1462 .arg(torrentID
.toString()), Log::WARNING
);
1467 if (m_resumeDataStorage
!= context
->startupStorage
)
1470 // TODO: Remove the following upgrade code in v4.6
1471 // == BEGIN UPGRADE CODE ==
1474 if (m_needUpgradeDownloadPath
&& isDownloadPathEnabled() && !resumeData
.useAutoTMM
)
1476 resumeData
.downloadPath
= downloadPath();
1480 // == END UPGRADE CODE ==
1483 m_resumeDataStorage
->store(torrentID
, resumeData
);
1485 const QString category
= resumeData
.category
;
1486 bool isCategoryRecovered
= context
->recoveredCategories
.contains(category
);
1487 if (!category
.isEmpty() && (isCategoryRecovered
|| !m_categories
.contains(category
)))
1489 if (!isCategoryRecovered
)
1491 if (addCategory(category
))
1493 context
->recoveredCategories
.insert(category
);
1494 isCategoryRecovered
= true;
1495 LogMsg(tr("Detected inconsistent data: category is missing from the configuration file."
1496 " Category will be recovered but its settings will be reset to default."
1497 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID
.toString(), category
), Log::WARNING
);
1501 resumeData
.category
.clear();
1502 LogMsg(tr("Detected inconsistent data: invalid category. Torrent: \"%1\". Category: \"%2\"")
1503 .arg(torrentID
.toString(), category
), Log::WARNING
);
1507 // We should check isCategoryRecovered again since the category
1508 // can be just recovered by the code above
1509 if (isCategoryRecovered
&& resumeData
.useAutoTMM
)
1511 const Path storageLocation
{resumeData
.ltAddTorrentParams
.save_path
};
1512 if ((storageLocation
!= categorySavePath(resumeData
.category
)) && (storageLocation
!= categoryDownloadPath(resumeData
.category
)))
1514 resumeData
.useAutoTMM
= false;
1515 resumeData
.savePath
= storageLocation
;
1516 resumeData
.downloadPath
= {};
1517 LogMsg(tr("Detected mismatch between the save paths of the recovered category and the current save path of the torrent."
1518 " Torrent is now switched to Manual mode."
1519 " Torrent: \"%1\". Category: \"%2\"").arg(torrentID
.toString(), category
), Log::WARNING
);
1524 std::erase_if(resumeData
.tags
, [this, &torrentID
](const Tag
&tag
)
1531 LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file."
1532 " Tag will be recovered."
1533 " Torrent: \"%1\". Tag: \"%2\"").arg(torrentID
.toString(), tag
.toString()), Log::WARNING
);
1537 LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"")
1538 .arg(torrentID
.toString(), tag
.toString()), Log::WARNING
);
1542 resumeData
.ltAddTorrentParams
.userdata
= LTClientData(new ExtensionData
);
1543 #ifndef QBT_USES_LIBTORRENT2
1544 resumeData
.ltAddTorrentParams
.storage
= customStorageConstructor
;
1547 qDebug() << "Starting up torrent" << torrentID
.toString() << "...";
1548 m_loadingTorrents
.insert(torrentID
, resumeData
);
1549 #ifdef QBT_USES_LIBTORRENT2
1550 if (infoHash
.isHybrid())
1552 // this allows to know the being added hybrid torrent by its v1 info hash
1553 // without having yet another mapping table
1554 m_hybridTorrentsByAltID
.insert(torrentIDv1
, nullptr);
1557 m_nativeSession
->async_add_torrent(resumeData
.ltAddTorrentParams
);
1558 ++context
->processingResumeDataCount
;
1561 void SessionImpl::endStartup(ResumeSessionContext
*context
)
1563 if (m_resumeDataStorage
!= context
->startupStorage
)
1565 if (isQueueingSystemEnabled())
1566 saveTorrentsQueue();
1568 const Path dbPath
= context
->startupStorage
->path();
1569 context
->startupStorage
->deleteLater();
1571 if (context
->currentStorageType
== ResumeDataStorageType::Legacy
)
1573 connect(context
->startupStorage
, &QObject::destroyed
, [dbPath
]
1575 Utils::Fs::removeFile(dbPath
);
1580 context
->deleteLater();
1581 connect(context
, &QObject::destroyed
, this, [this]
1584 m_nativeSession
->resume();
1586 if (m_refreshEnqueued
)
1587 m_refreshEnqueued
= false;
1591 m_statisticsLastUpdateTimer
.start();
1593 // Regular saving of fastresume data
1594 connect(m_resumeDataTimer
, &QTimer::timeout
, this, &SessionImpl::generateResumeData
);
1595 const int saveInterval
= saveResumeDataInterval();
1596 if (saveInterval
> 0)
1598 m_resumeDataTimer
->setInterval(std::chrono::minutes(saveInterval
));
1599 m_resumeDataTimer
->start();
1602 m_wakeupCheckTimer
= new QTimer(this);
1603 connect(m_wakeupCheckTimer
, &QTimer::timeout
, this, [this]
1605 const auto now
= QDateTime::currentDateTime();
1606 if (m_wakeupCheckTimestamp
.secsTo(now
) > 100)
1608 LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
1609 reannounceToAllTrackers();
1612 m_wakeupCheckTimestamp
= QDateTime::currentDateTime();
1614 m_wakeupCheckTimestamp
= QDateTime::currentDateTime();
1615 m_wakeupCheckTimer
->start(30s
);
1617 m_isRestored
= true;
1618 emit
startupProgressUpdated(100);
1623 void SessionImpl::initializeNativeSession()
1625 lt::settings_pack pack
= loadLTSettings();
1627 const std::string peerId
= lt::generate_fingerprint(PEER_ID
, QBT_VERSION_MAJOR
, QBT_VERSION_MINOR
, QBT_VERSION_BUGFIX
, QBT_VERSION_BUILD
);
1628 pack
.set_str(lt::settings_pack::peer_fingerprint
, peerId
);
1630 pack
.set_bool(lt::settings_pack::listen_system_port_fallback
, false);
1631 pack
.set_str(lt::settings_pack::user_agent
, USER_AGENT
.toStdString());
1632 pack
.set_bool(lt::settings_pack::use_dht_as_fallback
, false);
1634 pack
.set_int(lt::settings_pack::auto_scrape_interval
, 1200); // 20 minutes
1635 pack
.set_int(lt::settings_pack::auto_scrape_min_interval
, 900); // 15 minutes
1636 // libtorrent 1.1 enables UPnP & NAT-PMP by default
1637 // turn them off before `lt::session` ctor to avoid split second effects
1638 pack
.set_bool(lt::settings_pack::enable_upnp
, false);
1639 pack
.set_bool(lt::settings_pack::enable_natpmp
, false);
1641 #ifdef QBT_USES_LIBTORRENT2
1642 // preserve the same behavior as in earlier libtorrent versions
1643 pack
.set_bool(lt::settings_pack::enable_set_file_valid_data
, true);
1645 // This is a special case. We use MMap disk IO but tweak it to always fallback to pread/pwrite.
1646 if (diskIOType() == DiskIOType::SimplePreadPwrite
)
1648 pack
.set_int(lt::settings_pack::mmap_file_size_cutoff
, std::numeric_limits
<int>::max());
1649 pack
.set_int(lt::settings_pack::disk_write_mode
, lt::settings_pack::mmap_write_mode_t::always_pwrite
);
1653 lt::session_params sessionParams
{std::move(pack
), {}};
1654 #ifdef QBT_USES_LIBTORRENT2
1655 switch (diskIOType())
1657 case DiskIOType::Posix
:
1658 sessionParams
.disk_io_constructor
= customPosixDiskIOConstructor
;
1660 case DiskIOType::MMap
:
1661 case DiskIOType::SimplePreadPwrite
:
1662 sessionParams
.disk_io_constructor
= customMMapDiskIOConstructor
;
1665 sessionParams
.disk_io_constructor
= customDiskIOConstructor
;
1670 #if LIBTORRENT_VERSION_NUM < 20100
1671 m_nativeSession
= new lt::session(sessionParams
, lt::session::paused
);
1673 m_nativeSession
= new lt::session(sessionParams
);
1674 m_nativeSession
->pause();
1677 LogMsg(tr("Peer ID: \"%1\"").arg(QString::fromStdString(peerId
)), Log::INFO
);
1678 LogMsg(tr("HTTP User-Agent: \"%1\"").arg(USER_AGENT
), Log::INFO
);
1679 LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO
);
1680 LogMsg(tr("Local Peer Discovery support: %1").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO
);
1681 LogMsg(tr("Peer Exchange (PeX) support: %1").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO
);
1682 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO
);
1683 LogMsg(tr("Encryption support: %1").arg((encryption() == 0) ? tr("ON") : ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO
);
1685 m_nativeSession
->set_alert_notify([this]()
1687 QMetaObject::invokeMethod(this, &SessionImpl::readAlerts
, Qt::QueuedConnection
);
1691 m_nativeSession
->add_extension(<::create_smart_ban_plugin
);
1692 m_nativeSession
->add_extension(<::create_ut_metadata_plugin
);
1694 m_nativeSession
->add_extension(<::create_ut_pex_plugin
);
1696 auto nativeSessionExtension
= std::make_shared
<NativeSessionExtension
>();
1697 m_nativeSession
->add_extension(nativeSessionExtension
);
1698 m_nativeSessionExtension
= nativeSessionExtension
.get();
1701 void SessionImpl::processBannedIPs(lt::ip_filter
&filter
)
1703 // First, import current filter
1704 for (const QString
&ip
: asConst(m_bannedIPs
.get()))
1707 const lt::address addr
= lt::make_address(ip
.toLatin1().constData(), ec
);
1710 filter
.add_rule(addr
, addr
, lt::ip_filter::blocked
);
1714 void SessionImpl::initMetrics()
1716 const auto findMetricIndex
= [](const char *name
) -> int
1718 const int index
= lt::find_metric_idx(name
);
1719 Q_ASSERT(index
>= 0);
1727 .hasIncomingConnections
= findMetricIndex("net.has_incoming_connections"),
1728 .sentPayloadBytes
= findMetricIndex("net.sent_payload_bytes"),
1729 .recvPayloadBytes
= findMetricIndex("net.recv_payload_bytes"),
1730 .sentBytes
= findMetricIndex("net.sent_bytes"),
1731 .recvBytes
= findMetricIndex("net.recv_bytes"),
1732 .sentIPOverheadBytes
= findMetricIndex("net.sent_ip_overhead_bytes"),
1733 .recvIPOverheadBytes
= findMetricIndex("net.recv_ip_overhead_bytes"),
1734 .sentTrackerBytes
= findMetricIndex("net.sent_tracker_bytes"),
1735 .recvTrackerBytes
= findMetricIndex("net.recv_tracker_bytes"),
1736 .recvRedundantBytes
= findMetricIndex("net.recv_redundant_bytes"),
1737 .recvFailedBytes
= findMetricIndex("net.recv_failed_bytes")
1741 .numPeersConnected
= findMetricIndex("peer.num_peers_connected"),
1742 .numPeersUpDisk
= findMetricIndex("peer.num_peers_up_disk"),
1743 .numPeersDownDisk
= findMetricIndex("peer.num_peers_down_disk")
1747 .dhtBytesIn
= findMetricIndex("dht.dht_bytes_in"),
1748 .dhtBytesOut
= findMetricIndex("dht.dht_bytes_out"),
1749 .dhtNodes
= findMetricIndex("dht.dht_nodes")
1753 .diskBlocksInUse
= findMetricIndex("disk.disk_blocks_in_use"),
1754 .numBlocksRead
= findMetricIndex("disk.num_blocks_read"),
1755 #ifndef QBT_USES_LIBTORRENT2
1756 .numBlocksCacheHits
= findMetricIndex("disk.num_blocks_cache_hits"),
1758 .writeJobs
= findMetricIndex("disk.num_write_ops"),
1759 .readJobs
= findMetricIndex("disk.num_read_ops"),
1760 .hashJobs
= findMetricIndex("disk.num_blocks_hashed"),
1761 .queuedDiskJobs
= findMetricIndex("disk.queued_disk_jobs"),
1762 .diskJobTime
= findMetricIndex("disk.disk_job_time")
1767 lt::settings_pack
SessionImpl::loadLTSettings() const
1769 lt::settings_pack settingsPack
;
1771 const lt::alert_category_t alertMask
= lt::alert::error_notification
1772 | lt::alert::file_progress_notification
1773 | lt::alert::ip_block_notification
1774 | lt::alert::peer_notification
1775 | (isPerformanceWarningEnabled() ? lt::alert::performance_warning
: lt::alert_category_t())
1776 | lt::alert::port_mapping_notification
1777 | lt::alert::status_notification
1778 | lt::alert::storage_notification
1779 | lt::alert::tracker_notification
;
1780 settingsPack
.set_int(lt::settings_pack::alert_mask
, alertMask
);
1782 settingsPack
.set_int(lt::settings_pack::connection_speed
, connectionSpeed());
1784 // from libtorrent doc:
1785 // It will not take affect until the listen_interfaces settings is updated
1786 settingsPack
.set_int(lt::settings_pack::send_socket_buffer_size
, socketSendBufferSize());
1787 settingsPack
.set_int(lt::settings_pack::recv_socket_buffer_size
, socketReceiveBufferSize());
1788 settingsPack
.set_int(lt::settings_pack::listen_queue_size
, socketBacklogSize());
1790 applyNetworkInterfacesSettings(settingsPack
);
1792 settingsPack
.set_int(lt::settings_pack::download_rate_limit
, downloadSpeedLimit());
1793 settingsPack
.set_int(lt::settings_pack::upload_rate_limit
, uploadSpeedLimit());
1795 // The most secure, rc4 only so that all streams are encrypted
1796 settingsPack
.set_int(lt::settings_pack::allowed_enc_level
, lt::settings_pack::pe_rc4
);
1797 settingsPack
.set_bool(lt::settings_pack::prefer_rc4
, true);
1798 switch (encryption())
1801 settingsPack
.set_int(lt::settings_pack::out_enc_policy
, lt::settings_pack::pe_enabled
);
1802 settingsPack
.set_int(lt::settings_pack::in_enc_policy
, lt::settings_pack::pe_enabled
);
1805 settingsPack
.set_int(lt::settings_pack::out_enc_policy
, lt::settings_pack::pe_forced
);
1806 settingsPack
.set_int(lt::settings_pack::in_enc_policy
, lt::settings_pack::pe_forced
);
1808 default: // Disabled
1809 settingsPack
.set_int(lt::settings_pack::out_enc_policy
, lt::settings_pack::pe_disabled
);
1810 settingsPack
.set_int(lt::settings_pack::in_enc_policy
, lt::settings_pack::pe_disabled
);
1813 settingsPack
.set_int(lt::settings_pack::active_checking
, maxActiveCheckingTorrents());
1816 #if defined(QBT_USES_LIBTORRENT2) && TORRENT_USE_I2P
1819 settingsPack
.set_str(lt::settings_pack::i2p_hostname
, I2PAddress().toStdString());
1820 settingsPack
.set_int(lt::settings_pack::i2p_port
, I2PPort());
1821 settingsPack
.set_bool(lt::settings_pack::allow_i2p_mixed
, I2PMixedMode());
1825 settingsPack
.set_str(lt::settings_pack::i2p_hostname
, "");
1826 settingsPack
.set_int(lt::settings_pack::i2p_port
, 0);
1827 settingsPack
.set_bool(lt::settings_pack::allow_i2p_mixed
, false);
1830 // I2P session options
1831 settingsPack
.set_int(lt::settings_pack::i2p_inbound_quantity
, I2PInboundQuantity());
1832 settingsPack
.set_int(lt::settings_pack::i2p_outbound_quantity
, I2POutboundQuantity());
1833 settingsPack
.set_int(lt::settings_pack::i2p_inbound_length
, I2PInboundLength());
1834 settingsPack
.set_int(lt::settings_pack::i2p_outbound_length
, I2POutboundLength());
1838 settingsPack
.set_int(lt::settings_pack::proxy_type
, lt::settings_pack::none
);
1839 const auto *proxyManager
= Net::ProxyConfigurationManager::instance();
1840 const Net::ProxyConfiguration proxyConfig
= proxyManager
->proxyConfiguration();
1841 if ((proxyConfig
.type
!= Net::ProxyType::None
) && Preferences::instance()->useProxyForBT())
1843 switch (proxyConfig
.type
)
1845 case Net::ProxyType::SOCKS4
:
1846 settingsPack
.set_int(lt::settings_pack::proxy_type
, lt::settings_pack::socks4
);
1849 case Net::ProxyType::HTTP
:
1850 if (proxyConfig
.authEnabled
)
1851 settingsPack
.set_int(lt::settings_pack::proxy_type
, lt::settings_pack::http_pw
);
1853 settingsPack
.set_int(lt::settings_pack::proxy_type
, lt::settings_pack::http
);
1856 case Net::ProxyType::SOCKS5
:
1857 if (proxyConfig
.authEnabled
)
1858 settingsPack
.set_int(lt::settings_pack::proxy_type
, lt::settings_pack::socks5_pw
);
1860 settingsPack
.set_int(lt::settings_pack::proxy_type
, lt::settings_pack::socks5
);
1867 settingsPack
.set_str(lt::settings_pack::proxy_hostname
, proxyConfig
.ip
.toStdString());
1868 settingsPack
.set_int(lt::settings_pack::proxy_port
, proxyConfig
.port
);
1870 if (proxyConfig
.authEnabled
)
1872 settingsPack
.set_str(lt::settings_pack::proxy_username
, proxyConfig
.username
.toStdString());
1873 settingsPack
.set_str(lt::settings_pack::proxy_password
, proxyConfig
.password
.toStdString());
1876 settingsPack
.set_bool(lt::settings_pack::proxy_peer_connections
, isProxyPeerConnectionsEnabled());
1877 settingsPack
.set_bool(lt::settings_pack::proxy_hostnames
, proxyConfig
.hostnameLookupEnabled
);
1880 settingsPack
.set_bool(lt::settings_pack::announce_to_all_trackers
, announceToAllTrackers());
1881 settingsPack
.set_bool(lt::settings_pack::announce_to_all_tiers
, announceToAllTiers());
1883 settingsPack
.set_int(lt::settings_pack::peer_turnover
, peerTurnover());
1884 settingsPack
.set_int(lt::settings_pack::peer_turnover_cutoff
, peerTurnoverCutoff());
1885 settingsPack
.set_int(lt::settings_pack::peer_turnover_interval
, peerTurnoverInterval());
1887 settingsPack
.set_int(lt::settings_pack::max_out_request_queue
, requestQueueSize());
1889 #ifdef QBT_USES_LIBTORRENT2
1890 settingsPack
.set_int(lt::settings_pack::metadata_token_limit
, Preferences::instance()->getBdecodeTokenLimit());
1893 settingsPack
.set_int(lt::settings_pack::aio_threads
, asyncIOThreads());
1894 #ifdef QBT_USES_LIBTORRENT2
1895 settingsPack
.set_int(lt::settings_pack::hashing_threads
, hashingThreads());
1897 settingsPack
.set_int(lt::settings_pack::file_pool_size
, filePoolSize());
1899 const int checkingMemUsageSize
= checkingMemUsage() * 64;
1900 settingsPack
.set_int(lt::settings_pack::checking_mem_usage
, checkingMemUsageSize
);
1902 #ifndef QBT_USES_LIBTORRENT2
1903 const int cacheSize
= (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
1904 settingsPack
.set_int(lt::settings_pack::cache_size
, cacheSize
);
1905 settingsPack
.set_int(lt::settings_pack::cache_expiry
, diskCacheTTL());
1908 settingsPack
.set_int(lt::settings_pack::max_queued_disk_bytes
, diskQueueSize());
1910 switch (diskIOReadMode())
1912 case DiskIOReadMode::DisableOSCache
:
1913 settingsPack
.set_int(lt::settings_pack::disk_io_read_mode
, lt::settings_pack::disable_os_cache
);
1915 case DiskIOReadMode::EnableOSCache
:
1917 settingsPack
.set_int(lt::settings_pack::disk_io_read_mode
, lt::settings_pack::enable_os_cache
);
1921 switch (diskIOWriteMode())
1923 case DiskIOWriteMode::DisableOSCache
:
1924 settingsPack
.set_int(lt::settings_pack::disk_io_write_mode
, lt::settings_pack::disable_os_cache
);
1926 case DiskIOWriteMode::EnableOSCache
:
1928 settingsPack
.set_int(lt::settings_pack::disk_io_write_mode
, lt::settings_pack::enable_os_cache
);
1930 #ifdef QBT_USES_LIBTORRENT2
1931 case DiskIOWriteMode::WriteThrough
:
1932 settingsPack
.set_int(lt::settings_pack::disk_io_write_mode
, lt::settings_pack::write_through
);
1937 #ifndef QBT_USES_LIBTORRENT2
1938 settingsPack
.set_bool(lt::settings_pack::coalesce_reads
, isCoalesceReadWriteEnabled());
1939 settingsPack
.set_bool(lt::settings_pack::coalesce_writes
, isCoalesceReadWriteEnabled());
1942 settingsPack
.set_bool(lt::settings_pack::piece_extent_affinity
, usePieceExtentAffinity());
1944 settingsPack
.set_int(lt::settings_pack::suggest_mode
, isSuggestModeEnabled()
1945 ? lt::settings_pack::suggest_read_cache
: lt::settings_pack::no_piece_suggestions
);
1947 settingsPack
.set_int(lt::settings_pack::send_buffer_watermark
, sendBufferWatermark() * 1024);
1948 settingsPack
.set_int(lt::settings_pack::send_buffer_low_watermark
, sendBufferLowWatermark() * 1024);
1949 settingsPack
.set_int(lt::settings_pack::send_buffer_watermark_factor
, sendBufferWatermarkFactor());
1951 settingsPack
.set_bool(lt::settings_pack::anonymous_mode
, isAnonymousModeEnabled());
1954 if (isQueueingSystemEnabled())
1956 settingsPack
.set_int(lt::settings_pack::active_downloads
, maxActiveDownloads());
1957 settingsPack
.set_int(lt::settings_pack::active_limit
, maxActiveTorrents());
1958 settingsPack
.set_int(lt::settings_pack::active_seeds
, maxActiveUploads());
1959 settingsPack
.set_bool(lt::settings_pack::dont_count_slow_torrents
, ignoreSlowTorrentsForQueueing());
1960 settingsPack
.set_int(lt::settings_pack::inactive_down_rate
, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
1961 settingsPack
.set_int(lt::settings_pack::inactive_up_rate
, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
1962 settingsPack
.set_int(lt::settings_pack::auto_manage_startup
, slowTorrentsInactivityTimer());
1966 settingsPack
.set_int(lt::settings_pack::active_downloads
, -1);
1967 settingsPack
.set_int(lt::settings_pack::active_seeds
, -1);
1968 settingsPack
.set_int(lt::settings_pack::active_limit
, -1);
1970 settingsPack
.set_int(lt::settings_pack::active_tracker_limit
, -1);
1971 settingsPack
.set_int(lt::settings_pack::active_dht_limit
, -1);
1972 settingsPack
.set_int(lt::settings_pack::active_lsd_limit
, -1);
1973 settingsPack
.set_int(lt::settings_pack::alert_queue_size
, std::numeric_limits
<int>::max() / 2);
1976 settingsPack
.set_int(lt::settings_pack::outgoing_port
, outgoingPortsMin());
1977 settingsPack
.set_int(lt::settings_pack::num_outgoing_ports
, (outgoingPortsMax() - outgoingPortsMin()));
1978 // UPnP lease duration
1979 settingsPack
.set_int(lt::settings_pack::upnp_lease_duration
, UPnPLeaseDuration());
1981 settingsPack
.set_int(lt::settings_pack::peer_tos
, peerToS());
1982 // Include overhead in transfer limits
1983 settingsPack
.set_bool(lt::settings_pack::rate_limit_ip_overhead
, includeOverheadInLimits());
1984 // IP address to announce to trackers
1985 settingsPack
.set_str(lt::settings_pack::announce_ip
, announceIP().toStdString());
1986 // Max concurrent HTTP announces
1987 settingsPack
.set_int(lt::settings_pack::max_concurrent_http_announces
, maxConcurrentHTTPAnnounces());
1988 // Stop tracker timeout
1989 settingsPack
.set_int(lt::settings_pack::stop_tracker_timeout
, stopTrackerTimeout());
1990 // * Max connections limit
1991 settingsPack
.set_int(lt::settings_pack::connections_limit
, maxConnections());
1992 // * Global max upload slots
1993 settingsPack
.set_int(lt::settings_pack::unchoke_slots_limit
, maxUploads());
1995 switch (btProtocol())
1997 case BTProtocol::Both
:
1999 settingsPack
.set_bool(lt::settings_pack::enable_incoming_tcp
, true);
2000 settingsPack
.set_bool(lt::settings_pack::enable_outgoing_tcp
, true);
2001 settingsPack
.set_bool(lt::settings_pack::enable_incoming_utp
, true);
2002 settingsPack
.set_bool(lt::settings_pack::enable_outgoing_utp
, true);
2005 case BTProtocol::TCP
:
2006 settingsPack
.set_bool(lt::settings_pack::enable_incoming_tcp
, true);
2007 settingsPack
.set_bool(lt::settings_pack::enable_outgoing_tcp
, true);
2008 settingsPack
.set_bool(lt::settings_pack::enable_incoming_utp
, false);
2009 settingsPack
.set_bool(lt::settings_pack::enable_outgoing_utp
, false);
2012 case BTProtocol::UTP
:
2013 settingsPack
.set_bool(lt::settings_pack::enable_incoming_tcp
, false);
2014 settingsPack
.set_bool(lt::settings_pack::enable_outgoing_tcp
, false);
2015 settingsPack
.set_bool(lt::settings_pack::enable_incoming_utp
, true);
2016 settingsPack
.set_bool(lt::settings_pack::enable_outgoing_utp
, true);
2020 switch (utpMixedMode())
2022 case MixedModeAlgorithm::TCP
:
2024 settingsPack
.set_int(lt::settings_pack::mixed_mode_algorithm
, lt::settings_pack::prefer_tcp
);
2026 case MixedModeAlgorithm::Proportional
:
2027 settingsPack
.set_int(lt::settings_pack::mixed_mode_algorithm
, lt::settings_pack::peer_proportional
);
2031 settingsPack
.set_bool(lt::settings_pack::allow_idna
, isIDNSupportEnabled());
2033 settingsPack
.set_bool(lt::settings_pack::allow_multiple_connections_per_ip
, multiConnectionsPerIpEnabled());
2035 settingsPack
.set_bool(lt::settings_pack::validate_https_trackers
, validateHTTPSTrackerCertificate());
2037 settingsPack
.set_bool(lt::settings_pack::ssrf_mitigation
, isSSRFMitigationEnabled());
2039 settingsPack
.set_bool(lt::settings_pack::no_connect_privileged_ports
, blockPeersOnPrivilegedPorts());
2041 settingsPack
.set_bool(lt::settings_pack::apply_ip_filter_to_trackers
, isTrackerFilteringEnabled());
2043 settingsPack
.set_str(lt::settings_pack::dht_bootstrap_nodes
, getDHTBootstrapNodes().toStdString());
2044 settingsPack
.set_bool(lt::settings_pack::enable_dht
, isDHTEnabled());
2045 settingsPack
.set_bool(lt::settings_pack::enable_lsd
, isLSDEnabled());
2047 switch (chokingAlgorithm())
2049 case ChokingAlgorithm::FixedSlots
:
2051 settingsPack
.set_int(lt::settings_pack::choking_algorithm
, lt::settings_pack::fixed_slots_choker
);
2053 case ChokingAlgorithm::RateBased
:
2054 settingsPack
.set_int(lt::settings_pack::choking_algorithm
, lt::settings_pack::rate_based_choker
);
2058 switch (seedChokingAlgorithm())
2060 case SeedChokingAlgorithm::RoundRobin
:
2061 settingsPack
.set_int(lt::settings_pack::seed_choking_algorithm
, lt::settings_pack::round_robin
);
2063 case SeedChokingAlgorithm::FastestUpload
:
2065 settingsPack
.set_int(lt::settings_pack::seed_choking_algorithm
, lt::settings_pack::fastest_upload
);
2067 case SeedChokingAlgorithm::AntiLeech
:
2068 settingsPack
.set_int(lt::settings_pack::seed_choking_algorithm
, lt::settings_pack::anti_leech
);
2072 return settingsPack
;
2075 void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack
&settingsPack
) const
2077 if (m_listenInterfaceConfigured
)
2080 if (port() > 0) // user has specified port number
2081 settingsPack
.set_int(lt::settings_pack::max_retry_port_bind
, 0);
2083 QStringList endpoints
;
2084 QStringList outgoingInterfaces
;
2085 QStringList portStrings
= {u
':' + QString::number(port())};
2087 portStrings
.append(u
':' + QString::number(sslPort()) + u
's');
2089 for (const QString
&ip
: asConst(getListeningIPs()))
2091 const QHostAddress addr
{ip
};
2094 const bool isIPv6
= (addr
.protocol() == QAbstractSocket::IPv6Protocol
);
2095 const QString ip
= isIPv6
2096 ? Utils::Net::canonicalIPv6Addr(addr
).toString()
2099 for (const QString
&portString
: asConst(portStrings
))
2100 endpoints
<< ((isIPv6
? (u
'[' + ip
+ u
']') : ip
) + portString
);
2102 if ((ip
!= u
"0.0.0.0") && (ip
!= u
"::"))
2103 outgoingInterfaces
<< ip
;
2107 // ip holds an interface name
2109 // On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
2110 // the interface's LUID and not the GUID.
2111 // Libtorrent expects GUIDs for the 'listen_interfaces' setting.
2112 const QString guid
= convertIfaceNameToGuid(ip
);
2113 if (!guid
.isEmpty())
2115 for (const QString
&portString
: asConst(portStrings
))
2116 endpoints
<< (guid
+ portString
);
2117 outgoingInterfaces
<< guid
;
2121 LogMsg(tr("Could not find GUID of network interface. Interface: \"%1\"").arg(ip
), Log::WARNING
);
2122 // Since we can't get the GUID, we'll pass the interface name instead.
2123 // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
2124 for (const QString
&portString
: asConst(portStrings
))
2125 endpoints
<< (ip
+ portString
);
2126 outgoingInterfaces
<< ip
;
2129 for (const QString
&portString
: asConst(portStrings
))
2130 endpoints
<< (ip
+ portString
);
2131 outgoingInterfaces
<< ip
;
2136 const QString finalEndpoints
= endpoints
.join(u
',');
2137 settingsPack
.set_str(lt::settings_pack::listen_interfaces
, finalEndpoints
.toStdString());
2138 LogMsg(tr("Trying to listen on the following list of IP addresses: \"%1\"").arg(finalEndpoints
));
2140 settingsPack
.set_str(lt::settings_pack::outgoing_interfaces
, outgoingInterfaces
.join(u
',').toStdString());
2141 m_listenInterfaceConfigured
= true;
2144 void SessionImpl::configurePeerClasses()
2147 // lt::make_address("255.255.255.255") crashes on some people's systems
2148 // so instead we use address_v4::broadcast()
2149 // Proactively do the same for 0.0.0.0 and address_v4::any()
2150 f
.add_rule(lt::address_v4::any()
2151 , lt::address_v4::broadcast()
2152 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id
));
2154 // IPv6 may not be available on OS and the parsing
2155 // would result in an exception -> abnormal program termination
2156 // Affects Windows XP
2159 f
.add_rule(lt::address_v6::any()
2160 , lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2161 , 1 << LT::toUnderlyingType(lt::session::global_peer_class_id
));
2163 catch (const std::exception
&) {}
2165 if (ignoreLimitsOnLAN())
2168 f
.add_rule(lt::make_address("10.0.0.0")
2169 , lt::make_address("10.255.255.255")
2170 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id
));
2171 f
.add_rule(lt::make_address("172.16.0.0")
2172 , lt::make_address("172.31.255.255")
2173 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id
));
2174 f
.add_rule(lt::make_address("192.168.0.0")
2175 , lt::make_address("192.168.255.255")
2176 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id
));
2178 f
.add_rule(lt::make_address("169.254.0.0")
2179 , lt::make_address("169.254.255.255")
2180 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id
));
2182 f
.add_rule(lt::make_address("127.0.0.0")
2183 , lt::make_address("127.255.255.255")
2184 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id
));
2186 // IPv6 may not be available on OS and the parsing
2187 // would result in an exception -> abnormal program termination
2188 // Affects Windows XP
2192 f
.add_rule(lt::make_address("fe80::")
2193 , lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2194 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id
));
2195 // unique local addresses
2196 f
.add_rule(lt::make_address("fc00::")
2197 , lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
2198 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id
));
2200 f
.add_rule(lt::address_v6::loopback()
2201 , lt::address_v6::loopback()
2202 , 1 << LT::toUnderlyingType(lt::session::local_peer_class_id
));
2204 catch (const std::exception
&) {}
2206 m_nativeSession
->set_peer_class_filter(f
);
2208 lt::peer_class_type_filter peerClassTypeFilter
;
2209 peerClassTypeFilter
.add(lt::peer_class_type_filter::tcp_socket
, lt::session::tcp_peer_class_id
);
2210 peerClassTypeFilter
.add(lt::peer_class_type_filter::ssl_tcp_socket
, lt::session::tcp_peer_class_id
);
2211 peerClassTypeFilter
.add(lt::peer_class_type_filter::i2p_socket
, lt::session::tcp_peer_class_id
);
2212 if (!isUTPRateLimited())
2214 peerClassTypeFilter
.disallow(lt::peer_class_type_filter::utp_socket
2215 , lt::session::global_peer_class_id
);
2216 peerClassTypeFilter
.disallow(lt::peer_class_type_filter::ssl_utp_socket
2217 , lt::session::global_peer_class_id
);
2219 m_nativeSession
->set_peer_class_type_filter(peerClassTypeFilter
);
2222 void SessionImpl::enableTracker(const bool enable
)
2224 const QString profile
= u
"embeddedTracker"_s
;
2225 auto *portForwarder
= Net::PortForwarder::instance();
2230 m_tracker
= new Tracker(this);
2234 const auto *pref
= Preferences::instance();
2235 if (pref
->isTrackerPortForwardingEnabled())
2236 portForwarder
->setPorts(profile
, {static_cast<quint16
>(pref
->getTrackerPort())});
2238 portForwarder
->removePorts(profile
);
2244 portForwarder
->removePorts(profile
);
2248 void SessionImpl::enableBandwidthScheduler()
2252 m_bwScheduler
= new BandwidthScheduler(this);
2253 connect(m_bwScheduler
.data(), &BandwidthScheduler::bandwidthLimitRequested
2254 , this, &SessionImpl::setAltGlobalSpeedLimitEnabled
);
2256 m_bwScheduler
->start();
2259 void SessionImpl::populateAdditionalTrackers()
2261 m_additionalTrackerEntries
= parseTrackerEntries(additionalTrackers());
2264 void SessionImpl::processTorrentShareLimits(TorrentImpl
*torrent
)
2266 if (!torrent
->isFinished() || torrent
->isForced())
2269 const auto effectiveLimit
= []<typename T
>(const T limit
, const T useGlobalLimit
, const T globalLimit
) -> T
2271 return (limit
== useGlobalLimit
) ? globalLimit
: limit
;
2274 const qreal ratioLimit
= effectiveLimit(torrent
->ratioLimit(), Torrent::USE_GLOBAL_RATIO
, globalMaxRatio());
2275 const int seedingTimeLimit
= effectiveLimit(torrent
->seedingTimeLimit(), Torrent::USE_GLOBAL_SEEDING_TIME
, globalMaxSeedingMinutes());
2276 const int inactiveSeedingTimeLimit
= effectiveLimit(torrent
->inactiveSeedingTimeLimit(), Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME
, globalMaxInactiveSeedingMinutes());
2278 bool reached
= false;
2279 QString description
;
2281 if (const qreal ratio
= torrent
->realRatio();
2282 (ratioLimit
>= 0) && (ratio
<= Torrent::MAX_RATIO
) && (ratio
>= ratioLimit
))
2285 description
= tr("Torrent reached the share ratio limit.");
2287 else if (const qlonglong seedingTimeInMinutes
= torrent
->finishedTime() / 60;
2288 (seedingTimeLimit
>= 0) && (seedingTimeInMinutes
<= Torrent::MAX_SEEDING_TIME
) && (seedingTimeInMinutes
>= seedingTimeLimit
))
2291 description
= tr("Torrent reached the seeding time limit.");
2293 else if (const qlonglong inactiveSeedingTimeInMinutes
= torrent
->timeSinceActivity() / 60;
2294 (inactiveSeedingTimeLimit
>= 0) && (inactiveSeedingTimeInMinutes
<= Torrent::MAX_INACTIVE_SEEDING_TIME
) && (inactiveSeedingTimeInMinutes
>= inactiveSeedingTimeLimit
))
2297 description
= tr("Torrent reached the inactive seeding time limit.");
2302 const QString torrentName
= tr("Torrent: \"%1\".").arg(torrent
->name());
2303 const ShareLimitAction shareLimitAction
= (torrent
->shareLimitAction() == ShareLimitAction::Default
) ? m_shareLimitAction
: torrent
->shareLimitAction();
2305 if (shareLimitAction
== ShareLimitAction::Remove
)
2307 LogMsg(u
"%1 %2 %3"_s
.arg(description
, tr("Removing torrent."), torrentName
));
2308 removeTorrent(torrent
->id(), TorrentRemoveOption::KeepContent
);
2310 else if (shareLimitAction
== ShareLimitAction::RemoveWithContent
)
2312 LogMsg(u
"%1 %2 %3"_s
.arg(description
, tr("Removing torrent and deleting its content."), torrentName
));
2313 removeTorrent(torrent
->id(), TorrentRemoveOption::RemoveContent
);
2315 else if ((shareLimitAction
== ShareLimitAction::Stop
) && !torrent
->isStopped())
2318 LogMsg(u
"%1 %2 %3"_s
.arg(description
, tr("Torrent stopped."), torrentName
));
2320 else if ((shareLimitAction
== ShareLimitAction::EnableSuperSeeding
) && !torrent
->isStopped() && !torrent
->superSeeding())
2322 torrent
->setSuperSeeding(true);
2323 LogMsg(u
"%1 %2 %3"_s
.arg(description
, tr("Super seeding enabled."), torrentName
));
2328 void SessionImpl::fileSearchFinished(const TorrentID
&id
, const Path
&savePath
, const PathList
&fileNames
)
2330 TorrentImpl
*torrent
= m_torrents
.value(id
);
2333 torrent
->fileSearchFinished(savePath
, fileNames
);
2337 const auto loadingTorrentsIter
= m_loadingTorrents
.find(id
);
2338 if (loadingTorrentsIter
!= m_loadingTorrents
.end())
2340 LoadTorrentParams
¶ms
= loadingTorrentsIter
.value();
2341 lt::add_torrent_params
&p
= params
.ltAddTorrentParams
;
2343 p
.save_path
= savePath
.toString().toStdString();
2344 const TorrentInfo torrentInfo
{*p
.ti
};
2345 const auto nativeIndexes
= torrentInfo
.nativeIndexes();
2346 for (int i
= 0; i
< fileNames
.size(); ++i
)
2347 p
.renamed_files
[nativeIndexes
[i
]] = fileNames
[i
].toString().toStdString();
2349 m_nativeSession
->async_add_torrent(p
);
2353 void SessionImpl::torrentContentRemovingFinished(const QString
&torrentName
, const QString
&errorMessage
)
2355 if (errorMessage
.isEmpty())
2357 LogMsg(tr("Torrent content removed. Torrent: \"%1\"").arg(torrentName
));
2361 LogMsg(tr("Failed to remove torrent content. Torrent: \"%1\". Error: \"%2\"")
2362 .arg(torrentName
, errorMessage
), Log::WARNING
);
2366 Torrent
*SessionImpl::getTorrent(const TorrentID
&id
) const
2368 return m_torrents
.value(id
);
2371 Torrent
*SessionImpl::findTorrent(const InfoHash
&infoHash
) const
2373 const auto id
= TorrentID::fromInfoHash(infoHash
);
2374 if (Torrent
*torrent
= m_torrents
.value(id
); torrent
)
2377 if (!infoHash
.isHybrid())
2378 return m_hybridTorrentsByAltID
.value(id
);
2380 // alternative ID can be useful to find existing torrent
2381 // in case if hybrid torrent was added by v1 info hash
2382 const auto altID
= TorrentID::fromSHA1Hash(infoHash
.v1());
2383 return m_torrents
.value(altID
);
2386 void SessionImpl::banIP(const QString
&ip
)
2388 if (m_bannedIPs
.get().contains(ip
))
2392 const lt::address addr
= lt::make_address(ip
.toLatin1().constData(), ec
);
2397 invokeAsync([session
= m_nativeSession
, addr
]
2399 lt::ip_filter filter
= session
->get_ip_filter();
2400 filter
.add_rule(addr
, addr
, lt::ip_filter::blocked
);
2401 session
->set_ip_filter(std::move(filter
));
2404 QStringList bannedIPs
= m_bannedIPs
;
2405 bannedIPs
.append(ip
);
2407 m_bannedIPs
= bannedIPs
;
2410 // Delete a torrent from the session, given its hash
2411 // and from the disk, if the corresponding deleteOption is chosen
2412 bool SessionImpl::removeTorrent(const TorrentID
&id
, const TorrentRemoveOption deleteOption
)
2414 TorrentImpl
*const torrent
= m_torrents
.take(id
);
2418 const TorrentID torrentID
= torrent
->id();
2419 const QString torrentName
= torrent
->name();
2421 qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrentID
.toString()));
2422 emit
torrentAboutToBeRemoved(torrent
);
2424 if (const InfoHash infoHash
= torrent
->infoHash(); infoHash
.isHybrid())
2425 m_hybridTorrentsByAltID
.remove(TorrentID::fromSHA1Hash(infoHash
.v1()));
2427 // Remove it from session
2428 if (deleteOption
== TorrentRemoveOption::KeepContent
)
2430 m_removingTorrents
[torrentID
] = {torrentName
, torrent
->actualStorageLocation(), {}, deleteOption
};
2432 const lt::torrent_handle nativeHandle
{torrent
->nativeHandle()};
2433 const auto iter
= std::find_if(m_moveStorageQueue
.begin(), m_moveStorageQueue
.end()
2434 , [&nativeHandle
](const MoveStorageJob
&job
)
2436 return job
.torrentHandle
== nativeHandle
;
2438 if (iter
!= m_moveStorageQueue
.end())
2440 // We shouldn't actually remove torrent until existing "move storage jobs" are done
2441 torrentQueuePositionBottom(nativeHandle
);
2442 nativeHandle
.unset_flags(lt::torrent_flags::auto_managed
);
2443 nativeHandle
.pause();
2447 m_nativeSession
->remove_torrent(nativeHandle
, lt::session::delete_partfile
);
2452 m_removingTorrents
[torrentID
] = {torrentName
, torrent
->actualStorageLocation(), torrent
->actualFilePaths(), deleteOption
};
2454 if (m_moveStorageQueue
.size() > 1)
2456 // Delete "move storage job" for the deleted torrent
2457 // (note: we shouldn't delete active job)
2458 const auto iter
= std::find_if((m_moveStorageQueue
.begin() + 1), m_moveStorageQueue
.end()
2459 , [torrent
](const MoveStorageJob
&job
)
2461 return job
.torrentHandle
== torrent
->nativeHandle();
2463 if (iter
!= m_moveStorageQueue
.end())
2464 m_moveStorageQueue
.erase(iter
);
2467 m_nativeSession
->remove_torrent(torrent
->nativeHandle(), lt::session::delete_partfile
);
2470 // Remove it from torrent resume directory
2471 m_resumeDataStorage
->remove(torrentID
);
2473 LogMsg(tr("Torrent removed. Torrent: \"%1\"").arg(torrentName
));
2478 bool SessionImpl::cancelDownloadMetadata(const TorrentID
&id
)
2480 const auto downloadedMetadataIter
= m_downloadedMetadata
.find(id
);
2481 if (downloadedMetadataIter
== m_downloadedMetadata
.end())
2484 const lt::torrent_handle nativeHandle
= downloadedMetadataIter
.value();
2485 m_downloadedMetadata
.erase(downloadedMetadataIter
);
2487 if (!nativeHandle
.is_valid())
2490 #ifdef QBT_USES_LIBTORRENT2
2491 const InfoHash infoHash
{nativeHandle
.info_hashes()};
2492 if (infoHash
.isHybrid())
2494 // if magnet link was hybrid initially then it is indexed also by v1 info hash
2495 // so we need to remove both entries
2496 const auto altID
= TorrentID::fromSHA1Hash(infoHash
.v1());
2497 m_downloadedMetadata
.remove(altID
);
2501 m_nativeSession
->remove_torrent(nativeHandle
);
2505 void SessionImpl::increaseTorrentsQueuePos(const QList
<TorrentID
> &ids
)
2507 using ElementType
= std::pair
<int, const TorrentImpl
*>;
2508 std::priority_queue
<ElementType
2509 , std::vector
<ElementType
>
2510 , std::greater
<ElementType
>> torrentQueue
;
2512 // Sort torrents by queue position
2513 for (const TorrentID
&id
: ids
)
2515 const TorrentImpl
*torrent
= m_torrents
.value(id
);
2516 if (!torrent
) continue;
2517 if (const int position
= torrent
->queuePosition(); position
>= 0)
2518 torrentQueue
.emplace(position
, torrent
);
2521 // Increase torrents queue position (starting with the one in the highest queue position)
2522 while (!torrentQueue
.empty())
2524 const TorrentImpl
*torrent
= torrentQueue
.top().second
;
2525 torrentQueuePositionUp(torrent
->nativeHandle());
2529 m_torrentsQueueChanged
= true;
2532 void SessionImpl::decreaseTorrentsQueuePos(const QList
<TorrentID
> &ids
)
2534 using ElementType
= std::pair
<int, const TorrentImpl
*>;
2535 std::priority_queue
<ElementType
> torrentQueue
;
2537 // Sort torrents by queue position
2538 for (const TorrentID
&id
: ids
)
2540 const TorrentImpl
*torrent
= m_torrents
.value(id
);
2541 if (!torrent
) continue;
2542 if (const int position
= torrent
->queuePosition(); position
>= 0)
2543 torrentQueue
.emplace(position
, torrent
);
2546 // Decrease torrents queue position (starting with the one in the lowest queue position)
2547 while (!torrentQueue
.empty())
2549 const TorrentImpl
*torrent
= torrentQueue
.top().second
;
2550 torrentQueuePositionDown(torrent
->nativeHandle());
2554 for (const lt::torrent_handle
&torrentHandle
: asConst(m_downloadedMetadata
))
2555 torrentQueuePositionBottom(torrentHandle
);
2557 m_torrentsQueueChanged
= true;
2560 void SessionImpl::topTorrentsQueuePos(const QList
<TorrentID
> &ids
)
2562 using ElementType
= std::pair
<int, const TorrentImpl
*>;
2563 std::priority_queue
<ElementType
> torrentQueue
;
2565 // Sort torrents by queue position
2566 for (const TorrentID
&id
: ids
)
2568 const TorrentImpl
*torrent
= m_torrents
.value(id
);
2569 if (!torrent
) continue;
2570 if (const int position
= torrent
->queuePosition(); position
>= 0)
2571 torrentQueue
.emplace(position
, torrent
);
2574 // Top torrents queue position (starting with the one in the lowest queue position)
2575 while (!torrentQueue
.empty())
2577 const TorrentImpl
*torrent
= torrentQueue
.top().second
;
2578 torrentQueuePositionTop(torrent
->nativeHandle());
2582 m_torrentsQueueChanged
= true;
2585 void SessionImpl::bottomTorrentsQueuePos(const QList
<TorrentID
> &ids
)
2587 using ElementType
= std::pair
<int, const TorrentImpl
*>;
2588 std::priority_queue
<ElementType
2589 , std::vector
<ElementType
>
2590 , std::greater
<ElementType
>> torrentQueue
;
2592 // Sort torrents by queue position
2593 for (const TorrentID
&id
: ids
)
2595 const TorrentImpl
*torrent
= m_torrents
.value(id
);
2596 if (!torrent
) continue;
2597 if (const int position
= torrent
->queuePosition(); position
>= 0)
2598 torrentQueue
.emplace(position
, torrent
);
2601 // Bottom torrents queue position (starting with the one in the highest queue position)
2602 while (!torrentQueue
.empty())
2604 const TorrentImpl
*torrent
= torrentQueue
.top().second
;
2605 torrentQueuePositionBottom(torrent
->nativeHandle());
2609 for (const lt::torrent_handle
&torrentHandle
: asConst(m_downloadedMetadata
))
2610 torrentQueuePositionBottom(torrentHandle
);
2612 m_torrentsQueueChanged
= true;
2615 void SessionImpl::handleTorrentResumeDataRequested(const TorrentImpl
*torrent
)
2617 qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent
->name()));
2621 QList
<Torrent
*> SessionImpl::torrents() const
2623 QList
<Torrent
*> result
;
2624 result
.reserve(m_torrents
.size());
2625 for (TorrentImpl
*torrent
: asConst(m_torrents
))
2631 qsizetype
SessionImpl::torrentsCount() const
2633 return m_torrents
.size();
2636 bool SessionImpl::addTorrent(const TorrentDescriptor
&torrentDescr
, const AddTorrentParams
¶ms
)
2641 return addTorrent_impl(torrentDescr
, params
);
2644 LoadTorrentParams
SessionImpl::initLoadTorrentParams(const AddTorrentParams
&addTorrentParams
)
2646 LoadTorrentParams loadTorrentParams
;
2648 loadTorrentParams
.name
= addTorrentParams
.name
;
2649 loadTorrentParams
.firstLastPiecePriority
= addTorrentParams
.firstLastPiecePriority
;
2650 loadTorrentParams
.hasFinishedStatus
= addTorrentParams
.skipChecking
; // do not react on 'torrent_finished_alert' when skipping
2651 loadTorrentParams
.contentLayout
= addTorrentParams
.contentLayout
.value_or(torrentContentLayout());
2652 loadTorrentParams
.operatingMode
= (addTorrentParams
.addForced
? TorrentOperatingMode::Forced
: TorrentOperatingMode::AutoManaged
);
2653 loadTorrentParams
.stopped
= addTorrentParams
.addStopped
.value_or(isAddTorrentStopped());
2654 loadTorrentParams
.stopCondition
= addTorrentParams
.stopCondition
.value_or(torrentStopCondition());
2655 loadTorrentParams
.addToQueueTop
= addTorrentParams
.addToQueueTop
.value_or(isAddTorrentToQueueTop());
2656 loadTorrentParams
.ratioLimit
= addTorrentParams
.ratioLimit
;
2657 loadTorrentParams
.seedingTimeLimit
= addTorrentParams
.seedingTimeLimit
;
2658 loadTorrentParams
.inactiveSeedingTimeLimit
= addTorrentParams
.inactiveSeedingTimeLimit
;
2659 loadTorrentParams
.shareLimitAction
= addTorrentParams
.shareLimitAction
;
2660 loadTorrentParams
.sslParameters
= addTorrentParams
.sslParameters
;
2662 const QString category
= addTorrentParams
.category
;
2663 if (!category
.isEmpty() && !m_categories
.contains(category
) && !addCategory(category
))
2664 loadTorrentParams
.category
= u
""_s
;
2666 loadTorrentParams
.category
= category
;
2668 const auto defaultSavePath
= suggestedSavePath(loadTorrentParams
.category
, addTorrentParams
.useAutoTMM
);
2669 const auto defaultDownloadPath
= suggestedDownloadPath(loadTorrentParams
.category
, addTorrentParams
.useAutoTMM
);
2671 loadTorrentParams
.useAutoTMM
= addTorrentParams
.useAutoTMM
.value_or(
2672 addTorrentParams
.savePath
.isEmpty() && addTorrentParams
.downloadPath
.isEmpty() && !isAutoTMMDisabledByDefault());
2674 if (!loadTorrentParams
.useAutoTMM
)
2676 if (addTorrentParams
.savePath
.isAbsolute())
2677 loadTorrentParams
.savePath
= addTorrentParams
.savePath
;
2679 loadTorrentParams
.savePath
= defaultSavePath
/ addTorrentParams
.savePath
;
2681 // if useDownloadPath isn't specified but downloadPath is explicitly set we prefer to use it
2682 const bool useDownloadPath
= addTorrentParams
.useDownloadPath
.value_or(!addTorrentParams
.downloadPath
.isEmpty() || isDownloadPathEnabled());
2683 if (useDownloadPath
)
2685 // Overridden "Download path" settings
2687 if (addTorrentParams
.downloadPath
.isAbsolute())
2689 loadTorrentParams
.downloadPath
= addTorrentParams
.downloadPath
;
2693 const Path basePath
= (!defaultDownloadPath
.isEmpty() ? defaultDownloadPath
: downloadPath());
2694 loadTorrentParams
.downloadPath
= basePath
/ addTorrentParams
.downloadPath
;
2699 for (const Tag
&tag
: addTorrentParams
.tags
)
2701 if (hasTag(tag
) || addTag(tag
))
2702 loadTorrentParams
.tags
.insert(tag
);
2705 return loadTorrentParams
;
2708 // Add a torrent to the BitTorrent session
2709 bool SessionImpl::addTorrent_impl(const TorrentDescriptor
&source
, const AddTorrentParams
&addTorrentParams
)
2711 Q_ASSERT(isRestored());
2713 const bool hasMetadata
= (source
.info().has_value());
2714 const auto infoHash
= source
.infoHash();
2715 const auto id
= TorrentID::fromInfoHash(infoHash
);
2717 // alternative ID can be useful to find existing torrent in case if hybrid torrent was added by v1 info hash
2718 const auto altID
= (infoHash
.isHybrid() ? TorrentID::fromSHA1Hash(infoHash
.v1()) : TorrentID());
2720 // We should not add the torrent if it is already
2721 // processed or is pending to add to session
2722 if (m_loadingTorrents
.contains(id
) || (infoHash
.isHybrid() && m_loadingTorrents
.contains(altID
)))
2725 if (Torrent
*torrent
= findTorrent(infoHash
))
2727 // a duplicate torrent is being added
2731 // Trying to set metadata to existing torrent in case if it has none
2732 torrent
->setMetadata(*source
.info());
2735 if (!isMergeTrackersEnabled())
2737 LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
2738 .arg(torrent
->name(), tr("Merging of trackers is disabled")));
2742 const bool isPrivate
= torrent
->isPrivate() || (hasMetadata
&& source
.info()->isPrivate());
2745 LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
2746 .arg(torrent
->name(), tr("Trackers cannot be merged because it is a private torrent")));
2750 // merge trackers and web seeds
2751 torrent
->addTrackers(source
.trackers());
2752 torrent
->addUrlSeeds(source
.urlSeeds());
2754 LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
2755 .arg(torrent
->name(), tr("Trackers are merged from new source")));
2759 // It looks illogical that we don't just use an existing handle,
2760 // but as previous experience has shown, it actually creates unnecessary
2761 // problems and unwanted behavior due to the fact that it was originally
2762 // added with parameters other than those provided by the user.
2763 cancelDownloadMetadata(id
);
2764 if (infoHash
.isHybrid())
2765 cancelDownloadMetadata(altID
);
2767 LoadTorrentParams loadTorrentParams
= initLoadTorrentParams(addTorrentParams
);
2768 lt::add_torrent_params
&p
= loadTorrentParams
.ltAddTorrentParams
;
2769 p
= source
.ltAddTorrentParams();
2771 bool isFindingIncompleteFiles
= false;
2773 const bool useAutoTMM
= loadTorrentParams
.useAutoTMM
;
2774 const Path actualSavePath
= useAutoTMM
? categorySavePath(loadTorrentParams
.category
) : loadTorrentParams
.savePath
;
2778 // Torrent that is being added with metadata is considered to be added as stopped
2779 // if "metadata received" stop condition is set for it.
2780 if (loadTorrentParams
.stopCondition
== Torrent::StopCondition::MetadataReceived
)
2782 loadTorrentParams
.stopped
= true;
2783 loadTorrentParams
.stopCondition
= Torrent::StopCondition::None
;
2786 const TorrentInfo
&torrentInfo
= *source
.info();
2788 Q_ASSERT(addTorrentParams
.filePaths
.isEmpty() || (addTorrentParams
.filePaths
.size() == torrentInfo
.filesCount()));
2790 PathList filePaths
= addTorrentParams
.filePaths
;
2791 if (filePaths
.isEmpty())
2793 filePaths
= torrentInfo
.filePaths();
2794 if (loadTorrentParams
.contentLayout
!= TorrentContentLayout::Original
)
2796 const Path originalRootFolder
= Path::findRootFolder(filePaths
);
2797 const auto originalContentLayout
= (originalRootFolder
.isEmpty()
2798 ? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder
);
2799 if (loadTorrentParams
.contentLayout
!= originalContentLayout
)
2801 if (loadTorrentParams
.contentLayout
== TorrentContentLayout::NoSubfolder
)
2802 Path::stripRootFolder(filePaths
);
2804 Path::addRootFolder(filePaths
, filePaths
.at(0).removedExtension());
2809 // if torrent name wasn't explicitly set we handle the case of
2810 // initial renaming of torrent content and rename torrent accordingly
2811 if (loadTorrentParams
.name
.isEmpty())
2813 QString contentName
= Path::findRootFolder(filePaths
).toString();
2814 if (contentName
.isEmpty() && (filePaths
.size() == 1))
2815 contentName
= filePaths
.at(0).filename();
2817 if (!contentName
.isEmpty() && (contentName
!= torrentInfo
.name()))
2818 loadTorrentParams
.name
= contentName
;
2821 const auto nativeIndexes
= torrentInfo
.nativeIndexes();
2823 Q_ASSERT(p
.file_priorities
.empty());
2824 Q_ASSERT(addTorrentParams
.filePriorities
.isEmpty() || (addTorrentParams
.filePriorities
.size() == nativeIndexes
.size()));
2825 QList
<DownloadPriority
> filePriorities
= addTorrentParams
.filePriorities
;
2827 // Filename filter should be applied before `findIncompleteFiles()` is called.
2828 if (filePriorities
.isEmpty() && isExcludedFileNamesEnabled())
2830 // Check file name blacklist when priorities are not explicitly set
2831 applyFilenameFilter(filePaths
, filePriorities
);
2834 if (!loadTorrentParams
.hasFinishedStatus
)
2836 const Path actualDownloadPath
= useAutoTMM
2837 ? categoryDownloadPath(loadTorrentParams
.category
) : loadTorrentParams
.downloadPath
;
2838 findIncompleteFiles(torrentInfo
, actualSavePath
, actualDownloadPath
, filePaths
);
2839 isFindingIncompleteFiles
= true;
2842 if (!isFindingIncompleteFiles
)
2844 for (int index
= 0; index
< filePaths
.size(); ++index
)
2845 p
.renamed_files
[nativeIndexes
[index
]] = filePaths
.at(index
).toString().toStdString();
2848 const int internalFilesCount
= torrentInfo
.nativeInfo()->files().num_files(); // including .pad files
2849 // Use qBittorrent default priority rather than libtorrent's (4)
2850 p
.file_priorities
= std::vector(internalFilesCount
, LT::toNative(DownloadPriority::Normal
));
2852 if (!filePriorities
.isEmpty())
2854 for (int i
= 0; i
< filePriorities
.size(); ++i
)
2855 p
.file_priorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(filePriorities
[i
]);
2862 if (loadTorrentParams
.name
.isEmpty() && !p
.name
.empty())
2863 loadTorrentParams
.name
= QString::fromStdString(p
.name
);
2866 p
.save_path
= actualSavePath
.toString().toStdString();
2868 if (isAddTrackersEnabled() && !(hasMetadata
&& p
.ti
->priv()))
2870 const auto maxTierIter
= std::max_element(p
.tracker_tiers
.cbegin(), p
.tracker_tiers
.cend());
2871 const int baseTier
= (maxTierIter
!= p
.tracker_tiers
.cend()) ? (*maxTierIter
+ 1) : 0;
2873 p
.trackers
.reserve(p
.trackers
.size() + static_cast<std::size_t>(m_additionalTrackerEntries
.size()));
2874 p
.tracker_tiers
.reserve(p
.trackers
.size() + static_cast<std::size_t>(m_additionalTrackerEntries
.size()));
2875 p
.tracker_tiers
.resize(p
.trackers
.size(), 0);
2876 for (const TrackerEntry
&trackerEntry
: asConst(m_additionalTrackerEntries
))
2878 p
.trackers
.emplace_back(trackerEntry
.url
.toStdString());
2879 p
.tracker_tiers
.emplace_back(Utils::Number::clampingAdd(trackerEntry
.tier
, baseTier
));
2883 p
.upload_limit
= addTorrentParams
.uploadLimit
;
2884 p
.download_limit
= addTorrentParams
.downloadLimit
;
2886 // Preallocation mode
2887 p
.storage_mode
= isPreallocationEnabled() ? lt::storage_mode_allocate
: lt::storage_mode_sparse
;
2889 if (addTorrentParams
.sequential
)
2890 p
.flags
|= lt::torrent_flags::sequential_download
;
2892 p
.flags
&= ~lt::torrent_flags::sequential_download
;
2895 // Skip checking and directly start seeding
2896 if (addTorrentParams
.skipChecking
)
2897 p
.flags
|= lt::torrent_flags::seed_mode
;
2899 p
.flags
&= ~lt::torrent_flags::seed_mode
;
2901 if (loadTorrentParams
.stopped
|| (loadTorrentParams
.operatingMode
== TorrentOperatingMode::AutoManaged
))
2902 p
.flags
|= lt::torrent_flags::paused
;
2904 p
.flags
&= ~lt::torrent_flags::paused
;
2905 if (loadTorrentParams
.stopped
|| (loadTorrentParams
.operatingMode
== TorrentOperatingMode::Forced
))
2906 p
.flags
&= ~lt::torrent_flags::auto_managed
;
2908 p
.flags
|= lt::torrent_flags::auto_managed
;
2910 p
.flags
|= lt::torrent_flags::duplicate_is_error
;
2912 p
.added_time
= std::time(nullptr);
2915 p
.max_connections
= maxConnectionsPerTorrent();
2916 p
.max_uploads
= maxUploadsPerTorrent();
2918 p
.userdata
= LTClientData(new ExtensionData
);
2919 #ifndef QBT_USES_LIBTORRENT2
2920 p
.storage
= customStorageConstructor
;
2923 m_loadingTorrents
.insert(id
, loadTorrentParams
);
2924 if (infoHash
.isHybrid())
2925 m_hybridTorrentsByAltID
.insert(altID
, nullptr);
2926 if (!isFindingIncompleteFiles
)
2927 m_nativeSession
->async_add_torrent(p
);
2932 void SessionImpl::findIncompleteFiles(const TorrentInfo
&torrentInfo
, const Path
&savePath
2933 , const Path
&downloadPath
, const PathList
&filePaths
) const
2935 Q_ASSERT(filePaths
.isEmpty() || (filePaths
.size() == torrentInfo
.filesCount()));
2937 const auto searchId
= TorrentID::fromInfoHash(torrentInfo
.infoHash());
2938 const PathList originalFileNames
= (filePaths
.isEmpty() ? torrentInfo
.filePaths() : filePaths
);
2939 QMetaObject::invokeMethod(m_fileSearcher
, [=, this]
2941 m_fileSearcher
->search(searchId
, originalFileNames
, savePath
, downloadPath
, isAppendExtensionEnabled());
2945 void SessionImpl::enablePortMapping()
2949 if (m_isPortMappingEnabled
)
2952 lt::settings_pack settingsPack
;
2953 settingsPack
.set_bool(lt::settings_pack::enable_upnp
, true);
2954 settingsPack
.set_bool(lt::settings_pack::enable_natpmp
, true);
2955 m_nativeSession
->apply_settings(std::move(settingsPack
));
2957 m_isPortMappingEnabled
= true;
2959 LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO
);
2963 void SessionImpl::disablePortMapping()
2967 if (!m_isPortMappingEnabled
)
2970 lt::settings_pack settingsPack
;
2971 settingsPack
.set_bool(lt::settings_pack::enable_upnp
, false);
2972 settingsPack
.set_bool(lt::settings_pack::enable_natpmp
, false);
2973 m_nativeSession
->apply_settings(std::move(settingsPack
));
2975 m_mappedPorts
.clear();
2976 m_isPortMappingEnabled
= false;
2978 LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO
);
2982 void SessionImpl::addMappedPorts(const QSet
<quint16
> &ports
)
2984 invokeAsync([this, ports
]
2986 if (!m_isPortMappingEnabled
)
2989 for (const quint16 port
: ports
)
2991 if (!m_mappedPorts
.contains(port
))
2992 m_mappedPorts
.insert(port
, m_nativeSession
->add_port_mapping(lt::session::tcp
, port
, port
));
2997 void SessionImpl::removeMappedPorts(const QSet
<quint16
> &ports
)
2999 invokeAsync([this, ports
]
3001 if (!m_isPortMappingEnabled
)
3004 Algorithm::removeIf(m_mappedPorts
, [this, ports
](const quint16 port
, const std::vector
<lt::port_mapping_t
> &handles
)
3006 if (!ports
.contains(port
))
3009 for (const lt::port_mapping_t
&handle
: handles
)
3010 m_nativeSession
->delete_port_mapping(handle
);
3017 // Add a torrent to libtorrent session in hidden mode
3018 // and force it to download its metadata
3019 bool SessionImpl::downloadMetadata(const TorrentDescriptor
&torrentDescr
)
3021 Q_ASSERT(!torrentDescr
.info().has_value());
3022 if (torrentDescr
.info().has_value()) [[unlikely
]]
3025 const InfoHash infoHash
= torrentDescr
.infoHash();
3027 // We should not add torrent if it's already
3028 // processed or adding to session
3029 if (isKnownTorrent(infoHash
))
3032 lt::add_torrent_params p
= torrentDescr
.ltAddTorrentParams();
3034 if (isAddTrackersEnabled())
3036 // Use "additional trackers" when metadata retrieving (this can help when the DHT nodes are few)
3038 const auto maxTierIter
= std::max_element(p
.tracker_tiers
.cbegin(), p
.tracker_tiers
.cend());
3039 const int baseTier
= (maxTierIter
!= p
.tracker_tiers
.cend()) ? (*maxTierIter
+ 1) : 0;
3041 p
.trackers
.reserve(p
.trackers
.size() + static_cast<std::size_t>(m_additionalTrackerEntries
.size()));
3042 p
.tracker_tiers
.reserve(p
.trackers
.size() + static_cast<std::size_t>(m_additionalTrackerEntries
.size()));
3043 p
.tracker_tiers
.resize(p
.trackers
.size(), 0);
3044 for (const TrackerEntry
&trackerEntry
: asConst(m_additionalTrackerEntries
))
3046 p
.trackers
.emplace_back(trackerEntry
.url
.toStdString());
3047 p
.tracker_tiers
.emplace_back(Utils::Number::clampingAdd(trackerEntry
.tier
, baseTier
));
3052 // Preallocation mode
3053 if (isPreallocationEnabled())
3054 p
.storage_mode
= lt::storage_mode_allocate
;
3056 p
.storage_mode
= lt::storage_mode_sparse
;
3059 p
.max_connections
= maxConnectionsPerTorrent();
3060 p
.max_uploads
= maxUploadsPerTorrent();
3062 const auto id
= TorrentID::fromInfoHash(infoHash
);
3063 const Path savePath
= Utils::Fs::tempPath() / Path(id
.toString());
3064 p
.save_path
= savePath
.toString().toStdString();
3067 p
.flags
&= ~lt::torrent_flags::paused
;
3068 p
.flags
&= ~lt::torrent_flags::auto_managed
;
3070 // Solution to avoid accidental file writes
3071 p
.flags
|= lt::torrent_flags::upload_mode
;
3073 #ifndef QBT_USES_LIBTORRENT2
3074 p
.storage
= customStorageConstructor
;
3077 // Adding torrent to libtorrent session
3078 m_nativeSession
->async_add_torrent(p
);
3079 m_downloadedMetadata
.insert(id
, {});
3084 void SessionImpl::exportTorrentFile(const Torrent
*torrent
, const Path
&folderPath
)
3086 if (!folderPath
.exists() && !Utils::Fs::mkpath(folderPath
))
3089 const QString validName
= Utils::Fs::toValidFileName(torrent
->name());
3090 QString torrentExportFilename
= u
"%1.torrent"_s
.arg(validName
);
3091 Path newTorrentPath
= folderPath
/ Path(torrentExportFilename
);
3093 while (newTorrentPath
.exists())
3095 // Append number to torrent name to make it unique
3096 torrentExportFilename
= u
"%1 %2.torrent"_s
.arg(validName
).arg(++counter
);
3097 newTorrentPath
= folderPath
/ Path(torrentExportFilename
);
3100 const nonstd::expected
<void, QString
> result
= torrent
->exportToFile(newTorrentPath
);
3103 LogMsg(tr("Failed to export torrent. Torrent: \"%1\". Destination: \"%2\". Reason: \"%3\"")
3104 .arg(torrent
->name(), newTorrentPath
.toString(), result
.error()), Log::WARNING
);
3108 void SessionImpl::generateResumeData()
3110 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
3112 if (torrent
->needSaveResumeData())
3113 torrent
->requestResumeData();
3118 void SessionImpl::saveResumeData()
3120 for (TorrentImpl
*torrent
: asConst(m_torrents
))
3122 // When the session is terminated due to unrecoverable error
3123 // some of the torrent handles can be corrupted
3126 torrent
->requestResumeData(lt::torrent_handle::only_if_modified
);
3128 catch (const std::exception
&) {}
3131 // clear queued storage move jobs except the current ongoing one
3132 if (m_moveStorageQueue
.size() > 1)
3133 m_moveStorageQueue
.resize(1);
3135 QElapsedTimer timer
;
3138 while ((m_numResumeData
> 0) || !m_moveStorageQueue
.isEmpty() || m_needSaveTorrentsQueue
)
3140 const lt::seconds waitTime
{5};
3141 const lt::seconds expireTime
{30};
3143 // only terminate when no storage is moving
3144 if (timer
.hasExpired(lt::total_milliseconds(expireTime
)) && m_moveStorageQueue
.isEmpty())
3146 LogMsg(tr("Aborted saving resume data. Number of outstanding torrents: %1").arg(QString::number(m_numResumeData
))
3151 const std::vector
<lt::alert
*> alerts
= getPendingAlerts(waitTime
);
3153 bool hasWantedAlert
= false;
3154 for (const lt::alert
*alert
: alerts
)
3156 if (const int alertType
= alert
->type();
3157 (alertType
== lt::save_resume_data_alert::alert_type
) || (alertType
== lt::save_resume_data_failed_alert::alert_type
)
3158 || (alertType
== lt::storage_moved_alert::alert_type
) || (alertType
== lt::storage_moved_failed_alert::alert_type
)
3159 || (alertType
== lt::state_update_alert::alert_type
))
3161 hasWantedAlert
= true;
3172 void SessionImpl::saveTorrentsQueue()
3174 QList
<TorrentID
> queue
;
3175 for (const TorrentImpl
*torrent
: asConst(m_torrents
))
3177 if (const int queuePos
= torrent
->queuePosition(); queuePos
>= 0)
3179 if (queuePos
>= queue
.size())
3180 queue
.resize(queuePos
+ 1);
3181 queue
[queuePos
] = torrent
->id();
3185 m_resumeDataStorage
->storeQueue(queue
);
3186 m_needSaveTorrentsQueue
= false;
3189 void SessionImpl::removeTorrentsQueue()
3191 m_resumeDataStorage
->storeQueue({});
3192 m_torrentsQueueChanged
= false;
3193 m_needSaveTorrentsQueue
= false;
3196 void SessionImpl::setSavePath(const Path
&path
)
3198 const auto newPath
= (path
.isAbsolute() ? path
: (specialFolderLocation(SpecialFolder::Downloads
) / path
));
3199 if (newPath
== m_savePath
)
3202 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3204 QSet
<QString
> affectedCatogories
{{}}; // includes default (unnamed) category
3205 for (auto it
= m_categories
.cbegin(); it
!= m_categories
.cend(); ++it
)
3207 const QString
&categoryName
= it
.key();
3208 const CategoryOptions
&categoryOptions
= it
.value();
3209 if (categoryOptions
.savePath
.isRelative())
3210 affectedCatogories
.insert(categoryName
);
3213 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
3215 if (affectedCatogories
.contains(torrent
->category()))
3216 torrent
->setAutoTMMEnabled(false);
3220 m_savePath
= newPath
;
3221 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
3222 torrent
->handleCategoryOptionsChanged();
3225 void SessionImpl::setDownloadPath(const Path
&path
)
3227 const Path newPath
= (path
.isAbsolute() ? path
: (savePath() / Path(u
"temp"_s
) / path
));
3228 if (newPath
== m_downloadPath
)
3231 if (isDisableAutoTMMWhenDefaultSavePathChanged())
3233 QSet
<QString
> affectedCatogories
{{}}; // includes default (unnamed) category
3234 for (auto it
= m_categories
.cbegin(); it
!= m_categories
.cend(); ++it
)
3236 const QString
&categoryName
= it
.key();
3237 const CategoryOptions
&categoryOptions
= it
.value();
3238 const DownloadPathOption downloadPathOption
=
3239 categoryOptions
.downloadPath
.value_or(DownloadPathOption
{isDownloadPathEnabled(), downloadPath()});
3240 if (downloadPathOption
.enabled
&& downloadPathOption
.path
.isRelative())
3241 affectedCatogories
.insert(categoryName
);
3244 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
3246 if (affectedCatogories
.contains(torrent
->category()))
3247 torrent
->setAutoTMMEnabled(false);
3251 m_downloadPath
= newPath
;
3252 for (TorrentImpl
*const torrent
: asConst(m_torrents
))
3253 torrent
->handleCategoryOptionsChanged();
3256 QStringList
SessionImpl::getListeningIPs() const
3260 const QString ifaceName
= networkInterface();
3261 const QString ifaceAddr
= networkInterfaceAddress();
3262 const QHostAddress
configuredAddr(ifaceAddr
);
3263 const bool allIPv4
= (ifaceAddr
== u
"0.0.0.0"); // Means All IPv4 addresses
3264 const bool allIPv6
= (ifaceAddr
== u
"::"); // Means All IPv6 addresses
3266 if (!ifaceAddr
.isEmpty() && !allIPv4
&& !allIPv6
&& configuredAddr
.isNull())
3268 LogMsg(tr("The configured network address is invalid. Address: \"%1\"").arg(ifaceAddr
), Log::CRITICAL
);
3269 // Pass the invalid user configured interface name/address to libtorrent
3270 // in hopes that it will come online later.
3271 // This will not cause IP leak but allow user to reconnect the interface
3272 // and re-establish connection without restarting the client.
3273 IPs
.append(ifaceAddr
);
3277 if (ifaceName
.isEmpty())
3279 if (ifaceAddr
.isEmpty())
3280 return {u
"0.0.0.0"_s
, u
"::"_s
}; // Indicates all interfaces + all addresses (aka default)
3283 return {u
"0.0.0.0"_s
};
3289 const auto checkAndAddIP
= [allIPv4
, allIPv6
, &IPs
](const QHostAddress
&addr
, const QHostAddress
&match
)
3291 if ((allIPv4
&& (addr
.protocol() != QAbstractSocket::IPv4Protocol
))
3292 || (allIPv6
&& (addr
.protocol() != QAbstractSocket::IPv6Protocol
)))
3295 if ((match
== addr
) || allIPv4
|| allIPv6
)
3296 IPs
.append(addr
.toString());
3299 if (ifaceName
.isEmpty())
3301 const QList
<QHostAddress
> addresses
= QNetworkInterface::allAddresses();
3302 for (const auto &addr
: addresses
)
3303 checkAndAddIP(addr
, configuredAddr
);
3305 // At this point ifaceAddr was non-empty
3306 // If IPs.isEmpty() it means the configured Address was not found
3309 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3310 .arg(ifaceAddr
), Log::CRITICAL
);
3311 IPs
.append(ifaceAddr
);
3317 // Attempt to listen on provided interface
3318 const QNetworkInterface networkIFace
= QNetworkInterface::interfaceFromName(ifaceName
);
3319 if (!networkIFace
.isValid())
3321 qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName
));
3322 LogMsg(tr("The configured network interface is invalid. Interface: \"%1\"").arg(ifaceName
), Log::CRITICAL
);
3323 IPs
.append(ifaceName
);
3327 if (ifaceAddr
.isEmpty())
3329 IPs
.append(ifaceName
);
3330 return IPs
; // On Windows calling code converts it to GUID
3333 const QList
<QNetworkAddressEntry
> addresses
= networkIFace
.addressEntries();
3334 qDebug() << "This network interface has " << addresses
.size() << " IP addresses";
3335 for (const QNetworkAddressEntry
&entry
: addresses
)
3336 checkAndAddIP(entry
.ip(), configuredAddr
);
3338 // Make sure there is at least one IP
3339 // At this point there was an explicit interface and an explicit address set
3340 // and the address should have been found
3343 LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
3344 .arg(ifaceAddr
), Log::CRITICAL
);
3345 IPs
.append(ifaceAddr
);
3351 // Set the ports range in which is chosen the port
3352 // the BitTorrent session will listen to
3353 void SessionImpl::configureListeningInterface()
3355 m_listenInterfaceConfigured
= false;
3356 configureDeferred();
3359 int SessionImpl::globalDownloadSpeedLimit() const
3361 // Unfortunately the value was saved as KiB instead of B.
3362 // But it is better to pass it around internally(+ webui) as Bytes.
3363 return m_globalDownloadSpeedLimit
* 1024;
3366 void SessionImpl::setGlobalDownloadSpeedLimit(const int limit
)
3368 // Unfortunately the value was saved as KiB instead of B.
3369 // But it is better to pass it around internally(+ webui) as Bytes.
3370 if (limit
== globalDownloadSpeedLimit())
3374 m_globalDownloadSpeedLimit
= 0;
3375 else if (limit
<= 1024)
3376 m_globalDownloadSpeedLimit
= 1;
3378 m_globalDownloadSpeedLimit
= (limit
/ 1024);
3380 if (!isAltGlobalSpeedLimitEnabled())
3381 configureDeferred();
3384 int SessionImpl::globalUploadSpeedLimit() const
3386 // Unfortunately the value was saved as KiB instead of B.
3387 // But it is better to pass it around internally(+ webui) as Bytes.
3388 return m_globalUploadSpeedLimit
* 1024;
3391 void SessionImpl::setGlobalUploadSpeedLimit(const int limit
)
3393 // Unfortunately the value was saved as KiB instead of B.
3394 // But it is better to pass it around internally(+ webui) as Bytes.
3395 if (limit
== globalUploadSpeedLimit())
3399 m_globalUploadSpeedLimit
= 0;
3400 else if (limit
<= 1024)
3401 m_globalUploadSpeedLimit
= 1;
3403 m_globalUploadSpeedLimit
= (limit
/ 1024);
3405 if (!isAltGlobalSpeedLimitEnabled())
3406 configureDeferred();
3409 int SessionImpl::altGlobalDownloadSpeedLimit() const
3411 // Unfortunately the value was saved as KiB instead of B.
3412 // But it is better to pass it around internally(+ webui) as Bytes.
3413 return m_altGlobalDownloadSpeedLimit
* 1024;
3416 void SessionImpl::setAltGlobalDownloadSpeedLimit(const int limit
)
3418 // Unfortunately the value was saved as KiB instead of B.
3419 // But it is better to pass it around internally(+ webui) as Bytes.
3420 if (limit
== altGlobalDownloadSpeedLimit())
3424 m_altGlobalDownloadSpeedLimit
= 0;
3425 else if (limit
<= 1024)
3426 m_altGlobalDownloadSpeedLimit
= 1;
3428 m_altGlobalDownloadSpeedLimit
= (limit
/ 1024);
3430 if (isAltGlobalSpeedLimitEnabled())
3431 configureDeferred();
3434 int SessionImpl::altGlobalUploadSpeedLimit() const
3436 // Unfortunately the value was saved as KiB instead of B.
3437 // But it is better to pass it around internally(+ webui) as Bytes.
3438 return m_altGlobalUploadSpeedLimit
* 1024;
3441 void SessionImpl::setAltGlobalUploadSpeedLimit(const int limit
)
3443 // Unfortunately the value was saved as KiB instead of B.
3444 // But it is better to pass it around internally(+ webui) as Bytes.
3445 if (limit
== altGlobalUploadSpeedLimit())
3449 m_altGlobalUploadSpeedLimit
= 0;
3450 else if (limit
<= 1024)
3451 m_altGlobalUploadSpeedLimit
= 1;
3453 m_altGlobalUploadSpeedLimit
= (limit
/ 1024);
3455 if (isAltGlobalSpeedLimitEnabled())
3456 configureDeferred();
3459 int SessionImpl::downloadSpeedLimit() const
3461 return isAltGlobalSpeedLimitEnabled()
3462 ? altGlobalDownloadSpeedLimit()
3463 : globalDownloadSpeedLimit();
3466 void SessionImpl::setDownloadSpeedLimit(const int limit
)
3468 if (isAltGlobalSpeedLimitEnabled())
3469 setAltGlobalDownloadSpeedLimit(limit
);
3471 setGlobalDownloadSpeedLimit(limit
);
3474 int SessionImpl::uploadSpeedLimit() const
3476 return isAltGlobalSpeedLimitEnabled()
3477 ? altGlobalUploadSpeedLimit()
3478 : globalUploadSpeedLimit();
3481 void SessionImpl::setUploadSpeedLimit(const int limit
)
3483 if (isAltGlobalSpeedLimitEnabled())
3484 setAltGlobalUploadSpeedLimit(limit
);
3486 setGlobalUploadSpeedLimit(limit
);
3489 bool SessionImpl::isAltGlobalSpeedLimitEnabled() const
3491 return m_isAltGlobalSpeedLimitEnabled
;
3494 void SessionImpl::setAltGlobalSpeedLimitEnabled(const bool enabled
)
3496 if (enabled
== isAltGlobalSpeedLimitEnabled()) return;
3498 // Save new state to remember it on startup
3499 m_isAltGlobalSpeedLimitEnabled
= enabled
;
3500 applyBandwidthLimits();
3502 emit
speedLimitModeChanged(m_isAltGlobalSpeedLimitEnabled
);
3505 bool SessionImpl::isBandwidthSchedulerEnabled() const
3507 return m_isBandwidthSchedulerEnabled
;
3510 void SessionImpl::setBandwidthSchedulerEnabled(const bool enabled
)
3512 if (enabled
!= isBandwidthSchedulerEnabled())
3514 m_isBandwidthSchedulerEnabled
= enabled
;
3516 enableBandwidthScheduler();
3518 delete m_bwScheduler
;
3522 bool SessionImpl::isPerformanceWarningEnabled() const
3524 return m_isPerformanceWarningEnabled
;
3527 void SessionImpl::setPerformanceWarningEnabled(const bool enable
)
3529 if (enable
== m_isPerformanceWarningEnabled
)
3532 m_isPerformanceWarningEnabled
= enable
;
3533 configureDeferred();
3536 int SessionImpl::saveResumeDataInterval() const
3538 return m_saveResumeDataInterval
;
3541 void SessionImpl::setSaveResumeDataInterval(const int value
)
3543 if (value
== m_saveResumeDataInterval
)
3546 m_saveResumeDataInterval
= value
;
3550 m_resumeDataTimer
->setInterval(std::chrono::minutes(value
));
3551 m_resumeDataTimer
->start();
3555 m_resumeDataTimer
->stop();
3559 std::chrono::minutes
SessionImpl::saveStatisticsInterval() const
3561 return std::chrono::minutes(m_saveStatisticsInterval
);
3564 void SessionImpl::setSaveStatisticsInterval(const std::chrono::minutes timeInMinutes
)
3566 m_saveStatisticsInterval
= timeInMinutes
.count();
3569 int SessionImpl::shutdownTimeout() const
3571 return m_shutdownTimeout
;
3574 void SessionImpl::setShutdownTimeout(const int value
)
3576 m_shutdownTimeout
= value
;
3579 int SessionImpl::port() const
3584 void SessionImpl::setPort(const int port
)
3589 configureListeningInterface();
3591 if (isReannounceWhenAddressChangedEnabled())
3592 reannounceToAllTrackers();
3596 bool SessionImpl::isSSLEnabled() const
3598 return m_sslEnabled
;
3601 void SessionImpl::setSSLEnabled(const bool enabled
)
3603 if (enabled
== isSSLEnabled())
3606 m_sslEnabled
= enabled
;
3607 configureListeningInterface();
3609 if (isReannounceWhenAddressChangedEnabled())
3610 reannounceToAllTrackers();
3613 int SessionImpl::sslPort() const
3618 void SessionImpl::setSSLPort(const int port
)
3620 if (port
== sslPort())
3624 configureListeningInterface();
3626 if (isReannounceWhenAddressChangedEnabled())
3627 reannounceToAllTrackers();
3630 QString
SessionImpl::networkInterface() const
3632 return m_networkInterface
;
3635 void SessionImpl::setNetworkInterface(const QString
&iface
)
3637 if (iface
!= networkInterface())
3639 m_networkInterface
= iface
;
3640 configureListeningInterface();
3644 QString
SessionImpl::networkInterfaceName() const
3646 return m_networkInterfaceName
;
3649 void SessionImpl::setNetworkInterfaceName(const QString
&name
)
3651 m_networkInterfaceName
= name
;
3654 QString
SessionImpl::networkInterfaceAddress() const
3656 return m_networkInterfaceAddress
;
3659 void SessionImpl::setNetworkInterfaceAddress(const QString
&address
)
3661 if (address
!= networkInterfaceAddress())
3663 m_networkInterfaceAddress
= address
;
3664 configureListeningInterface();
3668 int SessionImpl::encryption() const
3670 return m_encryption
;
3673 void SessionImpl::setEncryption(const int state
)
3675 if (state
!= encryption())
3677 m_encryption
= state
;
3678 configureDeferred();
3679 LogMsg(tr("Encryption support: %1").arg(
3680 state
== 0 ? tr("ON") : ((state
== 1) ? tr("FORCED") : tr("OFF")))
3685 int SessionImpl::maxActiveCheckingTorrents() const
3687 return m_maxActiveCheckingTorrents
;
3690 void SessionImpl::setMaxActiveCheckingTorrents(const int val
)
3692 if (val
== m_maxActiveCheckingTorrents
)
3695 m_maxActiveCheckingTorrents
= val
;
3696 configureDeferred();
3699 bool SessionImpl::isI2PEnabled() const
3701 return m_isI2PEnabled
;
3704 void SessionImpl::setI2PEnabled(const bool enabled
)
3706 if (m_isI2PEnabled
!= enabled
)
3708 m_isI2PEnabled
= enabled
;
3709 configureDeferred();
3713 QString
SessionImpl::I2PAddress() const
3715 return m_I2PAddress
;
3718 void SessionImpl::setI2PAddress(const QString
&address
)
3720 if (m_I2PAddress
!= address
)
3722 m_I2PAddress
= address
;
3723 configureDeferred();
3727 int SessionImpl::I2PPort() const
3732 void SessionImpl::setI2PPort(int port
)
3734 if (m_I2PPort
!= port
)
3737 configureDeferred();
3741 bool SessionImpl::I2PMixedMode() const
3743 return m_I2PMixedMode
;
3746 void SessionImpl::setI2PMixedMode(const bool enabled
)
3748 if (m_I2PMixedMode
!= enabled
)
3750 m_I2PMixedMode
= enabled
;
3751 configureDeferred();
3755 int SessionImpl::I2PInboundQuantity() const
3757 return m_I2PInboundQuantity
;
3760 void SessionImpl::setI2PInboundQuantity(const int value
)
3762 if (value
== m_I2PInboundQuantity
)
3765 m_I2PInboundQuantity
= value
;
3766 configureDeferred();
3769 int SessionImpl::I2POutboundQuantity() const
3771 return m_I2POutboundQuantity
;
3774 void SessionImpl::setI2POutboundQuantity(const int value
)
3776 if (value
== m_I2POutboundQuantity
)
3779 m_I2POutboundQuantity
= value
;
3780 configureDeferred();
3783 int SessionImpl::I2PInboundLength() const
3785 return m_I2PInboundLength
;
3788 void SessionImpl::setI2PInboundLength(const int value
)
3790 if (value
== m_I2PInboundLength
)
3793 m_I2PInboundLength
= value
;
3794 configureDeferred();
3797 int SessionImpl::I2POutboundLength() const
3799 return m_I2POutboundLength
;
3802 void SessionImpl::setI2POutboundLength(const int value
)
3804 if (value
== m_I2POutboundLength
)
3807 m_I2POutboundLength
= value
;
3808 configureDeferred();
3811 bool SessionImpl::isProxyPeerConnectionsEnabled() const
3813 return m_isProxyPeerConnectionsEnabled
;
3816 void SessionImpl::setProxyPeerConnectionsEnabled(const bool enabled
)
3818 if (enabled
!= isProxyPeerConnectionsEnabled())
3820 m_isProxyPeerConnectionsEnabled
= enabled
;
3821 configureDeferred();
3825 ChokingAlgorithm
SessionImpl::chokingAlgorithm() const
3827 return m_chokingAlgorithm
;
3830 void SessionImpl::setChokingAlgorithm(const ChokingAlgorithm mode
)
3832 if (mode
== m_chokingAlgorithm
) return;
3834 m_chokingAlgorithm
= mode
;
3835 configureDeferred();
3838 SeedChokingAlgorithm
SessionImpl::seedChokingAlgorithm() const
3840 return m_seedChokingAlgorithm
;
3843 void SessionImpl::setSeedChokingAlgorithm(const SeedChokingAlgorithm mode
)
3845 if (mode
== m_seedChokingAlgorithm
) return;
3847 m_seedChokingAlgorithm
= mode
;
3848 configureDeferred();
3851 bool SessionImpl::isAddTrackersEnabled() const
3853 return m_isAddTrackersEnabled
;
3856 void SessionImpl::setAddTrackersEnabled(const bool enabled
)
3858 m_isAddTrackersEnabled
= enabled
;
3861 QString
SessionImpl::additionalTrackers() const
3863 return m_additionalTrackers
;
3866 void SessionImpl::setAdditionalTrackers(const QString
&trackers
)
3868 if (trackers
== additionalTrackers())
3871 m_additionalTrackers
= trackers
;
3872 populateAdditionalTrackers();
3875 bool SessionImpl::isIPFilteringEnabled() const
3877 return m_isIPFilteringEnabled
;
3880 void SessionImpl::setIPFilteringEnabled(const bool enabled
)
3882 if (enabled
!= m_isIPFilteringEnabled
)
3884 m_isIPFilteringEnabled
= enabled
;
3885 m_IPFilteringConfigured
= false;
3886 configureDeferred();
3890 Path
SessionImpl::IPFilterFile() const
3892 return m_IPFilterFile
;
3895 void SessionImpl::setIPFilterFile(const Path
&path
)
3897 if (path
!= IPFilterFile())
3899 m_IPFilterFile
= path
;
3900 m_IPFilteringConfigured
= false;
3901 configureDeferred();
3905 bool SessionImpl::isExcludedFileNamesEnabled() const
3907 return m_isExcludedFileNamesEnabled
;
3910 void SessionImpl::setExcludedFileNamesEnabled(const bool enabled
)
3912 if (m_isExcludedFileNamesEnabled
== enabled
)
3915 m_isExcludedFileNamesEnabled
= enabled
;
3918 populateExcludedFileNamesRegExpList();
3920 m_excludedFileNamesRegExpList
.clear();
3923 QStringList
SessionImpl::excludedFileNames() const
3925 return m_excludedFileNames
;
3928 void SessionImpl::setExcludedFileNames(const QStringList
&excludedFileNames
)
3930 if (excludedFileNames
!= m_excludedFileNames
)
3932 m_excludedFileNames
= excludedFileNames
;
3933 populateExcludedFileNamesRegExpList();
3937 void SessionImpl::populateExcludedFileNamesRegExpList()
3939 const QStringList excludedNames
= excludedFileNames();
3941 m_excludedFileNamesRegExpList
.clear();
3942 m_excludedFileNamesRegExpList
.reserve(excludedNames
.size());
3944 for (const QString
&str
: excludedNames
)
3946 const QString pattern
= QRegularExpression::wildcardToRegularExpression(str
);
3947 const QRegularExpression re
{pattern
, QRegularExpression::CaseInsensitiveOption
};
3948 m_excludedFileNamesRegExpList
.append(re
);
3952 void SessionImpl::applyFilenameFilter(const PathList
&files
, QList
<DownloadPriority
> &priorities
)
3954 if (!isExcludedFileNamesEnabled())
3957 const auto isFilenameExcluded
= [patterns
= m_excludedFileNamesRegExpList
](const Path
&fileName
)
3959 return std::any_of(patterns
.begin(), patterns
.end(), [&fileName
](const QRegularExpression
&re
)
3961 Path path
= fileName
;
3962 while (!re
.match(path
.filename()).hasMatch())
3964 path
= path
.parentPath();
3972 priorities
.resize(files
.count(), DownloadPriority::Normal
);
3973 for (int i
= 0; i
< priorities
.size(); ++i
)
3975 if (priorities
[i
] == BitTorrent::DownloadPriority::Ignored
)
3978 if (isFilenameExcluded(files
.at(i
)))
3979 priorities
[i
] = BitTorrent::DownloadPriority::Ignored
;
3983 void SessionImpl::setBannedIPs(const QStringList
&newList
)
3985 if (newList
== m_bannedIPs
)
3986 return; // do nothing
3987 // here filter out incorrect IP
3988 QStringList filteredList
;
3989 for (const QString
&ip
: newList
)
3991 if (Utils::Net::isValidIP(ip
))
3993 // the same IPv6 addresses could be written in different forms;
3994 // QHostAddress::toString() result format follows RFC5952;
3995 // thus we avoid duplicate entries pointing to the same address
3996 filteredList
<< QHostAddress(ip
).toString();
4000 LogMsg(tr("Rejected invalid IP address while applying the list of banned IP addresses. IP: \"%1\"")
4005 // now we have to sort IPs and make them unique
4006 filteredList
.sort();
4007 filteredList
.removeDuplicates();
4008 // Again ensure that the new list is different from the stored one.
4009 if (filteredList
== m_bannedIPs
)
4010 return; // do nothing
4011 // store to session settings
4012 // also here we have to recreate filter list including 3rd party ban file
4013 // and install it again into m_session
4014 m_bannedIPs
= filteredList
;
4015 m_IPFilteringConfigured
= false;
4016 configureDeferred();
4019 ResumeDataStorageType
SessionImpl::resumeDataStorageType() const
4021 return m_resumeDataStorageType
;
4024 void SessionImpl::setResumeDataStorageType(const ResumeDataStorageType type
)
4026 m_resumeDataStorageType
= type
;
4029 bool SessionImpl::isMergeTrackersEnabled() const
4031 return m_isMergeTrackersEnabled
;
4034 void SessionImpl::setMergeTrackersEnabled(const bool enabled
)
4036 m_isMergeTrackersEnabled
= enabled
;
4039 bool SessionImpl::isStartPaused() const
4041 return m_startPaused
.get(false);
4044 void SessionImpl::setStartPaused(const bool value
)
4046 m_startPaused
= value
;
4049 TorrentContentRemoveOption
SessionImpl::torrentContentRemoveOption() const
4051 return m_torrentContentRemoveOption
;
4054 void SessionImpl::setTorrentContentRemoveOption(const TorrentContentRemoveOption option
)
4056 m_torrentContentRemoveOption
= option
;
4059 QStringList
SessionImpl::bannedIPs() const
4064 bool SessionImpl::isRestored() const
4066 return m_isRestored
;
4069 bool SessionImpl::isPaused() const
4074 void SessionImpl::pause()
4081 m_nativeSession
->pause();
4083 for (TorrentImpl
*torrent
: asConst(m_torrents
))
4085 torrent
->resetTrackerEntryStatuses();
4087 const QList
<TrackerEntryStatus
> trackers
= torrent
->trackers();
4088 QHash
<QString
, TrackerEntryStatus
> updatedTrackers
;
4089 updatedTrackers
.reserve(trackers
.size());
4091 for (const TrackerEntryStatus
&status
: trackers
)
4092 updatedTrackers
.emplace(status
.url
, status
);
4093 emit
trackerEntryStatusesUpdated(torrent
, updatedTrackers
);
4101 void SessionImpl::resume()
4106 m_nativeSession
->resume();
4113 int SessionImpl::maxConnectionsPerTorrent() const
4115 return m_maxConnectionsPerTorrent
;
4118 void SessionImpl::setMaxConnectionsPerTorrent(int max
)
4120 max
= (max
> 0) ? max
: -1;
4121 if (max
!= maxConnectionsPerTorrent())
4123 m_maxConnectionsPerTorrent
= max
;
4125 for (const TorrentImpl
*torrent
: asConst(m_torrents
))
4129 torrent
->nativeHandle().set_max_connections(max
);
4131 catch (const std::exception
&) {}
4136 int SessionImpl::maxUploadsPerTorrent() const
4138 return m_maxUploadsPerTorrent
;
4141 void SessionImpl::setMaxUploadsPerTorrent(int max
)
4143 max
= (max
> 0) ? max
: -1;
4144 if (max
!= maxUploadsPerTorrent())
4146 m_maxUploadsPerTorrent
= max
;
4148 for (const TorrentImpl
*torrent
: asConst(m_torrents
))
4152 torrent
->nativeHandle().set_max_uploads(max
);
4154 catch (const std::exception
&) {}
4159 bool SessionImpl::announceToAllTrackers() const
4161 return m_announceToAllTrackers
;
4164 void SessionImpl::setAnnounceToAllTrackers(const bool val
)
4166 if (val
!= m_announceToAllTrackers
)
4168 m_announceToAllTrackers
= val
;
4169 configureDeferred();
4173 bool SessionImpl::announceToAllTiers() const
4175 return m_announceToAllTiers
;
4178 void SessionImpl::setAnnounceToAllTiers(const bool val
)
4180 if (val
!= m_announceToAllTiers
)
4182 m_announceToAllTiers
= val
;
4183 configureDeferred();
4187 int SessionImpl::peerTurnover() const
4189 return m_peerTurnover
;
4192 void SessionImpl::setPeerTurnover(const int val
)
4194 if (val
== m_peerTurnover
)
4197 m_peerTurnover
= val
;
4198 configureDeferred();
4201 int SessionImpl::peerTurnoverCutoff() const
4203 return m_peerTurnoverCutoff
;
4206 void SessionImpl::setPeerTurnoverCutoff(const int val
)
4208 if (val
== m_peerTurnoverCutoff
)
4211 m_peerTurnoverCutoff
= val
;
4212 configureDeferred();
4215 int SessionImpl::peerTurnoverInterval() const
4217 return m_peerTurnoverInterval
;
4220 void SessionImpl::setPeerTurnoverInterval(const int val
)
4222 if (val
== m_peerTurnoverInterval
)
4225 m_peerTurnoverInterval
= val
;
4226 configureDeferred();
4229 DiskIOType
SessionImpl::diskIOType() const
4231 return m_diskIOType
;
4234 void SessionImpl::setDiskIOType(const DiskIOType type
)
4236 if (type
!= m_diskIOType
)
4238 m_diskIOType
= type
;
4242 int SessionImpl::requestQueueSize() const
4244 return m_requestQueueSize
;
4247 void SessionImpl::setRequestQueueSize(const int val
)
4249 if (val
== m_requestQueueSize
)
4252 m_requestQueueSize
= val
;
4253 configureDeferred();
4256 int SessionImpl::asyncIOThreads() const
4258 return std::clamp(m_asyncIOThreads
.get(), 1, 1024);
4261 void SessionImpl::setAsyncIOThreads(const int num
)
4263 if (num
== m_asyncIOThreads
)
4266 m_asyncIOThreads
= num
;
4267 configureDeferred();
4270 int SessionImpl::hashingThreads() const
4272 return std::clamp(m_hashingThreads
.get(), 1, 1024);
4275 void SessionImpl::setHashingThreads(const int num
)
4277 if (num
== m_hashingThreads
)
4280 m_hashingThreads
= num
;
4281 configureDeferred();
4284 int SessionImpl::filePoolSize() const
4286 return m_filePoolSize
;
4289 void SessionImpl::setFilePoolSize(const int size
)
4291 if (size
== m_filePoolSize
)
4294 m_filePoolSize
= size
;
4295 configureDeferred();
4298 int SessionImpl::checkingMemUsage() const
4300 return std::max(1, m_checkingMemUsage
.get());
4303 void SessionImpl::setCheckingMemUsage(int size
)
4305 size
= std::max(size
, 1);
4307 if (size
== m_checkingMemUsage
)
4310 m_checkingMemUsage
= size
;
4311 configureDeferred();
4314 int SessionImpl::diskCacheSize() const
4316 #ifdef QBT_APP_64BIT
4317 return std::min(m_diskCacheSize
.get(), 33554431); // 32768GiB
4319 // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
4320 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4321 return std::min(m_diskCacheSize
.get(), 1536);
4325 void SessionImpl::setDiskCacheSize(int size
)
4327 #ifdef QBT_APP_64BIT
4328 size
= std::min(size
, 33554431); // 32768GiB
4330 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
4331 size
= std::min(size
, 1536);
4333 if (size
!= m_diskCacheSize
)
4335 m_diskCacheSize
= size
;
4336 configureDeferred();
4340 int SessionImpl::diskCacheTTL() const
4342 return m_diskCacheTTL
;
4345 void SessionImpl::setDiskCacheTTL(const int ttl
)
4347 if (ttl
!= m_diskCacheTTL
)
4349 m_diskCacheTTL
= ttl
;
4350 configureDeferred();
4354 qint64
SessionImpl::diskQueueSize() const
4356 return m_diskQueueSize
;
4359 void SessionImpl::setDiskQueueSize(const qint64 size
)
4361 if (size
== m_diskQueueSize
)
4364 m_diskQueueSize
= size
;
4365 configureDeferred();
4368 DiskIOReadMode
SessionImpl::diskIOReadMode() const
4370 return m_diskIOReadMode
;
4373 void SessionImpl::setDiskIOReadMode(const DiskIOReadMode mode
)
4375 if (mode
== m_diskIOReadMode
)
4378 m_diskIOReadMode
= mode
;
4379 configureDeferred();
4382 DiskIOWriteMode
SessionImpl::diskIOWriteMode() const
4384 return m_diskIOWriteMode
;
4387 void SessionImpl::setDiskIOWriteMode(const DiskIOWriteMode mode
)
4389 if (mode
== m_diskIOWriteMode
)
4392 m_diskIOWriteMode
= mode
;
4393 configureDeferred();
4396 bool SessionImpl::isCoalesceReadWriteEnabled() const
4398 return m_coalesceReadWriteEnabled
;
4401 void SessionImpl::setCoalesceReadWriteEnabled(const bool enabled
)
4403 if (enabled
== m_coalesceReadWriteEnabled
) return;
4405 m_coalesceReadWriteEnabled
= enabled
;
4406 configureDeferred();
4409 bool SessionImpl::isSuggestModeEnabled() const
4411 return m_isSuggestMode
;
4414 bool SessionImpl::usePieceExtentAffinity() const
4416 return m_usePieceExtentAffinity
;
4419 void SessionImpl::setPieceExtentAffinity(const bool enabled
)
4421 if (enabled
== m_usePieceExtentAffinity
) return;
4423 m_usePieceExtentAffinity
= enabled
;
4424 configureDeferred();
4427 void SessionImpl::setSuggestMode(const bool mode
)
4429 if (mode
== m_isSuggestMode
) return;
4431 m_isSuggestMode
= mode
;
4432 configureDeferred();
4435 int SessionImpl::sendBufferWatermark() const
4437 return m_sendBufferWatermark
;
4440 void SessionImpl::setSendBufferWatermark(const int value
)
4442 if (value
== m_sendBufferWatermark
) return;
4444 m_sendBufferWatermark
= value
;
4445 configureDeferred();
4448 int SessionImpl::sendBufferLowWatermark() const
4450 return m_sendBufferLowWatermark
;
4453 void SessionImpl::setSendBufferLowWatermark(const int value
)
4455 if (value
== m_sendBufferLowWatermark
) return;
4457 m_sendBufferLowWatermark
= value
;
4458 configureDeferred();
4461 int SessionImpl::sendBufferWatermarkFactor() const
4463 return m_sendBufferWatermarkFactor
;
4466 void SessionImpl::setSendBufferWatermarkFactor(const int value
)
4468 if (value
== m_sendBufferWatermarkFactor
) return;
4470 m_sendBufferWatermarkFactor
= value
;
4471 configureDeferred();
4474 int SessionImpl::connectionSpeed() const
4476 return m_connectionSpeed
;
4479 void SessionImpl::setConnectionSpeed(const int value
)
4481 if (value
== m_connectionSpeed
) return;
4483 m_connectionSpeed
= value
;
4484 configureDeferred();
4487 int SessionImpl::socketSendBufferSize() const
4489 return m_socketSendBufferSize
;
4492 void SessionImpl::setSocketSendBufferSize(const int value
)
4494 if (value
== m_socketSendBufferSize
)
4497 m_socketSendBufferSize
= value
;
4498 configureDeferred();
4501 int SessionImpl::socketReceiveBufferSize() const
4503 return m_socketReceiveBufferSize
;
4506 void SessionImpl::setSocketReceiveBufferSize(const int value
)
4508 if (value
== m_socketReceiveBufferSize
)
4511 m_socketReceiveBufferSize
= value
;
4512 configureDeferred();
4515 int SessionImpl::socketBacklogSize() const
4517 return m_socketBacklogSize
;
4520 void SessionImpl::setSocketBacklogSize(const int value
)
4522 if (value
== m_socketBacklogSize
) return;
4524 m_socketBacklogSize
= value
;
4525 configureDeferred();
4528 bool SessionImpl::isAnonymousModeEnabled() const
4530 return m_isAnonymousModeEnabled
;
4533 void SessionImpl::setAnonymousModeEnabled(const bool enabled
)
4535 if (enabled
!= m_isAnonymousModeEnabled
)
4537 m_isAnonymousModeEnabled
= enabled
;
4538 configureDeferred();
4539 LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
4544 bool SessionImpl::isQueueingSystemEnabled() const
4546 return m_isQueueingEnabled
;
4549 void SessionImpl::setQueueingSystemEnabled(const bool enabled
)
4551 if (enabled
!= m_isQueueingEnabled
)
4553 m_isQueueingEnabled
= enabled
;
4554 configureDeferred();
4557 m_torrentsQueueChanged
= true;
4559 removeTorrentsQueue();
4561 for (TorrentImpl
*torrent
: asConst(m_torrents
))
4562 torrent
->handleQueueingModeChanged();
4566 int SessionImpl::maxActiveDownloads() const
4568 return m_maxActiveDownloads
;
4571 void SessionImpl::setMaxActiveDownloads(int max
)
4573 max
= std::max(max
, -1);
4574 if (max
!= m_maxActiveDownloads
)
4576 m_maxActiveDownloads
= max
;
4577 configureDeferred();
4581 int SessionImpl::maxActiveUploads() const
4583 return m_maxActiveUploads
;
4586 void SessionImpl::setMaxActiveUploads(int max
)
4588 max
= std::max(max
, -1);
4589 if (max
!= m_maxActiveUploads
)
4591 m_maxActiveUploads
= max
;
4592 configureDeferred();
4596 int SessionImpl::maxActiveTorrents() const
4598 return m_maxActiveTorrents
;
4601 void SessionImpl::setMaxActiveTorrents(int max
)
4603 max
= std::max(max
, -1);
4604 if (max
!= m_maxActiveTorrents
)
4606 m_maxActiveTorrents
= max
;
4607 configureDeferred();
4611 bool SessionImpl::ignoreSlowTorrentsForQueueing() const
4613 return m_ignoreSlowTorrentsForQueueing
;
4616 void SessionImpl::setIgnoreSlowTorrentsForQueueing(const bool ignore
)
4618 if (ignore
!= m_ignoreSlowTorrentsForQueueing
)
4620 m_ignoreSlowTorrentsForQueueing
= ignore
;
4621 configureDeferred();
4625 int SessionImpl::downloadRateForSlowTorrents() const
4627 return m_downloadRateForSlowTorrents
;
4630 void SessionImpl::setDownloadRateForSlowTorrents(const int rateInKibiBytes
)
4632 if (rateInKibiBytes
== m_downloadRateForSlowTorrents
)
4635 m_downloadRateForSlowTorrents
= rateInKibiBytes
;
4636 configureDeferred();
4639 int SessionImpl::uploadRateForSlowTorrents() const
4641 return m_uploadRateForSlowTorrents
;
4644 void SessionImpl::setUploadRateForSlowTorrents(const int rateInKibiBytes
)
4646 if (rateInKibiBytes
== m_uploadRateForSlowTorrents
)
4649 m_uploadRateForSlowTorrents
= rateInKibiBytes
;
4650 configureDeferred();
4653 int SessionImpl::slowTorrentsInactivityTimer() const
4655 return m_slowTorrentsInactivityTimer
;
4658 void SessionImpl::setSlowTorrentsInactivityTimer(const int timeInSeconds
)
4660 if (timeInSeconds
== m_slowTorrentsInactivityTimer
)
4663 m_slowTorrentsInactivityTimer
= timeInSeconds
;
4664 configureDeferred();
4667 int SessionImpl::outgoingPortsMin() const
4669 return m_outgoingPortsMin
;
4672 void SessionImpl::setOutgoingPortsMin(const int min
)
4674 if (min
!= m_outgoingPortsMin
)
4676 m_outgoingPortsMin
= min
;
4677 configureDeferred();
4681 int SessionImpl::outgoingPortsMax() const
4683 return m_outgoingPortsMax
;
4686 void SessionImpl::setOutgoingPortsMax(const int max
)
4688 if (max
!= m_outgoingPortsMax
)
4690 m_outgoingPortsMax
= max
;
4691 configureDeferred();
4695 int SessionImpl::UPnPLeaseDuration() const
4697 return m_UPnPLeaseDuration
;
4700 void SessionImpl::setUPnPLeaseDuration(const int duration
)
4702 if (duration
!= m_UPnPLeaseDuration
)
4704 m_UPnPLeaseDuration
= duration
;
4705 configureDeferred();
4709 int SessionImpl::peerToS() const
4714 void SessionImpl::setPeerToS(const int value
)
4716 if (value
== m_peerToS
)
4720 configureDeferred();
4723 bool SessionImpl::ignoreLimitsOnLAN() const
4725 return m_ignoreLimitsOnLAN
;
4728 void SessionImpl::setIgnoreLimitsOnLAN(const bool ignore
)
4730 if (ignore
!= m_ignoreLimitsOnLAN
)
4732 m_ignoreLimitsOnLAN
= ignore
;
4733 configureDeferred();
4737 bool SessionImpl::includeOverheadInLimits() const
4739 return m_includeOverheadInLimits
;
4742 void SessionImpl::setIncludeOverheadInLimits(const bool include
)
4744 if (include
!= m_includeOverheadInLimits
)
4746 m_includeOverheadInLimits
= include
;
4747 configureDeferred();
4751 QString
SessionImpl::announceIP() const
4753 return m_announceIP
;
4756 void SessionImpl::setAnnounceIP(const QString
&ip
)
4758 if (ip
!= m_announceIP
)
4761 configureDeferred();
4765 int SessionImpl::maxConcurrentHTTPAnnounces() const
4767 return m_maxConcurrentHTTPAnnounces
;
4770 void SessionImpl::setMaxConcurrentHTTPAnnounces(const int value
)
4772 if (value
== m_maxConcurrentHTTPAnnounces
)
4775 m_maxConcurrentHTTPAnnounces
= value
;
4776 configureDeferred();
4779 bool SessionImpl::isReannounceWhenAddressChangedEnabled() const
4781 return m_isReannounceWhenAddressChangedEnabled
;
4784 void SessionImpl::setReannounceWhenAddressChangedEnabled(const bool enabled
)
4786 if (enabled
== m_isReannounceWhenAddressChangedEnabled
)
4789 m_isReannounceWhenAddressChangedEnabled
= enabled
;
4792 void SessionImpl::reannounceToAllTrackers() const
4794 for (const TorrentImpl
*torrent
: asConst(m_torrents
))
4798 torrent
->nativeHandle().force_reannounce(0, -1, lt::torrent_handle::ignore_min_interval
);
4800 catch (const std::exception
&) {}
4804 int SessionImpl::stopTrackerTimeout() const
4806 return m_stopTrackerTimeout
;
4809 void SessionImpl::setStopTrackerTimeout(const int value
)
4811 if (value
== m_stopTrackerTimeout
)
4814 m_stopTrackerTimeout
= value
;
4815 configureDeferred();
4818 int SessionImpl::maxConnections() const
4820 return m_maxConnections
;
4823 void SessionImpl::setMaxConnections(int max
)
4825 max
= (max
> 0) ? max
: -1;
4826 if (max
!= m_maxConnections
)
4828 m_maxConnections
= max
;
4829 configureDeferred();
4833 int SessionImpl::maxUploads() const
4835 return m_maxUploads
;
4838 void SessionImpl::setMaxUploads(int max
)
4840 max
= (max
> 0) ? max
: -1;
4841 if (max
!= m_maxUploads
)
4844 configureDeferred();
4848 BTProtocol
SessionImpl::btProtocol() const
4850 return m_btProtocol
;
4853 void SessionImpl::setBTProtocol(const BTProtocol protocol
)
4855 if ((protocol
< BTProtocol::Both
) || (BTProtocol::UTP
< protocol
))
4858 if (protocol
== m_btProtocol
) return;
4860 m_btProtocol
= protocol
;
4861 configureDeferred();
4864 bool SessionImpl::isUTPRateLimited() const
4866 return m_isUTPRateLimited
;
4869 void SessionImpl::setUTPRateLimited(const bool limited
)
4871 if (limited
!= m_isUTPRateLimited
)
4873 m_isUTPRateLimited
= limited
;
4874 configureDeferred();
4878 MixedModeAlgorithm
SessionImpl::utpMixedMode() const
4880 return m_utpMixedMode
;
4883 void SessionImpl::setUtpMixedMode(const MixedModeAlgorithm mode
)
4885 if (mode
== m_utpMixedMode
) return;
4887 m_utpMixedMode
= mode
;
4888 configureDeferred();
4891 bool SessionImpl::isIDNSupportEnabled() const
4893 return m_IDNSupportEnabled
;
4896 void SessionImpl::setIDNSupportEnabled(const bool enabled
)
4898 if (enabled
== m_IDNSupportEnabled
) return;
4900 m_IDNSupportEnabled
= enabled
;
4901 configureDeferred();
4904 bool SessionImpl::multiConnectionsPerIpEnabled() const
4906 return m_multiConnectionsPerIpEnabled
;
4909 void SessionImpl::setMultiConnectionsPerIpEnabled(const bool enabled
)
4911 if (enabled
== m_multiConnectionsPerIpEnabled
) return;
4913 m_multiConnectionsPerIpEnabled
= enabled
;
4914 configureDeferred();
4917 bool SessionImpl::validateHTTPSTrackerCertificate() const
4919 return m_validateHTTPSTrackerCertificate
;
4922 void SessionImpl::setValidateHTTPSTrackerCertificate(const bool enabled
)
4924 if (enabled
== m_validateHTTPSTrackerCertificate
) return;
4926 m_validateHTTPSTrackerCertificate
= enabled
;
4927 configureDeferred();
4930 bool SessionImpl::isSSRFMitigationEnabled() const
4932 return m_SSRFMitigationEnabled
;
4935 void SessionImpl::setSSRFMitigationEnabled(const bool enabled
)
4937 if (enabled
== m_SSRFMitigationEnabled
) return;
4939 m_SSRFMitigationEnabled
= enabled
;
4940 configureDeferred();
4943 bool SessionImpl::blockPeersOnPrivilegedPorts() const
4945 return m_blockPeersOnPrivilegedPorts
;
4948 void SessionImpl::setBlockPeersOnPrivilegedPorts(const bool enabled
)
4950 if (enabled
== m_blockPeersOnPrivilegedPorts
) return;
4952 m_blockPeersOnPrivilegedPorts
= enabled
;
4953 configureDeferred();
4956 bool SessionImpl::isTrackerFilteringEnabled() const
4958 return m_isTrackerFilteringEnabled
;
4961 void SessionImpl::setTrackerFilteringEnabled(const bool enabled
)
4963 if (enabled
!= m_isTrackerFilteringEnabled
)
4965 m_isTrackerFilteringEnabled
= enabled
;
4966 configureDeferred();
4970 bool SessionImpl::isListening() const
4972 return m_nativeSessionExtension
->isSessionListening();
4975 ShareLimitAction
SessionImpl::shareLimitAction() const
4977 return m_shareLimitAction
;
4980 void SessionImpl::setShareLimitAction(const ShareLimitAction act
)
4982 Q_ASSERT(act
!= ShareLimitAction::Default
);
4984 m_shareLimitAction
= act
;
4987 bool SessionImpl::isKnownTorrent(const InfoHash
&infoHash
) const
4989 const bool isHybrid
= infoHash
.isHybrid();
4990 const auto id
= TorrentID::fromInfoHash(infoHash
);
4991 // alternative ID can be useful to find existing torrent
4992 // in case if hybrid torrent was added by v1 info hash
4993 const auto altID
= (isHybrid
? TorrentID::fromSHA1Hash(infoHash
.v1()) : TorrentID());
4995 if (m_loadingTorrents
.contains(id
) || (isHybrid
&& m_loadingTorrents
.contains(altID
)))
4997 if (m_downloadedMetadata
.contains(id
) || (isHybrid
&& m_downloadedMetadata
.contains(altID
)))
4999 return findTorrent(infoHash
);
5002 void SessionImpl::updateSeedingLimitTimer()
5004 if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT
) && !hasPerTorrentRatioLimit()
5005 && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT
) && !hasPerTorrentSeedingTimeLimit()
5006 && (globalMaxInactiveSeedingMinutes() == Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT
) && !hasPerTorrentInactiveSeedingTimeLimit())
5008 if (m_seedingLimitTimer
->isActive())
5009 m_seedingLimitTimer
->stop();
5011 else if (!m_seedingLimitTimer
->isActive())
5013 m_seedingLimitTimer
->start();
5017 void SessionImpl::handleTorrentShareLimitChanged(TorrentImpl
*const)
5019 updateSeedingLimitTimer();
5022 void SessionImpl::handleTorrentNameChanged(TorrentImpl
*const)
5026 void SessionImpl::handleTorrentSavePathChanged(TorrentImpl
*const torrent
)
5028 emit
torrentSavePathChanged(torrent
);
5031 void SessionImpl::handleTorrentCategoryChanged(TorrentImpl
*const torrent
, const QString
&oldCategory
)
5033 emit
torrentCategoryChanged(torrent
, oldCategory
);
5036 void SessionImpl::handleTorrentTagAdded(TorrentImpl
*const torrent
, const Tag
&tag
)
5038 emit
torrentTagAdded(torrent
, tag
);
5041 void SessionImpl::handleTorrentTagRemoved(TorrentImpl
*const torrent
, const Tag
&tag
)
5043 emit
torrentTagRemoved(torrent
, tag
);
5046 void SessionImpl::handleTorrentSavingModeChanged(TorrentImpl
*const torrent
)
5048 emit
torrentSavingModeChanged(torrent
);
5051 void SessionImpl::handleTorrentTrackersAdded(TorrentImpl
*const torrent
, const QList
<TrackerEntry
> &newTrackers
)
5053 for (const TrackerEntry
&newTracker
: newTrackers
)
5054 LogMsg(tr("Added tracker to torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent
->name(), newTracker
.url
));
5055 emit
trackersAdded(torrent
, newTrackers
);
5058 void SessionImpl::handleTorrentTrackersRemoved(TorrentImpl
*const torrent
, const QStringList
&deletedTrackers
)
5060 for (const QString
&deletedTracker
: deletedTrackers
)
5061 LogMsg(tr("Removed tracker from torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent
->name(), deletedTracker
));
5062 emit
trackersRemoved(torrent
, deletedTrackers
);
5065 void SessionImpl::handleTorrentTrackersChanged(TorrentImpl
*const torrent
)
5067 emit
trackersChanged(torrent
);
5070 void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl
*const torrent
, const QList
<QUrl
> &newUrlSeeds
)
5072 for (const QUrl
&newUrlSeed
: newUrlSeeds
)
5073 LogMsg(tr("Added URL seed to torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent
->name(), newUrlSeed
.toString()));
5076 void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl
*const torrent
, const QList
<QUrl
> &urlSeeds
)
5078 for (const QUrl
&urlSeed
: urlSeeds
)
5079 LogMsg(tr("Removed URL seed from torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent
->name(), urlSeed
.toString()));
5082 void SessionImpl::handleTorrentMetadataReceived(TorrentImpl
*const torrent
)
5084 if (!torrentExportDirectory().isEmpty())
5085 exportTorrentFile(torrent
, torrentExportDirectory());
5087 emit
torrentMetadataReceived(torrent
);
5090 void SessionImpl::handleTorrentStopped(TorrentImpl
*const torrent
)
5092 torrent
->resetTrackerEntryStatuses();
5094 const QList
<TrackerEntryStatus
> trackers
= torrent
->trackers();
5095 QHash
<QString
, TrackerEntryStatus
> updatedTrackers
;
5096 updatedTrackers
.reserve(trackers
.size());
5098 for (const TrackerEntryStatus
&status
: trackers
)
5099 updatedTrackers
.emplace(status
.url
, status
);
5100 emit
trackerEntryStatusesUpdated(torrent
, updatedTrackers
);
5102 LogMsg(tr("Torrent stopped. Torrent: \"%1\"").arg(torrent
->name()));
5103 emit
torrentStopped(torrent
);
5106 void SessionImpl::handleTorrentStarted(TorrentImpl
*const torrent
)
5108 LogMsg(tr("Torrent resumed. Torrent: \"%1\"").arg(torrent
->name()));
5109 emit
torrentStarted(torrent
);
5112 void SessionImpl::handleTorrentChecked(TorrentImpl
*const torrent
)
5114 emit
torrentFinishedChecking(torrent
);
5117 void SessionImpl::handleTorrentFinished(TorrentImpl
*const torrent
)
5119 m_pendingFinishedTorrents
.append(torrent
);
5122 void SessionImpl::handleTorrentResumeDataReady(TorrentImpl
*const torrent
, const LoadTorrentParams
&data
)
5124 m_resumeDataStorage
->store(torrent
->id(), data
);
5125 const auto iter
= m_changedTorrentIDs
.find(torrent
->id());
5126 if (iter
!= m_changedTorrentIDs
.end())
5128 m_resumeDataStorage
->remove(iter
.value());
5129 m_changedTorrentIDs
.erase(iter
);
5133 void SessionImpl::handleTorrentInfoHashChanged(TorrentImpl
*torrent
, const InfoHash
&prevInfoHash
)
5135 Q_ASSERT(torrent
->infoHash().isHybrid());
5137 m_hybridTorrentsByAltID
.insert(TorrentID::fromSHA1Hash(torrent
->infoHash().v1()), torrent
);
5139 const auto prevID
= TorrentID::fromInfoHash(prevInfoHash
);
5140 const TorrentID currentID
= torrent
->id();
5141 if (currentID
!= prevID
)
5143 m_torrents
[torrent
->id()] = m_torrents
.take(prevID
);
5144 m_changedTorrentIDs
[torrent
->id()] = prevID
;
5148 void SessionImpl::handleTorrentStorageMovingStateChanged(TorrentImpl
*torrent
)
5150 emit
torrentsUpdated({torrent
});
5153 bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl
*torrent
, const Path
&newPath
, const MoveStorageMode mode
, const MoveStorageContext context
)
5157 const lt::torrent_handle torrentHandle
= torrent
->nativeHandle();
5158 const Path currentLocation
= torrent
->actualStorageLocation();
5159 const bool torrentHasActiveJob
= !m_moveStorageQueue
.isEmpty() && (m_moveStorageQueue
.first().torrentHandle
== torrentHandle
);
5161 if (m_moveStorageQueue
.size() > 1)
5163 auto iter
= std::find_if((m_moveStorageQueue
.begin() + 1), m_moveStorageQueue
.end()
5164 , [&torrentHandle
](const MoveStorageJob
&job
)
5166 return job
.torrentHandle
== torrentHandle
;
5169 if (iter
!= m_moveStorageQueue
.end())
5171 // remove existing inactive job
5172 torrent
->handleMoveStorageJobFinished(currentLocation
, iter
->context
, torrentHasActiveJob
);
5173 LogMsg(tr("Torrent move canceled. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent
->name(), currentLocation
.toString(), iter
->path
.toString()));
5174 m_moveStorageQueue
.erase(iter
);
5178 if (torrentHasActiveJob
)
5180 // if there is active job for this torrent prevent creating meaningless
5181 // job that will move torrent to the same location as current one
5182 if (m_moveStorageQueue
.first().path
== newPath
)
5184 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: torrent is currently moving to the destination")
5185 .arg(torrent
->name(), currentLocation
.toString(), newPath
.toString()));
5191 if (currentLocation
== newPath
)
5193 LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\" Destination: \"%3\". Reason: both paths point to the same location")
5194 .arg(torrent
->name(), currentLocation
.toString(), newPath
.toString()));
5199 const MoveStorageJob moveStorageJob
{torrentHandle
, newPath
, mode
, context
};
5200 m_moveStorageQueue
<< moveStorageJob
;
5201 LogMsg(tr("Enqueued torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent
->name(), currentLocation
.toString(), newPath
.toString()));
5203 if (m_moveStorageQueue
.size() == 1)
5204 moveTorrentStorage(moveStorageJob
);
5209 void SessionImpl::moveTorrentStorage(const MoveStorageJob
&job
) const
5211 #ifdef QBT_USES_LIBTORRENT2
5212 const auto id
= TorrentID::fromInfoHash(job
.torrentHandle
.info_hashes());
5214 const auto id
= TorrentID::fromInfoHash(job
.torrentHandle
.info_hash());
5216 const TorrentImpl
*torrent
= m_torrents
.value(id
);
5217 const QString torrentName
= (torrent
? torrent
->name() : id
.toString());
5218 LogMsg(tr("Start moving torrent. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName
, job
.path
.toString()));
5220 job
.torrentHandle
.move_storage(job
.path
.toString().toStdString(), toNative(job
.mode
));
5223 void SessionImpl::handleMoveTorrentStorageJobFinished(const Path
&newPath
)
5225 const MoveStorageJob finishedJob
= m_moveStorageQueue
.takeFirst();
5226 if (!m_moveStorageQueue
.isEmpty())
5227 moveTorrentStorage(m_moveStorageQueue
.first());
5229 const auto iter
= std::find_if(m_moveStorageQueue
.cbegin(), m_moveStorageQueue
.cend()
5230 , [&finishedJob
](const MoveStorageJob
&job
)
5232 return job
.torrentHandle
== finishedJob
.torrentHandle
;
5235 const bool torrentHasOutstandingJob
= (iter
!= m_moveStorageQueue
.cend());
5237 TorrentImpl
*torrent
= m_torrents
.value(finishedJob
.torrentHandle
.info_hash());
5240 torrent
->handleMoveStorageJobFinished(newPath
, finishedJob
.context
, torrentHasOutstandingJob
);
5242 else if (!torrentHasOutstandingJob
)
5244 // Last job is completed for torrent that being removing, so actually remove it
5245 const lt::torrent_handle nativeHandle
{finishedJob
.torrentHandle
};
5246 const RemovingTorrentData
&removingTorrentData
= m_removingTorrents
[nativeHandle
.info_hash()];
5247 if (removingTorrentData
.removeOption
== TorrentRemoveOption::KeepContent
)
5248 m_nativeSession
->remove_torrent(nativeHandle
, lt::session::delete_partfile
);
5252 void SessionImpl::processPendingFinishedTorrents()
5254 if (m_pendingFinishedTorrents
.isEmpty())
5257 for (TorrentImpl
*torrent
: asConst(m_pendingFinishedTorrents
))
5259 LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent
->name()));
5260 emit
torrentFinished(torrent
);
5262 if (const Path exportPath
= finishedTorrentExportDirectory(); !exportPath
.isEmpty())
5263 exportTorrentFile(torrent
, exportPath
);
5265 processTorrentShareLimits(torrent
);
5268 m_pendingFinishedTorrents
.clear();
5270 const bool hasUnfinishedTorrents
= std::any_of(m_torrents
.cbegin(), m_torrents
.cend(), [](const TorrentImpl
*torrent
)
5272 return !(torrent
->isFinished() || torrent
->isStopped() || torrent
->isErrored());
5274 if (!hasUnfinishedTorrents
)
5275 emit
allTorrentsFinished();
5278 void SessionImpl::storeCategories() const
5280 QJsonObject jsonObj
;
5281 for (auto it
= m_categories
.cbegin(); it
!= m_categories
.cend(); ++it
)
5283 const QString
&categoryName
= it
.key();
5284 const CategoryOptions
&categoryOptions
= it
.value();
5285 jsonObj
[categoryName
] = categoryOptions
.toJSON();
5288 const Path path
= specialFolderLocation(SpecialFolder::Config
) / CATEGORIES_FILE_NAME
;
5289 const QByteArray data
= QJsonDocument(jsonObj
).toJson();
5290 const nonstd::expected
<void, QString
> result
= Utils::IO::saveToFile(path
, data
);
5293 LogMsg(tr("Failed to save Categories configuration. File: \"%1\". Error: \"%2\"")
5294 .arg(path
.toString(), result
.error()), Log::WARNING
);
5298 void SessionImpl::upgradeCategories()
5300 const auto legacyCategories
= SettingValue
<QVariantMap
>(u
"BitTorrent/Session/Categories"_s
).get();
5301 for (auto it
= legacyCategories
.cbegin(); it
!= legacyCategories
.cend(); ++it
)
5303 const QString
&categoryName
= it
.key();
5304 CategoryOptions categoryOptions
;
5305 categoryOptions
.savePath
= Path(it
.value().toString());
5306 m_categories
[categoryName
] = categoryOptions
;
5312 void SessionImpl::loadCategories()
5314 m_categories
.clear();
5316 const Path path
= specialFolderLocation(SpecialFolder::Config
) / CATEGORIES_FILE_NAME
;
5319 // TODO: Remove the following upgrade code in v4.5
5320 // == BEGIN UPGRADE CODE ==
5321 upgradeCategories();
5322 m_needUpgradeDownloadPath
= true;
5323 // == END UPGRADE CODE ==
5328 const int fileMaxSize
= 1024 * 1024;
5329 const auto readResult
= Utils::IO::readFile(path
, fileMaxSize
);
5332 LogMsg(tr("Failed to load Categories. %1").arg(readResult
.error().message
), Log::WARNING
);
5336 QJsonParseError jsonError
;
5337 const QJsonDocument jsonDoc
= QJsonDocument::fromJson(readResult
.value(), &jsonError
);
5338 if (jsonError
.error
!= QJsonParseError::NoError
)
5340 LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
5341 .arg(path
.toString(), jsonError
.errorString()), Log::WARNING
);
5345 if (!jsonDoc
.isObject())
5347 LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Error: \"Invalid data format\"")
5348 .arg(path
.toString()), Log::WARNING
);
5352 const QJsonObject jsonObj
= jsonDoc
.object();
5353 for (auto it
= jsonObj
.constBegin(); it
!= jsonObj
.constEnd(); ++it
)
5355 const QString
&categoryName
= it
.key();
5356 const auto categoryOptions
= CategoryOptions::fromJSON(it
.value().toObject());
5357 m_categories
[categoryName
] = categoryOptions
;
5361 bool SessionImpl::hasPerTorrentRatioLimit() const
5363 return std::any_of(m_torrents
.cbegin(), m_torrents
.cend(), [](const TorrentImpl
*torrent
)
5365 return (torrent
->ratioLimit() >= 0);
5369 bool SessionImpl::hasPerTorrentSeedingTimeLimit() const
5371 return std::any_of(m_torrents
.cbegin(), m_torrents
.cend(), [](const TorrentImpl
*torrent
)
5373 return (torrent
->seedingTimeLimit() >= 0);
5377 bool SessionImpl::hasPerTorrentInactiveSeedingTimeLimit() const
5379 return std::any_of(m_torrents
.cbegin(), m_torrents
.cend(), [](const TorrentImpl
*torrent
)
5381 return (torrent
->inactiveSeedingTimeLimit() >= 0);
5385 void SessionImpl::configureDeferred()
5387 if (m_deferredConfigureScheduled
)
5390 m_deferredConfigureScheduled
= true;
5391 QMetaObject::invokeMethod(this, qOverload
<>(&SessionImpl::configure
), Qt::QueuedConnection
);
5394 // Enable IP Filtering
5395 // this method creates ban list from scratch combining user ban list and 3rd party ban list file
5396 void SessionImpl::enableIPFilter()
5398 qDebug("Enabling IPFilter");
5399 // 1. Parse the IP filter
5400 // 2. In the slot add the manually banned IPs to the provided lt::ip_filter
5401 // 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
5402 // set between clearing the old one and setting the new one.
5403 if (!m_filterParser
)
5405 m_filterParser
= new FilterParserThread(this);
5406 connect(m_filterParser
.data(), &FilterParserThread::IPFilterParsed
, this, &SessionImpl::handleIPFilterParsed
);
5407 connect(m_filterParser
.data(), &FilterParserThread::IPFilterError
, this, &SessionImpl::handleIPFilterError
);
5409 m_filterParser
->processFilterFile(IPFilterFile());
5412 // Disable IP Filtering
5413 void SessionImpl::disableIPFilter()
5415 qDebug("Disabling IPFilter");
5418 disconnect(m_filterParser
.data(), nullptr, this, nullptr);
5419 delete m_filterParser
;
5422 // Add the banned IPs after the IPFilter disabling
5423 // which creates an empty filter and overrides all previously
5425 lt::ip_filter filter
;
5426 processBannedIPs(filter
);
5427 m_nativeSession
->set_ip_filter(filter
);
5430 const SessionStatus
&SessionImpl::status() const
5435 const CacheStatus
&SessionImpl::cacheStatus() const
5437 return m_cacheStatus
;
5440 void SessionImpl::enqueueRefresh()
5442 Q_ASSERT(!m_refreshEnqueued
);
5444 QTimer::singleShot(refreshInterval(), Qt::CoarseTimer
, this, [this]
5446 m_nativeSession
->post_torrent_updates();
5447 m_nativeSession
->post_session_stats();
5449 if (m_torrentsQueueChanged
)
5451 m_torrentsQueueChanged
= false;
5452 m_needSaveTorrentsQueue
= true;
5456 m_refreshEnqueued
= true;
5459 void SessionImpl::handleIPFilterParsed(const int ruleCount
)
5463 lt::ip_filter filter
= m_filterParser
->IPfilter();
5464 processBannedIPs(filter
);
5465 m_nativeSession
->set_ip_filter(filter
);
5467 LogMsg(tr("Successfully parsed the IP filter file. Number of rules applied: %1").arg(ruleCount
));
5468 emit
IPFilterParsed(false, ruleCount
);
5471 void SessionImpl::handleIPFilterError()
5473 lt::ip_filter filter
;
5474 processBannedIPs(filter
);
5475 m_nativeSession
->set_ip_filter(filter
);
5477 LogMsg(tr("Failed to parse the IP filter file"), Log::WARNING
);
5478 emit
IPFilterParsed(true, 0);
5481 std::vector
<lt::alert
*> SessionImpl::getPendingAlerts(const lt::time_duration time
) const
5483 if (time
> lt::time_duration::zero())
5484 m_nativeSession
->wait_for_alert(time
);
5486 std::vector
<lt::alert
*> alerts
;
5487 m_nativeSession
->pop_alerts(&alerts
);
5491 TorrentContentLayout
SessionImpl::torrentContentLayout() const
5493 return m_torrentContentLayout
;
5496 void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value
)
5498 m_torrentContentLayout
= value
;
5501 // Read alerts sent by libtorrent session
5502 void SessionImpl::readAlerts()
5504 // cache current datetime of Qt and libtorrent clocks in order
5505 // to optimize conversion of time points from lt to Qt clocks
5506 m_ltNow
= lt::clock_type::now();
5507 m_qNow
= QDateTime::currentDateTime();
5509 const std::vector
<lt::alert
*> alerts
= getPendingAlerts();
5511 Q_ASSERT(m_loadedTorrents
.isEmpty());
5512 Q_ASSERT(m_receivedAddTorrentAlertsCount
== 0);
5515 m_loadedTorrents
.reserve(MAX_PROCESSING_RESUMEDATA_COUNT
);
5517 for (const lt::alert
*a
: alerts
)
5520 if (m_receivedAddTorrentAlertsCount
> 0)
5522 emit
addTorrentAlertsReceived(m_receivedAddTorrentAlertsCount
);
5523 m_receivedAddTorrentAlertsCount
= 0;
5525 if (!m_loadedTorrents
.isEmpty())
5528 m_torrentsQueueChanged
= true;
5530 emit
torrentsLoaded(m_loadedTorrents
);
5531 m_loadedTorrents
.clear();
5535 // Some torrents may become "finished" after different alerts handling.
5536 processPendingFinishedTorrents();
5538 processTrackerStatuses();
5541 void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert
*alert
)
5543 ++m_receivedAddTorrentAlertsCount
;
5547 const QString msg
= QString::fromStdString(alert
->message());
5548 LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(msg
), Log::WARNING
);
5549 emit
loadTorrentFailed(msg
);
5551 const lt::add_torrent_params
¶ms
= alert
->params
;
5552 const bool hasMetadata
= (params
.ti
&& params
.ti
->is_valid());
5554 #ifdef QBT_USES_LIBTORRENT2
5555 const InfoHash infoHash
{(hasMetadata
? params
.ti
->info_hashes() : params
.info_hashes
)};
5556 if (infoHash
.isHybrid())
5557 m_hybridTorrentsByAltID
.remove(TorrentID::fromSHA1Hash(infoHash
.v1()));
5559 const InfoHash infoHash
{(hasMetadata
? params
.ti
->info_hash() : params
.info_hash
)};
5561 if (const auto loadingTorrentsIter
= m_loadingTorrents
.find(TorrentID::fromInfoHash(infoHash
))
5562 ; loadingTorrentsIter
!= m_loadingTorrents
.end())
5564 emit
addTorrentFailed(infoHash
, msg
);
5565 m_loadingTorrents
.erase(loadingTorrentsIter
);
5567 else if (const auto downloadedMetadataIter
= m_downloadedMetadata
.find(TorrentID::fromInfoHash(infoHash
))
5568 ; downloadedMetadataIter
!= m_downloadedMetadata
.end())
5570 m_downloadedMetadata
.erase(downloadedMetadataIter
);
5571 if (infoHash
.isHybrid())
5573 // index hybrid magnet links by both v1 and v2 info hashes
5574 const auto altID
= TorrentID::fromSHA1Hash(infoHash
.v1());
5575 m_downloadedMetadata
.remove(altID
);
5582 #ifdef QBT_USES_LIBTORRENT2
5583 const InfoHash infoHash
{alert
->handle
.info_hashes()};
5585 const InfoHash infoHash
{alert
->handle
.info_hash()};
5587 const auto torrentID
= TorrentID::fromInfoHash(infoHash
);
5589 if (const auto loadingTorrentsIter
= m_loadingTorrents
.find(torrentID
)
5590 ; loadingTorrentsIter
!= m_loadingTorrents
.end())
5592 const LoadTorrentParams params
= loadingTorrentsIter
.value();
5593 m_loadingTorrents
.erase(loadingTorrentsIter
);
5595 Torrent
*torrent
= createTorrent(alert
->handle
, params
);
5596 m_loadedTorrents
.append(torrent
);
5598 else if (const auto downloadedMetadataIter
= m_downloadedMetadata
.find(torrentID
)
5599 ; downloadedMetadataIter
!= m_downloadedMetadata
.end())
5601 downloadedMetadataIter
.value() = alert
->handle
;
5602 if (infoHash
.isHybrid())
5604 // index hybrid magnet links by both v1 and v2 info hashes
5605 const auto altID
= TorrentID::fromSHA1Hash(infoHash
.v1());
5606 m_downloadedMetadata
[altID
] = alert
->handle
;
5611 void SessionImpl::handleAlert(const lt::alert
*alert
)
5615 switch (alert
->type())
5617 #ifdef QBT_USES_LIBTORRENT2
5618 case lt::file_prio_alert::alert_type
:
5620 case lt::file_renamed_alert::alert_type
:
5621 case lt::file_rename_failed_alert::alert_type
:
5622 case lt::file_completed_alert::alert_type
:
5623 case lt::torrent_finished_alert::alert_type
:
5624 case lt::save_resume_data_alert::alert_type
:
5625 case lt::save_resume_data_failed_alert::alert_type
:
5626 case lt::torrent_paused_alert::alert_type
:
5627 case lt::torrent_resumed_alert::alert_type
:
5628 case lt::fastresume_rejected_alert::alert_type
:
5629 case lt::torrent_checked_alert::alert_type
:
5630 case lt::metadata_received_alert::alert_type
:
5631 case lt::performance_alert::alert_type
:
5632 dispatchTorrentAlert(static_cast<const lt::torrent_alert
*>(alert
));
5634 case lt::state_update_alert::alert_type
:
5635 handleStateUpdateAlert(static_cast<const lt::state_update_alert
*>(alert
));
5637 case lt::session_error_alert::alert_type
:
5638 handleSessionErrorAlert(static_cast<const lt::session_error_alert
*>(alert
));
5640 case lt::session_stats_alert::alert_type
:
5641 handleSessionStatsAlert(static_cast<const lt::session_stats_alert
*>(alert
));
5643 case lt::tracker_announce_alert::alert_type
:
5644 case lt::tracker_error_alert::alert_type
:
5645 case lt::tracker_reply_alert::alert_type
:
5646 case lt::tracker_warning_alert::alert_type
:
5647 handleTrackerAlert(static_cast<const lt::tracker_alert
*>(alert
));
5649 case lt::file_error_alert::alert_type
:
5650 handleFileErrorAlert(static_cast<const lt::file_error_alert
*>(alert
));
5652 case lt::add_torrent_alert::alert_type
:
5653 handleAddTorrentAlert(static_cast<const lt::add_torrent_alert
*>(alert
));
5655 case lt::torrent_removed_alert::alert_type
:
5656 handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert
*>(alert
));
5658 case lt::torrent_deleted_alert::alert_type
:
5659 handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert
*>(alert
));
5661 case lt::torrent_delete_failed_alert::alert_type
:
5662 handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert
*>(alert
));
5664 case lt::torrent_need_cert_alert::alert_type
:
5665 handleTorrentNeedCertAlert(static_cast<const lt::torrent_need_cert_alert
*>(alert
));
5667 case lt::portmap_error_alert::alert_type
:
5668 handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert
*>(alert
));
5670 case lt::portmap_alert::alert_type
:
5671 handlePortmapAlert(static_cast<const lt::portmap_alert
*>(alert
));
5673 case lt::peer_blocked_alert::alert_type
:
5674 handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert
*>(alert
));
5676 case lt::peer_ban_alert::alert_type
:
5677 handlePeerBanAlert(static_cast<const lt::peer_ban_alert
*>(alert
));
5679 case lt::url_seed_alert::alert_type
:
5680 handleUrlSeedAlert(static_cast<const lt::url_seed_alert
*>(alert
));
5682 case lt::listen_succeeded_alert::alert_type
:
5683 handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert
*>(alert
));
5685 case lt::listen_failed_alert::alert_type
:
5686 handleListenFailedAlert(static_cast<const lt::listen_failed_alert
*>(alert
));
5688 case lt::external_ip_alert::alert_type
:
5689 handleExternalIPAlert(static_cast<const lt::external_ip_alert
*>(alert
));
5691 case lt::alerts_dropped_alert::alert_type
:
5692 handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert
*>(alert
));
5694 case lt::storage_moved_alert::alert_type
:
5695 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert
*>(alert
));
5697 case lt::storage_moved_failed_alert::alert_type
:
5698 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert
*>(alert
));
5700 case lt::socks5_alert::alert_type
:
5701 handleSocks5Alert(static_cast<const lt::socks5_alert
*>(alert
));
5703 case lt::i2p_alert::alert_type
:
5704 handleI2PAlert(static_cast<const lt::i2p_alert
*>(alert
));
5706 #ifdef QBT_USES_LIBTORRENT2
5707 case lt::torrent_conflict_alert::alert_type
:
5708 handleTorrentConflictAlert(static_cast<const lt::torrent_conflict_alert
*>(alert
));
5713 catch (const std::exception
&exc
)
5715 qWarning() << "Caught exception in " << Q_FUNC_INFO
<< ": " << QString::fromStdString(exc
.what());
5719 void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert
*alert
)
5721 // The torrent can be deleted between the time the resume data was requested and
5722 // the time we received the appropriate alert. We have to decrease `m_numResumeData` anyway,
5723 // so we do this before checking for an existing torrent.
5724 if ((alert
->type() == lt::save_resume_data_alert::alert_type
)
5725 || (alert
->type() == lt::save_resume_data_failed_alert::alert_type
))
5730 const TorrentID torrentID
{alert
->handle
.info_hash()};
5731 TorrentImpl
*torrent
= m_torrents
.value(torrentID
);
5732 #ifdef QBT_USES_LIBTORRENT2
5733 if (!torrent
&& (alert
->type() == lt::metadata_received_alert::alert_type
))
5735 const InfoHash infoHash
{alert
->handle
.info_hashes()};
5736 if (infoHash
.isHybrid())
5737 torrent
= m_torrents
.value(TorrentID::fromSHA1Hash(infoHash
.v1()));
5743 torrent
->handleAlert(alert
);
5747 switch (alert
->type())
5749 case lt::metadata_received_alert::alert_type
:
5750 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(alert
));
5755 TorrentImpl
*SessionImpl::createTorrent(const lt::torrent_handle
&nativeHandle
, const LoadTorrentParams
¶ms
)
5757 auto *const torrent
= new TorrentImpl(this, m_nativeSession
, nativeHandle
, params
);
5758 m_torrents
.insert(torrent
->id(), torrent
);
5759 if (const InfoHash infoHash
= torrent
->infoHash(); infoHash
.isHybrid())
5760 m_hybridTorrentsByAltID
.insert(TorrentID::fromSHA1Hash(infoHash
.v1()), torrent
);
5764 if (params
.addToQueueTop
)
5765 nativeHandle
.queue_position_top();
5767 torrent
->requestResumeData(lt::torrent_handle::save_info_dict
);
5769 // The following is useless for newly added magnet
5770 if (torrent
->hasMetadata())
5772 if (!torrentExportDirectory().isEmpty())
5773 exportTorrentFile(torrent
, torrentExportDirectory());
5777 if (((torrent
->ratioLimit() >= 0) || (torrent
->seedingTimeLimit() >= 0))
5778 && !m_seedingLimitTimer
->isActive())
5780 m_seedingLimitTimer
->start();
5785 LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent
->name()));
5789 LogMsg(tr("Added new torrent. Torrent: \"%1\"").arg(torrent
->name()));
5790 emit
torrentAdded(torrent
);
5793 // Torrent could have error just after adding to libtorrent
5794 if (torrent
->hasError())
5795 LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: \"%2\"").arg(torrent
->name(), torrent
->error()), Log::WARNING
);
5800 void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert */
*alert*/
)
5802 // We cannot consider `torrent_removed_alert` as a starting point for removing content,
5803 // because it has an inconsistent posting time between different versions of libtorrent,
5804 // so files may still be in use in some cases.
5807 void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert
*alert
)
5809 #ifdef QBT_USES_LIBTORRENT2
5810 const auto torrentID
= TorrentID::fromInfoHash(alert
->info_hashes
);
5812 const auto torrentID
= TorrentID::fromInfoHash(alert
->info_hash
);
5814 handleRemovedTorrent(torrentID
);
5817 void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert
*alert
)
5819 #ifdef QBT_USES_LIBTORRENT2
5820 const auto torrentID
= TorrentID::fromInfoHash(alert
->info_hashes
);
5822 const auto torrentID
= TorrentID::fromInfoHash(alert
->info_hash
);
5824 const auto errorMessage
= alert
->error
? QString::fromLocal8Bit(alert
->error
.message().c_str()) : QString();
5825 handleRemovedTorrent(torrentID
, errorMessage
);
5828 void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert
*alert
)
5830 #ifdef QBT_USES_LIBTORRENT2
5831 const InfoHash infoHash
{alert
->handle
.info_hashes()};
5833 const InfoHash infoHash
{alert
->handle
.info_hash()};
5835 const auto torrentID
= TorrentID::fromInfoHash(infoHash
);
5837 TorrentImpl
*const torrent
= m_torrents
.value(torrentID
);
5838 if (!torrent
) [[unlikely
]]
5841 if (!torrent
->applySSLParameters())
5843 LogMsg(tr("Torrent is missing SSL parameters. Torrent: \"%1\". Message: \"%2\"").arg(torrent
->name(), QString::fromStdString(alert
->message()))
5848 void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
*alert
)
5850 const TorrentID torrentID
{alert
->handle
.info_hash()};
5853 if (const auto iter
= m_downloadedMetadata
.find(torrentID
); iter
!= m_downloadedMetadata
.end())
5856 m_downloadedMetadata
.erase(iter
);
5858 #ifdef QBT_USES_LIBTORRENT2
5859 const InfoHash infoHash
{alert
->handle
.info_hashes()};
5860 if (infoHash
.isHybrid())
5862 const auto altID
= TorrentID::fromSHA1Hash(infoHash
.v1());
5863 if (const auto iter
= m_downloadedMetadata
.find(altID
); iter
!= m_downloadedMetadata
.end())
5866 m_downloadedMetadata
.erase(iter
);
5872 const TorrentInfo metadata
{*alert
->handle
.torrent_file()};
5873 m_nativeSession
->remove_torrent(alert
->handle
, lt::session::delete_files
);
5875 emit
metadataDownloaded(metadata
);
5879 void SessionImpl::handleFileErrorAlert(const lt::file_error_alert
*alert
)
5881 TorrentImpl
*const torrent
= m_torrents
.value(alert
->handle
.info_hash());
5885 torrent
->handleAlert(alert
);
5887 const TorrentID id
= torrent
->id();
5888 if (!m_recentErroredTorrents
.contains(id
))
5890 m_recentErroredTorrents
.insert(id
);
5892 const QString msg
= QString::fromStdString(alert
->message());
5893 LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: \"%3\"")
5894 .arg(torrent
->name(), QString::fromUtf8(alert
->filename()), msg
)
5896 emit
fullDiskError(torrent
, msg
);
5899 m_recentErroredTorrentsTimer
->start();
5902 void SessionImpl::handlePortmapWarningAlert(const lt::portmap_error_alert
*alert
)
5904 LogMsg(tr("UPnP/NAT-PMP port mapping failed. Message: \"%1\"").arg(QString::fromStdString(alert
->message())), Log::WARNING
);
5907 void SessionImpl::handlePortmapAlert(const lt::portmap_alert
*alert
)
5909 qDebug("UPnP Success, msg: %s", alert
->message().c_str());
5910 LogMsg(tr("UPnP/NAT-PMP port mapping succeeded. Message: \"%1\"").arg(QString::fromStdString(alert
->message())), Log::INFO
);
5913 void SessionImpl::handlePeerBlockedAlert(const lt::peer_blocked_alert
*alert
)
5916 switch (alert
->reason
)
5918 case lt::peer_blocked_alert::ip_filter
:
5919 reason
= tr("IP filter", "this peer was blocked. Reason: IP filter.");
5921 case lt::peer_blocked_alert::port_filter
:
5922 reason
= tr("filtered port (%1)", "this peer was blocked. Reason: filtered port (8899).").arg(QString::number(alert
->endpoint
.port()));
5924 case lt::peer_blocked_alert::i2p_mixed
:
5925 reason
= tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg(u
"I2P"_s
); // don't translate I2P
5927 case lt::peer_blocked_alert::privileged_ports
:
5928 reason
= tr("privileged port (%1)", "this peer was blocked. Reason: privileged port (80).").arg(QString::number(alert
->endpoint
.port()));
5930 case lt::peer_blocked_alert::utp_disabled
:
5931 reason
= tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(C_UTP
); // don't translate μTP
5933 case lt::peer_blocked_alert::tcp_disabled
:
5934 reason
= tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg(u
"TCP"_s
); // don't translate TCP
5938 const QString ip
{toString(alert
->endpoint
.address())};
5940 Logger::instance()->addPeer(ip
, true, reason
);
5943 void SessionImpl::handlePeerBanAlert(const lt::peer_ban_alert
*alert
)
5945 const QString ip
{toString(alert
->endpoint
.address())};
5947 Logger::instance()->addPeer(ip
, false);
5950 void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert
*alert
)
5952 const TorrentImpl
*torrent
= m_torrents
.value(alert
->handle
.info_hash());
5958 LogMsg(tr("URL seed DNS lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
5959 .arg(torrent
->name(), QString::fromUtf8(alert
->server_url()), QString::fromStdString(alert
->message()))
5964 LogMsg(tr("Received error message from URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
5965 .arg(torrent
->name(), QString::fromUtf8(alert
->server_url()), QString::fromUtf8(alert
->error_message()))
5970 void SessionImpl::handleListenSucceededAlert(const lt::listen_succeeded_alert
*alert
)
5972 const QString proto
{toString(alert
->socket_type
)};
5973 LogMsg(tr("Successfully listening on IP. IP: \"%1\". Port: \"%2/%3\"")
5974 .arg(toString(alert
->address
), proto
, QString::number(alert
->port
)), Log::INFO
);
5977 void SessionImpl::handleListenFailedAlert(const lt::listen_failed_alert
*alert
)
5979 const QString proto
{toString(alert
->socket_type
)};
5980 LogMsg(tr("Failed to listen on IP. IP: \"%1\". Port: \"%2/%3\". Reason: \"%4\"")
5981 .arg(toString(alert
->address
), proto
, QString::number(alert
->port
)
5982 , QString::fromLocal8Bit(alert
->error
.message().c_str())), Log::CRITICAL
);
5985 void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert
*alert
)
5987 const QString externalIP
{toString(alert
->external_address
)};
5988 LogMsg(tr("Detected external IP. IP: \"%1\"")
5989 .arg(externalIP
), Log::INFO
);
5991 if (m_lastExternalIP
!= externalIP
)
5993 if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP
.isEmpty())
5994 reannounceToAllTrackers();
5995 m_lastExternalIP
= externalIP
;
5999 void SessionImpl::handleSessionErrorAlert(const lt::session_error_alert
*alert
) const
6001 LogMsg(tr("BitTorrent session encountered a serious error. Reason: \"%1\"")
6002 .arg(QString::fromStdString(alert
->message())), Log::CRITICAL
);
6005 void SessionImpl::handleSessionStatsAlert(const lt::session_stats_alert
*alert
)
6007 if (m_refreshEnqueued
)
6008 m_refreshEnqueued
= false;
6012 const int64_t interval
= lt::total_microseconds(alert
->timestamp() - m_statsLastTimestamp
);
6016 m_statsLastTimestamp
= alert
->timestamp();
6018 const auto stats
= alert
->counters();
6020 m_status
.hasIncomingConnections
= static_cast<bool>(stats
[m_metricIndices
.net
.hasIncomingConnections
]);
6022 const int64_t ipOverheadDownload
= stats
[m_metricIndices
.net
.recvIPOverheadBytes
];
6023 const int64_t ipOverheadUpload
= stats
[m_metricIndices
.net
.sentIPOverheadBytes
];
6024 const int64_t totalDownload
= stats
[m_metricIndices
.net
.recvBytes
] + ipOverheadDownload
;
6025 const int64_t totalUpload
= stats
[m_metricIndices
.net
.sentBytes
] + ipOverheadUpload
;
6026 const int64_t totalPayloadDownload
= stats
[m_metricIndices
.net
.recvPayloadBytes
];
6027 const int64_t totalPayloadUpload
= stats
[m_metricIndices
.net
.sentPayloadBytes
];
6028 const int64_t trackerDownload
= stats
[m_metricIndices
.net
.recvTrackerBytes
];
6029 const int64_t trackerUpload
= stats
[m_metricIndices
.net
.sentTrackerBytes
];
6030 const int64_t dhtDownload
= stats
[m_metricIndices
.dht
.dhtBytesIn
];
6031 const int64_t dhtUpload
= stats
[m_metricIndices
.dht
.dhtBytesOut
];
6033 const auto calcRate
= [interval
](const qint64 previous
, const qint64 current
) -> qint64
6035 Q_ASSERT(current
>= previous
);
6036 Q_ASSERT(interval
>= 0);
6037 return (((current
- previous
) * lt::microseconds(1s
).count()) / interval
);
6040 m_status
.payloadDownloadRate
= calcRate(m_status
.totalPayloadDownload
, totalPayloadDownload
);
6041 m_status
.payloadUploadRate
= calcRate(m_status
.totalPayloadUpload
, totalPayloadUpload
);
6042 m_status
.downloadRate
= calcRate(m_status
.totalDownload
, totalDownload
);
6043 m_status
.uploadRate
= calcRate(m_status
.totalUpload
, totalUpload
);
6044 m_status
.ipOverheadDownloadRate
= calcRate(m_status
.ipOverheadDownload
, ipOverheadDownload
);
6045 m_status
.ipOverheadUploadRate
= calcRate(m_status
.ipOverheadUpload
, ipOverheadUpload
);
6046 m_status
.dhtDownloadRate
= calcRate(m_status
.dhtDownload
, dhtDownload
);
6047 m_status
.dhtUploadRate
= calcRate(m_status
.dhtUpload
, dhtUpload
);
6048 m_status
.trackerDownloadRate
= calcRate(m_status
.trackerDownload
, trackerDownload
);
6049 m_status
.trackerUploadRate
= calcRate(m_status
.trackerUpload
, trackerUpload
);
6051 m_status
.totalPayloadDownload
= totalPayloadDownload
;
6052 m_status
.totalPayloadUpload
= totalPayloadUpload
;
6053 m_status
.ipOverheadDownload
= ipOverheadDownload
;
6054 m_status
.ipOverheadUpload
= ipOverheadUpload
;
6055 m_status
.trackerDownload
= trackerDownload
;
6056 m_status
.trackerUpload
= trackerUpload
;
6057 m_status
.dhtDownload
= dhtDownload
;
6058 m_status
.dhtUpload
= dhtUpload
;
6059 m_status
.totalWasted
= stats
[m_metricIndices
.net
.recvRedundantBytes
]
6060 + stats
[m_metricIndices
.net
.recvFailedBytes
];
6061 m_status
.dhtNodes
= stats
[m_metricIndices
.dht
.dhtNodes
];
6062 m_status
.diskReadQueue
= stats
[m_metricIndices
.peer
.numPeersUpDisk
];
6063 m_status
.diskWriteQueue
= stats
[m_metricIndices
.peer
.numPeersDownDisk
];
6064 m_status
.peersCount
= stats
[m_metricIndices
.peer
.numPeersConnected
];
6066 if (totalDownload
> m_status
.totalDownload
)
6068 m_status
.totalDownload
= totalDownload
;
6069 m_isStatisticsDirty
= true;
6072 if (totalUpload
> m_status
.totalUpload
)
6074 m_status
.totalUpload
= totalUpload
;
6075 m_isStatisticsDirty
= true;
6078 m_status
.allTimeDownload
= m_previouslyDownloaded
+ m_status
.totalDownload
;
6079 m_status
.allTimeUpload
= m_previouslyUploaded
+ m_status
.totalUpload
;
6081 if (m_saveStatisticsInterval
> 0)
6083 const auto saveInterval
= std::chrono::duration_cast
<std::chrono::milliseconds
>(std::chrono::minutes(m_saveStatisticsInterval
));
6084 if (m_statisticsLastUpdateTimer
.hasExpired(saveInterval
.count()))
6090 m_cacheStatus
.totalUsedBuffers
= stats
[m_metricIndices
.disk
.diskBlocksInUse
];
6091 m_cacheStatus
.jobQueueLength
= stats
[m_metricIndices
.disk
.queuedDiskJobs
];
6093 #ifndef QBT_USES_LIBTORRENT2
6094 const int64_t numBlocksRead
= stats
[m_metricIndices
.disk
.numBlocksRead
];
6095 const int64_t numBlocksCacheHits
= stats
[m_metricIndices
.disk
.numBlocksCacheHits
];
6096 m_cacheStatus
.readRatio
= static_cast<qreal
>(numBlocksCacheHits
) / std::max
<int64_t>((numBlocksCacheHits
+ numBlocksRead
), 1);
6099 const int64_t totalJobs
= stats
[m_metricIndices
.disk
.writeJobs
] + stats
[m_metricIndices
.disk
.readJobs
]
6100 + stats
[m_metricIndices
.disk
.hashJobs
];
6101 m_cacheStatus
.averageJobTime
= (totalJobs
> 0)
6102 ? (stats
[m_metricIndices
.disk
.diskJobTime
] / totalJobs
) : 0;
6104 emit
statsUpdated();
6107 void SessionImpl::handleAlertsDroppedAlert(const lt::alerts_dropped_alert
*alert
) const
6109 LogMsg(tr("Error: Internal alert queue is full and alerts are dropped, you might see degraded performance. Dropped alert type: \"%1\". Message: \"%2\"")
6110 .arg(QString::fromStdString(alert
->dropped_alerts
.to_string()), QString::fromStdString(alert
->message())), Log::CRITICAL
);
6113 void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert
*alert
)
6115 Q_ASSERT(!m_moveStorageQueue
.isEmpty());
6117 const MoveStorageJob
¤tJob
= m_moveStorageQueue
.first();
6118 Q_ASSERT(currentJob
.torrentHandle
== alert
->handle
);
6120 const Path newPath
{QString::fromUtf8(alert
->storage_path())};
6121 Q_ASSERT(newPath
== currentJob
.path
);
6123 #ifdef QBT_USES_LIBTORRENT2
6124 const auto id
= TorrentID::fromInfoHash(currentJob
.torrentHandle
.info_hashes());
6126 const auto id
= TorrentID::fromInfoHash(currentJob
.torrentHandle
.info_hash());
6129 TorrentImpl
*torrent
= m_torrents
.value(id
);
6130 const QString torrentName
= (torrent
? torrent
->name() : id
.toString());
6131 LogMsg(tr("Moved torrent successfully. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName
, newPath
.toString()));
6133 handleMoveTorrentStorageJobFinished(newPath
);
6136 void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert
*alert
)
6138 Q_ASSERT(!m_moveStorageQueue
.isEmpty());
6140 const MoveStorageJob
¤tJob
= m_moveStorageQueue
.first();
6141 Q_ASSERT(currentJob
.torrentHandle
== alert
->handle
);
6143 #ifdef QBT_USES_LIBTORRENT2
6144 const auto id
= TorrentID::fromInfoHash(currentJob
.torrentHandle
.info_hashes());
6146 const auto id
= TorrentID::fromInfoHash(currentJob
.torrentHandle
.info_hash());
6149 TorrentImpl
*torrent
= m_torrents
.value(id
);
6150 const QString torrentName
= (torrent
? torrent
->name() : id
.toString());
6151 const Path currentLocation
= (torrent
? torrent
->actualStorageLocation()
6152 : Path(alert
->handle
.status(lt::torrent_handle::query_save_path
).save_path
));
6153 const QString errorMessage
= QString::fromStdString(alert
->message());
6154 LogMsg(tr("Failed to move torrent. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: \"%4\"")
6155 .arg(torrentName
, currentLocation
.toString(), currentJob
.path
.toString(), errorMessage
), Log::WARNING
);
6157 handleMoveTorrentStorageJobFinished(currentLocation
);
6160 void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert
*alert
)
6162 QList
<Torrent
*> updatedTorrents
;
6163 updatedTorrents
.reserve(static_cast<decltype(updatedTorrents
)::size_type
>(alert
->status
.size()));
6165 for (const lt::torrent_status
&status
: alert
->status
)
6167 #ifdef QBT_USES_LIBTORRENT2
6168 const auto id
= TorrentID::fromInfoHash(status
.info_hashes
);
6170 const auto id
= TorrentID::fromInfoHash(status
.info_hash
);
6172 TorrentImpl
*const torrent
= m_torrents
.value(id
);
6176 torrent
->handleStateUpdate(status
);
6177 updatedTorrents
.push_back(torrent
);
6180 if (!updatedTorrents
.isEmpty())
6181 emit
torrentsUpdated(updatedTorrents
);
6183 if (m_needSaveTorrentsQueue
)
6184 saveTorrentsQueue();
6186 if (m_refreshEnqueued
)
6187 m_refreshEnqueued
= false;
6192 void SessionImpl::handleSocks5Alert(const lt::socks5_alert
*alert
) const
6196 const auto addr
= alert
->ip
.address();
6197 const QString endpoint
= (addr
.is_v6() ? u
"[%1]:%2"_s
: u
"%1:%2"_s
)
6198 .arg(QString::fromStdString(addr
.to_string()), QString::number(alert
->ip
.port()));
6199 LogMsg(tr("SOCKS5 proxy error. Address: %1. Message: \"%2\".")
6200 .arg(endpoint
, QString::fromLocal8Bit(alert
->error
.message().c_str()))
6205 void SessionImpl::handleI2PAlert(const lt::i2p_alert
*alert
) const
6209 LogMsg(tr("I2P error. Message: \"%1\".")
6210 .arg(QString::fromStdString(alert
->message())), Log::WARNING
);
6214 void SessionImpl::handleTrackerAlert(const lt::tracker_alert
*alert
)
6216 TorrentImpl
*torrent
= m_torrents
.value(alert
->handle
.info_hash());
6220 QMap
<int, int> &updateInfo
= m_updatedTrackerStatuses
[torrent
->nativeHandle()][std::string(alert
->tracker_url())][alert
->local_endpoint
];
6222 if (alert
->type() == lt::tracker_reply_alert::alert_type
)
6224 const int numPeers
= static_cast<const lt::tracker_reply_alert
*>(alert
)->num_peers
;
6225 #ifdef QBT_USES_LIBTORRENT2
6226 const int protocolVersionNum
= (static_cast<const lt::tracker_reply_alert
*>(alert
)->version
== lt::protocol_version::V1
) ? 1 : 2;
6228 const int protocolVersionNum
= 1;
6230 updateInfo
.insert(protocolVersionNum
, numPeers
);
6234 #ifdef QBT_USES_LIBTORRENT2
6235 void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert
*alert
)
6237 const auto torrentIDv1
= TorrentID::fromSHA1Hash(alert
->metadata
->info_hashes().v1
);
6238 const auto torrentIDv2
= TorrentID::fromSHA256Hash(alert
->metadata
->info_hashes().v2
);
6239 TorrentImpl
*torrent1
= m_torrents
.value(torrentIDv1
);
6240 TorrentImpl
*torrent2
= m_torrents
.value(torrentIDv2
);
6244 removeTorrent(torrentIDv1
);
6246 cancelDownloadMetadata(torrentIDv1
);
6248 invokeAsync([torrentHandle
= torrent2
->nativeHandle(), metadata
= alert
->metadata
]
6252 torrentHandle
.set_metadata(metadata
->info_section());
6254 catch (const std::exception
&) {}
6260 cancelDownloadMetadata(torrentIDv2
);
6262 invokeAsync([torrentHandle
= torrent1
->nativeHandle(), metadata
= alert
->metadata
]
6266 torrentHandle
.set_metadata(metadata
->info_section());
6268 catch (const std::exception
&) {}
6273 cancelDownloadMetadata(torrentIDv1
);
6274 cancelDownloadMetadata(torrentIDv2
);
6277 if (!torrent1
|| !torrent2
)
6278 emit
metadataDownloaded(TorrentInfo(*alert
->metadata
));
6282 void SessionImpl::processTrackerStatuses()
6284 if (m_updatedTrackerStatuses
.isEmpty())
6287 for (auto it
= m_updatedTrackerStatuses
.cbegin(); it
!= m_updatedTrackerStatuses
.cend(); ++it
)
6288 updateTrackerEntryStatuses(it
.key(), it
.value());
6290 m_updatedTrackerStatuses
.clear();
6293 void SessionImpl::saveStatistics() const
6295 if (!m_isStatisticsDirty
)
6298 const QVariantHash stats
{
6299 {u
"AlltimeDL"_s
, m_status
.allTimeDownload
},
6300 {u
"AlltimeUL"_s
, m_status
.allTimeUpload
}};
6301 std::unique_ptr
<QSettings
> settings
= Profile::instance()->applicationSettings(u
"qBittorrent-data"_s
);
6302 settings
->setValue(u
"Stats/AllStats"_s
, stats
);
6304 m_statisticsLastUpdateTimer
.start();
6305 m_isStatisticsDirty
= false;
6308 void SessionImpl::loadStatistics()
6310 const std::unique_ptr
<QSettings
> settings
= Profile::instance()->applicationSettings(u
"qBittorrent-data"_s
);
6311 const QVariantHash value
= settings
->value(u
"Stats/AllStats"_s
).toHash();
6313 m_previouslyDownloaded
= value
[u
"AlltimeDL"_s
].toLongLong();
6314 m_previouslyUploaded
= value
[u
"AlltimeUL"_s
].toLongLong();
6317 void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle
, QHash
<std::string
, QHash
<lt::tcp::endpoint
, QMap
<int, int>>> updatedTrackers
)
6319 invokeAsync([this, torrentHandle
= std::move(torrentHandle
), updatedTrackers
= std::move(updatedTrackers
)]() mutable
6323 std::vector
<lt::announce_entry
> nativeTrackers
= torrentHandle
.trackers();
6324 invoke([this, torrentHandle
, nativeTrackers
= std::move(nativeTrackers
)
6325 , updatedTrackers
= std::move(updatedTrackers
)]
6327 TorrentImpl
*torrent
= m_torrents
.value(torrentHandle
.info_hash());
6328 if (!torrent
|| torrent
->isStopped())
6331 QHash
<QString
, TrackerEntryStatus
> trackers
;
6332 trackers
.reserve(updatedTrackers
.size());
6333 for (const lt::announce_entry
&announceEntry
: nativeTrackers
)
6335 const auto updatedTrackersIter
= updatedTrackers
.find(announceEntry
.url
);
6336 if (updatedTrackersIter
== updatedTrackers
.end())
6339 const auto &updateInfo
= updatedTrackersIter
.value();
6340 TrackerEntryStatus status
= torrent
->updateTrackerEntryStatus(announceEntry
, updateInfo
);
6341 const QString url
= status
.url
;
6342 trackers
.emplace(url
, std::move(status
));
6345 emit
trackerEntryStatusesUpdated(torrent
, trackers
);
6348 catch (const std::exception
&)
6354 void SessionImpl::handleRemovedTorrent(const TorrentID
&torrentID
, const QString
&partfileRemoveError
)
6356 const auto removingTorrentDataIter
= m_removingTorrents
.find(torrentID
);
6357 if (removingTorrentDataIter
== m_removingTorrents
.end())
6360 if (!partfileRemoveError
.isEmpty())
6362 LogMsg(tr("Failed to remove partfile. Torrent: \"%1\". Reason: \"%2\".")
6363 .arg(removingTorrentDataIter
->name
, partfileRemoveError
)
6367 if ((removingTorrentDataIter
->removeOption
== TorrentRemoveOption::RemoveContent
)
6368 && !removingTorrentDataIter
->contentStoragePath
.isEmpty())
6370 QMetaObject::invokeMethod(m_torrentContentRemover
, [this, jobData
= *removingTorrentDataIter
]
6372 m_torrentContentRemover
->performJob(jobData
.name
, jobData
.contentStoragePath
6373 , jobData
.fileNames
, m_torrentContentRemoveOption
);
6377 m_removingTorrents
.erase(removingTorrentDataIter
);
6380 QDateTime
SessionImpl::fromLTTimePoint32(const libtorrent::time_point32
&timePoint
) const
6382 const auto secsSinceNow
= lt::duration_cast
<lt::seconds
>(timePoint
- m_ltNow
+ lt::milliseconds(500)).count();
6383 return m_qNow
.addSecs(secsSinceNow
);