Improve received metadata handling
[qBittorrent.git] / src / base / bittorrent / torrentimpl.cpp
blob24d469e9b2f761123f9b9cac275b3a669a839844
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2023 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"
75 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
76 #include "base/utils/os.h"
77 #endif // Q_OS_MACOS || Q_OS_WIN
79 using namespace BitTorrent;
81 namespace
83 lt::announce_entry makeNativeAnnounceEntry(const QString &url, const int tier)
85 lt::announce_entry entry {url.toStdString()};
86 entry.tier = tier;
87 return entry;
90 QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint)
92 const auto ltNow = lt::clock_type::now();
93 const auto qNow = QDateTime::currentDateTime();
94 const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - ltNow + lt::milliseconds(500)).count();
96 return qNow.addSecs(secsSinceNow);
99 QString toString(const lt::tcp::endpoint &ltTCPEndpoint)
101 return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str());
104 void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
105 , const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
107 Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
109 trackerEntry.tier = nativeEntry.tier;
111 // remove outdated endpoints
112 trackerEntry.endpointEntries.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointEntry>::iterator &iter)
114 return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
115 , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
117 return (endpointName == toString(existingEndpoint.local_endpoint));
121 const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * btProtocols.size();
123 int numUpdating = 0;
124 int numWorking = 0;
125 int numNotWorking = 0;
126 int numTrackerError = 0;
127 int numUnreachable = 0;
129 for (const lt::announce_endpoint &ltAnnounceEndpoint : nativeEntry.endpoints)
131 const auto endpointName = toString(ltAnnounceEndpoint.local_endpoint);
133 for (const auto protocolVersion : btProtocols)
135 #ifdef QBT_USES_LIBTORRENT2
136 Q_ASSERT((protocolVersion == 1) || (protocolVersion == 2));
137 const auto ltProtocolVersion = (protocolVersion == 1) ? lt::protocol_version::V1 : lt::protocol_version::V2;
138 const lt::announce_infohash &ltAnnounceInfo = ltAnnounceEndpoint.info_hashes[ltProtocolVersion];
139 #else
140 Q_ASSERT(protocolVersion == 1);
141 const lt::announce_endpoint &ltAnnounceInfo = ltAnnounceEndpoint;
142 #endif
143 const QMap<int, int> &endpointUpdateInfo = updateInfo[ltAnnounceEndpoint.local_endpoint];
144 TrackerEndpointEntry &trackerEndpointEntry = trackerEntry.endpointEntries[std::make_pair(endpointName, protocolVersion)];
146 trackerEndpointEntry.name = endpointName;
147 trackerEndpointEntry.btVersion = protocolVersion;
148 trackerEndpointEntry.numPeers = endpointUpdateInfo.value(protocolVersion, trackerEndpointEntry.numPeers);
149 trackerEndpointEntry.numSeeds = ltAnnounceInfo.scrape_complete;
150 trackerEndpointEntry.numLeeches = ltAnnounceInfo.scrape_incomplete;
151 trackerEndpointEntry.numDownloaded = ltAnnounceInfo.scrape_downloaded;
152 trackerEndpointEntry.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
153 trackerEndpointEntry.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
155 if (ltAnnounceInfo.updating)
157 trackerEndpointEntry.status = TrackerEntryStatus::Updating;
158 ++numUpdating;
160 else if (ltAnnounceInfo.fails > 0)
162 if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
164 trackerEndpointEntry.status = TrackerEntryStatus::TrackerError;
165 ++numTrackerError;
167 else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
169 trackerEndpointEntry.status = TrackerEntryStatus::Unreachable;
170 ++numUnreachable;
172 else
174 trackerEndpointEntry.status = TrackerEntryStatus::NotWorking;
175 ++numNotWorking;
178 else if (nativeEntry.verified)
180 trackerEndpointEntry.status = TrackerEntryStatus::Working;
181 ++numWorking;
183 else
185 trackerEndpointEntry.status = TrackerEntryStatus::NotContacted;
188 if (!ltAnnounceInfo.message.empty())
190 trackerEndpointEntry.message = QString::fromStdString(ltAnnounceInfo.message);
192 else if (ltAnnounceInfo.last_error)
194 trackerEndpointEntry.message = QString::fromLocal8Bit(ltAnnounceInfo.last_error.message());
196 else
198 trackerEndpointEntry.message.clear();
203 if (numEndpoints > 0)
205 if (numUpdating > 0)
207 trackerEntry.status = TrackerEntryStatus::Updating;
209 else if (numWorking > 0)
211 trackerEntry.status = TrackerEntryStatus::Working;
213 else if (numTrackerError > 0)
215 trackerEntry.status = TrackerEntryStatus::TrackerError;
217 else if (numUnreachable == numEndpoints)
219 trackerEntry.status = TrackerEntryStatus::Unreachable;
221 else if ((numUnreachable + numNotWorking) == numEndpoints)
223 trackerEntry.status = TrackerEntryStatus::NotWorking;
227 trackerEntry.numPeers = -1;
228 trackerEntry.numSeeds = -1;
229 trackerEntry.numLeeches = -1;
230 trackerEntry.numDownloaded = -1;
231 trackerEntry.nextAnnounceTime = QDateTime();
232 trackerEntry.minAnnounceTime = QDateTime();
233 trackerEntry.message.clear();
235 for (const TrackerEndpointEntry &endpointEntry : asConst(trackerEntry.endpointEntries))
237 trackerEntry.numPeers = std::max(trackerEntry.numPeers, endpointEntry.numPeers);
238 trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, endpointEntry.numSeeds);
239 trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, endpointEntry.numLeeches);
240 trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, endpointEntry.numDownloaded);
242 if (endpointEntry.status == trackerEntry.status)
244 if (!trackerEntry.nextAnnounceTime.isValid() || (trackerEntry.nextAnnounceTime > endpointEntry.nextAnnounceTime))
246 trackerEntry.nextAnnounceTime = endpointEntry.nextAnnounceTime;
247 trackerEntry.minAnnounceTime = endpointEntry.minAnnounceTime;
248 if ((endpointEntry.status != TrackerEntryStatus::Working)
249 || !endpointEntry.message.isEmpty())
251 trackerEntry.message = endpointEntry.message;
255 if (endpointEntry.status == TrackerEntryStatus::Working)
257 if (trackerEntry.message.isEmpty())
258 trackerEntry.message = endpointEntry.message;
264 template <typename Vector>
265 Vector resized(const Vector &inVector, const typename Vector::size_type size, const typename Vector::value_type &defaultValue)
267 Vector outVector = inVector;
268 outVector.resize(size, defaultValue);
269 return outVector;
272 // This is an imitation of limit normalization performed by libtorrent itself.
273 // We need perform it to keep cached values in line with the ones used by libtorrent.
274 int cleanLimitValue(const int value)
276 return ((value < 0) || (value == std::numeric_limits<int>::max())) ? 0 : value;
280 // TorrentImpl
282 TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
283 , const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
284 : Torrent(session)
285 , m_session(session)
286 , m_nativeSession(nativeSession)
287 , m_nativeHandle(nativeHandle)
288 #ifdef QBT_USES_LIBTORRENT2
289 , m_infoHash(m_nativeHandle.info_hashes())
290 #else
291 , m_infoHash(m_nativeHandle.info_hash())
292 #endif
293 , m_name(params.name)
294 , m_savePath(params.savePath)
295 , m_downloadPath(params.downloadPath)
296 , m_category(params.category)
297 , m_tags(params.tags)
298 , m_ratioLimit(params.ratioLimit)
299 , m_seedingTimeLimit(params.seedingTimeLimit)
300 , m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit)
301 , m_operatingMode(params.operatingMode)
302 , m_contentLayout(params.contentLayout)
303 , m_hasFinishedStatus(params.hasFinishedStatus)
304 , m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
305 , m_useAutoTMM(params.useAutoTMM)
306 , m_isStopped(params.stopped)
307 , m_ltAddTorrentParams(params.ltAddTorrentParams)
308 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit))
309 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit))
311 if (m_ltAddTorrentParams.ti)
313 // Initialize it only if torrent is added with metadata.
314 // Otherwise it should be initialized in "Metadata received" handler.
315 m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
317 Q_ASSERT(m_filePaths.isEmpty());
318 Q_ASSERT(m_indexMap.isEmpty());
319 const int filesCount = m_torrentInfo.filesCount();
320 m_filePaths.reserve(filesCount);
321 m_indexMap.reserve(filesCount);
322 m_filePriorities.reserve(filesCount);
323 const std::vector<lt::download_priority_t> filePriorities =
324 resized(m_ltAddTorrentParams.file_priorities, m_ltAddTorrentParams.ti->num_files()
325 , LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
327 m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
328 m_filesProgress.resize(filesCount);
330 for (int i = 0; i < filesCount; ++i)
332 const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
333 m_indexMap[nativeIndex] = i;
335 const auto fileIter = m_ltAddTorrentParams.renamed_files.find(nativeIndex);
336 const Path filePath = ((fileIter != m_ltAddTorrentParams.renamed_files.end())
337 ? makeUserPath(Path(fileIter->second)) : m_torrentInfo.filePath(i));
338 m_filePaths.append(filePath);
340 const auto priority = LT::fromNative(filePriorities[LT::toUnderlyingType(nativeIndex)]);
341 m_filePriorities.append(priority);
345 setStopCondition(params.stopCondition);
347 const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
348 m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
349 for (const lt::announce_entry &announceEntry : extensionData->trackers)
350 m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
351 m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
352 for (const std::string &urlSeed : extensionData->urlSeeds)
353 m_urlSeeds.append(QString::fromStdString(urlSeed));
354 m_nativeStatus = extensionData->status;
356 if (hasMetadata())
357 updateProgress();
359 updateState();
361 if (hasMetadata())
362 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
365 TorrentImpl::~TorrentImpl() = default;
367 bool TorrentImpl::isValid() const
369 return m_nativeHandle.is_valid();
372 Session *TorrentImpl::session() const
374 return m_session;
377 InfoHash TorrentImpl::infoHash() const
379 return m_infoHash;
382 QString TorrentImpl::name() const
384 if (!m_name.isEmpty())
385 return m_name;
387 if (hasMetadata())
388 return m_torrentInfo.name();
390 const QString name = QString::fromStdString(m_nativeStatus.name);
391 if (!name.isEmpty())
392 return name;
394 return id().toString();
397 QDateTime TorrentImpl::creationDate() const
399 return m_torrentInfo.creationDate();
402 QString TorrentImpl::creator() const
404 return m_torrentInfo.creator();
407 QString TorrentImpl::comment() const
409 return m_torrentInfo.comment();
412 bool TorrentImpl::isPrivate() const
414 return m_torrentInfo.isPrivate();
417 qlonglong TorrentImpl::totalSize() const
419 return m_torrentInfo.totalSize();
422 // size without the "don't download" files
423 qlonglong TorrentImpl::wantedSize() const
425 return m_nativeStatus.total_wanted;
428 qlonglong TorrentImpl::completedSize() const
430 return m_nativeStatus.total_wanted_done;
433 qlonglong TorrentImpl::pieceLength() const
435 return m_torrentInfo.pieceLength();
438 qlonglong TorrentImpl::wastedSize() const
440 return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes);
443 QString TorrentImpl::currentTracker() const
445 return QString::fromStdString(m_nativeStatus.current_tracker);
448 Path TorrentImpl::savePath() const
450 return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath;
453 void TorrentImpl::setSavePath(const Path &path)
455 Q_ASSERT(!isAutoTMMEnabled());
457 const Path basePath = m_session->useCategoryPathsInManualMode()
458 ? m_session->categorySavePath(category()) : m_session->savePath();
459 const Path resolvedPath = (path.isAbsolute() ? path : (basePath / path));
460 if (resolvedPath == savePath())
461 return;
463 if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
465 moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
467 else
469 m_savePath = resolvedPath;
470 m_session->handleTorrentSavePathChanged(this);
471 deferredRequestResumeData();
475 Path TorrentImpl::downloadPath() const
477 return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath;
480 void TorrentImpl::setDownloadPath(const Path &path)
482 Q_ASSERT(!isAutoTMMEnabled());
484 const Path basePath = m_session->useCategoryPathsInManualMode()
485 ? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
486 const Path resolvedPath = (path.isEmpty() || path.isAbsolute()) ? path : (basePath / path);
487 if (resolvedPath == m_downloadPath)
488 return;
490 const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
491 if (isIncomplete)
493 moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
495 else
497 m_downloadPath = resolvedPath;
498 m_session->handleTorrentSavePathChanged(this);
499 deferredRequestResumeData();
503 Path TorrentImpl::rootPath() const
505 if (!hasMetadata())
506 return {};
508 const Path relativeRootPath = Path::findRootFolder(filePaths());
509 if (relativeRootPath.isEmpty())
510 return {};
512 return (actualStorageLocation() / relativeRootPath);
515 Path TorrentImpl::contentPath() const
517 if (!hasMetadata())
518 return {};
520 if (filesCount() == 1)
521 return (actualStorageLocation() / filePath(0));
523 const Path rootPath = this->rootPath();
524 return (rootPath.isEmpty() ? actualStorageLocation() : rootPath);
527 bool TorrentImpl::isAutoTMMEnabled() const
529 return m_useAutoTMM;
532 void TorrentImpl::setAutoTMMEnabled(bool enabled)
534 if (m_useAutoTMM == enabled)
535 return;
537 m_useAutoTMM = enabled;
538 if (!m_useAutoTMM)
540 m_savePath = m_session->categorySavePath(category());
541 m_downloadPath = m_session->categoryDownloadPath(category());
544 deferredRequestResumeData();
545 m_session->handleTorrentSavingModeChanged(this);
547 adjustStorageLocation();
550 Path TorrentImpl::actualStorageLocation() const
552 if (!hasMetadata())
553 return {};
555 return Path(m_nativeStatus.save_path);
558 void TorrentImpl::setAutoManaged(const bool enable)
560 if (enable)
561 m_nativeHandle.set_flags(lt::torrent_flags::auto_managed);
562 else
563 m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
566 Path TorrentImpl::makeActualPath(int index, const Path &path) const
568 Path actualPath = path;
570 if (m_session->isAppendExtensionEnabled()
571 && (fileSize(index) > 0) && !m_completedFiles.at(index))
573 actualPath += QB_EXT;
576 if (m_session->isUnwantedFolderEnabled()
577 && (m_filePriorities[index] == DownloadPriority::Ignored))
579 const Path parentPath = actualPath.parentPath();
580 const QString fileName = actualPath.filename();
581 actualPath = parentPath / Path(UNWANTED_FOLDER_NAME) / Path(fileName);
584 return actualPath;
587 Path TorrentImpl::makeUserPath(const Path &path) const
589 Path userPath = path.removedExtension(QB_EXT);
591 const Path parentRelPath = userPath.parentPath();
592 if (parentRelPath.filename() == UNWANTED_FOLDER_NAME)
594 const QString fileName = userPath.filename();
595 const Path relPath = parentRelPath.parentPath();
596 userPath = relPath / Path(fileName);
599 return userPath;
602 QVector<TrackerEntry> TorrentImpl::trackers() const
604 return m_trackerEntries;
607 void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
609 trackers.removeIf([](const TrackerEntry &entry) { return entry.url.isEmpty(); });
611 const auto newTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend())
612 - QSet<TrackerEntry>(m_trackerEntries.cbegin(), m_trackerEntries.cend());
613 if (newTrackers.isEmpty())
614 return;
616 trackers = QVector<TrackerEntry>(newTrackers.cbegin(), newTrackers.cend());
617 for (const TrackerEntry &tracker : trackers)
618 m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
620 m_trackerEntries.append(trackers);
621 std::sort(m_trackerEntries.begin(), m_trackerEntries.end()
622 , [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
624 deferredRequestResumeData();
625 m_session->handleTorrentTrackersAdded(this, trackers);
628 void TorrentImpl::removeTrackers(const QStringList &trackers)
630 QStringList removedTrackers = trackers;
631 for (const QString &tracker : trackers)
633 if (!m_trackerEntries.removeOne({tracker}))
634 removedTrackers.removeOne(tracker);
637 std::vector<lt::announce_entry> nativeTrackers;
638 nativeTrackers.reserve(m_trackerEntries.size());
639 for (const TrackerEntry &tracker : asConst(m_trackerEntries))
640 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
642 if (!removedTrackers.isEmpty())
644 m_nativeHandle.replace_trackers(nativeTrackers);
646 deferredRequestResumeData();
647 m_session->handleTorrentTrackersRemoved(this, removedTrackers);
651 void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
653 trackers.removeIf([](const TrackerEntry &entry) { return entry.url.isEmpty(); });
654 // Filter out duplicate trackers
655 const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
656 trackers = QVector<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
657 std::sort(trackers.begin(), trackers.end()
658 , [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
660 std::vector<lt::announce_entry> nativeTrackers;
661 nativeTrackers.reserve(trackers.size());
662 for (const TrackerEntry &tracker : trackers)
663 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
665 m_nativeHandle.replace_trackers(nativeTrackers);
666 m_trackerEntries = trackers;
668 // Clear the peer list if it's a private torrent since
669 // we do not want to keep connecting with peers from old tracker.
670 if (isPrivate())
671 clearPeers();
673 deferredRequestResumeData();
674 m_session->handleTorrentTrackersChanged(this);
677 QVector<QUrl> TorrentImpl::urlSeeds() const
679 return m_urlSeeds;
682 void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
684 m_session->invokeAsync([urlSeeds, session = m_session
685 , nativeHandle = m_nativeHandle
686 , thisTorrent = QPointer<TorrentImpl>(this)]
690 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
691 QVector<QUrl> currentSeeds;
692 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
693 for (const std::string &urlSeed : nativeSeeds)
694 currentSeeds.append(QString::fromStdString(urlSeed));
696 QVector<QUrl> addedUrlSeeds;
697 addedUrlSeeds.reserve(urlSeeds.size());
699 for (const QUrl &url : urlSeeds)
701 if (!currentSeeds.contains(url))
703 nativeHandle.add_url_seed(url.toString().toStdString());
704 addedUrlSeeds.append(url);
708 currentSeeds.append(addedUrlSeeds);
709 session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
711 if (!thisTorrent)
712 return;
714 thisTorrent->m_urlSeeds = currentSeeds;
715 if (!addedUrlSeeds.isEmpty())
717 thisTorrent->deferredRequestResumeData();
718 session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
722 catch (const std::exception &) {}
726 void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
728 m_session->invokeAsync([urlSeeds, session = m_session
729 , nativeHandle = m_nativeHandle
730 , thisTorrent = QPointer<TorrentImpl>(this)]
734 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
735 QVector<QUrl> currentSeeds;
736 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
737 for (const std::string &urlSeed : nativeSeeds)
738 currentSeeds.append(QString::fromStdString(urlSeed));
740 QVector<QUrl> removedUrlSeeds;
741 removedUrlSeeds.reserve(urlSeeds.size());
743 for (const QUrl &url : urlSeeds)
745 if (currentSeeds.removeOne(url))
747 nativeHandle.remove_url_seed(url.toString().toStdString());
748 removedUrlSeeds.append(url);
752 session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
754 if (!thisTorrent)
755 return;
757 thisTorrent->m_urlSeeds = currentSeeds;
759 if (!removedUrlSeeds.isEmpty())
761 thisTorrent->deferredRequestResumeData();
762 session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
766 catch (const std::exception &) {}
770 void TorrentImpl::clearPeers()
772 m_nativeHandle.clear_peers();
775 bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
777 lt::error_code ec;
778 const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec);
779 if (ec) return false;
781 const lt::tcp::endpoint endpoint(addr, peerAddress.port);
784 m_nativeHandle.connect_peer(endpoint);
786 catch (const lt::system_error &err)
788 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
789 .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
790 return false;
793 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name()));
794 return true;
797 bool TorrentImpl::needSaveResumeData() const
799 return m_nativeStatus.need_save_resume;
802 void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags)
804 m_nativeHandle.save_resume_data(flags);
805 m_deferredRequestResumeDataInvoked = false;
807 m_session->handleTorrentResumeDataRequested(this);
810 void TorrentImpl::deferredRequestResumeData()
812 if (!m_deferredRequestResumeDataInvoked)
814 QMetaObject::invokeMethod(this, [this]
816 requestResumeData((m_maintenanceJob == MaintenanceJob::HandleMetadata)
817 ? lt::torrent_handle::save_info_dict : lt::resume_data_flags_t());
818 }, Qt::QueuedConnection);
820 m_deferredRequestResumeDataInvoked = true;
824 int TorrentImpl::filesCount() const
826 return m_torrentInfo.filesCount();
829 int TorrentImpl::piecesCount() const
831 return m_torrentInfo.piecesCount();
834 int TorrentImpl::piecesHave() const
836 return m_nativeStatus.num_pieces;
839 qreal TorrentImpl::progress() const
841 if (isChecking())
842 return m_nativeStatus.progress;
844 if (m_nativeStatus.total_wanted == 0)
845 return 0.;
847 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
848 return 1.;
850 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
851 if ((progress < 0.f) || (progress > 1.f))
853 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
854 .arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
855 , Log::WARNING);
858 return progress;
861 QString TorrentImpl::category() const
863 return m_category;
866 bool TorrentImpl::belongsToCategory(const QString &category) const
868 if (m_category.isEmpty())
869 return category.isEmpty();
871 if (m_category == category)
872 return true;
874 return (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + u'/'));
877 TagSet TorrentImpl::tags() const
879 return m_tags;
882 bool TorrentImpl::hasTag(const Tag &tag) const
884 return m_tags.contains(tag);
887 bool TorrentImpl::addTag(const Tag &tag)
889 if (!tag.isValid())
890 return false;
891 if (hasTag(tag))
892 return false;
894 if (!m_session->hasTag(tag))
896 if (!m_session->addTag(tag))
897 return false;
899 m_tags.insert(tag);
900 deferredRequestResumeData();
901 m_session->handleTorrentTagAdded(this, tag);
902 return true;
905 bool TorrentImpl::removeTag(const Tag &tag)
907 if (m_tags.remove(tag))
909 deferredRequestResumeData();
910 m_session->handleTorrentTagRemoved(this, tag);
911 return true;
913 return false;
916 void TorrentImpl::removeAllTags()
918 for (const Tag &tag : asConst(tags()))
919 removeTag(tag);
922 QDateTime TorrentImpl::addedTime() const
924 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
927 qreal TorrentImpl::ratioLimit() const
929 return m_ratioLimit;
932 int TorrentImpl::seedingTimeLimit() const
934 return m_seedingTimeLimit;
937 int TorrentImpl::inactiveSeedingTimeLimit() const
939 return m_inactiveSeedingTimeLimit;
942 Path TorrentImpl::filePath(const int index) const
944 Q_ASSERT(index >= 0);
945 Q_ASSERT(index < m_filePaths.size());
947 return m_filePaths.value(index, {});
950 Path TorrentImpl::actualFilePath(const int index) const
952 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
954 Q_ASSERT(index >= 0);
955 Q_ASSERT(index < nativeIndexes.size());
956 if ((index < 0) || (index >= nativeIndexes.size()))
957 return {};
959 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes[index]));
962 qlonglong TorrentImpl::fileSize(const int index) const
964 return m_torrentInfo.fileSize(index);
967 PathList TorrentImpl::filePaths() const
969 return m_filePaths;
972 QVector<DownloadPriority> TorrentImpl::filePriorities() const
974 return m_filePriorities;
977 TorrentInfo TorrentImpl::info() const
979 return m_torrentInfo;
982 bool TorrentImpl::isPaused() const
984 return m_isStopped;
987 bool TorrentImpl::isQueued() const
989 // Torrent is Queued if it isn't in Paused state but paused internally
990 return (!isPaused()
991 && (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
992 && (m_nativeStatus.flags & lt::torrent_flags::paused));
995 bool TorrentImpl::isChecking() const
997 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
998 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
1001 bool TorrentImpl::isDownloading() const
1003 switch (m_state)
1005 case TorrentState::Downloading:
1006 case TorrentState::DownloadingMetadata:
1007 case TorrentState::ForcedDownloadingMetadata:
1008 case TorrentState::StalledDownloading:
1009 case TorrentState::CheckingDownloading:
1010 case TorrentState::PausedDownloading:
1011 case TorrentState::QueuedDownloading:
1012 case TorrentState::ForcedDownloading:
1013 return true;
1014 default:
1015 break;
1018 return false;
1021 bool TorrentImpl::isMoving() const
1023 return m_state == TorrentState::Moving;
1026 bool TorrentImpl::isUploading() const
1028 switch (m_state)
1030 case TorrentState::Uploading:
1031 case TorrentState::StalledUploading:
1032 case TorrentState::CheckingUploading:
1033 case TorrentState::QueuedUploading:
1034 case TorrentState::ForcedUploading:
1035 return true;
1036 default:
1037 break;
1040 return false;
1043 bool TorrentImpl::isCompleted() const
1045 switch (m_state)
1047 case TorrentState::Uploading:
1048 case TorrentState::StalledUploading:
1049 case TorrentState::CheckingUploading:
1050 case TorrentState::PausedUploading:
1051 case TorrentState::QueuedUploading:
1052 case TorrentState::ForcedUploading:
1053 return true;
1054 default:
1055 break;
1058 return false;
1061 bool TorrentImpl::isActive() const
1063 switch (m_state)
1065 case TorrentState::StalledDownloading:
1066 return (uploadPayloadRate() > 0);
1068 case TorrentState::DownloadingMetadata:
1069 case TorrentState::ForcedDownloadingMetadata:
1070 case TorrentState::Downloading:
1071 case TorrentState::ForcedDownloading:
1072 case TorrentState::Uploading:
1073 case TorrentState::ForcedUploading:
1074 case TorrentState::Moving:
1075 return true;
1077 default:
1078 break;
1081 return false;
1084 bool TorrentImpl::isInactive() const
1086 return !isActive();
1089 bool TorrentImpl::isErrored() const
1091 return ((m_state == TorrentState::MissingFiles)
1092 || (m_state == TorrentState::Error));
1095 bool TorrentImpl::isFinished() const
1097 return ((m_nativeStatus.state == lt::torrent_status::finished)
1098 || (m_nativeStatus.state == lt::torrent_status::seeding));
1101 bool TorrentImpl::isForced() const
1103 return (!isPaused() && (m_operatingMode == TorrentOperatingMode::Forced));
1106 bool TorrentImpl::isSequentialDownload() const
1108 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::sequential_download);
1111 bool TorrentImpl::hasFirstLastPiecePriority() const
1113 return m_hasFirstLastPiecePriority;
1116 TorrentState TorrentImpl::state() const
1118 return m_state;
1121 void TorrentImpl::updateState()
1123 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
1125 m_state = TorrentState::CheckingResumeData;
1127 else if (isMoveInProgress())
1129 m_state = TorrentState::Moving;
1131 else if (hasMissingFiles())
1133 m_state = TorrentState::MissingFiles;
1135 else if (hasError())
1137 m_state = TorrentState::Error;
1139 else if (!hasMetadata())
1141 if (isPaused())
1142 m_state = TorrentState::PausedDownloading;
1143 else if (m_session->isQueueingSystemEnabled() && isQueued())
1144 m_state = TorrentState::QueuedDownloading;
1145 else
1146 m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata;
1148 else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused())
1150 // If the torrent is not just in the "checking" state, but is being actually checked
1151 m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
1153 else if (isFinished())
1155 if (isPaused())
1156 m_state = TorrentState::PausedUploading;
1157 else if (m_session->isQueueingSystemEnabled() && isQueued())
1158 m_state = TorrentState::QueuedUploading;
1159 else if (isForced())
1160 m_state = TorrentState::ForcedUploading;
1161 else if (m_nativeStatus.upload_payload_rate > 0)
1162 m_state = TorrentState::Uploading;
1163 else
1164 m_state = TorrentState::StalledUploading;
1166 else
1168 if (isPaused())
1169 m_state = TorrentState::PausedDownloading;
1170 else if (m_session->isQueueingSystemEnabled() && isQueued())
1171 m_state = TorrentState::QueuedDownloading;
1172 else if (isForced())
1173 m_state = TorrentState::ForcedDownloading;
1174 else if (m_nativeStatus.download_payload_rate > 0)
1175 m_state = TorrentState::Downloading;
1176 else
1177 m_state = TorrentState::StalledDownloading;
1181 bool TorrentImpl::hasMetadata() const
1183 return m_torrentInfo.isValid();
1186 bool TorrentImpl::hasMissingFiles() const
1188 return m_hasMissingFiles;
1191 bool TorrentImpl::hasError() const
1193 return (m_nativeStatus.errc || (m_nativeStatus.flags & lt::torrent_flags::upload_mode));
1196 int TorrentImpl::queuePosition() const
1198 return static_cast<int>(m_nativeStatus.queue_position);
1201 QString TorrentImpl::error() const
1203 if (m_nativeStatus.errc)
1204 return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
1206 if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
1208 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1209 .arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
1212 return {};
1215 qlonglong TorrentImpl::totalDownload() const
1217 return m_nativeStatus.all_time_download;
1220 qlonglong TorrentImpl::totalUpload() const
1222 return m_nativeStatus.all_time_upload;
1225 qlonglong TorrentImpl::activeTime() const
1227 return lt::total_seconds(m_nativeStatus.active_duration);
1230 qlonglong TorrentImpl::finishedTime() const
1232 return lt::total_seconds(m_nativeStatus.finished_duration);
1235 qlonglong TorrentImpl::eta() const
1237 if (isPaused()) return MAX_ETA;
1239 const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
1241 if (isFinished())
1243 const qreal maxRatioValue = maxRatio();
1244 const int maxSeedingTimeValue = maxSeedingTime();
1245 const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
1246 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
1248 qlonglong ratioEta = MAX_ETA;
1250 if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
1253 qlonglong realDL = totalDownload();
1254 if (realDL <= 0)
1255 realDL = wantedSize();
1257 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1260 qlonglong seedingTimeEta = MAX_ETA;
1262 if (maxSeedingTimeValue >= 0)
1264 seedingTimeEta = (maxSeedingTimeValue * 60) - finishedTime();
1265 if (seedingTimeEta < 0)
1266 seedingTimeEta = 0;
1269 qlonglong inactiveSeedingTimeEta = MAX_ETA;
1271 if (maxInactiveSeedingTimeValue >= 0)
1273 inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
1274 inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
1277 return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
1280 if (!speedAverage.download) return MAX_ETA;
1282 return (wantedSize() - completedSize()) / speedAverage.download;
1285 QVector<qreal> TorrentImpl::filesProgress() const
1287 if (!hasMetadata())
1288 return {};
1290 const int count = m_filesProgress.size();
1291 Q_ASSERT(count == filesCount());
1292 if (count != filesCount()) [[unlikely]]
1293 return {};
1295 if (m_completedFiles.count(true) == count)
1296 return QVector<qreal>(count, 1);
1298 QVector<qreal> result;
1299 result.reserve(count);
1300 for (int i = 0; i < count; ++i)
1302 const int64_t progress = m_filesProgress.at(i);
1303 const int64_t size = fileSize(i);
1304 if ((size <= 0) || (progress == size))
1305 result << 1;
1306 else
1307 result << (progress / static_cast<qreal>(size));
1310 return result;
1313 int TorrentImpl::seedsCount() const
1315 return m_nativeStatus.num_seeds;
1318 int TorrentImpl::peersCount() const
1320 return m_nativeStatus.num_peers;
1323 int TorrentImpl::leechsCount() const
1325 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1328 int TorrentImpl::totalSeedsCount() const
1330 return (m_nativeStatus.num_complete > -1) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1333 int TorrentImpl::totalPeersCount() const
1335 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1336 return (peers > -1) ? peers : m_nativeStatus.list_peers;
1339 int TorrentImpl::totalLeechersCount() const
1341 return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1344 QDateTime TorrentImpl::lastSeenComplete() const
1346 if (m_nativeStatus.last_seen_complete > 0)
1347 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1348 else
1349 return {};
1352 QDateTime TorrentImpl::completedTime() const
1354 if (m_nativeStatus.completed_time > 0)
1355 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1356 else
1357 return {};
1360 qlonglong TorrentImpl::timeSinceUpload() const
1362 if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
1363 return -1;
1364 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1367 qlonglong TorrentImpl::timeSinceDownload() const
1369 if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
1370 return -1;
1371 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1374 qlonglong TorrentImpl::timeSinceActivity() const
1376 const qlonglong upTime = timeSinceUpload();
1377 const qlonglong downTime = timeSinceDownload();
1378 return ((upTime < 0) != (downTime < 0))
1379 ? std::max(upTime, downTime)
1380 : std::min(upTime, downTime);
1383 int TorrentImpl::downloadLimit() const
1385 return m_downloadLimit;;
1388 int TorrentImpl::uploadLimit() const
1390 return m_uploadLimit;
1393 bool TorrentImpl::superSeeding() const
1395 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::super_seeding);
1398 bool TorrentImpl::isDHTDisabled() const
1400 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_dht);
1403 bool TorrentImpl::isPEXDisabled() const
1405 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_pex);
1408 bool TorrentImpl::isLSDDisabled() const
1410 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
1413 QVector<PeerInfo> TorrentImpl::peers() const
1415 std::vector<lt::peer_info> nativePeers;
1416 m_nativeHandle.get_peer_info(nativePeers);
1418 QVector<PeerInfo> peers;
1419 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
1421 for (const lt::peer_info &peer : nativePeers)
1422 peers.append(PeerInfo(peer, pieces()));
1424 return peers;
1427 QBitArray TorrentImpl::pieces() const
1429 return m_pieces;
1432 QBitArray TorrentImpl::downloadingPieces() const
1434 QBitArray result(piecesCount());
1436 std::vector<lt::partial_piece_info> queue;
1437 m_nativeHandle.get_download_queue(queue);
1439 for (const lt::partial_piece_info &info : queue)
1440 result.setBit(LT::toUnderlyingType(info.piece_index));
1442 return result;
1445 QVector<int> TorrentImpl::pieceAvailability() const
1447 std::vector<int> avail;
1448 m_nativeHandle.piece_availability(avail);
1450 return {avail.cbegin(), avail.cend()};
1453 qreal TorrentImpl::distributedCopies() const
1455 return m_nativeStatus.distributed_copies;
1458 qreal TorrentImpl::maxRatio() const
1460 if (m_ratioLimit == USE_GLOBAL_RATIO)
1461 return m_session->globalMaxRatio();
1463 return m_ratioLimit;
1466 int TorrentImpl::maxSeedingTime() const
1468 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1469 return m_session->globalMaxSeedingMinutes();
1471 return m_seedingTimeLimit;
1474 int TorrentImpl::maxInactiveSeedingTime() const
1476 if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
1477 return m_session->globalMaxInactiveSeedingMinutes();
1479 return m_inactiveSeedingTimeLimit;
1482 qreal TorrentImpl::realRatio() const
1484 const int64_t upload = m_nativeStatus.all_time_upload;
1485 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1486 const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01))
1487 ? m_nativeStatus.total_done
1488 : m_nativeStatus.all_time_download;
1490 if (download == 0)
1491 return (upload == 0) ? 0 : MAX_RATIO;
1493 const qreal ratio = upload / static_cast<qreal>(download);
1494 Q_ASSERT(ratio >= 0);
1495 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1498 int TorrentImpl::uploadPayloadRate() const
1500 // workaround: suppress the speed for paused state
1501 return isPaused() ? 0 : m_nativeStatus.upload_payload_rate;
1504 int TorrentImpl::downloadPayloadRate() const
1506 // workaround: suppress the speed for paused state
1507 return isPaused() ? 0 : m_nativeStatus.download_payload_rate;
1510 qlonglong TorrentImpl::totalPayloadUpload() const
1512 return m_nativeStatus.total_payload_upload;
1515 qlonglong TorrentImpl::totalPayloadDownload() const
1517 return m_nativeStatus.total_payload_download;
1520 int TorrentImpl::connectionsCount() const
1522 return m_nativeStatus.num_connections;
1525 int TorrentImpl::connectionsLimit() const
1527 return m_nativeStatus.connections_limit;
1530 qlonglong TorrentImpl::nextAnnounce() const
1532 return lt::total_seconds(m_nativeStatus.next_announce);
1535 void TorrentImpl::setName(const QString &name)
1537 if (m_name != name)
1539 m_name = name;
1540 deferredRequestResumeData();
1541 m_session->handleTorrentNameChanged(this);
1545 bool TorrentImpl::setCategory(const QString &category)
1547 if (m_category != category)
1549 if (!category.isEmpty() && !m_session->categories().contains(category))
1550 return false;
1552 const QString oldCategory = m_category;
1553 m_category = category;
1554 deferredRequestResumeData();
1555 m_session->handleTorrentCategoryChanged(this, oldCategory);
1557 if (m_useAutoTMM)
1559 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1560 adjustStorageLocation();
1561 else
1562 setAutoTMMEnabled(false);
1566 return true;
1569 void TorrentImpl::forceReannounce(const int index)
1571 m_nativeHandle.force_reannounce(0, index);
1574 void TorrentImpl::forceDHTAnnounce()
1576 m_nativeHandle.force_dht_announce();
1579 void TorrentImpl::forceRecheck()
1581 if (!hasMetadata())
1582 return;
1584 m_nativeHandle.force_recheck();
1585 // We have to force update the cached state, otherwise someone will be able to get
1586 // an incorrect one during the interval until the cached state is updated in a regular way.
1587 m_nativeStatus.state = lt::torrent_status::checking_resume_data;
1589 m_hasMissingFiles = false;
1590 m_unchecked = false;
1592 m_completedFiles.fill(false);
1593 m_filesProgress.fill(0);
1594 m_pieces.fill(false);
1595 m_nativeStatus.pieces.clear_all();
1596 m_nativeStatus.num_pieces = 0;
1598 if (isPaused())
1600 // When "force recheck" is applied on paused torrent, we temporarily resume it
1601 resume();
1602 m_stopCondition = StopCondition::FilesChecked;
1606 void TorrentImpl::setSequentialDownload(const bool enable)
1608 if (enable)
1610 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1611 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1613 else
1615 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1616 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1619 deferredRequestResumeData();
1622 void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
1624 if (m_hasFirstLastPiecePriority == enabled)
1625 return;
1627 m_hasFirstLastPiecePriority = enabled;
1628 if (hasMetadata())
1629 applyFirstLastPiecePriority(enabled);
1631 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1632 .arg((enabled ? tr("On") : tr("Off")), name()));
1634 deferredRequestResumeData();
1637 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
1639 Q_ASSERT(hasMetadata());
1641 // Download first and last pieces first for every file in the torrent
1643 auto piecePriorities = std::vector<lt::download_priority_t>(m_torrentInfo.piecesCount(), LT::toNative(DownloadPriority::Ignored));
1645 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1646 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1647 for (int fileIndex = 0; fileIndex < m_filePriorities.size(); ++fileIndex)
1649 const DownloadPriority filePrio = m_filePriorities[fileIndex];
1650 if (filePrio <= DownloadPriority::Ignored)
1651 continue;
1653 // Determine the priority to set
1654 const lt::download_priority_t piecePrio = LT::toNative(enabled ? DownloadPriority::Maximum : filePrio);
1655 const TorrentInfo::PieceRange pieceRange = m_torrentInfo.filePieces(fileIndex);
1657 // worst case: AVI index = 1% of total file size (at the end of the file)
1658 const int numPieces = std::ceil(fileSize(fileIndex) * 0.01 / pieceLength());
1659 for (int i = 0; i < numPieces; ++i)
1661 piecePriorities[pieceRange.first() + i] = piecePrio;
1662 piecePriorities[pieceRange.last() - i] = piecePrio;
1665 const int firstPiece = pieceRange.first() + numPieces;
1666 const int lastPiece = pieceRange.last() - numPieces;
1667 for (int pieceIndex = firstPiece; pieceIndex <= lastPiece; ++pieceIndex)
1668 piecePriorities[pieceIndex] = LT::toNative(filePrio);
1671 m_nativeHandle.prioritize_pieces(piecePriorities);
1674 void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
1676 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1677 endReceivedMetadataHandling(savePath, fileNames);
1680 TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
1682 const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
1683 , [&announceEntry](const TrackerEntry &trackerEntry)
1685 return (trackerEntry.url == QString::fromStdString(announceEntry.url));
1688 Q_ASSERT(it != m_trackerEntries.end());
1689 if (it == m_trackerEntries.end()) [[unlikely]]
1690 return {};
1692 #ifdef QBT_USES_LIBTORRENT2
1693 QSet<int> btProtocols;
1694 const auto &infoHashes = nativeHandle().info_hashes();
1695 if (infoHashes.has(lt::protocol_version::V1))
1696 btProtocols.insert(1);
1697 if (infoHashes.has(lt::protocol_version::V2))
1698 btProtocols.insert(2);
1699 #else
1700 const QSet<int> btProtocols {1};
1701 #endif
1702 ::updateTrackerEntry(*it, announceEntry, btProtocols, updateInfo);
1703 return *it;
1706 void TorrentImpl::resetTrackerEntries()
1708 for (auto &trackerEntry : m_trackerEntries)
1709 trackerEntry = {trackerEntry.url, trackerEntry.tier};
1712 std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
1714 Q_ASSERT(!m_nativeStatus.torrent_file.expired());
1716 return m_nativeStatus.torrent_file.lock();
1719 void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
1721 Q_ASSERT(m_maintenanceJob == MaintenanceJob::HandleMetadata);
1722 if (m_maintenanceJob != MaintenanceJob::HandleMetadata) [[unlikely]]
1723 return;
1725 Q_ASSERT(m_filePaths.isEmpty());
1726 if (!m_filePaths.isEmpty()) [[unlikely]]
1727 m_filePaths.clear();
1729 lt::add_torrent_params &p = m_ltAddTorrentParams;
1731 const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1732 m_torrentInfo = TorrentInfo(*metadata);
1733 m_filePriorities.reserve(filesCount());
1734 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
1735 p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
1736 , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
1738 m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
1739 m_filesProgress.resize(filesCount());
1740 updateProgress();
1742 for (int i = 0; i < fileNames.size(); ++i)
1744 const auto nativeIndex = nativeIndexes.at(i);
1746 const Path &actualFilePath = fileNames.at(i);
1747 p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
1749 const Path filePath = actualFilePath.removedExtension(QB_EXT);
1750 m_filePaths.append(filePath);
1752 lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
1753 if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
1754 nativePriority = lt::dont_download;
1755 const auto priority = LT::fromNative(nativePriority);
1756 m_filePriorities.append(priority);
1758 p.save_path = savePath.toString().toStdString();
1759 p.ti = metadata;
1761 if (stopCondition() == StopCondition::MetadataReceived)
1763 m_stopCondition = StopCondition::None;
1765 m_isStopped = true;
1766 p.flags |= lt::torrent_flags::paused;
1767 p.flags &= ~lt::torrent_flags::auto_managed;
1769 m_session->handleTorrentPaused(this);
1772 reload();
1774 // If first/last piece priority was specified when adding this torrent,
1775 // we should apply it now that we have metadata:
1776 if (m_hasFirstLastPiecePriority)
1777 applyFirstLastPiecePriority(true);
1779 m_maintenanceJob = MaintenanceJob::None;
1780 prepareResumeData(p);
1782 m_session->handleTorrentMetadataReceived(this);
1785 void TorrentImpl::reload()
1789 m_completedFiles.fill(false);
1790 m_filesProgress.fill(0);
1791 m_pieces.fill(false);
1792 m_nativeStatus.pieces.clear_all();
1793 m_nativeStatus.num_pieces = 0;
1795 const auto queuePos = m_nativeHandle.queue_position();
1797 m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
1799 lt::add_torrent_params p = m_ltAddTorrentParams;
1800 p.flags |= lt::torrent_flags::update_subscribe
1801 | lt::torrent_flags::override_trackers
1802 | lt::torrent_flags::override_web_seeds;
1804 if (m_isStopped)
1806 p.flags |= lt::torrent_flags::paused;
1807 p.flags &= ~lt::torrent_flags::auto_managed;
1809 else if (m_operatingMode == TorrentOperatingMode::AutoManaged)
1811 p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1813 else
1815 p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1818 auto *const extensionData = new ExtensionData;
1819 p.userdata = LTClientData(extensionData);
1820 m_nativeHandle = m_nativeSession->add_torrent(p);
1822 m_nativeStatus = extensionData->status;
1824 if (queuePos >= lt::queue_position_t {})
1825 m_nativeHandle.queue_position_set(queuePos);
1826 m_nativeStatus.queue_position = queuePos;
1828 updateState();
1830 catch (const lt::system_error &err)
1832 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1833 .arg(id().toString(), QString::fromLocal8Bit(err.what())));
1837 void TorrentImpl::pause()
1839 if (!m_isStopped)
1841 m_stopCondition = StopCondition::None;
1842 m_isStopped = true;
1843 deferredRequestResumeData();
1844 m_session->handleTorrentPaused(this);
1847 if (m_maintenanceJob == MaintenanceJob::None)
1849 setAutoManaged(false);
1850 m_nativeHandle.pause();
1852 m_payloadRateMonitor.reset();
1856 void TorrentImpl::resume(const TorrentOperatingMode mode)
1858 if (hasError())
1860 m_nativeHandle.clear_error();
1861 m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode);
1864 m_operatingMode = mode;
1866 if (m_hasMissingFiles)
1868 m_hasMissingFiles = false;
1869 m_isStopped = false;
1870 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1871 reload();
1872 return;
1875 if (m_isStopped)
1877 m_isStopped = false;
1878 deferredRequestResumeData();
1879 m_session->handleTorrentResumed(this);
1882 if (m_maintenanceJob == MaintenanceJob::None)
1884 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1885 if (m_operatingMode == TorrentOperatingMode::Forced)
1886 m_nativeHandle.resume();
1890 void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
1892 if (!hasMetadata())
1894 m_savePath = newPath;
1895 m_session->handleTorrentSavePathChanged(this);
1896 return;
1899 const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
1900 ? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
1901 if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
1903 if (!m_storageIsMoving)
1905 m_storageIsMoving = true;
1906 updateState();
1907 m_session->handleTorrentStorageMovingStateChanged(this);
1912 void TorrentImpl::renameFile(const int index, const Path &path)
1914 Q_ASSERT((index >= 0) && (index < filesCount()));
1915 if ((index < 0) || (index >= filesCount())) [[unlikely]]
1916 return;
1918 const Path targetActualPath = makeActualPath(index, path);
1919 doRenameFile(index, targetActualPath);
1922 void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
1924 updateStatus(nativeStatus);
1927 void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
1929 if (context == MoveStorageContext::ChangeSavePath)
1930 m_savePath = path;
1931 else if (context == MoveStorageContext::ChangeDownloadPath)
1932 m_downloadPath = path;
1933 m_storageIsMoving = hasOutstandingJob;
1934 m_nativeStatus.save_path = path.toString().toStdString();
1936 m_session->handleTorrentSavePathChanged(this);
1937 deferredRequestResumeData();
1939 if (!m_storageIsMoving)
1941 updateState();
1942 m_session->handleTorrentStorageMovingStateChanged(this);
1944 if (m_hasMissingFiles)
1946 // it can be moved to the proper location
1947 m_hasMissingFiles = false;
1948 m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
1949 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1950 reload();
1953 while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1954 std::invoke(m_moveFinishedTriggers.dequeue());
1958 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_checked_alert *p)
1960 if (!hasMetadata())
1962 // The torrent is checked due to metadata received, but we should not process
1963 // this event until the torrent is reloaded using the received metadata.
1964 return;
1967 if (stopCondition() == StopCondition::FilesChecked)
1968 pause();
1970 m_statusUpdatedTriggers.enqueue([this]()
1972 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1974 if (!m_hasMissingFiles)
1976 if ((progress() < 1.0) && (wantedSize() > 0))
1977 m_hasFinishedStatus = false;
1978 else if (progress() == 1.0)
1979 m_hasFinishedStatus = true;
1981 adjustStorageLocation();
1982 manageActualFilePaths();
1984 if (!isPaused())
1986 // torrent is internally paused using NativeTorrentExtension after files checked
1987 // so we need to resume it if there is no corresponding "stop condition" set
1988 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1989 if (m_operatingMode == TorrentOperatingMode::Forced)
1990 m_nativeHandle.resume();
1994 if (m_nativeStatus.need_save_resume)
1995 deferredRequestResumeData();
1997 m_session->handleTorrentChecked(this);
2001 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused]] const lt::torrent_finished_alert *p)
2003 m_hasMissingFiles = false;
2004 if (m_hasFinishedStatus)
2005 return;
2007 m_statusUpdatedTriggers.enqueue([this]()
2009 adjustStorageLocation();
2010 manageActualFilePaths();
2012 deferredRequestResumeData();
2014 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
2015 if (recheckTorrentsOnCompletion && m_unchecked)
2017 forceRecheck();
2019 else
2021 m_hasFinishedStatus = true;
2023 if (isMoveInProgress() || (m_renameCount > 0))
2024 m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
2025 else
2026 m_session->handleTorrentFinished(this);
2031 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused]] const lt::torrent_paused_alert *p)
2035 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused]] const lt::torrent_resumed_alert *p)
2039 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
2041 if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
2043 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2044 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2045 // than the list we usually cache, so we have to request it from the appropriate source.
2046 fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
2049 if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
2051 Q_ASSERT(m_indexMap.isEmpty());
2053 const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
2054 m_ltAddTorrentParams = p->params;
2055 if (isSeedMode)
2056 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2058 m_ltAddTorrentParams.have_pieces.clear();
2059 m_ltAddTorrentParams.verified_pieces.clear();
2061 m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti;
2063 const auto metadata = TorrentInfo(*m_ltAddTorrentParams.ti);
2065 const auto &renamedFiles = m_ltAddTorrentParams.renamed_files;
2066 PathList filePaths = metadata.filePaths();
2067 if (renamedFiles.empty() && (m_contentLayout != TorrentContentLayout::Original))
2069 const Path originalRootFolder = Path::findRootFolder(filePaths);
2070 const auto originalContentLayout = (originalRootFolder.isEmpty()
2071 ? TorrentContentLayout::NoSubfolder : TorrentContentLayout::Subfolder);
2072 if (m_contentLayout != originalContentLayout)
2074 if (m_contentLayout == TorrentContentLayout::NoSubfolder)
2075 Path::stripRootFolder(filePaths);
2076 else
2077 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2081 const auto nativeIndexes = metadata.nativeIndexes();
2082 m_indexMap.reserve(filePaths.size());
2083 for (int i = 0; i < filePaths.size(); ++i)
2085 const auto nativeIndex = nativeIndexes.at(i);
2086 m_indexMap[nativeIndex] = i;
2088 if (const auto it = renamedFiles.find(nativeIndex); it != renamedFiles.cend())
2089 filePaths[i] = Path(it->second);
2092 m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
2094 else
2096 prepareResumeData(p->params);
2100 void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
2102 if (m_hasMissingFiles)
2104 const auto havePieces = m_ltAddTorrentParams.have_pieces;
2105 const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
2106 const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
2108 // Update recent resume data but preserve existing progress
2109 m_ltAddTorrentParams = params;
2110 m_ltAddTorrentParams.have_pieces = havePieces;
2111 m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
2112 m_ltAddTorrentParams.verified_pieces = verifiedPieces;
2114 else
2116 const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
2117 // Update recent resume data
2118 m_ltAddTorrentParams = params;
2119 if (preserveSeedMode)
2120 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2123 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2124 m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
2126 LoadTorrentParams resumeData;
2127 resumeData.name = m_name;
2128 resumeData.category = m_category;
2129 resumeData.tags = m_tags;
2130 resumeData.contentLayout = m_contentLayout;
2131 resumeData.ratioLimit = m_ratioLimit;
2132 resumeData.seedingTimeLimit = m_seedingTimeLimit;
2133 resumeData.inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit;
2134 resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
2135 resumeData.hasFinishedStatus = m_hasFinishedStatus;
2136 resumeData.stopped = m_isStopped;
2137 resumeData.stopCondition = m_stopCondition;
2138 resumeData.operatingMode = m_operatingMode;
2139 resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
2140 resumeData.useAutoTMM = m_useAutoTMM;
2141 if (!resumeData.useAutoTMM)
2143 resumeData.savePath = m_savePath;
2144 resumeData.downloadPath = m_downloadPath;
2147 m_session->handleTorrentResumeDataReady(this, resumeData);
2150 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
2152 if (p->error != lt::errors::resume_data_not_modified)
2154 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2155 .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
2159 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
2161 // Files were probably moved or storage isn't accessible
2162 m_hasMissingFiles = true;
2163 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2164 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
2167 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
2169 const int fileIndex = m_indexMap.value(p->index, -1);
2170 Q_ASSERT(fileIndex >= 0);
2172 const Path newActualFilePath {QString::fromUtf8(p->new_name())};
2174 const Path oldFilePath = m_filePaths.at(fileIndex);
2175 const Path newFilePath = makeUserPath(newActualFilePath);
2177 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2178 // We should compare path in a case sensitive manner even on case insensitive
2179 // platforms since it can be renamed by only changing case of some character(s)
2180 if (oldFilePath.data() == newFilePath.data())
2182 // Remove empty ".unwanted" folders
2183 #ifdef QBT_USES_LIBTORRENT2
2184 const Path oldActualFilePath {QString::fromUtf8(p->old_name())};
2185 #else
2186 const Path oldActualFilePath;
2187 #endif
2188 const Path oldActualParentPath = oldActualFilePath.parentPath();
2189 const Path newActualParentPath = newActualFilePath.parentPath();
2190 if (newActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2192 if (oldActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2194 #ifdef Q_OS_WIN
2195 const std::wstring winPath = (actualStorageLocation() / newActualParentPath).toString().toStdWString();
2196 const DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str());
2197 ::SetFileAttributesW(winPath.c_str(), (dwAttrs | FILE_ATTRIBUTE_HIDDEN));
2198 #endif
2201 #ifdef QBT_USES_LIBTORRENT2
2202 else if (oldActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2204 if (newActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2205 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath);
2207 #else
2208 else
2210 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath / Path(UNWANTED_FOLDER_NAME));
2212 #endif
2214 else
2216 m_filePaths[fileIndex] = newFilePath;
2218 // Remove empty leftover folders
2219 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2220 // be removed if they are empty
2221 Path oldParentPath = oldFilePath.parentPath();
2222 const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath());
2223 while (oldParentPath != commonBasePath)
2225 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath);
2226 oldParentPath = oldParentPath.parentPath();
2230 --m_renameCount;
2231 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2232 m_moveFinishedTriggers.takeFirst()();
2234 deferredRequestResumeData();
2237 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
2239 const int fileIndex = m_indexMap.value(p->index, -1);
2240 Q_ASSERT(fileIndex >= 0);
2242 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2243 .arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
2245 --m_renameCount;
2246 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2247 m_moveFinishedTriggers.takeFirst()();
2249 deferredRequestResumeData();
2252 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
2254 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2255 return;
2257 const int fileIndex = m_indexMap.value(p->index, -1);
2258 Q_ASSERT(fileIndex >= 0);
2260 m_completedFiles.setBit(fileIndex);
2262 const Path actualPath = actualFilePath(fileIndex);
2264 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2265 // only apply Mark-of-the-Web to new download files
2266 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2268 const Path fullpath = actualStorageLocation() / actualPath;
2269 Utils::OS::applyMarkOfTheWeb(fullpath);
2271 #endif // Q_OS_MACOS || Q_OS_WIN
2273 if (m_session->isAppendExtensionEnabled())
2275 const Path path = filePath(fileIndex);
2276 if (actualPath != path)
2278 qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
2279 doRenameFile(fileIndex, path);
2284 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
2286 m_lastFileError = {p->error, p->op};
2289 #ifdef QBT_USES_LIBTORRENT2
2290 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
2292 deferredRequestResumeData();
2294 #endif
2296 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused]] const lt::metadata_received_alert *p)
2298 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2300 #ifdef QBT_USES_LIBTORRENT2
2301 const InfoHash prevInfoHash = infoHash();
2302 m_infoHash = InfoHash(m_nativeHandle.info_hashes());
2303 if (prevInfoHash != infoHash())
2304 m_session->handleTorrentInfoHashChanged(this, prevInfoHash);
2305 #endif
2307 m_maintenanceJob = MaintenanceJob::HandleMetadata;
2308 deferredRequestResumeData();
2311 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
2313 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))
2314 , Log::INFO);
2317 void TorrentImpl::handleCategoryOptionsChanged()
2319 if (m_useAutoTMM)
2320 adjustStorageLocation();
2323 void TorrentImpl::handleAppendExtensionToggled()
2325 if (!hasMetadata())
2326 return;
2328 manageActualFilePaths();
2331 void TorrentImpl::handleUnwantedFolderToggled()
2333 if (!hasMetadata())
2334 return;
2336 manageActualFilePaths();
2339 void TorrentImpl::handleAlert(const lt::alert *a)
2341 switch (a->type())
2343 #ifdef QBT_USES_LIBTORRENT2
2344 case lt::file_prio_alert::alert_type:
2345 handleFilePrioAlert(static_cast<const lt::file_prio_alert*>(a));
2346 break;
2347 #endif
2348 case lt::file_renamed_alert::alert_type:
2349 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
2350 break;
2351 case lt::file_rename_failed_alert::alert_type:
2352 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
2353 break;
2354 case lt::file_completed_alert::alert_type:
2355 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
2356 break;
2357 case lt::file_error_alert::alert_type:
2358 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
2359 break;
2360 case lt::torrent_finished_alert::alert_type:
2361 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
2362 break;
2363 case lt::save_resume_data_alert::alert_type:
2364 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
2365 break;
2366 case lt::save_resume_data_failed_alert::alert_type:
2367 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
2368 break;
2369 case lt::torrent_paused_alert::alert_type:
2370 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
2371 break;
2372 case lt::torrent_resumed_alert::alert_type:
2373 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
2374 break;
2375 case lt::metadata_received_alert::alert_type:
2376 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2377 break;
2378 case lt::fastresume_rejected_alert::alert_type:
2379 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2380 break;
2381 case lt::torrent_checked_alert::alert_type:
2382 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2383 break;
2384 case lt::performance_alert::alert_type:
2385 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2386 break;
2390 void TorrentImpl::manageActualFilePaths()
2392 const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
2393 const lt::file_storage &nativeFiles = nativeInfo->files();
2395 for (int i = 0; i < filesCount(); ++i)
2397 const Path path = filePath(i);
2399 const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
2400 const Path actualPath {nativeFiles.file_path(nativeIndex)};
2401 const Path targetActualPath = makeActualPath(i, path);
2402 if (actualPath != targetActualPath)
2404 qDebug() << "Renaming" << actualPath.toString() << "to" << targetActualPath.toString();
2405 doRenameFile(i, targetActualPath);
2410 void TorrentImpl::adjustStorageLocation()
2412 const Path downloadPath = this->downloadPath();
2413 const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
2415 if ((targetPath != actualStorageLocation()) || isMoveInProgress())
2416 moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
2419 void TorrentImpl::doRenameFile(const int index, const Path &path)
2421 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
2423 Q_ASSERT(index >= 0);
2424 Q_ASSERT(index < nativeIndexes.size());
2425 if ((index < 0) || (index >= nativeIndexes.size())) [[unlikely]]
2426 return;
2428 ++m_renameCount;
2429 m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
2432 lt::torrent_handle TorrentImpl::nativeHandle() const
2434 return m_nativeHandle;
2437 void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
2439 if (hasMetadata())
2440 return;
2442 m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
2446 #ifdef QBT_USES_LIBTORRENT2
2447 nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
2448 #else
2449 const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
2450 nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
2451 #endif
2453 catch (const std::exception &) {}
2457 Torrent::StopCondition TorrentImpl::stopCondition() const
2459 return m_stopCondition;
2462 void TorrentImpl::setStopCondition(const StopCondition stopCondition)
2464 if (stopCondition == m_stopCondition)
2465 return;
2467 if (isPaused())
2468 return;
2470 if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata())
2471 return;
2473 if ((stopCondition == StopCondition::FilesChecked) && hasMetadata() && !isChecking())
2474 return;
2476 m_stopCondition = stopCondition;
2479 bool TorrentImpl::isMoveInProgress() const
2481 return m_storageIsMoving;
2484 void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
2486 const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
2488 if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
2489 updateProgress();
2491 updateState();
2493 m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
2494 , nativeStatus.upload_payload_rate});
2496 if (hasMetadata())
2498 // NOTE: Don't change the order of these conditionals!
2499 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2500 if (isChecking())
2501 m_unchecked = false;
2502 else if (isDownloading())
2503 m_unchecked = true;
2506 while (!m_statusUpdatedTriggers.isEmpty())
2507 std::invoke(m_statusUpdatedTriggers.dequeue());
2510 void TorrentImpl::updateProgress()
2512 Q_ASSERT(hasMetadata());
2513 if (!hasMetadata()) [[unlikely]]
2514 return;
2516 Q_ASSERT(!m_filesProgress.isEmpty());
2517 if (m_filesProgress.isEmpty()) [[unlikely]]
2518 m_filesProgress.resize(filesCount());
2520 const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
2521 const QBitArray newPieces = m_pieces ^ oldPieces;
2523 const int64_t pieceSize = m_torrentInfo.pieceLength();
2524 for (qsizetype index = 0; index < newPieces.size(); ++index)
2526 if (!newPieces.at(index))
2527 continue;
2529 int64_t size = m_torrentInfo.pieceLength(index);
2530 int64_t pieceOffset = index * pieceSize;
2532 for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
2534 const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
2535 const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
2537 m_filesProgress[fileIndex] += add;
2539 size -= add;
2540 if (size <= 0)
2541 break;
2543 pieceOffset += add;
2548 void TorrentImpl::setRatioLimit(qreal limit)
2550 if (limit < USE_GLOBAL_RATIO)
2551 limit = NO_RATIO_LIMIT;
2552 else if (limit > MAX_RATIO)
2553 limit = MAX_RATIO;
2555 if (m_ratioLimit != limit)
2557 m_ratioLimit = limit;
2558 deferredRequestResumeData();
2559 m_session->handleTorrentShareLimitChanged(this);
2563 void TorrentImpl::setSeedingTimeLimit(int limit)
2565 if (limit < USE_GLOBAL_SEEDING_TIME)
2566 limit = NO_SEEDING_TIME_LIMIT;
2567 else if (limit > MAX_SEEDING_TIME)
2568 limit = MAX_SEEDING_TIME;
2570 if (m_seedingTimeLimit != limit)
2572 m_seedingTimeLimit = limit;
2573 deferredRequestResumeData();
2574 m_session->handleTorrentShareLimitChanged(this);
2578 void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
2580 if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
2581 limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
2582 else if (limit > MAX_INACTIVE_SEEDING_TIME)
2583 limit = MAX_SEEDING_TIME;
2585 if (m_inactiveSeedingTimeLimit != limit)
2587 m_inactiveSeedingTimeLimit = limit;
2588 deferredRequestResumeData();
2589 m_session->handleTorrentShareLimitChanged(this);
2593 void TorrentImpl::setUploadLimit(const int limit)
2595 const int cleanValue = cleanLimitValue(limit);
2596 if (cleanValue == uploadLimit())
2597 return;
2599 m_uploadLimit = cleanValue;
2600 m_nativeHandle.set_upload_limit(m_uploadLimit);
2601 deferredRequestResumeData();
2604 void TorrentImpl::setDownloadLimit(const int limit)
2606 const int cleanValue = cleanLimitValue(limit);
2607 if (cleanValue == downloadLimit())
2608 return;
2610 m_downloadLimit = cleanValue;
2611 m_nativeHandle.set_download_limit(m_downloadLimit);
2612 deferredRequestResumeData();
2615 void TorrentImpl::setSuperSeeding(const bool enable)
2617 if (enable == superSeeding())
2618 return;
2620 if (enable)
2621 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2622 else
2623 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2625 deferredRequestResumeData();
2628 void TorrentImpl::setDHTDisabled(const bool disable)
2630 if (disable == isDHTDisabled())
2631 return;
2633 if (disable)
2634 m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
2635 else
2636 m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
2638 deferredRequestResumeData();
2641 void TorrentImpl::setPEXDisabled(const bool disable)
2643 if (disable == isPEXDisabled())
2644 return;
2646 if (disable)
2647 m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
2648 else
2649 m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
2651 deferredRequestResumeData();
2654 void TorrentImpl::setLSDDisabled(const bool disable)
2656 if (disable == isLSDDisabled())
2657 return;
2659 if (disable)
2660 m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
2661 else
2662 m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
2664 deferredRequestResumeData();
2667 void TorrentImpl::flushCache() const
2669 m_nativeHandle.flush_cache();
2672 QString TorrentImpl::createMagnetURI() const
2674 QString ret = u"magnet:?"_s;
2676 const SHA1Hash infoHash1 = infoHash().v1();
2677 if (infoHash1.isValid())
2679 ret += u"xt=urn:btih:" + infoHash1.toString();
2682 const SHA256Hash infoHash2 = infoHash().v2();
2683 if (infoHash2.isValid())
2685 if (infoHash1.isValid())
2686 ret += u'&';
2687 ret += u"xt=urn:btmh:1220" + infoHash2.toString();
2690 const QString displayName = name();
2691 if (displayName != id().toString())
2693 ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
2696 for (const TrackerEntry &tracker : asConst(trackers()))
2698 ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
2701 for (const QUrl &urlSeed : asConst(urlSeeds()))
2703 ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
2706 return ret;
2709 nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
2711 if (!hasMetadata())
2712 return nonstd::make_unexpected(tr("Missing metadata"));
2716 #ifdef QBT_USES_LIBTORRENT2
2717 const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
2718 const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
2719 #else
2720 const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
2721 #endif
2722 lt::create_torrent creator {*torrentInfo};
2724 for (const TrackerEntry &entry : asConst(trackers()))
2725 creator.add_tracker(entry.url.toStdString(), entry.tier);
2727 return creator.generate();
2729 catch (const lt::system_error &err)
2731 return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
2735 nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
2737 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2738 if (!preparationResult)
2739 return preparationResult.get_unexpected();
2741 // usually torrent size should be smaller than 1 MB,
2742 // however there are >100 MB v2/hybrid torrent files out in the wild
2743 QByteArray buffer;
2744 buffer.reserve(1024 * 1024);
2745 lt::bencode(std::back_inserter(buffer), preparationResult.value());
2746 return buffer;
2749 nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) const
2751 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2752 if (!preparationResult)
2753 return preparationResult.get_unexpected();
2755 const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
2756 if (!saveResult)
2757 return saveResult.get_unexpected();
2759 return {};
2762 void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
2764 invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
2768 std::vector<lt::peer_info> nativePeers;
2769 nativeHandle.get_peer_info(nativePeers);
2770 QVector<PeerInfo> peers;
2771 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
2772 for (const lt::peer_info &peer : nativePeers)
2773 peers.append(PeerInfo(peer, allPieces));
2774 return peers;
2776 catch (const std::exception &) {}
2778 return {};
2780 , std::move(resultHandler));
2783 void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
2785 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
2789 const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
2790 QVector<QUrl> urlSeeds;
2791 urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
2792 for (const std::string &urlSeed : currentSeeds)
2793 urlSeeds.append(QString::fromStdString(urlSeed));
2794 return urlSeeds;
2796 catch (const std::exception &) {}
2798 return {};
2800 , std::move(resultHandler));
2803 void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
2805 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
2809 std::vector<int> piecesAvailability;
2810 nativeHandle.piece_availability(piecesAvailability);
2811 return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
2813 catch (const std::exception &) {}
2815 return {};
2817 , std::move(resultHandler));
2820 void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
2822 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
2826 #ifdef QBT_USES_LIBTORRENT2
2827 const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
2828 #else
2829 std::vector<lt::partial_piece_info> queue;
2830 nativeHandle.get_download_queue(queue);
2831 #endif
2832 QBitArray result;
2833 result.resize(torrentInfo.piecesCount());
2834 for (const lt::partial_piece_info &info : queue)
2835 result.setBit(LT::toUnderlyingType(info.piece_index));
2836 return result;
2838 catch (const std::exception &) {}
2840 return {};
2842 , std::move(resultHandler));
2845 void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
2847 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
2849 if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
2850 return {};
2854 std::vector<int> piecesAvailability;
2855 nativeHandle.piece_availability(piecesAvailability);
2856 const int filesCount = torrentInfo.filesCount();
2857 // libtorrent returns empty array for seeding only torrents
2858 if (piecesAvailability.empty())
2859 return QVector<qreal>(filesCount, -1);
2861 QVector<qreal> result;
2862 result.reserve(filesCount);
2863 for (int i = 0; i < filesCount; ++i)
2865 const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
2867 int availablePieces = 0;
2868 for (const int piece : filePieces)
2869 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2871 const qreal availability = filePieces.isEmpty()
2872 ? 1 // the file has no pieces, so it is available by default
2873 : static_cast<qreal>(availablePieces) / filePieces.size();
2874 result.append(availability);
2876 return result;
2878 catch (const std::exception &) {}
2880 return {};
2882 , std::move(resultHandler));
2885 void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
2887 if (!hasMetadata())
2888 return;
2890 Q_ASSERT(priorities.size() == filesCount());
2892 // Reset 'm_hasSeedStatus' if needed in order to react again to
2893 // 'torrent_finished_alert' and eg show tray notifications
2894 const QVector<DownloadPriority> oldPriorities = filePriorities();
2895 for (int i = 0; i < oldPriorities.size(); ++i)
2897 if ((oldPriorities[i] == DownloadPriority::Ignored)
2898 && (priorities[i] > DownloadPriority::Ignored)
2899 && !m_completedFiles.at(i))
2901 m_hasFinishedStatus = false;
2902 break;
2906 const int internalFilesCount = m_torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2907 auto nativePriorities = std::vector<lt::download_priority_t>(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2908 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
2909 for (int i = 0; i < priorities.size(); ++i)
2910 nativePriorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(priorities[i]);
2912 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
2913 m_nativeHandle.prioritize_files(nativePriorities);
2915 m_filePriorities = priorities;
2916 // Restore first/last piece first option if necessary
2917 if (m_hasFirstLastPiecePriority)
2918 applyFirstLastPiecePriority(true);
2919 manageActualFilePaths();
2922 QVector<qreal> TorrentImpl::availableFileFractions() const
2924 Q_ASSERT(hasMetadata());
2926 const int filesCount = this->filesCount();
2927 if (filesCount <= 0) return {};
2929 const QVector<int> piecesAvailability = pieceAvailability();
2930 // libtorrent returns empty array for seeding only torrents
2931 if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1);
2933 QVector<qreal> res;
2934 res.reserve(filesCount);
2935 for (int i = 0; i < filesCount; ++i)
2937 const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
2939 int availablePieces = 0;
2940 for (const int piece : filePieces)
2941 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2943 const qreal availability = filePieces.isEmpty()
2944 ? 1 // the file has no pieces, so it is available by default
2945 : static_cast<qreal>(availablePieces) / filePieces.size();
2946 res.push_back(availability);
2948 return res;
2951 template <typename Func, typename Callback>
2952 void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
2954 m_session->invokeAsync([session = m_session
2955 , func = std::move(func)
2956 , resultHandler = std::move(resultHandler)
2957 , thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
2959 session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
2961 if (thisTorrent)
2962 resultHandler(result);