WebUI: use Fetch API to login
[qBittorrent.git] / src / base / bittorrent / torrentimpl.cpp
blob7458814c46950c3f0eb4322c72d5c85a71b73dd2
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "torrentimpl.h"
32 #include <algorithm>
33 #include <memory>
35 #ifdef Q_OS_WIN
36 #include <windows.h>
37 #endif
39 #include <libtorrent/address.hpp>
40 #include <libtorrent/alert_types.hpp>
41 #include <libtorrent/create_torrent.hpp>
42 #include <libtorrent/session.hpp>
43 #include <libtorrent/storage_defs.hpp>
44 #include <libtorrent/time.hpp>
46 #ifdef QBT_USES_LIBTORRENT2
47 #include <libtorrent/info_hash.hpp>
48 #endif
50 #include <QtSystemDetection>
51 #include <QByteArray>
52 #include <QCache>
53 #include <QDebug>
54 #include <QPointer>
55 #include <QSet>
56 #include <QStringList>
57 #include <QUrl>
59 #include "base/exceptions.h"
60 #include "base/global.h"
61 #include "base/logger.h"
62 #include "base/preferences.h"
63 #include "base/types.h"
64 #include "base/utils/fs.h"
65 #include "base/utils/io.h"
66 #include "common.h"
67 #include "downloadpriority.h"
68 #include "extensiondata.h"
69 #include "loadtorrentparams.h"
70 #include "ltqbitarray.h"
71 #include "lttypecast.h"
72 #include "peeraddress.h"
73 #include "peerinfo.h"
74 #include "sessionimpl.h"
75 #include "trackerentry.h"
77 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
78 #include "base/utils/os.h"
79 #endif // Q_OS_MACOS || Q_OS_WIN
81 #ifndef QBT_USES_LIBTORRENT2
82 #include "customstorage.h"
83 #endif
85 using namespace BitTorrent;
87 namespace
89 lt::announce_entry makeNativeAnnounceEntry(const QString &url, const int tier)
91 lt::announce_entry entry {url.toStdString()};
92 entry.tier = tier;
93 return entry;
96 QString toString(const lt::tcp::endpoint &ltTCPEndpoint)
98 static QCache<lt::tcp::endpoint, QString> cache;
100 if (const QString *endpointName = cache.object(ltTCPEndpoint))
101 return *endpointName;
103 const std::string tmp = (std::ostringstream() << ltTCPEndpoint).str();
104 const auto endpointName = QString::fromLatin1(tmp.c_str(), tmp.size());
105 cache.insert(ltTCPEndpoint, new QString(endpointName));
106 return endpointName;
109 template <typename FromLTTimePoint32Func>
110 void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry
111 , const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo
112 , const FromLTTimePoint32Func &fromLTTimePoint32)
114 Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url));
116 trackerEntryStatus.tier = nativeEntry.tier;
118 const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * btProtocols.size();
120 int numUpdating = 0;
121 int numWorking = 0;
122 int numNotWorking = 0;
123 int numTrackerError = 0;
124 int numUnreachable = 0;
126 for (const lt::announce_endpoint &ltAnnounceEndpoint : nativeEntry.endpoints)
128 const auto endpointName = toString(ltAnnounceEndpoint.local_endpoint);
130 for (const auto protocolVersion : btProtocols)
132 #ifdef QBT_USES_LIBTORRENT2
133 Q_ASSERT((protocolVersion == 1) || (protocolVersion == 2));
134 const auto ltProtocolVersion = (protocolVersion == 1) ? lt::protocol_version::V1 : lt::protocol_version::V2;
135 const lt::announce_infohash &ltAnnounceInfo = ltAnnounceEndpoint.info_hashes[ltProtocolVersion];
136 #else
137 Q_ASSERT(protocolVersion == 1);
138 const lt::announce_endpoint &ltAnnounceInfo = ltAnnounceEndpoint;
139 #endif
140 const QMap<int, int> &endpointUpdateInfo = updateInfo[ltAnnounceEndpoint.local_endpoint];
141 TrackerEndpointStatus &trackerEndpointStatus = trackerEntryStatus.endpoints[std::make_pair(endpointName, protocolVersion)];
143 trackerEndpointStatus.name = endpointName;
144 trackerEndpointStatus.btVersion = protocolVersion;
145 trackerEndpointStatus.numPeers = endpointUpdateInfo.value(protocolVersion, trackerEndpointStatus.numPeers);
146 trackerEndpointStatus.numSeeds = ltAnnounceInfo.scrape_complete;
147 trackerEndpointStatus.numLeeches = ltAnnounceInfo.scrape_incomplete;
148 trackerEndpointStatus.numDownloaded = ltAnnounceInfo.scrape_downloaded;
149 trackerEndpointStatus.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
150 trackerEndpointStatus.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
152 if (ltAnnounceInfo.updating)
154 trackerEndpointStatus.state = TrackerEndpointState::Updating;
155 ++numUpdating;
157 else if (ltAnnounceInfo.fails > 0)
159 if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
161 trackerEndpointStatus.state = TrackerEndpointState::TrackerError;
162 ++numTrackerError;
164 else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
166 trackerEndpointStatus.state = TrackerEndpointState::Unreachable;
167 ++numUnreachable;
169 else
171 trackerEndpointStatus.state = TrackerEndpointState::NotWorking;
172 ++numNotWorking;
175 else if (nativeEntry.verified)
177 trackerEndpointStatus.state = TrackerEndpointState::Working;
178 ++numWorking;
180 else
182 trackerEndpointStatus.state = TrackerEndpointState::NotContacted;
185 if (!ltAnnounceInfo.message.empty())
187 trackerEndpointStatus.message = QString::fromStdString(ltAnnounceInfo.message);
189 else if (ltAnnounceInfo.last_error)
191 trackerEndpointStatus.message = QString::fromLocal8Bit(ltAnnounceInfo.last_error.message());
193 else
195 trackerEndpointStatus.message.clear();
200 if (trackerEntryStatus.endpoints.size() > numEndpoints)
202 // remove outdated endpoints
203 trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointStatus>::iterator &iter)
205 return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
206 , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
208 return (endpointName == toString(existingEndpoint.local_endpoint));
213 if (numEndpoints > 0)
215 if (numUpdating > 0)
217 trackerEntryStatus.state = TrackerEndpointState::Updating;
219 else if (numWorking > 0)
221 trackerEntryStatus.state = TrackerEndpointState::Working;
223 else if (numTrackerError > 0)
225 trackerEntryStatus.state = TrackerEndpointState::TrackerError;
227 else if (numUnreachable == numEndpoints)
229 trackerEntryStatus.state = TrackerEndpointState::Unreachable;
231 else if ((numUnreachable + numNotWorking) == numEndpoints)
233 trackerEntryStatus.state = TrackerEndpointState::NotWorking;
237 trackerEntryStatus.numPeers = -1;
238 trackerEntryStatus.numSeeds = -1;
239 trackerEntryStatus.numLeeches = -1;
240 trackerEntryStatus.numDownloaded = -1;
241 trackerEntryStatus.nextAnnounceTime = QDateTime();
242 trackerEntryStatus.minAnnounceTime = QDateTime();
243 trackerEntryStatus.message.clear();
245 for (const TrackerEndpointStatus &endpointStatus : asConst(trackerEntryStatus.endpoints))
247 trackerEntryStatus.numPeers = std::max(trackerEntryStatus.numPeers, endpointStatus.numPeers);
248 trackerEntryStatus.numSeeds = std::max(trackerEntryStatus.numSeeds, endpointStatus.numSeeds);
249 trackerEntryStatus.numLeeches = std::max(trackerEntryStatus.numLeeches, endpointStatus.numLeeches);
250 trackerEntryStatus.numDownloaded = std::max(trackerEntryStatus.numDownloaded, endpointStatus.numDownloaded);
252 if (endpointStatus.state == trackerEntryStatus.state)
254 if (!trackerEntryStatus.nextAnnounceTime.isValid() || (trackerEntryStatus.nextAnnounceTime > endpointStatus.nextAnnounceTime))
256 trackerEntryStatus.nextAnnounceTime = endpointStatus.nextAnnounceTime;
257 trackerEntryStatus.minAnnounceTime = endpointStatus.minAnnounceTime;
258 if ((endpointStatus.state != TrackerEndpointState::Working)
259 || !endpointStatus.message.isEmpty())
261 trackerEntryStatus.message = endpointStatus.message;
265 if (endpointStatus.state == TrackerEndpointState::Working)
267 if (trackerEntryStatus.message.isEmpty())
268 trackerEntryStatus.message = endpointStatus.message;
274 template <typename Vector>
275 Vector resized(const Vector &inVector, const typename Vector::size_type size, const typename Vector::value_type &defaultValue)
277 Vector outVector = inVector;
278 outVector.resize(size, defaultValue);
279 return outVector;
282 // This is an imitation of limit normalization performed by libtorrent itself.
283 // We need perform it to keep cached values in line with the ones used by libtorrent.
284 int cleanLimitValue(const int value)
286 return ((value < 0) || (value == std::numeric_limits<int>::max())) ? 0 : value;
290 // TorrentImpl
292 TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
293 , const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
294 : Torrent(session)
295 , m_session(session)
296 , m_nativeSession(nativeSession)
297 , m_nativeHandle(nativeHandle)
298 #ifdef QBT_USES_LIBTORRENT2
299 , m_infoHash(m_nativeHandle.info_hashes())
300 #else
301 , m_infoHash(m_nativeHandle.info_hash())
302 #endif
303 , m_name(params.name)
304 , m_savePath(params.savePath)
305 , m_downloadPath(params.downloadPath)
306 , m_category(params.category)
307 , m_tags(params.tags)
308 , m_ratioLimit(params.ratioLimit)
309 , m_seedingTimeLimit(params.seedingTimeLimit)
310 , m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit)
311 , m_shareLimitAction(params.shareLimitAction)
312 , m_operatingMode(params.operatingMode)
313 , m_contentLayout(params.contentLayout)
314 , m_hasFinishedStatus(params.hasFinishedStatus)
315 , m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
316 , m_useAutoTMM(params.useAutoTMM)
317 , m_isStopped(params.stopped)
318 , m_sslParams(params.sslParameters)
319 , m_ltAddTorrentParams(params.ltAddTorrentParams)
320 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit))
321 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit))
323 if (m_ltAddTorrentParams.ti)
325 // Initialize it only if torrent is added with metadata.
326 // Otherwise it should be initialized in "Metadata received" handler.
327 m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
329 Q_ASSERT(m_filePaths.isEmpty());
330 Q_ASSERT(m_indexMap.isEmpty());
331 const int filesCount = m_torrentInfo.filesCount();
332 m_filePaths.reserve(filesCount);
333 m_indexMap.reserve(filesCount);
334 m_filePriorities.reserve(filesCount);
335 const std::vector<lt::download_priority_t> filePriorities =
336 resized(m_ltAddTorrentParams.file_priorities, m_ltAddTorrentParams.ti->num_files()
337 , LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
339 m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
340 m_filesProgress.resize(filesCount);
342 for (int i = 0; i < filesCount; ++i)
344 const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
345 m_indexMap[nativeIndex] = i;
347 const auto fileIter = m_ltAddTorrentParams.renamed_files.find(nativeIndex);
348 const Path filePath = ((fileIter != m_ltAddTorrentParams.renamed_files.end())
349 ? makeUserPath(Path(fileIter->second)) : m_torrentInfo.filePath(i));
350 m_filePaths.append(filePath);
352 const auto priority = LT::fromNative(filePriorities[LT::toUnderlyingType(nativeIndex)]);
353 m_filePriorities.append(priority);
357 setStopCondition(params.stopCondition);
359 const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
360 m_trackerEntryStatuses.reserve(static_cast<decltype(m_trackerEntryStatuses)::size_type>(extensionData->trackers.size()));
361 for (const lt::announce_entry &announceEntry : extensionData->trackers)
362 m_trackerEntryStatuses.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
363 m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
364 for (const std::string &urlSeed : extensionData->urlSeeds)
365 m_urlSeeds.append(QString::fromStdString(urlSeed));
366 m_nativeStatus = extensionData->status;
368 if (hasMetadata())
369 updateProgress();
371 updateState();
373 if (hasMetadata())
374 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
377 TorrentImpl::~TorrentImpl() = default;
379 bool TorrentImpl::isValid() const
381 return m_nativeHandle.is_valid();
384 Session *TorrentImpl::session() const
386 return m_session;
389 InfoHash TorrentImpl::infoHash() const
391 return m_infoHash;
394 QString TorrentImpl::name() const
396 if (!m_name.isEmpty())
397 return m_name;
399 if (hasMetadata())
400 return m_torrentInfo.name();
402 const QString name = QString::fromStdString(m_nativeStatus.name);
403 if (!name.isEmpty())
404 return name;
406 return id().toString();
409 QDateTime TorrentImpl::creationDate() const
411 if (!hasMetadata())
412 return {};
414 const std::time_t date = nativeTorrentInfo()->creation_date();
415 return ((date != 0) ? QDateTime::fromSecsSinceEpoch(date) : QDateTime());
418 QString TorrentImpl::creator() const
420 if (!hasMetadata())
421 return {};
423 return QString::fromStdString(nativeTorrentInfo()->creator());
426 QString TorrentImpl::comment() const
428 if (!hasMetadata())
429 return {};
431 return QString::fromStdString(nativeTorrentInfo()->comment());
434 bool TorrentImpl::isPrivate() const
436 return m_torrentInfo.isPrivate();
439 qlonglong TorrentImpl::totalSize() const
441 return m_torrentInfo.totalSize();
444 // size without the "don't download" files
445 qlonglong TorrentImpl::wantedSize() const
447 return m_nativeStatus.total_wanted;
450 qlonglong TorrentImpl::completedSize() const
452 return m_nativeStatus.total_wanted_done;
455 qlonglong TorrentImpl::pieceLength() const
457 return m_torrentInfo.pieceLength();
460 qlonglong TorrentImpl::wastedSize() const
462 return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes);
465 QString TorrentImpl::currentTracker() const
467 return QString::fromStdString(m_nativeStatus.current_tracker);
470 Path TorrentImpl::savePath() const
472 return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath;
475 void TorrentImpl::setSavePath(const Path &path)
477 Q_ASSERT(!isAutoTMMEnabled());
478 if (isAutoTMMEnabled()) [[unlikely]]
479 return;
481 const Path basePath = m_session->useCategoryPathsInManualMode()
482 ? m_session->categorySavePath(category()) : m_session->savePath();
483 const Path resolvedPath = (path.isAbsolute() ? path : (basePath / path));
484 if (resolvedPath == savePath())
485 return;
487 if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
489 moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
491 else
493 m_savePath = resolvedPath;
494 m_session->handleTorrentSavePathChanged(this);
495 deferredRequestResumeData();
499 Path TorrentImpl::downloadPath() const
501 return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath;
504 void TorrentImpl::setDownloadPath(const Path &path)
506 Q_ASSERT(!isAutoTMMEnabled());
507 if (isAutoTMMEnabled()) [[unlikely]]
508 return;
510 const Path basePath = m_session->useCategoryPathsInManualMode()
511 ? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
512 const Path resolvedPath = (path.isEmpty() || path.isAbsolute()) ? path : (basePath / path);
513 if (resolvedPath == m_downloadPath)
514 return;
516 const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
517 if (isIncomplete)
519 moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
521 else
523 m_downloadPath = resolvedPath;
524 m_session->handleTorrentSavePathChanged(this);
525 deferredRequestResumeData();
529 Path TorrentImpl::rootPath() const
531 if (!hasMetadata())
532 return {};
534 const Path relativeRootPath = Path::findRootFolder(filePaths());
535 if (relativeRootPath.isEmpty())
536 return {};
538 return (actualStorageLocation() / relativeRootPath);
541 Path TorrentImpl::contentPath() const
543 if (!hasMetadata())
544 return {};
546 if (filesCount() == 1)
547 return (actualStorageLocation() / filePath(0));
549 const Path rootPath = this->rootPath();
550 return (rootPath.isEmpty() ? actualStorageLocation() : rootPath);
553 bool TorrentImpl::isAutoTMMEnabled() const
555 return m_useAutoTMM;
558 void TorrentImpl::setAutoTMMEnabled(bool enabled)
560 if (m_useAutoTMM == enabled)
561 return;
563 m_useAutoTMM = enabled;
564 if (!m_useAutoTMM)
566 m_savePath = m_session->categorySavePath(category());
567 m_downloadPath = m_session->categoryDownloadPath(category());
570 deferredRequestResumeData();
571 m_session->handleTorrentSavingModeChanged(this);
573 adjustStorageLocation();
576 Path TorrentImpl::actualStorageLocation() const
578 if (!hasMetadata())
579 return {};
581 return Path(m_nativeStatus.save_path);
584 void TorrentImpl::setAutoManaged(const bool enable)
586 if (enable)
587 m_nativeHandle.set_flags(lt::torrent_flags::auto_managed);
588 else
589 m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
592 Path TorrentImpl::makeActualPath(int index, const Path &path) const
594 Path actualPath = path;
596 if (m_session->isAppendExtensionEnabled()
597 && (fileSize(index) > 0) && !m_completedFiles.at(index))
599 actualPath += QB_EXT;
602 if (m_session->isUnwantedFolderEnabled()
603 && (m_filePriorities[index] == DownloadPriority::Ignored))
605 const Path parentPath = actualPath.parentPath();
606 const QString fileName = actualPath.filename();
607 actualPath = parentPath / Path(UNWANTED_FOLDER_NAME) / Path(fileName);
610 return actualPath;
613 Path TorrentImpl::makeUserPath(const Path &path) const
615 Path userPath = path.removedExtension(QB_EXT);
617 const Path parentRelPath = userPath.parentPath();
618 if (parentRelPath.filename() == UNWANTED_FOLDER_NAME)
620 const QString fileName = userPath.filename();
621 const Path relPath = parentRelPath.parentPath();
622 userPath = relPath / Path(fileName);
625 return userPath;
628 QList<TrackerEntryStatus> TorrentImpl::trackers() const
630 return m_trackerEntryStatuses;
633 void TorrentImpl::addTrackers(QList<TrackerEntry> trackers)
635 trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
637 QSet<TrackerEntry> currentTrackerSet;
638 currentTrackerSet.reserve(m_trackerEntryStatuses.size());
639 for (const TrackerEntryStatus &status : asConst(m_trackerEntryStatuses))
640 currentTrackerSet.insert({.url = status.url, .tier = status.tier});
642 const auto newTrackerSet = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend()) - currentTrackerSet;
643 if (newTrackerSet.isEmpty())
644 return;
646 trackers = QList<TrackerEntry>(newTrackerSet.cbegin(), newTrackerSet.cend());
647 for (const TrackerEntry &tracker : asConst(trackers))
649 m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
650 m_trackerEntryStatuses.append({tracker.url, tracker.tier});
652 std::sort(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
653 , [](const TrackerEntryStatus &left, const TrackerEntryStatus &right) { return left.tier < right.tier; });
655 deferredRequestResumeData();
656 m_session->handleTorrentTrackersAdded(this, trackers);
659 void TorrentImpl::removeTrackers(const QStringList &trackers)
661 QStringList removedTrackers = trackers;
662 for (const QString &tracker : trackers)
664 if (!m_trackerEntryStatuses.removeOne({tracker}))
665 removedTrackers.removeOne(tracker);
668 std::vector<lt::announce_entry> nativeTrackers;
669 nativeTrackers.reserve(m_trackerEntryStatuses.size());
670 for (const TrackerEntryStatus &tracker : asConst(m_trackerEntryStatuses))
671 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
673 if (!removedTrackers.isEmpty())
675 m_nativeHandle.replace_trackers(nativeTrackers);
677 deferredRequestResumeData();
678 m_session->handleTorrentTrackersRemoved(this, removedTrackers);
682 void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
684 trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
686 // Filter out duplicate trackers
687 const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
688 trackers = QList<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
689 std::sort(trackers.begin(), trackers.end()
690 , [](const TrackerEntry &left, const TrackerEntry &right) { return left.tier < right.tier; });
692 std::vector<lt::announce_entry> nativeTrackers;
693 nativeTrackers.reserve(trackers.size());
694 m_trackerEntryStatuses.clear();
696 for (const TrackerEntry &tracker : trackers)
698 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
699 m_trackerEntryStatuses.append({tracker.url, tracker.tier});
702 m_nativeHandle.replace_trackers(nativeTrackers);
704 // Clear the peer list if it's a private torrent since
705 // we do not want to keep connecting with peers from old tracker.
706 if (isPrivate())
707 clearPeers();
709 deferredRequestResumeData();
710 m_session->handleTorrentTrackersChanged(this);
713 QList<QUrl> TorrentImpl::urlSeeds() const
715 return m_urlSeeds;
718 void TorrentImpl::addUrlSeeds(const QList<QUrl> &urlSeeds)
720 m_session->invokeAsync([urlSeeds, session = m_session
721 , nativeHandle = m_nativeHandle
722 , thisTorrent = QPointer<TorrentImpl>(this)]
726 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
727 QList<QUrl> currentSeeds;
728 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
729 for (const std::string &urlSeed : nativeSeeds)
730 currentSeeds.append(QString::fromStdString(urlSeed));
732 QList<QUrl> addedUrlSeeds;
733 addedUrlSeeds.reserve(urlSeeds.size());
735 for (const QUrl &url : urlSeeds)
737 if (!currentSeeds.contains(url))
739 nativeHandle.add_url_seed(url.toString().toStdString());
740 addedUrlSeeds.append(url);
744 currentSeeds.append(addedUrlSeeds);
745 session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
747 if (!thisTorrent)
748 return;
750 thisTorrent->m_urlSeeds = currentSeeds;
751 if (!addedUrlSeeds.isEmpty())
753 thisTorrent->deferredRequestResumeData();
754 session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
758 catch (const std::exception &) {}
762 void TorrentImpl::removeUrlSeeds(const QList<QUrl> &urlSeeds)
764 m_session->invokeAsync([urlSeeds, session = m_session
765 , nativeHandle = m_nativeHandle
766 , thisTorrent = QPointer<TorrentImpl>(this)]
770 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
771 QList<QUrl> currentSeeds;
772 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
773 for (const std::string &urlSeed : nativeSeeds)
774 currentSeeds.append(QString::fromStdString(urlSeed));
776 QList<QUrl> removedUrlSeeds;
777 removedUrlSeeds.reserve(urlSeeds.size());
779 for (const QUrl &url : urlSeeds)
781 if (currentSeeds.removeOne(url))
783 nativeHandle.remove_url_seed(url.toString().toStdString());
784 removedUrlSeeds.append(url);
788 session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
790 if (!thisTorrent)
791 return;
793 thisTorrent->m_urlSeeds = currentSeeds;
795 if (!removedUrlSeeds.isEmpty())
797 thisTorrent->deferredRequestResumeData();
798 session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
802 catch (const std::exception &) {}
806 void TorrentImpl::clearPeers()
808 m_nativeHandle.clear_peers();
811 bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
813 lt::error_code ec;
814 const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec);
815 if (ec) return false;
817 const lt::tcp::endpoint endpoint(addr, peerAddress.port);
820 m_nativeHandle.connect_peer(endpoint);
822 catch (const lt::system_error &err)
824 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
825 .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
826 return false;
829 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name()));
830 return true;
833 bool TorrentImpl::needSaveResumeData() const
835 return m_nativeStatus.need_save_resume;
838 void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags)
840 m_nativeHandle.save_resume_data(flags);
841 m_deferredRequestResumeDataInvoked = false;
843 m_session->handleTorrentResumeDataRequested(this);
846 void TorrentImpl::deferredRequestResumeData()
848 if (!m_deferredRequestResumeDataInvoked)
850 QMetaObject::invokeMethod(this, [this]
852 requestResumeData((m_maintenanceJob == MaintenanceJob::HandleMetadata)
853 ? lt::torrent_handle::save_info_dict : lt::resume_data_flags_t());
854 }, Qt::QueuedConnection);
856 m_deferredRequestResumeDataInvoked = true;
860 int TorrentImpl::filesCount() const
862 return m_torrentInfo.filesCount();
865 int TorrentImpl::piecesCount() const
867 return m_torrentInfo.piecesCount();
870 int TorrentImpl::piecesHave() const
872 return m_nativeStatus.num_pieces;
875 qreal TorrentImpl::progress() const
877 if (isChecking())
878 return m_nativeStatus.progress;
880 if (m_nativeStatus.total_wanted == 0)
881 return 0.;
883 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
884 return 1.;
886 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
887 if ((progress < 0.f) || (progress > 1.f))
889 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
890 .arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
891 , Log::WARNING);
894 return progress;
897 QString TorrentImpl::category() const
899 return m_category;
902 bool TorrentImpl::belongsToCategory(const QString &category) const
904 if (m_category.isEmpty())
905 return category.isEmpty();
907 if (m_category == category)
908 return true;
910 return (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + u'/'));
913 TagSet TorrentImpl::tags() const
915 return m_tags;
918 bool TorrentImpl::hasTag(const Tag &tag) const
920 return m_tags.contains(tag);
923 bool TorrentImpl::addTag(const Tag &tag)
925 if (!tag.isValid())
926 return false;
927 if (hasTag(tag))
928 return false;
930 if (!m_session->hasTag(tag))
932 if (!m_session->addTag(tag))
933 return false;
935 m_tags.insert(tag);
936 deferredRequestResumeData();
937 m_session->handleTorrentTagAdded(this, tag);
938 return true;
941 bool TorrentImpl::removeTag(const Tag &tag)
943 if (m_tags.remove(tag))
945 deferredRequestResumeData();
946 m_session->handleTorrentTagRemoved(this, tag);
947 return true;
949 return false;
952 void TorrentImpl::removeAllTags()
954 for (const Tag &tag : asConst(tags()))
955 removeTag(tag);
958 QDateTime TorrentImpl::addedTime() const
960 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
963 qreal TorrentImpl::ratioLimit() const
965 return m_ratioLimit;
968 int TorrentImpl::seedingTimeLimit() const
970 return m_seedingTimeLimit;
973 int TorrentImpl::inactiveSeedingTimeLimit() const
975 return m_inactiveSeedingTimeLimit;
978 Path TorrentImpl::filePath(const int index) const
980 Q_ASSERT(index >= 0);
981 Q_ASSERT(index < m_filePaths.size());
983 return m_filePaths.value(index, {});
986 Path TorrentImpl::actualFilePath(const int index) const
988 const QList<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
990 Q_ASSERT(index >= 0);
991 Q_ASSERT(index < nativeIndexes.size());
992 if ((index < 0) || (index >= nativeIndexes.size()))
993 return {};
995 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes[index]));
998 qlonglong TorrentImpl::fileSize(const int index) const
1000 return m_torrentInfo.fileSize(index);
1003 PathList TorrentImpl::filePaths() const
1005 return m_filePaths;
1008 PathList TorrentImpl::actualFilePaths() const
1010 if (!hasMetadata())
1011 return {};
1013 PathList paths;
1014 paths.reserve(filesCount());
1016 const lt::file_storage files = nativeTorrentInfo()->files();
1017 for (const lt::file_index_t &nativeIndex : asConst(m_torrentInfo.nativeIndexes()))
1018 paths.emplaceBack(files.file_path(nativeIndex));
1020 return paths;
1023 QList<DownloadPriority> TorrentImpl::filePriorities() const
1025 return m_filePriorities;
1028 TorrentInfo TorrentImpl::info() const
1030 return m_torrentInfo;
1033 bool TorrentImpl::isStopped() const
1035 return m_isStopped;
1038 bool TorrentImpl::isQueued() const
1040 if (!m_session->isQueueingSystemEnabled())
1041 return false;
1043 // Torrent is Queued if it isn't in Stopped state but paused internally
1044 return (!isStopped()
1045 && (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
1046 && (m_nativeStatus.flags & lt::torrent_flags::paused));
1049 bool TorrentImpl::isChecking() const
1051 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
1052 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
1055 bool TorrentImpl::isDownloading() const
1057 switch (m_state)
1059 case TorrentState::Downloading:
1060 case TorrentState::DownloadingMetadata:
1061 case TorrentState::ForcedDownloadingMetadata:
1062 case TorrentState::StalledDownloading:
1063 case TorrentState::CheckingDownloading:
1064 case TorrentState::StoppedDownloading:
1065 case TorrentState::QueuedDownloading:
1066 case TorrentState::ForcedDownloading:
1067 return true;
1068 default:
1069 break;
1072 return false;
1075 bool TorrentImpl::isMoving() const
1077 return m_state == TorrentState::Moving;
1080 bool TorrentImpl::isUploading() const
1082 switch (m_state)
1084 case TorrentState::Uploading:
1085 case TorrentState::StalledUploading:
1086 case TorrentState::CheckingUploading:
1087 case TorrentState::QueuedUploading:
1088 case TorrentState::ForcedUploading:
1089 return true;
1090 default:
1091 break;
1094 return false;
1097 bool TorrentImpl::isCompleted() const
1099 switch (m_state)
1101 case TorrentState::Uploading:
1102 case TorrentState::StalledUploading:
1103 case TorrentState::CheckingUploading:
1104 case TorrentState::StoppedUploading:
1105 case TorrentState::QueuedUploading:
1106 case TorrentState::ForcedUploading:
1107 return true;
1108 default:
1109 break;
1112 return false;
1115 bool TorrentImpl::isActive() const
1117 switch (m_state)
1119 case TorrentState::StalledDownloading:
1120 return (uploadPayloadRate() > 0);
1122 case TorrentState::DownloadingMetadata:
1123 case TorrentState::ForcedDownloadingMetadata:
1124 case TorrentState::Downloading:
1125 case TorrentState::ForcedDownloading:
1126 case TorrentState::Uploading:
1127 case TorrentState::ForcedUploading:
1128 case TorrentState::Moving:
1129 return true;
1131 default:
1132 break;
1135 return false;
1138 bool TorrentImpl::isInactive() const
1140 return !isActive();
1143 bool TorrentImpl::isErrored() const
1145 return ((m_state == TorrentState::MissingFiles)
1146 || (m_state == TorrentState::Error));
1149 bool TorrentImpl::isFinished() const
1151 return ((m_nativeStatus.state == lt::torrent_status::finished)
1152 || (m_nativeStatus.state == lt::torrent_status::seeding));
1155 bool TorrentImpl::isForced() const
1157 return (!isStopped() && (m_operatingMode == TorrentOperatingMode::Forced));
1160 bool TorrentImpl::isSequentialDownload() const
1162 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::sequential_download);
1165 bool TorrentImpl::hasFirstLastPiecePriority() const
1167 return m_hasFirstLastPiecePriority;
1170 TorrentState TorrentImpl::state() const
1172 return m_state;
1175 void TorrentImpl::updateState()
1177 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
1179 m_state = TorrentState::CheckingResumeData;
1181 else if (isMoveInProgress())
1183 m_state = TorrentState::Moving;
1185 else if (hasMissingFiles())
1187 m_state = TorrentState::MissingFiles;
1189 else if (hasError())
1191 m_state = TorrentState::Error;
1193 else if (!hasMetadata())
1195 if (isStopped())
1196 m_state = TorrentState::StoppedDownloading;
1197 else if (isQueued())
1198 m_state = TorrentState::QueuedDownloading;
1199 else
1200 m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata;
1202 else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isStopped())
1204 // If the torrent is not just in the "checking" state, but is being actually checked
1205 m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
1207 else if (isFinished())
1209 if (isStopped())
1210 m_state = TorrentState::StoppedUploading;
1211 else if (isQueued())
1212 m_state = TorrentState::QueuedUploading;
1213 else if (isForced())
1214 m_state = TorrentState::ForcedUploading;
1215 else if (m_nativeStatus.upload_payload_rate > 0)
1216 m_state = TorrentState::Uploading;
1217 else
1218 m_state = TorrentState::StalledUploading;
1220 else
1222 if (isStopped())
1223 m_state = TorrentState::StoppedDownloading;
1224 else if (isQueued())
1225 m_state = TorrentState::QueuedDownloading;
1226 else if (isForced())
1227 m_state = TorrentState::ForcedDownloading;
1228 else if (m_nativeStatus.download_payload_rate > 0)
1229 m_state = TorrentState::Downloading;
1230 else
1231 m_state = TorrentState::StalledDownloading;
1235 bool TorrentImpl::hasMetadata() const
1237 return m_torrentInfo.isValid();
1240 bool TorrentImpl::hasMissingFiles() const
1242 return m_hasMissingFiles;
1245 bool TorrentImpl::hasError() const
1247 return (m_nativeStatus.errc || (m_nativeStatus.flags & lt::torrent_flags::upload_mode));
1250 int TorrentImpl::queuePosition() const
1252 return static_cast<int>(m_nativeStatus.queue_position);
1255 QString TorrentImpl::error() const
1257 if (m_nativeStatus.errc)
1258 return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
1260 if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
1262 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1263 .arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
1266 return {};
1269 qlonglong TorrentImpl::totalDownload() const
1271 return m_nativeStatus.all_time_download;
1274 qlonglong TorrentImpl::totalUpload() const
1276 return m_nativeStatus.all_time_upload;
1279 qlonglong TorrentImpl::activeTime() const
1281 return lt::total_seconds(m_nativeStatus.active_duration);
1284 qlonglong TorrentImpl::finishedTime() const
1286 return lt::total_seconds(m_nativeStatus.finished_duration);
1289 qlonglong TorrentImpl::eta() const
1291 if (isStopped()) return MAX_ETA;
1293 const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
1295 if (isFinished())
1297 const qreal maxRatioValue = maxRatio();
1298 const int maxSeedingTimeValue = maxSeedingTime();
1299 const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
1300 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
1302 qlonglong ratioEta = MAX_ETA;
1304 if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
1307 qlonglong realDL = totalDownload();
1308 if (realDL <= 0)
1309 realDL = wantedSize();
1311 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1314 qlonglong seedingTimeEta = MAX_ETA;
1316 if (maxSeedingTimeValue >= 0)
1318 seedingTimeEta = (maxSeedingTimeValue * 60) - finishedTime();
1319 if (seedingTimeEta < 0)
1320 seedingTimeEta = 0;
1323 qlonglong inactiveSeedingTimeEta = MAX_ETA;
1325 if (maxInactiveSeedingTimeValue >= 0)
1327 inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
1328 inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
1331 return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
1334 if (!speedAverage.download) return MAX_ETA;
1336 return (wantedSize() - completedSize()) / speedAverage.download;
1339 QList<qreal> TorrentImpl::filesProgress() const
1341 if (!hasMetadata())
1342 return {};
1344 const int count = m_filesProgress.size();
1345 Q_ASSERT(count == filesCount());
1346 if (count != filesCount()) [[unlikely]]
1347 return {};
1349 if (m_completedFiles.count(true) == count)
1350 return QList<qreal>(count, 1);
1352 QList<qreal> result;
1353 result.reserve(count);
1354 for (int i = 0; i < count; ++i)
1356 const int64_t progress = m_filesProgress.at(i);
1357 const int64_t size = fileSize(i);
1358 if ((size <= 0) || (progress == size))
1359 result << 1;
1360 else
1361 result << (progress / static_cast<qreal>(size));
1364 return result;
1367 int TorrentImpl::seedsCount() const
1369 return m_nativeStatus.num_seeds;
1372 int TorrentImpl::peersCount() const
1374 return m_nativeStatus.num_peers;
1377 int TorrentImpl::leechsCount() const
1379 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1382 int TorrentImpl::totalSeedsCount() const
1384 return (m_nativeStatus.num_complete > -1) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1387 int TorrentImpl::totalPeersCount() const
1389 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1390 return (peers > -1) ? peers : m_nativeStatus.list_peers;
1393 int TorrentImpl::totalLeechersCount() const
1395 return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1398 QDateTime TorrentImpl::lastSeenComplete() const
1400 if (m_nativeStatus.last_seen_complete > 0)
1401 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1402 else
1403 return {};
1406 QDateTime TorrentImpl::completedTime() const
1408 if (m_nativeStatus.completed_time > 0)
1409 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1410 else
1411 return {};
1414 qlonglong TorrentImpl::timeSinceUpload() const
1416 if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
1417 return -1;
1418 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1421 qlonglong TorrentImpl::timeSinceDownload() const
1423 if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
1424 return -1;
1425 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1428 qlonglong TorrentImpl::timeSinceActivity() const
1430 const qlonglong upTime = timeSinceUpload();
1431 const qlonglong downTime = timeSinceDownload();
1432 return ((upTime < 0) != (downTime < 0))
1433 ? std::max(upTime, downTime)
1434 : std::min(upTime, downTime);
1437 int TorrentImpl::downloadLimit() const
1439 return m_downloadLimit;;
1442 int TorrentImpl::uploadLimit() const
1444 return m_uploadLimit;
1447 bool TorrentImpl::superSeeding() const
1449 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::super_seeding);
1452 bool TorrentImpl::isDHTDisabled() const
1454 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_dht);
1457 bool TorrentImpl::isPEXDisabled() const
1459 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_pex);
1462 bool TorrentImpl::isLSDDisabled() const
1464 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
1467 QList<PeerInfo> TorrentImpl::peers() const
1469 std::vector<lt::peer_info> nativePeers;
1470 m_nativeHandle.get_peer_info(nativePeers);
1472 QList<PeerInfo> peers;
1473 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
1475 for (const lt::peer_info &peer : nativePeers)
1476 peers.append(PeerInfo(peer, pieces()));
1478 return peers;
1481 QBitArray TorrentImpl::pieces() const
1483 return m_pieces;
1486 QBitArray TorrentImpl::downloadingPieces() const
1488 if (!hasMetadata())
1489 return {};
1491 std::vector<lt::partial_piece_info> queue;
1492 m_nativeHandle.get_download_queue(queue);
1494 QBitArray result {piecesCount()};
1495 for (const lt::partial_piece_info &info : queue)
1496 result.setBit(LT::toUnderlyingType(info.piece_index));
1498 return result;
1501 QList<int> TorrentImpl::pieceAvailability() const
1503 std::vector<int> avail;
1504 m_nativeHandle.piece_availability(avail);
1506 return {avail.cbegin(), avail.cend()};
1509 qreal TorrentImpl::distributedCopies() const
1511 return m_nativeStatus.distributed_copies;
1514 qreal TorrentImpl::maxRatio() const
1516 if (m_ratioLimit == USE_GLOBAL_RATIO)
1517 return m_session->globalMaxRatio();
1519 return m_ratioLimit;
1522 int TorrentImpl::maxSeedingTime() const
1524 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1525 return m_session->globalMaxSeedingMinutes();
1527 return m_seedingTimeLimit;
1530 int TorrentImpl::maxInactiveSeedingTime() const
1532 if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
1533 return m_session->globalMaxInactiveSeedingMinutes();
1535 return m_inactiveSeedingTimeLimit;
1538 qreal TorrentImpl::realRatio() const
1540 const int64_t upload = m_nativeStatus.all_time_upload;
1541 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1542 const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01))
1543 ? m_nativeStatus.total_done
1544 : m_nativeStatus.all_time_download;
1546 if (download == 0)
1547 return (upload == 0) ? 0 : MAX_RATIO;
1549 const qreal ratio = upload / static_cast<qreal>(download);
1550 Q_ASSERT(ratio >= 0);
1551 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1554 int TorrentImpl::uploadPayloadRate() const
1556 // workaround: suppress the speed for Stopped state
1557 return isStopped() ? 0 : m_nativeStatus.upload_payload_rate;
1560 int TorrentImpl::downloadPayloadRate() const
1562 // workaround: suppress the speed for Stopped state
1563 return isStopped() ? 0 : m_nativeStatus.download_payload_rate;
1566 qlonglong TorrentImpl::totalPayloadUpload() const
1568 return m_nativeStatus.total_payload_upload;
1571 qlonglong TorrentImpl::totalPayloadDownload() const
1573 return m_nativeStatus.total_payload_download;
1576 int TorrentImpl::connectionsCount() const
1578 return m_nativeStatus.num_connections;
1581 int TorrentImpl::connectionsLimit() const
1583 return m_nativeStatus.connections_limit;
1586 qlonglong TorrentImpl::nextAnnounce() const
1588 return lt::total_seconds(m_nativeStatus.next_announce);
1591 qreal TorrentImpl::popularity() const
1593 // in order to produce floating-point numbers using `std::chrono::duration_cast`,
1594 // we should use `qreal` as `Rep` to define the `months` duration
1595 using months = std::chrono::duration<qreal, std::chrono::months::period>;
1596 const auto activeMonths = std::chrono::duration_cast<months>(m_nativeStatus.active_duration).count();
1597 return (activeMonths > 0) ? (realRatio() / activeMonths) : 0;
1600 void TorrentImpl::setName(const QString &name)
1602 if (m_name != name)
1604 m_name = name;
1605 deferredRequestResumeData();
1606 m_session->handleTorrentNameChanged(this);
1610 bool TorrentImpl::setCategory(const QString &category)
1612 if (m_category != category)
1614 if (!category.isEmpty() && !m_session->categories().contains(category))
1615 return false;
1617 const QString oldCategory = m_category;
1618 m_category = category;
1619 deferredRequestResumeData();
1620 m_session->handleTorrentCategoryChanged(this, oldCategory);
1622 if (m_useAutoTMM)
1624 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1625 adjustStorageLocation();
1626 else
1627 setAutoTMMEnabled(false);
1631 return true;
1634 void TorrentImpl::forceReannounce(const int index)
1636 m_nativeHandle.force_reannounce(0, index);
1639 void TorrentImpl::forceDHTAnnounce()
1641 m_nativeHandle.force_dht_announce();
1644 void TorrentImpl::forceRecheck()
1646 if (!hasMetadata())
1647 return;
1649 m_nativeHandle.force_recheck();
1650 // We have to force update the cached state, otherwise someone will be able to get
1651 // an incorrect one during the interval until the cached state is updated in a regular way.
1652 m_nativeStatus.state = lt::torrent_status::checking_resume_data;
1654 if (m_hasMissingFiles)
1656 m_hasMissingFiles = false;
1657 if (!isStopped())
1659 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1660 if (m_operatingMode == TorrentOperatingMode::Forced)
1661 m_nativeHandle.resume();
1665 m_unchecked = false;
1667 m_completedFiles.fill(false);
1668 m_filesProgress.fill(0);
1669 m_pieces.fill(false);
1670 m_nativeStatus.pieces.clear_all();
1671 m_nativeStatus.num_pieces = 0;
1673 if (isStopped())
1675 // When "force recheck" is applied on Stopped torrent, we start them to perform checking
1676 start();
1677 m_stopCondition = StopCondition::FilesChecked;
1681 void TorrentImpl::setSequentialDownload(const bool enable)
1683 if (enable)
1685 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1686 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1688 else
1690 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1691 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1694 deferredRequestResumeData();
1697 void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
1699 if (m_hasFirstLastPiecePriority == enabled)
1700 return;
1702 m_hasFirstLastPiecePriority = enabled;
1703 if (hasMetadata())
1704 applyFirstLastPiecePriority(enabled);
1706 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1707 .arg((enabled ? tr("On") : tr("Off")), name()));
1709 deferredRequestResumeData();
1712 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
1714 Q_ASSERT(hasMetadata());
1716 // Download first and last pieces first for every file in the torrent
1718 auto piecePriorities = std::vector<lt::download_priority_t>(m_torrentInfo.piecesCount(), LT::toNative(DownloadPriority::Ignored));
1720 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1721 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1722 for (int fileIndex = 0; fileIndex < m_filePriorities.size(); ++fileIndex)
1724 const DownloadPriority filePrio = m_filePriorities[fileIndex];
1725 if (filePrio <= DownloadPriority::Ignored)
1726 continue;
1728 // Determine the priority to set
1729 const lt::download_priority_t piecePrio = LT::toNative(enabled ? DownloadPriority::Maximum : filePrio);
1730 const TorrentInfo::PieceRange pieceRange = m_torrentInfo.filePieces(fileIndex);
1732 // worst case: AVI index = 1% of total file size (at the end of the file)
1733 const int numPieces = std::ceil(fileSize(fileIndex) * 0.01 / pieceLength());
1734 for (int i = 0; i < numPieces; ++i)
1736 piecePriorities[pieceRange.first() + i] = piecePrio;
1737 piecePriorities[pieceRange.last() - i] = piecePrio;
1740 const int firstPiece = pieceRange.first() + numPieces;
1741 const int lastPiece = pieceRange.last() - numPieces;
1742 for (int pieceIndex = firstPiece; pieceIndex <= lastPiece; ++pieceIndex)
1743 piecePriorities[pieceIndex] = LT::toNative(filePrio);
1746 m_nativeHandle.prioritize_pieces(piecePriorities);
1749 void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
1751 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1752 endReceivedMetadataHandling(savePath, fileNames);
1755 TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
1757 const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
1758 , [&announceEntry](const TrackerEntryStatus &trackerEntryStatus)
1760 return (trackerEntryStatus.url == QString::fromStdString(announceEntry.url));
1763 Q_ASSERT(it != m_trackerEntryStatuses.end());
1764 if (it == m_trackerEntryStatuses.end()) [[unlikely]]
1765 return {};
1767 #ifdef QBT_USES_LIBTORRENT2
1768 QSet<int> btProtocols;
1769 const auto &infoHashes = nativeHandle().info_hashes();
1770 if (infoHashes.has(lt::protocol_version::V1))
1771 btProtocols.insert(1);
1772 if (infoHashes.has(lt::protocol_version::V2))
1773 btProtocols.insert(2);
1774 #else
1775 const QSet<int> btProtocols {1};
1776 #endif
1778 const auto fromLTTimePoint32 = [this](const lt::time_point32 &timePoint)
1780 return m_session->fromLTTimePoint32(timePoint);
1782 ::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo, fromLTTimePoint32);
1784 return *it;
1787 void TorrentImpl::resetTrackerEntryStatuses()
1789 for (TrackerEntryStatus &status : m_trackerEntryStatuses)
1791 const QString tempUrl = status.url;
1792 const int tempTier = status.tier;
1794 status.clear();
1795 status.url = tempUrl;
1796 status.tier = tempTier;
1800 std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
1802 Q_ASSERT(!m_nativeStatus.torrent_file.expired());
1804 return m_nativeStatus.torrent_file.lock();
1807 void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
1809 Q_ASSERT(m_maintenanceJob == MaintenanceJob::HandleMetadata);
1810 if (m_maintenanceJob != MaintenanceJob::HandleMetadata) [[unlikely]]
1811 return;
1813 Q_ASSERT(m_filePaths.isEmpty());
1814 if (!m_filePaths.isEmpty()) [[unlikely]]
1815 m_filePaths.clear();
1817 lt::add_torrent_params &p = m_ltAddTorrentParams;
1819 const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1820 m_torrentInfo = TorrentInfo(*metadata);
1821 m_filePriorities.reserve(filesCount());
1822 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
1823 p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
1824 , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
1826 m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
1827 m_filesProgress.resize(filesCount());
1828 updateProgress();
1830 for (int i = 0; i < fileNames.size(); ++i)
1832 const auto nativeIndex = nativeIndexes.at(i);
1834 const Path &actualFilePath = fileNames.at(i);
1835 p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
1837 const Path filePath = actualFilePath.removedExtension(QB_EXT);
1838 m_filePaths.append(filePath);
1840 m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
1843 m_session->applyFilenameFilter(m_filePaths, m_filePriorities);
1844 for (int i = 0; i < m_filePriorities.size(); ++i)
1845 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
1847 p.save_path = savePath.toString().toStdString();
1848 p.ti = metadata;
1850 if (stopCondition() == StopCondition::MetadataReceived)
1852 m_stopCondition = StopCondition::None;
1854 m_isStopped = true;
1855 p.flags |= lt::torrent_flags::paused;
1856 p.flags &= ~lt::torrent_flags::auto_managed;
1858 m_session->handleTorrentStopped(this);
1861 reload();
1863 // If first/last piece priority was specified when adding this torrent,
1864 // we should apply it now that we have metadata:
1865 if (m_hasFirstLastPiecePriority)
1866 applyFirstLastPiecePriority(true);
1868 m_maintenanceJob = MaintenanceJob::None;
1869 prepareResumeData(p);
1871 m_session->handleTorrentMetadataReceived(this);
1874 void TorrentImpl::reload()
1878 m_completedFiles.fill(false);
1879 m_filesProgress.fill(0);
1880 m_pieces.fill(false);
1881 m_nativeStatus.pieces.clear_all();
1882 m_nativeStatus.num_pieces = 0;
1884 const auto queuePos = m_nativeHandle.queue_position();
1886 m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
1888 lt::add_torrent_params p = m_ltAddTorrentParams;
1889 p.flags |= lt::torrent_flags::update_subscribe
1890 | lt::torrent_flags::override_trackers
1891 | lt::torrent_flags::override_web_seeds;
1893 if (m_isStopped)
1895 p.flags |= lt::torrent_flags::paused;
1896 p.flags &= ~lt::torrent_flags::auto_managed;
1898 else if (m_operatingMode == TorrentOperatingMode::AutoManaged)
1900 p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1902 else
1904 p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1907 auto *const extensionData = new ExtensionData;
1908 p.userdata = LTClientData(extensionData);
1909 #ifndef QBT_USES_LIBTORRENT2
1910 p.storage = customStorageConstructor;
1911 #endif
1912 m_nativeHandle = m_nativeSession->add_torrent(p);
1914 m_nativeStatus = extensionData->status;
1916 if (queuePos >= lt::queue_position_t {})
1917 m_nativeHandle.queue_position_set(queuePos);
1918 m_nativeStatus.queue_position = queuePos;
1920 updateState();
1922 catch (const lt::system_error &err)
1924 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1925 .arg(id().toString(), QString::fromLocal8Bit(err.what())));
1929 void TorrentImpl::stop()
1931 if (!m_isStopped)
1933 m_stopCondition = StopCondition::None;
1934 m_isStopped = true;
1935 deferredRequestResumeData();
1936 m_session->handleTorrentStopped(this);
1939 if (m_maintenanceJob == MaintenanceJob::None)
1941 setAutoManaged(false);
1942 m_nativeHandle.pause();
1944 m_payloadRateMonitor.reset();
1948 void TorrentImpl::start(const TorrentOperatingMode mode)
1950 if (hasError())
1952 m_nativeHandle.clear_error();
1953 m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode);
1956 m_operatingMode = mode;
1958 if (m_hasMissingFiles)
1960 m_hasMissingFiles = false;
1961 m_isStopped = false;
1962 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1963 reload();
1964 return;
1967 if (m_isStopped)
1969 m_isStopped = false;
1970 deferredRequestResumeData();
1971 m_session->handleTorrentStarted(this);
1974 if (m_maintenanceJob == MaintenanceJob::None)
1976 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1977 if (m_operatingMode == TorrentOperatingMode::Forced)
1978 m_nativeHandle.resume();
1982 void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
1984 if (!hasMetadata())
1986 if (context == MoveStorageContext::ChangeSavePath)
1988 m_savePath = newPath;
1989 m_session->handleTorrentSavePathChanged(this);
1991 else if (context == MoveStorageContext::ChangeDownloadPath)
1993 m_downloadPath = newPath;
1994 m_session->handleTorrentSavePathChanged(this);
1997 return;
2000 const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
2001 ? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
2002 if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
2004 if (!m_storageIsMoving)
2006 m_storageIsMoving = true;
2007 updateState();
2008 m_session->handleTorrentStorageMovingStateChanged(this);
2013 void TorrentImpl::renameFile(const int index, const Path &path)
2015 Q_ASSERT((index >= 0) && (index < filesCount()));
2016 if ((index < 0) || (index >= filesCount())) [[unlikely]]
2017 return;
2019 const Path targetActualPath = makeActualPath(index, path);
2020 doRenameFile(index, targetActualPath);
2023 void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
2025 updateStatus(nativeStatus);
2028 void TorrentImpl::handleQueueingModeChanged()
2030 updateState();
2033 void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
2035 if (context == MoveStorageContext::ChangeSavePath)
2036 m_savePath = path;
2037 else if (context == MoveStorageContext::ChangeDownloadPath)
2038 m_downloadPath = path;
2039 m_storageIsMoving = hasOutstandingJob;
2040 m_nativeStatus.save_path = path.toString().toStdString();
2042 m_session->handleTorrentSavePathChanged(this);
2043 deferredRequestResumeData();
2045 if (!m_storageIsMoving)
2047 updateState();
2048 m_session->handleTorrentStorageMovingStateChanged(this);
2050 if (m_hasMissingFiles)
2052 // it can be moved to the proper location
2053 m_hasMissingFiles = false;
2054 m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
2055 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
2056 reload();
2059 while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2060 std::invoke(m_moveFinishedTriggers.dequeue());
2064 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_checked_alert *p)
2066 if (!hasMetadata())
2068 // The torrent is checked due to metadata received, but we should not process
2069 // this event until the torrent is reloaded using the received metadata.
2070 return;
2073 if (stopCondition() == StopCondition::FilesChecked)
2074 stop();
2076 m_statusUpdatedTriggers.enqueue([this]()
2078 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
2080 if (!m_hasMissingFiles)
2082 if ((progress() < 1.0) && (wantedSize() > 0))
2083 m_hasFinishedStatus = false;
2084 else if (progress() == 1.0)
2085 m_hasFinishedStatus = true;
2087 adjustStorageLocation();
2088 manageActualFilePaths();
2090 if (!isStopped())
2092 // torrent is internally paused using NativeTorrentExtension after files checked
2093 // so we need to resume it if there is no corresponding "stop condition" set
2094 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
2095 if (m_operatingMode == TorrentOperatingMode::Forced)
2096 m_nativeHandle.resume();
2100 if (m_nativeStatus.need_save_resume)
2101 deferredRequestResumeData();
2103 m_session->handleTorrentChecked(this);
2107 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused]] const lt::torrent_finished_alert *p)
2109 m_hasMissingFiles = false;
2110 if (m_hasFinishedStatus)
2111 return;
2113 m_statusUpdatedTriggers.enqueue([this]()
2115 adjustStorageLocation();
2116 manageActualFilePaths();
2118 deferredRequestResumeData();
2120 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
2121 if (recheckTorrentsOnCompletion && m_unchecked)
2123 forceRecheck();
2125 else
2127 m_hasFinishedStatus = true;
2129 if (isMoveInProgress() || (m_renameCount > 0))
2130 m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
2131 else
2132 m_session->handleTorrentFinished(this);
2137 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused]] const lt::torrent_paused_alert *p)
2141 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused]] const lt::torrent_resumed_alert *p)
2145 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
2147 if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
2149 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2150 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2151 // than the list we usually cache, so we have to request it from the appropriate source.
2152 fetchURLSeeds([this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
2155 if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
2157 Q_ASSERT(m_indexMap.isEmpty());
2159 const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
2160 m_ltAddTorrentParams = p->params;
2161 if (isSeedMode)
2162 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2164 m_ltAddTorrentParams.have_pieces.clear();
2165 m_ltAddTorrentParams.verified_pieces.clear();
2167 m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti;
2169 const auto metadata = TorrentInfo(*m_ltAddTorrentParams.ti);
2171 const auto &renamedFiles = m_ltAddTorrentParams.renamed_files;
2172 PathList filePaths = metadata.filePaths();
2173 if (renamedFiles.empty() && (m_contentLayout != TorrentContentLayout::Original))
2175 const Path originalRootFolder = Path::findRootFolder(filePaths);
2176 const auto originalContentLayout = (originalRootFolder.isEmpty()
2177 ? TorrentContentLayout::NoSubfolder : TorrentContentLayout::Subfolder);
2178 if (m_contentLayout != originalContentLayout)
2180 if (m_contentLayout == TorrentContentLayout::NoSubfolder)
2181 Path::stripRootFolder(filePaths);
2182 else
2183 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2187 const auto nativeIndexes = metadata.nativeIndexes();
2188 m_indexMap.reserve(filePaths.size());
2189 for (int i = 0; i < filePaths.size(); ++i)
2191 const auto nativeIndex = nativeIndexes.at(i);
2192 m_indexMap[nativeIndex] = i;
2194 if (const auto it = renamedFiles.find(nativeIndex); it != renamedFiles.cend())
2195 filePaths[i] = Path(it->second);
2198 m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
2200 else
2202 prepareResumeData(p->params);
2206 void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
2208 if (m_hasMissingFiles)
2210 const auto havePieces = m_ltAddTorrentParams.have_pieces;
2211 const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
2212 const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
2214 // Update recent resume data but preserve existing progress
2215 m_ltAddTorrentParams = params;
2216 m_ltAddTorrentParams.have_pieces = havePieces;
2217 m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
2218 m_ltAddTorrentParams.verified_pieces = verifiedPieces;
2220 else
2222 const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
2223 // Update recent resume data
2224 m_ltAddTorrentParams = params;
2225 if (preserveSeedMode)
2226 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2229 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2230 m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
2232 const LoadTorrentParams resumeData
2234 .ltAddTorrentParams = m_ltAddTorrentParams,
2235 .name = m_name,
2236 .category = m_category,
2237 .tags = m_tags,
2238 .savePath = (!m_useAutoTMM ? m_savePath : Path()),
2239 .downloadPath = (!m_useAutoTMM ? m_downloadPath : Path()),
2240 .contentLayout = m_contentLayout,
2241 .operatingMode = m_operatingMode,
2242 .useAutoTMM = m_useAutoTMM,
2243 .firstLastPiecePriority = m_hasFirstLastPiecePriority,
2244 .hasFinishedStatus = m_hasFinishedStatus,
2245 .stopped = m_isStopped,
2246 .stopCondition = m_stopCondition,
2247 .addToQueueTop = false,
2248 .ratioLimit = m_ratioLimit,
2249 .seedingTimeLimit = m_seedingTimeLimit,
2250 .inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit,
2251 .shareLimitAction = m_shareLimitAction,
2252 .sslParameters = m_sslParams
2255 m_session->handleTorrentResumeDataReady(this, resumeData);
2258 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
2260 if (p->error != lt::errors::resume_data_not_modified)
2262 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2263 .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
2267 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
2269 // Files were probably moved or storage isn't accessible
2270 m_hasMissingFiles = true;
2271 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2272 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
2275 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
2277 const int fileIndex = m_indexMap.value(p->index, -1);
2278 Q_ASSERT(fileIndex >= 0);
2280 const Path newActualFilePath {QString::fromUtf8(p->new_name())};
2282 const Path oldFilePath = m_filePaths.at(fileIndex);
2283 const Path newFilePath = makeUserPath(newActualFilePath);
2285 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2286 // We should compare path in a case sensitive manner even on case insensitive
2287 // platforms since it can be renamed by only changing case of some character(s)
2288 if (oldFilePath.data() == newFilePath.data())
2290 // Remove empty ".unwanted" folders
2291 #ifdef QBT_USES_LIBTORRENT2
2292 const Path oldActualFilePath {QString::fromUtf8(p->old_name())};
2293 #else
2294 const Path oldActualFilePath;
2295 #endif
2296 const Path oldActualParentPath = oldActualFilePath.parentPath();
2297 const Path newActualParentPath = newActualFilePath.parentPath();
2298 if (newActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2300 if (oldActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2302 #ifdef Q_OS_WIN
2303 const std::wstring winPath = (actualStorageLocation() / newActualParentPath).toString().toStdWString();
2304 const DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str());
2305 ::SetFileAttributesW(winPath.c_str(), (dwAttrs | FILE_ATTRIBUTE_HIDDEN));
2306 #endif
2309 #ifdef QBT_USES_LIBTORRENT2
2310 else if (oldActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2312 if (newActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2313 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath);
2315 #else
2316 else
2318 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath / Path(UNWANTED_FOLDER_NAME));
2320 #endif
2322 else
2324 m_filePaths[fileIndex] = newFilePath;
2326 // Remove empty leftover folders
2327 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2328 // be removed if they are empty
2329 Path oldParentPath = oldFilePath.parentPath();
2330 const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath());
2331 while (oldParentPath != commonBasePath)
2333 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath);
2334 oldParentPath = oldParentPath.parentPath();
2338 --m_renameCount;
2339 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2340 m_moveFinishedTriggers.takeFirst()();
2342 deferredRequestResumeData();
2345 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
2347 const int fileIndex = m_indexMap.value(p->index, -1);
2348 Q_ASSERT(fileIndex >= 0);
2350 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2351 .arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
2353 --m_renameCount;
2354 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2355 m_moveFinishedTriggers.takeFirst()();
2357 deferredRequestResumeData();
2360 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
2362 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2363 return;
2365 const int fileIndex = m_indexMap.value(p->index, -1);
2366 Q_ASSERT(fileIndex >= 0);
2368 m_completedFiles.setBit(fileIndex);
2370 const Path actualPath = actualFilePath(fileIndex);
2372 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2373 // only apply Mark-of-the-Web to new download files
2374 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2376 const Path fullpath = actualStorageLocation() / actualPath;
2377 Utils::OS::applyMarkOfTheWeb(fullpath);
2379 #endif // Q_OS_MACOS || Q_OS_WIN
2381 if (m_session->isAppendExtensionEnabled())
2383 const Path path = filePath(fileIndex);
2384 if (actualPath != path)
2386 qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
2387 doRenameFile(fileIndex, path);
2392 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
2394 m_lastFileError = {p->error, p->op};
2397 #ifdef QBT_USES_LIBTORRENT2
2398 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
2400 deferredRequestResumeData();
2402 #endif
2404 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused]] const lt::metadata_received_alert *p)
2406 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2408 #ifdef QBT_USES_LIBTORRENT2
2409 const InfoHash prevInfoHash = infoHash();
2410 m_infoHash = InfoHash(m_nativeHandle.info_hashes());
2411 if (prevInfoHash != infoHash())
2412 m_session->handleTorrentInfoHashChanged(this, prevInfoHash);
2413 #endif
2415 m_maintenanceJob = MaintenanceJob::HandleMetadata;
2416 deferredRequestResumeData();
2419 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
2421 LogMsg((tr("Performance alert: %1. More info: %2").arg(QString::fromStdString(p->message()), u"https://libtorrent.org/reference-Alerts.html#enum-performance-warning-t"_s))
2422 , Log::INFO);
2425 void TorrentImpl::handleCategoryOptionsChanged()
2427 if (m_useAutoTMM)
2428 adjustStorageLocation();
2431 void TorrentImpl::handleAppendExtensionToggled()
2433 if (!hasMetadata())
2434 return;
2436 manageActualFilePaths();
2439 void TorrentImpl::handleUnwantedFolderToggled()
2441 if (!hasMetadata())
2442 return;
2444 manageActualFilePaths();
2447 void TorrentImpl::handleAlert(const lt::alert *a)
2449 switch (a->type())
2451 #ifdef QBT_USES_LIBTORRENT2
2452 case lt::file_prio_alert::alert_type:
2453 handleFilePrioAlert(static_cast<const lt::file_prio_alert*>(a));
2454 break;
2455 #endif
2456 case lt::file_renamed_alert::alert_type:
2457 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
2458 break;
2459 case lt::file_rename_failed_alert::alert_type:
2460 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
2461 break;
2462 case lt::file_completed_alert::alert_type:
2463 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
2464 break;
2465 case lt::file_error_alert::alert_type:
2466 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
2467 break;
2468 case lt::torrent_finished_alert::alert_type:
2469 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
2470 break;
2471 case lt::save_resume_data_alert::alert_type:
2472 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
2473 break;
2474 case lt::save_resume_data_failed_alert::alert_type:
2475 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
2476 break;
2477 case lt::torrent_paused_alert::alert_type:
2478 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
2479 break;
2480 case lt::torrent_resumed_alert::alert_type:
2481 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
2482 break;
2483 case lt::metadata_received_alert::alert_type:
2484 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2485 break;
2486 case lt::fastresume_rejected_alert::alert_type:
2487 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2488 break;
2489 case lt::torrent_checked_alert::alert_type:
2490 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2491 break;
2492 case lt::performance_alert::alert_type:
2493 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2494 break;
2498 void TorrentImpl::manageActualFilePaths()
2500 const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
2501 const lt::file_storage &nativeFiles = nativeInfo->files();
2503 for (int i = 0; i < filesCount(); ++i)
2505 const Path path = filePath(i);
2507 const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
2508 const Path actualPath {nativeFiles.file_path(nativeIndex)};
2509 const Path targetActualPath = makeActualPath(i, path);
2510 if (actualPath != targetActualPath)
2512 qDebug() << "Renaming" << actualPath.toString() << "to" << targetActualPath.toString();
2513 doRenameFile(i, targetActualPath);
2518 void TorrentImpl::adjustStorageLocation()
2520 const Path downloadPath = this->downloadPath();
2521 const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
2523 if ((targetPath != actualStorageLocation()) || isMoveInProgress())
2524 moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
2527 void TorrentImpl::doRenameFile(const int index, const Path &path)
2529 const QList<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
2531 Q_ASSERT(index >= 0);
2532 Q_ASSERT(index < nativeIndexes.size());
2533 if ((index < 0) || (index >= nativeIndexes.size())) [[unlikely]]
2534 return;
2536 ++m_renameCount;
2537 m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
2540 lt::torrent_handle TorrentImpl::nativeHandle() const
2542 return m_nativeHandle;
2545 void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
2547 if (hasMetadata())
2548 return;
2550 m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
2554 #ifdef QBT_USES_LIBTORRENT2
2555 nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
2556 #else
2557 const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
2558 nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
2559 #endif
2561 catch (const std::exception &) {}
2565 Torrent::StopCondition TorrentImpl::stopCondition() const
2567 return m_stopCondition;
2570 void TorrentImpl::setStopCondition(const StopCondition stopCondition)
2572 if (stopCondition == m_stopCondition)
2573 return;
2575 if (isStopped())
2576 return;
2578 if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata())
2579 return;
2581 if ((stopCondition == StopCondition::FilesChecked) && hasMetadata() && !isChecking())
2582 return;
2584 m_stopCondition = stopCondition;
2587 SSLParameters TorrentImpl::getSSLParameters() const
2589 return m_sslParams;
2592 void TorrentImpl::setSSLParameters(const SSLParameters &sslParams)
2594 if (sslParams == getSSLParameters())
2595 return;
2597 m_sslParams = sslParams;
2598 applySSLParameters();
2600 deferredRequestResumeData();
2603 bool TorrentImpl::applySSLParameters()
2605 if (!m_sslParams.isValid())
2606 return false;
2608 m_nativeHandle.set_ssl_certificate_buffer(m_sslParams.certificate.toPem().toStdString()
2609 , m_sslParams.privateKey.toPem().toStdString(), m_sslParams.dhParams.toStdString());
2610 return true;
2613 bool TorrentImpl::isMoveInProgress() const
2615 return m_storageIsMoving;
2618 void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
2620 const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
2622 if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
2623 updateProgress();
2625 updateState();
2627 m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
2628 , nativeStatus.upload_payload_rate});
2630 if (hasMetadata())
2632 // NOTE: Don't change the order of these conditionals!
2633 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2634 if (isChecking())
2635 m_unchecked = false;
2636 else if (isDownloading())
2637 m_unchecked = true;
2640 while (!m_statusUpdatedTriggers.isEmpty())
2641 std::invoke(m_statusUpdatedTriggers.dequeue());
2644 void TorrentImpl::updateProgress()
2646 Q_ASSERT(hasMetadata());
2647 if (!hasMetadata()) [[unlikely]]
2648 return;
2650 Q_ASSERT(!m_filesProgress.isEmpty());
2651 if (m_filesProgress.isEmpty()) [[unlikely]]
2652 m_filesProgress.resize(filesCount());
2654 const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
2655 const QBitArray newPieces = m_pieces ^ oldPieces;
2657 const int64_t pieceSize = m_torrentInfo.pieceLength();
2658 for (qsizetype index = 0; index < newPieces.size(); ++index)
2660 if (!newPieces.at(index))
2661 continue;
2663 int64_t size = m_torrentInfo.pieceLength(index);
2664 int64_t pieceOffset = index * pieceSize;
2666 for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
2668 const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
2669 const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
2671 m_filesProgress[fileIndex] += add;
2673 size -= add;
2674 if (size <= 0)
2675 break;
2677 pieceOffset += add;
2682 void TorrentImpl::setRatioLimit(qreal limit)
2684 if (limit < USE_GLOBAL_RATIO)
2685 limit = NO_RATIO_LIMIT;
2686 else if (limit > MAX_RATIO)
2687 limit = MAX_RATIO;
2689 if (m_ratioLimit != limit)
2691 m_ratioLimit = limit;
2692 deferredRequestResumeData();
2693 m_session->handleTorrentShareLimitChanged(this);
2697 void TorrentImpl::setSeedingTimeLimit(int limit)
2699 if (limit < USE_GLOBAL_SEEDING_TIME)
2700 limit = NO_SEEDING_TIME_LIMIT;
2701 else if (limit > MAX_SEEDING_TIME)
2702 limit = MAX_SEEDING_TIME;
2704 if (m_seedingTimeLimit != limit)
2706 m_seedingTimeLimit = limit;
2707 deferredRequestResumeData();
2708 m_session->handleTorrentShareLimitChanged(this);
2712 void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
2714 if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
2715 limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
2716 else if (limit > MAX_INACTIVE_SEEDING_TIME)
2717 limit = MAX_SEEDING_TIME;
2719 if (m_inactiveSeedingTimeLimit != limit)
2721 m_inactiveSeedingTimeLimit = limit;
2722 deferredRequestResumeData();
2723 m_session->handleTorrentShareLimitChanged(this);
2727 ShareLimitAction TorrentImpl::shareLimitAction() const
2729 return m_shareLimitAction;
2732 void TorrentImpl::setShareLimitAction(const ShareLimitAction action)
2734 if (m_shareLimitAction != action)
2736 m_shareLimitAction = action;
2737 deferredRequestResumeData();
2738 m_session->handleTorrentShareLimitChanged(this);
2742 void TorrentImpl::setUploadLimit(const int limit)
2744 const int cleanValue = cleanLimitValue(limit);
2745 if (cleanValue == uploadLimit())
2746 return;
2748 m_uploadLimit = cleanValue;
2749 m_nativeHandle.set_upload_limit(m_uploadLimit);
2750 deferredRequestResumeData();
2753 void TorrentImpl::setDownloadLimit(const int limit)
2755 const int cleanValue = cleanLimitValue(limit);
2756 if (cleanValue == downloadLimit())
2757 return;
2759 m_downloadLimit = cleanValue;
2760 m_nativeHandle.set_download_limit(m_downloadLimit);
2761 deferredRequestResumeData();
2764 void TorrentImpl::setSuperSeeding(const bool enable)
2766 if (enable == superSeeding())
2767 return;
2769 if (enable)
2770 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2771 else
2772 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2774 deferredRequestResumeData();
2777 void TorrentImpl::setDHTDisabled(const bool disable)
2779 if (disable == isDHTDisabled())
2780 return;
2782 if (disable)
2783 m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
2784 else
2785 m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
2787 deferredRequestResumeData();
2790 void TorrentImpl::setPEXDisabled(const bool disable)
2792 if (disable == isPEXDisabled())
2793 return;
2795 if (disable)
2796 m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
2797 else
2798 m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
2800 deferredRequestResumeData();
2803 void TorrentImpl::setLSDDisabled(const bool disable)
2805 if (disable == isLSDDisabled())
2806 return;
2808 if (disable)
2809 m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
2810 else
2811 m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
2813 deferredRequestResumeData();
2816 void TorrentImpl::flushCache() const
2818 m_nativeHandle.flush_cache();
2821 QString TorrentImpl::createMagnetURI() const
2823 QString ret = u"magnet:?"_s;
2825 const SHA1Hash infoHash1 = infoHash().v1();
2826 if (infoHash1.isValid())
2828 ret += u"xt=urn:btih:" + infoHash1.toString();
2831 const SHA256Hash infoHash2 = infoHash().v2();
2832 if (infoHash2.isValid())
2834 if (infoHash1.isValid())
2835 ret += u'&';
2836 ret += u"xt=urn:btmh:1220" + infoHash2.toString();
2839 const QString displayName = name();
2840 if (displayName != id().toString())
2842 ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
2845 for (const TrackerEntryStatus &tracker : asConst(trackers()))
2847 ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
2850 for (const QUrl &urlSeed : asConst(urlSeeds()))
2852 ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
2855 return ret;
2858 nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
2860 if (!hasMetadata())
2861 return nonstd::make_unexpected(tr("Missing metadata"));
2865 #ifdef QBT_USES_LIBTORRENT2
2866 const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
2867 const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
2868 #else
2869 const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
2870 #endif
2871 lt::create_torrent creator {*torrentInfo};
2873 for (const TrackerEntryStatus &status : asConst(trackers()))
2874 creator.add_tracker(status.url.toStdString(), status.tier);
2876 return creator.generate();
2878 catch (const lt::system_error &err)
2880 return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
2884 nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
2886 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2887 if (!preparationResult)
2888 return preparationResult.get_unexpected();
2890 // usually torrent size should be smaller than 1 MB,
2891 // however there are >100 MB v2/hybrid torrent files out in the wild
2892 QByteArray buffer;
2893 buffer.reserve(1024 * 1024);
2894 lt::bencode(std::back_inserter(buffer), preparationResult.value());
2895 return buffer;
2898 nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) const
2900 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2901 if (!preparationResult)
2902 return preparationResult.get_unexpected();
2904 const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
2905 if (!saveResult)
2906 return saveResult.get_unexpected();
2908 return {};
2911 void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const
2913 invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
2917 std::vector<lt::peer_info> nativePeers;
2918 nativeHandle.get_peer_info(nativePeers);
2919 QList<PeerInfo> peers;
2920 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
2921 for (const lt::peer_info &peer : nativePeers)
2922 peers.append(PeerInfo(peer, allPieces));
2923 return peers;
2925 catch (const std::exception &) {}
2927 return {};
2929 , std::move(resultHandler));
2932 void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const
2934 invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
2938 const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
2939 QList<QUrl> urlSeeds;
2940 urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
2941 for (const std::string &urlSeed : currentSeeds)
2942 urlSeeds.append(QString::fromStdString(urlSeed));
2943 return urlSeeds;
2945 catch (const std::exception &) {}
2947 return {};
2949 , std::move(resultHandler));
2952 void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const
2954 invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
2958 std::vector<int> piecesAvailability;
2959 nativeHandle.piece_availability(piecesAvailability);
2960 return QList<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
2962 catch (const std::exception &) {}
2964 return {};
2966 , std::move(resultHandler));
2969 void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
2971 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
2975 #ifdef QBT_USES_LIBTORRENT2
2976 const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
2977 #else
2978 std::vector<lt::partial_piece_info> queue;
2979 nativeHandle.get_download_queue(queue);
2980 #endif
2981 QBitArray result;
2982 result.resize(torrentInfo.piecesCount());
2983 for (const lt::partial_piece_info &info : queue)
2984 result.setBit(LT::toUnderlyingType(info.piece_index));
2985 return result;
2987 catch (const std::exception &) {}
2989 return {};
2991 , std::move(resultHandler));
2994 void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const
2996 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
2998 if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
2999 return {};
3003 std::vector<int> piecesAvailability;
3004 nativeHandle.piece_availability(piecesAvailability);
3005 const int filesCount = torrentInfo.filesCount();
3006 // libtorrent returns empty array for seeding only torrents
3007 if (piecesAvailability.empty())
3008 return QList<qreal>(filesCount, -1);
3010 QList<qreal> result;
3011 result.reserve(filesCount);
3012 for (int i = 0; i < filesCount; ++i)
3014 const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
3016 int availablePieces = 0;
3017 for (const int piece : filePieces)
3018 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
3020 const qreal availability = filePieces.isEmpty()
3021 ? 1 // the file has no pieces, so it is available by default
3022 : static_cast<qreal>(availablePieces) / filePieces.size();
3023 result.append(availability);
3025 return result;
3027 catch (const std::exception &) {}
3029 return {};
3031 , std::move(resultHandler));
3034 void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
3036 if (!hasMetadata())
3037 return;
3039 Q_ASSERT(priorities.size() == filesCount());
3041 // Reset 'm_hasSeedStatus' if needed in order to react again to
3042 // 'torrent_finished_alert' and eg show tray notifications
3043 const QList<DownloadPriority> oldPriorities = filePriorities();
3044 for (int i = 0; i < oldPriorities.size(); ++i)
3046 if ((oldPriorities[i] == DownloadPriority::Ignored)
3047 && (priorities[i] > DownloadPriority::Ignored)
3048 && !m_completedFiles.at(i))
3050 m_hasFinishedStatus = false;
3051 break;
3055 const int internalFilesCount = m_torrentInfo.nativeInfo()->files().num_files(); // including .pad files
3056 auto nativePriorities = std::vector<lt::download_priority_t>(internalFilesCount, LT::toNative(DownloadPriority::Normal));
3057 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
3058 for (int i = 0; i < priorities.size(); ++i)
3059 nativePriorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(priorities[i]);
3061 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
3062 m_nativeHandle.prioritize_files(nativePriorities);
3064 m_filePriorities = priorities;
3065 // Restore first/last piece first option if necessary
3066 if (m_hasFirstLastPiecePriority)
3067 applyFirstLastPiecePriority(true);
3068 manageActualFilePaths();
3071 QList<qreal> TorrentImpl::availableFileFractions() const
3073 Q_ASSERT(hasMetadata());
3075 const int filesCount = this->filesCount();
3076 if (filesCount <= 0) return {};
3078 const QList<int> piecesAvailability = pieceAvailability();
3079 // libtorrent returns empty array for seeding only torrents
3080 if (piecesAvailability.empty()) return QList<qreal>(filesCount, -1);
3082 QList<qreal> res;
3083 res.reserve(filesCount);
3084 for (int i = 0; i < filesCount; ++i)
3086 const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
3088 int availablePieces = 0;
3089 for (const int piece : filePieces)
3090 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
3092 const qreal availability = filePieces.isEmpty()
3093 ? 1 // the file has no pieces, so it is available by default
3094 : static_cast<qreal>(availablePieces) / filePieces.size();
3095 res.push_back(availability);
3097 return res;
3100 template <typename Func, typename Callback>
3101 void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
3103 m_session->invokeAsync([session = m_session
3104 , func = std::move(func)
3105 , resultHandler = std::move(resultHandler)
3106 , thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
3108 session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
3110 if (thisTorrent)
3111 resultHandler(result);