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