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