Fix incorrect usage of translation functions
[qBittorrent.git] / src / base / bittorrent / torrentimpl.cpp
blobc7ed105a534ad87e686377cd416c0c7607451a16
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 m_session->handleTorrentNeedSaveResumeData(this);
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 m_session->handleTorrentNeedSaveResumeData(this);
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 m_session->handleTorrentNeedSaveResumeData(this);
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 m_session->handleTorrentNeedSaveResumeData(this);
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 m_session->handleTorrentNeedSaveResumeData(this);
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 m_session->handleTorrentNeedSaveResumeData(this);
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 session->handleTorrentNeedSaveResumeData(thisTorrent);
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 session->handleTorrentNeedSaveResumeData(thisTorrent);
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::saveResumeData(lt::resume_data_flags_t flags)
804 m_nativeHandle.save_resume_data(flags);
805 m_session->handleTorrentSaveResumeDataRequested(this);
808 int TorrentImpl::filesCount() const
810 return m_torrentInfo.filesCount();
813 int TorrentImpl::piecesCount() const
815 return m_torrentInfo.piecesCount();
818 int TorrentImpl::piecesHave() const
820 return m_nativeStatus.num_pieces;
823 qreal TorrentImpl::progress() const
825 if (isChecking())
826 return m_nativeStatus.progress;
828 if (m_nativeStatus.total_wanted == 0)
829 return 0.;
831 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
832 return 1.;
834 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
835 if ((progress < 0.f) || (progress > 1.f))
837 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
838 .arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
839 , Log::WARNING);
842 return progress;
845 QString TorrentImpl::category() const
847 return m_category;
850 bool TorrentImpl::belongsToCategory(const QString &category) const
852 if (m_category.isEmpty())
853 return category.isEmpty();
855 if (m_category == category)
856 return true;
858 return (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + u'/'));
861 TagSet TorrentImpl::tags() const
863 return m_tags;
866 bool TorrentImpl::hasTag(const Tag &tag) const
868 return m_tags.contains(tag);
871 bool TorrentImpl::addTag(const Tag &tag)
873 if (!tag.isValid())
874 return false;
875 if (hasTag(tag))
876 return false;
878 if (!m_session->hasTag(tag))
880 if (!m_session->addTag(tag))
881 return false;
883 m_tags.insert(tag);
884 m_session->handleTorrentNeedSaveResumeData(this);
885 m_session->handleTorrentTagAdded(this, tag);
886 return true;
889 bool TorrentImpl::removeTag(const Tag &tag)
891 if (m_tags.remove(tag))
893 m_session->handleTorrentNeedSaveResumeData(this);
894 m_session->handleTorrentTagRemoved(this, tag);
895 return true;
897 return false;
900 void TorrentImpl::removeAllTags()
902 for (const Tag &tag : asConst(tags()))
903 removeTag(tag);
906 QDateTime TorrentImpl::addedTime() const
908 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
911 qreal TorrentImpl::ratioLimit() const
913 return m_ratioLimit;
916 int TorrentImpl::seedingTimeLimit() const
918 return m_seedingTimeLimit;
921 int TorrentImpl::inactiveSeedingTimeLimit() const
923 return m_inactiveSeedingTimeLimit;
926 Path TorrentImpl::filePath(const int index) const
928 Q_ASSERT(index >= 0);
929 Q_ASSERT(index < m_filePaths.size());
931 return m_filePaths.value(index, {});
934 Path TorrentImpl::actualFilePath(const int index) const
936 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
938 Q_ASSERT(index >= 0);
939 Q_ASSERT(index < nativeIndexes.size());
940 if ((index < 0) || (index >= nativeIndexes.size()))
941 return {};
943 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes[index]));
946 qlonglong TorrentImpl::fileSize(const int index) const
948 return m_torrentInfo.fileSize(index);
951 PathList TorrentImpl::filePaths() const
953 return m_filePaths;
956 QVector<DownloadPriority> TorrentImpl::filePriorities() const
958 return m_filePriorities;
961 TorrentInfo TorrentImpl::info() const
963 return m_torrentInfo;
966 bool TorrentImpl::isPaused() const
968 return m_isStopped;
971 bool TorrentImpl::isQueued() const
973 // Torrent is Queued if it isn't in Paused state but paused internally
974 return (!isPaused()
975 && (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
976 && (m_nativeStatus.flags & lt::torrent_flags::paused));
979 bool TorrentImpl::isChecking() const
981 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
982 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
985 bool TorrentImpl::isDownloading() const
987 switch (m_state)
989 case TorrentState::Downloading:
990 case TorrentState::DownloadingMetadata:
991 case TorrentState::ForcedDownloadingMetadata:
992 case TorrentState::StalledDownloading:
993 case TorrentState::CheckingDownloading:
994 case TorrentState::PausedDownloading:
995 case TorrentState::QueuedDownloading:
996 case TorrentState::ForcedDownloading:
997 return true;
998 default:
999 break;
1002 return false;
1005 bool TorrentImpl::isMoving() const
1007 return m_state == TorrentState::Moving;
1010 bool TorrentImpl::isUploading() const
1012 switch (m_state)
1014 case TorrentState::Uploading:
1015 case TorrentState::StalledUploading:
1016 case TorrentState::CheckingUploading:
1017 case TorrentState::QueuedUploading:
1018 case TorrentState::ForcedUploading:
1019 return true;
1020 default:
1021 break;
1024 return false;
1027 bool TorrentImpl::isCompleted() const
1029 switch (m_state)
1031 case TorrentState::Uploading:
1032 case TorrentState::StalledUploading:
1033 case TorrentState::CheckingUploading:
1034 case TorrentState::PausedUploading:
1035 case TorrentState::QueuedUploading:
1036 case TorrentState::ForcedUploading:
1037 return true;
1038 default:
1039 break;
1042 return false;
1045 bool TorrentImpl::isActive() const
1047 switch (m_state)
1049 case TorrentState::StalledDownloading:
1050 return (uploadPayloadRate() > 0);
1052 case TorrentState::DownloadingMetadata:
1053 case TorrentState::ForcedDownloadingMetadata:
1054 case TorrentState::Downloading:
1055 case TorrentState::ForcedDownloading:
1056 case TorrentState::Uploading:
1057 case TorrentState::ForcedUploading:
1058 case TorrentState::Moving:
1059 return true;
1061 default:
1062 break;
1065 return false;
1068 bool TorrentImpl::isInactive() const
1070 return !isActive();
1073 bool TorrentImpl::isErrored() const
1075 return ((m_state == TorrentState::MissingFiles)
1076 || (m_state == TorrentState::Error));
1079 bool TorrentImpl::isFinished() const
1081 return ((m_nativeStatus.state == lt::torrent_status::finished)
1082 || (m_nativeStatus.state == lt::torrent_status::seeding));
1085 bool TorrentImpl::isForced() const
1087 return (!isPaused() && (m_operatingMode == TorrentOperatingMode::Forced));
1090 bool TorrentImpl::isSequentialDownload() const
1092 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::sequential_download);
1095 bool TorrentImpl::hasFirstLastPiecePriority() const
1097 return m_hasFirstLastPiecePriority;
1100 TorrentState TorrentImpl::state() const
1102 return m_state;
1105 void TorrentImpl::updateState()
1107 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
1109 m_state = TorrentState::CheckingResumeData;
1111 else if (isMoveInProgress())
1113 m_state = TorrentState::Moving;
1115 else if (hasMissingFiles())
1117 m_state = TorrentState::MissingFiles;
1119 else if (hasError())
1121 m_state = TorrentState::Error;
1123 else if (!hasMetadata())
1125 if (isPaused())
1126 m_state = TorrentState::PausedDownloading;
1127 else if (m_session->isQueueingSystemEnabled() && isQueued())
1128 m_state = TorrentState::QueuedDownloading;
1129 else
1130 m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata;
1132 else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused())
1134 // If the torrent is not just in the "checking" state, but is being actually checked
1135 m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
1137 else if (isFinished())
1139 if (isPaused())
1140 m_state = TorrentState::PausedUploading;
1141 else if (m_session->isQueueingSystemEnabled() && isQueued())
1142 m_state = TorrentState::QueuedUploading;
1143 else if (isForced())
1144 m_state = TorrentState::ForcedUploading;
1145 else if (m_nativeStatus.upload_payload_rate > 0)
1146 m_state = TorrentState::Uploading;
1147 else
1148 m_state = TorrentState::StalledUploading;
1150 else
1152 if (isPaused())
1153 m_state = TorrentState::PausedDownloading;
1154 else if (m_session->isQueueingSystemEnabled() && isQueued())
1155 m_state = TorrentState::QueuedDownloading;
1156 else if (isForced())
1157 m_state = TorrentState::ForcedDownloading;
1158 else if (m_nativeStatus.download_payload_rate > 0)
1159 m_state = TorrentState::Downloading;
1160 else
1161 m_state = TorrentState::StalledDownloading;
1165 bool TorrentImpl::hasMetadata() const
1167 return m_torrentInfo.isValid();
1170 bool TorrentImpl::hasMissingFiles() const
1172 return m_hasMissingFiles;
1175 bool TorrentImpl::hasError() const
1177 return (m_nativeStatus.errc || (m_nativeStatus.flags & lt::torrent_flags::upload_mode));
1180 int TorrentImpl::queuePosition() const
1182 return static_cast<int>(m_nativeStatus.queue_position);
1185 QString TorrentImpl::error() const
1187 if (m_nativeStatus.errc)
1188 return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
1190 if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
1192 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1193 .arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
1196 return {};
1199 qlonglong TorrentImpl::totalDownload() const
1201 return m_nativeStatus.all_time_download;
1204 qlonglong TorrentImpl::totalUpload() const
1206 return m_nativeStatus.all_time_upload;
1209 qlonglong TorrentImpl::activeTime() const
1211 return lt::total_seconds(m_nativeStatus.active_duration);
1214 qlonglong TorrentImpl::finishedTime() const
1216 return lt::total_seconds(m_nativeStatus.finished_duration);
1219 qlonglong TorrentImpl::eta() const
1221 if (isPaused()) return MAX_ETA;
1223 const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
1225 if (isFinished())
1227 const qreal maxRatioValue = maxRatio();
1228 const int maxSeedingTimeValue = maxSeedingTime();
1229 const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
1230 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
1232 qlonglong ratioEta = MAX_ETA;
1234 if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
1237 qlonglong realDL = totalDownload();
1238 if (realDL <= 0)
1239 realDL = wantedSize();
1241 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1244 qlonglong seedingTimeEta = MAX_ETA;
1246 if (maxSeedingTimeValue >= 0)
1248 seedingTimeEta = (maxSeedingTimeValue * 60) - finishedTime();
1249 if (seedingTimeEta < 0)
1250 seedingTimeEta = 0;
1253 qlonglong inactiveSeedingTimeEta = MAX_ETA;
1255 if (maxInactiveSeedingTimeValue >= 0)
1257 inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
1258 inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
1261 return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
1264 if (!speedAverage.download) return MAX_ETA;
1266 return (wantedSize() - completedSize()) / speedAverage.download;
1269 QVector<qreal> TorrentImpl::filesProgress() const
1271 if (!hasMetadata())
1272 return {};
1274 const int count = m_filesProgress.size();
1275 Q_ASSERT(count == filesCount());
1276 if (count != filesCount()) [[unlikely]]
1277 return {};
1279 if (m_completedFiles.count(true) == count)
1280 return QVector<qreal>(count, 1);
1282 QVector<qreal> result;
1283 result.reserve(count);
1284 for (int i = 0; i < count; ++i)
1286 const int64_t progress = m_filesProgress.at(i);
1287 const int64_t size = fileSize(i);
1288 if ((size <= 0) || (progress == size))
1289 result << 1;
1290 else
1291 result << (progress / static_cast<qreal>(size));
1294 return result;
1297 int TorrentImpl::seedsCount() const
1299 return m_nativeStatus.num_seeds;
1302 int TorrentImpl::peersCount() const
1304 return m_nativeStatus.num_peers;
1307 int TorrentImpl::leechsCount() const
1309 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1312 int TorrentImpl::totalSeedsCount() const
1314 return (m_nativeStatus.num_complete > -1) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1317 int TorrentImpl::totalPeersCount() const
1319 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1320 return (peers > -1) ? peers : m_nativeStatus.list_peers;
1323 int TorrentImpl::totalLeechersCount() const
1325 return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1328 QDateTime TorrentImpl::lastSeenComplete() const
1330 if (m_nativeStatus.last_seen_complete > 0)
1331 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1332 else
1333 return {};
1336 QDateTime TorrentImpl::completedTime() const
1338 if (m_nativeStatus.completed_time > 0)
1339 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1340 else
1341 return {};
1344 qlonglong TorrentImpl::timeSinceUpload() const
1346 if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
1347 return -1;
1348 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1351 qlonglong TorrentImpl::timeSinceDownload() const
1353 if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
1354 return -1;
1355 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1358 qlonglong TorrentImpl::timeSinceActivity() const
1360 const qlonglong upTime = timeSinceUpload();
1361 const qlonglong downTime = timeSinceDownload();
1362 return ((upTime < 0) != (downTime < 0))
1363 ? std::max(upTime, downTime)
1364 : std::min(upTime, downTime);
1367 int TorrentImpl::downloadLimit() const
1369 return m_downloadLimit;;
1372 int TorrentImpl::uploadLimit() const
1374 return m_uploadLimit;
1377 bool TorrentImpl::superSeeding() const
1379 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::super_seeding);
1382 bool TorrentImpl::isDHTDisabled() const
1384 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_dht);
1387 bool TorrentImpl::isPEXDisabled() const
1389 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_pex);
1392 bool TorrentImpl::isLSDDisabled() const
1394 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
1397 QVector<PeerInfo> TorrentImpl::peers() const
1399 std::vector<lt::peer_info> nativePeers;
1400 m_nativeHandle.get_peer_info(nativePeers);
1402 QVector<PeerInfo> peers;
1403 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
1405 for (const lt::peer_info &peer : nativePeers)
1406 peers.append(PeerInfo(peer, pieces()));
1408 return peers;
1411 QBitArray TorrentImpl::pieces() const
1413 return m_pieces;
1416 QBitArray TorrentImpl::downloadingPieces() const
1418 QBitArray result(piecesCount());
1420 std::vector<lt::partial_piece_info> queue;
1421 m_nativeHandle.get_download_queue(queue);
1423 for (const lt::partial_piece_info &info : queue)
1424 result.setBit(LT::toUnderlyingType(info.piece_index));
1426 return result;
1429 QVector<int> TorrentImpl::pieceAvailability() const
1431 std::vector<int> avail;
1432 m_nativeHandle.piece_availability(avail);
1434 return {avail.cbegin(), avail.cend()};
1437 qreal TorrentImpl::distributedCopies() const
1439 return m_nativeStatus.distributed_copies;
1442 qreal TorrentImpl::maxRatio() const
1444 if (m_ratioLimit == USE_GLOBAL_RATIO)
1445 return m_session->globalMaxRatio();
1447 return m_ratioLimit;
1450 int TorrentImpl::maxSeedingTime() const
1452 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1453 return m_session->globalMaxSeedingMinutes();
1455 return m_seedingTimeLimit;
1458 int TorrentImpl::maxInactiveSeedingTime() const
1460 if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
1461 return m_session->globalMaxInactiveSeedingMinutes();
1463 return m_inactiveSeedingTimeLimit;
1466 qreal TorrentImpl::realRatio() const
1468 const int64_t upload = m_nativeStatus.all_time_upload;
1469 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1470 const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01))
1471 ? m_nativeStatus.total_done
1472 : m_nativeStatus.all_time_download;
1474 if (download == 0)
1475 return (upload == 0) ? 0 : MAX_RATIO;
1477 const qreal ratio = upload / static_cast<qreal>(download);
1478 Q_ASSERT(ratio >= 0);
1479 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1482 int TorrentImpl::uploadPayloadRate() const
1484 // workaround: suppress the speed for paused state
1485 return isPaused() ? 0 : m_nativeStatus.upload_payload_rate;
1488 int TorrentImpl::downloadPayloadRate() const
1490 // workaround: suppress the speed for paused state
1491 return isPaused() ? 0 : m_nativeStatus.download_payload_rate;
1494 qlonglong TorrentImpl::totalPayloadUpload() const
1496 return m_nativeStatus.total_payload_upload;
1499 qlonglong TorrentImpl::totalPayloadDownload() const
1501 return m_nativeStatus.total_payload_download;
1504 int TorrentImpl::connectionsCount() const
1506 return m_nativeStatus.num_connections;
1509 int TorrentImpl::connectionsLimit() const
1511 return m_nativeStatus.connections_limit;
1514 qlonglong TorrentImpl::nextAnnounce() const
1516 return lt::total_seconds(m_nativeStatus.next_announce);
1519 void TorrentImpl::setName(const QString &name)
1521 if (m_name != name)
1523 m_name = name;
1524 m_session->handleTorrentNeedSaveResumeData(this);
1525 m_session->handleTorrentNameChanged(this);
1529 bool TorrentImpl::setCategory(const QString &category)
1531 if (m_category != category)
1533 if (!category.isEmpty() && !m_session->categories().contains(category))
1534 return false;
1536 const QString oldCategory = m_category;
1537 m_category = category;
1538 m_session->handleTorrentNeedSaveResumeData(this);
1539 m_session->handleTorrentCategoryChanged(this, oldCategory);
1541 if (m_useAutoTMM)
1543 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1544 adjustStorageLocation();
1545 else
1546 setAutoTMMEnabled(false);
1550 return true;
1553 void TorrentImpl::forceReannounce(const int index)
1555 m_nativeHandle.force_reannounce(0, index);
1558 void TorrentImpl::forceDHTAnnounce()
1560 m_nativeHandle.force_dht_announce();
1563 void TorrentImpl::forceRecheck()
1565 if (!hasMetadata())
1566 return;
1568 m_nativeHandle.force_recheck();
1569 // We have to force update the cached state, otherwise someone will be able to get
1570 // an incorrect one during the interval until the cached state is updated in a regular way.
1571 m_nativeStatus.state = lt::torrent_status::checking_resume_data;
1573 m_hasMissingFiles = false;
1574 m_unchecked = false;
1576 m_completedFiles.fill(false);
1577 m_filesProgress.fill(0);
1578 m_pieces.fill(false);
1579 m_nativeStatus.pieces.clear_all();
1580 m_nativeStatus.num_pieces = 0;
1582 if (isPaused())
1584 // When "force recheck" is applied on paused torrent, we temporarily resume it
1585 resume();
1586 m_stopCondition = StopCondition::FilesChecked;
1590 void TorrentImpl::setSequentialDownload(const bool enable)
1592 if (enable)
1594 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1595 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1597 else
1599 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1600 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1603 m_session->handleTorrentNeedSaveResumeData(this);
1606 void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
1608 if (m_hasFirstLastPiecePriority == enabled)
1609 return;
1611 m_hasFirstLastPiecePriority = enabled;
1612 if (hasMetadata())
1613 applyFirstLastPiecePriority(enabled);
1615 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1616 .arg((enabled ? tr("On") : tr("Off")), name()));
1618 m_session->handleTorrentNeedSaveResumeData(this);
1621 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
1623 Q_ASSERT(hasMetadata());
1625 // Download first and last pieces first for every file in the torrent
1627 auto piecePriorities = std::vector<lt::download_priority_t>(m_torrentInfo.piecesCount(), LT::toNative(DownloadPriority::Ignored));
1629 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1630 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1631 for (int fileIndex = 0; fileIndex < m_filePriorities.size(); ++fileIndex)
1633 const DownloadPriority filePrio = m_filePriorities[fileIndex];
1634 if (filePrio <= DownloadPriority::Ignored)
1635 continue;
1637 // Determine the priority to set
1638 const lt::download_priority_t piecePrio = LT::toNative(enabled ? DownloadPriority::Maximum : filePrio);
1639 const TorrentInfo::PieceRange pieceRange = m_torrentInfo.filePieces(fileIndex);
1641 // worst case: AVI index = 1% of total file size (at the end of the file)
1642 const int numPieces = std::ceil(fileSize(fileIndex) * 0.01 / pieceLength());
1643 for (int i = 0; i < numPieces; ++i)
1645 piecePriorities[pieceRange.first() + i] = piecePrio;
1646 piecePriorities[pieceRange.last() - i] = piecePrio;
1649 const int firstPiece = pieceRange.first() + numPieces;
1650 const int lastPiece = pieceRange.last() - numPieces;
1651 for (int pieceIndex = firstPiece; pieceIndex <= lastPiece; ++pieceIndex)
1652 piecePriorities[pieceIndex] = LT::toNative(filePrio);
1655 m_nativeHandle.prioritize_pieces(piecePriorities);
1658 void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
1660 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1661 endReceivedMetadataHandling(savePath, fileNames);
1664 TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
1666 const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
1667 , [&announceEntry](const TrackerEntry &trackerEntry)
1669 return (trackerEntry.url == QString::fromStdString(announceEntry.url));
1672 Q_ASSERT(it != m_trackerEntries.end());
1673 if (it == m_trackerEntries.end()) [[unlikely]]
1674 return {};
1676 #ifdef QBT_USES_LIBTORRENT2
1677 QSet<int> btProtocols;
1678 const auto &infoHashes = nativeHandle().info_hashes();
1679 if (infoHashes.has(lt::protocol_version::V1))
1680 btProtocols.insert(1);
1681 if (infoHashes.has(lt::protocol_version::V2))
1682 btProtocols.insert(2);
1683 #else
1684 const QSet<int> btProtocols {1};
1685 #endif
1686 ::updateTrackerEntry(*it, announceEntry, btProtocols, updateInfo);
1687 return *it;
1690 void TorrentImpl::resetTrackerEntries()
1692 for (auto &trackerEntry : m_trackerEntries)
1693 trackerEntry = {trackerEntry.url, trackerEntry.tier};
1696 std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
1698 if (m_nativeStatus.torrent_file.expired())
1699 m_nativeStatus.torrent_file = m_nativeHandle.torrent_file();
1700 return m_nativeStatus.torrent_file.lock();
1703 void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
1705 Q_ASSERT(m_maintenanceJob == MaintenanceJob::HandleMetadata);
1706 if (m_maintenanceJob != MaintenanceJob::HandleMetadata) [[unlikely]]
1707 return;
1709 Q_ASSERT(m_filePaths.isEmpty());
1710 if (!m_filePaths.isEmpty()) [[unlikely]]
1711 m_filePaths.clear();
1713 lt::add_torrent_params &p = m_ltAddTorrentParams;
1715 const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1716 m_torrentInfo = TorrentInfo(*metadata);
1717 m_filePriorities.reserve(filesCount());
1718 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
1719 p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
1720 , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
1722 m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
1723 m_filesProgress.resize(filesCount());
1724 updateProgress();
1726 for (int i = 0; i < fileNames.size(); ++i)
1728 const auto nativeIndex = nativeIndexes.at(i);
1730 const Path &actualFilePath = fileNames.at(i);
1731 p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
1733 const Path filePath = actualFilePath.removedExtension(QB_EXT);
1734 m_filePaths.append(filePath);
1736 lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
1737 if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
1738 nativePriority = lt::dont_download;
1739 const auto priority = LT::fromNative(nativePriority);
1740 m_filePriorities.append(priority);
1742 p.save_path = savePath.toString().toStdString();
1743 p.ti = metadata;
1745 if (stopCondition() == StopCondition::MetadataReceived)
1747 m_stopCondition = StopCondition::None;
1749 m_isStopped = true;
1750 p.flags |= lt::torrent_flags::paused;
1751 p.flags &= ~lt::torrent_flags::auto_managed;
1753 m_session->handleTorrentPaused(this);
1756 reload();
1758 // If first/last piece priority was specified when adding this torrent,
1759 // we should apply it now that we have metadata:
1760 if (m_hasFirstLastPiecePriority)
1761 applyFirstLastPiecePriority(true);
1763 m_maintenanceJob = MaintenanceJob::None;
1764 prepareResumeData(p);
1766 m_session->handleTorrentMetadataReceived(this);
1769 void TorrentImpl::reload()
1773 m_completedFiles.fill(false);
1774 m_filesProgress.fill(0);
1775 m_pieces.fill(false);
1776 m_nativeStatus.pieces.clear_all();
1777 m_nativeStatus.num_pieces = 0;
1779 const auto queuePos = m_nativeHandle.queue_position();
1781 m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
1783 lt::add_torrent_params p = m_ltAddTorrentParams;
1784 p.flags |= lt::torrent_flags::update_subscribe
1785 | lt::torrent_flags::override_trackers
1786 | lt::torrent_flags::override_web_seeds;
1788 if (m_isStopped)
1790 p.flags |= lt::torrent_flags::paused;
1791 p.flags &= ~lt::torrent_flags::auto_managed;
1793 else if (m_operatingMode == TorrentOperatingMode::AutoManaged)
1795 p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1797 else
1799 p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1802 auto *const extensionData = new ExtensionData;
1803 p.userdata = LTClientData(extensionData);
1804 m_nativeHandle = m_nativeSession->add_torrent(p);
1806 m_nativeStatus = extensionData->status;
1808 if (queuePos >= lt::queue_position_t {})
1809 m_nativeHandle.queue_position_set(queuePos);
1810 m_nativeStatus.queue_position = queuePos;
1812 updateState();
1814 catch (const lt::system_error &err)
1816 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1817 .arg(id().toString(), QString::fromLocal8Bit(err.what())));
1821 void TorrentImpl::pause()
1823 if (!m_isStopped)
1825 m_stopCondition = StopCondition::None;
1826 m_isStopped = true;
1827 m_session->handleTorrentNeedSaveResumeData(this);
1828 m_session->handleTorrentPaused(this);
1831 if (m_maintenanceJob == MaintenanceJob::None)
1833 setAutoManaged(false);
1834 m_nativeHandle.pause();
1836 m_payloadRateMonitor.reset();
1840 void TorrentImpl::resume(const TorrentOperatingMode mode)
1842 if (hasError())
1844 m_nativeHandle.clear_error();
1845 m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode);
1848 m_operatingMode = mode;
1850 if (m_hasMissingFiles)
1852 m_hasMissingFiles = false;
1853 m_isStopped = false;
1854 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1855 reload();
1856 return;
1859 if (m_isStopped)
1861 m_isStopped = false;
1862 m_session->handleTorrentNeedSaveResumeData(this);
1863 m_session->handleTorrentResumed(this);
1866 if (m_maintenanceJob == MaintenanceJob::None)
1868 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1869 if (m_operatingMode == TorrentOperatingMode::Forced)
1870 m_nativeHandle.resume();
1874 void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
1876 if (!hasMetadata())
1878 m_savePath = newPath;
1879 m_session->handleTorrentSavePathChanged(this);
1880 return;
1883 const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
1884 ? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
1885 if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
1887 if (!m_storageIsMoving)
1889 m_storageIsMoving = true;
1890 updateState();
1891 m_session->handleTorrentStorageMovingStateChanged(this);
1896 void TorrentImpl::renameFile(const int index, const Path &path)
1898 Q_ASSERT((index >= 0) && (index < filesCount()));
1899 if ((index < 0) || (index >= filesCount())) [[unlikely]]
1900 return;
1902 const Path targetActualPath = makeActualPath(index, path);
1903 doRenameFile(index, targetActualPath);
1906 void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
1908 updateStatus(nativeStatus);
1911 void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
1913 if (context == MoveStorageContext::ChangeSavePath)
1914 m_savePath = path;
1915 else if (context == MoveStorageContext::ChangeDownloadPath)
1916 m_downloadPath = path;
1917 m_storageIsMoving = hasOutstandingJob;
1918 m_nativeStatus.save_path = path.toString().toStdString();
1920 m_session->handleTorrentSavePathChanged(this);
1921 m_session->handleTorrentNeedSaveResumeData(this);
1923 if (!m_storageIsMoving)
1925 updateState();
1926 m_session->handleTorrentStorageMovingStateChanged(this);
1928 if (m_hasMissingFiles)
1930 // it can be moved to the proper location
1931 m_hasMissingFiles = false;
1932 m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
1933 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1934 reload();
1937 while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1938 std::invoke(m_moveFinishedTriggers.dequeue());
1942 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_checked_alert *p)
1944 if (!hasMetadata())
1946 // The torrent is checked due to metadata received, but we should not process
1947 // this event until the torrent is reloaded using the received metadata.
1948 return;
1951 if (stopCondition() == StopCondition::FilesChecked)
1952 pause();
1954 m_statusUpdatedTriggers.enqueue([this]()
1956 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1958 if (!m_hasMissingFiles)
1960 if ((progress() < 1.0) && (wantedSize() > 0))
1961 m_hasFinishedStatus = false;
1962 else if (progress() == 1.0)
1963 m_hasFinishedStatus = true;
1965 adjustStorageLocation();
1966 manageActualFilePaths();
1968 if (!isPaused())
1970 // torrent is internally paused using NativeTorrentExtension after files checked
1971 // so we need to resume it if there is no corresponding "stop condition" set
1972 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1973 if (m_operatingMode == TorrentOperatingMode::Forced)
1974 m_nativeHandle.resume();
1978 if (m_nativeStatus.need_save_resume)
1979 m_session->handleTorrentNeedSaveResumeData(this);
1981 m_session->handleTorrentChecked(this);
1985 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused]] const lt::torrent_finished_alert *p)
1987 m_hasMissingFiles = false;
1988 if (m_hasFinishedStatus)
1989 return;
1991 m_statusUpdatedTriggers.enqueue([this]()
1993 adjustStorageLocation();
1994 manageActualFilePaths();
1996 m_session->handleTorrentNeedSaveResumeData(this);
1998 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
1999 if (recheckTorrentsOnCompletion && m_unchecked)
2001 forceRecheck();
2003 else
2005 m_hasFinishedStatus = true;
2007 if (isMoveInProgress() || (m_renameCount > 0))
2008 m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
2009 else
2010 m_session->handleTorrentFinished(this);
2015 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused]] const lt::torrent_paused_alert *p)
2019 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused]] const lt::torrent_resumed_alert *p)
2023 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
2025 if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
2027 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2028 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2029 // than the list we usually cache, so we have to request it from the appropriate source.
2030 fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
2033 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2035 Q_ASSERT(m_indexMap.isEmpty());
2037 const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
2038 m_ltAddTorrentParams = p->params;
2039 if (isSeedMode)
2040 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2042 m_ltAddTorrentParams.have_pieces.clear();
2043 m_ltAddTorrentParams.verified_pieces.clear();
2045 TorrentInfo metadata = TorrentInfo(*nativeTorrentInfo());
2047 const auto &renamedFiles = m_ltAddTorrentParams.renamed_files;
2048 PathList filePaths = metadata.filePaths();
2049 if (renamedFiles.empty() && (m_contentLayout != TorrentContentLayout::Original))
2051 const Path originalRootFolder = Path::findRootFolder(filePaths);
2052 const auto originalContentLayout = (originalRootFolder.isEmpty()
2053 ? TorrentContentLayout::NoSubfolder
2054 : TorrentContentLayout::Subfolder);
2055 if (m_contentLayout != originalContentLayout)
2057 if (m_contentLayout == TorrentContentLayout::NoSubfolder)
2058 Path::stripRootFolder(filePaths);
2059 else
2060 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2064 const auto nativeIndexes = metadata.nativeIndexes();
2065 m_indexMap.reserve(filePaths.size());
2066 for (int i = 0; i < filePaths.size(); ++i)
2068 const auto nativeIndex = nativeIndexes.at(i);
2069 m_indexMap[nativeIndex] = i;
2071 if (const auto it = renamedFiles.find(nativeIndex); it != renamedFiles.cend())
2072 filePaths[i] = Path(it->second);
2075 m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
2077 else
2079 prepareResumeData(p->params);
2083 void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
2085 if (m_hasMissingFiles)
2087 const auto havePieces = m_ltAddTorrentParams.have_pieces;
2088 const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
2089 const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
2091 // Update recent resume data but preserve existing progress
2092 m_ltAddTorrentParams = params;
2093 m_ltAddTorrentParams.have_pieces = havePieces;
2094 m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
2095 m_ltAddTorrentParams.verified_pieces = verifiedPieces;
2097 else
2099 const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
2100 // Update recent resume data
2101 m_ltAddTorrentParams = params;
2102 if (preserveSeedMode)
2103 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2106 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2107 m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
2109 LoadTorrentParams resumeData;
2110 resumeData.name = m_name;
2111 resumeData.category = m_category;
2112 resumeData.tags = m_tags;
2113 resumeData.contentLayout = m_contentLayout;
2114 resumeData.ratioLimit = m_ratioLimit;
2115 resumeData.seedingTimeLimit = m_seedingTimeLimit;
2116 resumeData.inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit;
2117 resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
2118 resumeData.hasFinishedStatus = m_hasFinishedStatus;
2119 resumeData.stopped = m_isStopped;
2120 resumeData.stopCondition = m_stopCondition;
2121 resumeData.operatingMode = m_operatingMode;
2122 resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
2123 resumeData.useAutoTMM = m_useAutoTMM;
2124 if (!resumeData.useAutoTMM)
2126 resumeData.savePath = m_savePath;
2127 resumeData.downloadPath = m_downloadPath;
2130 m_session->handleTorrentResumeDataReady(this, resumeData);
2133 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
2135 if (p->error != lt::errors::resume_data_not_modified)
2137 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2138 .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
2141 m_session->handleTorrentSaveResumeDataFailed(this);
2144 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
2146 // Files were probably moved or storage isn't accessible
2147 m_hasMissingFiles = true;
2148 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2149 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
2152 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
2154 const int fileIndex = m_indexMap.value(p->index, -1);
2155 Q_ASSERT(fileIndex >= 0);
2157 const Path newActualFilePath {QString::fromUtf8(p->new_name())};
2159 const Path oldFilePath = m_filePaths.at(fileIndex);
2160 const Path newFilePath = makeUserPath(newActualFilePath);
2162 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2163 // We should compare path in a case sensitive manner even on case insensitive
2164 // platforms since it can be renamed by only changing case of some character(s)
2165 if (oldFilePath.data() == newFilePath.data())
2167 // Remove empty ".unwanted" folders
2168 #ifdef QBT_USES_LIBTORRENT2
2169 const Path oldActualFilePath {QString::fromUtf8(p->old_name())};
2170 #else
2171 const Path oldActualFilePath;
2172 #endif
2173 const Path oldActualParentPath = oldActualFilePath.parentPath();
2174 const Path newActualParentPath = newActualFilePath.parentPath();
2175 if (newActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2177 if (oldActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2179 #ifdef Q_OS_WIN
2180 const std::wstring winPath = (actualStorageLocation() / newActualParentPath).toString().toStdWString();
2181 const DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str());
2182 ::SetFileAttributesW(winPath.c_str(), (dwAttrs | FILE_ATTRIBUTE_HIDDEN));
2183 #endif
2186 #ifdef QBT_USES_LIBTORRENT2
2187 else if (oldActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2189 if (newActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2190 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath);
2192 #else
2193 else
2195 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath / Path(UNWANTED_FOLDER_NAME));
2197 #endif
2199 else
2201 m_filePaths[fileIndex] = newFilePath;
2203 // Remove empty leftover folders
2204 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2205 // be removed if they are empty
2206 Path oldParentPath = oldFilePath.parentPath();
2207 const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath());
2208 while (oldParentPath != commonBasePath)
2210 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath);
2211 oldParentPath = oldParentPath.parentPath();
2215 --m_renameCount;
2216 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2217 m_moveFinishedTriggers.takeFirst()();
2219 m_session->handleTorrentNeedSaveResumeData(this);
2222 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
2224 const int fileIndex = m_indexMap.value(p->index, -1);
2225 Q_ASSERT(fileIndex >= 0);
2227 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2228 .arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
2230 --m_renameCount;
2231 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2232 m_moveFinishedTriggers.takeFirst()();
2234 m_session->handleTorrentNeedSaveResumeData(this);
2237 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
2239 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2240 return;
2242 const int fileIndex = m_indexMap.value(p->index, -1);
2243 Q_ASSERT(fileIndex >= 0);
2245 m_completedFiles.setBit(fileIndex);
2247 const Path actualPath = actualFilePath(fileIndex);
2249 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2250 // only apply Mark-of-the-Web to new download files
2251 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2253 const Path fullpath = actualStorageLocation() / actualPath;
2254 Utils::OS::applyMarkOfTheWeb(fullpath);
2256 #endif // Q_OS_MACOS || Q_OS_WIN
2258 if (m_session->isAppendExtensionEnabled())
2260 const Path path = filePath(fileIndex);
2261 if (actualPath != path)
2263 qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
2264 doRenameFile(fileIndex, path);
2269 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
2271 m_lastFileError = {p->error, p->op};
2274 #ifdef QBT_USES_LIBTORRENT2
2275 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
2277 m_session->handleTorrentNeedSaveResumeData(this);
2279 #endif
2281 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused]] const lt::metadata_received_alert *p)
2283 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2285 #ifdef QBT_USES_LIBTORRENT2
2286 const InfoHash prevInfoHash = infoHash();
2287 m_infoHash = InfoHash(m_nativeHandle.info_hashes());
2288 if (prevInfoHash != infoHash())
2289 m_session->handleTorrentInfoHashChanged(this, prevInfoHash);
2290 #endif
2292 m_maintenanceJob = MaintenanceJob::HandleMetadata;
2293 m_session->handleTorrentNeedSaveResumeData(this);
2296 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
2298 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))
2299 , Log::INFO);
2302 void TorrentImpl::handleCategoryOptionsChanged()
2304 if (m_useAutoTMM)
2305 adjustStorageLocation();
2308 void TorrentImpl::handleAppendExtensionToggled()
2310 if (!hasMetadata())
2311 return;
2313 manageActualFilePaths();
2316 void TorrentImpl::handleUnwantedFolderToggled()
2318 if (!hasMetadata())
2319 return;
2321 manageActualFilePaths();
2324 void TorrentImpl::handleAlert(const lt::alert *a)
2326 switch (a->type())
2328 #ifdef QBT_USES_LIBTORRENT2
2329 case lt::file_prio_alert::alert_type:
2330 handleFilePrioAlert(static_cast<const lt::file_prio_alert*>(a));
2331 break;
2332 #endif
2333 case lt::file_renamed_alert::alert_type:
2334 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
2335 break;
2336 case lt::file_rename_failed_alert::alert_type:
2337 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
2338 break;
2339 case lt::file_completed_alert::alert_type:
2340 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
2341 break;
2342 case lt::file_error_alert::alert_type:
2343 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
2344 break;
2345 case lt::torrent_finished_alert::alert_type:
2346 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
2347 break;
2348 case lt::save_resume_data_alert::alert_type:
2349 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
2350 break;
2351 case lt::save_resume_data_failed_alert::alert_type:
2352 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
2353 break;
2354 case lt::torrent_paused_alert::alert_type:
2355 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
2356 break;
2357 case lt::torrent_resumed_alert::alert_type:
2358 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
2359 break;
2360 case lt::metadata_received_alert::alert_type:
2361 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2362 break;
2363 case lt::fastresume_rejected_alert::alert_type:
2364 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2365 break;
2366 case lt::torrent_checked_alert::alert_type:
2367 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2368 break;
2369 case lt::performance_alert::alert_type:
2370 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2371 break;
2375 void TorrentImpl::manageActualFilePaths()
2377 const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
2378 const lt::file_storage &nativeFiles = nativeInfo->files();
2380 for (int i = 0; i < filesCount(); ++i)
2382 const Path path = filePath(i);
2384 const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
2385 const Path actualPath {nativeFiles.file_path(nativeIndex)};
2386 const Path targetActualPath = makeActualPath(i, path);
2387 if (actualPath != targetActualPath)
2389 qDebug() << "Renaming" << actualPath.toString() << "to" << targetActualPath.toString();
2390 doRenameFile(i, targetActualPath);
2395 void TorrentImpl::adjustStorageLocation()
2397 const Path downloadPath = this->downloadPath();
2398 const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
2400 if ((targetPath != actualStorageLocation()) || isMoveInProgress())
2401 moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
2404 void TorrentImpl::doRenameFile(const int index, const Path &path)
2406 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
2408 Q_ASSERT(index >= 0);
2409 Q_ASSERT(index < nativeIndexes.size());
2410 if ((index < 0) || (index >= nativeIndexes.size())) [[unlikely]]
2411 return;
2413 ++m_renameCount;
2414 m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
2417 lt::torrent_handle TorrentImpl::nativeHandle() const
2419 return m_nativeHandle;
2422 void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
2424 if (hasMetadata())
2425 return;
2427 m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
2431 #ifdef QBT_USES_LIBTORRENT2
2432 nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
2433 #else
2434 const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
2435 nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
2436 #endif
2438 catch (const std::exception &) {}
2442 Torrent::StopCondition TorrentImpl::stopCondition() const
2444 return m_stopCondition;
2447 void TorrentImpl::setStopCondition(const StopCondition stopCondition)
2449 if (stopCondition == m_stopCondition)
2450 return;
2452 if (isPaused())
2453 return;
2455 if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata())
2456 return;
2458 if ((stopCondition == StopCondition::FilesChecked) && hasMetadata() && !isChecking())
2459 return;
2461 m_stopCondition = stopCondition;
2464 bool TorrentImpl::isMoveInProgress() const
2466 return m_storageIsMoving;
2469 void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
2471 const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
2473 if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
2474 updateProgress();
2476 updateState();
2478 m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
2479 , nativeStatus.upload_payload_rate});
2481 if (hasMetadata())
2483 // NOTE: Don't change the order of these conditionals!
2484 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2485 if (isChecking())
2486 m_unchecked = false;
2487 else if (isDownloading())
2488 m_unchecked = true;
2491 while (!m_statusUpdatedTriggers.isEmpty())
2492 std::invoke(m_statusUpdatedTriggers.dequeue());
2495 void TorrentImpl::updateProgress()
2497 Q_ASSERT(hasMetadata());
2498 if (!hasMetadata()) [[unlikely]]
2499 return;
2501 Q_ASSERT(!m_filesProgress.isEmpty());
2502 if (m_filesProgress.isEmpty()) [[unlikely]]
2503 m_filesProgress.resize(filesCount());
2505 const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
2506 const QBitArray newPieces = m_pieces ^ oldPieces;
2508 const int64_t pieceSize = m_torrentInfo.pieceLength();
2509 for (qsizetype index = 0; index < newPieces.size(); ++index)
2511 if (!newPieces.at(index))
2512 continue;
2514 int64_t size = m_torrentInfo.pieceLength(index);
2515 int64_t pieceOffset = index * pieceSize;
2517 for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
2519 const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
2520 const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
2522 m_filesProgress[fileIndex] += add;
2524 size -= add;
2525 if (size <= 0)
2526 break;
2528 pieceOffset += add;
2533 void TorrentImpl::setRatioLimit(qreal limit)
2535 if (limit < USE_GLOBAL_RATIO)
2536 limit = NO_RATIO_LIMIT;
2537 else if (limit > MAX_RATIO)
2538 limit = MAX_RATIO;
2540 if (m_ratioLimit != limit)
2542 m_ratioLimit = limit;
2543 m_session->handleTorrentNeedSaveResumeData(this);
2544 m_session->handleTorrentShareLimitChanged(this);
2548 void TorrentImpl::setSeedingTimeLimit(int limit)
2550 if (limit < USE_GLOBAL_SEEDING_TIME)
2551 limit = NO_SEEDING_TIME_LIMIT;
2552 else if (limit > MAX_SEEDING_TIME)
2553 limit = MAX_SEEDING_TIME;
2555 if (m_seedingTimeLimit != limit)
2557 m_seedingTimeLimit = limit;
2558 m_session->handleTorrentNeedSaveResumeData(this);
2559 m_session->handleTorrentShareLimitChanged(this);
2563 void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
2565 if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
2566 limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
2567 else if (limit > MAX_INACTIVE_SEEDING_TIME)
2568 limit = MAX_SEEDING_TIME;
2570 if (m_inactiveSeedingTimeLimit != limit)
2572 m_inactiveSeedingTimeLimit = limit;
2573 m_session->handleTorrentNeedSaveResumeData(this);
2574 m_session->handleTorrentShareLimitChanged(this);
2578 void TorrentImpl::setUploadLimit(const int limit)
2580 const int cleanValue = cleanLimitValue(limit);
2581 if (cleanValue == uploadLimit())
2582 return;
2584 m_uploadLimit = cleanValue;
2585 m_nativeHandle.set_upload_limit(m_uploadLimit);
2586 m_session->handleTorrentNeedSaveResumeData(this);
2589 void TorrentImpl::setDownloadLimit(const int limit)
2591 const int cleanValue = cleanLimitValue(limit);
2592 if (cleanValue == downloadLimit())
2593 return;
2595 m_downloadLimit = cleanValue;
2596 m_nativeHandle.set_download_limit(m_downloadLimit);
2597 m_session->handleTorrentNeedSaveResumeData(this);
2600 void TorrentImpl::setSuperSeeding(const bool enable)
2602 if (enable == superSeeding())
2603 return;
2605 if (enable)
2606 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2607 else
2608 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2610 m_session->handleTorrentNeedSaveResumeData(this);
2613 void TorrentImpl::setDHTDisabled(const bool disable)
2615 if (disable == isDHTDisabled())
2616 return;
2618 if (disable)
2619 m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
2620 else
2621 m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
2623 m_session->handleTorrentNeedSaveResumeData(this);
2626 void TorrentImpl::setPEXDisabled(const bool disable)
2628 if (disable == isPEXDisabled())
2629 return;
2631 if (disable)
2632 m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
2633 else
2634 m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
2636 m_session->handleTorrentNeedSaveResumeData(this);
2639 void TorrentImpl::setLSDDisabled(const bool disable)
2641 if (disable == isLSDDisabled())
2642 return;
2644 if (disable)
2645 m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
2646 else
2647 m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
2649 m_session->handleTorrentNeedSaveResumeData(this);
2652 void TorrentImpl::flushCache() const
2654 m_nativeHandle.flush_cache();
2657 QString TorrentImpl::createMagnetURI() const
2659 QString ret = u"magnet:?"_s;
2661 const SHA1Hash infoHash1 = infoHash().v1();
2662 if (infoHash1.isValid())
2664 ret += u"xt=urn:btih:" + infoHash1.toString();
2667 const SHA256Hash infoHash2 = infoHash().v2();
2668 if (infoHash2.isValid())
2670 if (infoHash1.isValid())
2671 ret += u'&';
2672 ret += u"xt=urn:btmh:1220" + infoHash2.toString();
2675 const QString displayName = name();
2676 if (displayName != id().toString())
2678 ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
2681 for (const TrackerEntry &tracker : asConst(trackers()))
2683 ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
2686 for (const QUrl &urlSeed : asConst(urlSeeds()))
2688 ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
2691 return ret;
2694 nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
2696 if (!hasMetadata())
2697 return nonstd::make_unexpected(tr("Missing metadata"));
2701 #ifdef QBT_USES_LIBTORRENT2
2702 const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
2703 const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
2704 #else
2705 const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
2706 #endif
2707 lt::create_torrent creator {*torrentInfo};
2709 for (const TrackerEntry &entry : asConst(trackers()))
2710 creator.add_tracker(entry.url.toStdString(), entry.tier);
2712 return creator.generate();
2714 catch (const lt::system_error &err)
2716 return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
2720 nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
2722 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2723 if (!preparationResult)
2724 return preparationResult.get_unexpected();
2726 // usually torrent size should be smaller than 1 MB,
2727 // however there are >100 MB v2/hybrid torrent files out in the wild
2728 QByteArray buffer;
2729 buffer.reserve(1024 * 1024);
2730 lt::bencode(std::back_inserter(buffer), preparationResult.value());
2731 return buffer;
2734 nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) const
2736 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2737 if (!preparationResult)
2738 return preparationResult.get_unexpected();
2740 const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
2741 if (!saveResult)
2742 return saveResult.get_unexpected();
2744 return {};
2747 void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
2749 invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
2753 std::vector<lt::peer_info> nativePeers;
2754 nativeHandle.get_peer_info(nativePeers);
2755 QVector<PeerInfo> peers;
2756 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
2757 for (const lt::peer_info &peer : nativePeers)
2758 peers.append(PeerInfo(peer, allPieces));
2759 return peers;
2761 catch (const std::exception &) {}
2763 return {};
2765 , std::move(resultHandler));
2768 void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
2770 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
2774 const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
2775 QVector<QUrl> urlSeeds;
2776 urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
2777 for (const std::string &urlSeed : currentSeeds)
2778 urlSeeds.append(QString::fromStdString(urlSeed));
2779 return urlSeeds;
2781 catch (const std::exception &) {}
2783 return {};
2785 , std::move(resultHandler));
2788 void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
2790 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
2794 std::vector<int> piecesAvailability;
2795 nativeHandle.piece_availability(piecesAvailability);
2796 return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
2798 catch (const std::exception &) {}
2800 return {};
2802 , std::move(resultHandler));
2805 void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
2807 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
2811 #ifdef QBT_USES_LIBTORRENT2
2812 const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
2813 #else
2814 std::vector<lt::partial_piece_info> queue;
2815 nativeHandle.get_download_queue(queue);
2816 #endif
2817 QBitArray result;
2818 result.resize(torrentInfo.piecesCount());
2819 for (const lt::partial_piece_info &info : queue)
2820 result.setBit(LT::toUnderlyingType(info.piece_index));
2821 return result;
2823 catch (const std::exception &) {}
2825 return {};
2827 , std::move(resultHandler));
2830 void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
2832 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
2834 if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
2835 return {};
2839 std::vector<int> piecesAvailability;
2840 nativeHandle.piece_availability(piecesAvailability);
2841 const int filesCount = torrentInfo.filesCount();
2842 // libtorrent returns empty array for seeding only torrents
2843 if (piecesAvailability.empty())
2844 return QVector<qreal>(filesCount, -1);
2846 QVector<qreal> result;
2847 result.reserve(filesCount);
2848 for (int i = 0; i < filesCount; ++i)
2850 const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
2852 int availablePieces = 0;
2853 for (const int piece : filePieces)
2854 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2856 const qreal availability = filePieces.isEmpty()
2857 ? 1 // the file has no pieces, so it is available by default
2858 : static_cast<qreal>(availablePieces) / filePieces.size();
2859 result.append(availability);
2861 return result;
2863 catch (const std::exception &) {}
2865 return {};
2867 , std::move(resultHandler));
2870 void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
2872 if (!hasMetadata())
2873 return;
2875 Q_ASSERT(priorities.size() == filesCount());
2877 // Reset 'm_hasSeedStatus' if needed in order to react again to
2878 // 'torrent_finished_alert' and eg show tray notifications
2879 const QVector<DownloadPriority> oldPriorities = filePriorities();
2880 for (int i = 0; i < oldPriorities.size(); ++i)
2882 if ((oldPriorities[i] == DownloadPriority::Ignored)
2883 && (priorities[i] > DownloadPriority::Ignored)
2884 && !m_completedFiles.at(i))
2886 m_hasFinishedStatus = false;
2887 break;
2891 const int internalFilesCount = m_torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2892 auto nativePriorities = std::vector<lt::download_priority_t>(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2893 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
2894 for (int i = 0; i < priorities.size(); ++i)
2895 nativePriorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(priorities[i]);
2897 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
2898 m_nativeHandle.prioritize_files(nativePriorities);
2900 m_filePriorities = priorities;
2901 // Restore first/last piece first option if necessary
2902 if (m_hasFirstLastPiecePriority)
2903 applyFirstLastPiecePriority(true);
2904 manageActualFilePaths();
2907 QVector<qreal> TorrentImpl::availableFileFractions() const
2909 Q_ASSERT(hasMetadata());
2911 const int filesCount = this->filesCount();
2912 if (filesCount <= 0) return {};
2914 const QVector<int> piecesAvailability = pieceAvailability();
2915 // libtorrent returns empty array for seeding only torrents
2916 if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1);
2918 QVector<qreal> res;
2919 res.reserve(filesCount);
2920 for (int i = 0; i < filesCount; ++i)
2922 const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
2924 int availablePieces = 0;
2925 for (const int piece : filePieces)
2926 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2928 const qreal availability = filePieces.isEmpty()
2929 ? 1 // the file has no pieces, so it is available by default
2930 : static_cast<qreal>(availablePieces) / filePieces.size();
2931 res.push_back(availability);
2933 return res;
2936 template <typename Func, typename Callback>
2937 void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
2939 m_session->invokeAsync([session = m_session
2940 , func = std::move(func)
2941 , resultHandler = std::move(resultHandler)
2942 , thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
2944 session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
2946 if (thisTorrent)
2947 resultHandler(result);