Fix Incomplete Save Path cannot be changed for torrents without metadata
[qBittorrent.git] / src / base / bittorrent / torrentimpl.cpp
blob6d7ce0e3602ddbc1881cd611137e375b664b0a47
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 <QDebug>
53 #include <QPointer>
54 #include <QSet>
55 #include <QStringList>
56 #include <QUrl>
58 #include "base/exceptions.h"
59 #include "base/global.h"
60 #include "base/logger.h"
61 #include "base/preferences.h"
62 #include "base/types.h"
63 #include "base/utils/fs.h"
64 #include "base/utils/io.h"
65 #include "common.h"
66 #include "downloadpriority.h"
67 #include "extensiondata.h"
68 #include "loadtorrentparams.h"
69 #include "ltqbitarray.h"
70 #include "lttypecast.h"
71 #include "peeraddress.h"
72 #include "peerinfo.h"
73 #include "sessionimpl.h"
74 #include "trackerentry.h"
76 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
77 #include "base/utils/os.h"
78 #endif // Q_OS_MACOS || Q_OS_WIN
80 #ifndef QBT_USES_LIBTORRENT2
81 #include "customstorage.h"
82 #endif
84 using namespace BitTorrent;
86 namespace
88 lt::announce_entry makeNativeAnnounceEntry(const QString &url, const int tier)
90 lt::announce_entry entry {url.toStdString()};
91 entry.tier = tier;
92 return entry;
95 QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint)
97 const auto ltNow = lt::clock_type::now();
98 const auto qNow = QDateTime::currentDateTime();
99 const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - ltNow + lt::milliseconds(500)).count();
101 return qNow.addSecs(secsSinceNow);
104 QString toString(const lt::tcp::endpoint &ltTCPEndpoint)
106 return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str());
109 void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry
110 , const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
112 Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url));
114 trackerEntryStatus.tier = nativeEntry.tier;
116 // remove outdated endpoints
117 trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointStatus>::iterator &iter)
119 return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
120 , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
122 return (endpointName == toString(existingEndpoint.local_endpoint));
126 const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * btProtocols.size();
128 int numUpdating = 0;
129 int numWorking = 0;
130 int numNotWorking = 0;
131 int numTrackerError = 0;
132 int numUnreachable = 0;
134 for (const lt::announce_endpoint &ltAnnounceEndpoint : nativeEntry.endpoints)
136 const auto endpointName = toString(ltAnnounceEndpoint.local_endpoint);
138 for (const auto protocolVersion : btProtocols)
140 #ifdef QBT_USES_LIBTORRENT2
141 Q_ASSERT((protocolVersion == 1) || (protocolVersion == 2));
142 const auto ltProtocolVersion = (protocolVersion == 1) ? lt::protocol_version::V1 : lt::protocol_version::V2;
143 const lt::announce_infohash &ltAnnounceInfo = ltAnnounceEndpoint.info_hashes[ltProtocolVersion];
144 #else
145 Q_ASSERT(protocolVersion == 1);
146 const lt::announce_endpoint &ltAnnounceInfo = ltAnnounceEndpoint;
147 #endif
148 const QMap<int, int> &endpointUpdateInfo = updateInfo[ltAnnounceEndpoint.local_endpoint];
149 TrackerEndpointStatus &trackerEndpointStatus = trackerEntryStatus.endpoints[std::make_pair(endpointName, protocolVersion)];
151 trackerEndpointStatus.name = endpointName;
152 trackerEndpointStatus.btVersion = protocolVersion;
153 trackerEndpointStatus.numPeers = endpointUpdateInfo.value(protocolVersion, trackerEndpointStatus.numPeers);
154 trackerEndpointStatus.numSeeds = ltAnnounceInfo.scrape_complete;
155 trackerEndpointStatus.numLeeches = ltAnnounceInfo.scrape_incomplete;
156 trackerEndpointStatus.numDownloaded = ltAnnounceInfo.scrape_downloaded;
157 trackerEndpointStatus.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
158 trackerEndpointStatus.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
160 if (ltAnnounceInfo.updating)
162 trackerEndpointStatus.state = TrackerEndpointState::Updating;
163 ++numUpdating;
165 else if (ltAnnounceInfo.fails > 0)
167 if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
169 trackerEndpointStatus.state = TrackerEndpointState::TrackerError;
170 ++numTrackerError;
172 else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
174 trackerEndpointStatus.state = TrackerEndpointState::Unreachable;
175 ++numUnreachable;
177 else
179 trackerEndpointStatus.state = TrackerEndpointState::NotWorking;
180 ++numNotWorking;
183 else if (nativeEntry.verified)
185 trackerEndpointStatus.state = TrackerEndpointState::Working;
186 ++numWorking;
188 else
190 trackerEndpointStatus.state = TrackerEndpointState::NotContacted;
193 if (!ltAnnounceInfo.message.empty())
195 trackerEndpointStatus.message = QString::fromStdString(ltAnnounceInfo.message);
197 else if (ltAnnounceInfo.last_error)
199 trackerEndpointStatus.message = QString::fromLocal8Bit(ltAnnounceInfo.last_error.message());
201 else
203 trackerEndpointStatus.message.clear();
208 if (numEndpoints > 0)
210 if (numUpdating > 0)
212 trackerEntryStatus.state = TrackerEndpointState::Updating;
214 else if (numWorking > 0)
216 trackerEntryStatus.state = TrackerEndpointState::Working;
218 else if (numTrackerError > 0)
220 trackerEntryStatus.state = TrackerEndpointState::TrackerError;
222 else if (numUnreachable == numEndpoints)
224 trackerEntryStatus.state = TrackerEndpointState::Unreachable;
226 else if ((numUnreachable + numNotWorking) == numEndpoints)
228 trackerEntryStatus.state = TrackerEndpointState::NotWorking;
232 trackerEntryStatus.numPeers = -1;
233 trackerEntryStatus.numSeeds = -1;
234 trackerEntryStatus.numLeeches = -1;
235 trackerEntryStatus.numDownloaded = -1;
236 trackerEntryStatus.nextAnnounceTime = QDateTime();
237 trackerEntryStatus.minAnnounceTime = QDateTime();
238 trackerEntryStatus.message.clear();
240 for (const TrackerEndpointStatus &endpointStatus : asConst(trackerEntryStatus.endpoints))
242 trackerEntryStatus.numPeers = std::max(trackerEntryStatus.numPeers, endpointStatus.numPeers);
243 trackerEntryStatus.numSeeds = std::max(trackerEntryStatus.numSeeds, endpointStatus.numSeeds);
244 trackerEntryStatus.numLeeches = std::max(trackerEntryStatus.numLeeches, endpointStatus.numLeeches);
245 trackerEntryStatus.numDownloaded = std::max(trackerEntryStatus.numDownloaded, endpointStatus.numDownloaded);
247 if (endpointStatus.state == trackerEntryStatus.state)
249 if (!trackerEntryStatus.nextAnnounceTime.isValid() || (trackerEntryStatus.nextAnnounceTime > endpointStatus.nextAnnounceTime))
251 trackerEntryStatus.nextAnnounceTime = endpointStatus.nextAnnounceTime;
252 trackerEntryStatus.minAnnounceTime = endpointStatus.minAnnounceTime;
253 if ((endpointStatus.state != TrackerEndpointState::Working)
254 || !endpointStatus.message.isEmpty())
256 trackerEntryStatus.message = endpointStatus.message;
260 if (endpointStatus.state == TrackerEndpointState::Working)
262 if (trackerEntryStatus.message.isEmpty())
263 trackerEntryStatus.message = endpointStatus.message;
269 template <typename Vector>
270 Vector resized(const Vector &inVector, const typename Vector::size_type size, const typename Vector::value_type &defaultValue)
272 Vector outVector = inVector;
273 outVector.resize(size, defaultValue);
274 return outVector;
277 // This is an imitation of limit normalization performed by libtorrent itself.
278 // We need perform it to keep cached values in line with the ones used by libtorrent.
279 int cleanLimitValue(const int value)
281 return ((value < 0) || (value == std::numeric_limits<int>::max())) ? 0 : value;
285 // TorrentImpl
287 TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
288 , const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
289 : Torrent(session)
290 , m_session(session)
291 , m_nativeSession(nativeSession)
292 , m_nativeHandle(nativeHandle)
293 #ifdef QBT_USES_LIBTORRENT2
294 , m_infoHash(m_nativeHandle.info_hashes())
295 #else
296 , m_infoHash(m_nativeHandle.info_hash())
297 #endif
298 , m_name(params.name)
299 , m_savePath(params.savePath)
300 , m_downloadPath(params.downloadPath)
301 , m_category(params.category)
302 , m_tags(params.tags)
303 , m_ratioLimit(params.ratioLimit)
304 , m_seedingTimeLimit(params.seedingTimeLimit)
305 , m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit)
306 , m_shareLimitAction(params.shareLimitAction)
307 , m_operatingMode(params.operatingMode)
308 , m_contentLayout(params.contentLayout)
309 , m_hasFinishedStatus(params.hasFinishedStatus)
310 , m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
311 , m_useAutoTMM(params.useAutoTMM)
312 , m_isStopped(params.stopped)
313 , m_sslParams(params.sslParameters)
314 , m_ltAddTorrentParams(params.ltAddTorrentParams)
315 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit))
316 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit))
318 if (m_ltAddTorrentParams.ti)
320 // Initialize it only if torrent is added with metadata.
321 // Otherwise it should be initialized in "Metadata received" handler.
322 m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
324 Q_ASSERT(m_filePaths.isEmpty());
325 Q_ASSERT(m_indexMap.isEmpty());
326 const int filesCount = m_torrentInfo.filesCount();
327 m_filePaths.reserve(filesCount);
328 m_indexMap.reserve(filesCount);
329 m_filePriorities.reserve(filesCount);
330 const std::vector<lt::download_priority_t> filePriorities =
331 resized(m_ltAddTorrentParams.file_priorities, m_ltAddTorrentParams.ti->num_files()
332 , LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
334 m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
335 m_filesProgress.resize(filesCount);
337 for (int i = 0; i < filesCount; ++i)
339 const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
340 m_indexMap[nativeIndex] = i;
342 const auto fileIter = m_ltAddTorrentParams.renamed_files.find(nativeIndex);
343 const Path filePath = ((fileIter != m_ltAddTorrentParams.renamed_files.end())
344 ? makeUserPath(Path(fileIter->second)) : m_torrentInfo.filePath(i));
345 m_filePaths.append(filePath);
347 const auto priority = LT::fromNative(filePriorities[LT::toUnderlyingType(nativeIndex)]);
348 m_filePriorities.append(priority);
352 setStopCondition(params.stopCondition);
354 const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
355 m_trackerEntryStatuses.reserve(static_cast<decltype(m_trackerEntryStatuses)::size_type>(extensionData->trackers.size()));
356 for (const lt::announce_entry &announceEntry : extensionData->trackers)
357 m_trackerEntryStatuses.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
358 m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
359 for (const std::string &urlSeed : extensionData->urlSeeds)
360 m_urlSeeds.append(QString::fromStdString(urlSeed));
361 m_nativeStatus = extensionData->status;
363 if (hasMetadata())
364 updateProgress();
366 updateState();
368 if (hasMetadata())
369 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
372 TorrentImpl::~TorrentImpl() = default;
374 bool TorrentImpl::isValid() const
376 return m_nativeHandle.is_valid();
379 Session *TorrentImpl::session() const
381 return m_session;
384 InfoHash TorrentImpl::infoHash() const
386 return m_infoHash;
389 QString TorrentImpl::name() const
391 if (!m_name.isEmpty())
392 return m_name;
394 if (hasMetadata())
395 return m_torrentInfo.name();
397 const QString name = QString::fromStdString(m_nativeStatus.name);
398 if (!name.isEmpty())
399 return name;
401 return id().toString();
404 QDateTime TorrentImpl::creationDate() const
406 if (!hasMetadata())
407 return {};
409 const std::time_t date = nativeTorrentInfo()->creation_date();
410 return ((date != 0) ? QDateTime::fromSecsSinceEpoch(date) : QDateTime());
413 QString TorrentImpl::creator() const
415 if (!hasMetadata())
416 return {};
418 return QString::fromStdString(nativeTorrentInfo()->creator());
421 QString TorrentImpl::comment() const
423 if (!hasMetadata())
424 return {};
426 return QString::fromStdString(nativeTorrentInfo()->comment());
429 bool TorrentImpl::isPrivate() const
431 return m_torrentInfo.isPrivate();
434 qlonglong TorrentImpl::totalSize() const
436 return m_torrentInfo.totalSize();
439 // size without the "don't download" files
440 qlonglong TorrentImpl::wantedSize() const
442 return m_nativeStatus.total_wanted;
445 qlonglong TorrentImpl::completedSize() const
447 return m_nativeStatus.total_wanted_done;
450 qlonglong TorrentImpl::pieceLength() const
452 return m_torrentInfo.pieceLength();
455 qlonglong TorrentImpl::wastedSize() const
457 return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes);
460 QString TorrentImpl::currentTracker() const
462 return QString::fromStdString(m_nativeStatus.current_tracker);
465 Path TorrentImpl::savePath() const
467 return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath;
470 void TorrentImpl::setSavePath(const Path &path)
472 Q_ASSERT(!isAutoTMMEnabled());
473 if (isAutoTMMEnabled()) [[unlikely]]
474 return;
476 const Path basePath = m_session->useCategoryPathsInManualMode()
477 ? m_session->categorySavePath(category()) : m_session->savePath();
478 const Path resolvedPath = (path.isAbsolute() ? path : (basePath / path));
479 if (resolvedPath == savePath())
480 return;
482 if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
484 moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
486 else
488 m_savePath = resolvedPath;
489 m_session->handleTorrentSavePathChanged(this);
490 deferredRequestResumeData();
494 Path TorrentImpl::downloadPath() const
496 return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath;
499 void TorrentImpl::setDownloadPath(const Path &path)
501 Q_ASSERT(!isAutoTMMEnabled());
502 if (isAutoTMMEnabled()) [[unlikely]]
503 return;
505 const Path basePath = m_session->useCategoryPathsInManualMode()
506 ? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
507 const Path resolvedPath = (path.isEmpty() || path.isAbsolute()) ? path : (basePath / path);
508 if (resolvedPath == m_downloadPath)
509 return;
511 const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
512 if (isIncomplete)
514 moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
516 else
518 m_downloadPath = resolvedPath;
519 m_session->handleTorrentSavePathChanged(this);
520 deferredRequestResumeData();
524 Path TorrentImpl::rootPath() const
526 if (!hasMetadata())
527 return {};
529 const Path relativeRootPath = Path::findRootFolder(filePaths());
530 if (relativeRootPath.isEmpty())
531 return {};
533 return (actualStorageLocation() / relativeRootPath);
536 Path TorrentImpl::contentPath() const
538 if (!hasMetadata())
539 return {};
541 if (filesCount() == 1)
542 return (actualStorageLocation() / filePath(0));
544 const Path rootPath = this->rootPath();
545 return (rootPath.isEmpty() ? actualStorageLocation() : rootPath);
548 bool TorrentImpl::isAutoTMMEnabled() const
550 return m_useAutoTMM;
553 void TorrentImpl::setAutoTMMEnabled(bool enabled)
555 if (m_useAutoTMM == enabled)
556 return;
558 m_useAutoTMM = enabled;
559 if (!m_useAutoTMM)
561 m_savePath = m_session->categorySavePath(category());
562 m_downloadPath = m_session->categoryDownloadPath(category());
565 deferredRequestResumeData();
566 m_session->handleTorrentSavingModeChanged(this);
568 adjustStorageLocation();
571 Path TorrentImpl::actualStorageLocation() const
573 if (!hasMetadata())
574 return {};
576 return Path(m_nativeStatus.save_path);
579 void TorrentImpl::setAutoManaged(const bool enable)
581 if (enable)
582 m_nativeHandle.set_flags(lt::torrent_flags::auto_managed);
583 else
584 m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
587 Path TorrentImpl::makeActualPath(int index, const Path &path) const
589 Path actualPath = path;
591 if (m_session->isAppendExtensionEnabled()
592 && (fileSize(index) > 0) && !m_completedFiles.at(index))
594 actualPath += QB_EXT;
597 if (m_session->isUnwantedFolderEnabled()
598 && (m_filePriorities[index] == DownloadPriority::Ignored))
600 const Path parentPath = actualPath.parentPath();
601 const QString fileName = actualPath.filename();
602 actualPath = parentPath / Path(UNWANTED_FOLDER_NAME) / Path(fileName);
605 return actualPath;
608 Path TorrentImpl::makeUserPath(const Path &path) const
610 Path userPath = path.removedExtension(QB_EXT);
612 const Path parentRelPath = userPath.parentPath();
613 if (parentRelPath.filename() == UNWANTED_FOLDER_NAME)
615 const QString fileName = userPath.filename();
616 const Path relPath = parentRelPath.parentPath();
617 userPath = relPath / Path(fileName);
620 return userPath;
623 QList<TrackerEntryStatus> TorrentImpl::trackers() const
625 return m_trackerEntryStatuses;
628 void TorrentImpl::addTrackers(QList<TrackerEntry> trackers)
630 trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
632 QSet<TrackerEntry> currentTrackerSet;
633 currentTrackerSet.reserve(m_trackerEntryStatuses.size());
634 for (const TrackerEntryStatus &status : asConst(m_trackerEntryStatuses))
635 currentTrackerSet.insert({.url = status.url, .tier = status.tier});
637 const auto newTrackerSet = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend()) - currentTrackerSet;
638 if (newTrackerSet.isEmpty())
639 return;
641 trackers = QList<TrackerEntry>(newTrackerSet.cbegin(), newTrackerSet.cend());
642 for (const TrackerEntry &tracker : asConst(trackers))
644 m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
645 m_trackerEntryStatuses.append({tracker.url, tracker.tier});
647 std::sort(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
648 , [](const TrackerEntryStatus &left, const TrackerEntryStatus &right) { return left.tier < right.tier; });
650 deferredRequestResumeData();
651 m_session->handleTorrentTrackersAdded(this, trackers);
654 void TorrentImpl::removeTrackers(const QStringList &trackers)
656 QStringList removedTrackers = trackers;
657 for (const QString &tracker : trackers)
659 if (!m_trackerEntryStatuses.removeOne({tracker}))
660 removedTrackers.removeOne(tracker);
663 std::vector<lt::announce_entry> nativeTrackers;
664 nativeTrackers.reserve(m_trackerEntryStatuses.size());
665 for (const TrackerEntryStatus &tracker : asConst(m_trackerEntryStatuses))
666 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
668 if (!removedTrackers.isEmpty())
670 m_nativeHandle.replace_trackers(nativeTrackers);
672 deferredRequestResumeData();
673 m_session->handleTorrentTrackersRemoved(this, removedTrackers);
677 void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
679 trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
681 // Filter out duplicate trackers
682 const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
683 trackers = QList<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
684 std::sort(trackers.begin(), trackers.end()
685 , [](const TrackerEntry &left, const TrackerEntry &right) { return left.tier < right.tier; });
687 std::vector<lt::announce_entry> nativeTrackers;
688 nativeTrackers.reserve(trackers.size());
689 m_trackerEntryStatuses.clear();
691 for (const TrackerEntry &tracker : trackers)
693 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
694 m_trackerEntryStatuses.append({tracker.url, tracker.tier});
697 m_nativeHandle.replace_trackers(nativeTrackers);
699 // Clear the peer list if it's a private torrent since
700 // we do not want to keep connecting with peers from old tracker.
701 if (isPrivate())
702 clearPeers();
704 deferredRequestResumeData();
705 m_session->handleTorrentTrackersChanged(this);
708 QList<QUrl> TorrentImpl::urlSeeds() const
710 return m_urlSeeds;
713 void TorrentImpl::addUrlSeeds(const QList<QUrl> &urlSeeds)
715 m_session->invokeAsync([urlSeeds, session = m_session
716 , nativeHandle = m_nativeHandle
717 , thisTorrent = QPointer<TorrentImpl>(this)]
721 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
722 QList<QUrl> currentSeeds;
723 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
724 for (const std::string &urlSeed : nativeSeeds)
725 currentSeeds.append(QString::fromStdString(urlSeed));
727 QList<QUrl> addedUrlSeeds;
728 addedUrlSeeds.reserve(urlSeeds.size());
730 for (const QUrl &url : urlSeeds)
732 if (!currentSeeds.contains(url))
734 nativeHandle.add_url_seed(url.toString().toStdString());
735 addedUrlSeeds.append(url);
739 currentSeeds.append(addedUrlSeeds);
740 session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
742 if (!thisTorrent)
743 return;
745 thisTorrent->m_urlSeeds = currentSeeds;
746 if (!addedUrlSeeds.isEmpty())
748 thisTorrent->deferredRequestResumeData();
749 session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
753 catch (const std::exception &) {}
757 void TorrentImpl::removeUrlSeeds(const QList<QUrl> &urlSeeds)
759 m_session->invokeAsync([urlSeeds, session = m_session
760 , nativeHandle = m_nativeHandle
761 , thisTorrent = QPointer<TorrentImpl>(this)]
765 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
766 QList<QUrl> currentSeeds;
767 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
768 for (const std::string &urlSeed : nativeSeeds)
769 currentSeeds.append(QString::fromStdString(urlSeed));
771 QList<QUrl> removedUrlSeeds;
772 removedUrlSeeds.reserve(urlSeeds.size());
774 for (const QUrl &url : urlSeeds)
776 if (currentSeeds.removeOne(url))
778 nativeHandle.remove_url_seed(url.toString().toStdString());
779 removedUrlSeeds.append(url);
783 session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
785 if (!thisTorrent)
786 return;
788 thisTorrent->m_urlSeeds = currentSeeds;
790 if (!removedUrlSeeds.isEmpty())
792 thisTorrent->deferredRequestResumeData();
793 session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
797 catch (const std::exception &) {}
801 void TorrentImpl::clearPeers()
803 m_nativeHandle.clear_peers();
806 bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
808 lt::error_code ec;
809 const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec);
810 if (ec) return false;
812 const lt::tcp::endpoint endpoint(addr, peerAddress.port);
815 m_nativeHandle.connect_peer(endpoint);
817 catch (const lt::system_error &err)
819 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
820 .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
821 return false;
824 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name()));
825 return true;
828 bool TorrentImpl::needSaveResumeData() const
830 return m_nativeStatus.need_save_resume;
833 void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags)
835 m_nativeHandle.save_resume_data(flags);
836 m_deferredRequestResumeDataInvoked = false;
838 m_session->handleTorrentResumeDataRequested(this);
841 void TorrentImpl::deferredRequestResumeData()
843 if (!m_deferredRequestResumeDataInvoked)
845 QMetaObject::invokeMethod(this, [this]
847 requestResumeData((m_maintenanceJob == MaintenanceJob::HandleMetadata)
848 ? lt::torrent_handle::save_info_dict : lt::resume_data_flags_t());
849 }, Qt::QueuedConnection);
851 m_deferredRequestResumeDataInvoked = true;
855 int TorrentImpl::filesCount() const
857 return m_torrentInfo.filesCount();
860 int TorrentImpl::piecesCount() const
862 return m_torrentInfo.piecesCount();
865 int TorrentImpl::piecesHave() const
867 return m_nativeStatus.num_pieces;
870 qreal TorrentImpl::progress() const
872 if (isChecking())
873 return m_nativeStatus.progress;
875 if (m_nativeStatus.total_wanted == 0)
876 return 0.;
878 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
879 return 1.;
881 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
882 if ((progress < 0.f) || (progress > 1.f))
884 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
885 .arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
886 , Log::WARNING);
889 return progress;
892 QString TorrentImpl::category() const
894 return m_category;
897 bool TorrentImpl::belongsToCategory(const QString &category) const
899 if (m_category.isEmpty())
900 return category.isEmpty();
902 if (m_category == category)
903 return true;
905 return (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + u'/'));
908 TagSet TorrentImpl::tags() const
910 return m_tags;
913 bool TorrentImpl::hasTag(const Tag &tag) const
915 return m_tags.contains(tag);
918 bool TorrentImpl::addTag(const Tag &tag)
920 if (!tag.isValid())
921 return false;
922 if (hasTag(tag))
923 return false;
925 if (!m_session->hasTag(tag))
927 if (!m_session->addTag(tag))
928 return false;
930 m_tags.insert(tag);
931 deferredRequestResumeData();
932 m_session->handleTorrentTagAdded(this, tag);
933 return true;
936 bool TorrentImpl::removeTag(const Tag &tag)
938 if (m_tags.remove(tag))
940 deferredRequestResumeData();
941 m_session->handleTorrentTagRemoved(this, tag);
942 return true;
944 return false;
947 void TorrentImpl::removeAllTags()
949 for (const Tag &tag : asConst(tags()))
950 removeTag(tag);
953 QDateTime TorrentImpl::addedTime() const
955 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
958 qreal TorrentImpl::ratioLimit() const
960 return m_ratioLimit;
963 int TorrentImpl::seedingTimeLimit() const
965 return m_seedingTimeLimit;
968 int TorrentImpl::inactiveSeedingTimeLimit() const
970 return m_inactiveSeedingTimeLimit;
973 Path TorrentImpl::filePath(const int index) const
975 Q_ASSERT(index >= 0);
976 Q_ASSERT(index < m_filePaths.size());
978 return m_filePaths.value(index, {});
981 Path TorrentImpl::actualFilePath(const int index) const
983 const QList<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
985 Q_ASSERT(index >= 0);
986 Q_ASSERT(index < nativeIndexes.size());
987 if ((index < 0) || (index >= nativeIndexes.size()))
988 return {};
990 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes[index]));
993 qlonglong TorrentImpl::fileSize(const int index) const
995 return m_torrentInfo.fileSize(index);
998 PathList TorrentImpl::filePaths() const
1000 return m_filePaths;
1003 PathList TorrentImpl::actualFilePaths() const
1005 if (!hasMetadata())
1006 return {};
1008 PathList paths;
1009 paths.reserve(filesCount());
1011 const lt::file_storage files = nativeTorrentInfo()->files();
1012 for (const lt::file_index_t &nativeIndex : asConst(m_torrentInfo.nativeIndexes()))
1013 paths.emplaceBack(files.file_path(nativeIndex));
1015 return paths;
1018 QList<DownloadPriority> TorrentImpl::filePriorities() const
1020 return m_filePriorities;
1023 TorrentInfo TorrentImpl::info() const
1025 return m_torrentInfo;
1028 bool TorrentImpl::isStopped() const
1030 return m_isStopped;
1033 bool TorrentImpl::isQueued() const
1035 if (!m_session->isQueueingSystemEnabled())
1036 return false;
1038 // Torrent is Queued if it isn't in Stopped state but paused internally
1039 return (!isStopped()
1040 && (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
1041 && (m_nativeStatus.flags & lt::torrent_flags::paused));
1044 bool TorrentImpl::isChecking() const
1046 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
1047 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
1050 bool TorrentImpl::isDownloading() const
1052 switch (m_state)
1054 case TorrentState::Downloading:
1055 case TorrentState::DownloadingMetadata:
1056 case TorrentState::ForcedDownloadingMetadata:
1057 case TorrentState::StalledDownloading:
1058 case TorrentState::CheckingDownloading:
1059 case TorrentState::StoppedDownloading:
1060 case TorrentState::QueuedDownloading:
1061 case TorrentState::ForcedDownloading:
1062 return true;
1063 default:
1064 break;
1067 return false;
1070 bool TorrentImpl::isMoving() const
1072 return m_state == TorrentState::Moving;
1075 bool TorrentImpl::isUploading() const
1077 switch (m_state)
1079 case TorrentState::Uploading:
1080 case TorrentState::StalledUploading:
1081 case TorrentState::CheckingUploading:
1082 case TorrentState::QueuedUploading:
1083 case TorrentState::ForcedUploading:
1084 return true;
1085 default:
1086 break;
1089 return false;
1092 bool TorrentImpl::isCompleted() const
1094 switch (m_state)
1096 case TorrentState::Uploading:
1097 case TorrentState::StalledUploading:
1098 case TorrentState::CheckingUploading:
1099 case TorrentState::StoppedUploading:
1100 case TorrentState::QueuedUploading:
1101 case TorrentState::ForcedUploading:
1102 return true;
1103 default:
1104 break;
1107 return false;
1110 bool TorrentImpl::isActive() const
1112 switch (m_state)
1114 case TorrentState::StalledDownloading:
1115 return (uploadPayloadRate() > 0);
1117 case TorrentState::DownloadingMetadata:
1118 case TorrentState::ForcedDownloadingMetadata:
1119 case TorrentState::Downloading:
1120 case TorrentState::ForcedDownloading:
1121 case TorrentState::Uploading:
1122 case TorrentState::ForcedUploading:
1123 case TorrentState::Moving:
1124 return true;
1126 default:
1127 break;
1130 return false;
1133 bool TorrentImpl::isInactive() const
1135 return !isActive();
1138 bool TorrentImpl::isErrored() const
1140 return ((m_state == TorrentState::MissingFiles)
1141 || (m_state == TorrentState::Error));
1144 bool TorrentImpl::isFinished() const
1146 return ((m_nativeStatus.state == lt::torrent_status::finished)
1147 || (m_nativeStatus.state == lt::torrent_status::seeding));
1150 bool TorrentImpl::isForced() const
1152 return (!isStopped() && (m_operatingMode == TorrentOperatingMode::Forced));
1155 bool TorrentImpl::isSequentialDownload() const
1157 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::sequential_download);
1160 bool TorrentImpl::hasFirstLastPiecePriority() const
1162 return m_hasFirstLastPiecePriority;
1165 TorrentState TorrentImpl::state() const
1167 return m_state;
1170 void TorrentImpl::updateState()
1172 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
1174 m_state = TorrentState::CheckingResumeData;
1176 else if (isMoveInProgress())
1178 m_state = TorrentState::Moving;
1180 else if (hasMissingFiles())
1182 m_state = TorrentState::MissingFiles;
1184 else if (hasError())
1186 m_state = TorrentState::Error;
1188 else if (!hasMetadata())
1190 if (isStopped())
1191 m_state = TorrentState::StoppedDownloading;
1192 else if (isQueued())
1193 m_state = TorrentState::QueuedDownloading;
1194 else
1195 m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata;
1197 else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isStopped())
1199 // If the torrent is not just in the "checking" state, but is being actually checked
1200 m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
1202 else if (isFinished())
1204 if (isStopped())
1205 m_state = TorrentState::StoppedUploading;
1206 else if (isQueued())
1207 m_state = TorrentState::QueuedUploading;
1208 else if (isForced())
1209 m_state = TorrentState::ForcedUploading;
1210 else if (m_nativeStatus.upload_payload_rate > 0)
1211 m_state = TorrentState::Uploading;
1212 else
1213 m_state = TorrentState::StalledUploading;
1215 else
1217 if (isStopped())
1218 m_state = TorrentState::StoppedDownloading;
1219 else if (isQueued())
1220 m_state = TorrentState::QueuedDownloading;
1221 else if (isForced())
1222 m_state = TorrentState::ForcedDownloading;
1223 else if (m_nativeStatus.download_payload_rate > 0)
1224 m_state = TorrentState::Downloading;
1225 else
1226 m_state = TorrentState::StalledDownloading;
1230 bool TorrentImpl::hasMetadata() const
1232 return m_torrentInfo.isValid();
1235 bool TorrentImpl::hasMissingFiles() const
1237 return m_hasMissingFiles;
1240 bool TorrentImpl::hasError() const
1242 return (m_nativeStatus.errc || (m_nativeStatus.flags & lt::torrent_flags::upload_mode));
1245 int TorrentImpl::queuePosition() const
1247 return static_cast<int>(m_nativeStatus.queue_position);
1250 QString TorrentImpl::error() const
1252 if (m_nativeStatus.errc)
1253 return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
1255 if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
1257 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1258 .arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
1261 return {};
1264 qlonglong TorrentImpl::totalDownload() const
1266 return m_nativeStatus.all_time_download;
1269 qlonglong TorrentImpl::totalUpload() const
1271 return m_nativeStatus.all_time_upload;
1274 qlonglong TorrentImpl::activeTime() const
1276 return lt::total_seconds(m_nativeStatus.active_duration);
1279 qlonglong TorrentImpl::finishedTime() const
1281 return lt::total_seconds(m_nativeStatus.finished_duration);
1284 qlonglong TorrentImpl::eta() const
1286 if (isStopped()) return MAX_ETA;
1288 const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
1290 if (isFinished())
1292 const qreal maxRatioValue = maxRatio();
1293 const int maxSeedingTimeValue = maxSeedingTime();
1294 const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
1295 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
1297 qlonglong ratioEta = MAX_ETA;
1299 if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
1302 qlonglong realDL = totalDownload();
1303 if (realDL <= 0)
1304 realDL = wantedSize();
1306 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1309 qlonglong seedingTimeEta = MAX_ETA;
1311 if (maxSeedingTimeValue >= 0)
1313 seedingTimeEta = (maxSeedingTimeValue * 60) - finishedTime();
1314 if (seedingTimeEta < 0)
1315 seedingTimeEta = 0;
1318 qlonglong inactiveSeedingTimeEta = MAX_ETA;
1320 if (maxInactiveSeedingTimeValue >= 0)
1322 inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
1323 inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
1326 return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
1329 if (!speedAverage.download) return MAX_ETA;
1331 return (wantedSize() - completedSize()) / speedAverage.download;
1334 QList<qreal> TorrentImpl::filesProgress() const
1336 if (!hasMetadata())
1337 return {};
1339 const int count = m_filesProgress.size();
1340 Q_ASSERT(count == filesCount());
1341 if (count != filesCount()) [[unlikely]]
1342 return {};
1344 if (m_completedFiles.count(true) == count)
1345 return QList<qreal>(count, 1);
1347 QList<qreal> result;
1348 result.reserve(count);
1349 for (int i = 0; i < count; ++i)
1351 const int64_t progress = m_filesProgress.at(i);
1352 const int64_t size = fileSize(i);
1353 if ((size <= 0) || (progress == size))
1354 result << 1;
1355 else
1356 result << (progress / static_cast<qreal>(size));
1359 return result;
1362 int TorrentImpl::seedsCount() const
1364 return m_nativeStatus.num_seeds;
1367 int TorrentImpl::peersCount() const
1369 return m_nativeStatus.num_peers;
1372 int TorrentImpl::leechsCount() const
1374 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1377 int TorrentImpl::totalSeedsCount() const
1379 return (m_nativeStatus.num_complete > -1) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1382 int TorrentImpl::totalPeersCount() const
1384 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1385 return (peers > -1) ? peers : m_nativeStatus.list_peers;
1388 int TorrentImpl::totalLeechersCount() const
1390 return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1393 QDateTime TorrentImpl::lastSeenComplete() const
1395 if (m_nativeStatus.last_seen_complete > 0)
1396 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1397 else
1398 return {};
1401 QDateTime TorrentImpl::completedTime() const
1403 if (m_nativeStatus.completed_time > 0)
1404 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1405 else
1406 return {};
1409 qlonglong TorrentImpl::timeSinceUpload() const
1411 if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
1412 return -1;
1413 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1416 qlonglong TorrentImpl::timeSinceDownload() const
1418 if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
1419 return -1;
1420 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1423 qlonglong TorrentImpl::timeSinceActivity() const
1425 const qlonglong upTime = timeSinceUpload();
1426 const qlonglong downTime = timeSinceDownload();
1427 return ((upTime < 0) != (downTime < 0))
1428 ? std::max(upTime, downTime)
1429 : std::min(upTime, downTime);
1432 int TorrentImpl::downloadLimit() const
1434 return m_downloadLimit;;
1437 int TorrentImpl::uploadLimit() const
1439 return m_uploadLimit;
1442 bool TorrentImpl::superSeeding() const
1444 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::super_seeding);
1447 bool TorrentImpl::isDHTDisabled() const
1449 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_dht);
1452 bool TorrentImpl::isPEXDisabled() const
1454 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_pex);
1457 bool TorrentImpl::isLSDDisabled() const
1459 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
1462 QList<PeerInfo> TorrentImpl::peers() const
1464 std::vector<lt::peer_info> nativePeers;
1465 m_nativeHandle.get_peer_info(nativePeers);
1467 QList<PeerInfo> peers;
1468 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
1470 for (const lt::peer_info &peer : nativePeers)
1471 peers.append(PeerInfo(peer, pieces()));
1473 return peers;
1476 QBitArray TorrentImpl::pieces() const
1478 return m_pieces;
1481 QBitArray TorrentImpl::downloadingPieces() const
1483 if (!hasMetadata())
1484 return {};
1486 std::vector<lt::partial_piece_info> queue;
1487 m_nativeHandle.get_download_queue(queue);
1489 QBitArray result {piecesCount()};
1490 for (const lt::partial_piece_info &info : queue)
1491 result.setBit(LT::toUnderlyingType(info.piece_index));
1493 return result;
1496 QList<int> TorrentImpl::pieceAvailability() const
1498 std::vector<int> avail;
1499 m_nativeHandle.piece_availability(avail);
1501 return {avail.cbegin(), avail.cend()};
1504 qreal TorrentImpl::distributedCopies() const
1506 return m_nativeStatus.distributed_copies;
1509 qreal TorrentImpl::maxRatio() const
1511 if (m_ratioLimit == USE_GLOBAL_RATIO)
1512 return m_session->globalMaxRatio();
1514 return m_ratioLimit;
1517 int TorrentImpl::maxSeedingTime() const
1519 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1520 return m_session->globalMaxSeedingMinutes();
1522 return m_seedingTimeLimit;
1525 int TorrentImpl::maxInactiveSeedingTime() const
1527 if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
1528 return m_session->globalMaxInactiveSeedingMinutes();
1530 return m_inactiveSeedingTimeLimit;
1533 qreal TorrentImpl::realRatio() const
1535 const int64_t upload = m_nativeStatus.all_time_upload;
1536 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1537 const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01))
1538 ? m_nativeStatus.total_done
1539 : m_nativeStatus.all_time_download;
1541 if (download == 0)
1542 return (upload == 0) ? 0 : MAX_RATIO;
1544 const qreal ratio = upload / static_cast<qreal>(download);
1545 Q_ASSERT(ratio >= 0);
1546 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1549 int TorrentImpl::uploadPayloadRate() const
1551 // workaround: suppress the speed for Stopped state
1552 return isStopped() ? 0 : m_nativeStatus.upload_payload_rate;
1555 int TorrentImpl::downloadPayloadRate() const
1557 // workaround: suppress the speed for Stopped state
1558 return isStopped() ? 0 : m_nativeStatus.download_payload_rate;
1561 qlonglong TorrentImpl::totalPayloadUpload() const
1563 return m_nativeStatus.total_payload_upload;
1566 qlonglong TorrentImpl::totalPayloadDownload() const
1568 return m_nativeStatus.total_payload_download;
1571 int TorrentImpl::connectionsCount() const
1573 return m_nativeStatus.num_connections;
1576 int TorrentImpl::connectionsLimit() const
1578 return m_nativeStatus.connections_limit;
1581 qlonglong TorrentImpl::nextAnnounce() const
1583 return lt::total_seconds(m_nativeStatus.next_announce);
1586 qreal TorrentImpl::popularity() const
1588 // in order to produce floating-point numbers using `std::chrono::duration_cast`,
1589 // we should use `qreal` as `Rep` to define the `months` duration
1590 using months = std::chrono::duration<qreal, std::chrono::months::period>;
1591 const auto activeMonths = std::chrono::duration_cast<months>(m_nativeStatus.active_duration).count();
1592 return (activeMonths > 0) ? (realRatio() / activeMonths) : 0;
1595 void TorrentImpl::setName(const QString &name)
1597 if (m_name != name)
1599 m_name = name;
1600 deferredRequestResumeData();
1601 m_session->handleTorrentNameChanged(this);
1605 bool TorrentImpl::setCategory(const QString &category)
1607 if (m_category != category)
1609 if (!category.isEmpty() && !m_session->categories().contains(category))
1610 return false;
1612 const QString oldCategory = m_category;
1613 m_category = category;
1614 deferredRequestResumeData();
1615 m_session->handleTorrentCategoryChanged(this, oldCategory);
1617 if (m_useAutoTMM)
1619 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1620 adjustStorageLocation();
1621 else
1622 setAutoTMMEnabled(false);
1626 return true;
1629 void TorrentImpl::forceReannounce(const int index)
1631 m_nativeHandle.force_reannounce(0, index);
1634 void TorrentImpl::forceDHTAnnounce()
1636 m_nativeHandle.force_dht_announce();
1639 void TorrentImpl::forceRecheck()
1641 if (!hasMetadata())
1642 return;
1644 m_nativeHandle.force_recheck();
1645 // We have to force update the cached state, otherwise someone will be able to get
1646 // an incorrect one during the interval until the cached state is updated in a regular way.
1647 m_nativeStatus.state = lt::torrent_status::checking_resume_data;
1649 if (m_hasMissingFiles)
1651 m_hasMissingFiles = false;
1652 if (!isStopped())
1654 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1655 if (m_operatingMode == TorrentOperatingMode::Forced)
1656 m_nativeHandle.resume();
1660 m_unchecked = false;
1662 m_completedFiles.fill(false);
1663 m_filesProgress.fill(0);
1664 m_pieces.fill(false);
1665 m_nativeStatus.pieces.clear_all();
1666 m_nativeStatus.num_pieces = 0;
1668 if (isStopped())
1670 // When "force recheck" is applied on Stopped torrent, we start them to perform checking
1671 start();
1672 m_stopCondition = StopCondition::FilesChecked;
1676 void TorrentImpl::setSequentialDownload(const bool enable)
1678 if (enable)
1680 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1681 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1683 else
1685 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1686 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1689 deferredRequestResumeData();
1692 void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
1694 if (m_hasFirstLastPiecePriority == enabled)
1695 return;
1697 m_hasFirstLastPiecePriority = enabled;
1698 if (hasMetadata())
1699 applyFirstLastPiecePriority(enabled);
1701 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1702 .arg((enabled ? tr("On") : tr("Off")), name()));
1704 deferredRequestResumeData();
1707 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
1709 Q_ASSERT(hasMetadata());
1711 // Download first and last pieces first for every file in the torrent
1713 auto piecePriorities = std::vector<lt::download_priority_t>(m_torrentInfo.piecesCount(), LT::toNative(DownloadPriority::Ignored));
1715 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1716 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1717 for (int fileIndex = 0; fileIndex < m_filePriorities.size(); ++fileIndex)
1719 const DownloadPriority filePrio = m_filePriorities[fileIndex];
1720 if (filePrio <= DownloadPriority::Ignored)
1721 continue;
1723 // Determine the priority to set
1724 const lt::download_priority_t piecePrio = LT::toNative(enabled ? DownloadPriority::Maximum : filePrio);
1725 const TorrentInfo::PieceRange pieceRange = m_torrentInfo.filePieces(fileIndex);
1727 // worst case: AVI index = 1% of total file size (at the end of the file)
1728 const int numPieces = std::ceil(fileSize(fileIndex) * 0.01 / pieceLength());
1729 for (int i = 0; i < numPieces; ++i)
1731 piecePriorities[pieceRange.first() + i] = piecePrio;
1732 piecePriorities[pieceRange.last() - i] = piecePrio;
1735 const int firstPiece = pieceRange.first() + numPieces;
1736 const int lastPiece = pieceRange.last() - numPieces;
1737 for (int pieceIndex = firstPiece; pieceIndex <= lastPiece; ++pieceIndex)
1738 piecePriorities[pieceIndex] = LT::toNative(filePrio);
1741 m_nativeHandle.prioritize_pieces(piecePriorities);
1744 void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
1746 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1747 endReceivedMetadataHandling(savePath, fileNames);
1750 TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
1752 const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
1753 , [&announceEntry](const TrackerEntryStatus &trackerEntryStatus)
1755 return (trackerEntryStatus.url == QString::fromStdString(announceEntry.url));
1758 Q_ASSERT(it != m_trackerEntryStatuses.end());
1759 if (it == m_trackerEntryStatuses.end()) [[unlikely]]
1760 return {};
1762 #ifdef QBT_USES_LIBTORRENT2
1763 QSet<int> btProtocols;
1764 const auto &infoHashes = nativeHandle().info_hashes();
1765 if (infoHashes.has(lt::protocol_version::V1))
1766 btProtocols.insert(1);
1767 if (infoHashes.has(lt::protocol_version::V2))
1768 btProtocols.insert(2);
1769 #else
1770 const QSet<int> btProtocols {1};
1771 #endif
1772 ::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo);
1773 return *it;
1776 void TorrentImpl::resetTrackerEntryStatuses()
1778 for (TrackerEntryStatus &status : m_trackerEntryStatuses)
1780 const QString tempUrl = status.url;
1781 const int tempTier = status.tier;
1783 status.clear();
1784 status.url = tempUrl;
1785 status.tier = tempTier;
1789 std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
1791 Q_ASSERT(!m_nativeStatus.torrent_file.expired());
1793 return m_nativeStatus.torrent_file.lock();
1796 void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
1798 Q_ASSERT(m_maintenanceJob == MaintenanceJob::HandleMetadata);
1799 if (m_maintenanceJob != MaintenanceJob::HandleMetadata) [[unlikely]]
1800 return;
1802 Q_ASSERT(m_filePaths.isEmpty());
1803 if (!m_filePaths.isEmpty()) [[unlikely]]
1804 m_filePaths.clear();
1806 lt::add_torrent_params &p = m_ltAddTorrentParams;
1808 const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1809 m_torrentInfo = TorrentInfo(*metadata);
1810 m_filePriorities.reserve(filesCount());
1811 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
1812 p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
1813 , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
1815 m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
1816 m_filesProgress.resize(filesCount());
1817 updateProgress();
1819 for (int i = 0; i < fileNames.size(); ++i)
1821 const auto nativeIndex = nativeIndexes.at(i);
1823 const Path &actualFilePath = fileNames.at(i);
1824 p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
1826 const Path filePath = actualFilePath.removedExtension(QB_EXT);
1827 m_filePaths.append(filePath);
1829 m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
1832 m_session->applyFilenameFilter(fileNames, m_filePriorities);
1833 for (int i = 0; i < m_filePriorities.size(); ++i)
1834 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
1836 p.save_path = savePath.toString().toStdString();
1837 p.ti = metadata;
1839 if (stopCondition() == StopCondition::MetadataReceived)
1841 m_stopCondition = StopCondition::None;
1843 m_isStopped = true;
1844 p.flags |= lt::torrent_flags::paused;
1845 p.flags &= ~lt::torrent_flags::auto_managed;
1847 m_session->handleTorrentStopped(this);
1850 reload();
1852 // If first/last piece priority was specified when adding this torrent,
1853 // we should apply it now that we have metadata:
1854 if (m_hasFirstLastPiecePriority)
1855 applyFirstLastPiecePriority(true);
1857 m_maintenanceJob = MaintenanceJob::None;
1858 prepareResumeData(p);
1860 m_session->handleTorrentMetadataReceived(this);
1863 void TorrentImpl::reload()
1867 m_completedFiles.fill(false);
1868 m_filesProgress.fill(0);
1869 m_pieces.fill(false);
1870 m_nativeStatus.pieces.clear_all();
1871 m_nativeStatus.num_pieces = 0;
1873 const auto queuePos = m_nativeHandle.queue_position();
1875 m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
1877 lt::add_torrent_params p = m_ltAddTorrentParams;
1878 p.flags |= lt::torrent_flags::update_subscribe
1879 | lt::torrent_flags::override_trackers
1880 | lt::torrent_flags::override_web_seeds;
1882 if (m_isStopped)
1884 p.flags |= lt::torrent_flags::paused;
1885 p.flags &= ~lt::torrent_flags::auto_managed;
1887 else if (m_operatingMode == TorrentOperatingMode::AutoManaged)
1889 p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1891 else
1893 p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1896 auto *const extensionData = new ExtensionData;
1897 p.userdata = LTClientData(extensionData);
1898 #ifndef QBT_USES_LIBTORRENT2
1899 p.storage = customStorageConstructor;
1900 #endif
1901 m_nativeHandle = m_nativeSession->add_torrent(p);
1903 m_nativeStatus = extensionData->status;
1905 if (queuePos >= lt::queue_position_t {})
1906 m_nativeHandle.queue_position_set(queuePos);
1907 m_nativeStatus.queue_position = queuePos;
1909 updateState();
1911 catch (const lt::system_error &err)
1913 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1914 .arg(id().toString(), QString::fromLocal8Bit(err.what())));
1918 void TorrentImpl::stop()
1920 if (!m_isStopped)
1922 m_stopCondition = StopCondition::None;
1923 m_isStopped = true;
1924 deferredRequestResumeData();
1925 m_session->handleTorrentStopped(this);
1928 if (m_maintenanceJob == MaintenanceJob::None)
1930 setAutoManaged(false);
1931 m_nativeHandle.pause();
1933 m_payloadRateMonitor.reset();
1937 void TorrentImpl::start(const TorrentOperatingMode mode)
1939 if (hasError())
1941 m_nativeHandle.clear_error();
1942 m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode);
1945 m_operatingMode = mode;
1947 if (m_hasMissingFiles)
1949 m_hasMissingFiles = false;
1950 m_isStopped = false;
1951 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1952 reload();
1953 return;
1956 if (m_isStopped)
1958 m_isStopped = false;
1959 deferredRequestResumeData();
1960 m_session->handleTorrentStarted(this);
1963 if (m_maintenanceJob == MaintenanceJob::None)
1965 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1966 if (m_operatingMode == TorrentOperatingMode::Forced)
1967 m_nativeHandle.resume();
1971 void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
1973 if (!hasMetadata())
1975 if (context == MoveStorageContext::ChangeSavePath)
1977 m_savePath = newPath;
1978 m_session->handleTorrentSavePathChanged(this);
1980 else if (context == MoveStorageContext::ChangeDownloadPath)
1982 m_downloadPath = newPath;
1983 m_session->handleTorrentSavePathChanged(this);
1986 return;
1989 const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
1990 ? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
1991 if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
1993 if (!m_storageIsMoving)
1995 m_storageIsMoving = true;
1996 updateState();
1997 m_session->handleTorrentStorageMovingStateChanged(this);
2002 void TorrentImpl::renameFile(const int index, const Path &path)
2004 Q_ASSERT((index >= 0) && (index < filesCount()));
2005 if ((index < 0) || (index >= filesCount())) [[unlikely]]
2006 return;
2008 const Path targetActualPath = makeActualPath(index, path);
2009 doRenameFile(index, targetActualPath);
2012 void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
2014 updateStatus(nativeStatus);
2017 void TorrentImpl::handleQueueingModeChanged()
2019 updateState();
2022 void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
2024 if (context == MoveStorageContext::ChangeSavePath)
2025 m_savePath = path;
2026 else if (context == MoveStorageContext::ChangeDownloadPath)
2027 m_downloadPath = path;
2028 m_storageIsMoving = hasOutstandingJob;
2029 m_nativeStatus.save_path = path.toString().toStdString();
2031 m_session->handleTorrentSavePathChanged(this);
2032 deferredRequestResumeData();
2034 if (!m_storageIsMoving)
2036 updateState();
2037 m_session->handleTorrentStorageMovingStateChanged(this);
2039 if (m_hasMissingFiles)
2041 // it can be moved to the proper location
2042 m_hasMissingFiles = false;
2043 m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
2044 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
2045 reload();
2048 while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2049 std::invoke(m_moveFinishedTriggers.dequeue());
2053 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_checked_alert *p)
2055 if (!hasMetadata())
2057 // The torrent is checked due to metadata received, but we should not process
2058 // this event until the torrent is reloaded using the received metadata.
2059 return;
2062 if (stopCondition() == StopCondition::FilesChecked)
2063 stop();
2065 m_statusUpdatedTriggers.enqueue([this]()
2067 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
2069 if (!m_hasMissingFiles)
2071 if ((progress() < 1.0) && (wantedSize() > 0))
2072 m_hasFinishedStatus = false;
2073 else if (progress() == 1.0)
2074 m_hasFinishedStatus = true;
2076 adjustStorageLocation();
2077 manageActualFilePaths();
2079 if (!isStopped())
2081 // torrent is internally paused using NativeTorrentExtension after files checked
2082 // so we need to resume it if there is no corresponding "stop condition" set
2083 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
2084 if (m_operatingMode == TorrentOperatingMode::Forced)
2085 m_nativeHandle.resume();
2089 if (m_nativeStatus.need_save_resume)
2090 deferredRequestResumeData();
2092 m_session->handleTorrentChecked(this);
2096 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused]] const lt::torrent_finished_alert *p)
2098 m_hasMissingFiles = false;
2099 if (m_hasFinishedStatus)
2100 return;
2102 m_statusUpdatedTriggers.enqueue([this]()
2104 adjustStorageLocation();
2105 manageActualFilePaths();
2107 deferredRequestResumeData();
2109 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
2110 if (recheckTorrentsOnCompletion && m_unchecked)
2112 forceRecheck();
2114 else
2116 m_hasFinishedStatus = true;
2118 if (isMoveInProgress() || (m_renameCount > 0))
2119 m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
2120 else
2121 m_session->handleTorrentFinished(this);
2126 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused]] const lt::torrent_paused_alert *p)
2130 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused]] const lt::torrent_resumed_alert *p)
2134 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
2136 if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
2138 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2139 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2140 // than the list we usually cache, so we have to request it from the appropriate source.
2141 fetchURLSeeds([this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
2144 if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
2146 Q_ASSERT(m_indexMap.isEmpty());
2148 const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
2149 m_ltAddTorrentParams = p->params;
2150 if (isSeedMode)
2151 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2153 m_ltAddTorrentParams.have_pieces.clear();
2154 m_ltAddTorrentParams.verified_pieces.clear();
2156 m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti;
2158 const auto metadata = TorrentInfo(*m_ltAddTorrentParams.ti);
2160 const auto &renamedFiles = m_ltAddTorrentParams.renamed_files;
2161 PathList filePaths = metadata.filePaths();
2162 if (renamedFiles.empty() && (m_contentLayout != TorrentContentLayout::Original))
2164 const Path originalRootFolder = Path::findRootFolder(filePaths);
2165 const auto originalContentLayout = (originalRootFolder.isEmpty()
2166 ? TorrentContentLayout::NoSubfolder : TorrentContentLayout::Subfolder);
2167 if (m_contentLayout != originalContentLayout)
2169 if (m_contentLayout == TorrentContentLayout::NoSubfolder)
2170 Path::stripRootFolder(filePaths);
2171 else
2172 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2176 const auto nativeIndexes = metadata.nativeIndexes();
2177 m_indexMap.reserve(filePaths.size());
2178 for (int i = 0; i < filePaths.size(); ++i)
2180 const auto nativeIndex = nativeIndexes.at(i);
2181 m_indexMap[nativeIndex] = i;
2183 if (const auto it = renamedFiles.find(nativeIndex); it != renamedFiles.cend())
2184 filePaths[i] = Path(it->second);
2187 m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
2189 else
2191 prepareResumeData(p->params);
2195 void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
2197 if (m_hasMissingFiles)
2199 const auto havePieces = m_ltAddTorrentParams.have_pieces;
2200 const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
2201 const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
2203 // Update recent resume data but preserve existing progress
2204 m_ltAddTorrentParams = params;
2205 m_ltAddTorrentParams.have_pieces = havePieces;
2206 m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
2207 m_ltAddTorrentParams.verified_pieces = verifiedPieces;
2209 else
2211 const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
2212 // Update recent resume data
2213 m_ltAddTorrentParams = params;
2214 if (preserveSeedMode)
2215 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2218 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2219 m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
2221 const LoadTorrentParams resumeData
2223 .ltAddTorrentParams = m_ltAddTorrentParams,
2224 .name = m_name,
2225 .category = m_category,
2226 .tags = m_tags,
2227 .savePath = (!m_useAutoTMM ? m_savePath : Path()),
2228 .downloadPath = (!m_useAutoTMM ? m_downloadPath : Path()),
2229 .contentLayout = m_contentLayout,
2230 .operatingMode = m_operatingMode,
2231 .useAutoTMM = m_useAutoTMM,
2232 .firstLastPiecePriority = m_hasFirstLastPiecePriority,
2233 .hasFinishedStatus = m_hasFinishedStatus,
2234 .stopped = m_isStopped,
2235 .stopCondition = m_stopCondition,
2236 .addToQueueTop = false,
2237 .ratioLimit = m_ratioLimit,
2238 .seedingTimeLimit = m_seedingTimeLimit,
2239 .inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit,
2240 .shareLimitAction = m_shareLimitAction,
2241 .sslParameters = m_sslParams
2244 m_session->handleTorrentResumeDataReady(this, resumeData);
2247 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
2249 if (p->error != lt::errors::resume_data_not_modified)
2251 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2252 .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
2256 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
2258 // Files were probably moved or storage isn't accessible
2259 m_hasMissingFiles = true;
2260 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2261 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
2264 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
2266 const int fileIndex = m_indexMap.value(p->index, -1);
2267 Q_ASSERT(fileIndex >= 0);
2269 const Path newActualFilePath {QString::fromUtf8(p->new_name())};
2271 const Path oldFilePath = m_filePaths.at(fileIndex);
2272 const Path newFilePath = makeUserPath(newActualFilePath);
2274 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2275 // We should compare path in a case sensitive manner even on case insensitive
2276 // platforms since it can be renamed by only changing case of some character(s)
2277 if (oldFilePath.data() == newFilePath.data())
2279 // Remove empty ".unwanted" folders
2280 #ifdef QBT_USES_LIBTORRENT2
2281 const Path oldActualFilePath {QString::fromUtf8(p->old_name())};
2282 #else
2283 const Path oldActualFilePath;
2284 #endif
2285 const Path oldActualParentPath = oldActualFilePath.parentPath();
2286 const Path newActualParentPath = newActualFilePath.parentPath();
2287 if (newActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2289 if (oldActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2291 #ifdef Q_OS_WIN
2292 const std::wstring winPath = (actualStorageLocation() / newActualParentPath).toString().toStdWString();
2293 const DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str());
2294 ::SetFileAttributesW(winPath.c_str(), (dwAttrs | FILE_ATTRIBUTE_HIDDEN));
2295 #endif
2298 #ifdef QBT_USES_LIBTORRENT2
2299 else if (oldActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2301 if (newActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2302 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath);
2304 #else
2305 else
2307 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath / Path(UNWANTED_FOLDER_NAME));
2309 #endif
2311 else
2313 m_filePaths[fileIndex] = newFilePath;
2315 // Remove empty leftover folders
2316 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2317 // be removed if they are empty
2318 Path oldParentPath = oldFilePath.parentPath();
2319 const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath());
2320 while (oldParentPath != commonBasePath)
2322 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath);
2323 oldParentPath = oldParentPath.parentPath();
2327 --m_renameCount;
2328 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2329 m_moveFinishedTriggers.takeFirst()();
2331 deferredRequestResumeData();
2334 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
2336 const int fileIndex = m_indexMap.value(p->index, -1);
2337 Q_ASSERT(fileIndex >= 0);
2339 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2340 .arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
2342 --m_renameCount;
2343 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2344 m_moveFinishedTriggers.takeFirst()();
2346 deferredRequestResumeData();
2349 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
2351 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2352 return;
2354 const int fileIndex = m_indexMap.value(p->index, -1);
2355 Q_ASSERT(fileIndex >= 0);
2357 m_completedFiles.setBit(fileIndex);
2359 const Path actualPath = actualFilePath(fileIndex);
2361 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2362 // only apply Mark-of-the-Web to new download files
2363 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2365 const Path fullpath = actualStorageLocation() / actualPath;
2366 Utils::OS::applyMarkOfTheWeb(fullpath);
2368 #endif // Q_OS_MACOS || Q_OS_WIN
2370 if (m_session->isAppendExtensionEnabled())
2372 const Path path = filePath(fileIndex);
2373 if (actualPath != path)
2375 qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
2376 doRenameFile(fileIndex, path);
2381 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
2383 m_lastFileError = {p->error, p->op};
2386 #ifdef QBT_USES_LIBTORRENT2
2387 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
2389 deferredRequestResumeData();
2391 #endif
2393 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused]] const lt::metadata_received_alert *p)
2395 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2397 #ifdef QBT_USES_LIBTORRENT2
2398 const InfoHash prevInfoHash = infoHash();
2399 m_infoHash = InfoHash(m_nativeHandle.info_hashes());
2400 if (prevInfoHash != infoHash())
2401 m_session->handleTorrentInfoHashChanged(this, prevInfoHash);
2402 #endif
2404 m_maintenanceJob = MaintenanceJob::HandleMetadata;
2405 deferredRequestResumeData();
2408 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
2410 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))
2411 , Log::INFO);
2414 void TorrentImpl::handleCategoryOptionsChanged()
2416 if (m_useAutoTMM)
2417 adjustStorageLocation();
2420 void TorrentImpl::handleAppendExtensionToggled()
2422 if (!hasMetadata())
2423 return;
2425 manageActualFilePaths();
2428 void TorrentImpl::handleUnwantedFolderToggled()
2430 if (!hasMetadata())
2431 return;
2433 manageActualFilePaths();
2436 void TorrentImpl::handleAlert(const lt::alert *a)
2438 switch (a->type())
2440 #ifdef QBT_USES_LIBTORRENT2
2441 case lt::file_prio_alert::alert_type:
2442 handleFilePrioAlert(static_cast<const lt::file_prio_alert*>(a));
2443 break;
2444 #endif
2445 case lt::file_renamed_alert::alert_type:
2446 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
2447 break;
2448 case lt::file_rename_failed_alert::alert_type:
2449 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
2450 break;
2451 case lt::file_completed_alert::alert_type:
2452 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
2453 break;
2454 case lt::file_error_alert::alert_type:
2455 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
2456 break;
2457 case lt::torrent_finished_alert::alert_type:
2458 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
2459 break;
2460 case lt::save_resume_data_alert::alert_type:
2461 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
2462 break;
2463 case lt::save_resume_data_failed_alert::alert_type:
2464 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
2465 break;
2466 case lt::torrent_paused_alert::alert_type:
2467 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
2468 break;
2469 case lt::torrent_resumed_alert::alert_type:
2470 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
2471 break;
2472 case lt::metadata_received_alert::alert_type:
2473 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2474 break;
2475 case lt::fastresume_rejected_alert::alert_type:
2476 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2477 break;
2478 case lt::torrent_checked_alert::alert_type:
2479 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2480 break;
2481 case lt::performance_alert::alert_type:
2482 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2483 break;
2487 void TorrentImpl::manageActualFilePaths()
2489 const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
2490 const lt::file_storage &nativeFiles = nativeInfo->files();
2492 for (int i = 0; i < filesCount(); ++i)
2494 const Path path = filePath(i);
2496 const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
2497 const Path actualPath {nativeFiles.file_path(nativeIndex)};
2498 const Path targetActualPath = makeActualPath(i, path);
2499 if (actualPath != targetActualPath)
2501 qDebug() << "Renaming" << actualPath.toString() << "to" << targetActualPath.toString();
2502 doRenameFile(i, targetActualPath);
2507 void TorrentImpl::adjustStorageLocation()
2509 const Path downloadPath = this->downloadPath();
2510 const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
2512 if ((targetPath != actualStorageLocation()) || isMoveInProgress())
2513 moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
2516 void TorrentImpl::doRenameFile(const int index, const Path &path)
2518 const QList<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
2520 Q_ASSERT(index >= 0);
2521 Q_ASSERT(index < nativeIndexes.size());
2522 if ((index < 0) || (index >= nativeIndexes.size())) [[unlikely]]
2523 return;
2525 ++m_renameCount;
2526 m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
2529 lt::torrent_handle TorrentImpl::nativeHandle() const
2531 return m_nativeHandle;
2534 void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
2536 if (hasMetadata())
2537 return;
2539 m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
2543 #ifdef QBT_USES_LIBTORRENT2
2544 nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
2545 #else
2546 const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
2547 nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
2548 #endif
2550 catch (const std::exception &) {}
2554 Torrent::StopCondition TorrentImpl::stopCondition() const
2556 return m_stopCondition;
2559 void TorrentImpl::setStopCondition(const StopCondition stopCondition)
2561 if (stopCondition == m_stopCondition)
2562 return;
2564 if (isStopped())
2565 return;
2567 if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata())
2568 return;
2570 if ((stopCondition == StopCondition::FilesChecked) && hasMetadata() && !isChecking())
2571 return;
2573 m_stopCondition = stopCondition;
2576 SSLParameters TorrentImpl::getSSLParameters() const
2578 return m_sslParams;
2581 void TorrentImpl::setSSLParameters(const SSLParameters &sslParams)
2583 if (sslParams == getSSLParameters())
2584 return;
2586 m_sslParams = sslParams;
2587 applySSLParameters();
2589 deferredRequestResumeData();
2592 bool TorrentImpl::applySSLParameters()
2594 if (!m_sslParams.isValid())
2595 return false;
2597 m_nativeHandle.set_ssl_certificate_buffer(m_sslParams.certificate.toPem().toStdString()
2598 , m_sslParams.privateKey.toPem().toStdString(), m_sslParams.dhParams.toStdString());
2599 return true;
2602 bool TorrentImpl::isMoveInProgress() const
2604 return m_storageIsMoving;
2607 void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
2609 const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
2611 if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
2612 updateProgress();
2614 updateState();
2616 m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
2617 , nativeStatus.upload_payload_rate});
2619 if (hasMetadata())
2621 // NOTE: Don't change the order of these conditionals!
2622 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2623 if (isChecking())
2624 m_unchecked = false;
2625 else if (isDownloading())
2626 m_unchecked = true;
2629 while (!m_statusUpdatedTriggers.isEmpty())
2630 std::invoke(m_statusUpdatedTriggers.dequeue());
2633 void TorrentImpl::updateProgress()
2635 Q_ASSERT(hasMetadata());
2636 if (!hasMetadata()) [[unlikely]]
2637 return;
2639 Q_ASSERT(!m_filesProgress.isEmpty());
2640 if (m_filesProgress.isEmpty()) [[unlikely]]
2641 m_filesProgress.resize(filesCount());
2643 const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
2644 const QBitArray newPieces = m_pieces ^ oldPieces;
2646 const int64_t pieceSize = m_torrentInfo.pieceLength();
2647 for (qsizetype index = 0; index < newPieces.size(); ++index)
2649 if (!newPieces.at(index))
2650 continue;
2652 int64_t size = m_torrentInfo.pieceLength(index);
2653 int64_t pieceOffset = index * pieceSize;
2655 for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
2657 const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
2658 const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
2660 m_filesProgress[fileIndex] += add;
2662 size -= add;
2663 if (size <= 0)
2664 break;
2666 pieceOffset += add;
2671 void TorrentImpl::setRatioLimit(qreal limit)
2673 if (limit < USE_GLOBAL_RATIO)
2674 limit = NO_RATIO_LIMIT;
2675 else if (limit > MAX_RATIO)
2676 limit = MAX_RATIO;
2678 if (m_ratioLimit != limit)
2680 m_ratioLimit = limit;
2681 deferredRequestResumeData();
2682 m_session->handleTorrentShareLimitChanged(this);
2686 void TorrentImpl::setSeedingTimeLimit(int limit)
2688 if (limit < USE_GLOBAL_SEEDING_TIME)
2689 limit = NO_SEEDING_TIME_LIMIT;
2690 else if (limit > MAX_SEEDING_TIME)
2691 limit = MAX_SEEDING_TIME;
2693 if (m_seedingTimeLimit != limit)
2695 m_seedingTimeLimit = limit;
2696 deferredRequestResumeData();
2697 m_session->handleTorrentShareLimitChanged(this);
2701 void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
2703 if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
2704 limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
2705 else if (limit > MAX_INACTIVE_SEEDING_TIME)
2706 limit = MAX_SEEDING_TIME;
2708 if (m_inactiveSeedingTimeLimit != limit)
2710 m_inactiveSeedingTimeLimit = limit;
2711 deferredRequestResumeData();
2712 m_session->handleTorrentShareLimitChanged(this);
2716 ShareLimitAction TorrentImpl::shareLimitAction() const
2718 return m_shareLimitAction;
2721 void TorrentImpl::setShareLimitAction(const ShareLimitAction action)
2723 if (m_shareLimitAction != action)
2725 m_shareLimitAction = action;
2726 deferredRequestResumeData();
2727 m_session->handleTorrentShareLimitChanged(this);
2731 void TorrentImpl::setUploadLimit(const int limit)
2733 const int cleanValue = cleanLimitValue(limit);
2734 if (cleanValue == uploadLimit())
2735 return;
2737 m_uploadLimit = cleanValue;
2738 m_nativeHandle.set_upload_limit(m_uploadLimit);
2739 deferredRequestResumeData();
2742 void TorrentImpl::setDownloadLimit(const int limit)
2744 const int cleanValue = cleanLimitValue(limit);
2745 if (cleanValue == downloadLimit())
2746 return;
2748 m_downloadLimit = cleanValue;
2749 m_nativeHandle.set_download_limit(m_downloadLimit);
2750 deferredRequestResumeData();
2753 void TorrentImpl::setSuperSeeding(const bool enable)
2755 if (enable == superSeeding())
2756 return;
2758 if (enable)
2759 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2760 else
2761 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2763 deferredRequestResumeData();
2766 void TorrentImpl::setDHTDisabled(const bool disable)
2768 if (disable == isDHTDisabled())
2769 return;
2771 if (disable)
2772 m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
2773 else
2774 m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
2776 deferredRequestResumeData();
2779 void TorrentImpl::setPEXDisabled(const bool disable)
2781 if (disable == isPEXDisabled())
2782 return;
2784 if (disable)
2785 m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
2786 else
2787 m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
2789 deferredRequestResumeData();
2792 void TorrentImpl::setLSDDisabled(const bool disable)
2794 if (disable == isLSDDisabled())
2795 return;
2797 if (disable)
2798 m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
2799 else
2800 m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
2802 deferredRequestResumeData();
2805 void TorrentImpl::flushCache() const
2807 m_nativeHandle.flush_cache();
2810 QString TorrentImpl::createMagnetURI() const
2812 QString ret = u"magnet:?"_s;
2814 const SHA1Hash infoHash1 = infoHash().v1();
2815 if (infoHash1.isValid())
2817 ret += u"xt=urn:btih:" + infoHash1.toString();
2820 const SHA256Hash infoHash2 = infoHash().v2();
2821 if (infoHash2.isValid())
2823 if (infoHash1.isValid())
2824 ret += u'&';
2825 ret += u"xt=urn:btmh:1220" + infoHash2.toString();
2828 const QString displayName = name();
2829 if (displayName != id().toString())
2831 ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
2834 for (const TrackerEntryStatus &tracker : asConst(trackers()))
2836 ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
2839 for (const QUrl &urlSeed : asConst(urlSeeds()))
2841 ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
2844 return ret;
2847 nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
2849 if (!hasMetadata())
2850 return nonstd::make_unexpected(tr("Missing metadata"));
2854 #ifdef QBT_USES_LIBTORRENT2
2855 const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
2856 const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
2857 #else
2858 const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
2859 #endif
2860 lt::create_torrent creator {*torrentInfo};
2862 for (const TrackerEntryStatus &status : asConst(trackers()))
2863 creator.add_tracker(status.url.toStdString(), status.tier);
2865 return creator.generate();
2867 catch (const lt::system_error &err)
2869 return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
2873 nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
2875 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2876 if (!preparationResult)
2877 return preparationResult.get_unexpected();
2879 // usually torrent size should be smaller than 1 MB,
2880 // however there are >100 MB v2/hybrid torrent files out in the wild
2881 QByteArray buffer;
2882 buffer.reserve(1024 * 1024);
2883 lt::bencode(std::back_inserter(buffer), preparationResult.value());
2884 return buffer;
2887 nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) const
2889 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2890 if (!preparationResult)
2891 return preparationResult.get_unexpected();
2893 const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
2894 if (!saveResult)
2895 return saveResult.get_unexpected();
2897 return {};
2900 void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const
2902 invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
2906 std::vector<lt::peer_info> nativePeers;
2907 nativeHandle.get_peer_info(nativePeers);
2908 QList<PeerInfo> peers;
2909 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
2910 for (const lt::peer_info &peer : nativePeers)
2911 peers.append(PeerInfo(peer, allPieces));
2912 return peers;
2914 catch (const std::exception &) {}
2916 return {};
2918 , std::move(resultHandler));
2921 void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const
2923 invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
2927 const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
2928 QList<QUrl> urlSeeds;
2929 urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
2930 for (const std::string &urlSeed : currentSeeds)
2931 urlSeeds.append(QString::fromStdString(urlSeed));
2932 return urlSeeds;
2934 catch (const std::exception &) {}
2936 return {};
2938 , std::move(resultHandler));
2941 void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const
2943 invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
2947 std::vector<int> piecesAvailability;
2948 nativeHandle.piece_availability(piecesAvailability);
2949 return QList<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
2951 catch (const std::exception &) {}
2953 return {};
2955 , std::move(resultHandler));
2958 void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
2960 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
2964 #ifdef QBT_USES_LIBTORRENT2
2965 const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
2966 #else
2967 std::vector<lt::partial_piece_info> queue;
2968 nativeHandle.get_download_queue(queue);
2969 #endif
2970 QBitArray result;
2971 result.resize(torrentInfo.piecesCount());
2972 for (const lt::partial_piece_info &info : queue)
2973 result.setBit(LT::toUnderlyingType(info.piece_index));
2974 return result;
2976 catch (const std::exception &) {}
2978 return {};
2980 , std::move(resultHandler));
2983 void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const
2985 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
2987 if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
2988 return {};
2992 std::vector<int> piecesAvailability;
2993 nativeHandle.piece_availability(piecesAvailability);
2994 const int filesCount = torrentInfo.filesCount();
2995 // libtorrent returns empty array for seeding only torrents
2996 if (piecesAvailability.empty())
2997 return QList<qreal>(filesCount, -1);
2999 QList<qreal> result;
3000 result.reserve(filesCount);
3001 for (int i = 0; i < filesCount; ++i)
3003 const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
3005 int availablePieces = 0;
3006 for (const int piece : filePieces)
3007 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
3009 const qreal availability = filePieces.isEmpty()
3010 ? 1 // the file has no pieces, so it is available by default
3011 : static_cast<qreal>(availablePieces) / filePieces.size();
3012 result.append(availability);
3014 return result;
3016 catch (const std::exception &) {}
3018 return {};
3020 , std::move(resultHandler));
3023 void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
3025 if (!hasMetadata())
3026 return;
3028 Q_ASSERT(priorities.size() == filesCount());
3030 // Reset 'm_hasSeedStatus' if needed in order to react again to
3031 // 'torrent_finished_alert' and eg show tray notifications
3032 const QList<DownloadPriority> oldPriorities = filePriorities();
3033 for (int i = 0; i < oldPriorities.size(); ++i)
3035 if ((oldPriorities[i] == DownloadPriority::Ignored)
3036 && (priorities[i] > DownloadPriority::Ignored)
3037 && !m_completedFiles.at(i))
3039 m_hasFinishedStatus = false;
3040 break;
3044 const int internalFilesCount = m_torrentInfo.nativeInfo()->files().num_files(); // including .pad files
3045 auto nativePriorities = std::vector<lt::download_priority_t>(internalFilesCount, LT::toNative(DownloadPriority::Normal));
3046 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
3047 for (int i = 0; i < priorities.size(); ++i)
3048 nativePriorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(priorities[i]);
3050 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
3051 m_nativeHandle.prioritize_files(nativePriorities);
3053 m_filePriorities = priorities;
3054 // Restore first/last piece first option if necessary
3055 if (m_hasFirstLastPiecePriority)
3056 applyFirstLastPiecePriority(true);
3057 manageActualFilePaths();
3060 QList<qreal> TorrentImpl::availableFileFractions() const
3062 Q_ASSERT(hasMetadata());
3064 const int filesCount = this->filesCount();
3065 if (filesCount <= 0) return {};
3067 const QList<int> piecesAvailability = pieceAvailability();
3068 // libtorrent returns empty array for seeding only torrents
3069 if (piecesAvailability.empty()) return QList<qreal>(filesCount, -1);
3071 QList<qreal> res;
3072 res.reserve(filesCount);
3073 for (int i = 0; i < filesCount; ++i)
3075 const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
3077 int availablePieces = 0;
3078 for (const int piece : filePieces)
3079 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
3081 const qreal availability = filePieces.isEmpty()
3082 ? 1 // the file has no pieces, so it is available by default
3083 : static_cast<qreal>(availablePieces) / filePieces.size();
3084 res.push_back(availability);
3086 return res;
3089 template <typename Func, typename Callback>
3090 void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
3092 m_session->invokeAsync([session = m_session
3093 , func = std::move(func)
3094 , resultHandler = std::move(resultHandler)
3095 , thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
3097 session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
3099 if (thisTorrent)
3100 resultHandler(result);