1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
28 */
29
30 #include "session.h"
31
32 #include <algorithm>
33 #include <cstdint>
34 #include <queue>
35 #include <string>
36 #include <utility>
37
38 #ifdef Q_OS_WIN
39 #include <Windows.h>
40 #include <wincrypt.h>
41 #include <iphlpapi.h>
42 #endif
43
44 #include <libtorrent/alert_types.hpp>
45 #include <libtorrent/bdecode.hpp>
46 #include <libtorrent/bencode.hpp>
47 #include <libtorrent/entry.hpp>
48 #include <libtorrent/error_code.hpp>
49 #include <libtorrent/extensions/smart_ban.hpp>
50 #include <libtorrent/extensions/ut_metadata.hpp>
51 #include <libtorrent/extensions/ut_pex.hpp>
52 #include <libtorrent/ip_filter.hpp>
53 #include <libtorrent/magnet_uri.hpp>
54 #include <libtorrent/read_resume_data.hpp>
55 #include <libtorrent/session.hpp>
56 #include <libtorrent/session_stats.hpp>
57 #include <libtorrent/session_status.hpp>
58 #include <libtorrent/torrent_info.hpp>
59 #include <libtorrent/write_resume_data.hpp>
60
61 #include <QDebug>
62 #include <QDir>
63 #include <QFile>
64 #include <QHostAddress>
65 #include <QNetworkAddressEntry>
66 #include <QNetworkConfigurationManager>
67 #include <QNetworkInterface>
68 #include <QRegularExpression>
69 #include <QString>
70 #include <QThread>
71 #include <QTimer>
72 #include <QUuid>
73
74 #include "base/algorithm.h"
75 #include "base/exceptions.h"
76 #include "base/global.h"
77 #include "base/logger.h"
78 #include "base/net/downloadmanager.h"
79 #include "base/net/proxyconfigurationmanager.h"
80 #include "base/profile.h"
81 #include "base/torrentfileguard.h"
82 #include "base/torrentfilter.h"
83 #include "base/unicodestrings.h"
84 #include "base/utils/bytearray.h"
85 #include "base/utils/fs.h"
86 #include "base/utils/misc.h"
87 #include "base/utils/net.h"
88 #include "base/utils/random.h"
89 #include "base/version.h"
90 #include "bandwidthscheduler.h"
91 #include "common.h"
92 #include "customstorage.h"
93 #include "filesearcher.h"
94 #include "filterparserthread.h"
95 #include "ltunderlyingtype.h"
96 #include "magneturi.h"
97 #include "nativesessionextension.h"
98 #include "portforwarderimpl.h"
99 #include "resumedatasavingmanager.h"
100 #include "statistics.h"
101 #include "torrentimpl.h"
102 #include "tracker.h"
103 #include "trackerentry.h"
104
105 static const char PEER_ID[] = "qB";
106 static const char RESUME_FOLDER[] = "BT_backup";
107 static const char USER_AGENT[] = "qBittorrent/" QBT_VERSION_2;
108
109 using namespace BitTorrent;
110
111 namespace
112 {
113 template <typename LTStr>
fromLTString(const LTStr & str)114 QString fromLTString(const LTStr &str)
115 {
116 return QString::fromUtf8(str.data(), static_cast<int>(str.size()));
117 }
118
torrentQueuePositionUp(const lt::torrent_handle & handle)119 void torrentQueuePositionUp(const lt::torrent_handle &handle)
120 {
121 try
122 {
123 handle.queue_position_up();
124 }
125 catch (const std::exception &exc)
126 {
127 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
128 }
129 }
130
torrentQueuePositionDown(const lt::torrent_handle & handle)131 void torrentQueuePositionDown(const lt::torrent_handle &handle)
132 {
133 try
134 {
135 handle.queue_position_down();
136 }
137 catch (const std::exception &exc)
138 {
139 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
140 }
141 }
142
torrentQueuePositionTop(const lt::torrent_handle & handle)143 void torrentQueuePositionTop(const lt::torrent_handle &handle)
144 {
145 try
146 {
147 handle.queue_position_top();
148 }
149 catch (const std::exception &exc)
150 {
151 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
152 }
153 }
154
torrentQueuePositionBottom(const lt::torrent_handle & handle)155 void torrentQueuePositionBottom(const lt::torrent_handle &handle)
156 {
157 try
158 {
159 handle.queue_position_bottom();
160 }
161 catch (const std::exception &exc)
162 {
163 qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
164 }
165 }
166
map_cast(const QVariantMap & map)167 QStringMap map_cast(const QVariantMap &map)
168 {
169 QStringMap result;
170 for (auto i = map.cbegin(); i != map.cend(); ++i)
171 result[i.key()] = i.value().toString();
172 return result;
173 }
174
map_cast(const QStringMap & map)175 QVariantMap map_cast(const QStringMap &map)
176 {
177 QVariantMap result;
178 for (auto i = map.cbegin(); i != map.cend(); ++i)
179 result[i.key()] = i.value();
180 return result;
181 }
182
normalizePath(const QString & path)183 QString normalizePath(const QString &path)
184 {
185 QString tmp = Utils::Fs::toUniformPath(path.trimmed());
186 if (!tmp.isEmpty() && !tmp.endsWith('/'))
187 return tmp + '/';
188 return tmp;
189 }
190
normalizeSavePath(QString path,const QString & defaultPath=specialFolderLocation (SpecialFolder::Downloads))191 QString normalizeSavePath(QString path, const QString &defaultPath = specialFolderLocation(SpecialFolder::Downloads))
192 {
193 path = path.trimmed();
194 if (path.isEmpty())
195 path = Utils::Fs::toUniformPath(defaultPath.trimmed());
196
197 return normalizePath(path);
198 }
199
expandCategories(const QStringMap & categories)200 QStringMap expandCategories(const QStringMap &categories)
201 {
202 QStringMap expanded = categories;
203
204 for (auto i = categories.cbegin(); i != categories.cend(); ++i)
205 {
206 const QString &category = i.key();
207 for (const QString &subcat : asConst(Session::expandCategory(category)))
208 {
209 if (!expanded.contains(subcat))
210 expanded[subcat] = "";
211 }
212 }
213
214 return expanded;
215 }
216
toString(const lt::socket_type_t socketType)217 QString toString(const lt::socket_type_t socketType)
218 {
219 switch (socketType)
220 {
221 #if (LIBTORRENT_VERSION_NUM >= 20000)
222 case lt::socket_type_t::http:
223 return QLatin1String("HTTP");
224 case lt::socket_type_t::http_ssl:
225 return QLatin1String("HTTP_SSL");
226 #endif
227 case lt::socket_type_t::i2p:
228 return QLatin1String("I2P");
229 case lt::socket_type_t::socks5:
230 return QLatin1String("SOCKS5");
231 #if (LIBTORRENT_VERSION_NUM >= 20000)
232 case lt::socket_type_t::socks5_ssl:
233 return QLatin1String("SOCKS5_SSL");
234 #endif
235 case lt::socket_type_t::tcp:
236 return QLatin1String("TCP");
237 case lt::socket_type_t::tcp_ssl:
238 return QLatin1String("TCP_SSL");
239 #if (LIBTORRENT_VERSION_NUM >= 20000)
240 case lt::socket_type_t::utp:
241 return QLatin1String("UTP");
242 #else
243 case lt::socket_type_t::udp:
244 return QLatin1String("UDP");
245 #endif
246 case lt::socket_type_t::utp_ssl:
247 return QLatin1String("UTP_SSL");
248 }
249 return QLatin1String("INVALID");
250 }
251
toString(const lt::address & address)252 QString toString(const lt::address &address)
253 {
254 try
255 {
256 return QString::fromLatin1(address.to_string().c_str());
257 }
258 catch (const std::exception &)
259 {
260 // suppress conversion error
261 }
262 return {};
263 }
264
265 template <typename T>
266 struct LowerLimited
267 {
LowerLimited__anon73c796b40111::LowerLimited268 LowerLimited(T limit, T ret)
269 : m_limit(limit)
270 , m_ret(ret)
271 {
272 }
273
LowerLimited__anon73c796b40111::LowerLimited274 explicit LowerLimited(T limit)
275 : LowerLimited(limit, limit)
276 {
277 }
278
operator ()__anon73c796b40111::LowerLimited279 T operator()(T val) const
280 {
281 return val <= m_limit ? m_ret : val;
282 }
283
284 private:
285 const T m_limit;
286 const T m_ret;
287 };
288
289 template <typename T>
lowerLimited(T limit)290 LowerLimited<T> lowerLimited(T limit) { return LowerLimited<T>(limit); }
291
292 template <typename T>
lowerLimited(T limit,T ret)293 LowerLimited<T> lowerLimited(T limit, T ret) { return LowerLimited<T>(limit, ret); }
294
295 template <typename T>
clampValue(const T lower,const T upper)296 auto clampValue(const T lower, const T upper)
297 {
298 return [lower, upper](const T value) -> T
299 {
300 if (value < lower)
301 return lower;
302 if (value > upper)
303 return upper;
304 return value;
305 };
306 }
307
308 using ListType = lt::entry::list_type;
309
setToEntryList(const QSet<QString> & input)310 ListType setToEntryList(const QSet<QString> &input)
311 {
312 ListType entryList;
313 entryList.reserve(input.size());
314 for (const QString &setValue : input)
315 entryList.emplace_back(setValue.toStdString());
316 return entryList;
317 }
318
319 #ifdef Q_OS_WIN
convertIfaceNameToGuid(const QString & name)320 QString convertIfaceNameToGuid(const QString &name)
321 {
322 // Under Windows XP or on Qt version <= 5.5 'name' will be a GUID already.
323 const QUuid uuid(name);
324 if (!uuid.isNull())
325 return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
326
327 NET_LUID luid {};
328 const LONG res = ::ConvertInterfaceNameToLuidW(name.toStdWString().c_str(), &luid);
329 if (res == 0)
330 {
331 GUID guid;
332 if (::ConvertInterfaceLuidToGuid(&luid, &guid) == 0)
333 return QUuid(guid).toString().toUpper();
334 }
335
336 return {};
337 }
338 #endif
339 }
340
341 const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
342
343 // Session
344
345 Session *Session::m_instance = nullptr;
346
347 #define BITTORRENT_KEY(name) "BitTorrent/" name
348 #define BITTORRENT_SESSION_KEY(name) BITTORRENT_KEY("Session/") name
349
Session(QObject * parent)350 Session::Session(QObject *parent)
351 : QObject(parent)
352 , m_isDHTEnabled(BITTORRENT_SESSION_KEY("DHTEnabled"), true)
353 , m_isLSDEnabled(BITTORRENT_SESSION_KEY("LSDEnabled"), true)
354 , m_isPeXEnabled(BITTORRENT_SESSION_KEY("PeXEnabled"), true)
355 , m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY("IPFilteringEnabled"), false)
356 , m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY("TrackerFilteringEnabled"), false)
357 , m_IPFilterFile(BITTORRENT_SESSION_KEY("IPFilter"))
358 , m_announceToAllTrackers(BITTORRENT_SESSION_KEY("AnnounceToAllTrackers"), false)
359 , m_announceToAllTiers(BITTORRENT_SESSION_KEY("AnnounceToAllTiers"), true)
360 , m_asyncIOThreads(BITTORRENT_SESSION_KEY("AsyncIOThreadsCount"), 10)
361 , m_hashingThreads(BITTORRENT_SESSION_KEY("HashingThreadsCount"), 2)
362 , m_filePoolSize(BITTORRENT_SESSION_KEY("FilePoolSize"), 5000)
363 , m_checkingMemUsage(BITTORRENT_SESSION_KEY("CheckingMemUsageSize"), 32)
364 , m_diskCacheSize(BITTORRENT_SESSION_KEY("DiskCacheSize"), -1)
365 , m_diskCacheTTL(BITTORRENT_SESSION_KEY("DiskCacheTTL"), 60)
366 , m_useOSCache(BITTORRENT_SESSION_KEY("UseOSCache"), true)
367 #ifdef Q_OS_WIN
368 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY("CoalesceReadWrite"), true)
369 #else
370 , m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY("CoalesceReadWrite"), false)
371 #endif
372 , m_usePieceExtentAffinity(BITTORRENT_SESSION_KEY("PieceExtentAffinity"), false)
373 , m_isSuggestMode(BITTORRENT_SESSION_KEY("SuggestMode"), false)
374 , m_sendBufferWatermark(BITTORRENT_SESSION_KEY("SendBufferWatermark"), 500)
375 , m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY("SendBufferLowWatermark"), 10)
376 , m_sendBufferWatermarkFactor(BITTORRENT_SESSION_KEY("SendBufferWatermarkFactor"), 50)
377 , m_socketBacklogSize(BITTORRENT_SESSION_KEY("SocketBacklogSize"), 30)
378 , m_isAnonymousModeEnabled(BITTORRENT_SESSION_KEY("AnonymousModeEnabled"), false)
379 , m_isQueueingEnabled(BITTORRENT_SESSION_KEY("QueueingSystemEnabled"), false)
380 , m_maxActiveDownloads(BITTORRENT_SESSION_KEY("MaxActiveDownloads"), 3, lowerLimited(-1))
381 , m_maxActiveUploads(BITTORRENT_SESSION_KEY("MaxActiveUploads"), 3, lowerLimited(-1))
382 , m_maxActiveTorrents(BITTORRENT_SESSION_KEY("MaxActiveTorrents"), 5, lowerLimited(-1))
383 , m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY("IgnoreSlowTorrentsForQueueing"), false)
384 , m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY("SlowTorrentsDownloadRate"), 2)
385 , m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY("SlowTorrentsUploadRate"), 2)
386 , m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY("SlowTorrentsInactivityTimer"), 60)
387 , m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
388 , m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
389 , m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
390 , m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x20)
391 , m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
392 , m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
393 , m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
394 , m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY("MaxConcurrentHTTPAnnounces"), 50)
395 , m_stopTrackerTimeout(BITTORRENT_SESSION_KEY("StopTrackerTimeout"), 5)
396 , m_maxConnections(BITTORRENT_SESSION_KEY("MaxConnections"), 500, lowerLimited(0, -1))
397 , m_maxUploads(BITTORRENT_SESSION_KEY("MaxUploads"), 20, lowerLimited(0, -1))
398 , m_maxConnectionsPerTorrent(BITTORRENT_SESSION_KEY("MaxConnectionsPerTorrent"), 100, lowerLimited(0, -1))
399 , m_maxUploadsPerTorrent(BITTORRENT_SESSION_KEY("MaxUploadsPerTorrent"), 4, lowerLimited(0, -1))
400 , m_btProtocol(BITTORRENT_SESSION_KEY("BTProtocol"), BTProtocol::Both
401 , clampValue(BTProtocol::Both, BTProtocol::UTP))
402 , m_isUTPRateLimited(BITTORRENT_SESSION_KEY("uTPRateLimited"), true)
403 , m_utpMixedMode(BITTORRENT_SESSION_KEY("uTPMixedMode"), MixedModeAlgorithm::TCP
404 , clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
405 , m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), false)
406 , m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
407 , m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), true)
408 , m_SSRFMitigationEnabled(BITTORRENT_SESSION_KEY("SSRFMitigation"), true)
409 , m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY("BlockPeersOnPrivilegedPorts"), false)
410 , m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
411 , m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers"))
412 , m_globalMaxRatio(BITTORRENT_SESSION_KEY("GlobalMaxRatio"), -1, [](qreal r) { return r < 0 ? -1. : r;})
413 , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY("GlobalMaxSeedingMinutes"), -1, lowerLimited(-1))
414 , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY("AddTorrentPaused"), false)
415 , m_torrentContentLayout(BITTORRENT_SESSION_KEY("TorrentContentLayout"), TorrentContentLayout::Original)
416 , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY("AddExtensionToIncompleteFiles"), false)
417 , m_refreshInterval(BITTORRENT_SESSION_KEY("RefreshInterval"), 1500)
418 , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY("Preallocation"), false)
419 , m_torrentExportDirectory(BITTORRENT_SESSION_KEY("TorrentExportDirectory"))
420 , m_finishedTorrentExportDirectory(BITTORRENT_SESSION_KEY("FinishedTorrentExportDirectory"))
421 , m_globalDownloadSpeedLimit(BITTORRENT_SESSION_KEY("GlobalDLSpeedLimit"), 0, lowerLimited(0))
422 , m_globalUploadSpeedLimit(BITTORRENT_SESSION_KEY("GlobalUPSpeedLimit"), 0, lowerLimited(0))
423 , m_altGlobalDownloadSpeedLimit(BITTORRENT_SESSION_KEY("AlternativeGlobalDLSpeedLimit"), 10, lowerLimited(0))
424 , m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY("AlternativeGlobalUPSpeedLimit"), 10, lowerLimited(0))
425 , m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY("UseAlternativeGlobalSpeedLimit"), false)
426 , m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY("BandwidthSchedulerEnabled"), false)
427 , m_saveResumeDataInterval(BITTORRENT_SESSION_KEY("SaveResumeDataInterval"), 60)
428 , m_port(BITTORRENT_SESSION_KEY("Port"), -1)
429 , m_useRandomPort(BITTORRENT_SESSION_KEY("UseRandomPort"), false)
430 , m_networkInterface(BITTORRENT_SESSION_KEY("Interface"))
431 , m_networkInterfaceName(BITTORRENT_SESSION_KEY("InterfaceName"))
432 , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY("InterfaceAddress"))
433 , m_encryption(BITTORRENT_SESSION_KEY("Encryption"), 0)
434 , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY("ProxyPeerConnections"), false)
435 , m_chokingAlgorithm(BITTORRENT_SESSION_KEY("ChokingAlgorithm"), ChokingAlgorithm::FixedSlots
436 , clampValue(ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased))
437 , m_seedChokingAlgorithm(BITTORRENT_SESSION_KEY("SeedChokingAlgorithm"), SeedChokingAlgorithm::FastestUpload
438 , clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
439 , m_storedCategories(BITTORRENT_SESSION_KEY("Categories"))
440 , m_storedTags(BITTORRENT_SESSION_KEY("Tags"))
441 , m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause)
442 , m_defaultSavePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads), normalizePath)
443 , m_tempPath(BITTORRENT_SESSION_KEY("TempPath"), defaultSavePath() + "temp/", normalizePath)
444 , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY("SubcategoriesEnabled"), false)
445 , m_isTempPathEnabled(BITTORRENT_SESSION_KEY("TempPathEnabled"), false)
446 , m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY("DisableAutoTMMByDefault"), true)
447 , m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/CategoryChanged"), false)
448 , m_isDisableAutoTMMWhenDefaultSavePathChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/DefaultSavePathChanged"), true)
449 , m_isDisableAutoTMMWhenCategorySavePathChanged(BITTORRENT_SESSION_KEY("DisableAutoTMMTriggers/CategorySavePathChanged"), true)
450 , m_isTrackerEnabled(BITTORRENT_KEY("TrackerEnabled"), false)
451 , m_peerTurnover(BITTORRENT_SESSION_KEY("PeerTurnover"), 4)
452 , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY("PeerTurnoverCutOff"), 90)
453 , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY("PeerTurnoverInterval"), 300)
454 , m_bannedIPs("State/BannedIPs"
455 , QStringList()
456 , [](const QStringList &value)
__anon73c796b40402(const QStringList &value) 457 {
458 QStringList tmp = value;
459 tmp.sort();
460 return tmp;
461 }
462 )
463 #if defined(Q_OS_WIN)
464 , m_OSMemoryPriority(BITTORRENT_KEY("OSMemoryPriority"), OSMemoryPriority::BelowNormal)
465 #endif
466 , m_resumeFolderLock {new QFile {this}}
467 , m_seedingLimitTimer {new QTimer {this}}
468 , m_resumeDataTimer {new QTimer {this}}
469 , m_statistics {new Statistics {this}}
470 , m_ioThread {new QThread {this}}
471 , m_recentErroredTorrentsTimer {new QTimer {this}}
472 , m_networkManager {new QNetworkConfigurationManager {this}}
473 {
474 if (port() < 0)
475 m_port = Utils::Random::rand(1024, 65535);
476
477 initResumeFolder();
478
479 m_recentErroredTorrentsTimer->setSingleShot(true);
480 m_recentErroredTorrentsTimer->setInterval(1000);
481 connect(m_recentErroredTorrentsTimer, &QTimer::timeout
__anon73c796b40502() 482 , this, [this]() { m_recentErroredTorrents.clear(); });
483
484 m_seedingLimitTimer->setInterval(10000);
485 connect(m_seedingLimitTimer, &QTimer::timeout, this, &Session::processShareLimits);
486
487 initializeNativeSession();
488 configureComponents();
489
490 if (isBandwidthSchedulerEnabled())
491 enableBandwidthScheduler();
492
493 m_categories = map_cast(m_storedCategories);
494 if (isSubcategoriesEnabled())
495 {
496 // if subcategories support changed manually
497 m_categories = expandCategories(m_categories);
498 m_storedCategories = map_cast(m_categories);
499 }
500
501 m_tags = List::toSet(m_storedTags.get());
502
503 enqueueRefresh();
504 updateSeedingLimitTimer();
505 populateAdditionalTrackers();
506
507 enableTracker(isTrackerEnabled());
508
509 connect(Net::ProxyConfigurationManager::instance()
510 , &Net::ProxyConfigurationManager::proxyConfigurationChanged
511 , this, &Session::configureDeferred);
512
513 // Network configuration monitor
514 connect(m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &Session::networkOnlineStateChanged);
515 connect(m_networkManager, &QNetworkConfigurationManager::configurationAdded, this, &Session::networkConfigurationChange);
516 connect(m_networkManager, &QNetworkConfigurationManager::configurationRemoved, this, &Session::networkConfigurationChange);
517 connect(m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &Session::networkConfigurationChange);
518
519 m_resumeDataSavingManager = new ResumeDataSavingManager {m_resumeFolderPath};
520 m_resumeDataSavingManager->moveToThread(m_ioThread);
521 connect(m_ioThread, &QThread::finished, m_resumeDataSavingManager, &QObject::deleteLater);
522
523 m_fileSearcher = new FileSearcher;
524 m_fileSearcher->moveToThread(m_ioThread);
525 connect(m_ioThread, &QThread::finished, m_fileSearcher, &QObject::deleteLater);
526 connect(m_fileSearcher, &FileSearcher::searchFinished, this, &Session::fileSearchFinished);
527
528 m_ioThread->start();
529
530 // Regular saving of fastresume data
__anon73c796b40602() 531 connect(m_resumeDataTimer, &QTimer::timeout, this, [this]() { generateResumeData(); });
532 const int saveInterval = saveResumeDataInterval();
533 if (saveInterval > 0)
534 {
535 m_resumeDataTimer->setInterval(saveInterval * 60 * 1000);
536 m_resumeDataTimer->start();
537 }
538
539 // initialize PortForwarder instance
540 new PortForwarderImpl {m_nativeSession};
541
542 initMetrics();
543 }
544
isDHTEnabled() const545 bool Session::isDHTEnabled() const
546 {
547 return m_isDHTEnabled;
548 }
549
setDHTEnabled(bool enabled)550 void Session::setDHTEnabled(bool enabled)
551 {
552 if (enabled != m_isDHTEnabled)
553 {
554 m_isDHTEnabled = enabled;
555 configureDeferred();
556 LogMsg(tr("DHT support [%1]").arg(enabled ? tr("ON") : tr("OFF")), Log::INFO);
557 }
558 }
559
isLSDEnabled() const560 bool Session::isLSDEnabled() const
561 {
562 return m_isLSDEnabled;
563 }
564
setLSDEnabled(const bool enabled)565 void Session::setLSDEnabled(const bool enabled)
566 {
567 if (enabled != m_isLSDEnabled)
568 {
569 m_isLSDEnabled = enabled;
570 configureDeferred();
571 LogMsg(tr("Local Peer Discovery support [%1]").arg(enabled ? tr("ON") : tr("OFF"))
572 , Log::INFO);
573 }
574 }
575
isPeXEnabled() const576 bool Session::isPeXEnabled() const
577 {
578 return m_isPeXEnabled;
579 }
580
setPeXEnabled(const bool enabled)581 void Session::setPeXEnabled(const bool enabled)
582 {
583 m_isPeXEnabled = enabled;
584 if (m_wasPexEnabled != enabled)
585 LogMsg(tr("Restart is required to toggle PeX support"), Log::WARNING);
586 }
587
isTempPathEnabled() const588 bool Session::isTempPathEnabled() const
589 {
590 return m_isTempPathEnabled;
591 }
592
setTempPathEnabled(const bool enabled)593 void Session::setTempPathEnabled(const bool enabled)
594 {
595 if (enabled != isTempPathEnabled())
596 {
597 m_isTempPathEnabled = enabled;
598 for (TorrentImpl *const torrent : asConst(m_torrents))
599 torrent->handleTempPathChanged();
600 }
601 }
602
isAppendExtensionEnabled() const603 bool Session::isAppendExtensionEnabled() const
604 {
605 return m_isAppendExtensionEnabled;
606 }
607
setAppendExtensionEnabled(const bool enabled)608 void Session::setAppendExtensionEnabled(const bool enabled)
609 {
610 if (isAppendExtensionEnabled() != enabled)
611 {
612 m_isAppendExtensionEnabled = enabled;
613
614 // append or remove .!qB extension for incomplete files
615 for (TorrentImpl *const torrent : asConst(m_torrents))
616 torrent->handleAppendExtensionToggled();
617 }
618 }
619
refreshInterval() const620 int Session::refreshInterval() const
621 {
622 return m_refreshInterval;
623 }
624
setRefreshInterval(const int value)625 void Session::setRefreshInterval(const int value)
626 {
627 if (value != refreshInterval())
628 {
629 m_refreshInterval = value;
630 }
631 }
632
isPreallocationEnabled() const633 bool Session::isPreallocationEnabled() const
634 {
635 return m_isPreallocationEnabled;
636 }
637
setPreallocationEnabled(const bool enabled)638 void Session::setPreallocationEnabled(const bool enabled)
639 {
640 m_isPreallocationEnabled = enabled;
641 }
642
torrentExportDirectory() const643 QString Session::torrentExportDirectory() const
644 {
645 return Utils::Fs::toUniformPath(m_torrentExportDirectory);
646 }
647
setTorrentExportDirectory(QString path)648 void Session::setTorrentExportDirectory(QString path)
649 {
650 path = Utils::Fs::toUniformPath(path);
651 if (path != torrentExportDirectory())
652 m_torrentExportDirectory = path;
653 }
654
finishedTorrentExportDirectory() const655 QString Session::finishedTorrentExportDirectory() const
656 {
657 return Utils::Fs::toUniformPath(m_finishedTorrentExportDirectory);
658 }
659
setFinishedTorrentExportDirectory(QString path)660 void Session::setFinishedTorrentExportDirectory(QString path)
661 {
662 path = Utils::Fs::toUniformPath(path);
663 if (path != finishedTorrentExportDirectory())
664 m_finishedTorrentExportDirectory = path;
665 }
666
defaultSavePath() const667 QString Session::defaultSavePath() const
668 {
669 return Utils::Fs::toUniformPath(m_defaultSavePath);
670 }
671
tempPath() const672 QString Session::tempPath() const
673 {
674 return Utils::Fs::toUniformPath(m_tempPath);
675 }
676
torrentTempPath(const TorrentInfo & torrentInfo) const677 QString Session::torrentTempPath(const TorrentInfo &torrentInfo) const
678 {
679 if ((torrentInfo.filesCount() > 1) && !torrentInfo.hasRootFolder())
680 return tempPath() + torrentInfo.name() + '/';
681
682 return tempPath();
683 }
684
isValidCategoryName(const QString & name)685 bool Session::isValidCategoryName(const QString &name)
686 {
687 static const QRegularExpression re(R"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)");
688 if (!name.isEmpty() && (name.indexOf(re) != 0))
689 {
690 qDebug() << "Incorrect category name:" << name;
691 return false;
692 }
693
694 return true;
695 }
696
expandCategory(const QString & category)697 QStringList Session::expandCategory(const QString &category)
698 {
699 QStringList result;
700 if (!isValidCategoryName(category))
701 return result;
702
703 int index = 0;
704 while ((index = category.indexOf('/', index)) >= 0)
705 {
706 result << category.left(index);
707 ++index;
708 }
709 result << category;
710
711 return result;
712 }
713
categories() const714 QStringMap Session::categories() const
715 {
716 return m_categories;
717 }
718
categorySavePath(const QString & categoryName) const719 QString Session::categorySavePath(const QString &categoryName) const
720 {
721 const QString basePath = m_defaultSavePath;
722 if (categoryName.isEmpty()) return basePath;
723
724 QString path = m_categories.value(categoryName);
725 if (path.isEmpty()) // use implicit save path
726 path = Utils::Fs::toValidFileSystemName(categoryName, true);
727
728 if (!QDir::isAbsolutePath(path))
729 path.prepend(basePath);
730
731 return normalizeSavePath(path);
732 }
733
addCategory(const QString & name,const QString & savePath)734 bool Session::addCategory(const QString &name, const QString &savePath)
735 {
736 if (name.isEmpty()) return false;
737 if (!isValidCategoryName(name) || m_categories.contains(name))
738 return false;
739
740 if (isSubcategoriesEnabled())
741 {
742 for (const QString &parent : asConst(expandCategory(name)))
743 {
744 if ((parent != name) && !m_categories.contains(parent))
745 {
746 m_categories[parent] = "";
747 emit categoryAdded(parent);
748 }
749 }
750 }
751
752 m_categories[name] = savePath;
753 m_storedCategories = map_cast(m_categories);
754 emit categoryAdded(name);
755
756 return true;
757 }
758
editCategory(const QString & name,const QString & savePath)759 bool Session::editCategory(const QString &name, const QString &savePath)
760 {
761 if (!m_categories.contains(name)) return false;
762 if (categorySavePath(name) == savePath) return false;
763
764 m_categories[name] = savePath;
765 m_storedCategories = map_cast(m_categories);
766 if (isDisableAutoTMMWhenCategorySavePathChanged())
767 {
768 for (TorrentImpl *const torrent : asConst(m_torrents))
769 if (torrent->category() == name)
770 torrent->setAutoTMMEnabled(false);
771 }
772 else
773 {
774 for (TorrentImpl *const torrent : asConst(m_torrents))
775 if (torrent->category() == name)
776 torrent->handleCategorySavePathChanged();
777 }
778
779 return true;
780 }
781
removeCategory(const QString & name)782 bool Session::removeCategory(const QString &name)
783 {
784 for (TorrentImpl *const torrent : asConst(m_torrents))
785 if (torrent->belongsToCategory(name))
786 torrent->setCategory("");
787
788 // remove stored category and its subcategories if exist
789 bool result = false;
790 if (isSubcategoriesEnabled())
791 {
792 // remove subcategories
793 const QString test = name + '/';
794 Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const QString &)
795 {
796 if (category.startsWith(test))
797 {
798 result = true;
799 emit categoryRemoved(category);
800 return true;
801 }
802 return false;
803 });
804 }
805
806 result = (m_categories.remove(name) > 0) || result;
807
808 if (result)
809 {
810 // update stored categories
811 m_storedCategories = map_cast(m_categories);
812 emit categoryRemoved(name);
813 }
814
815 return result;
816 }
817
isSubcategoriesEnabled() const818 bool Session::isSubcategoriesEnabled() const
819 {
820 return m_isSubcategoriesEnabled;
821 }
822
setSubcategoriesEnabled(const bool value)823 void Session::setSubcategoriesEnabled(const bool value)
824 {
825 if (isSubcategoriesEnabled() == value) return;
826
827 if (value)
828 {
829 // expand categories to include all parent categories
830 m_categories = expandCategories(m_categories);
831 // update stored categories
832 m_storedCategories = map_cast(m_categories);
833 }
834 else
835 {
836 // reload categories
837 m_categories = map_cast(m_storedCategories);
838 }
839
840 m_isSubcategoriesEnabled = value;
841 emit subcategoriesSupportChanged();
842 }
843
tags() const844 QSet<QString> Session::tags() const
845 {
846 return m_tags;
847 }
848
isValidTag(const QString & tag)849 bool Session::isValidTag(const QString &tag)
850 {
851 return (!tag.trimmed().isEmpty() && !tag.contains(','));
852 }
853
hasTag(const QString & tag) const854 bool Session::hasTag(const QString &tag) const
855 {
856 return m_tags.contains(tag);
857 }
858
addTag(const QString & tag)859 bool Session::addTag(const QString &tag)
860 {
861 if (!isValidTag(tag) || hasTag(tag))
862 return false;
863
864 m_tags.insert(tag);
865 m_storedTags = m_tags.values();
866 emit tagAdded(tag);
867 return true;
868 }
869
removeTag(const QString & tag)870 bool Session::removeTag(const QString &tag)
871 {
872 if (m_tags.remove(tag))
873 {
874 for (TorrentImpl *const torrent : asConst(m_torrents))
875 torrent->removeTag(tag);
876 m_storedTags = m_tags.values();
877 emit tagRemoved(tag);
878 return true;
879 }
880 return false;
881 }
882
isAutoTMMDisabledByDefault() const883 bool Session::isAutoTMMDisabledByDefault() const
884 {
885 return m_isAutoTMMDisabledByDefault;
886 }
887
setAutoTMMDisabledByDefault(const bool value)888 void Session::setAutoTMMDisabledByDefault(const bool value)
889 {
890 m_isAutoTMMDisabledByDefault = value;
891 }
892
isDisableAutoTMMWhenCategoryChanged() const893 bool Session::isDisableAutoTMMWhenCategoryChanged() const
894 {
895 return m_isDisableAutoTMMWhenCategoryChanged;
896 }
897
setDisableAutoTMMWhenCategoryChanged(const bool value)898 void Session::setDisableAutoTMMWhenCategoryChanged(const bool value)
899 {
900 m_isDisableAutoTMMWhenCategoryChanged = value;
901 }
902
isDisableAutoTMMWhenDefaultSavePathChanged() const903 bool Session::isDisableAutoTMMWhenDefaultSavePathChanged() const
904 {
905 return m_isDisableAutoTMMWhenDefaultSavePathChanged;
906 }
907
setDisableAutoTMMWhenDefaultSavePathChanged(const bool value)908 void Session::setDisableAutoTMMWhenDefaultSavePathChanged(const bool value)
909 {
910 m_isDisableAutoTMMWhenDefaultSavePathChanged = value;
911 }
912
isDisableAutoTMMWhenCategorySavePathChanged() const913 bool Session::isDisableAutoTMMWhenCategorySavePathChanged() const
914 {
915 return m_isDisableAutoTMMWhenCategorySavePathChanged;
916 }
917
setDisableAutoTMMWhenCategorySavePathChanged(const bool value)918 void Session::setDisableAutoTMMWhenCategorySavePathChanged(const bool value)
919 {
920 m_isDisableAutoTMMWhenCategorySavePathChanged = value;
921 }
922
isAddTorrentPaused() const923 bool Session::isAddTorrentPaused() const
924 {
925 return m_isAddTorrentPaused;
926 }
927
setAddTorrentPaused(const bool value)928 void Session::setAddTorrentPaused(const bool value)
929 {
930 m_isAddTorrentPaused = value;
931 }
932
isTrackerEnabled() const933 bool Session::isTrackerEnabled() const
934 {
935 return m_isTrackerEnabled;
936 }
937
setTrackerEnabled(const bool enabled)938 void Session::setTrackerEnabled(const bool enabled)
939 {
940 if (m_isTrackerEnabled != enabled)
941 m_isTrackerEnabled = enabled;
942
943 // call enableTracker() unconditionally, otherwise port change won't trigger
944 // tracker restart
945 enableTracker(enabled);
946 }
947
globalMaxRatio() const948 qreal Session::globalMaxRatio() const
949 {
950 return m_globalMaxRatio;
951 }
952
953 // Torrents with a ratio superior to the given value will
954 // be automatically deleted
setGlobalMaxRatio(qreal ratio)955 void Session::setGlobalMaxRatio(qreal ratio)
956 {
957 if (ratio < 0)
958 ratio = -1.;
959
960 if (ratio != globalMaxRatio())
961 {
962 m_globalMaxRatio = ratio;
963 updateSeedingLimitTimer();
964 }
965 }
966
globalMaxSeedingMinutes() const967 int Session::globalMaxSeedingMinutes() const
968 {
969 return m_globalMaxSeedingMinutes;
970 }
971
setGlobalMaxSeedingMinutes(int minutes)972 void Session::setGlobalMaxSeedingMinutes(int minutes)
973 {
974 if (minutes < 0)
975 minutes = -1;
976
977 if (minutes != globalMaxSeedingMinutes())
978 {
979 m_globalMaxSeedingMinutes = minutes;
980 updateSeedingLimitTimer();
981 }
982 }
983
984 // Main destructor
~Session()985 Session::~Session()
986 {
987 // Do some BT related saving
988 saveResumeData();
989
990 // We must delete FilterParserThread
991 // before we delete lt::session
992 delete m_filterParser;
993
994 // We must delete PortForwarderImpl before
995 // we delete lt::session
996 delete Net::PortForwarder::instance();
997
998 qDebug("Deleting the session");
999 delete m_nativeSession;
1000
1001 m_ioThread->quit();
1002 m_ioThread->wait();
1003
1004 m_resumeFolderLock->close();
1005 m_resumeFolderLock->remove();
1006 }
1007
initInstance()1008 void Session::initInstance()
1009 {
1010 if (!m_instance)
1011 m_instance = new Session;
1012 }
1013
freeInstance()1014 void Session::freeInstance()
1015 {
1016 delete m_instance;
1017 m_instance = nullptr;
1018 }
1019
instance()1020 Session *Session::instance()
1021 {
1022 return m_instance;
1023 }
1024
adjustLimits()1025 void Session::adjustLimits()
1026 {
1027 if (isQueueingSystemEnabled())
1028 {
1029 lt::settings_pack settingsPack = m_nativeSession->get_settings();
1030 adjustLimits(settingsPack);
1031 m_nativeSession->apply_settings(settingsPack);
1032 }
1033 }
1034
applyBandwidthLimits()1035 void Session::applyBandwidthLimits()
1036 {
1037 lt::settings_pack settingsPack = m_nativeSession->get_settings();
1038 applyBandwidthLimits(settingsPack);
1039 m_nativeSession->apply_settings(settingsPack);
1040 }
1041
configure()1042 void Session::configure()
1043 {
1044 lt::settings_pack settingsPack = m_nativeSession->get_settings();
1045 loadLTSettings(settingsPack);
1046 m_nativeSession->apply_settings(settingsPack);
1047
1048 configureComponents();
1049
1050 m_deferredConfigureScheduled = false;
1051 }
1052
configureComponents()1053 void Session::configureComponents()
1054 {
1055 // This function contains components/actions that:
1056 // 1. Need to be setup at start up
1057 // 2. When deferred configure is called
1058
1059 configurePeerClasses();
1060
1061 if (!m_IPFilteringConfigured)
1062 {
1063 if (isIPFilteringEnabled())
1064 enableIPFilter();
1065 else
1066 disableIPFilter();
1067 m_IPFilteringConfigured = true;
1068 }
1069
1070 #if defined(Q_OS_WIN)
1071 applyOSMemoryPriority();
1072 #endif
1073 }
1074
initializeNativeSession()1075 void Session::initializeNativeSession()
1076 {
1077 const lt::alert_category_t alertMask = lt::alert::error_notification
1078 | lt::alert::file_progress_notification
1079 | lt::alert::ip_block_notification
1080 | lt::alert::peer_notification
1081 | lt::alert::performance_warning
1082 | lt::alert::port_mapping_notification
1083 | lt::alert::status_notification
1084 | lt::alert::storage_notification
1085 | lt::alert::tracker_notification;
1086 const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
1087
1088 lt::settings_pack pack;
1089 pack.set_int(lt::settings_pack::alert_mask, alertMask);
1090 pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
1091 pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
1092 pack.set_str(lt::settings_pack::user_agent, USER_AGENT);
1093 pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
1094 // Speed up exit
1095 pack.set_int(lt::settings_pack::auto_scrape_interval, 1200); // 20 minutes
1096 pack.set_int(lt::settings_pack::auto_scrape_min_interval, 900); // 15 minutes
1097 pack.set_int(lt::settings_pack::connection_speed, 20); // default is 10
1098 // libtorrent 1.1 enables UPnP & NAT-PMP by default
1099 // turn them off before `lt::session` ctor to avoid split second effects
1100 pack.set_bool(lt::settings_pack::enable_upnp, false);
1101 pack.set_bool(lt::settings_pack::enable_natpmp, false);
1102
1103 #if (LIBTORRENT_VERSION_NUM > 20000)
1104 // preserve the same behavior as in earlier libtorrent versions
1105 pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
1106 #endif
1107
1108 loadLTSettings(pack);
1109 lt::session_params sessionParams {pack, {}};
1110 #if (LIBTORRENT_VERSION_NUM >= 20000)
1111 sessionParams.disk_io_constructor = customDiskIOConstructor;
1112 #endif
1113 m_nativeSession = new lt::session {sessionParams};
1114
1115 LogMsg(tr("Peer ID: ") + QString::fromStdString(peerId));
1116 LogMsg(tr("HTTP User-Agent is '%1'").arg(USER_AGENT));
1117 LogMsg(tr("DHT support [%1]").arg(isDHTEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1118 LogMsg(tr("Local Peer Discovery support [%1]").arg(isLSDEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1119 LogMsg(tr("PeX support [%1]").arg(isPeXEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1120 LogMsg(tr("Anonymous mode [%1]").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF")), Log::INFO);
1121 LogMsg(tr("Encryption support [%1]").arg((encryption() == 0) ? tr("ON") :
1122 ((encryption() == 1) ? tr("FORCED") : tr("OFF"))), Log::INFO);
1123
1124 m_nativeSession->set_alert_notify([this]()
1125 {
1126 QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection);
1127 });
1128
1129 // Enabling plugins
1130 m_nativeSession->add_extension(<::create_smart_ban_plugin);
1131 m_nativeSession->add_extension(<::create_ut_metadata_plugin);
1132 if (isPeXEnabled())
1133 m_nativeSession->add_extension(<::create_ut_pex_plugin);
1134
1135 m_nativeSession->add_extension(std::make_shared<NativeSessionExtension>());
1136 }
1137
processBannedIPs(lt::ip_filter & filter)1138 void Session::processBannedIPs(lt::ip_filter &filter)
1139 {
1140 // First, import current filter
1141 for (const QString &ip : asConst(m_bannedIPs.get()))
1142 {
1143 lt::error_code ec;
1144 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1145 Q_ASSERT(!ec);
1146 if (!ec)
1147 filter.add_rule(addr, addr, lt::ip_filter::blocked);
1148 }
1149 }
1150
adjustLimits(lt::settings_pack & settingsPack) const1151 void Session::adjustLimits(lt::settings_pack &settingsPack) const
1152 {
1153 // Internally increase the queue limits to ensure that the magnet is started
1154 const int maxDownloads = maxActiveDownloads();
1155 const int maxActive = maxActiveTorrents();
1156
1157 settingsPack.set_int(lt::settings_pack::active_downloads
1158 , maxDownloads > -1 ? maxDownloads + m_extraLimit : maxDownloads);
1159 settingsPack.set_int(lt::settings_pack::active_limit
1160 , maxActive > -1 ? maxActive + m_extraLimit : maxActive);
1161 }
1162
applyBandwidthLimits(lt::settings_pack & settingsPack) const1163 void Session::applyBandwidthLimits(lt::settings_pack &settingsPack) const
1164 {
1165 const bool altSpeedLimitEnabled = isAltGlobalSpeedLimitEnabled();
1166 settingsPack.set_int(lt::settings_pack::download_rate_limit, altSpeedLimitEnabled ? altGlobalDownloadSpeedLimit() : globalDownloadSpeedLimit());
1167 settingsPack.set_int(lt::settings_pack::upload_rate_limit, altSpeedLimitEnabled ? altGlobalUploadSpeedLimit() : globalUploadSpeedLimit());
1168 }
1169
initMetrics()1170 void Session::initMetrics()
1171 {
1172 const auto findMetricIndex = [](const char *name) -> int
1173 {
1174 const int index = lt::find_metric_idx(name);
1175 Q_ASSERT(index >= 0);
1176 return index;
1177 };
1178
1179 // TODO: switch to "designated initializers" in C++20
1180 m_metricIndices.net.hasIncomingConnections = findMetricIndex("net.has_incoming_connections");
1181 m_metricIndices.net.sentPayloadBytes = findMetricIndex("net.sent_payload_bytes");
1182 m_metricIndices.net.recvPayloadBytes = findMetricIndex("net.recv_payload_bytes");
1183 m_metricIndices.net.sentBytes = findMetricIndex("net.sent_bytes");
1184 m_metricIndices.net.recvBytes = findMetricIndex("net.recv_bytes");
1185 m_metricIndices.net.sentIPOverheadBytes = findMetricIndex("net.sent_ip_overhead_bytes");
1186 m_metricIndices.net.recvIPOverheadBytes = findMetricIndex("net.recv_ip_overhead_bytes");
1187 m_metricIndices.net.sentTrackerBytes = findMetricIndex("net.sent_tracker_bytes");
1188 m_metricIndices.net.recvTrackerBytes = findMetricIndex("net.recv_tracker_bytes");
1189 m_metricIndices.net.recvRedundantBytes = findMetricIndex("net.recv_redundant_bytes");
1190 m_metricIndices.net.recvFailedBytes = findMetricIndex("net.recv_failed_bytes");
1191
1192 m_metricIndices.peer.numPeersConnected = findMetricIndex("peer.num_peers_connected");
1193 m_metricIndices.peer.numPeersDownDisk = findMetricIndex("peer.num_peers_down_disk");
1194 m_metricIndices.peer.numPeersUpDisk = findMetricIndex("peer.num_peers_up_disk");
1195
1196 m_metricIndices.dht.dhtBytesIn = findMetricIndex("dht.dht_bytes_in");
1197 m_metricIndices.dht.dhtBytesOut = findMetricIndex("dht.dht_bytes_out");
1198 m_metricIndices.dht.dhtNodes = findMetricIndex("dht.dht_nodes");
1199
1200 m_metricIndices.disk.diskBlocksInUse = findMetricIndex("disk.disk_blocks_in_use");
1201 m_metricIndices.disk.numBlocksRead = findMetricIndex("disk.num_blocks_read");
1202 #if (LIBTORRENT_VERSION_NUM < 20000)
1203 m_metricIndices.disk.numBlocksCacheHits = findMetricIndex("disk.num_blocks_cache_hits");
1204 #endif
1205 m_metricIndices.disk.writeJobs = findMetricIndex("disk.num_write_ops");
1206 m_metricIndices.disk.readJobs = findMetricIndex("disk.num_read_ops");
1207 m_metricIndices.disk.hashJobs = findMetricIndex("disk.num_blocks_hashed");
1208 m_metricIndices.disk.queuedDiskJobs = findMetricIndex("disk.queued_disk_jobs");
1209 m_metricIndices.disk.diskJobTime = findMetricIndex("disk.disk_job_time");
1210 }
1211
loadLTSettings(lt::settings_pack & settingsPack)1212 void Session::loadLTSettings(lt::settings_pack &settingsPack)
1213 {
1214 // from libtorrent doc:
1215 // It will not take affect until the listen_interfaces settings is updated
1216 settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
1217
1218 configureNetworkInterfaces(settingsPack);
1219 applyBandwidthLimits(settingsPack);
1220
1221 // The most secure, rc4 only so that all streams are encrypted
1222 settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
1223 settingsPack.set_bool(lt::settings_pack::prefer_rc4, true);
1224 switch (encryption())
1225 {
1226 case 0: // Enabled
1227 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_enabled);
1228 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_enabled);
1229 break;
1230 case 1: // Forced
1231 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_forced);
1232 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_forced);
1233 break;
1234 default: // Disabled
1235 settingsPack.set_int(lt::settings_pack::out_enc_policy, lt::settings_pack::pe_disabled);
1236 settingsPack.set_int(lt::settings_pack::in_enc_policy, lt::settings_pack::pe_disabled);
1237 }
1238
1239 // proxy
1240 const auto proxyManager = Net::ProxyConfigurationManager::instance();
1241 const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
1242
1243 switch (proxyConfig.type)
1244 {
1245 case Net::ProxyType::HTTP:
1246 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http);
1247 break;
1248 case Net::ProxyType::HTTP_PW:
1249 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::http_pw);
1250 break;
1251 case Net::ProxyType::SOCKS4:
1252 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks4);
1253 break;
1254 case Net::ProxyType::SOCKS5:
1255 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
1256 break;
1257 case Net::ProxyType::SOCKS5_PW:
1258 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5_pw);
1259 break;
1260 case Net::ProxyType::None:
1261 default:
1262 settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
1263 }
1264
1265 if (proxyConfig.type != Net::ProxyType::None)
1266 {
1267 settingsPack.set_str(lt::settings_pack::proxy_hostname, proxyConfig.ip.toStdString());
1268 settingsPack.set_int(lt::settings_pack::proxy_port, proxyConfig.port);
1269
1270 if (proxyManager->isAuthenticationRequired())
1271 {
1272 settingsPack.set_str(lt::settings_pack::proxy_username, proxyConfig.username.toStdString());
1273 settingsPack.set_str(lt::settings_pack::proxy_password, proxyConfig.password.toStdString());
1274 }
1275
1276 settingsPack.set_bool(lt::settings_pack::proxy_peer_connections, isProxyPeerConnectionsEnabled());
1277 }
1278
1279 settingsPack.set_bool(lt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
1280 settingsPack.set_bool(lt::settings_pack::announce_to_all_tiers, announceToAllTiers());
1281
1282 settingsPack.set_int(lt::settings_pack::peer_turnover, peerTurnover());
1283 settingsPack.set_int(lt::settings_pack::peer_turnover_cutoff, peerTurnoverCutoff());
1284 settingsPack.set_int(lt::settings_pack::peer_turnover_interval, peerTurnoverInterval());
1285
1286 settingsPack.set_int(lt::settings_pack::aio_threads, asyncIOThreads());
1287 #if (LIBTORRENT_VERSION_NUM >= 20000)
1288 settingsPack.set_int(lt::settings_pack::hashing_threads, hashingThreads());
1289 #endif
1290 settingsPack.set_int(lt::settings_pack::file_pool_size, filePoolSize());
1291
1292 const int checkingMemUsageSize = checkingMemUsage() * 64;
1293 settingsPack.set_int(lt::settings_pack::checking_mem_usage, checkingMemUsageSize);
1294
1295 #if (LIBTORRENT_VERSION_NUM < 20000)
1296 const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
1297 settingsPack.set_int(lt::settings_pack::cache_size, cacheSize);
1298 settingsPack.set_int(lt::settings_pack::cache_expiry, diskCacheTTL());
1299 #endif
1300
1301 lt::settings_pack::io_buffer_mode_t mode = useOSCache() ? lt::settings_pack::enable_os_cache
1302 : lt::settings_pack::disable_os_cache;
1303 settingsPack.set_int(lt::settings_pack::disk_io_read_mode, mode);
1304 settingsPack.set_int(lt::settings_pack::disk_io_write_mode, mode);
1305
1306 #if (LIBTORRENT_VERSION_NUM < 20000)
1307 settingsPack.set_bool(lt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
1308 settingsPack.set_bool(lt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
1309 #endif
1310
1311 settingsPack.set_bool(lt::settings_pack::piece_extent_affinity, usePieceExtentAffinity());
1312
1313 settingsPack.set_int(lt::settings_pack::suggest_mode, isSuggestModeEnabled()
1314 ? lt::settings_pack::suggest_read_cache : lt::settings_pack::no_piece_suggestions);
1315
1316 settingsPack.set_int(lt::settings_pack::send_buffer_watermark, sendBufferWatermark() * 1024);
1317 settingsPack.set_int(lt::settings_pack::send_buffer_low_watermark, sendBufferLowWatermark() * 1024);
1318 settingsPack.set_int(lt::settings_pack::send_buffer_watermark_factor, sendBufferWatermarkFactor());
1319
1320 settingsPack.set_bool(lt::settings_pack::anonymous_mode, isAnonymousModeEnabled());
1321
1322 // Queueing System
1323 if (isQueueingSystemEnabled())
1324 {
1325 adjustLimits(settingsPack);
1326
1327 settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
1328 settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
1329 settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
1330 settingsPack.set_int(lt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
1331 settingsPack.set_int(lt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
1332 }
1333 else
1334 {
1335 settingsPack.set_int(lt::settings_pack::active_downloads, -1);
1336 settingsPack.set_int(lt::settings_pack::active_seeds, -1);
1337 settingsPack.set_int(lt::settings_pack::active_limit, -1);
1338 }
1339 settingsPack.set_int(lt::settings_pack::active_tracker_limit, -1);
1340 settingsPack.set_int(lt::settings_pack::active_dht_limit, -1);
1341 settingsPack.set_int(lt::settings_pack::active_lsd_limit, -1);
1342 settingsPack.set_int(lt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
1343
1344 // Outgoing ports
1345 settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
1346 settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1);
1347 // UPnP lease duration
1348 settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
1349 // Type of service
1350 settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
1351 // Include overhead in transfer limits
1352 settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
1353 // IP address to announce to trackers
1354 settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
1355 // Max concurrent HTTP announces
1356 settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
1357 // Stop tracker timeout
1358 settingsPack.set_int(lt::settings_pack::stop_tracker_timeout, stopTrackerTimeout());
1359 // * Max connections limit
1360 settingsPack.set_int(lt::settings_pack::connections_limit, maxConnections());
1361 // * Global max upload slots
1362 settingsPack.set_int(lt::settings_pack::unchoke_slots_limit, maxUploads());
1363 // uTP
1364 switch (btProtocol())
1365 {
1366 case BTProtocol::Both:
1367 default:
1368 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1369 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1370 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1371 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1372 break;
1373
1374 case BTProtocol::TCP:
1375 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, true);
1376 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, true);
1377 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, false);
1378 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
1379 break;
1380
1381 case BTProtocol::UTP:
1382 settingsPack.set_bool(lt::settings_pack::enable_incoming_tcp, false);
1383 settingsPack.set_bool(lt::settings_pack::enable_outgoing_tcp, false);
1384 settingsPack.set_bool(lt::settings_pack::enable_incoming_utp, true);
1385 settingsPack.set_bool(lt::settings_pack::enable_outgoing_utp, true);
1386 break;
1387 }
1388
1389 switch (utpMixedMode())
1390 {
1391 case MixedModeAlgorithm::TCP:
1392 default:
1393 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::prefer_tcp);
1394 break;
1395 case MixedModeAlgorithm::Proportional:
1396 settingsPack.set_int(lt::settings_pack::mixed_mode_algorithm, lt::settings_pack::peer_proportional);
1397 break;
1398 }
1399
1400 settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
1401
1402 settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
1403
1404 settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
1405
1406 settingsPack.set_bool(lt::settings_pack::ssrf_mitigation, isSSRFMitigationEnabled());
1407
1408 settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
1409
1410 settingsPack.set_bool(lt::settings_pack::apply_ip_filter_to_trackers, isTrackerFilteringEnabled());
1411
1412 settingsPack.set_bool(lt::settings_pack::enable_dht, isDHTEnabled());
1413 if (isDHTEnabled())
1414 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");
1415 settingsPack.set_bool(lt::settings_pack::enable_lsd, isLSDEnabled());
1416
1417 switch (chokingAlgorithm())
1418 {
1419 case ChokingAlgorithm::FixedSlots:
1420 default:
1421 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::fixed_slots_choker);
1422 break;
1423 case ChokingAlgorithm::RateBased:
1424 settingsPack.set_int(lt::settings_pack::choking_algorithm, lt::settings_pack::rate_based_choker);
1425 break;
1426 }
1427
1428 switch (seedChokingAlgorithm())
1429 {
1430 case SeedChokingAlgorithm::RoundRobin:
1431 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::round_robin);
1432 break;
1433 case SeedChokingAlgorithm::FastestUpload:
1434 default:
1435 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::fastest_upload);
1436 break;
1437 case SeedChokingAlgorithm::AntiLeech:
1438 settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
1439 break;
1440 }
1441 }
1442
configureNetworkInterfaces(lt::settings_pack & settingsPack)1443 void Session::configureNetworkInterfaces(lt::settings_pack &settingsPack)
1444 {
1445 if (m_listenInterfaceConfigured)
1446 return;
1447
1448 const int port = useRandomPort() ? 0 : this->port();
1449 if (port > 0) // user specified port
1450 settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);
1451
1452 QStringList endpoints;
1453 QStringList outgoingInterfaces;
1454 const QString portString = ':' + QString::number(port);
1455
1456 for (const QString &ip : asConst(getListeningIPs()))
1457 {
1458 const QHostAddress addr {ip};
1459 if (!addr.isNull())
1460 {
1461 const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
1462 const QString ip = isIPv6
1463 ? Utils::Net::canonicalIPv6Addr(addr).toString()
1464 : addr.toString();
1465
1466 endpoints << ((isIPv6 ? ('[' + ip + ']') : ip) + portString);
1467
1468 if ((ip != QLatin1String("0.0.0.0")) && (ip != QLatin1String("::")))
1469 outgoingInterfaces << ip;
1470 }
1471 else
1472 {
1473 // ip holds an interface name
1474 #ifdef Q_OS_WIN
1475 // On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
1476 // the interface's LUID and not the GUID.
1477 // Libtorrent expects GUIDs for the 'listen_interfaces' setting.
1478 const QString guid = convertIfaceNameToGuid(ip);
1479 if (!guid.isEmpty())
1480 {
1481 endpoints << (guid + portString);
1482 outgoingInterfaces << guid;
1483 }
1484 else
1485 {
1486 LogMsg(tr("Could not get GUID of network interface: %1").arg(ip) , Log::WARNING);
1487 // Since we can't get the GUID, we'll pass the interface name instead.
1488 // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
1489 endpoints << (ip + portString);
1490 outgoingInterfaces << ip;
1491 }
1492 #else
1493 endpoints << (ip + portString);
1494 outgoingInterfaces << ip;
1495 #endif
1496 }
1497 }
1498
1499 const QString finalEndpoints = endpoints.join(',');
1500 settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
1501 LogMsg(tr("Trying to listen on: %1", "e.g: Trying to listen on: 192.168.0.1:6881")
1502 .arg(finalEndpoints), Log::INFO);
1503
1504 settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(',').toStdString());
1505 m_listenInterfaceConfigured = true;
1506 }
1507
configurePeerClasses()1508 void Session::configurePeerClasses()
1509 {
1510 lt::ip_filter f;
1511 // lt::make_address("255.255.255.255") crashes on some people's systems
1512 // so instead we use address_v4::broadcast()
1513 // Proactively do the same for 0.0.0.0 and address_v4::any()
1514 f.add_rule(lt::address_v4::any()
1515 , lt::address_v4::broadcast()
1516 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::global_peer_class_id));
1517
1518 // IPv6 may not be available on OS and the parsing
1519 // would result in an exception -> abnormal program termination
1520 // Affects Windows XP
1521 try
1522 {
1523 f.add_rule(lt::address_v6::any()
1524 , lt::make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1525 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::global_peer_class_id));
1526 }
1527 catch (const std::exception &) {}
1528
1529 if (ignoreLimitsOnLAN())
1530 {
1531 // local networks
1532 f.add_rule(lt::make_address("10.0.0.0")
1533 , lt::make_address("10.255.255.255")
1534 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::local_peer_class_id));
1535 f.add_rule(lt::make_address("172.16.0.0")
1536 , lt::make_address("172.31.255.255")
1537 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::local_peer_class_id));
1538 f.add_rule(lt::make_address("192.168.0.0")
1539 , lt::make_address("192.168.255.255")
1540 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::local_peer_class_id));
1541 // link local
1542 f.add_rule(lt::make_address("169.254.0.0")
1543 , lt::make_address("169.254.255.255")
1544 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::local_peer_class_id));
1545 // loopback
1546 f.add_rule(lt::make_address("127.0.0.0")
1547 , lt::make_address("127.255.255.255")
1548 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::local_peer_class_id));
1549
1550 // IPv6 may not be available on OS and the parsing
1551 // would result in an exception -> abnormal program termination
1552 // Affects Windows XP
1553 try
1554 {
1555 // link local
1556 f.add_rule(lt::make_address("fe80::")
1557 , lt::make_address("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1558 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::local_peer_class_id));
1559 // unique local addresses
1560 f.add_rule(lt::make_address("fc00::")
1561 , lt::make_address("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
1562 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::local_peer_class_id));
1563 // loopback
1564 f.add_rule(lt::address_v6::loopback()
1565 , lt::address_v6::loopback()
1566 , 1 << static_cast<LTUnderlyingType<lt::peer_class_t>>(lt::session::local_peer_class_id));
1567 }
1568 catch (const std::exception &) {}
1569 }
1570 m_nativeSession->set_peer_class_filter(f);
1571
1572 lt::peer_class_type_filter peerClassTypeFilter;
1573 peerClassTypeFilter.add(lt::peer_class_type_filter::tcp_socket, lt::session::tcp_peer_class_id);
1574 peerClassTypeFilter.add(lt::peer_class_type_filter::ssl_tcp_socket, lt::session::tcp_peer_class_id);
1575 peerClassTypeFilter.add(lt::peer_class_type_filter::i2p_socket, lt::session::tcp_peer_class_id);
1576 if (!isUTPRateLimited())
1577 {
1578 peerClassTypeFilter.disallow(lt::peer_class_type_filter::utp_socket
1579 , lt::session::global_peer_class_id);
1580 peerClassTypeFilter.disallow(lt::peer_class_type_filter::ssl_utp_socket
1581 , lt::session::global_peer_class_id);
1582 }
1583 m_nativeSession->set_peer_class_type_filter(peerClassTypeFilter);
1584 }
1585
enableTracker(const bool enable)1586 void Session::enableTracker(const bool enable)
1587 {
1588 if (enable)
1589 {
1590 if (!m_tracker)
1591 m_tracker = new Tracker(this);
1592
1593 m_tracker->start();
1594 }
1595 else
1596 {
1597 delete m_tracker;
1598 }
1599 }
1600
enableBandwidthScheduler()1601 void Session::enableBandwidthScheduler()
1602 {
1603 if (!m_bwScheduler)
1604 {
1605 m_bwScheduler = new BandwidthScheduler(this);
1606 connect(m_bwScheduler.data(), &BandwidthScheduler::bandwidthLimitRequested
1607 , this, &Session::setAltGlobalSpeedLimitEnabled);
1608 }
1609 m_bwScheduler->start();
1610 }
1611
populateAdditionalTrackers()1612 void Session::populateAdditionalTrackers()
1613 {
1614 m_additionalTrackerList.clear();
1615
1616 const QString trackers = additionalTrackers();
1617 for (QStringRef tracker : asConst(trackers.splitRef('\n')))
1618 {
1619 tracker = tracker.trimmed();
1620 if (!tracker.isEmpty())
1621 m_additionalTrackerList.append({tracker.toString()});
1622 }
1623 }
1624
processShareLimits()1625 void Session::processShareLimits()
1626 {
1627 qDebug("Processing share limits...");
1628
1629 // We shouldn't iterate over `m_torrents` in the loop below
1630 // since `deleteTorrent()` modifies it indirectly
1631 const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
1632 for (TorrentImpl *const torrent : torrents)
1633 {
1634 if (torrent->isSeed() && !torrent->isForced())
1635 {
1636 if (torrent->ratioLimit() != Torrent::NO_RATIO_LIMIT)
1637 {
1638 const qreal ratio = torrent->realRatio();
1639 qreal ratioLimit = torrent->ratioLimit();
1640 if (ratioLimit == Torrent::USE_GLOBAL_RATIO)
1641 // If Global Max Ratio is really set...
1642 ratioLimit = globalMaxRatio();
1643
1644 if (ratioLimit >= 0)
1645 {
1646 qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
1647
1648 if ((ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
1649 {
1650 if (m_maxRatioAction == Remove)
1651 {
1652 LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
1653 deleteTorrent(torrent->id());
1654 }
1655 else if (m_maxRatioAction == DeleteFiles)
1656 {
1657 LogMsg(tr("'%1' reached the maximum ratio you set. Removed torrent and its files.").arg(torrent->name()));
1658 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
1659 }
1660 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
1661 {
1662 torrent->pause();
1663 LogMsg(tr("'%1' reached the maximum ratio you set. Paused.").arg(torrent->name()));
1664 }
1665 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
1666 {
1667 torrent->setSuperSeeding(true);
1668 LogMsg(tr("'%1' reached the maximum ratio you set. Enabled super seeding for it.").arg(torrent->name()));
1669 }
1670 continue;
1671 }
1672 }
1673 }
1674
1675 if (torrent->seedingTimeLimit() != Torrent::NO_SEEDING_TIME_LIMIT)
1676 {
1677 const qlonglong seedingTimeInMinutes = torrent->seedingTime() / 60;
1678 int seedingTimeLimit = torrent->seedingTimeLimit();
1679 if (seedingTimeLimit == Torrent::USE_GLOBAL_SEEDING_TIME)
1680 {
1681 // If Global Seeding Time Limit is really set...
1682 seedingTimeLimit = globalMaxSeedingMinutes();
1683 }
1684
1685 if (seedingTimeLimit >= 0)
1686 {
1687 if ((seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
1688 {
1689 if (m_maxRatioAction == Remove)
1690 {
1691 LogMsg(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
1692 deleteTorrent(torrent->id());
1693 }
1694 else if (m_maxRatioAction == DeleteFiles)
1695 {
1696 LogMsg(tr("'%1' reached the maximum seeding time you set. Removed torrent and its files.").arg(torrent->name()));
1697 deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
1698 }
1699 else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
1700 {
1701 torrent->pause();
1702 LogMsg(tr("'%1' reached the maximum seeding time you set. Paused.").arg(torrent->name()));
1703 }
1704 else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding())
1705 {
1706 torrent->setSuperSeeding(true);
1707 LogMsg(tr("'%1' reached the maximum seeding time you set. Enabled super seeding for it.").arg(torrent->name()));
1708 }
1709 }
1710 }
1711 }
1712 }
1713 }
1714 }
1715
1716 // Add to BitTorrent session the downloaded torrent file
handleDownloadFinished(const Net::DownloadResult & result)1717 void Session::handleDownloadFinished(const Net::DownloadResult &result)
1718 {
1719 switch (result.status)
1720 {
1721 case Net::DownloadStatus::Success:
1722 emit downloadFromUrlFinished(result.url);
1723 addTorrent(TorrentInfo::load(result.data), m_downloadedTorrents.take(result.url));
1724 break;
1725 case Net::DownloadStatus::RedirectedToMagnet:
1726 emit downloadFromUrlFinished(result.url);
1727 addTorrent(MagnetUri {result.magnet}, m_downloadedTorrents.take(result.url));
1728 break;
1729 default:
1730 emit downloadFromUrlFailed(result.url, result.errorString);
1731 }
1732 }
1733
fileSearchFinished(const TorrentID & id,const QString & savePath,const QStringList & fileNames)1734 void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames)
1735 {
1736 TorrentImpl *torrent = m_torrents.value(id);
1737 if (torrent)
1738 {
1739 torrent->fileSearchFinished(savePath, fileNames);
1740 return;
1741 }
1742
1743 const auto loadingTorrentsIter = m_loadingTorrents.find(id);
1744 if (loadingTorrentsIter != m_loadingTorrents.end())
1745 {
1746 LoadTorrentParams params = loadingTorrentsIter.value();
1747 m_loadingTorrents.erase(loadingTorrentsIter);
1748
1749 lt::add_torrent_params &p = params.ltAddTorrentParams;
1750
1751 p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
1752 for (int i = 0; i < fileNames.size(); ++i)
1753 p.renamed_files[lt::file_index_t {i}] = fileNames[i].toStdString();
1754
1755 loadTorrent(params);
1756 }
1757 }
1758
1759 // Return the torrent handle, given its hash
findTorrent(const TorrentID & id) const1760 Torrent *Session::findTorrent(const TorrentID &id) const
1761 {
1762 return m_torrents.value(id);
1763 }
1764
hasActiveTorrents() const1765 bool Session::hasActiveTorrents() const
1766 {
1767 return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentImpl *torrent)
1768 {
1769 return TorrentFilter::ActiveTorrent.match(torrent);
1770 });
1771 }
1772
hasUnfinishedTorrents() const1773 bool Session::hasUnfinishedTorrents() const
1774 {
1775 return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
1776 {
1777 return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored());
1778 });
1779 }
1780
hasRunningSeed() const1781 bool Session::hasRunningSeed() const
1782 {
1783 return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
1784 {
1785 return (torrent->isSeed() && !torrent->isPaused());
1786 });
1787 }
1788
banIP(const QString & ip)1789 void Session::banIP(const QString &ip)
1790 {
1791 QStringList bannedIPs = m_bannedIPs;
1792 if (!bannedIPs.contains(ip))
1793 {
1794 lt::ip_filter filter = m_nativeSession->get_ip_filter();
1795 lt::error_code ec;
1796 const lt::address addr = lt::make_address(ip.toLatin1().constData(), ec);
1797 Q_ASSERT(!ec);
1798 if (ec) return;
1799 filter.add_rule(addr, addr, lt::ip_filter::blocked);
1800 m_nativeSession->set_ip_filter(filter);
1801
1802 bannedIPs << ip;
1803 bannedIPs.sort();
1804 m_bannedIPs = bannedIPs;
1805 }
1806 }
1807
1808 // Delete a torrent from the session, given its hash
1809 // and from the disk, if the corresponding deleteOption is chosen
deleteTorrent(const TorrentID & id,const DeleteOption deleteOption)1810 bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
1811 {
1812 TorrentImpl *const torrent = m_torrents.take(id);
1813 if (!torrent) return false;
1814
1815 qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
1816 emit torrentAboutToBeRemoved(torrent);
1817
1818 // Remove it from session
1819 if (deleteOption == DeleteTorrent)
1820 {
1821 m_removingTorrents[torrent->id()] = {torrent->name(), "", deleteOption};
1822
1823 const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
1824 const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
1825 , [&nativeHandle](const MoveStorageJob &job)
1826 {
1827 return job.torrentHandle == nativeHandle;
1828 });
1829 if (iter != m_moveStorageQueue.end())
1830 {
1831 // We shouldn't actually remove torrent until existing "move storage jobs" are done
1832 torrentQueuePositionBottom(nativeHandle);
1833 nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
1834 nativeHandle.pause();
1835 }
1836 else
1837 {
1838 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
1839 }
1840 }
1841 else
1842 {
1843 QString rootPath = torrent->rootPath(true);
1844 if (!rootPath.isEmpty() && torrent->useTempPath())
1845 {
1846 // torrent without root folder still has it in its temporary save path
1847 rootPath = torrent->actualStorageLocation();
1848 }
1849
1850 m_removingTorrents[torrent->id()] = {torrent->name(), rootPath, deleteOption};
1851
1852 if (m_moveStorageQueue.size() > 1)
1853 {
1854 // Delete "move storage job" for the deleted torrent
1855 // (note: we shouldn't delete active job)
1856 const auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
1857 , [torrent](const MoveStorageJob &job)
1858 {
1859 return job.torrentHandle == torrent->nativeHandle();
1860 });
1861 if (iter != m_moveStorageQueue.end())
1862 m_moveStorageQueue.erase(iter);
1863 }
1864
1865 m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
1866 }
1867
1868 // Remove it from torrent resume directory
1869 const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
1870 const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
1871 QMetaObject::invokeMethod(m_resumeDataSavingManager, [this, resumedataFile, metadataFile]()
1872 {
1873 m_resumeDataSavingManager->remove(resumedataFile);
1874 m_resumeDataSavingManager->remove(metadataFile);
1875 });
1876
1877 delete torrent;
1878 return true;
1879 }
1880
cancelDownloadMetadata(const TorrentID & id)1881 bool Session::cancelDownloadMetadata(const TorrentID &id)
1882 {
1883 const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
1884 if (downloadedMetadataIter == m_downloadedMetadata.end()) return false;
1885
1886 m_downloadedMetadata.erase(downloadedMetadataIter);
1887 --m_extraLimit;
1888 adjustLimits();
1889 m_nativeSession->remove_torrent(m_nativeSession->find_torrent(id), lt::session::delete_files);
1890 return true;
1891 }
1892
increaseTorrentsQueuePos(const QVector<TorrentID> & ids)1893 void Session::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
1894 {
1895 using ElementType = std::pair<int, const TorrentImpl *>;
1896 std::priority_queue<ElementType
1897 , std::vector<ElementType>
1898 , std::greater<ElementType>> torrentQueue;
1899
1900 // Sort torrents by queue position
1901 for (const TorrentID &id : ids)
1902 {
1903 const TorrentImpl *torrent = m_torrents.value(id);
1904 if (!torrent) continue;
1905 if (const int position = torrent->queuePosition(); position >= 0)
1906 torrentQueue.emplace(position, torrent);
1907 }
1908
1909 // Increase torrents queue position (starting with the one in the highest queue position)
1910 while (!torrentQueue.empty())
1911 {
1912 const TorrentImpl *torrent = torrentQueue.top().second;
1913 torrentQueuePositionUp(torrent->nativeHandle());
1914 torrentQueue.pop();
1915 }
1916
1917 saveTorrentsQueue();
1918 }
1919
decreaseTorrentsQueuePos(const QVector<TorrentID> & ids)1920 void Session::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
1921 {
1922 using ElementType = std::pair<int, const TorrentImpl *>;
1923 std::priority_queue<ElementType> torrentQueue;
1924
1925 // Sort torrents by queue position
1926 for (const TorrentID &id : ids)
1927 {
1928 const TorrentImpl *torrent = m_torrents.value(id);
1929 if (!torrent) continue;
1930 if (const int position = torrent->queuePosition(); position >= 0)
1931 torrentQueue.emplace(position, torrent);
1932 }
1933
1934 // Decrease torrents queue position (starting with the one in the lowest queue position)
1935 while (!torrentQueue.empty())
1936 {
1937 const TorrentImpl *torrent = torrentQueue.top().second;
1938 torrentQueuePositionDown(torrent->nativeHandle());
1939 torrentQueue.pop();
1940 }
1941
1942 for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i)
1943 torrentQueuePositionBottom(m_nativeSession->find_torrent(*i));
1944
1945 saveTorrentsQueue();
1946 }
1947
topTorrentsQueuePos(const QVector<TorrentID> & ids)1948 void Session::topTorrentsQueuePos(const QVector<TorrentID> &ids)
1949 {
1950 using ElementType = std::pair<int, const TorrentImpl *>;
1951 std::priority_queue<ElementType> torrentQueue;
1952
1953 // Sort torrents by queue position
1954 for (const TorrentID &id : ids)
1955 {
1956 const TorrentImpl *torrent = m_torrents.value(id);
1957 if (!torrent) continue;
1958 if (const int position = torrent->queuePosition(); position >= 0)
1959 torrentQueue.emplace(position, torrent);
1960 }
1961
1962 // Top torrents queue position (starting with the one in the lowest queue position)
1963 while (!torrentQueue.empty())
1964 {
1965 const TorrentImpl *torrent = torrentQueue.top().second;
1966 torrentQueuePositionTop(torrent->nativeHandle());
1967 torrentQueue.pop();
1968 }
1969
1970 saveTorrentsQueue();
1971 }
1972
bottomTorrentsQueuePos(const QVector<TorrentID> & ids)1973 void Session::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
1974 {
1975 using ElementType = std::pair<int, const TorrentImpl *>;
1976 std::priority_queue<ElementType
1977 , std::vector<ElementType>
1978 , std::greater<ElementType>> torrentQueue;
1979
1980 // Sort torrents by queue position
1981 for (const TorrentID &id : ids)
1982 {
1983 const TorrentImpl *torrent = m_torrents.value(id);
1984 if (!torrent) continue;
1985 if (const int position = torrent->queuePosition(); position >= 0)
1986 torrentQueue.emplace(position, torrent);
1987 }
1988
1989 // Bottom torrents queue position (starting with the one in the highest queue position)
1990 while (!torrentQueue.empty())
1991 {
1992 const TorrentImpl *torrent = torrentQueue.top().second;
1993 torrentQueuePositionBottom(torrent->nativeHandle());
1994 torrentQueue.pop();
1995 }
1996
1997 for (auto i = m_downloadedMetadata.cbegin(); i != m_downloadedMetadata.cend(); ++i)
1998 torrentQueuePositionBottom(m_nativeSession->find_torrent(*i));
1999
2000 saveTorrentsQueue();
2001 }
2002
handleTorrentNeedSaveResumeData(const TorrentImpl * torrent)2003 void Session::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
2004 {
2005 if (m_needSaveResumeDataTorrents.empty())
2006 {
2007 QMetaObject::invokeMethod(this, [this]()
2008 {
2009 for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
2010 {
2011 TorrentImpl *torrent = m_torrents.value(torrentID);
2012 if (torrent)
2013 torrent->saveResumeData();
2014 }
2015 m_needSaveResumeDataTorrents.clear();
2016 }, Qt::QueuedConnection);
2017 }
2018
2019 m_needSaveResumeDataTorrents.insert(torrent->id());
2020 }
2021
handleTorrentSaveResumeDataRequested(const TorrentImpl * torrent)2022 void Session::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
2023 {
2024 qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
2025 ++m_numResumeData;
2026 }
2027
torrents() const2028 QVector<Torrent *> Session::torrents() const
2029 {
2030 QVector<Torrent *> result;
2031 result.reserve(m_torrents.size());
2032 for (TorrentImpl *torrent : asConst(m_torrents))
2033 result << torrent;
2034
2035 return result;
2036 }
2037
addTorrent(const QString & source,const AddTorrentParams & params)2038 bool Session::addTorrent(const QString &source, const AddTorrentParams ¶ms)
2039 {
2040 // `source`: .torrent file path/url or magnet uri
2041
2042 if (Net::DownloadManager::hasSupportedScheme(source))
2043 {
2044 LogMsg(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source));
2045 // Launch downloader
2046 Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE)
2047 , this, &Session::handleDownloadFinished);
2048 m_downloadedTorrents[source] = params;
2049 return true;
2050 }
2051
2052 const MagnetUri magnetUri {source};
2053 if (magnetUri.isValid())
2054 return addTorrent(magnetUri, params);
2055
2056 TorrentFileGuard guard {source};
2057 if (addTorrent(TorrentInfo::loadFromFile(source), params))
2058 {
2059 guard.markAsAddedToSession();
2060 return true;
2061 }
2062
2063 return false;
2064 }
2065
addTorrent(const MagnetUri & magnetUri,const AddTorrentParams & params)2066 bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams ¶ms)
2067 {
2068 if (!magnetUri.isValid()) return false;
2069
2070 return addTorrent_impl(magnetUri, params);
2071 }
2072
addTorrent(const TorrentInfo & torrentInfo,const AddTorrentParams & params)2073 bool Session::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams ¶ms)
2074 {
2075 if (!torrentInfo.isValid()) return false;
2076
2077 return addTorrent_impl(torrentInfo, params);
2078 }
2079
initLoadTorrentParams(const AddTorrentParams & addTorrentParams)2080 LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorrentParams)
2081 {
2082 LoadTorrentParams loadTorrentParams;
2083
2084 loadTorrentParams.name = addTorrentParams.name;
2085 loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
2086 loadTorrentParams.hasSeedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
2087 loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
2088 loadTorrentParams.forced = addTorrentParams.addForced;
2089 loadTorrentParams.paused = addTorrentParams.addPaused.value_or(isAddTorrentPaused());
2090 loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
2091 loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
2092
2093 const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!isAutoTMMDisabledByDefault());
2094 if (useAutoTMM)
2095 loadTorrentParams.savePath = "";
2096 else if (addTorrentParams.savePath.isEmpty())
2097 loadTorrentParams.savePath = defaultSavePath();
2098 else if (QDir(addTorrentParams.savePath).isRelative())
2099 loadTorrentParams.savePath = QDir(defaultSavePath()).absoluteFilePath(addTorrentParams.savePath);
2100 else
2101 loadTorrentParams.savePath = normalizePath(addTorrentParams.savePath);
2102
2103 const QString category = addTorrentParams.category;
2104 if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
2105 loadTorrentParams.category = "";
2106 else
2107 loadTorrentParams.category = addTorrentParams.category;
2108
2109 for (const QString &tag : addTorrentParams.tags)
2110 {
2111 if (hasTag(tag) || addTag(tag))
2112 loadTorrentParams.tags.insert(tag);
2113 }
2114
2115 return loadTorrentParams;
2116 }
2117
2118 // Add a torrent to the BitTorrent session
addTorrent_impl(const std::variant<MagnetUri,TorrentInfo> & source,const AddTorrentParams & addTorrentParams)2119 bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams)
2120 {
2121 const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
2122 TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
2123 const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source));
2124 const auto id = TorrentID::fromInfoHash(hasMetadata ? metadata.infoHash() : magnetUri.infoHash());
2125
2126 // It looks illogical that we don't just use an existing handle,
2127 // but as previous experience has shown, it actually creates unnecessary
2128 // problems and unwanted behavior due to the fact that it was originally
2129 // added with parameters other than those provided by the user.
2130 cancelDownloadMetadata(id);
2131
2132 // We should not add the torrent if it is already
2133 // processed or is pending to add to session
2134 if (m_loadingTorrents.contains(id))
2135 return false;
2136
2137 TorrentImpl *const torrent = m_torrents.value(id);
2138 if (torrent)
2139 { // a duplicate torrent is added
2140 if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
2141 return false;
2142
2143 // merge trackers and web seeds
2144 torrent->addTrackers(hasMetadata ? metadata.trackers() : magnetUri.trackers());
2145 torrent->addUrlSeeds(hasMetadata ? metadata.urlSeeds() : magnetUri.urlSeeds());
2146 return true;
2147 }
2148
2149 LoadTorrentParams loadTorrentParams = initLoadTorrentParams(addTorrentParams);
2150 lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
2151
2152 bool isFindingIncompleteFiles = false;
2153
2154 // If empty then Automatic mode, otherwise Manual mode
2155 const QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
2156 if (hasMetadata)
2157 {
2158 metadata.setContentLayout(loadTorrentParams.contentLayout);
2159
2160 if (!loadTorrentParams.hasSeedStatus)
2161 {
2162 findIncompleteFiles(metadata, actualSavePath);
2163 isFindingIncompleteFiles = true;
2164 }
2165
2166 // if torrent name wasn't explicitly set we handle the case of
2167 // initial renaming of torrent content and rename torrent accordingly
2168 if (loadTorrentParams.name.isEmpty())
2169 {
2170 QString contentName = metadata.rootFolder();
2171 if (contentName.isEmpty() && (metadata.filesCount() == 1))
2172 contentName = metadata.fileName(0);
2173
2174 if (!contentName.isEmpty() && (contentName != metadata.name()))
2175 loadTorrentParams.name = contentName;
2176 }
2177
2178 Q_ASSERT(p.file_priorities.empty());
2179 std::transform(addTorrentParams.filePriorities.cbegin(), addTorrentParams.filePriorities.cend()
2180 , std::back_inserter(p.file_priorities), [](const DownloadPriority priority)
2181 {
2182 return static_cast<lt::download_priority_t>(
2183 static_cast<lt::download_priority_t::underlying_type>(priority));
2184 });
2185
2186 p.ti = metadata.nativeInfo();
2187 }
2188 else
2189 {
2190 p = magnetUri.addTorrentParams();
2191
2192 if (loadTorrentParams.name.isEmpty() && !p.name.empty())
2193 loadTorrentParams.name = QString::fromStdString(p.name);
2194 }
2195
2196 p.save_path = Utils::Fs::toNativePath(actualSavePath).toStdString();
2197
2198 p.upload_limit = addTorrentParams.uploadLimit;
2199 p.download_limit = addTorrentParams.downloadLimit;
2200
2201 // Preallocation mode
2202 p.storage_mode = isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse;
2203
2204 if (addTorrentParams.sequential)
2205 p.flags |= lt::torrent_flags::sequential_download;
2206 else
2207 p.flags &= ~lt::torrent_flags::sequential_download;
2208
2209 // Seeding mode
2210 // Skip checking and directly start seeding
2211 if (addTorrentParams.skipChecking)
2212 p.flags |= lt::torrent_flags::seed_mode;
2213 else
2214 p.flags &= ~lt::torrent_flags::seed_mode;
2215
2216 if (loadTorrentParams.paused || !loadTorrentParams.forced)
2217 p.flags |= lt::torrent_flags::paused;
2218 else
2219 p.flags &= ~lt::torrent_flags::paused;
2220 if (loadTorrentParams.paused || loadTorrentParams.forced)
2221 p.flags &= ~lt::torrent_flags::auto_managed;
2222 else
2223 p.flags |= lt::torrent_flags::auto_managed;
2224
2225 if (!isFindingIncompleteFiles)
2226 return loadTorrent(loadTorrentParams);
2227
2228 m_loadingTorrents.insert(id, loadTorrentParams);
2229 return true;
2230 }
2231
2232 // Add a torrent to the BitTorrent session
loadTorrent(LoadTorrentParams params)2233 bool Session::loadTorrent(LoadTorrentParams params)
2234 {
2235 lt::add_torrent_params &p = params.ltAddTorrentParams;
2236
2237 #if (LIBTORRENT_VERSION_NUM < 20000)
2238 p.storage = customStorageConstructor;
2239 #endif
2240 // Limits
2241 p.max_connections = maxConnectionsPerTorrent();
2242 p.max_uploads = maxUploadsPerTorrent();
2243
2244 const bool hasMetadata = (p.ti && p.ti->is_valid());
2245 #if (LIBTORRENT_VERSION_NUM >= 20000)
2246 const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hashes() : p.info_hashes);
2247 #else
2248 const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hash() : p.info_hash);
2249 #endif
2250 m_loadingTorrents.insert(id, params);
2251
2252 // Adding torrent to BitTorrent session
2253 m_nativeSession->async_add_torrent(p);
2254
2255 return true;
2256 }
2257
findIncompleteFiles(const TorrentInfo & torrentInfo,const QString & savePath) const2258 void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const
2259 {
2260 const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
2261 const QStringList originalFileNames = torrentInfo.filePaths();
2262 const QString completeSavePath = savePath;
2263 const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
2264 QMetaObject::invokeMethod(m_fileSearcher, [=]()
2265 {
2266 m_fileSearcher->search(searchId, originalFileNames, completeSavePath, incompleteSavePath);
2267 });
2268 }
2269
2270 // Add a torrent to libtorrent session in hidden mode
2271 // and force it to download its metadata
downloadMetadata(const MagnetUri & magnetUri)2272 bool Session::downloadMetadata(const MagnetUri &magnetUri)
2273 {
2274 if (!magnetUri.isValid()) return false;
2275
2276 const auto id = TorrentID::fromInfoHash(magnetUri.infoHash());
2277 const QString name = magnetUri.name();
2278
2279 // We should not add torrent if it's already
2280 // processed or adding to session
2281 if (m_torrents.contains(id)) return false;
2282 if (m_loadingTorrents.contains(id)) return false;
2283 if (m_downloadedMetadata.contains(id)) return false;
2284
2285 qDebug("Adding torrent to preload metadata...");
2286 qDebug(" -> Torrent ID: %s", qUtf8Printable(id.toString()));
2287 qDebug(" -> Name: %s", qUtf8Printable(name));
2288
2289 lt::add_torrent_params p = magnetUri.addTorrentParams();
2290
2291 // Flags
2292 // Preallocation mode
2293 if (isPreallocationEnabled())
2294 p.storage_mode = lt::storage_mode_allocate;
2295 else
2296 p.storage_mode = lt::storage_mode_sparse;
2297
2298 // Limits
2299 p.max_connections = maxConnectionsPerTorrent();
2300 p.max_uploads = maxUploadsPerTorrent();
2301
2302 const QString savePath = Utils::Fs::tempPath() + id.toString();
2303 p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
2304
2305 // Forced start
2306 p.flags &= ~lt::torrent_flags::paused;
2307 p.flags &= ~lt::torrent_flags::auto_managed;
2308
2309 // Solution to avoid accidental file writes
2310 p.flags |= lt::torrent_flags::upload_mode;
2311
2312 #if (LIBTORRENT_VERSION_NUM < 20000)
2313 p.storage = customStorageConstructor;
2314 #endif
2315
2316 // Adding torrent to libtorrent session
2317 lt::error_code ec;
2318 lt::torrent_handle h = m_nativeSession->add_torrent(p, ec);
2319 if (ec) return false;
2320
2321 // waiting for metadata...
2322 m_downloadedMetadata.insert(h.info_hash());
2323 ++m_extraLimit;
2324 adjustLimits();
2325
2326 return true;
2327 }
2328
exportTorrentFile(const Torrent * torrent,TorrentExportFolder folder)2329 void Session::exportTorrentFile(const Torrent *torrent, TorrentExportFolder folder)
2330 {
2331 Q_ASSERT(((folder == TorrentExportFolder::Regular) && !torrentExportDirectory().isEmpty()) ||
2332 ((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty()));
2333
2334 const QString validName = Utils::Fs::toValidFileSystemName(torrent->name());
2335 const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
2336 QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
2337 const QString torrentPath = QDir(m_resumeFolderPath).absoluteFilePath(torrentFilename);
2338 const QDir exportPath(folder == TorrentExportFolder::Regular ? torrentExportDirectory() : finishedTorrentExportDirectory());
2339 if (exportPath.exists() || exportPath.mkpath(exportPath.absolutePath()))
2340 {
2341 QString newTorrentPath = exportPath.absoluteFilePath(torrentExportFilename);
2342 int counter = 0;
2343 while (QFile::exists(newTorrentPath) && !Utils::Fs::sameFiles(torrentPath, newTorrentPath))
2344 {
2345 // Append number to torrent name to make it unique
2346 torrentExportFilename = QString::fromLatin1("%1 %2.torrent").arg(validName).arg(++counter);
2347 newTorrentPath = exportPath.absoluteFilePath(torrentExportFilename);
2348 }
2349
2350 if (!QFile::exists(newTorrentPath))
2351 QFile::copy(torrentPath, newTorrentPath);
2352 }
2353 }
2354
generateResumeData()2355 void Session::generateResumeData()
2356 {
2357 for (TorrentImpl *const torrent : asConst(m_torrents))
2358 {
2359 if (!torrent->isValid()) continue;
2360
2361 if (torrent->needSaveResumeData())
2362 {
2363 torrent->saveResumeData();
2364 m_needSaveResumeDataTorrents.remove(torrent->id());
2365 }
2366 }
2367 }
2368
2369 // Called on exit
saveResumeData()2370 void Session::saveResumeData()
2371 {
2372 // Pause session
2373 m_nativeSession->pause();
2374
2375 if (isQueueingSystemEnabled())
2376 saveTorrentsQueue();
2377 generateResumeData();
2378
2379 while (m_numResumeData > 0)
2380 {
2381 const std::vector<lt::alert *> alerts = getPendingAlerts(lt::seconds {30});
2382 if (alerts.empty())
2383 {
2384 LogMsg(tr("Error: Aborted saving resume data for %1 outstanding torrents.").arg(QString::number(m_numResumeData))
2385 , Log::CRITICAL);
2386 break;
2387 }
2388
2389 for (const lt::alert *a : alerts)
2390 {
2391 switch (a->type())
2392 {
2393 case lt::save_resume_data_failed_alert::alert_type:
2394 case lt::save_resume_data_alert::alert_type:
2395 dispatchTorrentAlert(a);
2396 break;
2397 }
2398 }
2399 }
2400 }
2401
saveTorrentsQueue() const2402 void Session::saveTorrentsQueue() const
2403 {
2404 // store hash in textual representation
2405 QMap<int, QString> queue; // Use QMap since it should be ordered by key
2406 for (const TorrentImpl *torrent : asConst(m_torrents))
2407 {
2408 // We require actual (non-cached) queue position here!
2409 const int queuePos = static_cast<LTUnderlyingType<lt::queue_position_t>>(torrent->nativeHandle().queue_position());
2410 if (queuePos >= 0)
2411 queue[queuePos] = torrent->id().toString();
2412 }
2413
2414 QByteArray data;
2415 data.reserve(((TorrentID::length() * 2) + 1) * queue.size());
2416 for (const QString &torrentID : asConst(queue))
2417 data += (torrentID.toLatin1() + '\n');
2418
2419 const QString filename = QLatin1String {"queue"};
2420 QMetaObject::invokeMethod(m_resumeDataSavingManager
2421 , [this, data, filename]() { m_resumeDataSavingManager->save(filename, data); });
2422 }
2423
removeTorrentsQueue() const2424 void Session::removeTorrentsQueue() const
2425 {
2426 const QString filename = QLatin1String {"queue"};
2427 QMetaObject::invokeMethod(m_resumeDataSavingManager
2428 , [this, filename]() { m_resumeDataSavingManager->remove(filename); });
2429 }
2430
setDefaultSavePath(QString path)2431 void Session::setDefaultSavePath(QString path)
2432 {
2433 path = normalizeSavePath(path);
2434 if (path == m_defaultSavePath) return;
2435
2436 m_defaultSavePath = path;
2437
2438 if (isDisableAutoTMMWhenDefaultSavePathChanged())
2439 for (TorrentImpl *const torrent : asConst(m_torrents))
2440 torrent->setAutoTMMEnabled(false);
2441 else
2442 for (TorrentImpl *const torrent : asConst(m_torrents))
2443 torrent->handleCategorySavePathChanged();
2444 }
2445
setTempPath(QString path)2446 void Session::setTempPath(QString path)
2447 {
2448 path = normalizeSavePath(path, defaultSavePath() + "temp/");
2449 if (path == m_tempPath) return;
2450
2451 m_tempPath = path;
2452
2453 for (TorrentImpl *const torrent : asConst(m_torrents))
2454 torrent->handleTempPathChanged();
2455 }
2456
networkOnlineStateChanged(const bool online)2457 void Session::networkOnlineStateChanged(const bool online)
2458 {
2459 LogMsg(tr("System network status changed to %1", "e.g: System network status changed to ONLINE").arg(online ? tr("ONLINE") : tr("OFFLINE")), Log::INFO);
2460 }
2461
networkConfigurationChange(const QNetworkConfiguration & cfg)2462 void Session::networkConfigurationChange(const QNetworkConfiguration &cfg)
2463 {
2464 const QString configuredInterfaceName = networkInterface();
2465 // Empty means "Any Interface". In this case libtorrent has binded to 0.0.0.0 so any change to any interface will
2466 // be automatically picked up. Otherwise we would rebinding here to 0.0.0.0 again.
2467 if (configuredInterfaceName.isEmpty()) return;
2468
2469 const QString changedInterface = cfg.name();
2470
2471 if (configuredInterfaceName == changedInterface)
2472 {
2473 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);
2474 configureListeningInterface();
2475 }
2476 }
2477
getListeningIPs() const2478 QStringList Session::getListeningIPs() const
2479 {
2480 QStringList IPs;
2481
2482 const QString ifaceName = networkInterface();
2483 const QString ifaceAddr = networkInterfaceAddress();
2484 const QHostAddress configuredAddr(ifaceAddr);
2485 const bool allIPv4 = (ifaceAddr == QLatin1String("0.0.0.0")); // Means All IPv4 addresses
2486 const bool allIPv6 = (ifaceAddr == QLatin1String("::")); // Means All IPv6 addresses
2487
2488 if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull())
2489 {
2490 LogMsg(tr("Configured network interface address %1 isn't valid.", "Configured network interface address 124.5.158.1 isn't valid.").arg(ifaceAddr), Log::CRITICAL);
2491 // Pass the invalid user configured interface name/address to libtorrent
2492 // in hopes that it will come online later.
2493 // This will not cause IP leak but allow user to reconnect the interface
2494 // and re-establish connection without restarting the client.
2495 IPs.append(ifaceAddr);
2496 return IPs;
2497 }
2498
2499 if (ifaceName.isEmpty())
2500 {
2501 if (ifaceAddr.isEmpty())
2502 return {QLatin1String("0.0.0.0"), QLatin1String("::")}; // Indicates all interfaces + all addresses (aka default)
2503
2504 if (allIPv4)
2505 return {QLatin1String("0.0.0.0")};
2506
2507 if (allIPv6)
2508 return {QLatin1String("::")};
2509 }
2510
2511 const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
2512 {
2513 if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
2514 || (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
2515 return;
2516
2517 if ((match == addr) || allIPv4 || allIPv6)
2518 IPs.append(addr.toString());
2519 };
2520
2521 if (ifaceName.isEmpty())
2522 {
2523 const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
2524 for (const auto &addr : addresses)
2525 checkAndAddIP(addr, configuredAddr);
2526
2527 // At this point ifaceAddr was non-empty
2528 // If IPs.isEmpty() it means the configured Address was not found
2529 if (IPs.isEmpty())
2530 {
2531 LogMsg(tr("Can't find the configured address '%1' to listen on"
2532 , "Can't find the configured address '192.168.1.3' to listen on")
2533 .arg(ifaceAddr), Log::CRITICAL);
2534 IPs.append(ifaceAddr);
2535 }
2536
2537 return IPs;
2538 }
2539
2540 // Attempt to listen on provided interface
2541 const QNetworkInterface networkIFace = QNetworkInterface::interfaceFromName(ifaceName);
2542 if (!networkIFace.isValid())
2543 {
2544 qDebug("Invalid network interface: %s", qUtf8Printable(ifaceName));
2545 LogMsg(tr("The network interface defined is invalid: %1").arg(ifaceName), Log::CRITICAL);
2546 IPs.append(ifaceName);
2547 return IPs;
2548 }
2549
2550 if (ifaceAddr.isEmpty())
2551 {
2552 IPs.append(ifaceName);
2553 return IPs; // On Windows calling code converts it to GUID
2554 }
2555
2556 const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
2557 qDebug("This network interface has %d IP addresses", addresses.size());
2558 for (const QNetworkAddressEntry &entry : addresses)
2559 checkAndAddIP(entry.ip(), configuredAddr);
2560
2561 // Make sure there is at least one IP
2562 // At this point there was an explicit interface and an explicit address set
2563 // and the address should have been found
2564 if (IPs.isEmpty())
2565 {
2566 LogMsg(tr("Can't find the configured address '%1' to listen on"
2567 , "Can't find the configured address '192.168.1.3' to listen on")
2568 .arg(ifaceAddr), Log::CRITICAL);
2569 IPs.append(ifaceAddr);
2570 }
2571
2572 return IPs;
2573 }
2574
2575 // Set the ports range in which is chosen the port
2576 // the BitTorrent session will listen to
configureListeningInterface()2577 void Session::configureListeningInterface()
2578 {
2579 m_listenInterfaceConfigured = false;
2580 configureDeferred();
2581 }
2582
globalDownloadSpeedLimit() const2583 int Session::globalDownloadSpeedLimit() const
2584 {
2585 // Unfortunately the value was saved as KiB instead of B.
2586 // But it is better to pass it around internally(+ webui) as Bytes.
2587 return m_globalDownloadSpeedLimit * 1024;
2588 }
2589
setGlobalDownloadSpeedLimit(const int limit)2590 void Session::setGlobalDownloadSpeedLimit(const int limit)
2591 {
2592 // Unfortunately the value was saved as KiB instead of B.
2593 // But it is better to pass it around internally(+ webui) as Bytes.
2594 if (limit == globalDownloadSpeedLimit())
2595 return;
2596
2597 if (limit <= 0)
2598 m_globalDownloadSpeedLimit = 0;
2599 else if (limit <= 1024)
2600 m_globalDownloadSpeedLimit = 1;
2601 else
2602 m_globalDownloadSpeedLimit = (limit / 1024);
2603
2604 if (!isAltGlobalSpeedLimitEnabled())
2605 configureDeferred();
2606 }
2607
globalUploadSpeedLimit() const2608 int Session::globalUploadSpeedLimit() const
2609 {
2610 // Unfortunately the value was saved as KiB instead of B.
2611 // But it is better to pass it around internally(+ webui) as Bytes.
2612 return m_globalUploadSpeedLimit * 1024;
2613 }
2614
setGlobalUploadSpeedLimit(const int limit)2615 void Session::setGlobalUploadSpeedLimit(const int limit)
2616 {
2617 // Unfortunately the value was saved as KiB instead of B.
2618 // But it is better to pass it around internally(+ webui) as Bytes.
2619 if (limit == globalUploadSpeedLimit())
2620 return;
2621
2622 if (limit <= 0)
2623 m_globalUploadSpeedLimit = 0;
2624 else if (limit <= 1024)
2625 m_globalUploadSpeedLimit = 1;
2626 else
2627 m_globalUploadSpeedLimit = (limit / 1024);
2628
2629 if (!isAltGlobalSpeedLimitEnabled())
2630 configureDeferred();
2631 }
2632
altGlobalDownloadSpeedLimit() const2633 int Session::altGlobalDownloadSpeedLimit() const
2634 {
2635 // Unfortunately the value was saved as KiB instead of B.
2636 // But it is better to pass it around internally(+ webui) as Bytes.
2637 return m_altGlobalDownloadSpeedLimit * 1024;
2638 }
2639
setAltGlobalDownloadSpeedLimit(const int limit)2640 void Session::setAltGlobalDownloadSpeedLimit(const int limit)
2641 {
2642 // Unfortunately the value was saved as KiB instead of B.
2643 // But it is better to pass it around internally(+ webui) as Bytes.
2644 if (limit == altGlobalDownloadSpeedLimit())
2645 return;
2646
2647 if (limit <= 0)
2648 m_altGlobalDownloadSpeedLimit = 0;
2649 else if (limit <= 1024)
2650 m_altGlobalDownloadSpeedLimit = 1;
2651 else
2652 m_altGlobalDownloadSpeedLimit = (limit / 1024);
2653
2654 if (isAltGlobalSpeedLimitEnabled())
2655 configureDeferred();
2656 }
2657
altGlobalUploadSpeedLimit() const2658 int Session::altGlobalUploadSpeedLimit() const
2659 {
2660 // Unfortunately the value was saved as KiB instead of B.
2661 // But it is better to pass it around internally(+ webui) as Bytes.
2662 return m_altGlobalUploadSpeedLimit * 1024;
2663 }
2664
setAltGlobalUploadSpeedLimit(const int limit)2665 void Session::setAltGlobalUploadSpeedLimit(const int limit)
2666 {
2667 // Unfortunately the value was saved as KiB instead of B.
2668 // But it is better to pass it around internally(+ webui) as Bytes.
2669 if (limit == altGlobalUploadSpeedLimit())
2670 return;
2671
2672 if (limit <= 0)
2673 m_altGlobalUploadSpeedLimit = 0;
2674 else if (limit <= 1024)
2675 m_altGlobalUploadSpeedLimit = 1;
2676 else
2677 m_altGlobalUploadSpeedLimit = (limit / 1024);
2678
2679 if (isAltGlobalSpeedLimitEnabled())
2680 configureDeferred();
2681 }
2682
downloadSpeedLimit() const2683 int Session::downloadSpeedLimit() const
2684 {
2685 return isAltGlobalSpeedLimitEnabled()
2686 ? altGlobalDownloadSpeedLimit()
2687 : globalDownloadSpeedLimit();
2688 }
2689
setDownloadSpeedLimit(const int limit)2690 void Session::setDownloadSpeedLimit(const int limit)
2691 {
2692 if (isAltGlobalSpeedLimitEnabled())
2693 setAltGlobalDownloadSpeedLimit(limit);
2694 else
2695 setGlobalDownloadSpeedLimit(limit);
2696 }
2697
uploadSpeedLimit() const2698 int Session::uploadSpeedLimit() const
2699 {
2700 return isAltGlobalSpeedLimitEnabled()
2701 ? altGlobalUploadSpeedLimit()
2702 : globalUploadSpeedLimit();
2703 }
2704
setUploadSpeedLimit(const int limit)2705 void Session::setUploadSpeedLimit(const int limit)
2706 {
2707 if (isAltGlobalSpeedLimitEnabled())
2708 setAltGlobalUploadSpeedLimit(limit);
2709 else
2710 setGlobalUploadSpeedLimit(limit);
2711 }
2712
isAltGlobalSpeedLimitEnabled() const2713 bool Session::isAltGlobalSpeedLimitEnabled() const
2714 {
2715 return m_isAltGlobalSpeedLimitEnabled;
2716 }
2717
setAltGlobalSpeedLimitEnabled(const bool enabled)2718 void Session::setAltGlobalSpeedLimitEnabled(const bool enabled)
2719 {
2720 if (enabled == isAltGlobalSpeedLimitEnabled()) return;
2721
2722 // Save new state to remember it on startup
2723 m_isAltGlobalSpeedLimitEnabled = enabled;
2724 applyBandwidthLimits();
2725 // Notify
2726 emit speedLimitModeChanged(m_isAltGlobalSpeedLimitEnabled);
2727 }
2728
isBandwidthSchedulerEnabled() const2729 bool Session::isBandwidthSchedulerEnabled() const
2730 {
2731 return m_isBandwidthSchedulerEnabled;
2732 }
2733
setBandwidthSchedulerEnabled(const bool enabled)2734 void Session::setBandwidthSchedulerEnabled(const bool enabled)
2735 {
2736 if (enabled != isBandwidthSchedulerEnabled())
2737 {
2738 m_isBandwidthSchedulerEnabled = enabled;
2739 if (enabled)
2740 enableBandwidthScheduler();
2741 else
2742 delete m_bwScheduler;
2743 }
2744 }
2745
saveResumeDataInterval() const2746 int Session::saveResumeDataInterval() const
2747 {
2748 return m_saveResumeDataInterval;
2749 }
2750
setSaveResumeDataInterval(const int value)2751 void Session::setSaveResumeDataInterval(const int value)
2752 {
2753 if (value == m_saveResumeDataInterval)
2754 return;
2755
2756 m_saveResumeDataInterval = value;
2757
2758 if (value > 0)
2759 {
2760 m_resumeDataTimer->setInterval(value * 60 * 1000);
2761 m_resumeDataTimer->start();
2762 }
2763 else
2764 {
2765 m_resumeDataTimer->stop();
2766 }
2767 }
2768
port() const2769 int Session::port() const
2770 {
2771 return m_port;
2772 }
2773
setPort(const int port)2774 void Session::setPort(const int port)
2775 {
2776 if (port != m_port)
2777 {
2778 m_port = port;
2779 configureListeningInterface();
2780 }
2781 }
2782
useRandomPort() const2783 bool Session::useRandomPort() const
2784 {
2785 return m_useRandomPort;
2786 }
2787
setUseRandomPort(const bool value)2788 void Session::setUseRandomPort(const bool value)
2789 {
2790 m_useRandomPort = value;
2791 }
2792
networkInterface() const2793 QString Session::networkInterface() const
2794 {
2795 return m_networkInterface;
2796 }
2797
setNetworkInterface(const QString & iface)2798 void Session::setNetworkInterface(const QString &iface)
2799 {
2800 if (iface != networkInterface())
2801 {
2802 m_networkInterface = iface;
2803 configureListeningInterface();
2804 }
2805 }
2806
networkInterfaceName() const2807 QString Session::networkInterfaceName() const
2808 {
2809 return m_networkInterfaceName;
2810 }
2811
setNetworkInterfaceName(const QString & name)2812 void Session::setNetworkInterfaceName(const QString &name)
2813 {
2814 m_networkInterfaceName = name;
2815 }
2816
networkInterfaceAddress() const2817 QString Session::networkInterfaceAddress() const
2818 {
2819 return m_networkInterfaceAddress;
2820 }
2821
setNetworkInterfaceAddress(const QString & address)2822 void Session::setNetworkInterfaceAddress(const QString &address)
2823 {
2824 if (address != networkInterfaceAddress())
2825 {
2826 m_networkInterfaceAddress = address;
2827 configureListeningInterface();
2828 }
2829 }
2830
encryption() const2831 int Session::encryption() const
2832 {
2833 return m_encryption;
2834 }
2835
setEncryption(const int state)2836 void Session::setEncryption(const int state)
2837 {
2838 if (state != encryption())
2839 {
2840 m_encryption = state;
2841 configureDeferred();
2842 LogMsg(tr("Encryption support [%1]").arg(
2843 state == 0 ? tr("ON") : ((state == 1) ? tr("FORCED") : tr("OFF")))
2844 , Log::INFO);
2845 }
2846 }
2847
isProxyPeerConnectionsEnabled() const2848 bool Session::isProxyPeerConnectionsEnabled() const
2849 {
2850 return m_isProxyPeerConnectionsEnabled;
2851 }
2852
setProxyPeerConnectionsEnabled(const bool enabled)2853 void Session::setProxyPeerConnectionsEnabled(const bool enabled)
2854 {
2855 if (enabled != isProxyPeerConnectionsEnabled())
2856 {
2857 m_isProxyPeerConnectionsEnabled = enabled;
2858 configureDeferred();
2859 }
2860 }
2861
chokingAlgorithm() const2862 ChokingAlgorithm Session::chokingAlgorithm() const
2863 {
2864 return m_chokingAlgorithm;
2865 }
2866
setChokingAlgorithm(const ChokingAlgorithm mode)2867 void Session::setChokingAlgorithm(const ChokingAlgorithm mode)
2868 {
2869 if (mode == m_chokingAlgorithm) return;
2870
2871 m_chokingAlgorithm = mode;
2872 configureDeferred();
2873 }
2874
seedChokingAlgorithm() const2875 SeedChokingAlgorithm Session::seedChokingAlgorithm() const
2876 {
2877 return m_seedChokingAlgorithm;
2878 }
2879
setSeedChokingAlgorithm(const SeedChokingAlgorithm mode)2880 void Session::setSeedChokingAlgorithm(const SeedChokingAlgorithm mode)
2881 {
2882 if (mode == m_seedChokingAlgorithm) return;
2883
2884 m_seedChokingAlgorithm = mode;
2885 configureDeferred();
2886 }
2887
isAddTrackersEnabled() const2888 bool Session::isAddTrackersEnabled() const
2889 {
2890 return m_isAddTrackersEnabled;
2891 }
2892
setAddTrackersEnabled(const bool enabled)2893 void Session::setAddTrackersEnabled(const bool enabled)
2894 {
2895 m_isAddTrackersEnabled = enabled;
2896 }
2897
additionalTrackers() const2898 QString Session::additionalTrackers() const
2899 {
2900 return m_additionalTrackers;
2901 }
2902
setAdditionalTrackers(const QString & trackers)2903 void Session::setAdditionalTrackers(const QString &trackers)
2904 {
2905 if (trackers != additionalTrackers())
2906 {
2907 m_additionalTrackers = trackers;
2908 populateAdditionalTrackers();
2909 }
2910 }
2911
isIPFilteringEnabled() const2912 bool Session::isIPFilteringEnabled() const
2913 {
2914 return m_isIPFilteringEnabled;
2915 }
2916
setIPFilteringEnabled(const bool enabled)2917 void Session::setIPFilteringEnabled(const bool enabled)
2918 {
2919 if (enabled != m_isIPFilteringEnabled)
2920 {
2921 m_isIPFilteringEnabled = enabled;
2922 m_IPFilteringConfigured = false;
2923 configureDeferred();
2924 }
2925 }
2926
IPFilterFile() const2927 QString Session::IPFilterFile() const
2928 {
2929 return Utils::Fs::toUniformPath(m_IPFilterFile);
2930 }
2931
setIPFilterFile(QString path)2932 void Session::setIPFilterFile(QString path)
2933 {
2934 path = Utils::Fs::toUniformPath(path);
2935 if (path != IPFilterFile())
2936 {
2937 m_IPFilterFile = path;
2938 m_IPFilteringConfigured = false;
2939 configureDeferred();
2940 }
2941 }
2942
setBannedIPs(const QStringList & newList)2943 void Session::setBannedIPs(const QStringList &newList)
2944 {
2945 if (newList == m_bannedIPs)
2946 return; // do nothing
2947 // here filter out incorrect IP
2948 QStringList filteredList;
2949 for (const QString &ip : newList)
2950 {
2951 if (Utils::Net::isValidIP(ip))
2952 {
2953 // the same IPv6 addresses could be written in different forms;
2954 // QHostAddress::toString() result format follows RFC5952;
2955 // thus we avoid duplicate entries pointing to the same address
2956 filteredList << QHostAddress(ip).toString();
2957 }
2958 else
2959 {
2960 LogMsg(tr("%1 is not a valid IP address and was rejected while applying the list of banned addresses.")
2961 .arg(ip)
2962 , Log::WARNING);
2963 }
2964 }
2965 // now we have to sort IPs and make them unique
2966 filteredList.sort();
2967 filteredList.removeDuplicates();
2968 // Again ensure that the new list is different from the stored one.
2969 if (filteredList == m_bannedIPs)
2970 return; // do nothing
2971 // store to session settings
2972 // also here we have to recreate filter list including 3rd party ban file
2973 // and install it again into m_session
2974 m_bannedIPs = filteredList;
2975 m_IPFilteringConfigured = false;
2976 configureDeferred();
2977 }
2978
bannedIPs() const2979 QStringList Session::bannedIPs() const
2980 {
2981 return m_bannedIPs;
2982 }
2983
2984 #if defined(Q_OS_WIN)
getOSMemoryPriority() const2985 OSMemoryPriority Session::getOSMemoryPriority() const
2986 {
2987 return m_OSMemoryPriority;
2988 }
2989
setOSMemoryPriority(const OSMemoryPriority priority)2990 void Session::setOSMemoryPriority(const OSMemoryPriority priority)
2991 {
2992 if (m_OSMemoryPriority == priority)
2993 return;
2994
2995 m_OSMemoryPriority = priority;
2996 configureDeferred();
2997 }
2998
applyOSMemoryPriority() const2999 void Session::applyOSMemoryPriority() const
3000 {
3001 using SETPROCESSINFORMATION = BOOL (WINAPI *)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD);
3002 const auto setProcessInformation = Utils::Misc::loadWinAPI<SETPROCESSINFORMATION>("Kernel32.dll", "SetProcessInformation");
3003 if (!setProcessInformation) // only available on Windows >= 8
3004 return;
3005
3006 #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
3007 // this dummy struct is required to compile successfully when targeting older Windows version
3008 struct MEMORY_PRIORITY_INFORMATION
3009 {
3010 ULONG MemoryPriority;
3011 };
3012
3013 #define MEMORY_PRIORITY_LOWEST 0
3014 #define MEMORY_PRIORITY_VERY_LOW 1
3015 #define MEMORY_PRIORITY_LOW 2
3016 #define MEMORY_PRIORITY_MEDIUM 3
3017 #define MEMORY_PRIORITY_BELOW_NORMAL 4
3018 #define MEMORY_PRIORITY_NORMAL 5
3019 #endif
3020
3021 MEMORY_PRIORITY_INFORMATION prioInfo {};
3022 switch (getOSMemoryPriority())
3023 {
3024 case OSMemoryPriority::Normal:
3025 default:
3026 prioInfo.MemoryPriority = MEMORY_PRIORITY_NORMAL;
3027 break;
3028 case OSMemoryPriority::BelowNormal:
3029 prioInfo.MemoryPriority = MEMORY_PRIORITY_BELOW_NORMAL;
3030 break;
3031 case OSMemoryPriority::Medium:
3032 prioInfo.MemoryPriority = MEMORY_PRIORITY_MEDIUM;
3033 break;
3034 case OSMemoryPriority::Low:
3035 prioInfo.MemoryPriority = MEMORY_PRIORITY_LOW;
3036 break;
3037 case OSMemoryPriority::VeryLow:
3038 prioInfo.MemoryPriority = MEMORY_PRIORITY_VERY_LOW;
3039 break;
3040 }
3041 setProcessInformation(::GetCurrentProcess(), ProcessMemoryPriority, &prioInfo, sizeof(prioInfo));
3042 }
3043 #endif
3044
maxConnectionsPerTorrent() const3045 int Session::maxConnectionsPerTorrent() const
3046 {
3047 return m_maxConnectionsPerTorrent;
3048 }
3049
setMaxConnectionsPerTorrent(int max)3050 void Session::setMaxConnectionsPerTorrent(int max)
3051 {
3052 max = (max > 0) ? max : -1;
3053 if (max != maxConnectionsPerTorrent())
3054 {
3055 m_maxConnectionsPerTorrent = max;
3056
3057 // Apply this to all session torrents
3058 for (const lt::torrent_handle &handle : m_nativeSession->get_torrents())
3059 {
3060 if (!handle.is_valid()) continue;
3061 try
3062 {
3063 handle.set_max_connections(max);
3064 }
3065 catch (const std::exception &) {}
3066 }
3067 }
3068 }
3069
maxUploadsPerTorrent() const3070 int Session::maxUploadsPerTorrent() const
3071 {
3072 return m_maxUploadsPerTorrent;
3073 }
3074
setMaxUploadsPerTorrent(int max)3075 void Session::setMaxUploadsPerTorrent(int max)
3076 {
3077 max = (max > 0) ? max : -1;
3078 if (max != maxUploadsPerTorrent())
3079 {
3080 m_maxUploadsPerTorrent = max;
3081
3082 // Apply this to all session torrents
3083 for (const lt::torrent_handle &handle : m_nativeSession->get_torrents())
3084 {
3085 if (!handle.is_valid()) continue;
3086 try
3087 {
3088 handle.set_max_uploads(max);
3089 }
3090 catch (const std::exception &) {}
3091 }
3092 }
3093 }
3094
announceToAllTrackers() const3095 bool Session::announceToAllTrackers() const
3096 {
3097 return m_announceToAllTrackers;
3098 }
3099
setAnnounceToAllTrackers(const bool val)3100 void Session::setAnnounceToAllTrackers(const bool val)
3101 {
3102 if (val != m_announceToAllTrackers)
3103 {
3104 m_announceToAllTrackers = val;
3105 configureDeferred();
3106 }
3107 }
3108
announceToAllTiers() const3109 bool Session::announceToAllTiers() const
3110 {
3111 return m_announceToAllTiers;
3112 }
3113
setAnnounceToAllTiers(const bool val)3114 void Session::setAnnounceToAllTiers(const bool val)
3115 {
3116 if (val != m_announceToAllTiers)
3117 {
3118 m_announceToAllTiers = val;
3119 configureDeferred();
3120 }
3121 }
3122
peerTurnover() const3123 int Session::peerTurnover() const
3124 {
3125 return m_peerTurnover;
3126 }
3127
setPeerTurnover(const int val)3128 void Session::setPeerTurnover(const int val)
3129 {
3130 if (val == m_peerTurnover)
3131 return;
3132
3133 m_peerTurnover = val;
3134 configureDeferred();
3135 }
3136
peerTurnoverCutoff() const3137 int Session::peerTurnoverCutoff() const
3138 {
3139 return m_peerTurnoverCutoff;
3140 }
3141
setPeerTurnoverCutoff(const int val)3142 void Session::setPeerTurnoverCutoff(const int val)
3143 {
3144 if (val == m_peerTurnoverCutoff)
3145 return;
3146
3147 m_peerTurnoverCutoff = val;
3148 configureDeferred();
3149 }
3150
peerTurnoverInterval() const3151 int Session::peerTurnoverInterval() const
3152 {
3153 return m_peerTurnoverInterval;
3154 }
3155
setPeerTurnoverInterval(const int val)3156 void Session::setPeerTurnoverInterval(const int val)
3157 {
3158 if (val == m_peerTurnoverInterval)
3159 return;
3160
3161 m_peerTurnoverInterval = val;
3162 configureDeferred();
3163 }
3164
asyncIOThreads() const3165 int Session::asyncIOThreads() const
3166 {
3167 return qBound(1, m_asyncIOThreads.get(), 1024);
3168 }
3169
setAsyncIOThreads(const int num)3170 void Session::setAsyncIOThreads(const int num)
3171 {
3172 if (num == m_asyncIOThreads)
3173 return;
3174
3175 m_asyncIOThreads = num;
3176 configureDeferred();
3177 }
3178
hashingThreads() const3179 int Session::hashingThreads() const
3180 {
3181 return qBound(1, m_hashingThreads.get(), 1024);
3182 }
3183
setHashingThreads(const int num)3184 void Session::setHashingThreads(const int num)
3185 {
3186 if (num == m_hashingThreads)
3187 return;
3188
3189 m_hashingThreads = num;
3190 configureDeferred();
3191 }
3192
filePoolSize() const3193 int Session::filePoolSize() const
3194 {
3195 return m_filePoolSize;
3196 }
3197
setFilePoolSize(const int size)3198 void Session::setFilePoolSize(const int size)
3199 {
3200 if (size == m_filePoolSize)
3201 return;
3202
3203 m_filePoolSize = size;
3204 configureDeferred();
3205 }
3206
checkingMemUsage() const3207 int Session::checkingMemUsage() const
3208 {
3209 return qMax(1, m_checkingMemUsage.get());
3210 }
3211
setCheckingMemUsage(int size)3212 void Session::setCheckingMemUsage(int size)
3213 {
3214 size = qMax(size, 1);
3215
3216 if (size == m_checkingMemUsage)
3217 return;
3218
3219 m_checkingMemUsage = size;
3220 configureDeferred();
3221 }
3222
diskCacheSize() const3223 int Session::diskCacheSize() const
3224 {
3225 #ifdef QBT_APP_64BIT
3226 return qMin(m_diskCacheSize.get(), 33554431); // 32768GiB
3227 #else
3228 // When build as 32bit binary, set the maximum at less than 2GB to prevent crashes
3229 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
3230 return qMin(m_diskCacheSize.get(), 1536);
3231 #endif
3232 }
3233
setDiskCacheSize(int size)3234 void Session::setDiskCacheSize(int size)
3235 {
3236 #ifdef QBT_APP_64BIT
3237 size = qMin(size, 33554431); // 32768GiB
3238 #else
3239 // allocate 1536MiB and leave 512MiB to the rest of program data in RAM
3240 size = qMin(size, 1536);
3241 #endif
3242 if (size != m_diskCacheSize)
3243 {
3244 m_diskCacheSize = size;
3245 configureDeferred();
3246 }
3247 }
3248
diskCacheTTL() const3249 int Session::diskCacheTTL() const
3250 {
3251 return m_diskCacheTTL;
3252 }
3253
setDiskCacheTTL(const int ttl)3254 void Session::setDiskCacheTTL(const int ttl)
3255 {
3256 if (ttl != m_diskCacheTTL)
3257 {
3258 m_diskCacheTTL = ttl;
3259 configureDeferred();
3260 }
3261 }
3262
useOSCache() const3263 bool Session::useOSCache() const
3264 {
3265 return m_useOSCache;
3266 }
3267
setUseOSCache(const bool use)3268 void Session::setUseOSCache(const bool use)
3269 {
3270 if (use != m_useOSCache)
3271 {
3272 m_useOSCache = use;
3273 configureDeferred();
3274 }
3275 }
3276
isCoalesceReadWriteEnabled() const3277 bool Session::isCoalesceReadWriteEnabled() const
3278 {
3279 return m_coalesceReadWriteEnabled;
3280 }
3281
setCoalesceReadWriteEnabled(const bool enabled)3282 void Session::setCoalesceReadWriteEnabled(const bool enabled)
3283 {
3284 if (enabled == m_coalesceReadWriteEnabled) return;
3285
3286 m_coalesceReadWriteEnabled = enabled;
3287 configureDeferred();
3288 }
3289
isSuggestModeEnabled() const3290 bool Session::isSuggestModeEnabled() const
3291 {
3292 return m_isSuggestMode;
3293 }
3294
usePieceExtentAffinity() const3295 bool Session::usePieceExtentAffinity() const
3296 {
3297 return m_usePieceExtentAffinity;
3298 }
3299
setPieceExtentAffinity(const bool enabled)3300 void Session::setPieceExtentAffinity(const bool enabled)
3301 {
3302 if (enabled == m_usePieceExtentAffinity) return;
3303
3304 m_usePieceExtentAffinity = enabled;
3305 configureDeferred();
3306 }
3307
setSuggestMode(const bool mode)3308 void Session::setSuggestMode(const bool mode)
3309 {
3310 if (mode == m_isSuggestMode) return;
3311
3312 m_isSuggestMode = mode;
3313 configureDeferred();
3314 }
3315
sendBufferWatermark() const3316 int Session::sendBufferWatermark() const
3317 {
3318 return m_sendBufferWatermark;
3319 }
3320
setSendBufferWatermark(const int value)3321 void Session::setSendBufferWatermark(const int value)
3322 {
3323 if (value == m_sendBufferWatermark) return;
3324
3325 m_sendBufferWatermark = value;
3326 configureDeferred();
3327 }
3328
sendBufferLowWatermark() const3329 int Session::sendBufferLowWatermark() const
3330 {
3331 return m_sendBufferLowWatermark;
3332 }
3333
setSendBufferLowWatermark(const int value)3334 void Session::setSendBufferLowWatermark(const int value)
3335 {
3336 if (value == m_sendBufferLowWatermark) return;
3337
3338 m_sendBufferLowWatermark = value;
3339 configureDeferred();
3340 }
3341
sendBufferWatermarkFactor() const3342 int Session::sendBufferWatermarkFactor() const
3343 {
3344 return m_sendBufferWatermarkFactor;
3345 }
3346
setSendBufferWatermarkFactor(const int value)3347 void Session::setSendBufferWatermarkFactor(const int value)
3348 {
3349 if (value == m_sendBufferWatermarkFactor) return;
3350
3351 m_sendBufferWatermarkFactor = value;
3352 configureDeferred();
3353 }
3354
socketBacklogSize() const3355 int Session::socketBacklogSize() const
3356 {
3357 return m_socketBacklogSize;
3358 }
3359
setSocketBacklogSize(const int value)3360 void Session::setSocketBacklogSize(const int value)
3361 {
3362 if (value == m_socketBacklogSize) return;
3363
3364 m_socketBacklogSize = value;
3365 configureDeferred();
3366 }
3367
isAnonymousModeEnabled() const3368 bool Session::isAnonymousModeEnabled() const
3369 {
3370 return m_isAnonymousModeEnabled;
3371 }
3372
setAnonymousModeEnabled(const bool enabled)3373 void Session::setAnonymousModeEnabled(const bool enabled)
3374 {
3375 if (enabled != m_isAnonymousModeEnabled)
3376 {
3377 m_isAnonymousModeEnabled = enabled;
3378 configureDeferred();
3379 LogMsg(tr("Anonymous mode [%1]").arg(isAnonymousModeEnabled() ? tr("ON") : tr("OFF"))
3380 , Log::INFO);
3381 }
3382 }
3383
isQueueingSystemEnabled() const3384 bool Session::isQueueingSystemEnabled() const
3385 {
3386 return m_isQueueingEnabled;
3387 }
3388
setQueueingSystemEnabled(const bool enabled)3389 void Session::setQueueingSystemEnabled(const bool enabled)
3390 {
3391 if (enabled != m_isQueueingEnabled)
3392 {
3393 m_isQueueingEnabled = enabled;
3394 configureDeferred();
3395
3396 if (enabled)
3397 saveTorrentsQueue();
3398 else
3399 removeTorrentsQueue();
3400 }
3401 }
3402
maxActiveDownloads() const3403 int Session::maxActiveDownloads() const
3404 {
3405 return m_maxActiveDownloads;
3406 }
3407
setMaxActiveDownloads(int max)3408 void Session::setMaxActiveDownloads(int max)
3409 {
3410 max = std::max(max, -1);
3411 if (max != m_maxActiveDownloads)
3412 {
3413 m_maxActiveDownloads = max;
3414 configureDeferred();
3415 }
3416 }
3417
maxActiveUploads() const3418 int Session::maxActiveUploads() const
3419 {
3420 return m_maxActiveUploads;
3421 }
3422
setMaxActiveUploads(int max)3423 void Session::setMaxActiveUploads(int max)
3424 {
3425 max = std::max(max, -1);
3426 if (max != m_maxActiveUploads)
3427 {
3428 m_maxActiveUploads = max;
3429 configureDeferred();
3430 }
3431 }
3432
maxActiveTorrents() const3433 int Session::maxActiveTorrents() const
3434 {
3435 return m_maxActiveTorrents;
3436 }
3437
setMaxActiveTorrents(int max)3438 void Session::setMaxActiveTorrents(int max)
3439 {
3440 max = std::max(max, -1);
3441 if (max != m_maxActiveTorrents)
3442 {
3443 m_maxActiveTorrents = max;
3444 configureDeferred();
3445 }
3446 }
3447
ignoreSlowTorrentsForQueueing() const3448 bool Session::ignoreSlowTorrentsForQueueing() const
3449 {
3450 return m_ignoreSlowTorrentsForQueueing;
3451 }
3452
setIgnoreSlowTorrentsForQueueing(const bool ignore)3453 void Session::setIgnoreSlowTorrentsForQueueing(const bool ignore)
3454 {
3455 if (ignore != m_ignoreSlowTorrentsForQueueing)
3456 {
3457 m_ignoreSlowTorrentsForQueueing = ignore;
3458 configureDeferred();
3459 }
3460 }
3461
downloadRateForSlowTorrents() const3462 int Session::downloadRateForSlowTorrents() const
3463 {
3464 return m_downloadRateForSlowTorrents;
3465 }
3466
setDownloadRateForSlowTorrents(const int rateInKibiBytes)3467 void Session::setDownloadRateForSlowTorrents(const int rateInKibiBytes)
3468 {
3469 if (rateInKibiBytes == m_downloadRateForSlowTorrents)
3470 return;
3471
3472 m_downloadRateForSlowTorrents = rateInKibiBytes;
3473 configureDeferred();
3474 }
3475
uploadRateForSlowTorrents() const3476 int Session::uploadRateForSlowTorrents() const
3477 {
3478 return m_uploadRateForSlowTorrents;
3479 }
3480
setUploadRateForSlowTorrents(const int rateInKibiBytes)3481 void Session::setUploadRateForSlowTorrents(const int rateInKibiBytes)
3482 {
3483 if (rateInKibiBytes == m_uploadRateForSlowTorrents)
3484 return;
3485
3486 m_uploadRateForSlowTorrents = rateInKibiBytes;
3487 configureDeferred();
3488 }
3489
slowTorrentsInactivityTimer() const3490 int Session::slowTorrentsInactivityTimer() const
3491 {
3492 return m_slowTorrentsInactivityTimer;
3493 }
3494
setSlowTorrentsInactivityTimer(const int timeInSeconds)3495 void Session::setSlowTorrentsInactivityTimer(const int timeInSeconds)
3496 {
3497 if (timeInSeconds == m_slowTorrentsInactivityTimer)
3498 return;
3499
3500 m_slowTorrentsInactivityTimer = timeInSeconds;
3501 configureDeferred();
3502 }
3503
outgoingPortsMin() const3504 int Session::outgoingPortsMin() const
3505 {
3506 return m_outgoingPortsMin;
3507 }
3508
setOutgoingPortsMin(const int min)3509 void Session::setOutgoingPortsMin(const int min)
3510 {
3511 if (min != m_outgoingPortsMin)
3512 {
3513 m_outgoingPortsMin = min;
3514 configureDeferred();
3515 }
3516 }
3517
outgoingPortsMax() const3518 int Session::outgoingPortsMax() const
3519 {
3520 return m_outgoingPortsMax;
3521 }
3522
setOutgoingPortsMax(const int max)3523 void Session::setOutgoingPortsMax(const int max)
3524 {
3525 if (max != m_outgoingPortsMax)
3526 {
3527 m_outgoingPortsMax = max;
3528 configureDeferred();
3529 }
3530 }
3531
UPnPLeaseDuration() const3532 int Session::UPnPLeaseDuration() const
3533 {
3534 return m_UPnPLeaseDuration;
3535 }
3536
setUPnPLeaseDuration(const int duration)3537 void Session::setUPnPLeaseDuration(const int duration)
3538 {
3539 if (duration != m_UPnPLeaseDuration)
3540 {
3541 m_UPnPLeaseDuration = duration;
3542 configureDeferred();
3543 }
3544 }
3545
peerToS() const3546 int Session::peerToS() const
3547 {
3548 return m_peerToS;
3549 }
3550
setPeerToS(const int value)3551 void Session::setPeerToS(const int value)
3552 {
3553 if (value == m_peerToS)
3554 return;
3555
3556 m_peerToS = value;
3557 configureDeferred();
3558 }
3559
ignoreLimitsOnLAN() const3560 bool Session::ignoreLimitsOnLAN() const
3561 {
3562 return m_ignoreLimitsOnLAN;
3563 }
3564
setIgnoreLimitsOnLAN(const bool ignore)3565 void Session::setIgnoreLimitsOnLAN(const bool ignore)
3566 {
3567 if (ignore != m_ignoreLimitsOnLAN)
3568 {
3569 m_ignoreLimitsOnLAN = ignore;
3570 configureDeferred();
3571 }
3572 }
3573
includeOverheadInLimits() const3574 bool Session::includeOverheadInLimits() const
3575 {
3576 return m_includeOverheadInLimits;
3577 }
3578
setIncludeOverheadInLimits(const bool include)3579 void Session::setIncludeOverheadInLimits(const bool include)
3580 {
3581 if (include != m_includeOverheadInLimits)
3582 {
3583 m_includeOverheadInLimits = include;
3584 configureDeferred();
3585 }
3586 }
3587
announceIP() const3588 QString Session::announceIP() const
3589 {
3590 return m_announceIP;
3591 }
3592
setAnnounceIP(const QString & ip)3593 void Session::setAnnounceIP(const QString &ip)
3594 {
3595 if (ip != m_announceIP)
3596 {
3597 m_announceIP = ip;
3598 configureDeferred();
3599 }
3600 }
3601
maxConcurrentHTTPAnnounces() const3602 int Session::maxConcurrentHTTPAnnounces() const
3603 {
3604 return m_maxConcurrentHTTPAnnounces;
3605 }
3606
setMaxConcurrentHTTPAnnounces(const int value)3607 void Session::setMaxConcurrentHTTPAnnounces(const int value)
3608 {
3609 if (value == m_maxConcurrentHTTPAnnounces)
3610 return;
3611
3612 m_maxConcurrentHTTPAnnounces = value;
3613 configureDeferred();
3614 }
3615
stopTrackerTimeout() const3616 int Session::stopTrackerTimeout() const
3617 {
3618 return m_stopTrackerTimeout;
3619 }
3620
setStopTrackerTimeout(const int value)3621 void Session::setStopTrackerTimeout(const int value)
3622 {
3623 if (value == m_stopTrackerTimeout)
3624 return;
3625
3626 m_stopTrackerTimeout = value;
3627 configureDeferred();
3628 }
3629
maxConnections() const3630 int Session::maxConnections() const
3631 {
3632 return m_maxConnections;
3633 }
3634
setMaxConnections(int max)3635 void Session::setMaxConnections(int max)
3636 {
3637 max = (max > 0) ? max : -1;
3638 if (max != m_maxConnections)
3639 {
3640 m_maxConnections = max;
3641 configureDeferred();
3642 }
3643 }
3644
maxUploads() const3645 int Session::maxUploads() const
3646 {
3647 return m_maxUploads;
3648 }
3649
setMaxUploads(int max)3650 void Session::setMaxUploads(int max)
3651 {
3652 max = (max > 0) ? max : -1;
3653 if (max != m_maxUploads)
3654 {
3655 m_maxUploads = max;
3656 configureDeferred();
3657 }
3658 }
3659
btProtocol() const3660 BTProtocol Session::btProtocol() const
3661 {
3662 return m_btProtocol;
3663 }
3664
setBTProtocol(const BTProtocol protocol)3665 void Session::setBTProtocol(const BTProtocol protocol)
3666 {
3667 if ((protocol < BTProtocol::Both) || (BTProtocol::UTP < protocol))
3668 return;
3669
3670 if (protocol == m_btProtocol) return;
3671
3672 m_btProtocol = protocol;
3673 configureDeferred();
3674 }
3675
isUTPRateLimited() const3676 bool Session::isUTPRateLimited() const
3677 {
3678 return m_isUTPRateLimited;
3679 }
3680
setUTPRateLimited(const bool limited)3681 void Session::setUTPRateLimited(const bool limited)
3682 {
3683 if (limited != m_isUTPRateLimited)
3684 {
3685 m_isUTPRateLimited = limited;
3686 configureDeferred();
3687 }
3688 }
3689
utpMixedMode() const3690 MixedModeAlgorithm Session::utpMixedMode() const
3691 {
3692 return m_utpMixedMode;
3693 }
3694
setUtpMixedMode(const MixedModeAlgorithm mode)3695 void Session::setUtpMixedMode(const MixedModeAlgorithm mode)
3696 {
3697 if (mode == m_utpMixedMode) return;
3698
3699 m_utpMixedMode = mode;
3700 configureDeferred();
3701 }
3702
isIDNSupportEnabled() const3703 bool Session::isIDNSupportEnabled() const
3704 {
3705 return m_IDNSupportEnabled;
3706 }
3707
setIDNSupportEnabled(const bool enabled)3708 void Session::setIDNSupportEnabled(const bool enabled)
3709 {
3710 if (enabled == m_IDNSupportEnabled) return;
3711
3712 m_IDNSupportEnabled = enabled;
3713 configureDeferred();
3714 }
3715
multiConnectionsPerIpEnabled() const3716 bool Session::multiConnectionsPerIpEnabled() const
3717 {
3718 return m_multiConnectionsPerIpEnabled;
3719 }
3720
setMultiConnectionsPerIpEnabled(const bool enabled)3721 void Session::setMultiConnectionsPerIpEnabled(const bool enabled)
3722 {
3723 if (enabled == m_multiConnectionsPerIpEnabled) return;
3724
3725 m_multiConnectionsPerIpEnabled = enabled;
3726 configureDeferred();
3727 }
3728
validateHTTPSTrackerCertificate() const3729 bool Session::validateHTTPSTrackerCertificate() const
3730 {
3731 return m_validateHTTPSTrackerCertificate;
3732 }
3733
setValidateHTTPSTrackerCertificate(const bool enabled)3734 void Session::setValidateHTTPSTrackerCertificate(const bool enabled)
3735 {
3736 if (enabled == m_validateHTTPSTrackerCertificate) return;
3737
3738 m_validateHTTPSTrackerCertificate = enabled;
3739 configureDeferred();
3740 }
3741
isSSRFMitigationEnabled() const3742 bool Session::isSSRFMitigationEnabled() const
3743 {
3744 return m_SSRFMitigationEnabled;
3745 }
3746
setSSRFMitigationEnabled(const bool enabled)3747 void Session::setSSRFMitigationEnabled(const bool enabled)
3748 {
3749 if (enabled == m_SSRFMitigationEnabled) return;
3750
3751 m_SSRFMitigationEnabled = enabled;
3752 configureDeferred();
3753 }
3754
blockPeersOnPrivilegedPorts() const3755 bool Session::blockPeersOnPrivilegedPorts() const
3756 {
3757 return m_blockPeersOnPrivilegedPorts;
3758 }
3759
setBlockPeersOnPrivilegedPorts(const bool enabled)3760 void Session::setBlockPeersOnPrivilegedPorts(const bool enabled)
3761 {
3762 if (enabled == m_blockPeersOnPrivilegedPorts) return;
3763
3764 m_blockPeersOnPrivilegedPorts = enabled;
3765 configureDeferred();
3766 }
3767
isTrackerFilteringEnabled() const3768 bool Session::isTrackerFilteringEnabled() const
3769 {
3770 return m_isTrackerFilteringEnabled;
3771 }
3772
setTrackerFilteringEnabled(const bool enabled)3773 void Session::setTrackerFilteringEnabled(const bool enabled)
3774 {
3775 if (enabled != m_isTrackerFilteringEnabled)
3776 {
3777 m_isTrackerFilteringEnabled = enabled;
3778 configureDeferred();
3779 }
3780 }
3781
isListening() const3782 bool Session::isListening() const
3783 {
3784 return m_nativeSession->is_listening();
3785 }
3786
maxRatioAction() const3787 MaxRatioAction Session::maxRatioAction() const
3788 {
3789 return static_cast<MaxRatioAction>(m_maxRatioAction.get());
3790 }
3791
setMaxRatioAction(const MaxRatioAction act)3792 void Session::setMaxRatioAction(const MaxRatioAction act)
3793 {
3794 m_maxRatioAction = static_cast<int>(act);
3795 }
3796
3797 // If this functions returns true, we cannot add torrent to session,
3798 // but it is still possible to merge trackers in some cases
isKnownTorrent(const TorrentID & id) const3799 bool Session::isKnownTorrent(const TorrentID &id) const
3800 {
3801 return (m_torrents.contains(id)
3802 || m_loadingTorrents.contains(id)
3803 || m_downloadedMetadata.contains(id));
3804 }
3805
updateSeedingLimitTimer()3806 void Session::updateSeedingLimitTimer()
3807 {
3808 if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
3809 && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit())
3810 {
3811 if (m_seedingLimitTimer->isActive())
3812 m_seedingLimitTimer->stop();
3813 }
3814 else if (!m_seedingLimitTimer->isActive())
3815 {
3816 m_seedingLimitTimer->start();
3817 }
3818 }
3819
handleTorrentShareLimitChanged(TorrentImpl * const torrent)3820 void Session::handleTorrentShareLimitChanged(TorrentImpl *const torrent)
3821 {
3822 updateSeedingLimitTimer();
3823 }
3824
handleTorrentNameChanged(TorrentImpl * const torrent)3825 void Session::handleTorrentNameChanged(TorrentImpl *const torrent)
3826 {
3827 Q_UNUSED(torrent);
3828 }
3829
handleTorrentSavePathChanged(TorrentImpl * const torrent)3830 void Session::handleTorrentSavePathChanged(TorrentImpl *const torrent)
3831 {
3832 emit torrentSavePathChanged(torrent);
3833 }
3834
handleTorrentCategoryChanged(TorrentImpl * const torrent,const QString & oldCategory)3835 void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
3836 {
3837 emit torrentCategoryChanged(torrent, oldCategory);
3838 }
3839
handleTorrentTagAdded(TorrentImpl * const torrent,const QString & tag)3840 void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
3841 {
3842 emit torrentTagAdded(torrent, tag);
3843 }
3844
handleTorrentTagRemoved(TorrentImpl * const torrent,const QString & tag)3845 void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
3846 {
3847 emit torrentTagRemoved(torrent, tag);
3848 }
3849
handleTorrentSavingModeChanged(TorrentImpl * const torrent)3850 void Session::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
3851 {
3852 emit torrentSavingModeChanged(torrent);
3853 }
3854
handleTorrentTrackersAdded(TorrentImpl * const torrent,const QVector<TrackerEntry> & newTrackers)3855 void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
3856 {
3857 for (const TrackerEntry &newTracker : newTrackers)
3858 LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url, torrent->name()));
3859 emit trackersAdded(torrent, newTrackers);
3860 if (torrent->trackers().size() == newTrackers.size())
3861 emit trackerlessStateChanged(torrent, false);
3862 emit trackersChanged(torrent);
3863 }
3864
handleTorrentTrackersRemoved(TorrentImpl * const torrent,const QVector<TrackerEntry> & deletedTrackers)3865 void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers)
3866 {
3867 for (const TrackerEntry &deletedTracker : deletedTrackers)
3868 LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url, torrent->name()));
3869 emit trackersRemoved(torrent, deletedTrackers);
3870 if (torrent->trackers().empty())
3871 emit trackerlessStateChanged(torrent, true);
3872 emit trackersChanged(torrent);
3873 }
3874
handleTorrentTrackersChanged(TorrentImpl * const torrent)3875 void Session::handleTorrentTrackersChanged(TorrentImpl *const torrent)
3876 {
3877 emit trackersChanged(torrent);
3878 }
3879
handleTorrentUrlSeedsAdded(TorrentImpl * const torrent,const QVector<QUrl> & newUrlSeeds)3880 void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
3881 {
3882 for (const QUrl &newUrlSeed : newUrlSeeds)
3883 LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
3884 }
3885
handleTorrentUrlSeedsRemoved(TorrentImpl * const torrent,const QVector<QUrl> & urlSeeds)3886 void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
3887 {
3888 for (const QUrl &urlSeed : urlSeeds)
3889 LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
3890 }
3891
handleTorrentMetadataReceived(TorrentImpl * const torrent)3892 void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
3893 {
3894 // Save metadata
3895 const QDir resumeDataDir {m_resumeFolderPath};
3896 const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->id().toString())};
3897 try
3898 {
3899 torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
3900 // Copy the torrent file to the export folder
3901 if (!torrentExportDirectory().isEmpty())
3902 exportTorrentFile(torrent);
3903 }
3904 catch (const RuntimeError &err)
3905 {
3906 LogMsg(tr("Couldn't save torrent metadata file '%1'. Reason: %2")
3907 .arg(torrentFileName, err.message()), Log::CRITICAL);
3908 }
3909
3910 emit torrentMetadataReceived(torrent);
3911 }
3912
handleTorrentPaused(TorrentImpl * const torrent)3913 void Session::handleTorrentPaused(TorrentImpl *const torrent)
3914 {
3915 emit torrentPaused(torrent);
3916 }
3917
handleTorrentResumed(TorrentImpl * const torrent)3918 void Session::handleTorrentResumed(TorrentImpl *const torrent)
3919 {
3920 emit torrentResumed(torrent);
3921 }
3922
handleTorrentChecked(TorrentImpl * const torrent)3923 void Session::handleTorrentChecked(TorrentImpl *const torrent)
3924 {
3925 emit torrentFinishedChecking(torrent);
3926 }
3927
handleTorrentFinished(TorrentImpl * const torrent)3928 void Session::handleTorrentFinished(TorrentImpl *const torrent)
3929 {
3930 emit torrentFinished(torrent);
3931
3932 qDebug("Checking if the torrent contains torrent files to download");
3933 // Check if there are torrent files inside
3934 for (int i = 0; i < torrent->filesCount(); ++i)
3935 {
3936 const QString torrentRelpath = torrent->filePath(i);
3937 if (torrentRelpath.endsWith(".torrent", Qt::CaseInsensitive))
3938 {
3939 qDebug("Found possible recursive torrent download.");
3940 const QString torrentFullpath = torrent->savePath(true) + '/' + torrentRelpath;
3941 qDebug("Full subtorrent path is %s", qUtf8Printable(torrentFullpath));
3942 TorrentInfo torrentInfo = TorrentInfo::loadFromFile(torrentFullpath);
3943 if (torrentInfo.isValid())
3944 {
3945 qDebug("emitting recursiveTorrentDownloadPossible()");
3946 emit recursiveTorrentDownloadPossible(torrent);
3947 break;
3948 }
3949 else
3950 {
3951 qDebug("Caught error loading torrent");
3952 LogMsg(tr("Unable to decode '%1' torrent file.").arg(Utils::Fs::toNativePath(torrentFullpath)), Log::CRITICAL);
3953 }
3954 }
3955 }
3956
3957 // Move .torrent file to another folder
3958 if (!finishedTorrentExportDirectory().isEmpty())
3959 exportTorrentFile(torrent, TorrentExportFolder::Finished);
3960
3961 if (!hasUnfinishedTorrents())
3962 emit allTorrentsFinished();
3963 }
3964
handleTorrentResumeDataReady(TorrentImpl * const torrent,const LoadTorrentParams & data)3965 void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
3966 {
3967 --m_numResumeData;
3968
3969 // We need to adjust native libtorrent resume data
3970 lt::add_torrent_params p = data.ltAddTorrentParams;
3971 p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString();
3972 if (data.paused)
3973 {
3974 p.flags |= lt::torrent_flags::paused;
3975 p.flags &= ~lt::torrent_flags::auto_managed;
3976 }
3977 else
3978 {
3979 // Torrent can be actually "running" but temporarily "paused" to perform some
3980 // service jobs behind the scenes so we need to restore it as "running"
3981 if (!data.forced)
3982 {
3983 p.flags |= lt::torrent_flags::auto_managed;
3984 }
3985 else
3986 {
3987 p.flags &= ~lt::torrent_flags::paused;
3988 p.flags &= ~lt::torrent_flags::auto_managed;
3989 }
3990 }
3991
3992 // Separated thread is used for the blocking IO which results in slow processing of many torrents.
3993 // Copying lt::entry objects around isn't cheap.
3994
3995 auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(p));
3996 lt::entry &resumeData = *resumeDataPtr;
3997
3998 resumeData["qBt-savePath"] = Profile::instance()->toPortablePath(data.savePath).toStdString();
3999 resumeData["qBt-ratioLimit"] = static_cast<int>(data.ratioLimit * 1000);
4000 resumeData["qBt-seedingTimeLimit"] = data.seedingTimeLimit;
4001 resumeData["qBt-category"] = data.category.toStdString();
4002 resumeData["qBt-tags"] = setToEntryList(data.tags);
4003 resumeData["qBt-name"] = data.name.toStdString();
4004 resumeData["qBt-seedStatus"] = data.hasSeedStatus;
4005 resumeData["qBt-contentLayout"] = Utils::String::fromEnum(data.contentLayout).toStdString();
4006 resumeData["qBt-firstLastPiecePriority"] = data.firstLastPiecePriority;
4007
4008 const QString filename = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
4009 QMetaObject::invokeMethod(m_resumeDataSavingManager
4010 , [this, filename, resumeDataPtr]() { m_resumeDataSavingManager->save(filename, resumeDataPtr); });
4011 }
4012
handleTorrentTrackerReply(TorrentImpl * const torrent,const QString & trackerUrl)4013 void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl)
4014 {
4015 emit trackerSuccess(torrent, trackerUrl);
4016 }
4017
handleTorrentTrackerError(TorrentImpl * const torrent,const QString & trackerUrl)4018 void Session::handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl)
4019 {
4020 emit trackerError(torrent, trackerUrl);
4021 }
4022
addMoveTorrentStorageJob(TorrentImpl * torrent,const QString & newPath,const MoveStorageMode mode)4023 bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, const MoveStorageMode mode)
4024 {
4025 Q_ASSERT(torrent);
4026
4027 const lt::torrent_handle torrentHandle = torrent->nativeHandle();
4028 const QString currentLocation = torrent->actualStorageLocation();
4029
4030 if (m_moveStorageQueue.size() > 1)
4031 {
4032 const auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
4033 , [&torrentHandle](const MoveStorageJob &job)
4034 {
4035 return job.torrentHandle == torrentHandle;
4036 });
4037
4038 if (iter != m_moveStorageQueue.end())
4039 {
4040 // remove existing inactive job
4041 m_moveStorageQueue.erase(iter);
4042 LogMsg(tr("Cancelled moving \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, iter->path));
4043 }
4044 }
4045
4046 if (!m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle))
4047 {
4048 // if there is active job for this torrent prevent creating meaningless
4049 // job that will move torrent to the same location as current one
4050 if (QDir {m_moveStorageQueue.first().path} == QDir {newPath})
4051 {
4052 LogMsg(tr("Couldn't enqueue move of \"%1\" to \"%2\". Torrent is currently moving to the same destination location.")
4053 .arg(torrent->name(), newPath));
4054 return false;
4055 }
4056 }
4057 else
4058 {
4059 if (QDir {currentLocation} == QDir {newPath})
4060 {
4061 LogMsg(tr("Couldn't enqueue move of \"%1\" from \"%2\" to \"%3\". Both paths point to the same location.")
4062 .arg(torrent->name(), currentLocation, newPath));
4063 return false;
4064 }
4065 }
4066
4067 const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode};
4068 m_moveStorageQueue << moveStorageJob;
4069 LogMsg(tr("Enqueued to move \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, newPath));
4070
4071 if (m_moveStorageQueue.size() == 1)
4072 moveTorrentStorage(moveStorageJob);
4073
4074 return true;
4075 }
4076
moveTorrentStorage(const MoveStorageJob & job) const4077 void Session::moveTorrentStorage(const MoveStorageJob &job) const
4078 {
4079 #if (LIBTORRENT_VERSION_NUM >= 20000)
4080 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
4081 #else
4082 const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
4083 #endif
4084 const TorrentImpl *torrent = m_torrents.value(id);
4085 const QString torrentName = (torrent ? torrent->name() : id.toString());
4086 LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path));
4087
4088 job.torrentHandle.move_storage(job.path.toUtf8().constData()
4089 , ((job.mode == MoveStorageMode::Overwrite)
4090 ? lt::move_flags_t::always_replace_files : lt::move_flags_t::dont_replace));
4091 }
4092
handleMoveTorrentStorageJobFinished()4093 void Session::handleMoveTorrentStorageJobFinished()
4094 {
4095 const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
4096 if (!m_moveStorageQueue.isEmpty())
4097 moveTorrentStorage(m_moveStorageQueue.first());
4098
4099 const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
4100 , [&finishedJob](const MoveStorageJob &job)
4101 {
4102 return job.torrentHandle == finishedJob.torrentHandle;
4103 });
4104
4105 const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
4106
4107 TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
4108 if (torrent)
4109 {
4110 torrent->handleMoveStorageJobFinished(torrentHasOutstandingJob);
4111 }
4112 else if (!torrentHasOutstandingJob)
4113 {
4114 // Last job is completed for torrent that being removing, so actually remove it
4115 const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
4116 const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
4117 if (removingTorrentData.deleteOption == DeleteTorrent)
4118 m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
4119 }
4120 }
4121
handleTorrentTrackerWarning(TorrentImpl * const torrent,const QString & trackerUrl)4122 void Session::handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl)
4123 {
4124 emit trackerWarning(torrent, trackerUrl);
4125 }
4126
hasPerTorrentRatioLimit() const4127 bool Session::hasPerTorrentRatioLimit() const
4128 {
4129 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
4130 {
4131 return (torrent->ratioLimit() >= 0);
4132 });
4133 }
4134
hasPerTorrentSeedingTimeLimit() const4135 bool Session::hasPerTorrentSeedingTimeLimit() const
4136 {
4137 return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
4138 {
4139 return (torrent->seedingTimeLimit() >= 0);
4140 });
4141 }
4142
initResumeFolder()4143 void Session::initResumeFolder()
4144 {
4145 m_resumeFolderPath = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + RESUME_FOLDER);
4146 const QDir resumeFolderDir(m_resumeFolderPath);
4147 if (resumeFolderDir.exists() || resumeFolderDir.mkpath(resumeFolderDir.absolutePath()))
4148 {
4149 m_resumeFolderLock->setFileName(resumeFolderDir.absoluteFilePath("session.lock"));
4150 if (!m_resumeFolderLock->open(QFile::WriteOnly))
4151 {
4152 throw RuntimeError
4153 {tr("Cannot write to torrent resume folder: \"%1\"")
4154 .arg(Utils::Fs::toNativePath(m_resumeFolderPath))};
4155 }
4156 }
4157 else
4158 {
4159 throw RuntimeError
4160 {tr("Cannot create torrent resume folder: \"%1\"")
4161 .arg(Utils::Fs::toNativePath(m_resumeFolderPath))};
4162 }
4163 }
4164
configureDeferred()4165 void Session::configureDeferred()
4166 {
4167 if (m_deferredConfigureScheduled)
4168 return;
4169
4170 m_deferredConfigureScheduled = true;
4171 QMetaObject::invokeMethod(this, qOverload<>(&Session::configure), Qt::QueuedConnection);
4172 }
4173
4174 // Enable IP Filtering
4175 // this method creates ban list from scratch combining user ban list and 3rd party ban list file
enableIPFilter()4176 void Session::enableIPFilter()
4177 {
4178 qDebug("Enabling IPFilter");
4179 // 1. Parse the IP filter
4180 // 2. In the slot add the manually banned IPs to the provided lt::ip_filter
4181 // 3. Set the ip_filter in one go so there isn't a time window where there isn't an ip_filter
4182 // set between clearing the old one and setting the new one.
4183 if (!m_filterParser)
4184 {
4185 m_filterParser = new FilterParserThread(this);
4186 connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &Session::handleIPFilterParsed);
4187 connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &Session::handleIPFilterError);
4188 }
4189 m_filterParser->processFilterFile(IPFilterFile());
4190 }
4191
4192 // Disable IP Filtering
disableIPFilter()4193 void Session::disableIPFilter()
4194 {
4195 qDebug("Disabling IPFilter");
4196 if (m_filterParser)
4197 {
4198 disconnect(m_filterParser.data(), nullptr, this, nullptr);
4199 delete m_filterParser;
4200 }
4201
4202 // Add the banned IPs after the IPFilter disabling
4203 // which creates an empty filter and overrides all previously
4204 // applied bans.
4205 lt::ip_filter filter;
4206 processBannedIPs(filter);
4207 m_nativeSession->set_ip_filter(filter);
4208 }
4209
recursiveTorrentDownload(const TorrentID & id)4210 void Session::recursiveTorrentDownload(const TorrentID &id)
4211 {
4212 TorrentImpl *const torrent = m_torrents.value(id);
4213 if (!torrent) return;
4214
4215 for (int i = 0; i < torrent->filesCount(); ++i)
4216 {
4217 const QString torrentRelpath = torrent->filePath(i);
4218 if (torrentRelpath.endsWith(".torrent"))
4219 {
4220 LogMsg(tr("Recursive download of file '%1' embedded in torrent '%2'"
4221 , "Recursive download of 'test.torrent' embedded in torrent 'test2'")
4222 .arg(Utils::Fs::toNativePath(torrentRelpath), torrent->name()));
4223 const QString torrentFullpath = torrent->savePath() + '/' + torrentRelpath;
4224
4225 AddTorrentParams params;
4226 // Passing the save path along to the sub torrent file
4227 params.savePath = torrent->savePath();
4228 addTorrent(TorrentInfo::loadFromFile(torrentFullpath), params);
4229 }
4230 }
4231 }
4232
status() const4233 const SessionStatus &Session::status() const
4234 {
4235 return m_status;
4236 }
4237
cacheStatus() const4238 const CacheStatus &Session::cacheStatus() const
4239 {
4240 return m_cacheStatus;
4241 }
4242
loadTorrentResumeData(const QByteArray & data,const TorrentInfo & metadata,LoadTorrentParams & torrentParams)4243 bool Session::loadTorrentResumeData(const QByteArray &data, const TorrentInfo &metadata, LoadTorrentParams &torrentParams)
4244 {
4245 torrentParams = {};
4246
4247 lt::error_code ec;
4248 const lt::bdecode_node root = lt::bdecode(data, ec);
4249 if (ec || (root.type() != lt::bdecode_node::dict_t)) return false;
4250
4251 torrentParams.restored = true;
4252 torrentParams.category = fromLTString(root.dict_find_string_value("qBt-category"));
4253 torrentParams.name = fromLTString(root.dict_find_string_value("qBt-name"));
4254 torrentParams.savePath = Profile::instance()->fromPortablePath(
4255 Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-savePath"))));
4256 torrentParams.hasSeedStatus = root.dict_find_int_value("qBt-seedStatus");
4257 torrentParams.firstLastPiecePriority = root.dict_find_int_value("qBt-firstLastPiecePriority");
4258 torrentParams.seedingTimeLimit = root.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
4259
4260 // TODO: The following code is deprecated. Replace with the commented one after several releases in 4.4.x.
4261 // === BEGIN DEPRECATED CODE === //
4262 const lt::bdecode_node contentLayoutNode = root.dict_find("qBt-contentLayout");
4263 if (contentLayoutNode.type() == lt::bdecode_node::string_t)
4264 {
4265 const QString contentLayoutStr = fromLTString(contentLayoutNode.string_value());
4266 torrentParams.contentLayout = Utils::String::toEnum(contentLayoutStr, TorrentContentLayout::Original);
4267 }
4268 else
4269 {
4270 const bool hasRootFolder = root.dict_find_int_value("qBt-hasRootFolder");
4271 torrentParams.contentLayout = (hasRootFolder ? TorrentContentLayout::Original : TorrentContentLayout::NoSubfolder);
4272 }
4273 // === END DEPRECATED CODE === //
4274 // === BEGIN REPLACEMENT CODE === //
4275 // torrentParams.contentLayout = Utils::String::parse(
4276 // fromLTString(root.dict_find_string_value("qBt-contentLayout")), TorrentContentLayout::Default);
4277 // === END REPLACEMENT CODE === //
4278
4279 const lt::string_view ratioLimitString = root.dict_find_string_value("qBt-ratioLimit");
4280 if (ratioLimitString.empty())
4281 torrentParams.ratioLimit = root.dict_find_int_value("qBt-ratioLimit", Torrent::USE_GLOBAL_RATIO * 1000) / 1000.0;
4282 else
4283 torrentParams.ratioLimit = fromLTString(ratioLimitString).toDouble();
4284
4285 const lt::bdecode_node tagsNode = root.dict_find("qBt-tags");
4286 if (tagsNode.type() == lt::bdecode_node::list_t)
4287 {
4288 for (int i = 0; i < tagsNode.list_size(); ++i)
4289 {
4290 const QString tag = fromLTString(tagsNode.list_string_value_at(i));
4291 if (Session::isValidTag(tag))
4292 torrentParams.tags << tag;
4293 }
4294 }
4295
4296 // NOTE: Do we really need the following block in case of existing (restored) torrent?
4297 torrentParams.savePath = normalizePath(torrentParams.savePath);
4298 if (!torrentParams.category.isEmpty())
4299 {
4300 if (!m_categories.contains(torrentParams.category) && !addCategory(torrentParams.category))
4301 torrentParams.category = "";
4302 }
4303
4304 lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
4305
4306 p = lt::read_resume_data(root, ec);
4307 p.save_path = Profile::instance()->fromPortablePath(fromLTString(p.save_path)).toStdString();
4308 if (metadata.isValid())
4309 p.ti = metadata.nativeInfo();
4310
4311 if (p.flags & lt::torrent_flags::stop_when_ready)
4312 {
4313 // If torrent has "stop_when_ready" flag set then it is actually "stopped"
4314 torrentParams.paused = true;
4315 torrentParams.forced = false;
4316 // ...but temporarily "resumed" to perform some service jobs (e.g. checking)
4317 p.flags &= ~lt::torrent_flags::paused;
4318 p.flags |= lt::torrent_flags::auto_managed;
4319 }
4320 else
4321 {
4322 torrentParams.paused = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed);
4323 torrentParams.forced = !(p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed);
4324 }
4325
4326 const bool hasMetadata = (p.ti && p.ti->is_valid());
4327 if (!hasMetadata && !root.dict_find("info-hash"))
4328 return false;
4329
4330 return true;
4331 }
4332
4333 // Will resume torrents in backup directory
startUpTorrents()4334 void Session::startUpTorrents()
4335 {
4336 const QDir resumeDataDir {m_resumeFolderPath};
4337 QStringList fastresumes = resumeDataDir.entryList(
4338 QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted);
4339
4340 const auto readFile = [](const QString &path, QByteArray &buf) -> bool
4341 {
4342 QFile file(path);
4343 if (!file.open(QIODevice::ReadOnly))
4344 {
4345 LogMsg(tr("Cannot read file %1: %2").arg(path, file.errorString()), Log::WARNING);
4346 return false;
4347 }
4348
4349 buf = file.readAll();
4350 return true;
4351 };
4352
4353 qDebug("Starting up torrents...");
4354 qDebug("Queue size: %d", fastresumes.size());
4355
4356 const QRegularExpression rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
4357
4358 if (isQueueingSystemEnabled())
4359 {
4360 QFile queueFile {resumeDataDir.absoluteFilePath(QLatin1String {"queue"})};
4361 QStringList queue;
4362 if (queueFile.open(QFile::ReadOnly))
4363 {
4364 QByteArray line;
4365 while (!(line = queueFile.readLine()).isEmpty())
4366 queue.append(QString::fromLatin1(line.trimmed()) + QLatin1String {".fastresume"});
4367 }
4368 else
4369 {
4370 LogMsg(tr("Couldn't load torrents queue from '%1'. Error: %2")
4371 .arg(queueFile.fileName(), queueFile.errorString()), Log::WARNING);
4372 }
4373
4374 if (!queue.empty())
4375 fastresumes = queue + List::toSet(fastresumes).subtract(List::toSet(queue)).values();
4376 }
4377
4378 int resumedTorrentsCount = 0;
4379 for (const QString &fastresumeName : asConst(fastresumes))
4380 {
4381 const QRegularExpressionMatch rxMatch = rx.match(fastresumeName);
4382 if (!rxMatch.hasMatch()) continue;
4383
4384 const QString hash = rxMatch.captured(1);
4385 const QString fastresumePath = resumeDataDir.absoluteFilePath(fastresumeName);
4386 QByteArray data;
4387 LoadTorrentParams torrentParams;
4388 const QString torrentFilePath = resumeDataDir.filePath(QString::fromLatin1("%1.torrent").arg(hash));
4389 TorrentInfo metadata = TorrentInfo::loadFromFile(torrentFilePath);
4390 if (readFile(fastresumePath, data) && loadTorrentResumeData(data, metadata, torrentParams))
4391 {
4392 qDebug() << "Starting up torrent" << hash << "...";
4393 if (!loadTorrent(torrentParams))
4394 LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
4395 .arg(hash), Log::CRITICAL);
4396
4397 // process add torrent messages before message queue overflow
4398 if ((resumedTorrentsCount % 100) == 0) readAlerts();
4399
4400 ++resumedTorrentsCount;
4401 }
4402 else
4403 {
4404 LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
4405 .arg(hash), Log::CRITICAL);
4406 }
4407 }
4408 }
4409
getAlltimeDL() const4410 quint64 Session::getAlltimeDL() const
4411 {
4412 return m_statistics->getAlltimeDL();
4413 }
4414
getAlltimeUL() const4415 quint64 Session::getAlltimeUL() const
4416 {
4417 return m_statistics->getAlltimeUL();
4418 }
4419
enqueueRefresh()4420 void Session::enqueueRefresh()
4421 {
4422 Q_ASSERT(!m_refreshEnqueued);
4423
4424 QTimer::singleShot(refreshInterval(), this, [this] ()
4425 {
4426 m_nativeSession->post_torrent_updates();
4427 m_nativeSession->post_session_stats();
4428 });
4429
4430 m_refreshEnqueued = true;
4431 }
4432
handleIPFilterParsed(const int ruleCount)4433 void Session::handleIPFilterParsed(const int ruleCount)
4434 {
4435 if (m_filterParser)
4436 {
4437 lt::ip_filter filter = m_filterParser->IPfilter();
4438 processBannedIPs(filter);
4439 m_nativeSession->set_ip_filter(filter);
4440 }
4441 LogMsg(tr("Successfully parsed the provided IP filter: %1 rules were applied.", "%1 is a number").arg(ruleCount));
4442 emit IPFilterParsed(false, ruleCount);
4443 }
4444
handleIPFilterError()4445 void Session::handleIPFilterError()
4446 {
4447 lt::ip_filter filter;
4448 processBannedIPs(filter);
4449 m_nativeSession->set_ip_filter(filter);
4450
4451 LogMsg(tr("Error: Failed to parse the provided IP filter."), Log::CRITICAL);
4452 emit IPFilterParsed(true, 0);
4453 }
4454
getPendingAlerts(const lt::time_duration time) const4455 std::vector<lt::alert *> Session::getPendingAlerts(const lt::time_duration time) const
4456 {
4457 if (time > lt::time_duration::zero())
4458 m_nativeSession->wait_for_alert(time);
4459
4460 std::vector<lt::alert *> alerts;
4461 m_nativeSession->pop_alerts(&alerts);
4462 return alerts;
4463 }
4464
torrentContentLayout() const4465 TorrentContentLayout Session::torrentContentLayout() const
4466 {
4467 return m_torrentContentLayout;
4468 }
4469
setTorrentContentLayout(const TorrentContentLayout value)4470 void Session::setTorrentContentLayout(const TorrentContentLayout value)
4471 {
4472 m_torrentContentLayout = value;
4473 }
4474
4475 // Read alerts sent by the BitTorrent session
readAlerts()4476 void Session::readAlerts()
4477 {
4478 const std::vector<lt::alert *> alerts = getPendingAlerts();
4479 for (const lt::alert *a : alerts)
4480 handleAlert(a);
4481 }
4482
handleAlert(const lt::alert * a)4483 void Session::handleAlert(const lt::alert *a)
4484 {
4485 try
4486 {
4487 switch (a->type())
4488 {
4489 case lt::file_renamed_alert::alert_type:
4490 case lt::file_completed_alert::alert_type:
4491 case lt::torrent_finished_alert::alert_type:
4492 case lt::save_resume_data_alert::alert_type:
4493 case lt::save_resume_data_failed_alert::alert_type:
4494 case lt::torrent_paused_alert::alert_type:
4495 case lt::torrent_resumed_alert::alert_type:
4496 case lt::tracker_error_alert::alert_type:
4497 case lt::tracker_reply_alert::alert_type:
4498 case lt::tracker_warning_alert::alert_type:
4499 case lt::fastresume_rejected_alert::alert_type:
4500 case lt::torrent_checked_alert::alert_type:
4501 case lt::metadata_received_alert::alert_type:
4502 dispatchTorrentAlert(a);
4503 break;
4504 case lt::state_update_alert::alert_type:
4505 handleStateUpdateAlert(static_cast<const lt::state_update_alert*>(a));
4506 break;
4507 case lt::session_stats_alert::alert_type:
4508 handleSessionStatsAlert(static_cast<const lt::session_stats_alert*>(a));
4509 break;
4510 case lt::file_error_alert::alert_type:
4511 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
4512 break;
4513 case lt::add_torrent_alert::alert_type:
4514 handleAddTorrentAlert(static_cast<const lt::add_torrent_alert*>(a));
4515 break;
4516 case lt::torrent_removed_alert::alert_type:
4517 handleTorrentRemovedAlert(static_cast<const lt::torrent_removed_alert*>(a));
4518 break;
4519 case lt::torrent_deleted_alert::alert_type:
4520 handleTorrentDeletedAlert(static_cast<const lt::torrent_deleted_alert*>(a));
4521 break;
4522 case lt::torrent_delete_failed_alert::alert_type:
4523 handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert*>(a));
4524 break;
4525 case lt::portmap_error_alert::alert_type:
4526 handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert*>(a));
4527 break;
4528 case lt::portmap_alert::alert_type:
4529 handlePortmapAlert(static_cast<const lt::portmap_alert*>(a));
4530 break;
4531 case lt::peer_blocked_alert::alert_type:
4532 handlePeerBlockedAlert(static_cast<const lt::peer_blocked_alert*>(a));
4533 break;
4534 case lt::peer_ban_alert::alert_type:
4535 handlePeerBanAlert(static_cast<const lt::peer_ban_alert*>(a));
4536 break;
4537 case lt::url_seed_alert::alert_type:
4538 handleUrlSeedAlert(static_cast<const lt::url_seed_alert*>(a));
4539 break;
4540 case lt::listen_succeeded_alert::alert_type:
4541 handleListenSucceededAlert(static_cast<const lt::listen_succeeded_alert*>(a));
4542 break;
4543 case lt::listen_failed_alert::alert_type:
4544 handleListenFailedAlert(static_cast<const lt::listen_failed_alert*>(a));
4545 break;
4546 case lt::external_ip_alert::alert_type:
4547 handleExternalIPAlert(static_cast<const lt::external_ip_alert*>(a));
4548 break;
4549 case lt::alerts_dropped_alert::alert_type:
4550 handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(a));
4551 break;
4552 case lt::storage_moved_alert::alert_type:
4553 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert*>(a));
4554 break;
4555 case lt::storage_moved_failed_alert::alert_type:
4556 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert*>(a));
4557 break;
4558 case lt::socks5_alert::alert_type:
4559 handleSocks5Alert(static_cast<const lt::socks5_alert *>(a));
4560 break;
4561 }
4562 }
4563 catch (const std::exception &exc)
4564 {
4565 qWarning() << "Caught exception in " << Q_FUNC_INFO << ": " << QString::fromStdString(exc.what());
4566 }
4567 }
4568
dispatchTorrentAlert(const lt::alert * a)4569 void Session::dispatchTorrentAlert(const lt::alert *a)
4570 {
4571 TorrentImpl *const torrent = m_torrents.value(static_cast<const lt::torrent_alert*>(a)->handle.info_hash());
4572 if (torrent)
4573 {
4574 torrent->handleAlert(a);
4575 return;
4576 }
4577
4578 switch (a->type())
4579 {
4580 case lt::metadata_received_alert::alert_type:
4581 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
4582 break;
4583 }
4584 }
4585
createTorrent(const lt::torrent_handle & nativeHandle)4586 void Session::createTorrent(const lt::torrent_handle &nativeHandle)
4587 {
4588 Q_ASSERT(m_loadingTorrents.contains(nativeHandle.info_hash()));
4589
4590 const LoadTorrentParams params = m_loadingTorrents.take(nativeHandle.info_hash());
4591
4592 auto *const torrent = new TorrentImpl {this, m_nativeSession, nativeHandle, params};
4593 m_torrents.insert(torrent->id(), torrent);
4594
4595 const bool hasMetadata = torrent->hasMetadata();
4596
4597 if (params.restored)
4598 {
4599 LogMsg(tr("'%1' restored.", "'torrent name' restored.").arg(torrent->name()));
4600 }
4601 else
4602 {
4603 // The following is useless for newly added magnet
4604 if (hasMetadata)
4605 {
4606 // Backup torrent file
4607 const QDir resumeDataDir {m_resumeFolderPath};
4608 const QString torrentFileName {QString::fromLatin1("%1.torrent").arg(torrent->id().toString())};
4609 try
4610 {
4611 torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
4612 // Copy the torrent file to the export folder
4613 if (!torrentExportDirectory().isEmpty())
4614 exportTorrentFile(torrent);
4615 }
4616 catch (const RuntimeError &err)
4617 {
4618 LogMsg(tr("Couldn't save torrent metadata file '%1'. Reason: %2")
4619 .arg(torrentFileName, err.message()), Log::CRITICAL);
4620 }
4621 }
4622
4623 if (isAddTrackersEnabled() && !torrent->isPrivate())
4624 torrent->addTrackers(m_additionalTrackerList);
4625
4626 LogMsg(tr("'%1' added to download list.", "'torrent name' was added to download list.")
4627 .arg(torrent->name()));
4628
4629 // In case of crash before the scheduled generation
4630 // of the fastresumes.
4631 torrent->saveResumeData();
4632 }
4633
4634 if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
4635 && !m_seedingLimitTimer->isActive())
4636 m_seedingLimitTimer->start();
4637
4638 // Send torrent addition signal
4639 emit torrentLoaded(torrent);
4640 // Send new torrent signal
4641 if (!params.restored)
4642 emit torrentAdded(torrent);
4643
4644 // Torrent could have error just after adding to libtorrent
4645 if (torrent->hasError())
4646 LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: %2.").arg(torrent->name(), torrent->error()), Log::WARNING);
4647 }
4648
handleAddTorrentAlert(const lt::add_torrent_alert * p)4649 void Session::handleAddTorrentAlert(const lt::add_torrent_alert *p)
4650 {
4651 if (p->error)
4652 {
4653 qDebug("/!\\ Error: Failed to add torrent!");
4654 QString msg = QString::fromStdString(p->message());
4655 LogMsg(tr("Couldn't load torrent. Reason: %1").arg(msg), Log::WARNING);
4656 emit loadTorrentFailed(msg);
4657 }
4658 else if (m_loadingTorrents.contains(p->handle.info_hash()))
4659 {
4660 createTorrent(p->handle);
4661 }
4662 }
4663
handleTorrentRemovedAlert(const lt::torrent_removed_alert * p)4664 void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
4665 {
4666 #if (LIBTORRENT_VERSION_NUM >= 20000)
4667 const auto id = TorrentID::fromInfoHash(p->info_hashes);
4668 #else
4669 const auto id = TorrentID::fromInfoHash(p->info_hash);
4670 #endif
4671
4672 const auto removingTorrentDataIter = m_removingTorrents.find(id);
4673 if (removingTorrentDataIter != m_removingTorrents.end())
4674 {
4675 if (removingTorrentDataIter->deleteOption == DeleteTorrent)
4676 {
4677 LogMsg(tr("'%1' was removed from the transfer list.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4678 m_removingTorrents.erase(removingTorrentDataIter);
4679 }
4680 }
4681 }
4682
handleTorrentDeletedAlert(const lt::torrent_deleted_alert * p)4683 void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
4684 {
4685 #if (LIBTORRENT_VERSION_NUM >= 20000)
4686 const auto id = TorrentID::fromInfoHash(p->info_hashes);
4687 #else
4688 const auto id = TorrentID::fromInfoHash(p->info_hash);
4689 #endif
4690
4691 const auto removingTorrentDataIter = m_removingTorrents.find(id);
4692
4693 if (removingTorrentDataIter == m_removingTorrents.end())
4694 return;
4695
4696 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
4697 LogMsg(tr("'%1' was removed from the transfer list and hard disk.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4698 m_removingTorrents.erase(removingTorrentDataIter);
4699 }
4700
handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert * p)4701 void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
4702 {
4703 #if (LIBTORRENT_VERSION_NUM >= 20000)
4704 const auto id = TorrentID::fromInfoHash(p->info_hashes);
4705 #else
4706 const auto id = TorrentID::fromInfoHash(p->info_hash);
4707 #endif
4708
4709 const auto removingTorrentDataIter = m_removingTorrents.find(id);
4710
4711 if (removingTorrentDataIter == m_removingTorrents.end())
4712 return;
4713
4714 if (p->error)
4715 {
4716 // libtorrent won't delete the directory if it contains files not listed in the torrent,
4717 // so we remove the directory ourselves
4718 Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
4719
4720 LogMsg(tr("'%1' was removed from the transfer list but the files couldn't be deleted. Error: %2", "'xxx.avi' was removed...")
4721 .arg(removingTorrentDataIter->name, QString::fromLocal8Bit(p->error.message().c_str()))
4722 , Log::WARNING);
4723 }
4724 else // torrent without metadata, hence no files on disk
4725 {
4726 LogMsg(tr("'%1' was removed from the transfer list.", "'xxx.avi' was removed...").arg(removingTorrentDataIter->name));
4727 }
4728 m_removingTorrents.erase(removingTorrentDataIter);
4729 }
4730
handleMetadataReceivedAlert(const lt::metadata_received_alert * p)4731 void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
4732 {
4733 #if (LIBTORRENT_VERSION_NUM >= 20000)
4734 const auto id = TorrentID::fromInfoHash(p->handle.info_hashes());
4735 #else
4736 const auto id = TorrentID::fromInfoHash(p->handle.info_hash());
4737 #endif
4738
4739 const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
4740
4741 if (downloadedMetadataIter != m_downloadedMetadata.end())
4742 {
4743 TorrentInfo metadata {p->handle.torrent_file()};
4744
4745 m_downloadedMetadata.erase(downloadedMetadataIter);
4746 --m_extraLimit;
4747 adjustLimits();
4748 m_nativeSession->remove_torrent(p->handle, lt::session::delete_files);
4749
4750 emit metadataDownloaded(metadata);
4751 }
4752 }
4753
handleFileErrorAlert(const lt::file_error_alert * p)4754 void Session::handleFileErrorAlert(const lt::file_error_alert *p)
4755 {
4756 TorrentImpl *const torrent = m_torrents.value(p->handle.info_hash());
4757 if (!torrent)
4758 return;
4759
4760 torrent->handleAlert(p);
4761
4762 const TorrentID id = torrent->id();
4763 if (!m_recentErroredTorrents.contains(id))
4764 {
4765 m_recentErroredTorrents.insert(id);
4766
4767 const QString msg = QString::fromStdString(p->message());
4768 LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: %3")
4769 .arg(torrent->name(), p->filename(), msg)
4770 , Log::WARNING);
4771 emit fullDiskError(torrent, msg);
4772 }
4773
4774 m_recentErroredTorrentsTimer->start();
4775 }
4776
handlePortmapWarningAlert(const lt::portmap_error_alert * p)4777 void Session::handlePortmapWarningAlert(const lt::portmap_error_alert *p)
4778 {
4779 LogMsg(tr("UPnP/NAT-PMP: Port mapping failure, message: %1").arg(QString::fromStdString(p->message())), Log::CRITICAL);
4780 }
4781
handlePortmapAlert(const lt::portmap_alert * p)4782 void Session::handlePortmapAlert(const lt::portmap_alert *p)
4783 {
4784 qDebug("UPnP Success, msg: %s", p->message().c_str());
4785 LogMsg(tr("UPnP/NAT-PMP: Port mapping successful, message: %1").arg(QString::fromStdString(p->message())), Log::INFO);
4786 }
4787
handlePeerBlockedAlert(const lt::peer_blocked_alert * p)4788 void Session::handlePeerBlockedAlert(const lt::peer_blocked_alert *p)
4789 {
4790 QString reason;
4791 switch (p->reason)
4792 {
4793 case lt::peer_blocked_alert::ip_filter:
4794 reason = tr("IP filter", "this peer was blocked. Reason: IP filter.");
4795 break;
4796 case lt::peer_blocked_alert::port_filter:
4797 reason = tr("port filter", "this peer was blocked. Reason: port filter.");
4798 break;
4799 case lt::peer_blocked_alert::i2p_mixed:
4800 reason = tr("%1 mixed mode restrictions", "this peer was blocked. Reason: I2P mixed mode restrictions.").arg("I2P"); // don't translate I2P
4801 break;
4802 case lt::peer_blocked_alert::privileged_ports:
4803 reason = tr("use of privileged port", "this peer was blocked. Reason: use of privileged port.");
4804 break;
4805 case lt::peer_blocked_alert::utp_disabled:
4806 reason = tr("%1 is disabled", "this peer was blocked. Reason: uTP is disabled.").arg(QString::fromUtf8(C_UTP)); // don't translate μTP
4807 break;
4808 case lt::peer_blocked_alert::tcp_disabled:
4809 reason = tr("%1 is disabled", "this peer was blocked. Reason: TCP is disabled.").arg("TCP"); // don't translate TCP
4810 break;
4811 }
4812
4813 const QString ip {toString(p->endpoint.address())};
4814 if (!ip.isEmpty())
4815 Logger::instance()->addPeer(ip, true, reason);
4816 }
4817
handlePeerBanAlert(const lt::peer_ban_alert * p)4818 void Session::handlePeerBanAlert(const lt::peer_ban_alert *p)
4819 {
4820 const QString ip {toString(p->endpoint.address())};
4821 if (!ip.isEmpty())
4822 Logger::instance()->addPeer(ip, false);
4823 }
4824
handleUrlSeedAlert(const lt::url_seed_alert * p)4825 void Session::handleUrlSeedAlert(const lt::url_seed_alert *p)
4826 {
4827 const TorrentImpl *torrent = m_torrents.value(p->handle.info_hash());
4828 if (!torrent)
4829 return;
4830
4831 if (p->error)
4832 {
4833 LogMsg(tr("URL seed name lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
4834 .arg(torrent->name(), p->server_url(), QString::fromStdString(p->message()))
4835 , Log::WARNING);
4836 }
4837 else
4838 {
4839 LogMsg(tr("Received error message from a URL seed. Torrent: \"%1\". URL: \"%2\". Message: \"%3\"")
4840 .arg(torrent->name(), p->server_url(), p->error_message())
4841 , Log::WARNING);
4842 }
4843 }
4844
handleListenSucceededAlert(const lt::listen_succeeded_alert * p)4845 void Session::handleListenSucceededAlert(const lt::listen_succeeded_alert *p)
4846 {
4847 const QString proto {toString(p->socket_type)};
4848 LogMsg(tr("Successfully listening on IP: %1, port: %2/%3"
4849 , "e.g: Successfully listening on IP: 192.168.0.1, port: TCP/6881")
4850 .arg(toString(p->address), proto, QString::number(p->port)), Log::INFO);
4851
4852 // Force reannounce on all torrents because some trackers blacklist some ports
4853 for (const lt::torrent_handle &torrent : m_nativeSession->get_torrents())
4854 torrent.force_reannounce();
4855 }
4856
handleListenFailedAlert(const lt::listen_failed_alert * p)4857 void Session::handleListenFailedAlert(const lt::listen_failed_alert *p)
4858 {
4859 const QString proto {toString(p->socket_type)};
4860 LogMsg(tr("Failed to listen on IP: %1, port: %2/%3. Reason: %4"
4861 , "e.g: Failed to listen on IP: 192.168.0.1, port: TCP/6881. Reason: already in use")
4862 .arg(toString(p->address), proto, QString::number(p->port)
4863 , QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
4864 }
4865
handleExternalIPAlert(const lt::external_ip_alert * p)4866 void Session::handleExternalIPAlert(const lt::external_ip_alert *p)
4867 {
4868 LogMsg(tr("Detected external IP: %1", "e.g. Detected external IP: 1.1.1.1")
4869 .arg(toString(p->external_address)), Log::INFO);
4870 }
4871
handleSessionStatsAlert(const lt::session_stats_alert * p)4872 void Session::handleSessionStatsAlert(const lt::session_stats_alert *p)
4873 {
4874 const qreal interval = lt::total_milliseconds(p->timestamp() - m_statsLastTimestamp) / 1000.;
4875 m_statsLastTimestamp = p->timestamp();
4876
4877 const auto stats = p->counters();
4878
4879 m_status.hasIncomingConnections = static_cast<bool>(stats[m_metricIndices.net.hasIncomingConnections]);
4880
4881 const int64_t ipOverheadDownload = stats[m_metricIndices.net.recvIPOverheadBytes];
4882 const int64_t ipOverheadUpload = stats[m_metricIndices.net.sentIPOverheadBytes];
4883 const int64_t totalDownload = stats[m_metricIndices.net.recvBytes] + ipOverheadDownload;
4884 const int64_t totalUpload = stats[m_metricIndices.net.sentBytes] + ipOverheadUpload;
4885 const int64_t totalPayloadDownload = stats[m_metricIndices.net.recvPayloadBytes];
4886 const int64_t totalPayloadUpload = stats[m_metricIndices.net.sentPayloadBytes];
4887 const int64_t trackerDownload = stats[m_metricIndices.net.recvTrackerBytes];
4888 const int64_t trackerUpload = stats[m_metricIndices.net.sentTrackerBytes];
4889 const int64_t dhtDownload = stats[m_metricIndices.dht.dhtBytesIn];
4890 const int64_t dhtUpload = stats[m_metricIndices.dht.dhtBytesOut];
4891
4892 auto calcRate = [interval](const quint64 previous, const quint64 current)
4893 {
4894 Q_ASSERT(current >= previous);
4895 return static_cast<quint64>((current - previous) / interval);
4896 };
4897
4898 m_status.payloadDownloadRate = calcRate(m_status.totalPayloadDownload, totalPayloadDownload);
4899 m_status.payloadUploadRate = calcRate(m_status.totalPayloadUpload, totalPayloadUpload);
4900 m_status.downloadRate = calcRate(m_status.totalDownload, totalDownload);
4901 m_status.uploadRate = calcRate(m_status.totalUpload, totalUpload);
4902 m_status.ipOverheadDownloadRate = calcRate(m_status.ipOverheadDownload, ipOverheadDownload);
4903 m_status.ipOverheadUploadRate = calcRate(m_status.ipOverheadUpload, ipOverheadUpload);
4904 m_status.dhtDownloadRate = calcRate(m_status.dhtDownload, dhtDownload);
4905 m_status.dhtUploadRate = calcRate(m_status.dhtUpload, dhtUpload);
4906 m_status.trackerDownloadRate = calcRate(m_status.trackerDownload, trackerDownload);
4907 m_status.trackerUploadRate = calcRate(m_status.trackerUpload, trackerUpload);
4908
4909 m_status.totalDownload = totalDownload;
4910 m_status.totalUpload = totalUpload;
4911 m_status.totalPayloadDownload = totalPayloadDownload;
4912 m_status.totalPayloadUpload = totalPayloadUpload;
4913 m_status.ipOverheadDownload = ipOverheadDownload;
4914 m_status.ipOverheadUpload = ipOverheadUpload;
4915 m_status.trackerDownload = trackerDownload;
4916 m_status.trackerUpload = trackerUpload;
4917 m_status.dhtDownload = dhtDownload;
4918 m_status.dhtUpload = dhtUpload;
4919 m_status.totalWasted = stats[m_metricIndices.net.recvRedundantBytes]
4920 + stats[m_metricIndices.net.recvFailedBytes];
4921 m_status.dhtNodes = stats[m_metricIndices.dht.dhtNodes];
4922 m_status.diskReadQueue = stats[m_metricIndices.peer.numPeersUpDisk];
4923 m_status.diskWriteQueue = stats[m_metricIndices.peer.numPeersDownDisk];
4924 m_status.peersCount = stats[m_metricIndices.peer.numPeersConnected];
4925
4926 const int64_t numBlocksRead = stats[m_metricIndices.disk.numBlocksRead];
4927 m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
4928 m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
4929
4930 #if (LIBTORRENT_VERSION_NUM < 20000)
4931 const int64_t numBlocksCacheHits = stats[m_metricIndices.disk.numBlocksCacheHits];
4932 m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max<int64_t>((numBlocksCacheHits + numBlocksRead), 1);
4933 #endif
4934
4935 const int64_t totalJobs = stats[m_metricIndices.disk.writeJobs] + stats[m_metricIndices.disk.readJobs]
4936 + stats[m_metricIndices.disk.hashJobs];
4937 m_cacheStatus.averageJobTime = (totalJobs > 0)
4938 ? (stats[m_metricIndices.disk.diskJobTime] / totalJobs) : 0;
4939
4940 emit statsUpdated();
4941
4942 if (m_refreshEnqueued)
4943 m_refreshEnqueued = false;
4944 else
4945 enqueueRefresh();
4946 }
4947
handleAlertsDroppedAlert(const lt::alerts_dropped_alert * p) const4948 void Session::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
4949 {
4950 LogMsg(tr("Error: Internal alert queue full and alerts were dropped, you might see degraded performance. Dropped alert types: %1. Message: %2")
4951 .arg(QString::fromStdString(p->dropped_alerts.to_string()), QString::fromStdString(p->message())), Log::CRITICAL);
4952 }
4953
handleStorageMovedAlert(const lt::storage_moved_alert * p)4954 void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
4955 {
4956 Q_ASSERT(!m_moveStorageQueue.isEmpty());
4957
4958 const MoveStorageJob ¤tJob = m_moveStorageQueue.first();
4959 Q_ASSERT(currentJob.torrentHandle == p->handle);
4960
4961 const QString newPath {p->storage_path()};
4962 Q_ASSERT(newPath == currentJob.path);
4963
4964 #if (LIBTORRENT_VERSION_NUM >= 20000)
4965 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
4966 #else
4967 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
4968 #endif
4969
4970 TorrentImpl *torrent = m_torrents.value(id);
4971 const QString torrentName = (torrent ? torrent->name() : id.toString());
4972 LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath));
4973
4974 handleMoveTorrentStorageJobFinished();
4975 }
4976
handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert * p)4977 void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
4978 {
4979 Q_ASSERT(!m_moveStorageQueue.isEmpty());
4980
4981 const MoveStorageJob ¤tJob = m_moveStorageQueue.first();
4982 Q_ASSERT(currentJob.torrentHandle == p->handle);
4983
4984 #if (LIBTORRENT_VERSION_NUM >= 20000)
4985 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
4986 #else
4987 const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
4988 #endif
4989
4990 TorrentImpl *torrent = m_torrents.value(id);
4991 const QString torrentName = (torrent ? torrent->name() : id.toString());
4992 const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path);
4993 const QString errorMessage = QString::fromStdString(p->message());
4994 LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.")
4995 .arg(torrentName, currentLocation, currentJob.path, errorMessage), Log::CRITICAL);
4996
4997 handleMoveTorrentStorageJobFinished();
4998 }
4999
handleStateUpdateAlert(const lt::state_update_alert * p)5000 void Session::handleStateUpdateAlert(const lt::state_update_alert *p)
5001 {
5002 QVector<Torrent *> updatedTorrents;
5003 updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(p->status.size()));
5004
5005 for (const lt::torrent_status &status : p->status)
5006 {
5007 #if (LIBTORRENT_VERSION_NUM >= 20000)
5008 const auto id = TorrentID::fromInfoHash(status.info_hashes);
5009 #else
5010 const auto id = TorrentID::fromInfoHash(status.info_hash);
5011 #endif
5012 TorrentImpl *const torrent = m_torrents.value(id);
5013 if (!torrent)
5014 continue;
5015
5016 torrent->handleStateUpdate(status);
5017 updatedTorrents.push_back(torrent);
5018 }
5019
5020 if (!updatedTorrents.isEmpty())
5021 emit torrentsUpdated(updatedTorrents);
5022
5023 if (m_refreshEnqueued)
5024 m_refreshEnqueued = false;
5025 else
5026 enqueueRefresh();
5027 }
5028
handleSocks5Alert(const lt::socks5_alert * p) const5029 void Session::handleSocks5Alert(const lt::socks5_alert *p) const
5030 {
5031 if (p->error)
5032 {
5033 LogMsg(tr("SOCKS5 proxy error. Message: %1").arg(QString::fromStdString(p->message()))
5034 , Log::WARNING);
5035 }
5036 }
5037