qBittorrent/src/base/bittorrent/sessionimpl.cpp
Vladimir Golovnev 6c9c40fd7c
Avoid blocking call when changing libtorrent session settings
We don't really need to get currently used settings pack in order to apply changes to session settings. It is enough to apply settings pack that contains only updated settings.

PR #17989.
2022-11-09 08:02:34 +03:00

5780 lines
188 KiB
C++

/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "sessionimpl.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <ctime>
#include <queue>
#include <string>
#include <utility>
#ifdef Q_OS_WIN
#include <Windows.h>
#include <wincrypt.h>
#include <iphlpapi.h>
#endif
#include <libtorrent/add_torrent_params.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/error_code.hpp>
#include <libtorrent/extensions/smart_ban.hpp>
#include <libtorrent/extensions/ut_metadata.hpp>
#include <libtorrent/extensions/ut_pex.hpp>
#include <libtorrent/ip_filter.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <libtorrent/session.hpp>
#include <libtorrent/session_stats.hpp>
#include <libtorrent/session_status.hpp>
#include <libtorrent/torrent_info.hpp>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QHostAddress>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QNetworkAddressEntry>
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
#include <QNetworkConfigurationManager>
#endif
#include <QNetworkInterface>
#include <QRegularExpression>
#include <QString>
#include <QThread>
#include <QTimer>
#include <QUuid>
#include "base/algorithm.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
#include "base/net/proxyconfigurationmanager.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/torrentfileguard.h"
#include "base/torrentfilter.h"
#include "base/unicodestrings.h"
#include "base/utils/bytearray.h"
#include "base/utils/fs.h"
#include "base/utils/io.h"
#include "base/utils/misc.h"
#include "base/utils/net.h"
#include "base/utils/random.h"
#include "base/version.h"
#include "bandwidthscheduler.h"
#include "bencoderesumedatastorage.h"
#include "common.h"
#include "customstorage.h"
#include "dbresumedatastorage.h"
#include "downloadpriority.h"
#include "extensiondata.h"
#include "filesearcher.h"
#include "filterparserthread.h"
#include "loadtorrentparams.h"
#include "lttypecast.h"
#include "magneturi.h"
#include "nativesessionextension.h"
#include "portforwarderimpl.h"
#include "resumedatastorage.h"
#include "torrentimpl.h"
#include "tracker.h"
using namespace std::chrono_literals;
using namespace BitTorrent;
const Path CATEGORIES_FILE_NAME {u"categories.json"_qs};
const int MAX_PROCESSING_RESUMEDATA_COUNT = 50;
const int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count();
namespace
{
const char PEER_ID[] = "qB";
const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2);
void torrentQueuePositionUp(const lt::torrent_handle &handle)
{
try
{
handle.queue_position_up();
}
catch (const std::exception &exc)
{
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
}
}
void torrentQueuePositionDown(const lt::torrent_handle &handle)
{
try
{
handle.queue_position_down();
}
catch (const std::exception &exc)
{
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
}
}
void torrentQueuePositionTop(const lt::torrent_handle &handle)
{
try
{
handle.queue_position_top();
}
catch (const std::exception &exc)
{
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
}
}
void torrentQueuePositionBottom(const lt::torrent_handle &handle)
{
try
{
handle.queue_position_bottom();
}
catch (const std::exception &exc)
{
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
}
}
QMap<QString, CategoryOptions> expandCategories(const QMap<QString, CategoryOptions> &categories)
{
QMap<QString, CategoryOptions> expanded = categories;
for (auto i = categories.cbegin(); i != categories.cend(); ++i)
{
const QString &category = i.key();
for (const QString &subcat : asConst(Session::expandCategory(category)))
{
if (!expanded.contains(subcat))
expanded[subcat] = {};
}
}
return expanded;
}
QString toString(const lt::socket_type_t socketType)
{
switch (socketType)
{
#ifdef QBT_USES_LIBTORRENT2
case lt::socket_type_t::http:
return u"HTTP"_qs;
case lt::socket_type_t::http_ssl:
return u"HTTP_SSL"_qs;
#endif
case lt::socket_type_t::i2p:
return u"I2P"_qs;
case lt::socket_type_t::socks5:
return u"SOCKS5"_qs;
#ifdef QBT_USES_LIBTORRENT2
case lt::socket_type_t::socks5_ssl:
return u"SOCKS5_SSL"_qs;
#endif
case lt::socket_type_t::tcp:
return u"TCP"_qs;
case lt::socket_type_t::tcp_ssl:
return u"TCP_SSL"_qs;
#ifdef QBT_USES_LIBTORRENT2
case lt::socket_type_t::utp:
return u"UTP"_qs;
#else
case lt::socket_type_t::udp:
return u"UDP"_qs;
#endif
case lt::socket_type_t::utp_ssl:
return u"UTP_SSL"_qs;
}
return u"INVALID"_qs;
}
QString toString(const lt::address &address)
{
try
{
return QString::fromLatin1(address.to_string().c_str());
}
catch (const std::exception &)
{
// suppress conversion error
}
return {};
}
template <typename T>
struct LowerLimited
{
LowerLimited(T limit, T ret)
: m_limit(limit)
, m_ret(ret)
{
}
explicit LowerLimited(T limit)
: LowerLimited(limit, limit)
{
}
T operator()(T val) const
{
return val <= m_limit ? m_ret : val;
}
private:
const T m_limit;
const T m_ret;
};
template <typename T>
LowerLimited<T> lowerLimited(T limit) { return LowerLimited<T>(limit); }
template <typename T>
LowerLimited<T> lowerLimited(T limit, T ret) { return LowerLimited<T>(limit, ret); }
template <typename T>
auto clampValue(const T lower, const T upper)
{
return [lower, upper](const T value) -> T
{
if (value < lower)
return lower;
if (value > upper)
return upper;
return value;
};
}
#ifdef Q_OS_WIN
QString convertIfaceNameToGuid(const QString &name)
{
// Under Windows XP or on Qt version <= 5.5 'name' will be a GUID already.
const QUuid uuid(name);
if (!uuid.isNull())
return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
const std::wstring nameWStr = name.toStdWString();
NET_LUID luid {};
const LONG res = ::ConvertInterfaceNameToLuidW(nameWStr.c_str(), &luid);
if (res == 0)
{
GUID guid;
if (::ConvertInterfaceLuidToGuid(&luid, &guid) == 0)
return QUuid(guid).toString().toUpper();
}
return {};
}
#endif
constexpr lt::move_flags_t toNative(const MoveStorageMode mode)
{
switch (mode)
{
default:
Q_ASSERT(false);
case MoveStorageMode::FailIfExist:
return lt::move_flags_t::fail_if_exist;
case MoveStorageMode::KeepExistingFiles:
return lt::move_flags_t::dont_replace;
case MoveStorageMode::Overwrite:
return lt::move_flags_t::always_replace_files;
}
}
}
struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject
{
using QObject::QObject;
ResumeDataStorage *startupStorage = nullptr;
ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy;
QVector<LoadedResumeData> loadedResumeData;
int processingResumeDataCount = 0;
int64_t totalResumeDataCount = 0;
int64_t finishedResumeDataCount = 0;
bool isLoadFinished = false;
bool isLoadedResumeDataHandlingEnqueued = false;
QSet<QString> recoveredCategories;
#ifdef QBT_USES_LIBTORRENT2
QSet<TorrentID> indexedTorrents;
QSet<TorrentID> skippedIDs;
#endif
};
const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
Session *SessionImpl::m_instance = nullptr;
void Session::initInstance()
{
if (!SessionImpl::m_instance)
SessionImpl::m_instance = new SessionImpl;
}
void Session::freeInstance()
{
delete SessionImpl::m_instance;
SessionImpl::m_instance = nullptr;
}
Session *Session::instance()
{
return SessionImpl::m_instance;
}
bool Session::isValidCategoryName(const QString &name)
{
const QRegularExpression re {uR"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)"_qs};
return (name.isEmpty() || (name.indexOf(re) == 0));
}
bool Session::isValidTag(const QString &tag)
{
return (!tag.trimmed().isEmpty() && !tag.contains(u','));
}
QStringList Session::expandCategory(const QString &category)
{
QStringList result;
int index = 0;
while ((index = category.indexOf(u'/', index)) >= 0)
{
result << category.left(index);
++index;
}
result << category;
return result;
}
#define BITTORRENT_KEY(name) u"BitTorrent/" name
#define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY(u"Session/") name
SessionImpl::SessionImpl(QObject *parent)
: Session(parent)
, m_isDHTEnabled(BITTORRENT_SESSION_KEY(u"DHTEnabled"_qs), true)
, m_isLSDEnabled(BITTORRENT_SESSION_KEY(u"LSDEnabled"_qs), true)
, m_isPeXEnabled(BITTORRENT_SESSION_KEY(u"PeXEnabled"_qs), true)
, m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY(u"IPFilteringEnabled"_qs), false)
, m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY(u"TrackerFilteringEnabled"_qs), false)
, m_IPFilterFile(BITTORRENT_SESSION_KEY(u"IPFilter"_qs))
, m_announceToAllTrackers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTrackers"_qs), false)
, m_announceToAllTiers(BITTORRENT_SESSION_KEY(u"AnnounceToAllTiers"_qs), true)
, m_asyncIOThreads(BITTORRENT_SESSION_KEY(u"AsyncIOThreadsCount"_qs), 10)
, m_hashingThreads(BITTORRENT_SESSION_KEY(u"HashingThreadsCount"_qs), 1)
, m_filePoolSize(BITTORRENT_SESSION_KEY(u"FilePoolSize"_qs), 5000)
, m_checkingMemUsage(BITTORRENT_SESSION_KEY(u"CheckingMemUsageSize"_qs), 32)
, m_diskCacheSize(BITTORRENT_SESSION_KEY(u"DiskCacheSize"_qs), -1)
, m_diskCacheTTL(BITTORRENT_SESSION_KEY(u"DiskCacheTTL"_qs), 60)
, m_diskQueueSize(BITTORRENT_SESSION_KEY(u"DiskQueueSize"_qs), (1024 * 1024))
, m_diskIOType(BITTORRENT_SESSION_KEY(u"DiskIOType"_qs), DiskIOType::Default)
, m_diskIOReadMode(BITTORRENT_SESSION_KEY(u"DiskIOReadMode"_qs), DiskIOReadMode::EnableOSCache)
, m_diskIOWriteMode(BITTORRENT_SESSION_KEY(u"DiskIOWriteMode"_qs), DiskIOWriteMode::EnableOSCache)
#ifdef Q_OS_WIN
, m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_qs), true)
#else
, m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY(u"CoalesceReadWrite"_qs), false)
#endif
, m_usePieceExtentAffinity(BITTORRENT_SESSION_KEY(u"PieceExtentAffinity"_qs), false)
, m_isSuggestMode(BITTORRENT_SESSION_KEY(u"SuggestMode"_qs), false)
, m_sendBufferWatermark(BITTORRENT_SESSION_KEY(u"SendBufferWatermark"_qs), 500)
, m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY(u"SendBufferLowWatermark"_qs), 10)
, m_sendBufferWatermarkFactor(BITTORRENT_SESSION_KEY(u"SendBufferWatermarkFactor"_qs), 50)
, m_connectionSpeed(BITTORRENT_SESSION_KEY(u"ConnectionSpeed"_qs), 30)
, m_socketBacklogSize(BITTORRENT_SESSION_KEY(u"SocketBacklogSize"_qs), 30)
, m_isAnonymousModeEnabled(BITTORRENT_SESSION_KEY(u"AnonymousModeEnabled"_qs), false)
, m_isQueueingEnabled(BITTORRENT_SESSION_KEY(u"QueueingSystemEnabled"_qs), false)
, m_maxActiveDownloads(BITTORRENT_SESSION_KEY(u"MaxActiveDownloads"_qs), 3, lowerLimited(-1))
, m_maxActiveUploads(BITTORRENT_SESSION_KEY(u"MaxActiveUploads"_qs), 3, lowerLimited(-1))
, m_maxActiveTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveTorrents"_qs), 5, lowerLimited(-1))
, m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY(u"IgnoreSlowTorrentsForQueueing"_qs), false)
, m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsDownloadRate"_qs), 2)
, m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY(u"SlowTorrentsUploadRate"_qs), 2)
, m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY(u"SlowTorrentsInactivityTimer"_qs), 60)
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY(u"OutgoingPortsMin"_qs), 0)
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY(u"OutgoingPortsMax"_qs), 0)
, m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY(u"UPnPLeaseDuration"_qs), 0)
, m_peerToS(BITTORRENT_SESSION_KEY(u"PeerToS"_qs), 0x04)
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY(u"IgnoreLimitsOnLAN"_qs), false)
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY(u"IncludeOverheadInLimits"_qs), false)
, m_announceIP(BITTORRENT_SESSION_KEY(u"AnnounceIP"_qs))
, m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY(u"MaxConcurrentHTTPAnnounces"_qs), 50)
, m_isReannounceWhenAddressChangedEnabled(BITTORRENT_SESSION_KEY(u"ReannounceWhenAddressChanged"_qs), false)
, m_stopTrackerTimeout(BITTORRENT_SESSION_KEY(u"StopTrackerTimeout"_qs), 5)
, m_maxConnections(BITTORRENT_SESSION_KEY(u"MaxConnections"_qs), 500, lowerLimited(0, -1))
, m_maxUploads(BITTORRENT_SESSION_KEY(u"MaxUploads"_qs), 20, lowerLimited(0, -1))
, m_maxConnectionsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxConnectionsPerTorrent"_qs), 100, lowerLimited(0, -1))
, m_maxUploadsPerTorrent(BITTORRENT_SESSION_KEY(u"MaxUploadsPerTorrent"_qs), 4, lowerLimited(0, -1))
, m_btProtocol(BITTORRENT_SESSION_KEY(u"BTProtocol"_qs), BTProtocol::Both
, clampValue(BTProtocol::Both, BTProtocol::UTP))
, m_isUTPRateLimited(BITTORRENT_SESSION_KEY(u"uTPRateLimited"_qs), true)
, m_utpMixedMode(BITTORRENT_SESSION_KEY(u"uTPMixedMode"_qs), MixedModeAlgorithm::TCP
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
, m_IDNSupportEnabled(BITTORRENT_SESSION_KEY(u"IDNSupportEnabled"_qs), false)
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY(u"MultiConnectionsPerIp"_qs), false)
, m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY(u"ValidateHTTPSTrackerCertificate"_qs), true)
, m_SSRFMitigationEnabled(BITTORRENT_SESSION_KEY(u"SSRFMitigation"_qs), true)
, m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY(u"BlockPeersOnPrivilegedPorts"_qs), false)
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersEnabled"_qs), false)
, m_additionalTrackers(BITTORRENT_SESSION_KEY(u"AdditionalTrackers"_qs))
, m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_qs), -1, [](qreal r) { return r < 0 ? -1. : r;})
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_qs), -1, lowerLimited(-1))
, m_isAddTorrentPaused(BITTORRENT_SESSION_KEY(u"AddTorrentPaused"_qs), false)
, m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_qs), Torrent::StopCondition::None)
, m_torrentContentLayout(BITTORRENT_SESSION_KEY(u"TorrentContentLayout"_qs), TorrentContentLayout::Original)
, m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY(u"AddExtensionToIncompleteFiles"_qs), false)
, m_refreshInterval(BITTORRENT_SESSION_KEY(u"RefreshInterval"_qs), 1500)
, m_isPreallocationEnabled(BITTORRENT_SESSION_KEY(u"Preallocation"_qs), false)
, m_torrentExportDirectory(BITTORRENT_SESSION_KEY(u"TorrentExportDirectory"_qs))
, m_finishedTorrentExportDirectory(BITTORRENT_SESSION_KEY(u"FinishedTorrentExportDirectory"_qs))
, m_globalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalDLSpeedLimit"_qs), 0, lowerLimited(0))
, m_globalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"GlobalUPSpeedLimit"_qs), 0, lowerLimited(0))
, m_altGlobalDownloadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalDLSpeedLimit"_qs), 10, lowerLimited(0))
, m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY(u"AlternativeGlobalUPSpeedLimit"_qs), 10, lowerLimited(0))
, m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY(u"UseAlternativeGlobalSpeedLimit"_qs), false)
, m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY(u"BandwidthSchedulerEnabled"_qs), false)
, m_isPerformanceWarningEnabled(BITTORRENT_SESSION_KEY(u"PerformanceWarning"_qs), false)
, m_saveResumeDataInterval(BITTORRENT_SESSION_KEY(u"SaveResumeDataInterval"_qs), 60)
, m_port(BITTORRENT_SESSION_KEY(u"Port"_qs), -1)
, m_networkInterface(BITTORRENT_SESSION_KEY(u"Interface"_qs))
, m_networkInterfaceName(BITTORRENT_SESSION_KEY(u"InterfaceName"_qs))
, m_networkInterfaceAddress(BITTORRENT_SESSION_KEY(u"InterfaceAddress"_qs))
, m_encryption(BITTORRENT_SESSION_KEY(u"Encryption"_qs), 0)
, m_maxActiveCheckingTorrents(BITTORRENT_SESSION_KEY(u"MaxActiveCheckingTorrents"_qs), 1)
, m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY(u"ProxyPeerConnections"_qs), false)
, m_isProxyHostnameLookupEnabled(BITTORRENT_SESSION_KEY(u"ProxyHostnameLookup"_qs), true)
, m_chokingAlgorithm(BITTORRENT_SESSION_KEY(u"ChokingAlgorithm"_qs), ChokingAlgorithm::FixedSlots
, clampValue(ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased))
, m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY(u"SeedChokingAlgorithm"_qs), SeedChokingAlgorithm::FastestUpload
, clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
, m_storedTags(BITTORRENT_SESSION_KEY(u"Tags"_qs))
, m_maxRatioAction(BITTORRENT_SESSION_KEY(u"MaxRatioAction"_qs), Pause)
, m_savePath(BITTORRENT_SESSION_KEY(u"DefaultSavePath"_qs), specialFolderLocation(SpecialFolder::Downloads))
, m_downloadPath(BITTORRENT_SESSION_KEY(u"TempPath"_qs), (savePath() / Path(u"temp"_qs)))
, m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY(u"TempPathEnabled"_qs), false)
, m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY(u"SubcategoriesEnabled"_qs), false)
, m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY(u"UseCategoryPathsInManualMode"_qs), false)
, m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY(u"DisableAutoTMMByDefault"_qs), true)
, m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategoryChanged"_qs), false)
, m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/DefaultSavePathChanged"_qs), true)
, m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategorySavePathChanged"_qs), true)
, m_isTrackerEnabled(BITTORRENT_KEY(u"TrackerEnabled"_qs), false)
, m_peerTurnover(BITTORRENT_SESSION_KEY(u"PeerTurnover"_qs), 4)
, m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY(u"PeerTurnoverCutOff"_qs), 90)
, m_peerTurnoverInterval(BITTORRENT_SESSION_KEY(u"PeerTurnoverInterval"_qs), 300)
, m_requestQueueSize(BITTORRENT_SESSION_KEY(u"RequestQueueSize"_qs), 500)
, m_isExcludedFileNamesEnabled(BITTORRENT_KEY(u"ExcludedFileNamesEnabled"_qs), false)
, m_excludedFileNames(BITTORRENT_SESSION_KEY(u"ExcludedFileNames"_qs))
, m_bannedIPs(u"State/BannedIPs"_qs
, QStringList()
, [](const QStringList &value)
{
QStringList tmp = value;
tmp.sort();
return tmp;
}
)
, m_resumeDataStorageType(BITTORRENT_SESSION_KEY(u"ResumeDataStorageType"_qs), ResumeDataStorageType::Legacy)
, m_seedingLimitTimer {new QTimer {this}}
, m_resumeDataTimer {new QTimer {this}}
, m_ioThread {new QThread {this}}
, m_recentErroredTorrentsTimer {new QTimer {this}}
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
, m_networkManager {new QNetworkConfigurationManager {this}}
#endif
{
if (port() < 0)
m_port = Utils::Random::rand(1024, 65535);
m_recentErroredTorrentsTimer->setSingleShot(true);
m_recentErroredTorrentsTimer->setInterval(1s);
connect(m_recentErroredTorrentsTimer, &QTimer::timeout
, this, [this]() { m_recentErroredTorrents.clear(); });
m_seedingLimitTimer->setInterval(10s);
connect(m_seedingLimitTimer, &QTimer::timeout, this, &SessionImpl::processShareLimits);
initializeNativeSession();
configureComponents();
if (isBandwidthSchedulerEnabled())
enableBandwidthScheduler();
loadCategories();
if (isSubcategoriesEnabled())
{
// if subcategories support changed manually
m_categories = expandCategories(m_categories);
}
const QStringList storedTags = m_storedTags.get();
m_tags = {storedTags.cbegin(), storedTags.cend()};
updateSeedingLimitTimer();
populateAdditionalTrackers();
if (isExcludedFileNamesEnabled())
populateExcludedFileNamesRegExpList();
connect(Net::ProxyConfigurationManager::instance()
, &Net::ProxyConfigurationManager::proxyConfigurationChanged
, this, &SessionImpl::configureDeferred);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
// Network configuration monitor
connect(m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &SessionImpl::networkOnlineStateChanged);
connect(m_networkManager, &QNetworkConfigurationManager::configurationAdded, this, &SessionImpl::networkConfigurationChange);
connect(m_networkManager, &QNetworkConfigurationManager::configurationRemoved, this, &SessionImpl::networkConfigurationChange);
connect(m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &SessionImpl::networkConfigurationChange);
#endif
m_fileSearcher = new FileSearcher;
m_fileSearcher->moveToThread(m_ioThread);
connect(m_ioThread, &QThread::finished, m_fileSearcher, &QObject::deleteLater);
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
m_ioThread->start();
initMetrics();
loadStatistics();
// initialize PortForwarder instance
new PortForwarderImpl(m_nativeSession);
// start embedded tracker
enableTracker(isTrackerEnabled());
prepareStartup();
}
SessionImpl::~SessionImpl()
{
m_nativeSession->pause();
if (m_torrentsQueueChanged)
{
m_nativeSession->post_torrent_updates({});
m_torrentsQueueChanged = false;
m_needSaveTorrentsQueue = true;
}
// Do some bittorrent related saving
// After this, (ideally) no more important alerts will be generated/handled
saveResumeData();
saveStatistics();
// We must delete FilterParserThread
// before we delete lt::session
delete m_filterParser;
// We must delete PortForwarderImpl before
// we delete lt::session
delete Net::PortForwarder::instance();
qDebug("Deleting the session");
delete m_nativeSession;
m_ioThread->quit();
m_ioThread->wait();
}
bool SessionImpl::isDHTEnabled() const
{
return m_isDHTEnabled;
}
void SessionImpl::setDHTEnabled(bool enabled)
{
if (enabled != m_isDHTEnabled)
{
m_isDHTEnabled = enabled;
configureDeferred();
LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(enabled ? tr("ON") : tr("OFF")), Log::INFO);
}
}
bool SessionImpl::isLSDEnabled() const
{
return m_isLSDEnabled;
}
void SessionImpl::setLSDEnabled(const bool enabled)
{
if (enabled != m_isLSDEnabled)
{
m_isLSDEnabled = enabled;
configureDeferred();
LogMsg(tr("Local Peer Discovery support: %1").arg(enabled ? tr("ON") : tr("OFF"))
, Log::INFO);
}
}
bool SessionImpl::isPeXEnabled() const
{
return m_isPeXEnabled;
}
void SessionImpl::setPeXEnabled(const bool enabled)
{
m_isPeXEnabled = enabled;
if (m_wasPexEnabled != enabled)
LogMsg(tr("Restart is required to toggle Peer Exchange (PeX) support"), Log::WARNING);
}
bool SessionImpl::isDownloadPathEnabled() const
{
return m_isDownloadPathEnabled;
}
void SessionImpl::setDownloadPathEnabled(const bool enabled)
{
if (enabled != isDownloadPathEnabled())
{
m_isDownloadPathEnabled = enabled;
for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->handleCategoryOptionsChanged();
}
}
bool SessionImpl::isAppendExtensionEnabled() const
{
return m_isAppendExtensionEnabled;
}
void SessionImpl::setAppendExtensionEnabled(const bool enabled)
{
if (isAppendExtensionEnabled() != enabled)
{
m_isAppendExtensionEnabled = enabled;
// append or remove .!qB extension for incomplete files
for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->handleAppendExtensionToggled();
}
}
int SessionImpl::refreshInterval() const
{
return m_refreshInterval;
}
void SessionImpl::setRefreshInterval(const int value)
{
if (value != refreshInterval())
{
m_refreshInterval = value;
}
}
bool SessionImpl::isPreallocationEnabled() const
{
return m_isPreallocationEnabled;
}
void SessionImpl::setPreallocationEnabled(const bool enabled)
{
m_isPreallocationEnabled = enabled;
}
Path SessionImpl::torrentExportDirectory() const
{
return m_torrentExportDirectory;
}
void SessionImpl::setTorrentExportDirectory(const Path &path)
{
if (path != torrentExportDirectory())
m_torrentExportDirectory = path;
}
Path SessionImpl::finishedTorrentExportDirectory() const
{
return m_finishedTorrentExportDirectory;
}
void SessionImpl::setFinishedTorrentExportDirectory(const Path &path)
{
if (path != finishedTorrentExportDirectory())
m_finishedTorrentExportDirectory = path;
}
Path SessionImpl::savePath() const
{
return m_savePath;
}
Path SessionImpl::downloadPath() const
{
return m_downloadPath;
}
QStringList SessionImpl::categories() const
{
return m_categories.keys();
}
CategoryOptions SessionImpl::categoryOptions(const QString &categoryName) const
{
return m_categories.value(categoryName);
}
Path SessionImpl::categorySavePath(const QString &categoryName) const
{
const Path basePath = savePath();
if (categoryName.isEmpty())
return basePath;
Path path = m_categories.value(categoryName).savePath;
if (path.isEmpty()) // use implicit save path
path = Utils::Fs::toValidPath(categoryName);
return (path.isAbsolute() ? path : (basePath / path));
}
Path SessionImpl::categoryDownloadPath(const QString &categoryName) const
{
const CategoryOptions categoryOptions = m_categories.value(categoryName);
const CategoryOptions::DownloadPathOption downloadPathOption =
categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
if (!downloadPathOption.enabled)
return {};
const Path basePath = downloadPath();
if (categoryName.isEmpty())
return basePath;
const Path path = (!downloadPathOption.path.isEmpty()
? downloadPathOption.path
: Utils::Fs::toValidPath(categoryName)); // use implicit download path
return (path.isAbsolute() ? path : (basePath / path));
}
bool SessionImpl::addCategory(const QString &name, const CategoryOptions &options)
{
if (name.isEmpty())
return false;
if (!isValidCategoryName(name) || m_categories.contains(name))
return false;
if (isSubcategoriesEnabled())
{
for (const QString &parent : asConst(expandCategory(name)))
{
if ((parent != name) && !m_categories.contains(parent))
{
m_categories[parent] = {};
emit categoryAdded(parent);
}
}
}
m_categories[name] = options;
storeCategories();
emit categoryAdded(name);
return true;
}
bool SessionImpl::editCategory(const QString &name, const CategoryOptions &options)
{
const auto it = m_categories.find(name);
if (it == m_categories.end())
return false;
CategoryOptions &currentOptions = it.value();
if (options == currentOptions)
return false;
currentOptions = options;
storeCategories();
if (isDisableAutoTMMWhenCategorySavePathChanged())
{
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (torrent->category() == name)
torrent->setAutoTMMEnabled(false);
}
}
else
{
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (torrent->category() == name)
torrent->handleCategoryOptionsChanged();
}
}
return true;
}
bool SessionImpl::removeCategory(const QString &name)
{
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (torrent->belongsToCategory(name))
torrent->setCategory(u""_qs);
}
// remove stored category and its subcategories if exist
bool result = false;
if (isSubcategoriesEnabled())
{
// remove subcategories
const QString test = name + u'/';
Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const CategoryOptions &)
{
if (category.startsWith(test))
{
result = true;
emit categoryRemoved(category);
return true;
}
return false;
});
}
result = (m_categories.remove(name) > 0) || result;
if (result)
{
// update stored categories
storeCategories();
emit categoryRemoved(name);
}
return result;
}
bool SessionImpl::isSubcategoriesEnabled() const
{
return m_isSubcategoriesEnabled;
}
void SessionImpl::setSubcategoriesEnabled(const bool value)
{
if (isSubcategoriesEnabled() == value) return;
if (value)
{
// expand categories to include all parent categories
m_categories = expandCategories(m_categories);
// update stored categories
storeCategories();
}
else
{
// reload categories
loadCategories();
}
m_isSubcategoriesEnabled = value;
emit subcategoriesSupportChanged();
}
bool SessionImpl::useCategoryPathsInManualMode() const
{
return m_useCategoryPathsInManualMode;
}
void SessionImpl::setUseCategoryPathsInManualMode(const bool value)
{
m_useCategoryPathsInManualMode = value;
}
QSet<QString> SessionImpl::tags() const
{
return m_tags;
}
bool SessionImpl::hasTag(const QString &tag) const
{
return m_tags.contains(tag);
}
bool SessionImpl::addTag(const QString &tag)
{
if (!isValidTag(tag) || hasTag(tag))
return false;
m_tags.insert(tag);
m_storedTags = m_tags.values();
emit tagAdded(tag);
return true;
}
bool SessionImpl::removeTag(const QString &tag)
{
if (m_tags.remove(tag))
{
for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->removeTag(tag);
m_storedTags = m_tags.values();
emit tagRemoved(tag);
return true;
}
return false;
}
bool SessionImpl::isAutoTMMDisabledByDefault() const
{
return m_isAutoTMMDisabledByDefault;
}
void SessionImpl::setAutoTMMDisabledByDefault(const bool value)
{
m_isAutoTMMDisabledByDefault = value;
}
bool SessionImpl::isDisableAutoTMMWhenCategoryChanged() const
{
return m_isDisableAutoTMMWhenCategoryChanged;
}
void SessionImpl::setDisableAutoTMMWhenCategoryChanged(const bool value)
{
m_isDisableAutoTMMWhenCategoryChanged = value;
}
bool SessionImpl::isDisableAutoTMMWhenDefaultSavePathChanged() const
{
return m_isDisableAutoTMMWhenDefaultSavePathChanged;
}
void SessionImpl::setDisableAutoTMMWhenDefaultSavePathChanged(const bool value)
{
m_isDisableAutoTMMWhenDefaultSavePathChanged = value;
}
bool SessionImpl::isDisableAutoTMMWhenCategorySavePathChanged() const
{
return m_isDisableAutoTMMWhenCategorySavePathChanged;
}
void SessionImpl::setDisableAutoTMMWhenCategorySavePathChanged(const bool value)
{
m_isDisableAutoTMMWhenCategorySavePathChanged = value;
}
bool SessionImpl::isAddTorrentPaused() const
{
return m_isAddTorrentPaused;
}
void SessionImpl::setAddTorrentPaused(const bool value)
{
m_isAddTorrentPaused = value;
}
Torrent::StopCondition SessionImpl::torrentStopCondition() const
{
return m_torrentStopCondition;
}
void SessionImpl::setTorrentStopCondition(const Torrent::StopCondition stopCondition)
{
m_torrentStopCondition = stopCondition;
}
bool SessionImpl::isTrackerEnabled() const
{
return m_isTrackerEnabled;
}
void SessionImpl::setTrackerEnabled(const bool enabled)
{
if (m_isTrackerEnabled != enabled)
m_isTrackerEnabled = enabled;
// call enableTracker() unconditionally, otherwise port change won't trigger
// tracker restart
enableTracker(enabled);
}
qreal SessionImpl::globalMaxRatio() const
{
return m_globalMaxRatio;
}
// Torrents with a ratio superior to the given value will
// be automatically deleted
void SessionImpl::setGlobalMaxRatio(qreal ratio)
{
if (ratio < 0)
ratio = -1.;
if (ratio != globalMaxRatio())
{
m_globalMaxRatio = ratio;
updateSeedingLimitTimer();
}
}
int SessionImpl::globalMaxSeedingMinutes() const
{
return m_globalMaxSeedingMinutes;
}
void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
{
if (minutes < 0)
minutes = -1;
if (minutes != globalMaxSeedingMinutes())
{
m_globalMaxSeedingMinutes = minutes;
updateSeedingLimitTimer();
}
}
void SessionImpl::adjustLimits()
{
if (isQueueingSystemEnabled())
{
lt::settings_pack settingsPack;
// Internally increase the queue limits to ensure that the magnet is started
settingsPack.set_int(lt::settings_pack::active_downloads, adjustLimit(maxActiveDownloads()));
settingsPack.set_int(lt::settings_pack::active_limit, adjustLimit(maxActiveTorrents()));
m_nativeSession->apply_settings(std::move(settingsPack));
}
}
void SessionImpl::applyBandwidthLimits()
{
lt::settings_pack settingsPack;
settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
m_nativeSession->apply_settings(std::move(settingsPack));
}
void SessionImpl::configure()
{
m_nativeSession->apply_settings(loadLTSettings());
configureComponents();
m_deferredConfigureScheduled = false;
}
void SessionImpl::configureComponents()
{
// This function contains components/actions that:
// 1. Need to be setup at start up
// 2. When deferred configure is called
configurePeerClasses();
if (!m_IPFilteringConfigured)
{
if (isIPFilteringEnabled())
enableIPFilter();
else
disableIPFilter();
m_IPFilteringConfigured = true;
}
}
void SessionImpl::prepareStartup()
{
qDebug("Initializing torrents resume data storage...");
const Path dbPath = specialFolderLocation(SpecialFolder::Data) / Path(u"torrents.db"_qs);
const bool dbStorageExists = dbPath.exists();
auto *context = new ResumeSessionContext(this);
context->currentStorageType = resumeDataStorageType();
if (context->currentStorageType == ResumeDataStorageType::SQLite)
{
m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
if (!dbStorageExists)
{
const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_qs);
context->startupStorage = new BencodeResumeDataStorage(dataPath, this);
}
}
else
{
const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path(u"BT_backup"_qs);
m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
if (dbStorageExists)
context->startupStorage = new DBResumeDataStorage(dbPath, this);
}
if (!context->startupStorage)
context->startupStorage = m_resumeDataStorage;
connect(context->startupStorage, &ResumeDataStorage::loadStarted, context
, [this, context](const QVector<TorrentID> &torrents)
{
context->totalResumeDataCount = torrents.size();
#ifdef QBT_USES_LIBTORRENT2
context->indexedTorrents = QSet<TorrentID>(torrents.cbegin(), torrents.cend());
#endif
handleLoadedResumeData(context);
});
connect(context->startupStorage, &ResumeDataStorage::loadFinished, context, [context]()
{
context->isLoadFinished = true;
});
connect(this, &SessionImpl::torrentsLoaded, context, [this, context](const QVector<Torrent *> &torrents)
{
context->processingResumeDataCount -= torrents.count();
context->finishedResumeDataCount += torrents.count();
if (!context->isLoadedResumeDataHandlingEnqueued)
{
QMetaObject::invokeMethod(this, [this, context]() { handleLoadedResumeData(context); }, Qt::QueuedConnection);
context->isLoadedResumeDataHandlingEnqueued = true;
}
if (!m_refreshEnqueued)
{
m_nativeSession->post_torrent_updates();
m_refreshEnqueued = true;
}
emit startupProgressUpdated((context->finishedResumeDataCount * 100.) / context->totalResumeDataCount);
});
context->startupStorage->loadAll();
}
void SessionImpl::handleLoadedResumeData(ResumeSessionContext *context)
{
context->isLoadedResumeDataHandlingEnqueued = false;
int count = context->processingResumeDataCount;
while (context->processingResumeDataCount < MAX_PROCESSING_RESUMEDATA_COUNT)
{
if (context->loadedResumeData.isEmpty())
context->loadedResumeData = context->startupStorage->fetchLoadedResumeData();
if (context->loadedResumeData.isEmpty())
{
if (context->processingResumeDataCount == 0)
{
if (context->isLoadFinished)
{
endStartup(context);
}
else if (!context->isLoadedResumeDataHandlingEnqueued)
{
QMetaObject::invokeMethod(this, [this, context]() { handleLoadedResumeData(context); }, Qt::QueuedConnection);
context->isLoadedResumeDataHandlingEnqueued = true;
}
}
break;
}
processNextResumeData(context);
++count;
}
context->finishedResumeDataCount += (count - context->processingResumeDataCount);
}
void SessionImpl::processNextResumeData(ResumeSessionContext *context)
{
const LoadedResumeData loadedResumeDataItem = context->loadedResumeData.takeFirst();
TorrentID torrentID = loadedResumeDataItem.torrentID;
#ifdef QBT_USES_LIBTORRENT2
if (context->skippedIDs.contains(torrentID))
return;
#endif
const nonstd::expected<LoadTorrentParams, QString> &loadResumeDataResult = loadedResumeDataItem.result;
if (!loadResumeDataResult)
{
LogMsg(tr("Failed to resume torrent. Torrent: \"%1\". Reason: \"%2\"")
.arg(torrentID.toString(), loadResumeDataResult.error()), Log::CRITICAL);
return;
}
LoadTorrentParams resumeData = *loadResumeDataResult;
bool needStore = false;
#ifdef QBT_USES_LIBTORRENT2
const InfoHash infoHash {(resumeData.ltAddTorrentParams.ti
? resumeData.ltAddTorrentParams.ti->info_hashes()
: resumeData.ltAddTorrentParams.info_hashes)};
const bool isHybrid = infoHash.isHybrid();
const auto torrentIDv2 = TorrentID::fromInfoHash(infoHash);
const auto torrentIDv1 = TorrentID::fromSHA1Hash(infoHash.v1());
if (torrentID == torrentIDv2)
{
if (isHybrid && context->indexedTorrents.contains(torrentIDv1))
{
// if we don't have metadata, try to find it in alternative "resume data"
if (!resumeData.ltAddTorrentParams.ti)
{
const nonstd::expected<LoadTorrentParams, QString> loadAltResumeDataResult = context->startupStorage->load(torrentIDv1);
if (loadAltResumeDataResult)
resumeData.ltAddTorrentParams.ti = loadAltResumeDataResult->ltAddTorrentParams.ti;
}
// remove alternative "resume data" and skip the attempt to load it
m_resumeDataStorage->remove(torrentIDv1);
context->skippedIDs.insert(torrentIDv1);
}
}
else if (torrentID == torrentIDv1)
{
torrentID = torrentIDv2;
needStore = true;
m_resumeDataStorage->remove(torrentIDv1);
if (context->indexedTorrents.contains(torrentID))
{
context->skippedIDs.insert(torrentID);
const nonstd::expected<LoadTorrentParams, QString> loadPreferredResumeDataResult = context->startupStorage->load(torrentID);
if (loadPreferredResumeDataResult)
{
std::shared_ptr<lt::torrent_info> ti = resumeData.ltAddTorrentParams.ti;
resumeData = *loadPreferredResumeDataResult;
if (!resumeData.ltAddTorrentParams.ti)
resumeData.ltAddTorrentParams.ti = ti;
}
}
}
else
{
LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
.arg(torrentID.toString()), Log::WARNING);
return;
}
#else
const lt::sha1_hash infoHash = (resumeData.ltAddTorrentParams.ti
? resumeData.ltAddTorrentParams.ti->info_hash()
: resumeData.ltAddTorrentParams.info_hash);
if (torrentID != TorrentID::fromInfoHash(infoHash))
{
LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
.arg(torrentID.toString()), Log::WARNING);
return;
}
#endif
if (m_resumeDataStorage != context->startupStorage)
needStore = true;
// TODO: Remove the following upgrade code in v4.6
// == BEGIN UPGRADE CODE ==
if (!needStore)
{
if (m_needUpgradeDownloadPath && isDownloadPathEnabled() && !resumeData.useAutoTMM)
{
resumeData.downloadPath = downloadPath();
needStore = true;
}
}
// == END UPGRADE CODE ==
if (needStore)
m_resumeDataStorage->store(torrentID, resumeData);
const QString category = resumeData.category;
bool isCategoryRecovered = context->recoveredCategories.contains(category);
if (!category.isEmpty() && (isCategoryRecovered || !m_categories.contains(category)))
{
if (!isCategoryRecovered)
{
if (addCategory(category))
{
context->recoveredCategories.insert(category);
isCategoryRecovered = true;
LogMsg(tr("Detected inconsistent data: category is missing from the configuration file."
" Category will be recovered but its settings will be reset to default."
" Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
}
else
{
resumeData.category.clear();
LogMsg(tr("Detected inconsistent data: invalid category. Torrent: \"%1\". Category: \"%2\"")
.arg(torrentID.toString(), category), Log::WARNING);
}
}
// We should check isCategoryRecovered again since the category
// can be just recovered by the code above
if (isCategoryRecovered && resumeData.useAutoTMM)
{
const Path storageLocation {resumeData.ltAddTorrentParams.save_path};
if ((storageLocation != categorySavePath(resumeData.category)) && (storageLocation != categoryDownloadPath(resumeData.category)))
{
resumeData.useAutoTMM = false;
resumeData.savePath = storageLocation;
resumeData.downloadPath = {};
LogMsg(tr("Detected mismatch between the save paths of the recovered category and the current save path of the torrent."
" Torrent is now switched to Manual mode."
" Torrent: \"%1\". Category: \"%2\"").arg(torrentID.toString(), category), Log::WARNING);
}
}
}
Algorithm::removeIf(resumeData.tags, [this, &torrentID](const QString &tag)
{
if (hasTag(tag))
return false;
if (addTag(tag))
{
LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file."
" Tag will be recovered."
" Torrent: \"%1\". Tag: \"%2\"").arg(torrentID.toString(), tag), Log::WARNING);
return false;
}
LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"")
.arg(torrentID.toString(), tag), Log::WARNING);
return true;
});
resumeData.ltAddTorrentParams.userdata = LTClientData(new ExtensionData);
#ifndef QBT_USES_LIBTORRENT2
resumeData.ltAddTorrentParams.storage = customStorageConstructor;
#endif
qDebug() << "Starting up torrent" << torrentID.toString() << "...";
m_loadingTorrents.insert(torrentID, resumeData);
#ifdef QBT_USES_LIBTORRENT2
if (infoHash.isHybrid())
{
// this allows to know the being added hybrid torrent by its v1 info hash
// without having yet another mapping table
m_hybridTorrentsByAltID.insert(torrentIDv1, nullptr);
}
#endif
m_nativeSession->async_add_torrent(resumeData.ltAddTorrentParams);
++context->processingResumeDataCount;
}
void SessionImpl::endStartup(ResumeSessionContext *context)
{
if (m_resumeDataStorage != context->startupStorage)
{
if (isQueueingSystemEnabled())
saveTorrentsQueue();
const Path dbPath = context->startupStorage->path();
context->startupStorage->deleteLater();
if (context->currentStorageType == ResumeDataStorageType::Legacy)
Utils::Fs::removeFile(dbPath);
}
context->deleteLater();
m_nativeSession->resume();
if (m_refreshEnqueued)
m_refreshEnqueued = false;
else
enqueueRefresh();
m_statisticsLastUpdateTimer.start();
// Regular saving of fastresume data
connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
const int saveInterval = saveResumeDataInterval();
if (saveInterval > 0)
{
m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
m_resumeDataTimer->start();
}
m_isRestored = true;
emit startupProgressUpdated(100);
emit restored();
}
void SessionImpl::initializeNativeSession()
{
lt::settings_pack pack = loadLTSettings();
const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
pack.set_str(lt::settings_pack::user_agent, USER_AGENT.toStdString());
pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
// Speed up exit
pack.set_int(lt::settings_pack::auto_scrape_interval, 1200); // 20 minutes
pack.set_int(lt::settings_pack::auto_scrape_min_interval, 900); // 15 minutes
// libtorrent 1.1 enables UPnP & NAT-PMP by default
// turn them off before `lt::session` ctor to avoid split second effects
pack.set_bool(lt::settings_pack::enable_upnp, false);
pack.set_bool(lt::settings_pack::enable_natpmp, false);
#ifdef QBT_USES_LIBTORRENT2
// preserve the same behavior as in earlier libtorrent versions
pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
#endif
lt::session_params sessionParams {std::move(pack), {}};
#ifdef QBT_USES_LIBTORRENT2
switch (diskIOType())
{
case DiskIOType::Posix:
sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
break;
case DiskIOType::MMap:
sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
break;
default:
sessionParams.disk_io_constructor = customDiskIOConstructor;
break;
}
#endif
#if LIBTORRENT_VERSION_NUM < 20100
m_nativeSession = new lt::session(sessionParams, lt::session::paused);
#else
m_nativeSession = new lt::session(sessionParams);
m_nativeSession->pause();
#endif
LogMsg(tr("Peer ID: \"%1\"").arg(QString::fromStdString(peerId)), Log::INFO);
LogMsg(tr("HTTP User-Agent: \"%1\"").arg(USER_AGENT), Log::INFO);
LogMsg(tr("Distributed Hash Table (DHT) support: %1").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
LogMsg(tr("Local Peer Discovery support: %1").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
LogMsg(tr("Peer Exchange (PeX) support: %1").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
LogMsg(tr("Encryption support: %1").arg((encryption() == 0) ? tr("ON") : ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO);
m_nativeSession->set_alert_notify([this]()
{
QMetaObject::invokeMethod(this, &SessionImpl::readAlerts, Qt::QueuedConnection);
});
// Enabling plugins
m_nativeSession->add_extension(&lt::create_smart_ban_plugin);
m_nativeSession->add_extension(&lt::create_ut_metadata_plugin);
if (isPeXEnabled())
m_nativeSession->add_extension(&lt::create_ut_pex_plugin);
m_nativeSession->add_extension(std::make_shared<NativeSessionExtension>());
}
void SessionImpl::processBannedIPs(lt::ip_filter &filter)
{
// First, import current filter
for (const QString &ip : asConst(m_bannedIPs.get()))
{
lt::error_code ec;
const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
Q_ASSERT(!ec);
if (!ec)
filter.add_rule(addr, addr, lt::ip_filter::blocked);
}
}
int SessionImpl::adjustLimit(const int limit) const
{
if (limit <= -1)
return limit;
// check for overflow: (limit + m_extraLimit) < std::numeric_limits<int>::max()
return (m_extraLimit < (std::numeric_limits<int>::max() - limit))
? (limit + m_extraLimit)
: std::numeric_limits<int>::max();
}
void SessionImpl::initMetrics()
{
const auto findMetricIndex = [](const char *name) -> int
{
const int index = lt::find_metric_idx(name);
Q_ASSERT(index >= 0);
return index;
};
// TODO: switch to "designated initializers" in C++20
m_metricIndices.net.hasIncomingConnections = findMetricIndex("net.has_incoming_connections");
m_metricIndices.net.sentPayloadBytes = findMetricIndex("net.sent_payload_bytes");
m_metricIndices.net.recvPayloadBytes = findMetricIndex("net.recv_payload_bytes");
m_metricIndices.net.sentBytes = findMetricIndex("net.sent_bytes");
m_metricIndices.net.recvBytes = findMetricIndex("net.recv_bytes");
m_metricIndices.net.sentIPOverheadBytes = findMetricIndex("net.sent_ip_overhead_bytes");
m_metricIndices.net.recvIPOverheadBytes = findMetricIndex("net.recv_ip_overhead_bytes");
m_metricIndices.net.sentTrackerBytes = findMetricIndex("net.sent_tracker_bytes");
m_metricIndices.net.recvTrackerBytes = findMetricIndex("net.recv_tracker_bytes");
m_metricIndices.net.recvRedundantBytes = findMetricIndex("net.recv_redundant_bytes");
m_metricIndices.net.recvFailedBytes = findMetricIndex("net.recv_failed_bytes");
m_metricIndices.peer.numPeersConnected = findMetricIndex("peer.num_peers_connected");
m_metricIndices.peer.numPeersDownDisk = findMetricIndex("peer.num_peers_down_disk");
m_metricIndices.peer.numPeersUpDisk = findMetricIndex("peer.num_peers_up_disk");
m_metricIndices.dht.dhtBytesIn = findMetricIndex("dht.dht_bytes_in");
m_metricIndices.dht.dhtBytesOut = findMetricIndex("dht.dht_bytes_out");
m_metricIndices.dht.dhtNodes = findMetricIndex("dht.dht_nodes");
m_metricIndices.disk.diskBlocksInUse = findMetricIndex("disk.disk_blocks_in_use");
m_metricIndices.disk.numBlocksRead = findMetricIndex("disk.num_blocks_read");
#ifndef QBT_USES_LIBTORRENT2
m_metricIndices.disk.numBlocksCacheHits = findMetricIndex("disk.num_blocks_cache_hits");
#endif
m_metricIndices.disk.writeJobs = findMetricIndex("disk.num_write_ops");
m_metricIndices.disk.readJobs = findMetricIndex("disk.num_read_ops");
m_metricIndices.disk.hashJobs = findMetricIndex("disk.num_blocks_hashed");
m_metricIndices.disk.queuedDiskJobs = findMetricIndex("disk.queued_disk_jobs");
m_metricIndices.disk.diskJobTime = findMetricIndex("disk.disk_job_time");
}
lt::settings_pack SessionImpl::loadLTSettings() const
{
lt::settings_pack settingsPack;
const lt::alert_category_t alertMask = lt::alert::error_notification
| lt::alert::file_progress_notification
| lt::alert::ip_block_notification
| lt::alert::peer_notification
| (isPerformanceWarningEnabled() ? lt::alert::performance_warning : lt::alert_category_t())
| lt::alert::port_mapping_notification
| lt::alert::status_notification
| lt::alert::storage_notification
| lt::alert::tracker_notification;
settingsPack.set_int(lt::settings_pack::alert_mask, alertMask);
settingsPack.set_int(lt::settings_pack::connection_speed, connectionSpeed());
// from libtorrent doc:
// It will not take affect until the listen_interfaces settings is updated
settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
applyNetworkInterfacesSettings(settingsPack);
settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
// The most secure, rc4 only so that all streams are encrypted
settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
settingsPack.set_bool(lt::settings_pack::prefer_rc4, true);
switch (encryption())
{
case 0: // Enabled
settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_enabled);
settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_enabled);
break;
case 1: // Forced
settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_forced);
settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_forced);
break;
default: // Disabled
settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_disabled);
settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_disabled);
}
settingsPack.set_int(lt::settings_pack::active_checking, maxActiveCheckingTorrents());
// proxy
const auto proxyManager = Net::ProxyConfigurationManager::instance();
const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
switch (proxyConfig.type)
{
case Net::ProxyType::HTTP:
settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http);
break;
case Net::ProxyType::HTTP_PW:
settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http_pw);
break;
case Net::ProxyType::SOCKS4:
settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks4);
break;
case Net::ProxyType::SOCKS5:
settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
break;
case Net::ProxyType::SOCKS5_PW:
settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5_pw);
break;
case Net::ProxyType::None:
default:
settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
}
if (proxyConfig.type != Net::ProxyType::None)
{
settingsPack.set_str(lt::settings_pack::proxy_hostname, proxyConfig.ip.toStdString());
settingsPack.set_int(lt::settings_pack::proxy_port, proxyConfig.port);
if (proxyManager->isAuthenticationRequired())
{
settingsPack.set_str(lt::settings_pack::proxy_username, proxyConfig.username.toStdString());
settingsPack.set_str(lt::settings_pack::proxy_password, proxyConfig.password.toStdString());
}
settingsPack.set_bool(lt::settings_pack::proxy_peer_connections, isProxyPeerConnectionsEnabled());
settingsPack.set_bool(lt::settings_pack::proxy_hostnames, isProxyHostnameLookupEnabled());
}
settingsPack.set_bool(lt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
settingsPack.set_bool(lt::settings_pack::announce_to_all_tiers, announceToAllTiers());
settingsPack.set_int(lt::settings_pack::peer_turnover, peerTurnover());
settingsPack.set_int(lt::settings_pack::peer_turnover_cutoff, peerTurnoverCutoff());
settingsPack.set_int(lt::settings_pack::peer_turnover_interval, peerTurnoverInterval());
settingsPack.set_int(lt::settings_pack::max_out_request_queue, requestQueueSize());
settingsPack.set_int(lt::settings_pack::aio_threads, asyncIOThreads());
#ifdef QBT_USES_LIBTORRENT2
settingsPack.set_int(lt::settings_pack::hashing_threads, hashingThreads());
#endif
settingsPack.set_int(lt::settings_pack::file_pool_size, filePoolSize());
const int checkingMemUsageSize = checkingMemUsage() * 64;
settingsPack.set_int(lt::settings_pack::checking_mem_usage, checkingMemUsageSize);
#ifndef QBT_USES_LIBTORRENT2
const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
settingsPack.set_int(lt::settings_pack::cache_size, cacheSize);
settingsPack.set_int(lt::settings_pack::cache_expiry, diskCacheTTL());
#endif
settingsPack.set_int(lt::settings_pack::max_queued_disk_bytes, diskQueueSize());
switch (diskIOReadMode())
{
case DiskIOReadMode::DisableOSCache:
settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::disable_os_cache);
break;
case DiskIOReadMode::EnableOSCache:
default:
settingsPack.set_int(lt::settings_pack::disk_io_read_mode, lt::settings_pack::enable_os_cache);
break;
}
switch (diskIOWriteMode())
{
case DiskIOWriteMode::DisableOSCache:
settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::disable_os_cache);
break;
case DiskIOWriteMode::EnableOSCache:
default:
settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::enable_os_cache);
break;
#ifdef QBT_USES_LIBTORRENT2
case DiskIOWriteMode::WriteThrough:
settingsPack.set_int(lt::settings_pack::disk_io_write_mode, lt::settings_pack::write_through);
break;
#endif
}
#ifndef QBT_USES_LIBTORRENT2
settingsPack.set_bool(lt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
settingsPack.set_bool(lt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
#endif
settingsPack.set_bool(lt::settings_pack::piece_extent_affinity, usePieceExtentAffinity());
settingsPack.set_int(lt::settings_pack::suggest_mode, isSuggestModeEnabled()
? lt::settings_pack::suggest_read_cache : lt::settings_pack::no_piece_suggestions);
settingsPack.set_int(lt::settings_pack::send_buffer_watermark, sendBufferWatermark() * 1024);
settingsPack.set_int(lt::settings_pack::send_buffer_low_watermark, sendBufferLowWatermark() * 1024);
settingsPack.set_int(lt::settings_pack::send_buffer_watermark_factor, sendBufferWatermarkFactor());
settingsPack.set_bool(lt::settings_pack::anonymous_mode, isAnonymousModeEnabled());
// Queueing System
if (isQueueingSystemEnabled())
{
// Internally increase the queue limits to ensure that the magnet is started
settingsPack.set_int(lt::settings_pack::active_downloads, adjustLimit(maxActiveDownloads()));
settingsPack.set_int(lt::settings_pack::active_limit, adjustLimit(maxActiveTorrents()));
settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
settingsPack.set_int(lt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
settingsPack.set_int(lt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
}
else
{
settingsPack.set_int(lt::settings_pack::active_downloads, -1);
settingsPack.set_int(lt::settings_pack::active_seeds, -1);
settingsPack.set_int(lt::settings_pack::active_limit, -1);
}
settingsPack.set_int(lt::settings_pack::active_tracker_limit, -1);
settingsPack.set_int(lt::settings_pack::active_dht_limit, -1);
settingsPack.set_int(lt::settings_pack::active_lsd_limit, -1);
settingsPack.set_int(lt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
// Outgoing ports
settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
settingsPack.set_int(lt::settings_pack::num_outgoing_ports, (outgoingPortsMax() - outgoingPortsMin()));
// UPnP lease duration
settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
// Type of service
settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
// Include overhead in transfer limits
settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
// IP address to announce to trackers
settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
// Max concurrent HTTP announces
settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
// Stop tracker timeout
settingsPack.set_int(lt::settings_pack::stop_tracker_timeout, stopTrackerTimeout());
// * Max connections limit
settingsPack.set_int(lt::settings_pack::connections_limit, maxConnections());
// * Global max upload slots
settingsPack.set_int(lt::settings_pack::unchoke_slots_limit, maxUploads());
// uTP
switch (btProtocol())
{
case BTProtocol::Both:
default:
settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
break;
case BTProtocol::TCP:
settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, false);
settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
break;
case BTProtocol::UTP:
settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, false);
settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, false);
settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
break;
}
switch (utpMixedMode())
{
case MixedModeAlgorithm::TCP:
default:
settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::prefer_tcp);
break;
case MixedModeAlgorithm::Proportional:
settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::peer_proportional);
break;
}
settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
settingsPack.set_bool(lt::settings_pack::ssrf_mitigation, isSSRFMitigationEnabled());
settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
settingsPack.set_bool(lt::settings_pack::apply_ip_filter_to_trackers, isTrackerFilteringEnabled());
settingsPack.set_bool(lt::settings_pack::enable_dht, isDHTEnabled());
if (isDHTEnabled())
settingsPack.set_str(lt::settings_pack::dht_bootstrap_nodes, "dht.libtorrent.org:25401,router.bittorrent.com:6881,router.utorrent.com:6881,dht.transmissionbt.com:6881,dht.aelitis.com:6881");
settingsPack.set_bool(lt::settings_pack::enable_lsd, isLSDEnabled());
switch (chokingAlgorithm())
{
case ChokingAlgorithm::FixedSlots:
default:
settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::fixed_slots_choker);
break;
case ChokingAlgorithm::RateBased:
settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::rate_based_choker);
break;
}
switch (seedChokingAlgorithm())
{
case SeedChokingAlgorithm::RoundRobin:
settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::round_robin);
break;
case SeedChokingAlgorithm::FastestUpload:
default:
settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::fastest_upload);
break;
case SeedChokingAlgorithm::AntiLeech:
settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
break;
}
return settingsPack;
}
void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const
{
if (m_listenInterfaceConfigured)
return;
if (port() > 0) // user has specified port number
settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);
QStringList endpoints;
QStringList outgoingInterfaces;
const QString portString = u':' + QString::number(port());
for (const QString &ip : asConst(getListeningIPs()))
{
const QHostAddress addr {ip};
if (!addr.isNull())
{
const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
const QString ip = isIPv6
? Utils::Net::canonicalIPv6Addr(addr).toString()
: addr.toString();
endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString);
if ((ip != u"0.0.0.0") && (ip != u"::"))
outgoingInterfaces << ip;
}
else
{
// ip holds an interface name
#ifdef Q_OS_WIN
// On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
// the interface's LUID and not the GUID.
// Libtorrent expects GUIDs for the 'listen_interfaces' setting.
const QString guid = convertIfaceNameToGuid(ip);
if (!guid.isEmpty())
{
endpoints << (guid + portString);
outgoingInterfaces << guid;
}
else
{
LogMsg(tr("Could not find GUID of network interface. Interface: \"%1\"").arg(ip), Log::WARNING);
// Since we can't get the GUID, we'll pass the interface name instead.
// Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
endpoints << (ip + portString);
outgoingInterfaces << ip;
}
#else
endpoints << (ip + portString);
outgoingInterfaces << ip;
#endif
}
}
const QString finalEndpoints = endpoints.join(u',');
settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
LogMsg(tr("Trying to listen on the following list of IP addresses: \"%1\"").arg(finalEndpoints));
settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(u',').toStdString());
m_listenInterfaceConfigured = true;
}
void SessionImpl::configurePeerClasses()
{
lt::ip_filter f;
// lt::make_address("255.255.255.255") crashes on some people's systems
// so instead we use address_v4::broadcast()
// Proactively do the same for 0.0.0.0 and address_v4::any()
f.add_rule(lt::address_v4::any()
, lt::address_v4::broadcast()
, 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
// IPv6 may not be available on OS and the parsing
// would result in an exception -> abnormal program termination
// Affects Windows XP
try
{
f.add_rule(lt::address_v6::any()
, lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
, 1 << LT::toUnderlyingType(lt::session::global_peer_class_id));
}
catch (const std::exception &) {}
if (ignoreLimitsOnLAN())
{
// local networks
f.add_rule(lt::make_address("10.0.0.0")
, lt::make_address("10.255.255.255")
, 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
f.add_rule(lt::make_address("172.16.0.0")
, lt::make_address("172.31.255.255")
, 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
f.add_rule(lt::make_address("192.168.0.0")
, lt::make_address("192.168.255.255")
, 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
// link local
f.add_rule(lt::make_address("169.254.0.0")
, lt::make_address("169.254.255.255")
, 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
// loopback
f.add_rule(lt::make_address("127.0.0.0")
, lt::make_address("127.255.255.255")
, 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
// IPv6 may not be available on OS and the parsing
// would result in an exception -> abnormal program termination
// Affects Windows XP
try
{
// link local
f.add_rule(lt::make_address("fe80::")
, lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
, 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
// unique local addresses
f.add_rule(lt::make_address("fc00::")
, lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
, 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
// loopback
f.add_rule(lt::address_v6::loopback()
, lt::address_v6::loopback()
, 1 << LT::toUnderlyingType(lt::session::local_peer_class_id));
}
catch (const std::exception &) {}
}
m_nativeSession->set_peer_class_filter(f);
lt::peer_class_type_filter peerClassTypeFilter;
peerClassTypeFilter.add(lt::peer_class_type_filter::tcp_socket, lt::session::tcp_peer_class_id);
peerClassTypeFilter.add(lt::peer_class_type_filter::ssl_tcp_socket, lt::session::tcp_peer_class_id);
peerClassTypeFilter.add(lt::peer_class_type_filter::i2p_socket, lt::session::tcp_peer_class_id);
if (!isUTPRateLimited())
{
peerClassTypeFilter.disallow(lt::peer_class_type_filter::utp_socket
, lt::session::global_peer_class_id);
peerClassTypeFilter.disallow(lt::peer_class_type_filter::ssl_utp_socket
, lt::session::global_peer_class_id);
}
m_nativeSession->set_peer_class_type_filter(peerClassTypeFilter);
}
void SessionImpl::enableTracker(const bool enable)
{
const QString profile = u"embeddedTracker"_qs;
auto *portForwarder = Net::PortForwarder::instance();
if (enable)
{
if (!m_tracker)
m_tracker = new Tracker(this);
m_tracker->start();
const auto *pref = Preferences::instance();
if (pref->isTrackerPortForwardingEnabled())
portForwarder->setPorts(profile, {static_cast<quint16>(pref->getTrackerPort())});
else
portForwarder->removePorts(profile);
}
else
{
delete m_tracker;
portForwarder->removePorts(profile);
}
}
void SessionImpl::enableBandwidthScheduler()
{
if (!m_bwScheduler)
{
m_bwScheduler = new BandwidthScheduler(this);
connect(m_bwScheduler.data(), &BandwidthScheduler::bandwidthLimitRequested
, this, &SessionImpl::setAltGlobalSpeedLimitEnabled);
}
m_bwScheduler->start();
}
void SessionImpl::populateAdditionalTrackers()
{
m_additionalTrackerList.clear();
const QString trackers = additionalTrackers();
for (QStringView tracker : asConst(QStringView(trackers).split(u'\n')))
{
tracker = tracker.trimmed();
if (!tracker.isEmpty())
m_additionalTrackerList.append({tracker.toString()});
}
}
void SessionImpl::processShareLimits()
{
qDebug("Processing share limits...");
// We shouldn't iterate over `m_torrents` in the loop below
// since `deleteTorrent()` modifies it indirectly
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
for (TorrentImpl *const torrent : torrents)
{
if (torrent->isSeed() && !torrent->isForced())
{
if (torrent->ratioLimit() != Torrent::NO_RATIO_LIMIT)
{
const qreal ratio = torrent->realRatio();
qreal ratioLimit = torrent->ratioLimit();
if (ratioLimit == Torrent::USE_GLOBAL_RATIO)
// If Global Max Ratio is really set...
ratioLimit = globalMaxRatio();
if (ratioLimit >= 0)
{
qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
if ((ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
{
const QString description = tr("Torrent reached the share ratio limit.");
const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
if (m_maxRatioAction == Remove)
{
LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Removed torrent."), torrentName));
deleteTorrent(torrent->id());
}
else if (m_maxRatioAction == DeleteFiles)
{
LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Removed torrent and deleted its content."), torrentName));
deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
}
else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
{
torrent->pause();
LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Torrent paused."), torrentName));
}
else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
{
torrent->setSuperSeeding(true);
LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Super seeding enabled."), torrentName));
}
continue;
}
}
}
if (torrent->seedingTimeLimit() != Torrent::NO_SEEDING_TIME_LIMIT)
{
const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
int seedingTimeLimit = torrent->seedingTimeLimit();
if (seedingTimeLimit == Torrent::USE_GLOBAL_SEEDING_TIME)
{
// If Global Seeding Time Limit is really set...
seedingTimeLimit = globalMaxSeedingMinutes();
}
if (seedingTimeLimit >= 0)
{
if ((seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
{
const QString description = tr("Torrent reached the seeding time limit.");
const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
if (m_maxRatioAction == Remove)
{
LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Removed torrent."), torrentName));
deleteTorrent(torrent->id());
}
else if (m_maxRatioAction == DeleteFiles)
{
LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Removed torrent and deleted its content."), torrentName));
deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
}
else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
{
torrent->pause();
LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Torrent paused."), torrentName));
}
else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
{
torrent->setSuperSeeding(true);
LogMsg(u"%1 %2 %3"_qs.arg(description, tr("Super seeding enabled."), torrentName));
}
}
}
}
}
}
}
// Add to BitTorrent session the downloaded torrent file
void SessionImpl::handleDownloadFinished(const Net::DownloadResult &result)
{
switch (result.status)
{
case Net::DownloadStatus::Success:
emit downloadFromUrlFinished(result.url);
if (const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::load(result.data); loadResult)
addTorrent(loadResult.value(), m_downloadedTorrents.take(result.url));
else
LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(loadResult.error()), Log::WARNING);
break;
case Net::DownloadStatus::RedirectedToMagnet:
emit downloadFromUrlFinished(result.url);
addTorrent(MagnetUri(result.magnet), m_downloadedTorrents.take(result.url));
break;
default:
emit downloadFromUrlFailed(result.url, result.errorString);
}
}
void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
{
TorrentImpl *torrent = m_torrents.value(id);
if (torrent)
{
torrent->fileSearchFinished(savePath, fileNames);
return;
}
const auto loadingTorrentsIter = m_loadingTorrents.find(id);
if (loadingTorrentsIter != m_loadingTorrents.end())
{
LoadTorrentParams &params = loadingTorrentsIter.value();
lt::add_torrent_params &p = params.ltAddTorrentParams;
p.save_path = savePath.toString().toStdString();
const TorrentInfo torrentInfo {*p.ti};
const auto nativeIndexes = torrentInfo.nativeIndexes();
for (int i = 0; i < fileNames.size(); ++i)
p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString();
m_nativeSession->async_add_torrent(p);
}
}
Torrent *SessionImpl::getTorrent(const TorrentID &id) const
{
return m_torrents.value(id);
}
Torrent *SessionImpl::findTorrent(const InfoHash &infoHash) const
{
const auto id = TorrentID::fromInfoHash(infoHash);
if (Torrent *torrent = m_torrents.value(id); torrent)
return torrent;
if (!infoHash.isHybrid())
return m_hybridTorrentsByAltID.value(id);
// alternative ID can be useful to find existing torrent
// in case if hybrid torrent was added by v1 info hash
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
return m_torrents.value(altID);
}
bool SessionImpl::hasActiveTorrents() const
{
return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentImpl *torrent)
{
return TorrentFilter::ActiveTorrent.match(torrent);
});
}
bool SessionImpl::hasUnfinishedTorrents() const
{
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
{
return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata());
});
}
bool SessionImpl::hasRunningSeed() const
{
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
{
return (torrent->isSeed() && !torrent->isPaused());
});
}
void SessionImpl::banIP(const QString &ip)
{
QStringList bannedIPs = m_bannedIPs;
if (!bannedIPs.contains(ip))
{
lt::ip_filter filter = m_nativeSession->get_ip_filter();
lt::error_code ec;
const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
Q_ASSERT(!ec);
if (ec) return;
filter.add_rule(addr, addr, lt::ip_filter::blocked);
m_nativeSession->set_ip_filter(filter);
bannedIPs << ip;
bannedIPs.sort();
m_bannedIPs = bannedIPs;
}
}
// Delete a torrent from the session, given its hash
// and from the disk, if the corresponding deleteOption is chosen
bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
{
TorrentImpl *const torrent = m_torrents.take(id);
if (!torrent) return false;
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
emit torrentAboutToBeRemoved(torrent);
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
// Remove it from session
if (deleteOption == DeleteTorrent)
{
m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
, [&nativeHandle](const MoveStorageJob &job)
{
return job.torrentHandle == nativeHandle;
});
if (iter != m_moveStorageQueue.end())
{
// We shouldn't actually remove torrent until existing "move storage jobs" are done
torrentQueuePositionBottom(nativeHandle);
nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
nativeHandle.pause();
}
else
{
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
}
}
else
{
m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
if (m_moveStorageQueue.size() > 1)
{
// Delete "move storage job" for the deleted torrent
// (note: we shouldn't delete active job)
const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
, [torrent](const MoveStorageJob &job)
{
return job.torrentHandle == torrent->nativeHandle();
});
if (iter != m_moveStorageQueue.end())
m_moveStorageQueue.erase(iter);
}
m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
}
// Remove it from torrent resume directory
m_resumeDataStorage->remove(torrent->id());
delete torrent;
return true;
}
bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
{
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
if (downloadedMetadataIter == m_downloadedMetadata.end())
return false;
const lt::torrent_handle nativeHandle = m_nativeSession->find_torrent(id);
#ifdef QBT_USES_LIBTORRENT2
const InfoHash infoHash {nativeHandle.info_hashes()};
if (infoHash.isHybrid())
{
// if magnet link was hybrid initially then it is indexed also by v1 info hash
// so we need to remove both entries
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
m_downloadedMetadata.remove((altID == *downloadedMetadataIter) ? id : altID);
}
#endif
m_downloadedMetadata.erase(downloadedMetadataIter);
--m_extraLimit;
adjustLimits();
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
return true;
}
void SessionImpl::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType
, std::vector<ElementType>
, std::greater<ElementType>> torrentQueue;
// Sort torrents by queue position
for (const TorrentID &id : ids)
{
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent) continue;
if (const int position = torrent->queuePosition(); position >= 0)
torrentQueue.emplace(position, torrent);
}
// Increase torrents queue position (starting with the one in the highest queue position)
while (!torrentQueue.empty())
{
const TorrentImpl *torrent = torrentQueue.top().second;
torrentQueuePositionUp(torrent->nativeHandle());
torrentQueue.pop();
}
m_torrentsQueueChanged = true;
}
void SessionImpl::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType> torrentQueue;
// Sort torrents by queue position
for (const TorrentID &id : ids)
{
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent) continue;
if (const int position = torrent->queuePosition(); position >= 0)
torrentQueue.emplace(position, torrent);
}
// Decrease torrents queue position (starting with the one in the lowest queue position)
while (!torrentQueue.empty())
{
const TorrentImpl *torrent = torrentQueue.top().second;
torrentQueuePositionDown(torrent->nativeHandle());
torrentQueue.pop();
}
for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i)
torrentQueuePositionBottom(m_nativeSession->find_torrent(*i));
m_torrentsQueueChanged = true;
}
void SessionImpl::topTorrentsQueuePos(const QVector<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType> torrentQueue;
// Sort torrents by queue position
for (const TorrentID &id : ids)
{
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent) continue;
if (const int position = torrent->queuePosition(); position >= 0)
torrentQueue.emplace(position, torrent);
}
// Top torrents queue position (starting with the one in the lowest queue position)
while (!torrentQueue.empty())
{
const TorrentImpl *torrent = torrentQueue.top().second;
torrentQueuePositionTop(torrent->nativeHandle());
torrentQueue.pop();
}
m_torrentsQueueChanged = true;
}
void SessionImpl::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType
, std::vector<ElementType>
, std::greater<ElementType>> torrentQueue;
// Sort torrents by queue position
for (const TorrentID &id : ids)
{
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent) continue;
if (const int position = torrent->queuePosition(); position >= 0)
torrentQueue.emplace(position, torrent);
}
// Bottom torrents queue position (starting with the one in the highest queue position)
while (!torrentQueue.empty())
{
const TorrentImpl *torrent = torrentQueue.top().second;
torrentQueuePositionBottom(torrent->nativeHandle());
torrentQueue.pop();
}
for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i)
torrentQueuePositionBottom(m_nativeSession->find_torrent(*i));
m_torrentsQueueChanged = true;
}
void SessionImpl::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
{
if (m_needSaveResumeDataTorrents.empty())
{
QMetaObject::invokeMethod(this, [this]()
{
for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
{
TorrentImpl *torrent = m_torrents.value(torrentID);
if (torrent)
torrent->saveResumeData();
}
m_needSaveResumeDataTorrents.clear();
}, Qt::QueuedConnection);
}
m_needSaveResumeDataTorrents.insert(torrent->id());
}
void SessionImpl::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
{
qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
++m_numResumeData;
}
void SessionImpl::handleTorrentSaveResumeDataFailed(const TorrentImpl *torrent)
{
Q_UNUSED(torrent);
--m_numResumeData;
}
QVector<Torrent *> SessionImpl::torrents() const
{
QVector<Torrent *> result;
result.reserve(m_torrents.size());
for (TorrentImpl *torrent : asConst(m_torrents))
result << torrent;
return result;
}
qsizetype SessionImpl::torrentsCount() const
{
return m_torrents.size();
}
bool SessionImpl::addTorrent(const QString &source, const AddTorrentParams &params)
{
// `source`: .torrent file path/url or magnet uri
if (!isRestored())
return false;
if (Net::DownloadManager::hasSupportedScheme(source))
{
LogMsg(tr("Downloading torrent, please wait... Source: \"%1\"").arg(source));
// Launch downloader
Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE)
, this, &SessionImpl::handleDownloadFinished);
m_downloadedTorrents[source] = params;
return true;
}
const MagnetUri magnetUri {source};
if (magnetUri.isValid())
return addTorrent(magnetUri, params);
const Path path {source};
TorrentFileGuard guard {path};
const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(path);
if (!loadResult)
{
LogMsg(tr("Failed to load torrent. Source: \"%1\". Reason: \"%2\"").arg(source, loadResult.error()), Log::WARNING);
return false;
}
guard.markAsAddedToSession();
return addTorrent(loadResult.value(), params);
}
bool SessionImpl::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &params)
{
if (!isRestored())
return false;
if (!magnetUri.isValid())
return false;
return addTorrent_impl(magnetUri, params);
}
bool SessionImpl::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams &params)
{
if (!isRestored())
return false;
return addTorrent_impl(torrentInfo, params);
}
LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &addTorrentParams)
{
LoadTorrentParams loadTorrentParams;
loadTorrentParams.name = addTorrentParams.name;
loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(!isAutoTMMDisabledByDefault());
loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
loadTorrentParams.hasSeedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged);
loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused());
loadTorrentParams.stopCondition = addTorrentParams.stopCondition.value_or(torrentStopCondition());
loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
const QString category = addTorrentParams.category;
if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
loadTorrentParams.category = u""_qs;
else
loadTorrentParams.category = category;
if (!loadTorrentParams.useAutoTMM)
{
if (addTorrentParams.savePath.isAbsolute())
{
loadTorrentParams.savePath = addTorrentParams.savePath;
}
else
{
const Path basePath = useCategoryPathsInManualMode() ? categorySavePath(loadTorrentParams.category) : savePath();
loadTorrentParams.savePath = basePath / addTorrentParams.savePath;
}
const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(isDownloadPathEnabled());
if (useDownloadPath)
{
if (addTorrentParams.downloadPath.isAbsolute())
{
loadTorrentParams.downloadPath = addTorrentParams.downloadPath;
}
else
{
const Path basePath = useCategoryPathsInManualMode() ? categoryDownloadPath(loadTorrentParams.category) : downloadPath();
loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath;
}
}
}
for (const QString &tag : addTorrentParams.tags)
{
if (hasTag(tag) || addTag(tag))
loadTorrentParams.tags.insert(tag);
}
return loadTorrentParams;
}
// Add a torrent to the BitTorrent session
bool SessionImpl::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams)
{
Q_ASSERT(isRestored());
const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
const auto infoHash = (hasMetadata ? std::get<TorrentInfo>(source).infoHash() : std::get<MagnetUri>(source).infoHash());
const auto id = TorrentID::fromInfoHash(infoHash);
// alternative ID can be useful to find existing torrent in case if hybrid torrent was added by v1 info hash
const auto altID = (infoHash.isHybrid() ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
// We should not add the torrent if it is already
// processed or is pending to add to session
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
return false;
if (Torrent *torrent = findTorrent(infoHash); torrent)
{
if (hasMetadata)
{
// Trying to set metadata to existing torrent in case if it has none
torrent->setMetadata(std::get<TorrentInfo>(source));
}
return false;
}
// It looks illogical that we don't just use an existing handle,
// but as previous experience has shown, it actually creates unnecessary
// problems and unwanted behavior due to the fact that it was originally
// added with parameters other than those provided by the user.
cancelDownloadMetadata(id);
if (infoHash.isHybrid())
cancelDownloadMetadata(altID);
LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams);
lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
bool isFindingIncompleteFiles = false;
const bool useAutoTMM = loadTorrentParams.useAutoTMM;
const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
if (hasMetadata)
{
const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
PathList filePaths = addTorrentParams.filePaths;
if (filePaths.isEmpty())
{
filePaths = torrentInfo.filePaths();
if (loadTorrentParams.contentLayout != TorrentContentLayout::Original)
{
const Path originalRootFolder = Path::findRootFolder(filePaths);
const auto originalContentLayout = (originalRootFolder.isEmpty()
? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder);
if (loadTorrentParams.contentLayout != originalContentLayout)
{
if (loadTorrentParams.contentLayout == TorrentContentLayout::NoSubfolder)
Path::stripRootFolder(filePaths);
else
Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
}
}
}
// if torrent name wasn't explicitly set we handle the case of
// initial renaming of torrent content and rename torrent accordingly
if (loadTorrentParams.name.isEmpty())
{
QString contentName = Path::findRootFolder(filePaths).toString();
if (contentName.isEmpty() && (filePaths.size() == 1))
contentName = filePaths.at(0).filename();
if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
loadTorrentParams.name = contentName;
}
if (!loadTorrentParams.hasSeedStatus)
{
const Path actualDownloadPath = useAutoTMM
? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
isFindingIncompleteFiles = true;
}
const auto nativeIndexes = torrentInfo.nativeIndexes();
if (!isFindingIncompleteFiles)
{
for (int index = 0; index < filePaths.size(); ++index)
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
}
Q_ASSERT(p.file_priorities.empty());
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
// Use qBittorrent default priority rather than libtorrent's (4)
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
if (addTorrentParams.filePriorities.isEmpty())
{
if (isExcludedFileNamesEnabled())
{
// Check file name blacklist when priorities are not explicitly set
for (int i = 0; i < filePaths.size(); ++i)
{
if (isFilenameExcluded(filePaths.at(i).filename()))
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = lt::dont_download;
}
}
}
else
{
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
}
p.ti = torrentInfo.nativeInfo();
}
else
{
const MagnetUri &magnetUri = std::get<MagnetUri>(source);
p = magnetUri.addTorrentParams();
if (loadTorrentParams.name.isEmpty() && !p.name.empty())
loadTorrentParams.name = QString::fromStdString(p.name);
}
p.save_path = actualSavePath.toString().toStdString();
if (isAddTrackersEnabled() && !(hasMetadata && p.ti->priv()))
{
p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
p.tracker_tiers.resize(p.trackers.size(), 0);
for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
{
p.trackers.push_back(trackerEntry.url.toStdString());
p.tracker_tiers.push_back(trackerEntry.tier);
}
}
p.upload_limit = addTorrentParams.uploadLimit;
p.download_limit = addTorrentParams.downloadLimit;
// Preallocation mode
p.storage_mode = isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse;
if (addTorrentParams.sequential)
p.flags |= lt::torrent_flags::sequential_download;
else
p.flags &= ~lt::torrent_flags::sequential_download;
// Seeding mode
// Skip checking and directly start seeding
if (addTorrentParams.skipChecking)
p.flags |= lt::torrent_flags::seed_mode;
else
p.flags &= ~lt::torrent_flags::seed_mode;
if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::AutoManaged))
p.flags |= lt::torrent_flags::paused;
else
p.flags &= ~lt::torrent_flags::paused;
if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::Forced))
p.flags &= ~lt::torrent_flags::auto_managed;
else
p.flags |= lt::torrent_flags::auto_managed;
p.flags |= lt::torrent_flags::duplicate_is_error;
// Prevent torrent from saving initial resume data twice
p.flags &= ~lt::torrent_flags::need_save_resume;
p.added_time = std::time(nullptr);
// Limits
p.max_connections = maxConnectionsPerTorrent();
p.max_uploads = maxUploadsPerTorrent();
p.userdata = LTClientData(new ExtensionData);
#ifndef QBT_USES_LIBTORRENT2
p.storage = customStorageConstructor;
#endif
m_loadingTorrents.insert(id, loadTorrentParams);
if (infoHash.isHybrid())
m_hybridTorrentsByAltID.insert(altID, nullptr);
if (!isFindingIncompleteFiles)
m_nativeSession->async_add_torrent(p);
return true;
}
void SessionImpl::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
, const Path &downloadPath, const PathList &filePaths) const
{
Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
QMetaObject::invokeMethod(m_fileSearcher, [=]()
{
m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath, isAppendExtensionEnabled());
});
}
// Add a torrent to libtorrent session in hidden mode
// and force it to download its metadata
bool SessionImpl::downloadMetadata(const MagnetUri &magnetUri)
{
if (!magnetUri.isValid())
return false;
const InfoHash infoHash = magnetUri.infoHash();
// We should not add torrent if it's already
// processed or adding to session
if (isKnownTorrent(infoHash))
return false;
lt::add_torrent_params p = magnetUri.addTorrentParams();
// Flags
// Preallocation mode
if (isPreallocationEnabled())
p.storage_mode = lt::storage_mode_allocate;
else
p.storage_mode = lt::storage_mode_sparse;
// Limits
p.max_connections = maxConnectionsPerTorrent();
p.max_uploads = maxUploadsPerTorrent();
const auto id = TorrentID::fromInfoHash(infoHash);
const Path savePath = Utils::Fs::tempPath() / Path(id.toString());
p.save_path = savePath.toString().toStdString();
// Forced start
p.flags &= ~lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
// Solution to avoid accidental file writes
p.flags |= lt::torrent_flags::upload_mode;
#ifndef QBT_USES_LIBTORRENT2
p.storage = customStorageConstructor;
#endif
// Adding torrent to libtorrent session
lt::error_code ec;
lt::torrent_handle h = m_nativeSession->add_torrent(p, ec);
if (ec) return false;
// waiting for metadata...
m_downloadedMetadata.insert(id);
if (infoHash.isHybrid())
{
// index hybrid magnet links by both v1 and v2 info hashes
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
m_downloadedMetadata.insert(altID);
}
++m_extraLimit;
adjustLimits();
return true;
}
void SessionImpl::exportTorrentFile(const Torrent *torrent, const Path &folderPath)
{
if (!folderPath.exists() && !Utils::Fs::mkpath(folderPath))
return;
const QString validName = Utils::Fs::toValidFileName(torrent->name());
QString torrentExportFilename = u"%1.torrent"_qs.arg(validName);
Path newTorrentPath = folderPath / Path(torrentExportFilename);
int counter = 0;
while (newTorrentPath.exists())
{
// Append number to torrent name to make it unique
torrentExportFilename = u"%1 %2.torrent"_qs.arg(validName).arg(++counter);
newTorrentPath = folderPath / Path(torrentExportFilename);
}
const nonstd::expected<void, QString> result = torrent->exportToFile(newTorrentPath);
if (!result)
{
LogMsg(tr("Failed to export torrent. Torrent: \"%1\". Destination: \"%2\". Reason: \"%3\"")
.arg(torrent->name(), newTorrentPath.toString(), result.error()), Log::WARNING);
}
}
void SessionImpl::generateResumeData()
{
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (!torrent->isValid()) continue;
if (torrent->needSaveResumeData())
{
torrent->saveResumeData();
m_needSaveResumeDataTorrents.remove(torrent->id());
}
}
}
// Called on exit
void SessionImpl::saveResumeData()
{
for (const TorrentImpl *torrent : asConst(m_torrents))
{
torrent->nativeHandle().save_resume_data(lt::torrent_handle::only_if_modified);
++m_numResumeData;
}
// clear queued storage move jobs except the current ongoing one
if (m_moveStorageQueue.size() > 1)
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
m_moveStorageQueue = m_moveStorageQueue.mid(0, 1);
#else
m_moveStorageQueue.resize(1);
#endif
}
QElapsedTimer timer;
timer.start();
while ((m_numResumeData > 0) || !m_moveStorageQueue.isEmpty() || m_needSaveTorrentsQueue)
{
const lt::seconds waitTime {5};
const lt::seconds expireTime {30};
// only terminate when no storage is moving
if (timer.hasExpired(lt::total_milliseconds(expireTime)) && m_moveStorageQueue.isEmpty())
{
LogMsg(tr("Aborted saving resume data. Number of outstanding torrents: %1").arg(QString::number(m_numResumeData))
, Log::CRITICAL);
break;
}
const std::vector<lt::alert *> alerts = getPendingAlerts(waitTime);
bool hasWantedAlert = false;
for (const lt::alert *a : alerts)
{
if (const int alertType = a->type();
(alertType == lt::save_resume_data_alert::alert_type) || (alertType == lt::save_resume_data_failed_alert::alert_type)
|| (alertType == lt::storage_moved_alert::alert_type) || (alertType == lt::storage_moved_failed_alert::alert_type)
|| (alertType == lt::state_update_alert::alert_type))
{
hasWantedAlert = true;
}
handleAlert(a);
}
if (hasWantedAlert)
timer.start();
}
}
void SessionImpl::saveTorrentsQueue()
{
QVector<TorrentID> queue;
for (const TorrentImpl *torrent : asConst(m_torrents))
{
if (const int queuePos = torrent->queuePosition(); queuePos >= 0)
{
if (queuePos >= queue.size())
queue.resize(queuePos + 1);
queue[queuePos] = torrent->id();
}
}
m_resumeDataStorage->storeQueue(queue);
m_needSaveTorrentsQueue = false;
}
void SessionImpl::removeTorrentsQueue()
{
m_resumeDataStorage->storeQueue({});
m_torrentsQueueChanged = false;
m_needSaveTorrentsQueue = false;
}
void SessionImpl::setSavePath(const Path &path)
{
const auto newPath = (path.isAbsolute() ? path : (specialFolderLocation(SpecialFolder::Downloads) / path));
if (newPath == m_savePath)
return;
if (isDisableAutoTMMWhenDefaultSavePathChanged())
{
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
{
const QString &categoryName = it.key();
const CategoryOptions &categoryOptions = it.value();
if (categoryOptions.savePath.isRelative())
affectedCatogories.insert(categoryName);
}
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (affectedCatogories.contains(torrent->category()))
torrent->setAutoTMMEnabled(false);
}
}
m_savePath = newPath;
for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->handleCategoryOptionsChanged();
}
void SessionImpl::setDownloadPath(const Path &path)
{
const Path newPath = (path.isAbsolute() ? path : (savePath() / Path(u"temp"_qs) / path));
if (newPath == m_downloadPath)
return;
if (isDisableAutoTMMWhenDefaultSavePathChanged())
{
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
{
const QString &categoryName = it.key();
const CategoryOptions &categoryOptions = it.value();
const CategoryOptions::DownloadPathOption downloadPathOption =
categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
if (downloadPathOption.enabled && downloadPathOption.path.isRelative())
affectedCatogories.insert(categoryName);
}
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (affectedCatogories.contains(torrent->category()))
torrent->setAutoTMMEnabled(false);
}
}
m_downloadPath = newPath;
for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->handleCategoryOptionsChanged();
}
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
void SessionImpl::networkOnlineStateChanged(const bool online)
{
LogMsg(tr("System network status changed to %1", "e.g: System network status changed to ONLINE").arg(online ? tr("ONLINE") : tr("OFFLINE")), Log::INFO);
}
void SessionImpl::networkConfigurationChange(const QNetworkConfiguration &cfg)
{
const QString configuredInterfaceName = networkInterface();
// Empty means "Any Interface". In this case libtorrent has binded to 0.0.0.0 so any change to any interface will
// be automatically picked up. Otherwise we would rebinding here to 0.0.0.0 again.
if (configuredInterfaceName.isEmpty()) return;
const QString changedInterface = cfg.name();
if (configuredInterfaceName == changedInterface)
{
LogMsg(tr("Network configuration of %1 has changed, refreshing session binding", "e.g: Network configuration of tun0 has changed, refreshing session binding").arg(changedInterface), Log::INFO);
configureListeningInterface();
}
}
#endif
QStringList SessionImpl::getListeningIPs() const
{
QStringList IPs;
const QString ifaceName = networkInterface();
const QString ifaceAddr = networkInterfaceAddress();
const QHostAddress configuredAddr(ifaceAddr);
const bool allIPv4 = (ifaceAddr == u"0.0.0.0"); // Means All IPv4 addresses
const bool allIPv6 = (ifaceAddr == u"::"); // Means All IPv6 addresses
if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull())
{
LogMsg(tr("The configured network address is invalid. Address: \"%1\"").arg(ifaceAddr), Log::CRITICAL);
// Pass the invalid user configured interface name/address to libtorrent
// in hopes that it will come online later.
// This will not cause IP leak but allow user to reconnect the interface
// and re-establish connection without restarting the client.
IPs.append(ifaceAddr);
return IPs;
}
if (ifaceName.isEmpty())
{
if (ifaceAddr.isEmpty())
return {u"0.0.0.0"_qs, u"::"_qs}; // Indicates all interfaces + all addresses (aka default)
if (allIPv4)
return {u"0.0.0.0"_qs};
if (allIPv6)
return {u"::"_qs};
}
const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
{
if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
|| (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
return;
if ((match == addr) || allIPv4 || allIPv6)
IPs.append(addr.toString());
};
if (ifaceName.isEmpty())
{
const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
for (const auto &addr : addresses)
checkAndAddIP(addr, configuredAddr);
// At this point ifaceAddr was non-empty
// If IPs.isEmpty() it means the configured Address was not found
if (IPs.isEmpty())
{
LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
.arg(ifaceAddr), Log::CRITICAL);
IPs.append(ifaceAddr);
}
return IPs;
}
// Attempt to listen on provided interface
const QNetworkInterface networkIFace = QNetworkInterface::interfaceFromName(ifaceName);
if (!networkIFace.isValid())
{
qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName));
LogMsg(tr("The configured network interface is invalid. Interface: \"%1\"").arg(ifaceName), Log::CRITICAL);
IPs.append(ifaceName);
return IPs;
}
if (ifaceAddr.isEmpty())
{
IPs.append(ifaceName);
return IPs; // On Windows calling code converts it to GUID
}
const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
qDebug() << "This network interface has " << addresses.size() << " IP addresses";
for (const QNetworkAddressEntry &entry : addresses)
checkAndAddIP(entry.ip(), configuredAddr);
// Make sure there is at least one IP
// At this point there was an explicit interface and an explicit address set
// and the address should have been found
if (IPs.isEmpty())
{
LogMsg(tr("Failed to find the configured network address to listen on. Address: \"%1\"")
.arg(ifaceAddr), Log::CRITICAL);
IPs.append(ifaceAddr);
}
return IPs;
}
// Set the ports range in which is chosen the port
// the BitTorrent session will listen to
void SessionImpl::configureListeningInterface()
{
m_listenInterfaceConfigured = false;
configureDeferred();
}
int SessionImpl::globalDownloadSpeedLimit() const
{
// Unfortunately the value was saved as KiB instead of B.
// But it is better to pass it around internally(+ webui) as Bytes.
return m_globalDownloadSpeedLimit * 1024;
}
void SessionImpl::setGlobalDownloadSpeedLimit(const int limit)
{
// Unfortunately the value was saved as KiB instead of B.
// But it is better to pass it around internally(+ webui) as Bytes.
if (limit == globalDownloadSpeedLimit())
return;
if (limit <= 0)
m_globalDownloadSpeedLimit = 0;
else if (limit <= 1024)
m_globalDownloadSpeedLimit = 1;
else
m_globalDownloadSpeedLimit = (limit / 1024);
if (!isAltGlobalSpeedLimitEnabled())
configureDeferred();
}
int SessionImpl::globalUploadSpeedLimit() const
{
// Unfortunately the value was saved as KiB instead of B.
// But it is better to pass it around internally(+ webui) as Bytes.
return m_globalUploadSpeedLimit * 1024;
}
void SessionImpl::setGlobalUploadSpeedLimit(const int limit)
{
// Unfortunately the value was saved as KiB instead of B.
// But it is better to pass it around internally(+ webui) as Bytes.
if (limit == globalUploadSpeedLimit())
return;
if (limit <= 0)
m_globalUploadSpeedLimit = 0;
else if (limit <= 1024)
m_globalUploadSpeedLimit = 1;
else
m_globalUploadSpeedLimit = (limit / 1024);
if (!isAltGlobalSpeedLimitEnabled())
configureDeferred();
}
int SessionImpl::altGlobalDownloadSpeedLimit() const
{
// Unfortunately the value was saved as KiB instead of B.
// But it is better to pass it around internally(+ webui) as Bytes.
return m_altGlobalDownloadSpeedLimit * 1024;
}
void SessionImpl::setAltGlobalDownloadSpeedLimit(const int limit)
{
// Unfortunately the value was saved as KiB instead of B.
// But it is better to pass it around internally(+ webui) as Bytes.
if (limit == altGlobalDownloadSpeedLimit())
return;
if (limit <= 0)
m_altGlobalDownloadSpeedLimit = 0;
else if (limit <= 1024)
m_altGlobalDownloadSpeedLimit = 1;
else
m_altGlobalDownloadSpeedLimit = (limit / 1024);
if (isAltGlobalSpeedLimitEnabled())
configureDeferred();
}
int SessionImpl::altGlobalUploadSpeedLimit() const
{
// Unfortunately the value was saved as KiB instead of B.
// But it is better to pass it around internally(+ webui) as Bytes.
return m_altGlobalUploadSpeedLimit * 1024;
}
void SessionImpl::setAltGlobalUploadSpeedLimit(const int limit)
{
// Unfortunately the value was saved as KiB instead of B.
// But it is better to pass it around internally(+ webui) as Bytes.
if (limit == altGlobalUploadSpeedLimit())
return;
if (limit <= 0)
m_altGlobalUploadSpeedLimit = 0;
else if (limit <= 1024)
m_altGlobalUploadSpeedLimit = 1;
else
m_altGlobalUploadSpeedLimit = (limit / 1024);
if (isAltGlobalSpeedLimitEnabled())
configureDeferred();
}
int SessionImpl::downloadSpeedLimit() const
{
return isAltGlobalSpeedLimitEnabled()
? altGlobalDownloadSpeedLimit()
: globalDownloadSpeedLimit();
}
void SessionImpl::setDownloadSpeedLimit(const int limit)
{
if (isAltGlobalSpeedLimitEnabled())
setAltGlobalDownloadSpeedLimit(limit);
else
setGlobalDownloadSpeedLimit(limit);
}
int SessionImpl::uploadSpeedLimit() const
{
return isAltGlobalSpeedLimitEnabled()
? altGlobalUploadSpeedLimit()
: globalUploadSpeedLimit();
}
void SessionImpl::setUploadSpeedLimit(const int limit)
{
if (isAltGlobalSpeedLimitEnabled())
setAltGlobalUploadSpeedLimit(limit);
else
setGlobalUploadSpeedLimit(limit);
}
bool SessionImpl::isAltGlobalSpeedLimitEnabled() const
{
return m_isAltGlobalSpeedLimitEnabled;
}
void SessionImpl::setAltGlobalSpeedLimitEnabled(const bool enabled)
{
if (enabled == isAltGlobalSpeedLimitEnabled()) return;
// Save new state to remember it on startup
m_isAltGlobalSpeedLimitEnabled = enabled;
applyBandwidthLimits();
// Notify
emit speedLimitModeChanged(m_isAltGlobalSpeedLimitEnabled);
}
bool SessionImpl::isBandwidthSchedulerEnabled() const
{
return m_isBandwidthSchedulerEnabled;
}
void SessionImpl::setBandwidthSchedulerEnabled(const bool enabled)
{
if (enabled != isBandwidthSchedulerEnabled())
{
m_isBandwidthSchedulerEnabled = enabled;
if (enabled)
enableBandwidthScheduler();
else
delete m_bwScheduler;
}
}
bool SessionImpl::isPerformanceWarningEnabled() const
{
return m_isPerformanceWarningEnabled;
}
void SessionImpl::setPerformanceWarningEnabled(const bool enable)
{
if (enable == m_isPerformanceWarningEnabled)
return;
m_isPerformanceWarningEnabled = enable;
configureDeferred();
}
int SessionImpl::saveResumeDataInterval() const
{
return m_saveResumeDataInterval;
}
void SessionImpl::setSaveResumeDataInterval(const int value)
{
if (value == m_saveResumeDataInterval)
return;
m_saveResumeDataInterval = value;
if (value > 0)
{
m_resumeDataTimer->setInterval(std::chrono::minutes(value));
m_resumeDataTimer->start();
}
else
{
m_resumeDataTimer->stop();
}
}
int SessionImpl::port() const
{
return m_port;
}
void SessionImpl::setPort(const int port)
{
if (port != m_port)
{
m_port = port;
configureListeningInterface();
if (isReannounceWhenAddressChangedEnabled())
reannounceToAllTrackers();
}
}
QString SessionImpl::networkInterface() const
{
return m_networkInterface;
}
void SessionImpl::setNetworkInterface(const QString &iface)
{
if (iface != networkInterface())
{
m_networkInterface = iface;
configureListeningInterface();
}
}
QString SessionImpl::networkInterfaceName() const
{
return m_networkInterfaceName;
}
void SessionImpl::setNetworkInterfaceName(const QString &name)
{
m_networkInterfaceName = name;
}
QString SessionImpl::networkInterfaceAddress() const
{
return m_networkInterfaceAddress;
}
void SessionImpl::setNetworkInterfaceAddress(const QString &address)
{
if (address != networkInterfaceAddress())
{
m_networkInterfaceAddress = address;
configureListeningInterface();
}
}
int SessionImpl::encryption() const
{
return m_encryption;
}
void SessionImpl::setEncryption(const int state)
{
if (state != encryption())
{
m_encryption = state;
configureDeferred();
LogMsg(tr("Encryption support: %1").arg(
state == 0 ? tr("ON") : ((state == 1) ? tr("FORCED") : tr("OFF")))
, Log::INFO);
}
}
int SessionImpl::maxActiveCheckingTorrents() const
{
return m_maxActiveCheckingTorrents;
}
void SessionImpl::setMaxActiveCheckingTorrents(const int val)
{
if (val == m_maxActiveCheckingTorrents)
return;
m_maxActiveCheckingTorrents = val;
configureDeferred();
}
bool SessionImpl::isProxyPeerConnectionsEnabled() const
{
return m_isProxyPeerConnectionsEnabled;
}
void SessionImpl::setProxyPeerConnectionsEnabled(const bool enabled)
{
if (enabled != isProxyPeerConnectionsEnabled())
{
m_isProxyPeerConnectionsEnabled = enabled;
configureDeferred();
}
}
bool SessionImpl::isProxyHostnameLookupEnabled() const
{
return m_isProxyHostnameLookupEnabled;
}
void SessionImpl::setProxyHostnameLookupEnabled(const bool enabled)
{
if (enabled != isProxyHostnameLookupEnabled())
{
m_isProxyHostnameLookupEnabled = enabled;
configureDeferred();
}
}
ChokingAlgorithm SessionImpl::chokingAlgorithm() const
{
return m_chokingAlgorithm;
}
void SessionImpl::setChokingAlgorithm(const ChokingAlgorithm mode)
{
if (mode == m_chokingAlgorithm) return;
m_chokingAlgorithm = mode;
configureDeferred();
}
SeedChokingAlgorithm SessionImpl::seedChokingAlgorithm() const
{
return m_seedChokingAlgorithm;
}
void SessionImpl::setSeedChokingAlgorithm(const SeedChokingAlgorithm mode)
{
if (mode == m_seedChokingAlgorithm) return;
m_seedChokingAlgorithm = mode;
configureDeferred();
}
bool SessionImpl::isAddTrackersEnabled() const
{
return m_isAddTrackersEnabled;
}
void SessionImpl::setAddTrackersEnabled(const bool enabled)
{
m_isAddTrackersEnabled = enabled;
}
QString SessionImpl::additionalTrackers() const
{
return m_additionalTrackers;
}
void SessionImpl::setAdditionalTrackers(const QString &trackers)
{
if (trackers != additionalTrackers())
{
m_additionalTrackers = trackers;
populateAdditionalTrackers();
}
}
bool SessionImpl::isIPFilteringEnabled() const
{
return m_isIPFilteringEnabled;
}
void SessionImpl::setIPFilteringEnabled(const bool enabled)
{
if (enabled != m_isIPFilteringEnabled)
{
m_isIPFilteringEnabled = enabled;
m_IPFilteringConfigured = false;
configureDeferred();
}
}
Path SessionImpl::IPFilterFile() const
{
return m_IPFilterFile;
}
void SessionImpl::setIPFilterFile(const Path &path)
{
if (path != IPFilterFile())
{
m_IPFilterFile = path;
m_IPFilteringConfigured = false;
configureDeferred();
}
}
bool SessionImpl::isExcludedFileNamesEnabled() const
{
return m_isExcludedFileNamesEnabled;
}
void SessionImpl::setExcludedFileNamesEnabled(const bool enabled)
{
if (m_isExcludedFileNamesEnabled == enabled)
return;
m_isExcludedFileNamesEnabled = enabled;
if (enabled)
populateExcludedFileNamesRegExpList();
else
m_excludedFileNamesRegExpList.clear();
}
QStringList SessionImpl::excludedFileNames() const
{
return m_excludedFileNames;
}
void SessionImpl::setExcludedFileNames(const QStringList &excludedFileNames)
{
if (excludedFileNames != m_excludedFileNames)
{
m_excludedFileNames = excludedFileNames;
populateExcludedFileNamesRegExpList();
}
}
void SessionImpl::populateExcludedFileNamesRegExpList()
{
const QStringList excludedNames = excludedFileNames();
m_excludedFileNamesRegExpList.clear();
m_excludedFileNamesRegExpList.reserve(excludedNames.size());
for (const QString &str : excludedNames)
{
const QString pattern = QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(str));
const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
m_excludedFileNamesRegExpList.append(re);
}
}
bool SessionImpl::isFilenameExcluded(const QString &fileName) const
{
if (!isExcludedFileNamesEnabled())
return false;
return std::any_of(m_excludedFileNamesRegExpList.begin(), m_excludedFileNamesRegExpList.end(), [&fileName](const QRegularExpression &re)
{
return re.match(fileName).hasMatch();
});
}
void SessionImpl::setBannedIPs(const QStringList &newList)
{
if (newList == m_bannedIPs)
return; // do nothing
// here filter out incorrect IP
QStringList filteredList;
for (const QString &ip : newList)
{
if (Utils::Net::isValidIP(ip))
{
// the same IPv6 addresses could be written in different forms;
// QHostAddress::toString() result format follows RFC5952;
// thus we avoid duplicate entries pointing to the same address
filteredList << QHostAddress(ip).toString();
}
else
{
LogMsg(tr("Rejected invalid IP address while applying the list of banned IP addresses. IP: \"%1\"")
.arg(ip)
, Log::WARNING);
}
}
// now we have to sort IPs and make them unique
filteredList.sort();
filteredList.removeDuplicates();
// Again ensure that the new list is different from the stored one.
if (filteredList == m_bannedIPs)
return; // do nothing
// store to session settings
// also here we have to recreate filter list including 3rd party ban file
// and install it again into m_session
m_bannedIPs = filteredList;
m_IPFilteringConfigured = false;
configureDeferred();
}
ResumeDataStorageType SessionImpl::resumeDataStorageType() const
{
return m_resumeDataStorageType;
}
void SessionImpl::setResumeDataStorageType(const ResumeDataStorageType type)
{
m_resumeDataStorageType = type;
}
QStringList SessionImpl::bannedIPs() const
{
return m_bannedIPs;
}
bool SessionImpl::isRestored() const
{
return m_isRestored;
}
int SessionImpl::maxConnectionsPerTorrent() const
{
return m_maxConnectionsPerTorrent;
}
void SessionImpl::setMaxConnectionsPerTorrent(int max)
{
max = (max > 0) ? max : -1;
if (max != maxConnectionsPerTorrent())
{
m_maxConnectionsPerTorrent = max;
// Apply this to all session torrents
for (const lt::torrent_handle &handle : m_nativeSession->get_torrents())
{
try
{
handle.set_max_connections(max);
}
catch (const std::exception &) {}
}
}
}
int SessionImpl::maxUploadsPerTorrent() const
{
return m_maxUploadsPerTorrent;
}
void SessionImpl::setMaxUploadsPerTorrent(int max)
{
max = (max > 0) ? max : -1;
if (max != maxUploadsPerTorrent())
{
m_maxUploadsPerTorrent = max;
// Apply this to all session torrents
for (const lt::torrent_handle &handle : m_nativeSession->get_torrents())
{
try
{
handle.set_max_uploads(max);
}
catch (const std::exception &) {}
}
}
}
bool SessionImpl::announceToAllTrackers() const
{
return m_announceToAllTrackers;
}
void SessionImpl::setAnnounceToAllTrackers(const bool val)
{
if (val != m_announceToAllTrackers)
{
m_announceToAllTrackers = val;
configureDeferred();
}
}
bool SessionImpl::announceToAllTiers() const
{
return m_announceToAllTiers;
}
void SessionImpl::setAnnounceToAllTiers(const bool val)
{
if (val != m_announceToAllTiers)
{
m_announceToAllTiers = val;
configureDeferred();
}
}
int SessionImpl::peerTurnover() const
{
return m_peerTurnover;
}
void SessionImpl::setPeerTurnover(const int val)
{
if (val == m_peerTurnover)
return;
m_peerTurnover = val;
configureDeferred();
}
int SessionImpl::peerTurnoverCutoff() const
{
return m_peerTurnoverCutoff;
}
void SessionImpl::setPeerTurnoverCutoff(const int val)
{
if (val == m_peerTurnoverCutoff)
return;
m_peerTurnoverCutoff = val;
configureDeferred();
}
int SessionImpl::peerTurnoverInterval() const
{
return m_peerTurnoverInterval;
}
void SessionImpl::setPeerTurnoverInterval(const int val)
{
if (val == m_peerTurnoverInterval)
return;
m_peerTurnoverInterval = val;
configureDeferred();
}
DiskIOType SessionImpl::diskIOType() const
{
return m_diskIOType;
}
void SessionImpl::setDiskIOType(const DiskIOType type)
{
if (type != m_diskIOType)
{
m_diskIOType = type;
}
}
int SessionImpl::requestQueueSize() const
{
return m_requestQueueSize;
}
void SessionImpl::setRequestQueueSize(const int val)
{
if (val == m_requestQueueSize)
return;
m_requestQueueSize = val;
configureDeferred();
}
int SessionImpl::asyncIOThreads() const
{
return std::clamp(m_asyncIOThreads.get(), 1, 1024);
}
void SessionImpl::setAsyncIOThreads(const int num)
{
if (num == m_asyncIOThreads)
return;
m_asyncIOThreads = num;
configureDeferred();
}
int SessionImpl::hashingThreads() const
{
return std::clamp(m_hashingThreads.get(), 1, 1024);
}
void SessionImpl::setHashingThreads(const int num)
{
if (num == m_hashingThreads)
return;
m_hashingThreads = num;
configureDeferred();
}
int SessionImpl::filePoolSize() const
{
return m_filePoolSize;
}
void SessionImpl::setFilePoolSize(const int size)
{
if (size == m_filePoolSize)
return;
m_filePoolSize = size;
configureDeferred();
}
int SessionImpl::checkingMemUsage() const
{
return std::max(1, m_checkingMemUsage.get());
}
void SessionImpl::setCheckingMemUsage(int size)
{
size = std::max(size, 1);
if (size == m_checkingMemUsage)
return;
m_checkingMemUsage = size;
configureDeferred();
}
int SessionImpl::diskCacheSize() const
{
#ifdef QBT_APP_64BIT
return std::min(m_diskCacheSize.get(), 33554431); // 32768GiB
#else
// When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
// allocate 1536MiB and leave 512MiB to the rest of program data in RAM
return std::min(m_diskCacheSize.get(), 1536);
#endif
}
void SessionImpl::setDiskCacheSize(int size)
{
#ifdef QBT_APP_64BIT
size = std::min(size, 33554431); // 32768GiB
#else
// allocate 1536MiB and leave 512MiB to the rest of program data in RAM
size = std::min(size, 1536);
#endif
if (size != m_diskCacheSize)
{
m_diskCacheSize = size;
configureDeferred();
}
}
int SessionImpl::diskCacheTTL() const
{
return m_diskCacheTTL;
}
void SessionImpl::setDiskCacheTTL(const int ttl)
{
if (ttl != m_diskCacheTTL)
{
m_diskCacheTTL = ttl;
configureDeferred();
}
}
qint64 SessionImpl::diskQueueSize() const
{
return m_diskQueueSize;
}
void SessionImpl::setDiskQueueSize(const qint64 size)
{
if (size == m_diskQueueSize)
return;
m_diskQueueSize = size;
configureDeferred();
}
DiskIOReadMode SessionImpl::diskIOReadMode() const
{
return m_diskIOReadMode;
}
void SessionImpl::setDiskIOReadMode(const DiskIOReadMode mode)
{
if (mode == m_diskIOReadMode)
return;
m_diskIOReadMode = mode;
configureDeferred();
}
DiskIOWriteMode SessionImpl::diskIOWriteMode() const
{
return m_diskIOWriteMode;
}
void SessionImpl::setDiskIOWriteMode(const DiskIOWriteMode mode)
{
if (mode == m_diskIOWriteMode)
return;
m_diskIOWriteMode = mode;
configureDeferred();
}
bool SessionImpl::isCoalesceReadWriteEnabled() const
{
return m_coalesceReadWriteEnabled;
}
void SessionImpl::setCoalesceReadWriteEnabled(const bool enabled)
{
if (enabled == m_coalesceReadWriteEnabled) return;
m_coalesceReadWriteEnabled = enabled;
configureDeferred();
}
bool SessionImpl::isSuggestModeEnabled() const
{
return m_isSuggestMode;
}
bool SessionImpl::usePieceExtentAffinity() const
{
return m_usePieceExtentAffinity;
}
void SessionImpl::setPieceExtentAffinity(const bool enabled)
{
if (enabled == m_usePieceExtentAffinity) return;
m_usePieceExtentAffinity = enabled;
configureDeferred();
}
void SessionImpl::setSuggestMode(const bool mode)
{
if (mode == m_isSuggestMode) return;
m_isSuggestMode = mode;
configureDeferred();
}
int SessionImpl::sendBufferWatermark() const
{
return m_sendBufferWatermark;
}
void SessionImpl::setSendBufferWatermark(const int value)
{
if (value == m_sendBufferWatermark) return;
m_sendBufferWatermark = value;
configureDeferred();
}
int SessionImpl::sendBufferLowWatermark() const
{
return m_sendBufferLowWatermark;
}
void SessionImpl::setSendBufferLowWatermark(const int value)
{
if (value == m_sendBufferLowWatermark) return;
m_sendBufferLowWatermark = value;
configureDeferred();
}
int SessionImpl::sendBufferWatermarkFactor() const
{
return m_sendBufferWatermarkFactor;
}
void SessionImpl::setSendBufferWatermarkFactor(const int value)
{
if (value == m_sendBufferWatermarkFactor) return;
m_sendBufferWatermarkFactor = value;
configureDeferred();
}
int SessionImpl::connectionSpeed() const
{
return m_connectionSpeed;
}
void SessionImpl::setConnectionSpeed(const int value)
{
if (value == m_connectionSpeed) return;
m_connectionSpeed = value;
configureDeferred();
}
int SessionImpl::socketBacklogSize() const
{
return m_socketBacklogSize;
}
void SessionImpl::setSocketBacklogSize(const int value)
{
if (value == m_socketBacklogSize) return;
m_socketBacklogSize = value;
configureDeferred();
}
bool SessionImpl::isAnonymousModeEnabled() const
{
return m_isAnonymousModeEnabled;
}
void SessionImpl::setAnonymousModeEnabled(const bool enabled)
{
if (enabled != m_isAnonymousModeEnabled)
{
m_isAnonymousModeEnabled = enabled;
configureDeferred();
LogMsg(tr("Anonymous mode: %1").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
, Log::INFO);
}
}
bool SessionImpl::isQueueingSystemEnabled() const
{
return m_isQueueingEnabled;
}
void SessionImpl::setQueueingSystemEnabled(const bool enabled)
{
if (enabled != m_isQueueingEnabled)
{
m_isQueueingEnabled = enabled;
configureDeferred();
if (enabled)
m_torrentsQueueChanged = true;
else
removeTorrentsQueue();
}
}
int SessionImpl::maxActiveDownloads() const
{
return m_maxActiveDownloads;
}
void SessionImpl::setMaxActiveDownloads(int max)
{
max = std::max(max, -1);
if (max != m_maxActiveDownloads)
{
m_maxActiveDownloads = max;
configureDeferred();
}
}
int SessionImpl::maxActiveUploads() const
{
return m_maxActiveUploads;
}
void SessionImpl::setMaxActiveUploads(int max)
{
max = std::max(max, -1);
if (max != m_maxActiveUploads)
{
m_maxActiveUploads = max;
configureDeferred();
}
}
int SessionImpl::maxActiveTorrents() const
{
return m_maxActiveTorrents;
}
void SessionImpl::setMaxActiveTorrents(int max)
{
max = std::max(max, -1);
if (max != m_maxActiveTorrents)
{
m_maxActiveTorrents = max;
configureDeferred();
}
}
bool SessionImpl::ignoreSlowTorrentsForQueueing() const
{
return m_ignoreSlowTorrentsForQueueing;
}
void SessionImpl::setIgnoreSlowTorrentsForQueueing(const bool ignore)
{
if (ignore != m_ignoreSlowTorrentsForQueueing)
{
m_ignoreSlowTorrentsForQueueing = ignore;
configureDeferred();
}
}
int SessionImpl::downloadRateForSlowTorrents() const
{
return m_downloadRateForSlowTorrents;
}
void SessionImpl::setDownloadRateForSlowTorrents(const int rateInKibiBytes)
{
if (rateInKibiBytes == m_downloadRateForSlowTorrents)
return;
m_downloadRateForSlowTorrents = rateInKibiBytes;
configureDeferred();
}
int SessionImpl::uploadRateForSlowTorrents() const
{
return m_uploadRateForSlowTorrents;
}
void SessionImpl::setUploadRateForSlowTorrents(const int rateInKibiBytes)
{
if (rateInKibiBytes == m_uploadRateForSlowTorrents)
return;
m_uploadRateForSlowTorrents = rateInKibiBytes;
configureDeferred();
}
int SessionImpl::slowTorrentsInactivityTimer() const
{
return m_slowTorrentsInactivityTimer;
}
void SessionImpl::setSlowTorrentsInactivityTimer(const int timeInSeconds)
{
if (timeInSeconds == m_slowTorrentsInactivityTimer)
return;
m_slowTorrentsInactivityTimer = timeInSeconds;
configureDeferred();
}
int SessionImpl::outgoingPortsMin() const
{
return m_outgoingPortsMin;
}
void SessionImpl::setOutgoingPortsMin(const int min)
{
if (min != m_outgoingPortsMin)
{
m_outgoingPortsMin = min;
configureDeferred();
}
}
int SessionImpl::outgoingPortsMax() const
{
return m_outgoingPortsMax;
}
void SessionImpl::setOutgoingPortsMax(const int max)
{
if (max != m_outgoingPortsMax)
{
m_outgoingPortsMax = max;
configureDeferred();
}
}
int SessionImpl::UPnPLeaseDuration() const
{
return m_UPnPLeaseDuration;
}
void SessionImpl::setUPnPLeaseDuration(const int duration)
{
if (duration != m_UPnPLeaseDuration)
{
m_UPnPLeaseDuration = duration;
configureDeferred();
}
}
int SessionImpl::peerToS() const
{
return m_peerToS;
}
void SessionImpl::setPeerToS(const int value)
{
if (value == m_peerToS)
return;
m_peerToS = value;
configureDeferred();
}
bool SessionImpl::ignoreLimitsOnLAN() const
{
return m_ignoreLimitsOnLAN;
}
void SessionImpl::setIgnoreLimitsOnLAN(const bool ignore)
{
if (ignore != m_ignoreLimitsOnLAN)
{
m_ignoreLimitsOnLAN = ignore;
configureDeferred();
}
}
bool SessionImpl::includeOverheadInLimits() const
{
return m_includeOverheadInLimits;
}
void SessionImpl::setIncludeOverheadInLimits(const bool include)
{
if (include != m_includeOverheadInLimits)
{
m_includeOverheadInLimits = include;
configureDeferred();
}
}
QString SessionImpl::announceIP() const
{
return m_announceIP;
}
void SessionImpl::setAnnounceIP(const QString &ip)
{
if (ip != m_announceIP)
{
m_announceIP = ip;
configureDeferred();
}
}
int SessionImpl::maxConcurrentHTTPAnnounces() const
{
return m_maxConcurrentHTTPAnnounces;
}
void SessionImpl::setMaxConcurrentHTTPAnnounces(const int value)
{
if (value == m_maxConcurrentHTTPAnnounces)
return;
m_maxConcurrentHTTPAnnounces = value;
configureDeferred();
}
bool SessionImpl::isReannounceWhenAddressChangedEnabled() const
{
return m_isReannounceWhenAddressChangedEnabled;
}
void SessionImpl::setReannounceWhenAddressChangedEnabled(const bool enabled)
{
if (enabled == m_isReannounceWhenAddressChangedEnabled)
return;
m_isReannounceWhenAddressChangedEnabled = enabled;
}
void SessionImpl::reannounceToAllTrackers() const
{
for (const lt::torrent_handle &torrent : m_nativeSession->get_torrents())
torrent.force_reannounce(0, -1, lt::torrent_handle::ignore_min_interval);
}
int SessionImpl::stopTrackerTimeout() const
{
return m_stopTrackerTimeout;
}
void SessionImpl::setStopTrackerTimeout(const int value)
{
if (value == m_stopTrackerTimeout)
return;
m_stopTrackerTimeout = value;
configureDeferred();
}
int SessionImpl::maxConnections() const
{
return m_maxConnections;
}
void SessionImpl::setMaxConnections(int max)
{
max = (max > 0) ? max : -1;
if (max != m_maxConnections)
{
m_maxConnections = max;
configureDeferred();
}
}
int SessionImpl::maxUploads() const
{
return m_maxUploads;
}
void SessionImpl::setMaxUploads(int max)
{
max = (max > 0) ? max : -1;
if (max != m_maxUploads)
{
m_maxUploads = max;
configureDeferred();
}
}
BTProtocol SessionImpl::btProtocol() const
{
return m_btProtocol;
}
void SessionImpl::setBTProtocol(const BTProtocol protocol)
{
if ((protocol < BTProtocol::Both) || (BTProtocol::UTP < protocol))
return;
if (protocol == m_btProtocol) return;
m_btProtocol = protocol;
configureDeferred();
}
bool SessionImpl::isUTPRateLimited() const
{
return m_isUTPRateLimited;
}
void SessionImpl::setUTPRateLimited(const bool limited)
{
if (limited != m_isUTPRateLimited)
{
m_isUTPRateLimited = limited;
configureDeferred();
}
}
MixedModeAlgorithm SessionImpl::utpMixedMode() const
{
return m_utpMixedMode;
}
void SessionImpl::setUtpMixedMode(const MixedModeAlgorithm mode)
{
if (mode == m_utpMixedMode) return;
m_utpMixedMode = mode;
configureDeferred();
}
bool SessionImpl::isIDNSupportEnabled() const
{
return m_IDNSupportEnabled;
}
void SessionImpl::setIDNSupportEnabled(const bool enabled)
{
if (enabled == m_IDNSupportEnabled) return;
m_IDNSupportEnabled = enabled;
configureDeferred();
}
bool SessionImpl::multiConnectionsPerIpEnabled() const
{
return m_multiConnectionsPerIpEnabled;
}
void SessionImpl::setMultiConnectionsPerIpEnabled(const bool enabled)
{
if (enabled == m_multiConnectionsPerIpEnabled) return;
m_multiConnectionsPerIpEnabled = enabled;
configureDeferred();
}
bool SessionImpl::validateHTTPSTrackerCertificate() const
{
return m_validateHTTPSTrackerCertificate;
}
void SessionImpl::setValidateHTTPSTrackerCertificate(const bool enabled)
{
if (enabled == m_validateHTTPSTrackerCertificate) return;
m_validateHTTPSTrackerCertificate = enabled;
configureDeferred();
}
bool SessionImpl::isSSRFMitigationEnabled() const
{
return m_SSRFMitigationEnabled;
}
void SessionImpl::setSSRFMitigationEnabled(const bool enabled)
{
if (enabled == m_SSRFMitigationEnabled) return;
m_SSRFMitigationEnabled = enabled;
configureDeferred();
}
bool SessionImpl::blockPeersOnPrivilegedPorts() const
{
return m_blockPeersOnPrivilegedPorts;
}
void SessionImpl::setBlockPeersOnPrivilegedPorts(const bool enabled)
{
if (enabled == m_blockPeersOnPrivilegedPorts) return;
m_blockPeersOnPrivilegedPorts = enabled;
configureDeferred();
}
bool SessionImpl::isTrackerFilteringEnabled() const
{
return m_isTrackerFilteringEnabled;
}
void SessionImpl::setTrackerFilteringEnabled(const bool enabled)
{
if (enabled != m_isTrackerFilteringEnabled)
{
m_isTrackerFilteringEnabled = enabled;
configureDeferred();
}
}
bool SessionImpl::isListening() const
{
return m_nativeSession->is_listening();
}
MaxRatioAction SessionImpl::maxRatioAction() const
{
return static_cast<MaxRatioAction>(m_maxRatioAction.get());
}
void SessionImpl::setMaxRatioAction(const MaxRatioAction act)
{
m_maxRatioAction = static_cast<int>(act);
}
bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const
{
const bool isHybrid = infoHash.isHybrid();
const auto id = TorrentID::fromInfoHash(infoHash);
// alternative ID can be useful to find existing torrent
// in case if hybrid torrent was added by v1 info hash
const auto altID = (isHybrid ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
if (m_loadingTorrents.contains(id) || (isHybrid && m_loadingTorrents.contains(altID)))
return true;
if (m_downloadedMetadata.contains(id) || (isHybrid && m_downloadedMetadata.contains(altID)))
return true;
return findTorrent(infoHash);
}
void SessionImpl::updateSeedingLimitTimer()
{
if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
&& (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit())
{
if (m_seedingLimitTimer->isActive())
m_seedingLimitTimer->stop();
}
else if (!m_seedingLimitTimer->isActive())
{
m_seedingLimitTimer->start();
}
}
void SessionImpl::handleTorrentShareLimitChanged(TorrentImpl *const)
{
updateSeedingLimitTimer();
}
void SessionImpl::handleTorrentNameChanged(TorrentImpl *const)
{
}
void SessionImpl::handleTorrentSavePathChanged(TorrentImpl *const torrent)
{
emit torrentSavePathChanged(torrent);
}
void SessionImpl::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
{
emit torrentCategoryChanged(torrent, oldCategory);
}
void SessionImpl::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
{
emit torrentTagAdded(torrent, tag);
}
void SessionImpl::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
{
emit torrentTagRemoved(torrent, tag);
}
void SessionImpl::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
{
emit torrentSavingModeChanged(torrent);
}
void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
{
for (const TrackerEntry &newTracker : newTrackers)
LogMsg(tr("Added tracker to torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), newTracker.url));
emit trackersAdded(torrent, newTrackers);
if (torrent->trackers().size() == newTrackers.size())
emit trackerlessStateChanged(torrent, false);
emit trackersChanged(torrent);
}
void SessionImpl::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers)
{
for (const QString &deletedTracker : deletedTrackers)
LogMsg(tr("Removed tracker from torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), deletedTracker));
emit trackersRemoved(torrent, deletedTrackers);
if (torrent->trackers().isEmpty())
emit trackerlessStateChanged(torrent, true);
emit trackersChanged(torrent);
}
void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
{
emit trackersChanged(torrent);
}
void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
{
for (const QUrl &newUrlSeed : newUrlSeeds)
LogMsg(tr("Added URL seed to torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), newUrlSeed.toString()));
}
void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
{
for (const QUrl &urlSeed : urlSeeds)
LogMsg(tr("Removed URL seed from torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), urlSeed.toString()));
}
void SessionImpl::handleTorrentMetadataReceived(TorrentImpl *const torrent)
{
if (!torrentExportDirectory().isEmpty())
exportTorrentFile(torrent, torrentExportDirectory());
emit torrentMetadataReceived(torrent);
}
void SessionImpl::handleTorrentPaused(TorrentImpl *const torrent)
{
LogMsg(tr("Torrent paused. Torrent: \"%1\"").arg(torrent->name()));
emit torrentPaused(torrent);
}
void SessionImpl::handleTorrentResumed(TorrentImpl *const torrent)
{
LogMsg(tr("Torrent resumed. Torrent: \"%1\"").arg(torrent->name()));
emit torrentResumed(torrent);
}
void SessionImpl::handleTorrentChecked(TorrentImpl *const torrent)
{
emit torrentFinishedChecking(torrent);
}
void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
{
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
emit torrentFinished(torrent);
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
exportTorrentFile(torrent, exportPath);
// Check whether it contains .torrent files
for (const Path &torrentRelpath : asConst(torrent->filePaths()))
{
if (torrentRelpath.hasExtension(u".torrent"_qs))
{
emit recursiveTorrentDownloadPossible(torrent);
break;
}
}
if (!hasUnfinishedTorrents())
emit allTorrentsFinished();
}
void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
{
--m_numResumeData;
m_resumeDataStorage->store(torrent->id(), data);
const auto iter = m_changedTorrentIDs.find(torrent->id());
if (iter != m_changedTorrentIDs.end())
{
m_resumeDataStorage->remove(iter.value());
m_changedTorrentIDs.erase(iter);
}
}
void SessionImpl::handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash)
{
Q_ASSERT(torrent->infoHash().isHybrid());
m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(torrent->infoHash().v1()), torrent);
const auto prevID = TorrentID::fromInfoHash(prevInfoHash);
const TorrentID currentID = torrent->id();
if (currentID != prevID)
{
m_torrents[torrent->id()] = m_torrents.take(prevID);
m_changedTorrentIDs[torrent->id()] = prevID;
}
}
bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode)
{
Q_ASSERT(torrent);
const lt::torrent_handle torrentHandle = torrent->nativeHandle();
const Path currentLocation = torrent->actualStorageLocation();
if (m_moveStorageQueue.size() > 1)
{
auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
, [&torrentHandle](const MoveStorageJob &job)
{
return job.torrentHandle == torrentHandle;
});
if (iter != m_moveStorageQueue.end())
{
// remove existing inactive job
LogMsg(tr("Torrent move canceled. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), iter->path.toString()));
iter = m_moveStorageQueue.erase(iter);
iter = std::find_if(iter, m_moveStorageQueue.end(), [&torrentHandle](const MoveStorageJob &job)
{
return job.torrentHandle == torrentHandle;
});
const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.end());
torrent->handleMoveStorageJobFinished(currentLocation, torrentHasOutstandingJob);
}
}
if (!m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle))
{
// if there is active job for this torrent prevent creating meaningless
// job that will move torrent to the same location as current one
if (m_moveStorageQueue.first().path == newPath)
{
LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: torrent is currently moving to the destination")
.arg(torrent->name(), currentLocation.toString(), newPath.toString()));
return false;
}
}
else
{
if (currentLocation == newPath)
{
LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\" Destination: \"%3\". Reason: both paths point to the same location")
.arg(torrent->name(), currentLocation.toString(), newPath.toString()));
return false;
}
}
const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode};
m_moveStorageQueue << moveStorageJob;
LogMsg(tr("Enqueued torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), newPath.toString()));
if (m_moveStorageQueue.size() == 1)
moveTorrentStorage(moveStorageJob);
return true;
}
void SessionImpl::moveTorrentStorage(const MoveStorageJob &job) const
{
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
#else
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
#endif
const TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
LogMsg(tr("Start moving torrent. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, job.path.toString()));
job.torrentHandle.move_storage(job.path.toString().toStdString(), toNative(job.mode));
}
void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
{
const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
if (!m_moveStorageQueue.isEmpty())
moveTorrentStorage(m_moveStorageQueue.first());
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
, [&finishedJob](const MoveStorageJob &job)
{
return job.torrentHandle == finishedJob.torrentHandle;
});
const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
if (torrent)
{
torrent->handleMoveStorageJobFinished(newPath, torrentHasOutstandingJob);
}
else if (!torrentHasOutstandingJob)
{
// Last job is completed for torrent that being removing, so actually remove it
const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
if (removingTorrentData.deleteOption == DeleteTorrent)
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
}
}
void SessionImpl::storeCategories() const
{
QJsonObject jsonObj;
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
{
const QString &categoryName = it.key();
const CategoryOptions &categoryOptions = it.value();
jsonObj[categoryName] = categoryOptions.toJSON();
}
const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
const QByteArray data = QJsonDocument(jsonObj).toJson();
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
if (!result)
{
LogMsg(tr("Failed to save Categories configuration. File: \"%1\". Error: \"%2\"")
.arg(path.toString(), result.error()), Log::WARNING);
}
}
void SessionImpl::upgradeCategories()
{
const auto legacyCategories = SettingValue<QVariantMap>(u"BitTorrent/Session/Categories"_qs).get();
for (auto it = legacyCategories.cbegin(); it != legacyCategories.cend(); ++it)
{
const QString categoryName = it.key();
CategoryOptions categoryOptions;
categoryOptions.savePath = Path(it.value().toString());
m_categories[categoryName] = categoryOptions;
}
storeCategories();
}
void SessionImpl::loadCategories()
{
m_categories.clear();
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME).data()};
if (!confFile.exists())
{
// TODO: Remove the following upgrade code in v4.5
// == BEGIN UPGRADE CODE ==
upgradeCategories();
m_needUpgradeDownloadPath = true;
// == END UPGRADE CODE ==
// return;
}
if (!confFile.open(QFile::ReadOnly))
{
LogMsg(tr("Failed to load Categories. File: \"%1\". Error: \"%2\"")
.arg(confFile.fileName(), confFile.errorString()), Log::CRITICAL);
return;
}
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
.arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
return;
}
if (!jsonDoc.isObject())
{
LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Reason: invalid data format")
.arg(confFile.fileName()), Log::WARNING);
return;
}
const QJsonObject jsonObj = jsonDoc.object();
for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
{
const QString &categoryName = it.key();
const auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
m_categories[categoryName] = categoryOptions;
}
}
bool SessionImpl::hasPerTorrentRatioLimit() const
{
return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
{
return (torrent->ratioLimit() >= 0);
});
}
bool SessionImpl::hasPerTorrentSeedingTimeLimit() const
{
return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
{
return (torrent->seedingTimeLimit() >= 0);
});
}
void SessionImpl::configureDeferred()
{
if (m_deferredConfigureScheduled)
return;
m_deferredConfigureScheduled = true;
QMetaObject::invokeMethod(this, qOverload<>(&SessionImpl::configure), Qt::QueuedConnection);
}
// Enable IP Filtering
// this method creates ban list from scratch combining user ban list and 3rd party ban list file
void SessionImpl::enableIPFilter()
{
qDebug("Enabling IPFilter");
// 1. Parse the IP filter
// 2. In the slot add the manually banned IPs to the provided lt::ip_filter
// 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
// set between clearing the old one and setting the new one.
if (!m_filterParser)
{
m_filterParser = new FilterParserThread(this);
connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &SessionImpl::handleIPFilterParsed);
connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &SessionImpl::handleIPFilterError);
}
m_filterParser->processFilterFile(IPFilterFile());
}
// Disable IP Filtering
void SessionImpl::disableIPFilter()
{
qDebug("Disabling IPFilter");
if (m_filterParser)
{
disconnect(m_filterParser.data(), nullptr, this, nullptr);
delete m_filterParser;
}
// Add the banned IPs after the IPFilter disabling
// which creates an empty filter and overrides all previously
// applied bans.
lt::ip_filter filter;
processBannedIPs(filter);
m_nativeSession->set_ip_filter(filter);
}
void SessionImpl::recursiveTorrentDownload(const TorrentID &id)
{
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent)
return;
for (const Path &torrentRelpath : asConst(torrent->filePaths()))
{
if (torrentRelpath.hasExtension(u".torrent"_qs))
{
const Path torrentFullpath = torrent->savePath() / torrentRelpath;
LogMsg(tr("Recursive download .torrent file within torrent. Source torrent: \"%1\". File: \"%2\"")
.arg(torrent->name(), torrentFullpath.toString()));
AddTorrentParams params;
// Passing the save path along to the sub torrent file
params.savePath = torrent->savePath();
const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(torrentFullpath);
if (loadResult)
{
addTorrent(loadResult.value(), params);
}
else
{
LogMsg(tr("Failed to load .torrent file within torrent. Source torrent: \"%1\". File: \"%2\". Error: \"%3\"")
.arg(torrent->name(), torrentFullpath.toString(), loadResult.error()), Log::WARNING);
}
}
}
}
const SessionStatus &SessionImpl::status() const
{
return m_status;
}
const CacheStatus &SessionImpl::cacheStatus() const
{
return m_cacheStatus;
}
void SessionImpl::enqueueRefresh()
{
Q_ASSERT(!m_refreshEnqueued);
QTimer::singleShot(refreshInterval(), this, [this] ()
{
m_nativeSession->post_torrent_updates();
m_nativeSession->post_session_stats();
if (m_torrentsQueueChanged)
{
m_torrentsQueueChanged = false;
m_needSaveTorrentsQueue = true;
}
});
m_refreshEnqueued = true;
}
void SessionImpl::handleIPFilterParsed(const int ruleCount)
{
if (m_filterParser)
{
lt::ip_filter filter = m_filterParser->IPfilter();
processBannedIPs(filter);
m_nativeSession->set_ip_filter(filter);
}
LogMsg(tr("Successfully parsed the IP filter file. Number of rules applied: %1").arg(ruleCount));
emit IPFilterParsed(false, ruleCount);
}
void SessionImpl::handleIPFilterError()
{
lt::ip_filter filter;
processBannedIPs(filter);
m_nativeSession->set_ip_filter(filter);
LogMsg(tr("Failed to parse the IP filter file"), Log::WARNING);
emit IPFilterParsed(true, 0);
}
std::vector<lt::alert *> SessionImpl::getPendingAlerts(const lt::time_duration time) const
{
if (time > lt::time_duration::zero())
m_nativeSession->wait_for_alert(time);
std::vector<lt::alert *> alerts;
m_nativeSession->pop_alerts(&alerts);
return alerts;
}
TorrentContentLayout SessionImpl::torrentContentLayout() const
{
return m_torrentContentLayout;
}
void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value)
{
m_torrentContentLayout = value;
}
// Read alerts sent by the BitTorrent session
void SessionImpl::readAlerts()
{
const std::vector<lt::alert *> alerts = getPendingAlerts();
handleAddTorrentAlerts(alerts);
for (const lt::alert *a : alerts)
handleAlert(a);
processTrackerStatuses();
}
void SessionImpl::handleAddTorrentAlerts(const std::vector<lt::alert *> &alerts)
{
QVector<Torrent *> loadedTorrents;
if (!isRestored())
loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
for (const lt::alert *a : alerts)
{
if (a->type() != lt::add_torrent_alert::alert_type)
continue;
auto alert = static_cast<const lt::add_torrent_alert *>(a);
if (alert->error)
{
const QString msg = QString::fromStdString(alert->message());
LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(msg), Log::WARNING);
emit loadTorrentFailed(msg);
const lt::add_torrent_params &params = alert->params;
const bool hasMetadata = (params.ti && params.ti->is_valid());
#ifdef QBT_USES_LIBTORRENT2
const InfoHash infoHash {(hasMetadata ? params.ti->info_hashes() : params.info_hashes)};
if (infoHash.isHybrid())
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
#else
const InfoHash infoHash {(hasMetadata ? params.ti->info_hash() : params.info_hash)};
#endif
m_loadingTorrents.remove(TorrentID::fromInfoHash(infoHash));
return;
}
#ifdef QBT_USES_LIBTORRENT2
const auto torrentID = TorrentID::fromInfoHash(alert->handle.info_hashes());
#else
const auto torrentID = TorrentID::fromInfoHash(alert->handle.info_hash());
#endif
const auto loadingTorrentsIter = m_loadingTorrents.find(torrentID);
if (loadingTorrentsIter != m_loadingTorrents.end())
{
LoadTorrentParams params = loadingTorrentsIter.value();
m_loadingTorrents.erase(loadingTorrentsIter);
Torrent *torrent = createTorrent(alert->handle, params);
loadedTorrents.append(torrent);
}
}
if (!loadedTorrents.isEmpty())
{
if (isRestored())
m_torrentsQueueChanged = true;
emit torrentsLoaded(loadedTorrents);
}
}
void SessionImpl::handleAlert(const lt::alert *a)
{
try
{
switch (a->type())
{
#ifdef QBT_USES_LIBTORRENT2
case lt::file_prio_alert::alert_type:
#endif
case lt::file_renamed_alert::alert_type:
case lt::file_rename_failed_alert::alert_type:
case lt::file_completed_alert::alert_type:
case lt::torrent_finished_alert::alert_type:
case lt::save_resume_data_alert::alert_type:
case lt::save_resume_data_failed_alert::alert_type:
case lt::torrent_paused_alert::alert_type:
case lt::torrent_resumed_alert::alert_type:
case lt::fastresume_rejected_alert::alert_type:
case lt::torrent_checked_alert::alert_type:
case lt::metadata_received_alert::alert_type:
case lt::performance_alert::alert_type:
dispatchTorrentAlert(static_cast<const lt::torrent_alert *>(a));
break;
case lt::state_update_alert::alert_type:
handleStateUpdateAlert(static_cast<const lt::state_update_alert*>(a));
break;
case lt::session_stats_alert::alert_type:
handleSessionStatsAlert(static_cast<const lt::session_stats_alert*>(a));
break;
case lt::tracker_announce_alert::alert_type:
case lt::tracker_error_alert::alert_type:
case lt::tracker_reply_alert::alert_type:
case lt::tracker_warning_alert::alert_type:
handleTrackerAlert(static_cast<const lt::tracker_alert *>(a));
break;
case lt::file_error_alert::alert_type:
handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
break;
case lt::add_torrent_alert::alert_type:
// handled separately
break;
case lt::torrent_removed_alert::alert_type:
handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert*>(a));
break;
case lt::torrent_deleted_alert::alert_type:
handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert*>(a));
break;
case lt::torrent_delete_failed_alert::alert_type:
handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert*>(a));
break;
case lt::portmap_error_alert::alert_type:
handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert*>(a));
break;
case lt::portmap_alert::alert_type:
handlePortmapAlert(static_cast<const lt::portmap_alert*>(a));
break;
case lt::peer_blocked_alert::alert_type:
handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert*>(a));
break;
case lt::peer_ban_alert::alert_type:
handlePeerBanAlert(static_cast<const lt::peer_ban_alert*>(a));
break;
case lt::url_seed_alert::alert_type:
handleUrlSeedAlert(static_cast<const lt::url_seed_alert*>(a));
break;
case lt::listen_succeeded_alert::alert_type:
handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert*>(a));
break;
case lt::listen_failed_alert::alert_type:
handleListenFailedAlert(static_cast<const lt::listen_failed_alert*>(a));
break;
case lt::external_ip_alert::alert_type:
handleExternalIPAlert(static_cast<const lt::external_ip_alert*>(a));
break;
case lt::alerts_dropped_alert::alert_type:
handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(a));
break;
case lt::storage_moved_alert::alert_type:
handleStorageMovedAlert(static_cast<const lt::storage_moved_alert*>(a));
break;
case lt::storage_moved_failed_alert::alert_type:
handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert*>(a));
break;
case lt::socks5_alert::alert_type:
handleSocks5Alert(static_cast<const lt::socks5_alert *>(a));
break;
#ifdef QBT_USES_LIBTORRENT2
case lt::torrent_conflict_alert::alert_type:
handleTorrentConflictAlert(static_cast<const lt::torrent_conflict_alert *>(a));
break;
#endif
}
}
catch (const std::exception &exc)
{
qWarning() << "Caught exception in " << Q_FUNC_INFO << ": " << QString::fromStdString(exc.what());
}
}
void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert *a)
{
const TorrentID torrentID {a->handle.info_hash()};
TorrentImpl *torrent = m_torrents.value(torrentID);
#ifdef QBT_USES_LIBTORRENT2
if (!torrent && (a->type() == lt::metadata_received_alert::alert_type))
{
const InfoHash infoHash {a->handle.info_hashes()};
if (infoHash.isHybrid())
torrent = m_torrents.value(TorrentID::fromSHA1Hash(infoHash.v1()));
}
#endif
if (torrent)
{
torrent->handleAlert(a);
return;
}
switch (a->type())
{
case lt::metadata_received_alert::alert_type:
handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert *>(a));
break;
}
}
TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
{
auto *const torrent = new TorrentImpl(this, m_nativeSession, nativeHandle, params);
m_torrents.insert(torrent->id(), torrent);
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent);
if (isRestored())
{
m_resumeDataStorage->store(torrent->id(), params);
// The following is useless for newly added magnet
if (torrent->hasMetadata())
{
if (!torrentExportDirectory().isEmpty())
exportTorrentFile(torrent, torrentExportDirectory());
}
}
if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
&& !m_seedingLimitTimer->isActive())
{
m_seedingLimitTimer->start();
}
if (!isRestored())
{
LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name()));
}
else
{
LogMsg(tr("Added new torrent. Torrent: \"%1\"").arg(torrent->name()));
emit torrentAdded(torrent);
}
// Torrent could have error just after adding to libtorrent
if (torrent->hasError())
LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: \"%2\"").arg(torrent->name(), torrent->error()), Log::WARNING);
return torrent;
}
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
{
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(p->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(p->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter != m_removingTorrents.end())
{
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
{
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
m_removingTorrents.erase(removingTorrentDataIter);
}
}
}
void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
{
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(p->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(p->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
m_removingTorrents.erase(removingTorrentDataIter);
}
void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
{
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(p->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(p->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
if (p->error)
{
// libtorrent won't delete the directory if it contains files not listed in the torrent,
// so we remove the directory ourselves
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
LogMsg(tr("Removed torrent but failed to delete its content. Torrent: \"%1\". Error: \"%2\"")
.arg(removingTorrentDataIter->name, QString::fromLocal8Bit(p->error.message().c_str()))
, Log::WARNING);
}
else // torrent without metadata, hence no files on disk
{
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
}
m_removingTorrents.erase(removingTorrentDataIter);
}
void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
{
const TorrentID torrentID {p->handle.info_hash()};
bool found = false;
if (const auto iter = m_downloadedMetadata.find(torrentID); iter != m_downloadedMetadata.end())
{
found = true;
m_downloadedMetadata.erase(iter);
}
#ifdef QBT_USES_LIBTORRENT2
const InfoHash infoHash {p->handle.info_hashes()};
if (infoHash.isHybrid())
{
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
if (const auto iter = m_downloadedMetadata.find(altID); iter != m_downloadedMetadata.end())
{
found = true;
m_downloadedMetadata.erase(iter);
}
}
#endif
if (found)
{
const TorrentInfo metadata {*p->handle.torrent_file()};
--m_extraLimit;
adjustLimits();
m_nativeSession->remove_torrent(p->handle, lt::session::delete_files);
emit metadataDownloaded(metadata);
}
}
void SessionImpl::handleFileErrorAlert(const lt::file_error_alert *p)
{
TorrentImpl *const torrent = m_torrents.value(p->handle.info_hash());
if (!torrent)
return;
torrent->handleAlert(p);
const TorrentID id = torrent->id();
if (!m_recentErroredTorrents.contains(id))
{
m_recentErroredTorrents.insert(id);
const QString msg = QString::fromStdString(p->message());
LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: \"%3\"")
.arg(torrent->name(), QString::fromLocal8Bit(p->filename()), msg)
, Log::WARNING);
emit fullDiskError(torrent, msg);
}
m_recentErroredTorrentsTimer->start();
}
void SessionImpl::handlePortmapWarningAlert(const lt::portmap_error_alert *p)
{
LogMsg(tr("UPnP/NAT-PMP port mapping failed. Message: \"%1\"").arg(QString::fromStdString(p->message())), Log::WARNING);
}
void SessionImpl::handlePortmapAlert(const lt::portmap_alert *p)
{
qDebug("UPnP Success, msg: %s", p->message().c_str());
LogMsg(tr("UPnP/NAT-PMP port mapping succeeded. Message: \"%1\"").arg(QString::fromStdString(p->message())), Log::INFO);
}
void SessionImpl::handlePeerBlockedAlert(const lt::peer_blocked_alert *p)
{
QString reason;
switch (p->reason)
{
case lt::peer_blocked_alert::ip_filter:
reason = tr("IP filter", "this peer was blocked. Reason: IP filter.");
break;
case lt::peer_blocked_alert::port_filter:
reason = tr("port filter", "this peer was blocked. Reason: port filter.");
break;
case lt::peer_blocked_alert::i2p_mixed:
reason = tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg(u"I2P"_qs); // don't translate I2P
break;
case lt::peer_blocked_alert::privileged_ports:
reason = tr("use of privileged port", "this peer was blocked. Reason: use of privileged port.");
break;
case lt::peer_blocked_alert::utp_disabled:
reason = tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(C_UTP); // don't translate μTP
break;
case lt::peer_blocked_alert::tcp_disabled:
reason = tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg(u"TCP"_qs); // don't translate TCP
break;
}
const QString ip {toString(p->endpoint.address())};
if (!ip.isEmpty())
Logger::instance()->addPeer(ip, true, reason);
}
void SessionImpl::handlePeerBanAlert(const lt::peer_ban_alert *p)
{
const QString ip {toString(p->endpoint.address())};
if (!ip.isEmpty())
Logger::instance()->addPeer(ip, false);
}
void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert *p)
{
const TorrentImpl *torrent = m_torrents.value(p->handle.info_hash());
if (!torrent)
return;
if (p->error)
{
LogMsg(tr("URL seed DNS lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
.arg(torrent->name(), QString::fromUtf8(p->server_url()), QString::fromStdString(p->message()))
, Log::WARNING);
}
else
{
LogMsg(tr("Received error message from URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
.arg(torrent->name(), QString::fromUtf8(p->server_url()), QString::fromUtf8(p->error_message()))
, Log::WARNING);
}
}
void SessionImpl::handleListenSucceededAlert(const lt::listen_succeeded_alert *p)
{
const QString proto {toString(p->socket_type)};
LogMsg(tr("Successfully listening on IP. IP: \"%1\". Port: \"%2/%3\"")
.arg(toString(p->address), proto, QString::number(p->port)), Log::INFO);
}
void SessionImpl::handleListenFailedAlert(const lt::listen_failed_alert *p)
{
const QString proto {toString(p->socket_type)};
LogMsg(tr("Failed to listen on IP. IP: \"%1\". Port: \"%2/%3\". Reason: \"%4\"")
.arg(toString(p->address), proto, QString::number(p->port)
, QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
}
void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *p)
{
const QString externalIP {toString(p->external_address)};
LogMsg(tr("Detected external IP. IP: \"%1\"")
.arg(externalIP), Log::INFO);
if (m_lastExternalIP != externalIP)
{
if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty())
reannounceToAllTrackers();
m_lastExternalIP = externalIP;
}
}
void SessionImpl::handleSessionStatsAlert(const lt::session_stats_alert *p)
{
if (m_refreshEnqueued)
m_refreshEnqueued = false;
else
enqueueRefresh();
const int64_t interval = lt::total_microseconds(p->timestamp() - m_statsLastTimestamp);
if (interval <= 0)
return;
m_statsLastTimestamp = p->timestamp();
const auto stats = p->counters();
m_status.hasIncomingConnections = static_cast<bool>(stats[m_metricIndices.net.hasIncomingConnections]);
const int64_t ipOverheadDownload = stats[m_metricIndices.net.recvIPOverheadBytes];
const int64_t ipOverheadUpload = stats[m_metricIndices.net.sentIPOverheadBytes];
const int64_t totalDownload = stats[m_metricIndices.net.recvBytes] + ipOverheadDownload;
const int64_t totalUpload = stats[m_metricIndices.net.sentBytes] + ipOverheadUpload;
const int64_t totalPayloadDownload = stats[m_metricIndices.net.recvPayloadBytes];
const int64_t totalPayloadUpload = stats[m_metricIndices.net.sentPayloadBytes];
const int64_t trackerDownload = stats[m_metricIndices.net.recvTrackerBytes];
const int64_t trackerUpload = stats[m_metricIndices.net.sentTrackerBytes];
const int64_t dhtDownload = stats[m_metricIndices.dht.dhtBytesIn];
const int64_t dhtUpload = stats[m_metricIndices.dht.dhtBytesOut];
const auto calcRate = [interval](const qint64 previous, const qint64 current) -> qint64
{
Q_ASSERT(current >= previous);
Q_ASSERT(interval >= 0);
return (((current - previous) * lt::microseconds(1s).count()) / interval);
};
m_status.payloadDownloadRate = calcRate(m_status.totalPayloadDownload, totalPayloadDownload);
m_status.payloadUploadRate = calcRate(m_status.totalPayloadUpload, totalPayloadUpload);
m_status.downloadRate = calcRate(m_status.totalDownload, totalDownload);
m_status.uploadRate = calcRate(m_status.totalUpload, totalUpload);
m_status.ipOverheadDownloadRate = calcRate(m_status.ipOverheadDownload, ipOverheadDownload);
m_status.ipOverheadUploadRate = calcRate(m_status.ipOverheadUpload, ipOverheadUpload);
m_status.dhtDownloadRate = calcRate(m_status.dhtDownload, dhtDownload);
m_status.dhtUploadRate = calcRate(m_status.dhtUpload, dhtUpload);
m_status.trackerDownloadRate = calcRate(m_status.trackerDownload, trackerDownload);
m_status.trackerUploadRate = calcRate(m_status.trackerUpload, trackerUpload);
m_status.totalPayloadDownload = totalPayloadDownload;
m_status.totalPayloadUpload = totalPayloadUpload;
m_status.ipOverheadDownload = ipOverheadDownload;
m_status.ipOverheadUpload = ipOverheadUpload;
m_status.trackerDownload = trackerDownload;
m_status.trackerUpload = trackerUpload;
m_status.dhtDownload = dhtDownload;
m_status.dhtUpload = dhtUpload;
m_status.totalWasted = stats[m_metricIndices.net.recvRedundantBytes]
+ stats[m_metricIndices.net.recvFailedBytes];
m_status.dhtNodes = stats[m_metricIndices.dht.dhtNodes];
m_status.diskReadQueue = stats[m_metricIndices.peer.numPeersUpDisk];
m_status.diskWriteQueue = stats[m_metricIndices.peer.numPeersDownDisk];
m_status.peersCount = stats[m_metricIndices.peer.numPeersConnected];
if (totalDownload > m_status.totalDownload)
{
m_status.totalDownload = totalDownload;
m_isStatisticsDirty = true;
}
if (totalUpload > m_status.totalUpload)
{
m_status.totalUpload = totalUpload;
m_isStatisticsDirty = true;
}
m_status.allTimeDownload = m_previouslyDownloaded + m_status.totalDownload;
m_status.allTimeUpload = m_previouslyUploaded + m_status.totalUpload;
if (m_statisticsLastUpdateTimer.hasExpired(STATISTICS_SAVE_INTERVAL))
saveStatistics();
m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
#ifndef QBT_USES_LIBTORRENT2
const int64_t numBlocksRead = stats[m_metricIndices.disk.numBlocksRead];
const int64_t numBlocksCacheHits = stats[m_metricIndices.disk.numBlocksCacheHits];
m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max<int64_t>((numBlocksCacheHits + numBlocksRead), 1);
#endif
const int64_t totalJobs = stats[m_metricIndices.disk.writeJobs] + stats[m_metricIndices.disk.readJobs]
+ stats[m_metricIndices.disk.hashJobs];
m_cacheStatus.averageJobTime = (totalJobs > 0)
? (stats[m_metricIndices.disk.diskJobTime] / totalJobs) : 0;
emit statsUpdated();
}
void SessionImpl::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
{
LogMsg(tr("Error: Internal alert queue is full and alerts are dropped, you might see degraded performance. Dropped alert type: \"%1\". Message: \"%2\"")
.arg(QString::fromStdString(p->dropped_alerts.to_string()), QString::fromStdString(p->message())), Log::CRITICAL);
}
void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *p)
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == p->handle);
const Path newPath {QString::fromUtf8(p->storage_path())};
Q_ASSERT(newPath == currentJob.path);
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
#else
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
#endif
TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
LogMsg(tr("Moved torrent successfully. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, newPath.toString()));
handleMoveTorrentStorageJobFinished(newPath);
}
void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == p->handle);
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
#else
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
#endif
TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
const Path currentLocation = (torrent ? torrent->actualStorageLocation()
: Path(p->handle.status(lt::torrent_handle::query_save_path).save_path));
const QString errorMessage = QString::fromStdString(p->message());
LogMsg(tr("Failed to move torrent. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: \"%4\"")
.arg(torrentName, currentLocation.toString(), currentJob.path.toString(), errorMessage), Log::WARNING);
handleMoveTorrentStorageJobFinished(currentLocation);
}
void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *p)
{
QVector<Torrent *> updatedTorrents;
updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(p->status.size()));
for (const lt::torrent_status &status : p->status)
{
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(status.info_hashes);
#else
const auto id = TorrentID::fromInfoHash(status.info_hash);
#endif
TorrentImpl *const torrent = m_torrents.value(id);
if (!torrent)
continue;
torrent->handleStateUpdate(status);
updatedTorrents.push_back(torrent);
}
if (!updatedTorrents.isEmpty())
emit torrentsUpdated(updatedTorrents);
if (m_needSaveTorrentsQueue)
saveTorrentsQueue();
if (m_refreshEnqueued)
m_refreshEnqueued = false;
else
enqueueRefresh();
}
void SessionImpl::handleSocks5Alert(const lt::socks5_alert *p) const
{
if (p->error)
{
LogMsg(tr("SOCKS5 proxy error. Message: \"%1\"").arg(QString::fromStdString(p->message()))
, Log::WARNING);
}
}
void SessionImpl::handleTrackerAlert(const lt::tracker_alert *a)
{
TorrentImpl *torrent = m_torrents.value(a->handle.info_hash());
if (!torrent)
return;
const auto trackerURL = QString::fromUtf8(a->tracker_url());
m_updatedTrackerEntries[torrent].insert(trackerURL);
if (a->type() == lt::tracker_reply_alert::alert_type)
{
const int numPeers = static_cast<const lt::tracker_reply_alert *>(a)->num_peers;
torrent->updatePeerCount(trackerURL, a->local_endpoint, numPeers);
}
}
#ifdef QBT_USES_LIBTORRENT2
void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a)
{
const auto torrentIDv1 = TorrentID::fromSHA1Hash(a->metadata->info_hashes().v1);
const auto torrentIDv2 = TorrentID::fromSHA256Hash(a->metadata->info_hashes().v2);
TorrentImpl *torrent1 = m_torrents.value(torrentIDv1);
TorrentImpl *torrent2 = m_torrents.value(torrentIDv2);
if (torrent2)
{
if (torrent1)
deleteTorrent(torrentIDv1);
else
cancelDownloadMetadata(torrentIDv1);
torrent2->nativeHandle().set_metadata(a->metadata->info_section());
}
else if (torrent1)
{
if (!torrent2)
cancelDownloadMetadata(torrentIDv2);
torrent1->nativeHandle().set_metadata(a->metadata->info_section());
}
else
{
cancelDownloadMetadata(torrentIDv1);
cancelDownloadMetadata(torrentIDv2);
}
if (!torrent1 || !torrent2)
emit metadataDownloaded(TorrentInfo(*a->metadata));
}
#endif
void SessionImpl::processTrackerStatuses()
{
for (auto it = m_updatedTrackerEntries.cbegin(); it != m_updatedTrackerEntries.cend(); ++it)
{
auto torrent = static_cast<TorrentImpl *>(it.key());
const QSet<QString> &updatedTrackers = it.value();
for (const QString &trackerURL : updatedTrackers)
torrent->invalidateTrackerEntry(trackerURL);
}
if (!m_updatedTrackerEntries.isEmpty())
{
emit trackerEntriesUpdated(m_updatedTrackerEntries);
m_updatedTrackerEntries.clear();
}
}
void SessionImpl::saveStatistics() const
{
if (!m_isStatisticsDirty)
return;
const QVariantHash stats {
{u"AlltimeDL"_qs, m_status.allTimeDownload},
{u"AlltimeUL"_qs, m_status.allTimeUpload}};
std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_qs);
settings->setValue(u"Stats/AllStats"_qs, stats);
m_statisticsLastUpdateTimer.start();
m_isStatisticsDirty = false;
}
void SessionImpl::loadStatistics()
{
const std::unique_ptr<QSettings> settings = Profile::instance()->applicationSettings(u"qBittorrent-data"_qs);
const QVariantHash value = settings->value(u"Stats/AllStats"_qs).toHash();
m_previouslyDownloaded = value[u"AlltimeDL"_qs].toLongLong();
m_previouslyUploaded = value[u"AlltimeUL"_qs].toLongLong();
}