Apply filename filter to subfolder names as well
[qBittorrent.git] / src / base / bittorrent / torrentimpl.cpp
blobd98e5f4e1abd5671eaf08c497dbeda62b16d8878
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "torrentimpl.h"
32 #include <algorithm>
33 #include <memory>
35 #ifdef Q_OS_WIN
36 #include <windows.h>
37 #endif
39 #include <libtorrent/address.hpp>
40 #include <libtorrent/alert_types.hpp>
41 #include <libtorrent/create_torrent.hpp>
42 #include <libtorrent/session.hpp>
43 #include <libtorrent/storage_defs.hpp>
44 #include <libtorrent/time.hpp>
46 #ifdef QBT_USES_LIBTORRENT2
47 #include <libtorrent/info_hash.hpp>
48 #endif
50 #include <QtSystemDetection>
51 #include <QByteArray>
52 #include <QDebug>
53 #include <QPointer>
54 #include <QSet>
55 #include <QStringList>
56 #include <QUrl>
58 #include "base/exceptions.h"
59 #include "base/global.h"
60 #include "base/logger.h"
61 #include "base/preferences.h"
62 #include "base/types.h"
63 #include "base/utils/fs.h"
64 #include "base/utils/io.h"
65 #include "common.h"
66 #include "downloadpriority.h"
67 #include "extensiondata.h"
68 #include "loadtorrentparams.h"
69 #include "ltqbitarray.h"
70 #include "lttypecast.h"
71 #include "peeraddress.h"
72 #include "peerinfo.h"
73 #include "sessionimpl.h"
74 #include "trackerentry.h"
76 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
77 #include "base/utils/os.h"
78 #endif // Q_OS_MACOS || Q_OS_WIN
80 using namespace BitTorrent;
82 namespace
84 lt::announce_entry makeNativeAnnounceEntry(const QString &url, const int tier)
86 lt::announce_entry entry {url.toStdString()};
87 entry.tier = tier;
88 return entry;
91 QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint)
93 const auto ltNow = lt::clock_type::now();
94 const auto qNow = QDateTime::currentDateTime();
95 const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - ltNow + lt::milliseconds(500)).count();
97 return qNow.addSecs(secsSinceNow);
100 QString toString(const lt::tcp::endpoint &ltTCPEndpoint)
102 return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str());
105 void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry
106 , const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
108 Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url));
110 trackerEntryStatus.tier = nativeEntry.tier;
112 // remove outdated endpoints
113 trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointStatus>::iterator &iter)
115 return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
116 , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
118 return (endpointName == toString(existingEndpoint.local_endpoint));
122 const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * btProtocols.size();
124 int numUpdating = 0;
125 int numWorking = 0;
126 int numNotWorking = 0;
127 int numTrackerError = 0;
128 int numUnreachable = 0;
130 for (const lt::announce_endpoint &ltAnnounceEndpoint : nativeEntry.endpoints)
132 const auto endpointName = toString(ltAnnounceEndpoint.local_endpoint);
134 for (const auto protocolVersion : btProtocols)
136 #ifdef QBT_USES_LIBTORRENT2
137 Q_ASSERT((protocolVersion == 1) || (protocolVersion == 2));
138 const auto ltProtocolVersion = (protocolVersion == 1) ? lt::protocol_version::V1 : lt::protocol_version::V2;
139 const lt::announce_infohash &ltAnnounceInfo = ltAnnounceEndpoint.info_hashes[ltProtocolVersion];
140 #else
141 Q_ASSERT(protocolVersion == 1);
142 const lt::announce_endpoint &ltAnnounceInfo = ltAnnounceEndpoint;
143 #endif
144 const QMap<int, int> &endpointUpdateInfo = updateInfo[ltAnnounceEndpoint.local_endpoint];
145 TrackerEndpointStatus &trackerEndpointStatus = trackerEntryStatus.endpoints[std::make_pair(endpointName, protocolVersion)];
147 trackerEndpointStatus.name = endpointName;
148 trackerEndpointStatus.btVersion = protocolVersion;
149 trackerEndpointStatus.numPeers = endpointUpdateInfo.value(protocolVersion, trackerEndpointStatus.numPeers);
150 trackerEndpointStatus.numSeeds = ltAnnounceInfo.scrape_complete;
151 trackerEndpointStatus.numLeeches = ltAnnounceInfo.scrape_incomplete;
152 trackerEndpointStatus.numDownloaded = ltAnnounceInfo.scrape_downloaded;
153 trackerEndpointStatus.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
154 trackerEndpointStatus.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
156 if (ltAnnounceInfo.updating)
158 trackerEndpointStatus.state = TrackerEndpointState::Updating;
159 ++numUpdating;
161 else if (ltAnnounceInfo.fails > 0)
163 if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
165 trackerEndpointStatus.state = TrackerEndpointState::TrackerError;
166 ++numTrackerError;
168 else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
170 trackerEndpointStatus.state = TrackerEndpointState::Unreachable;
171 ++numUnreachable;
173 else
175 trackerEndpointStatus.state = TrackerEndpointState::NotWorking;
176 ++numNotWorking;
179 else if (nativeEntry.verified)
181 trackerEndpointStatus.state = TrackerEndpointState::Working;
182 ++numWorking;
184 else
186 trackerEndpointStatus.state = TrackerEndpointState::NotContacted;
189 if (!ltAnnounceInfo.message.empty())
191 trackerEndpointStatus.message = QString::fromStdString(ltAnnounceInfo.message);
193 else if (ltAnnounceInfo.last_error)
195 trackerEndpointStatus.message = QString::fromLocal8Bit(ltAnnounceInfo.last_error.message());
197 else
199 trackerEndpointStatus.message.clear();
204 if (numEndpoints > 0)
206 if (numUpdating > 0)
208 trackerEntryStatus.state = TrackerEndpointState::Updating;
210 else if (numWorking > 0)
212 trackerEntryStatus.state = TrackerEndpointState::Working;
214 else if (numTrackerError > 0)
216 trackerEntryStatus.state = TrackerEndpointState::TrackerError;
218 else if (numUnreachable == numEndpoints)
220 trackerEntryStatus.state = TrackerEndpointState::Unreachable;
222 else if ((numUnreachable + numNotWorking) == numEndpoints)
224 trackerEntryStatus.state = TrackerEndpointState::NotWorking;
228 trackerEntryStatus.numPeers = -1;
229 trackerEntryStatus.numSeeds = -1;
230 trackerEntryStatus.numLeeches = -1;
231 trackerEntryStatus.numDownloaded = -1;
232 trackerEntryStatus.nextAnnounceTime = QDateTime();
233 trackerEntryStatus.minAnnounceTime = QDateTime();
234 trackerEntryStatus.message.clear();
236 for (const TrackerEndpointStatus &endpointStatus : asConst(trackerEntryStatus.endpoints))
238 trackerEntryStatus.numPeers = std::max(trackerEntryStatus.numPeers, endpointStatus.numPeers);
239 trackerEntryStatus.numSeeds = std::max(trackerEntryStatus.numSeeds, endpointStatus.numSeeds);
240 trackerEntryStatus.numLeeches = std::max(trackerEntryStatus.numLeeches, endpointStatus.numLeeches);
241 trackerEntryStatus.numDownloaded = std::max(trackerEntryStatus.numDownloaded, endpointStatus.numDownloaded);
243 if (endpointStatus.state == trackerEntryStatus.state)
245 if (!trackerEntryStatus.nextAnnounceTime.isValid() || (trackerEntryStatus.nextAnnounceTime > endpointStatus.nextAnnounceTime))
247 trackerEntryStatus.nextAnnounceTime = endpointStatus.nextAnnounceTime;
248 trackerEntryStatus.minAnnounceTime = endpointStatus.minAnnounceTime;
249 if ((endpointStatus.state != TrackerEndpointState::Working)
250 || !endpointStatus.message.isEmpty())
252 trackerEntryStatus.message = endpointStatus.message;
256 if (endpointStatus.state == TrackerEndpointState::Working)
258 if (trackerEntryStatus.message.isEmpty())
259 trackerEntryStatus.message = endpointStatus.message;
265 template <typename Vector>
266 Vector resized(const Vector &inVector, const typename Vector::size_type size, const typename Vector::value_type &defaultValue)
268 Vector outVector = inVector;
269 outVector.resize(size, defaultValue);
270 return outVector;
273 // This is an imitation of limit normalization performed by libtorrent itself.
274 // We need perform it to keep cached values in line with the ones used by libtorrent.
275 int cleanLimitValue(const int value)
277 return ((value < 0) || (value == std::numeric_limits<int>::max())) ? 0 : value;
281 // TorrentImpl
283 TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
284 , const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
285 : Torrent(session)
286 , m_session(session)
287 , m_nativeSession(nativeSession)
288 , m_nativeHandle(nativeHandle)
289 #ifdef QBT_USES_LIBTORRENT2
290 , m_infoHash(m_nativeHandle.info_hashes())
291 #else
292 , m_infoHash(m_nativeHandle.info_hash())
293 #endif
294 , m_name(params.name)
295 , m_savePath(params.savePath)
296 , m_downloadPath(params.downloadPath)
297 , m_category(params.category)
298 , m_tags(params.tags)
299 , m_ratioLimit(params.ratioLimit)
300 , m_seedingTimeLimit(params.seedingTimeLimit)
301 , m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit)
302 , m_shareLimitAction(params.shareLimitAction)
303 , m_operatingMode(params.operatingMode)
304 , m_contentLayout(params.contentLayout)
305 , m_hasFinishedStatus(params.hasFinishedStatus)
306 , m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
307 , m_useAutoTMM(params.useAutoTMM)
308 , m_isStopped(params.stopped)
309 , m_sslParams(params.sslParameters)
310 , m_ltAddTorrentParams(params.ltAddTorrentParams)
311 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit))
312 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit))
314 if (m_ltAddTorrentParams.ti)
316 // Initialize it only if torrent is added with metadata.
317 // Otherwise it should be initialized in "Metadata received" handler.
318 m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
320 Q_ASSERT(m_filePaths.isEmpty());
321 Q_ASSERT(m_indexMap.isEmpty());
322 const int filesCount = m_torrentInfo.filesCount();
323 m_filePaths.reserve(filesCount);
324 m_indexMap.reserve(filesCount);
325 m_filePriorities.reserve(filesCount);
326 const std::vector<lt::download_priority_t> filePriorities =
327 resized(m_ltAddTorrentParams.file_priorities, m_ltAddTorrentParams.ti->num_files()
328 , LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
330 m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
331 m_filesProgress.resize(filesCount);
333 for (int i = 0; i < filesCount; ++i)
335 const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
336 m_indexMap[nativeIndex] = i;
338 const auto fileIter = m_ltAddTorrentParams.renamed_files.find(nativeIndex);
339 const Path filePath = ((fileIter != m_ltAddTorrentParams.renamed_files.end())
340 ? makeUserPath(Path(fileIter->second)) : m_torrentInfo.filePath(i));
341 m_filePaths.append(filePath);
343 const auto priority = LT::fromNative(filePriorities[LT::toUnderlyingType(nativeIndex)]);
344 m_filePriorities.append(priority);
348 setStopCondition(params.stopCondition);
350 const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
351 m_trackerEntryStatuses.reserve(static_cast<decltype(m_trackerEntryStatuses)::size_type>(extensionData->trackers.size()));
352 for (const lt::announce_entry &announceEntry : extensionData->trackers)
353 m_trackerEntryStatuses.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
354 m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
355 for (const std::string &urlSeed : extensionData->urlSeeds)
356 m_urlSeeds.append(QString::fromStdString(urlSeed));
357 m_nativeStatus = extensionData->status;
359 if (hasMetadata())
360 updateProgress();
362 updateState();
364 if (hasMetadata())
365 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
368 TorrentImpl::~TorrentImpl() = default;
370 bool TorrentImpl::isValid() const
372 return m_nativeHandle.is_valid();
375 Session *TorrentImpl::session() const
377 return m_session;
380 InfoHash TorrentImpl::infoHash() const
382 return m_infoHash;
385 QString TorrentImpl::name() const
387 if (!m_name.isEmpty())
388 return m_name;
390 if (hasMetadata())
391 return m_torrentInfo.name();
393 const QString name = QString::fromStdString(m_nativeStatus.name);
394 if (!name.isEmpty())
395 return name;
397 return id().toString();
400 QDateTime TorrentImpl::creationDate() const
402 return m_torrentInfo.creationDate();
405 QString TorrentImpl::creator() const
407 return m_torrentInfo.creator();
410 QString TorrentImpl::comment() const
412 return m_torrentInfo.comment();
415 bool TorrentImpl::isPrivate() const
417 return m_torrentInfo.isPrivate();
420 qlonglong TorrentImpl::totalSize() const
422 return m_torrentInfo.totalSize();
425 // size without the "don't download" files
426 qlonglong TorrentImpl::wantedSize() const
428 return m_nativeStatus.total_wanted;
431 qlonglong TorrentImpl::completedSize() const
433 return m_nativeStatus.total_wanted_done;
436 qlonglong TorrentImpl::pieceLength() const
438 return m_torrentInfo.pieceLength();
441 qlonglong TorrentImpl::wastedSize() const
443 return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes);
446 QString TorrentImpl::currentTracker() const
448 return QString::fromStdString(m_nativeStatus.current_tracker);
451 Path TorrentImpl::savePath() const
453 return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath;
456 void TorrentImpl::setSavePath(const Path &path)
458 Q_ASSERT(!isAutoTMMEnabled());
460 const Path basePath = m_session->useCategoryPathsInManualMode()
461 ? m_session->categorySavePath(category()) : m_session->savePath();
462 const Path resolvedPath = (path.isAbsolute() ? path : (basePath / path));
463 if (resolvedPath == savePath())
464 return;
466 if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
468 moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
470 else
472 m_savePath = resolvedPath;
473 m_session->handleTorrentSavePathChanged(this);
474 deferredRequestResumeData();
478 Path TorrentImpl::downloadPath() const
480 return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath;
483 void TorrentImpl::setDownloadPath(const Path &path)
485 Q_ASSERT(!isAutoTMMEnabled());
487 const Path basePath = m_session->useCategoryPathsInManualMode()
488 ? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
489 const Path resolvedPath = (path.isEmpty() || path.isAbsolute()) ? path : (basePath / path);
490 if (resolvedPath == m_downloadPath)
491 return;
493 const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
494 if (isIncomplete)
496 moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
498 else
500 m_downloadPath = resolvedPath;
501 m_session->handleTorrentSavePathChanged(this);
502 deferredRequestResumeData();
506 Path TorrentImpl::rootPath() const
508 if (!hasMetadata())
509 return {};
511 const Path relativeRootPath = Path::findRootFolder(filePaths());
512 if (relativeRootPath.isEmpty())
513 return {};
515 return (actualStorageLocation() / relativeRootPath);
518 Path TorrentImpl::contentPath() const
520 if (!hasMetadata())
521 return {};
523 if (filesCount() == 1)
524 return (actualStorageLocation() / filePath(0));
526 const Path rootPath = this->rootPath();
527 return (rootPath.isEmpty() ? actualStorageLocation() : rootPath);
530 bool TorrentImpl::isAutoTMMEnabled() const
532 return m_useAutoTMM;
535 void TorrentImpl::setAutoTMMEnabled(bool enabled)
537 if (m_useAutoTMM == enabled)
538 return;
540 m_useAutoTMM = enabled;
541 if (!m_useAutoTMM)
543 m_savePath = m_session->categorySavePath(category());
544 m_downloadPath = m_session->categoryDownloadPath(category());
547 deferredRequestResumeData();
548 m_session->handleTorrentSavingModeChanged(this);
550 adjustStorageLocation();
553 Path TorrentImpl::actualStorageLocation() const
555 if (!hasMetadata())
556 return {};
558 return Path(m_nativeStatus.save_path);
561 void TorrentImpl::setAutoManaged(const bool enable)
563 if (enable)
564 m_nativeHandle.set_flags(lt::torrent_flags::auto_managed);
565 else
566 m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
569 Path TorrentImpl::makeActualPath(int index, const Path &path) const
571 Path actualPath = path;
573 if (m_session->isAppendExtensionEnabled()
574 && (fileSize(index) > 0) && !m_completedFiles.at(index))
576 actualPath += QB_EXT;
579 if (m_session->isUnwantedFolderEnabled()
580 && (m_filePriorities[index] == DownloadPriority::Ignored))
582 const Path parentPath = actualPath.parentPath();
583 const QString fileName = actualPath.filename();
584 actualPath = parentPath / Path(UNWANTED_FOLDER_NAME) / Path(fileName);
587 return actualPath;
590 Path TorrentImpl::makeUserPath(const Path &path) const
592 Path userPath = path.removedExtension(QB_EXT);
594 const Path parentRelPath = userPath.parentPath();
595 if (parentRelPath.filename() == UNWANTED_FOLDER_NAME)
597 const QString fileName = userPath.filename();
598 const Path relPath = parentRelPath.parentPath();
599 userPath = relPath / Path(fileName);
602 return userPath;
605 QVector<TrackerEntryStatus> TorrentImpl::trackers() const
607 return m_trackerEntryStatuses;
610 void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
612 trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
614 QSet<TrackerEntry> currentTrackerSet;
615 currentTrackerSet.reserve(m_trackerEntryStatuses.size());
616 for (const TrackerEntryStatus &status : asConst(m_trackerEntryStatuses))
617 currentTrackerSet.insert({.url = status.url, .tier = status.tier});
619 const auto newTrackerSet = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend()) - currentTrackerSet;
620 if (newTrackerSet.isEmpty())
621 return;
623 trackers = QVector<TrackerEntry>(newTrackerSet.cbegin(), newTrackerSet.cend());
624 for (const TrackerEntry &tracker : asConst(trackers))
626 m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
627 m_trackerEntryStatuses.append({tracker.url, tracker.tier});
629 std::sort(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
630 , [](const TrackerEntryStatus &left, const TrackerEntryStatus &right) { return left.tier < right.tier; });
632 deferredRequestResumeData();
633 m_session->handleTorrentTrackersAdded(this, trackers);
636 void TorrentImpl::removeTrackers(const QStringList &trackers)
638 QStringList removedTrackers = trackers;
639 for (const QString &tracker : trackers)
641 if (!m_trackerEntryStatuses.removeOne({tracker}))
642 removedTrackers.removeOne(tracker);
645 std::vector<lt::announce_entry> nativeTrackers;
646 nativeTrackers.reserve(m_trackerEntryStatuses.size());
647 for (const TrackerEntryStatus &tracker : asConst(m_trackerEntryStatuses))
648 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
650 if (!removedTrackers.isEmpty())
652 m_nativeHandle.replace_trackers(nativeTrackers);
654 deferredRequestResumeData();
655 m_session->handleTorrentTrackersRemoved(this, removedTrackers);
659 void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
661 trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
663 // Filter out duplicate trackers
664 const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
665 trackers = QVector<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
666 std::sort(trackers.begin(), trackers.end()
667 , [](const TrackerEntry &left, const TrackerEntry &right) { return left.tier < right.tier; });
669 std::vector<lt::announce_entry> nativeTrackers;
670 nativeTrackers.reserve(trackers.size());
671 m_trackerEntryStatuses.clear();
673 for (const TrackerEntry &tracker : trackers)
675 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
676 m_trackerEntryStatuses.append({tracker.url, tracker.tier});
679 m_nativeHandle.replace_trackers(nativeTrackers);
681 // Clear the peer list if it's a private torrent since
682 // we do not want to keep connecting with peers from old tracker.
683 if (isPrivate())
684 clearPeers();
686 deferredRequestResumeData();
687 m_session->handleTorrentTrackersChanged(this);
690 QVector<QUrl> TorrentImpl::urlSeeds() const
692 return m_urlSeeds;
695 void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
697 m_session->invokeAsync([urlSeeds, session = m_session
698 , nativeHandle = m_nativeHandle
699 , thisTorrent = QPointer<TorrentImpl>(this)]
703 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
704 QVector<QUrl> currentSeeds;
705 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
706 for (const std::string &urlSeed : nativeSeeds)
707 currentSeeds.append(QString::fromStdString(urlSeed));
709 QVector<QUrl> addedUrlSeeds;
710 addedUrlSeeds.reserve(urlSeeds.size());
712 for (const QUrl &url : urlSeeds)
714 if (!currentSeeds.contains(url))
716 nativeHandle.add_url_seed(url.toString().toStdString());
717 addedUrlSeeds.append(url);
721 currentSeeds.append(addedUrlSeeds);
722 session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
724 if (!thisTorrent)
725 return;
727 thisTorrent->m_urlSeeds = currentSeeds;
728 if (!addedUrlSeeds.isEmpty())
730 thisTorrent->deferredRequestResumeData();
731 session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
735 catch (const std::exception &) {}
739 void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
741 m_session->invokeAsync([urlSeeds, session = m_session
742 , nativeHandle = m_nativeHandle
743 , thisTorrent = QPointer<TorrentImpl>(this)]
747 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
748 QVector<QUrl> currentSeeds;
749 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
750 for (const std::string &urlSeed : nativeSeeds)
751 currentSeeds.append(QString::fromStdString(urlSeed));
753 QVector<QUrl> removedUrlSeeds;
754 removedUrlSeeds.reserve(urlSeeds.size());
756 for (const QUrl &url : urlSeeds)
758 if (currentSeeds.removeOne(url))
760 nativeHandle.remove_url_seed(url.toString().toStdString());
761 removedUrlSeeds.append(url);
765 session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
767 if (!thisTorrent)
768 return;
770 thisTorrent->m_urlSeeds = currentSeeds;
772 if (!removedUrlSeeds.isEmpty())
774 thisTorrent->deferredRequestResumeData();
775 session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
779 catch (const std::exception &) {}
783 void TorrentImpl::clearPeers()
785 m_nativeHandle.clear_peers();
788 bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
790 lt::error_code ec;
791 const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec);
792 if (ec) return false;
794 const lt::tcp::endpoint endpoint(addr, peerAddress.port);
797 m_nativeHandle.connect_peer(endpoint);
799 catch (const lt::system_error &err)
801 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
802 .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
803 return false;
806 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name()));
807 return true;
810 bool TorrentImpl::needSaveResumeData() const
812 return m_nativeStatus.need_save_resume;
815 void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags)
817 m_nativeHandle.save_resume_data(flags);
818 m_deferredRequestResumeDataInvoked = false;
820 m_session->handleTorrentResumeDataRequested(this);
823 void TorrentImpl::deferredRequestResumeData()
825 if (!m_deferredRequestResumeDataInvoked)
827 QMetaObject::invokeMethod(this, [this]
829 requestResumeData((m_maintenanceJob == MaintenanceJob::HandleMetadata)
830 ? lt::torrent_handle::save_info_dict : lt::resume_data_flags_t());
831 }, Qt::QueuedConnection);
833 m_deferredRequestResumeDataInvoked = true;
837 int TorrentImpl::filesCount() const
839 return m_torrentInfo.filesCount();
842 int TorrentImpl::piecesCount() const
844 return m_torrentInfo.piecesCount();
847 int TorrentImpl::piecesHave() const
849 return m_nativeStatus.num_pieces;
852 qreal TorrentImpl::progress() const
854 if (isChecking())
855 return m_nativeStatus.progress;
857 if (m_nativeStatus.total_wanted == 0)
858 return 0.;
860 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
861 return 1.;
863 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
864 if ((progress < 0.f) || (progress > 1.f))
866 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
867 .arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
868 , Log::WARNING);
871 return progress;
874 QString TorrentImpl::category() const
876 return m_category;
879 bool TorrentImpl::belongsToCategory(const QString &category) const
881 if (m_category.isEmpty())
882 return category.isEmpty();
884 if (m_category == category)
885 return true;
887 return (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + u'/'));
890 TagSet TorrentImpl::tags() const
892 return m_tags;
895 bool TorrentImpl::hasTag(const Tag &tag) const
897 return m_tags.contains(tag);
900 bool TorrentImpl::addTag(const Tag &tag)
902 if (!tag.isValid())
903 return false;
904 if (hasTag(tag))
905 return false;
907 if (!m_session->hasTag(tag))
909 if (!m_session->addTag(tag))
910 return false;
912 m_tags.insert(tag);
913 deferredRequestResumeData();
914 m_session->handleTorrentTagAdded(this, tag);
915 return true;
918 bool TorrentImpl::removeTag(const Tag &tag)
920 if (m_tags.remove(tag))
922 deferredRequestResumeData();
923 m_session->handleTorrentTagRemoved(this, tag);
924 return true;
926 return false;
929 void TorrentImpl::removeAllTags()
931 for (const Tag &tag : asConst(tags()))
932 removeTag(tag);
935 QDateTime TorrentImpl::addedTime() const
937 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
940 qreal TorrentImpl::ratioLimit() const
942 return m_ratioLimit;
945 int TorrentImpl::seedingTimeLimit() const
947 return m_seedingTimeLimit;
950 int TorrentImpl::inactiveSeedingTimeLimit() const
952 return m_inactiveSeedingTimeLimit;
955 Path TorrentImpl::filePath(const int index) const
957 Q_ASSERT(index >= 0);
958 Q_ASSERT(index < m_filePaths.size());
960 return m_filePaths.value(index, {});
963 Path TorrentImpl::actualFilePath(const int index) const
965 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
967 Q_ASSERT(index >= 0);
968 Q_ASSERT(index < nativeIndexes.size());
969 if ((index < 0) || (index >= nativeIndexes.size()))
970 return {};
972 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes[index]));
975 qlonglong TorrentImpl::fileSize(const int index) const
977 return m_torrentInfo.fileSize(index);
980 PathList TorrentImpl::filePaths() const
982 return m_filePaths;
985 QVector<DownloadPriority> TorrentImpl::filePriorities() const
987 return m_filePriorities;
990 TorrentInfo TorrentImpl::info() const
992 return m_torrentInfo;
995 bool TorrentImpl::isStopped() const
997 return m_isStopped;
1000 bool TorrentImpl::isQueued() const
1002 if (!m_session->isQueueingSystemEnabled())
1003 return false;
1005 // Torrent is Queued if it isn't in Stopped state but paused internally
1006 return (!isStopped()
1007 && (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
1008 && (m_nativeStatus.flags & lt::torrent_flags::paused));
1011 bool TorrentImpl::isChecking() const
1013 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
1014 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
1017 bool TorrentImpl::isDownloading() const
1019 switch (m_state)
1021 case TorrentState::Downloading:
1022 case TorrentState::DownloadingMetadata:
1023 case TorrentState::ForcedDownloadingMetadata:
1024 case TorrentState::StalledDownloading:
1025 case TorrentState::CheckingDownloading:
1026 case TorrentState::StoppedDownloading:
1027 case TorrentState::QueuedDownloading:
1028 case TorrentState::ForcedDownloading:
1029 return true;
1030 default:
1031 break;
1034 return false;
1037 bool TorrentImpl::isMoving() const
1039 return m_state == TorrentState::Moving;
1042 bool TorrentImpl::isUploading() const
1044 switch (m_state)
1046 case TorrentState::Uploading:
1047 case TorrentState::StalledUploading:
1048 case TorrentState::CheckingUploading:
1049 case TorrentState::QueuedUploading:
1050 case TorrentState::ForcedUploading:
1051 return true;
1052 default:
1053 break;
1056 return false;
1059 bool TorrentImpl::isCompleted() const
1061 switch (m_state)
1063 case TorrentState::Uploading:
1064 case TorrentState::StalledUploading:
1065 case TorrentState::CheckingUploading:
1066 case TorrentState::StoppedUploading:
1067 case TorrentState::QueuedUploading:
1068 case TorrentState::ForcedUploading:
1069 return true;
1070 default:
1071 break;
1074 return false;
1077 bool TorrentImpl::isActive() const
1079 switch (m_state)
1081 case TorrentState::StalledDownloading:
1082 return (uploadPayloadRate() > 0);
1084 case TorrentState::DownloadingMetadata:
1085 case TorrentState::ForcedDownloadingMetadata:
1086 case TorrentState::Downloading:
1087 case TorrentState::ForcedDownloading:
1088 case TorrentState::Uploading:
1089 case TorrentState::ForcedUploading:
1090 case TorrentState::Moving:
1091 return true;
1093 default:
1094 break;
1097 return false;
1100 bool TorrentImpl::isInactive() const
1102 return !isActive();
1105 bool TorrentImpl::isErrored() const
1107 return ((m_state == TorrentState::MissingFiles)
1108 || (m_state == TorrentState::Error));
1111 bool TorrentImpl::isFinished() const
1113 return ((m_nativeStatus.state == lt::torrent_status::finished)
1114 || (m_nativeStatus.state == lt::torrent_status::seeding));
1117 bool TorrentImpl::isForced() const
1119 return (!isStopped() && (m_operatingMode == TorrentOperatingMode::Forced));
1122 bool TorrentImpl::isSequentialDownload() const
1124 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::sequential_download);
1127 bool TorrentImpl::hasFirstLastPiecePriority() const
1129 return m_hasFirstLastPiecePriority;
1132 TorrentState TorrentImpl::state() const
1134 return m_state;
1137 void TorrentImpl::updateState()
1139 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
1141 m_state = TorrentState::CheckingResumeData;
1143 else if (isMoveInProgress())
1145 m_state = TorrentState::Moving;
1147 else if (hasMissingFiles())
1149 m_state = TorrentState::MissingFiles;
1151 else if (hasError())
1153 m_state = TorrentState::Error;
1155 else if (!hasMetadata())
1157 if (isStopped())
1158 m_state = TorrentState::StoppedDownloading;
1159 else if (isQueued())
1160 m_state = TorrentState::QueuedDownloading;
1161 else
1162 m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata;
1164 else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isStopped())
1166 // If the torrent is not just in the "checking" state, but is being actually checked
1167 m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
1169 else if (isFinished())
1171 if (isStopped())
1172 m_state = TorrentState::StoppedUploading;
1173 else if (isQueued())
1174 m_state = TorrentState::QueuedUploading;
1175 else if (isForced())
1176 m_state = TorrentState::ForcedUploading;
1177 else if (m_nativeStatus.upload_payload_rate > 0)
1178 m_state = TorrentState::Uploading;
1179 else
1180 m_state = TorrentState::StalledUploading;
1182 else
1184 if (isStopped())
1185 m_state = TorrentState::StoppedDownloading;
1186 else if (isQueued())
1187 m_state = TorrentState::QueuedDownloading;
1188 else if (isForced())
1189 m_state = TorrentState::ForcedDownloading;
1190 else if (m_nativeStatus.download_payload_rate > 0)
1191 m_state = TorrentState::Downloading;
1192 else
1193 m_state = TorrentState::StalledDownloading;
1197 bool TorrentImpl::hasMetadata() const
1199 return m_torrentInfo.isValid();
1202 bool TorrentImpl::hasMissingFiles() const
1204 return m_hasMissingFiles;
1207 bool TorrentImpl::hasError() const
1209 return (m_nativeStatus.errc || (m_nativeStatus.flags & lt::torrent_flags::upload_mode));
1212 int TorrentImpl::queuePosition() const
1214 return static_cast<int>(m_nativeStatus.queue_position);
1217 QString TorrentImpl::error() const
1219 if (m_nativeStatus.errc)
1220 return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
1222 if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
1224 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1225 .arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
1228 return {};
1231 qlonglong TorrentImpl::totalDownload() const
1233 return m_nativeStatus.all_time_download;
1236 qlonglong TorrentImpl::totalUpload() const
1238 return m_nativeStatus.all_time_upload;
1241 qlonglong TorrentImpl::activeTime() const
1243 return lt::total_seconds(m_nativeStatus.active_duration);
1246 qlonglong TorrentImpl::finishedTime() const
1248 return lt::total_seconds(m_nativeStatus.finished_duration);
1251 qlonglong TorrentImpl::eta() const
1253 if (isStopped()) return MAX_ETA;
1255 const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
1257 if (isFinished())
1259 const qreal maxRatioValue = maxRatio();
1260 const int maxSeedingTimeValue = maxSeedingTime();
1261 const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
1262 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
1264 qlonglong ratioEta = MAX_ETA;
1266 if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
1269 qlonglong realDL = totalDownload();
1270 if (realDL <= 0)
1271 realDL = wantedSize();
1273 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1276 qlonglong seedingTimeEta = MAX_ETA;
1278 if (maxSeedingTimeValue >= 0)
1280 seedingTimeEta = (maxSeedingTimeValue * 60) - finishedTime();
1281 if (seedingTimeEta < 0)
1282 seedingTimeEta = 0;
1285 qlonglong inactiveSeedingTimeEta = MAX_ETA;
1287 if (maxInactiveSeedingTimeValue >= 0)
1289 inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
1290 inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
1293 return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
1296 if (!speedAverage.download) return MAX_ETA;
1298 return (wantedSize() - completedSize()) / speedAverage.download;
1301 QVector<qreal> TorrentImpl::filesProgress() const
1303 if (!hasMetadata())
1304 return {};
1306 const int count = m_filesProgress.size();
1307 Q_ASSERT(count == filesCount());
1308 if (count != filesCount()) [[unlikely]]
1309 return {};
1311 if (m_completedFiles.count(true) == count)
1312 return QVector<qreal>(count, 1);
1314 QVector<qreal> result;
1315 result.reserve(count);
1316 for (int i = 0; i < count; ++i)
1318 const int64_t progress = m_filesProgress.at(i);
1319 const int64_t size = fileSize(i);
1320 if ((size <= 0) || (progress == size))
1321 result << 1;
1322 else
1323 result << (progress / static_cast<qreal>(size));
1326 return result;
1329 int TorrentImpl::seedsCount() const
1331 return m_nativeStatus.num_seeds;
1334 int TorrentImpl::peersCount() const
1336 return m_nativeStatus.num_peers;
1339 int TorrentImpl::leechsCount() const
1341 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1344 int TorrentImpl::totalSeedsCount() const
1346 return (m_nativeStatus.num_complete > -1) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1349 int TorrentImpl::totalPeersCount() const
1351 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1352 return (peers > -1) ? peers : m_nativeStatus.list_peers;
1355 int TorrentImpl::totalLeechersCount() const
1357 return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1360 QDateTime TorrentImpl::lastSeenComplete() const
1362 if (m_nativeStatus.last_seen_complete > 0)
1363 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1364 else
1365 return {};
1368 QDateTime TorrentImpl::completedTime() const
1370 if (m_nativeStatus.completed_time > 0)
1371 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1372 else
1373 return {};
1376 qlonglong TorrentImpl::timeSinceUpload() const
1378 if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
1379 return -1;
1380 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1383 qlonglong TorrentImpl::timeSinceDownload() const
1385 if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
1386 return -1;
1387 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1390 qlonglong TorrentImpl::timeSinceActivity() const
1392 const qlonglong upTime = timeSinceUpload();
1393 const qlonglong downTime = timeSinceDownload();
1394 return ((upTime < 0) != (downTime < 0))
1395 ? std::max(upTime, downTime)
1396 : std::min(upTime, downTime);
1399 int TorrentImpl::downloadLimit() const
1401 return m_downloadLimit;;
1404 int TorrentImpl::uploadLimit() const
1406 return m_uploadLimit;
1409 bool TorrentImpl::superSeeding() const
1411 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::super_seeding);
1414 bool TorrentImpl::isDHTDisabled() const
1416 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_dht);
1419 bool TorrentImpl::isPEXDisabled() const
1421 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_pex);
1424 bool TorrentImpl::isLSDDisabled() const
1426 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
1429 QVector<PeerInfo> TorrentImpl::peers() const
1431 std::vector<lt::peer_info> nativePeers;
1432 m_nativeHandle.get_peer_info(nativePeers);
1434 QVector<PeerInfo> peers;
1435 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
1437 for (const lt::peer_info &peer : nativePeers)
1438 peers.append(PeerInfo(peer, pieces()));
1440 return peers;
1443 QBitArray TorrentImpl::pieces() const
1445 return m_pieces;
1448 QBitArray TorrentImpl::downloadingPieces() const
1450 QBitArray result(piecesCount());
1452 std::vector<lt::partial_piece_info> queue;
1453 m_nativeHandle.get_download_queue(queue);
1455 for (const lt::partial_piece_info &info : queue)
1456 result.setBit(LT::toUnderlyingType(info.piece_index));
1458 return result;
1461 QVector<int> TorrentImpl::pieceAvailability() const
1463 std::vector<int> avail;
1464 m_nativeHandle.piece_availability(avail);
1466 return {avail.cbegin(), avail.cend()};
1469 qreal TorrentImpl::distributedCopies() const
1471 return m_nativeStatus.distributed_copies;
1474 qreal TorrentImpl::maxRatio() const
1476 if (m_ratioLimit == USE_GLOBAL_RATIO)
1477 return m_session->globalMaxRatio();
1479 return m_ratioLimit;
1482 int TorrentImpl::maxSeedingTime() const
1484 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1485 return m_session->globalMaxSeedingMinutes();
1487 return m_seedingTimeLimit;
1490 int TorrentImpl::maxInactiveSeedingTime() const
1492 if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
1493 return m_session->globalMaxInactiveSeedingMinutes();
1495 return m_inactiveSeedingTimeLimit;
1498 qreal TorrentImpl::realRatio() const
1500 const int64_t upload = m_nativeStatus.all_time_upload;
1501 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1502 const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01))
1503 ? m_nativeStatus.total_done
1504 : m_nativeStatus.all_time_download;
1506 if (download == 0)
1507 return (upload == 0) ? 0 : MAX_RATIO;
1509 const qreal ratio = upload / static_cast<qreal>(download);
1510 Q_ASSERT(ratio >= 0);
1511 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1514 int TorrentImpl::uploadPayloadRate() const
1516 // workaround: suppress the speed for Stopped state
1517 return isStopped() ? 0 : m_nativeStatus.upload_payload_rate;
1520 int TorrentImpl::downloadPayloadRate() const
1522 // workaround: suppress the speed for Stopped state
1523 return isStopped() ? 0 : m_nativeStatus.download_payload_rate;
1526 qlonglong TorrentImpl::totalPayloadUpload() const
1528 return m_nativeStatus.total_payload_upload;
1531 qlonglong TorrentImpl::totalPayloadDownload() const
1533 return m_nativeStatus.total_payload_download;
1536 int TorrentImpl::connectionsCount() const
1538 return m_nativeStatus.num_connections;
1541 int TorrentImpl::connectionsLimit() const
1543 return m_nativeStatus.connections_limit;
1546 qlonglong TorrentImpl::nextAnnounce() const
1548 return lt::total_seconds(m_nativeStatus.next_announce);
1551 qreal TorrentImpl::popularity() const
1553 // in order to produce floating-point numbers using `std::chrono::duration_cast`,
1554 // we should use `qreal` as `Rep` to define the `months` duration
1555 using months = std::chrono::duration<qreal, std::chrono::months::period>;
1556 const auto activeMonths = std::chrono::duration_cast<months>(m_nativeStatus.active_duration).count();
1557 return (activeMonths > 0) ? (realRatio() / activeMonths) : 0;
1560 void TorrentImpl::setName(const QString &name)
1562 if (m_name != name)
1564 m_name = name;
1565 deferredRequestResumeData();
1566 m_session->handleTorrentNameChanged(this);
1570 bool TorrentImpl::setCategory(const QString &category)
1572 if (m_category != category)
1574 if (!category.isEmpty() && !m_session->categories().contains(category))
1575 return false;
1577 const QString oldCategory = m_category;
1578 m_category = category;
1579 deferredRequestResumeData();
1580 m_session->handleTorrentCategoryChanged(this, oldCategory);
1582 if (m_useAutoTMM)
1584 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1585 adjustStorageLocation();
1586 else
1587 setAutoTMMEnabled(false);
1591 return true;
1594 void TorrentImpl::forceReannounce(const int index)
1596 m_nativeHandle.force_reannounce(0, index);
1599 void TorrentImpl::forceDHTAnnounce()
1601 m_nativeHandle.force_dht_announce();
1604 void TorrentImpl::forceRecheck()
1606 if (!hasMetadata())
1607 return;
1609 m_nativeHandle.force_recheck();
1610 // We have to force update the cached state, otherwise someone will be able to get
1611 // an incorrect one during the interval until the cached state is updated in a regular way.
1612 m_nativeStatus.state = lt::torrent_status::checking_resume_data;
1614 if (m_hasMissingFiles)
1616 m_hasMissingFiles = false;
1617 if (!isStopped())
1619 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1620 if (m_operatingMode == TorrentOperatingMode::Forced)
1621 m_nativeHandle.resume();
1625 m_unchecked = false;
1627 m_completedFiles.fill(false);
1628 m_filesProgress.fill(0);
1629 m_pieces.fill(false);
1630 m_nativeStatus.pieces.clear_all();
1631 m_nativeStatus.num_pieces = 0;
1633 if (isStopped())
1635 // When "force recheck" is applied on Stopped torrent, we start them to perform checking
1636 start();
1637 m_stopCondition = StopCondition::FilesChecked;
1641 void TorrentImpl::setSequentialDownload(const bool enable)
1643 if (enable)
1645 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1646 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1648 else
1650 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1651 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1654 deferredRequestResumeData();
1657 void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
1659 if (m_hasFirstLastPiecePriority == enabled)
1660 return;
1662 m_hasFirstLastPiecePriority = enabled;
1663 if (hasMetadata())
1664 applyFirstLastPiecePriority(enabled);
1666 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1667 .arg((enabled ? tr("On") : tr("Off")), name()));
1669 deferredRequestResumeData();
1672 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
1674 Q_ASSERT(hasMetadata());
1676 // Download first and last pieces first for every file in the torrent
1678 auto piecePriorities = std::vector<lt::download_priority_t>(m_torrentInfo.piecesCount(), LT::toNative(DownloadPriority::Ignored));
1680 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1681 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1682 for (int fileIndex = 0; fileIndex < m_filePriorities.size(); ++fileIndex)
1684 const DownloadPriority filePrio = m_filePriorities[fileIndex];
1685 if (filePrio <= DownloadPriority::Ignored)
1686 continue;
1688 // Determine the priority to set
1689 const lt::download_priority_t piecePrio = LT::toNative(enabled ? DownloadPriority::Maximum : filePrio);
1690 const TorrentInfo::PieceRange pieceRange = m_torrentInfo.filePieces(fileIndex);
1692 // worst case: AVI index = 1% of total file size (at the end of the file)
1693 const int numPieces = std::ceil(fileSize(fileIndex) * 0.01 / pieceLength());
1694 for (int i = 0; i < numPieces; ++i)
1696 piecePriorities[pieceRange.first() + i] = piecePrio;
1697 piecePriorities[pieceRange.last() - i] = piecePrio;
1700 const int firstPiece = pieceRange.first() + numPieces;
1701 const int lastPiece = pieceRange.last() - numPieces;
1702 for (int pieceIndex = firstPiece; pieceIndex <= lastPiece; ++pieceIndex)
1703 piecePriorities[pieceIndex] = LT::toNative(filePrio);
1706 m_nativeHandle.prioritize_pieces(piecePriorities);
1709 void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
1711 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1712 endReceivedMetadataHandling(savePath, fileNames);
1715 TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
1717 const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
1718 , [&announceEntry](const TrackerEntryStatus &trackerEntryStatus)
1720 return (trackerEntryStatus.url == QString::fromStdString(announceEntry.url));
1723 Q_ASSERT(it != m_trackerEntryStatuses.end());
1724 if (it == m_trackerEntryStatuses.end()) [[unlikely]]
1725 return {};
1727 #ifdef QBT_USES_LIBTORRENT2
1728 QSet<int> btProtocols;
1729 const auto &infoHashes = nativeHandle().info_hashes();
1730 if (infoHashes.has(lt::protocol_version::V1))
1731 btProtocols.insert(1);
1732 if (infoHashes.has(lt::protocol_version::V2))
1733 btProtocols.insert(2);
1734 #else
1735 const QSet<int> btProtocols {1};
1736 #endif
1737 ::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo);
1738 return *it;
1741 void TorrentImpl::resetTrackerEntryStatuses()
1743 for (TrackerEntryStatus &status : m_trackerEntryStatuses)
1745 const QString tempUrl = status.url;
1746 const int tempTier = status.tier;
1748 status.clear();
1749 status.url = tempUrl;
1750 status.tier = tempTier;
1754 std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
1756 Q_ASSERT(!m_nativeStatus.torrent_file.expired());
1758 return m_nativeStatus.torrent_file.lock();
1761 void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
1763 Q_ASSERT(m_maintenanceJob == MaintenanceJob::HandleMetadata);
1764 if (m_maintenanceJob != MaintenanceJob::HandleMetadata) [[unlikely]]
1765 return;
1767 Q_ASSERT(m_filePaths.isEmpty());
1768 if (!m_filePaths.isEmpty()) [[unlikely]]
1769 m_filePaths.clear();
1771 lt::add_torrent_params &p = m_ltAddTorrentParams;
1773 const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1774 m_torrentInfo = TorrentInfo(*metadata);
1775 m_filePriorities.reserve(filesCount());
1776 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
1777 p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
1778 , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
1780 m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
1781 m_filesProgress.resize(filesCount());
1782 updateProgress();
1784 for (int i = 0; i < fileNames.size(); ++i)
1786 const auto nativeIndex = nativeIndexes.at(i);
1788 const Path &actualFilePath = fileNames.at(i);
1789 p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
1791 const Path filePath = actualFilePath.removedExtension(QB_EXT);
1792 m_filePaths.append(filePath);
1794 m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
1797 m_session->applyFilenameFilter(fileNames, m_filePriorities);
1798 for (int i = 0; i < m_filePriorities.size(); ++i)
1799 p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
1801 p.save_path = savePath.toString().toStdString();
1802 p.ti = metadata;
1804 if (stopCondition() == StopCondition::MetadataReceived)
1806 m_stopCondition = StopCondition::None;
1808 m_isStopped = true;
1809 p.flags |= lt::torrent_flags::paused;
1810 p.flags &= ~lt::torrent_flags::auto_managed;
1812 m_session->handleTorrentStopped(this);
1815 reload();
1817 // If first/last piece priority was specified when adding this torrent,
1818 // we should apply it now that we have metadata:
1819 if (m_hasFirstLastPiecePriority)
1820 applyFirstLastPiecePriority(true);
1822 m_maintenanceJob = MaintenanceJob::None;
1823 prepareResumeData(p);
1825 m_session->handleTorrentMetadataReceived(this);
1828 void TorrentImpl::reload()
1832 m_completedFiles.fill(false);
1833 m_filesProgress.fill(0);
1834 m_pieces.fill(false);
1835 m_nativeStatus.pieces.clear_all();
1836 m_nativeStatus.num_pieces = 0;
1838 const auto queuePos = m_nativeHandle.queue_position();
1840 m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
1842 lt::add_torrent_params p = m_ltAddTorrentParams;
1843 p.flags |= lt::torrent_flags::update_subscribe
1844 | lt::torrent_flags::override_trackers
1845 | lt::torrent_flags::override_web_seeds;
1847 if (m_isStopped)
1849 p.flags |= lt::torrent_flags::paused;
1850 p.flags &= ~lt::torrent_flags::auto_managed;
1852 else if (m_operatingMode == TorrentOperatingMode::AutoManaged)
1854 p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1856 else
1858 p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1861 auto *const extensionData = new ExtensionData;
1862 p.userdata = LTClientData(extensionData);
1863 m_nativeHandle = m_nativeSession->add_torrent(p);
1865 m_nativeStatus = extensionData->status;
1867 if (queuePos >= lt::queue_position_t {})
1868 m_nativeHandle.queue_position_set(queuePos);
1869 m_nativeStatus.queue_position = queuePos;
1871 updateState();
1873 catch (const lt::system_error &err)
1875 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1876 .arg(id().toString(), QString::fromLocal8Bit(err.what())));
1880 void TorrentImpl::stop()
1882 if (!m_isStopped)
1884 m_stopCondition = StopCondition::None;
1885 m_isStopped = true;
1886 deferredRequestResumeData();
1887 m_session->handleTorrentStopped(this);
1890 if (m_maintenanceJob == MaintenanceJob::None)
1892 setAutoManaged(false);
1893 m_nativeHandle.pause();
1895 m_payloadRateMonitor.reset();
1899 void TorrentImpl::start(const TorrentOperatingMode mode)
1901 if (hasError())
1903 m_nativeHandle.clear_error();
1904 m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode);
1907 m_operatingMode = mode;
1909 if (m_hasMissingFiles)
1911 m_hasMissingFiles = false;
1912 m_isStopped = false;
1913 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1914 reload();
1915 return;
1918 if (m_isStopped)
1920 m_isStopped = false;
1921 deferredRequestResumeData();
1922 m_session->handleTorrentStarted(this);
1925 if (m_maintenanceJob == MaintenanceJob::None)
1927 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1928 if (m_operatingMode == TorrentOperatingMode::Forced)
1929 m_nativeHandle.resume();
1933 void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
1935 if (!hasMetadata())
1937 m_savePath = newPath;
1938 m_session->handleTorrentSavePathChanged(this);
1939 return;
1942 const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
1943 ? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
1944 if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
1946 if (!m_storageIsMoving)
1948 m_storageIsMoving = true;
1949 updateState();
1950 m_session->handleTorrentStorageMovingStateChanged(this);
1955 void TorrentImpl::renameFile(const int index, const Path &path)
1957 Q_ASSERT((index >= 0) && (index < filesCount()));
1958 if ((index < 0) || (index >= filesCount())) [[unlikely]]
1959 return;
1961 const Path targetActualPath = makeActualPath(index, path);
1962 doRenameFile(index, targetActualPath);
1965 void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
1967 updateStatus(nativeStatus);
1970 void TorrentImpl::handleQueueingModeChanged()
1972 updateState();
1975 void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
1977 if (context == MoveStorageContext::ChangeSavePath)
1978 m_savePath = path;
1979 else if (context == MoveStorageContext::ChangeDownloadPath)
1980 m_downloadPath = path;
1981 m_storageIsMoving = hasOutstandingJob;
1982 m_nativeStatus.save_path = path.toString().toStdString();
1984 m_session->handleTorrentSavePathChanged(this);
1985 deferredRequestResumeData();
1987 if (!m_storageIsMoving)
1989 updateState();
1990 m_session->handleTorrentStorageMovingStateChanged(this);
1992 if (m_hasMissingFiles)
1994 // it can be moved to the proper location
1995 m_hasMissingFiles = false;
1996 m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
1997 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1998 reload();
2001 while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2002 std::invoke(m_moveFinishedTriggers.dequeue());
2006 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_checked_alert *p)
2008 if (!hasMetadata())
2010 // The torrent is checked due to metadata received, but we should not process
2011 // this event until the torrent is reloaded using the received metadata.
2012 return;
2015 if (stopCondition() == StopCondition::FilesChecked)
2016 stop();
2018 m_statusUpdatedTriggers.enqueue([this]()
2020 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
2022 if (!m_hasMissingFiles)
2024 if ((progress() < 1.0) && (wantedSize() > 0))
2025 m_hasFinishedStatus = false;
2026 else if (progress() == 1.0)
2027 m_hasFinishedStatus = true;
2029 adjustStorageLocation();
2030 manageActualFilePaths();
2032 if (!isStopped())
2034 // torrent is internally paused using NativeTorrentExtension after files checked
2035 // so we need to resume it if there is no corresponding "stop condition" set
2036 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
2037 if (m_operatingMode == TorrentOperatingMode::Forced)
2038 m_nativeHandle.resume();
2042 if (m_nativeStatus.need_save_resume)
2043 deferredRequestResumeData();
2045 m_session->handleTorrentChecked(this);
2049 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused]] const lt::torrent_finished_alert *p)
2051 m_hasMissingFiles = false;
2052 if (m_hasFinishedStatus)
2053 return;
2055 m_statusUpdatedTriggers.enqueue([this]()
2057 adjustStorageLocation();
2058 manageActualFilePaths();
2060 deferredRequestResumeData();
2062 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
2063 if (recheckTorrentsOnCompletion && m_unchecked)
2065 forceRecheck();
2067 else
2069 m_hasFinishedStatus = true;
2071 if (isMoveInProgress() || (m_renameCount > 0))
2072 m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
2073 else
2074 m_session->handleTorrentFinished(this);
2079 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused]] const lt::torrent_paused_alert *p)
2083 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused]] const lt::torrent_resumed_alert *p)
2087 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
2089 if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
2091 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2092 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2093 // than the list we usually cache, so we have to request it from the appropriate source.
2094 fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
2097 if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
2099 Q_ASSERT(m_indexMap.isEmpty());
2101 const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
2102 m_ltAddTorrentParams = p->params;
2103 if (isSeedMode)
2104 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2106 m_ltAddTorrentParams.have_pieces.clear();
2107 m_ltAddTorrentParams.verified_pieces.clear();
2109 m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti;
2111 const auto metadata = TorrentInfo(*m_ltAddTorrentParams.ti);
2113 const auto &renamedFiles = m_ltAddTorrentParams.renamed_files;
2114 PathList filePaths = metadata.filePaths();
2115 if (renamedFiles.empty() && (m_contentLayout != TorrentContentLayout::Original))
2117 const Path originalRootFolder = Path::findRootFolder(filePaths);
2118 const auto originalContentLayout = (originalRootFolder.isEmpty()
2119 ? TorrentContentLayout::NoSubfolder : TorrentContentLayout::Subfolder);
2120 if (m_contentLayout != originalContentLayout)
2122 if (m_contentLayout == TorrentContentLayout::NoSubfolder)
2123 Path::stripRootFolder(filePaths);
2124 else
2125 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2129 const auto nativeIndexes = metadata.nativeIndexes();
2130 m_indexMap.reserve(filePaths.size());
2131 for (int i = 0; i < filePaths.size(); ++i)
2133 const auto nativeIndex = nativeIndexes.at(i);
2134 m_indexMap[nativeIndex] = i;
2136 if (const auto it = renamedFiles.find(nativeIndex); it != renamedFiles.cend())
2137 filePaths[i] = Path(it->second);
2140 m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
2142 else
2144 prepareResumeData(p->params);
2148 void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
2150 if (m_hasMissingFiles)
2152 const auto havePieces = m_ltAddTorrentParams.have_pieces;
2153 const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
2154 const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
2156 // Update recent resume data but preserve existing progress
2157 m_ltAddTorrentParams = params;
2158 m_ltAddTorrentParams.have_pieces = havePieces;
2159 m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
2160 m_ltAddTorrentParams.verified_pieces = verifiedPieces;
2162 else
2164 const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
2165 // Update recent resume data
2166 m_ltAddTorrentParams = params;
2167 if (preserveSeedMode)
2168 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2171 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2172 m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
2174 const LoadTorrentParams resumeData
2176 .ltAddTorrentParams = m_ltAddTorrentParams,
2177 .name = m_name,
2178 .category = m_category,
2179 .tags = m_tags,
2180 .savePath = (!m_useAutoTMM ? m_savePath : Path()),
2181 .downloadPath = (!m_useAutoTMM ? m_downloadPath : Path()),
2182 .contentLayout = m_contentLayout,
2183 .operatingMode = m_operatingMode,
2184 .useAutoTMM = m_useAutoTMM,
2185 .firstLastPiecePriority = m_hasFirstLastPiecePriority,
2186 .hasFinishedStatus = m_hasFinishedStatus,
2187 .stopped = m_isStopped,
2188 .stopCondition = m_stopCondition,
2189 .addToQueueTop = false,
2190 .ratioLimit = m_ratioLimit,
2191 .seedingTimeLimit = m_seedingTimeLimit,
2192 .inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit,
2193 .shareLimitAction = m_shareLimitAction,
2194 .sslParameters = m_sslParams
2197 m_session->handleTorrentResumeDataReady(this, resumeData);
2200 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
2202 if (p->error != lt::errors::resume_data_not_modified)
2204 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2205 .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
2209 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
2211 // Files were probably moved or storage isn't accessible
2212 m_hasMissingFiles = true;
2213 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2214 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
2217 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
2219 const int fileIndex = m_indexMap.value(p->index, -1);
2220 Q_ASSERT(fileIndex >= 0);
2222 const Path newActualFilePath {QString::fromUtf8(p->new_name())};
2224 const Path oldFilePath = m_filePaths.at(fileIndex);
2225 const Path newFilePath = makeUserPath(newActualFilePath);
2227 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2228 // We should compare path in a case sensitive manner even on case insensitive
2229 // platforms since it can be renamed by only changing case of some character(s)
2230 if (oldFilePath.data() == newFilePath.data())
2232 // Remove empty ".unwanted" folders
2233 #ifdef QBT_USES_LIBTORRENT2
2234 const Path oldActualFilePath {QString::fromUtf8(p->old_name())};
2235 #else
2236 const Path oldActualFilePath;
2237 #endif
2238 const Path oldActualParentPath = oldActualFilePath.parentPath();
2239 const Path newActualParentPath = newActualFilePath.parentPath();
2240 if (newActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2242 if (oldActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2244 #ifdef Q_OS_WIN
2245 const std::wstring winPath = (actualStorageLocation() / newActualParentPath).toString().toStdWString();
2246 const DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str());
2247 ::SetFileAttributesW(winPath.c_str(), (dwAttrs | FILE_ATTRIBUTE_HIDDEN));
2248 #endif
2251 #ifdef QBT_USES_LIBTORRENT2
2252 else if (oldActualParentPath.filename() == UNWANTED_FOLDER_NAME)
2254 if (newActualParentPath.filename() != UNWANTED_FOLDER_NAME)
2255 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath);
2257 #else
2258 else
2260 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath / Path(UNWANTED_FOLDER_NAME));
2262 #endif
2264 else
2266 m_filePaths[fileIndex] = newFilePath;
2268 // Remove empty leftover folders
2269 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2270 // be removed if they are empty
2271 Path oldParentPath = oldFilePath.parentPath();
2272 const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath());
2273 while (oldParentPath != commonBasePath)
2275 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath);
2276 oldParentPath = oldParentPath.parentPath();
2280 --m_renameCount;
2281 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2282 m_moveFinishedTriggers.takeFirst()();
2284 deferredRequestResumeData();
2287 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
2289 const int fileIndex = m_indexMap.value(p->index, -1);
2290 Q_ASSERT(fileIndex >= 0);
2292 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2293 .arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
2295 --m_renameCount;
2296 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2297 m_moveFinishedTriggers.takeFirst()();
2299 deferredRequestResumeData();
2302 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
2304 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2305 return;
2307 const int fileIndex = m_indexMap.value(p->index, -1);
2308 Q_ASSERT(fileIndex >= 0);
2310 m_completedFiles.setBit(fileIndex);
2312 const Path actualPath = actualFilePath(fileIndex);
2314 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2315 // only apply Mark-of-the-Web to new download files
2316 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2318 const Path fullpath = actualStorageLocation() / actualPath;
2319 Utils::OS::applyMarkOfTheWeb(fullpath);
2321 #endif // Q_OS_MACOS || Q_OS_WIN
2323 if (m_session->isAppendExtensionEnabled())
2325 const Path path = filePath(fileIndex);
2326 if (actualPath != path)
2328 qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
2329 doRenameFile(fileIndex, path);
2334 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
2336 m_lastFileError = {p->error, p->op};
2339 #ifdef QBT_USES_LIBTORRENT2
2340 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
2342 deferredRequestResumeData();
2344 #endif
2346 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused]] const lt::metadata_received_alert *p)
2348 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2350 #ifdef QBT_USES_LIBTORRENT2
2351 const InfoHash prevInfoHash = infoHash();
2352 m_infoHash = InfoHash(m_nativeHandle.info_hashes());
2353 if (prevInfoHash != infoHash())
2354 m_session->handleTorrentInfoHashChanged(this, prevInfoHash);
2355 #endif
2357 m_maintenanceJob = MaintenanceJob::HandleMetadata;
2358 deferredRequestResumeData();
2361 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
2363 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))
2364 , Log::INFO);
2367 void TorrentImpl::handleCategoryOptionsChanged()
2369 if (m_useAutoTMM)
2370 adjustStorageLocation();
2373 void TorrentImpl::handleAppendExtensionToggled()
2375 if (!hasMetadata())
2376 return;
2378 manageActualFilePaths();
2381 void TorrentImpl::handleUnwantedFolderToggled()
2383 if (!hasMetadata())
2384 return;
2386 manageActualFilePaths();
2389 void TorrentImpl::handleAlert(const lt::alert *a)
2391 switch (a->type())
2393 #ifdef QBT_USES_LIBTORRENT2
2394 case lt::file_prio_alert::alert_type:
2395 handleFilePrioAlert(static_cast<const lt::file_prio_alert*>(a));
2396 break;
2397 #endif
2398 case lt::file_renamed_alert::alert_type:
2399 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
2400 break;
2401 case lt::file_rename_failed_alert::alert_type:
2402 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
2403 break;
2404 case lt::file_completed_alert::alert_type:
2405 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
2406 break;
2407 case lt::file_error_alert::alert_type:
2408 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
2409 break;
2410 case lt::torrent_finished_alert::alert_type:
2411 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
2412 break;
2413 case lt::save_resume_data_alert::alert_type:
2414 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
2415 break;
2416 case lt::save_resume_data_failed_alert::alert_type:
2417 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
2418 break;
2419 case lt::torrent_paused_alert::alert_type:
2420 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
2421 break;
2422 case lt::torrent_resumed_alert::alert_type:
2423 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
2424 break;
2425 case lt::metadata_received_alert::alert_type:
2426 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2427 break;
2428 case lt::fastresume_rejected_alert::alert_type:
2429 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2430 break;
2431 case lt::torrent_checked_alert::alert_type:
2432 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2433 break;
2434 case lt::performance_alert::alert_type:
2435 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2436 break;
2440 void TorrentImpl::manageActualFilePaths()
2442 const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
2443 const lt::file_storage &nativeFiles = nativeInfo->files();
2445 for (int i = 0; i < filesCount(); ++i)
2447 const Path path = filePath(i);
2449 const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
2450 const Path actualPath {nativeFiles.file_path(nativeIndex)};
2451 const Path targetActualPath = makeActualPath(i, path);
2452 if (actualPath != targetActualPath)
2454 qDebug() << "Renaming" << actualPath.toString() << "to" << targetActualPath.toString();
2455 doRenameFile(i, targetActualPath);
2460 void TorrentImpl::adjustStorageLocation()
2462 const Path downloadPath = this->downloadPath();
2463 const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
2465 if ((targetPath != actualStorageLocation()) || isMoveInProgress())
2466 moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
2469 void TorrentImpl::doRenameFile(const int index, const Path &path)
2471 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
2473 Q_ASSERT(index >= 0);
2474 Q_ASSERT(index < nativeIndexes.size());
2475 if ((index < 0) || (index >= nativeIndexes.size())) [[unlikely]]
2476 return;
2478 ++m_renameCount;
2479 m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
2482 lt::torrent_handle TorrentImpl::nativeHandle() const
2484 return m_nativeHandle;
2487 void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
2489 if (hasMetadata())
2490 return;
2492 m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
2496 #ifdef QBT_USES_LIBTORRENT2
2497 nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
2498 #else
2499 const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
2500 nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
2501 #endif
2503 catch (const std::exception &) {}
2507 Torrent::StopCondition TorrentImpl::stopCondition() const
2509 return m_stopCondition;
2512 void TorrentImpl::setStopCondition(const StopCondition stopCondition)
2514 if (stopCondition == m_stopCondition)
2515 return;
2517 if (isStopped())
2518 return;
2520 if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata())
2521 return;
2523 if ((stopCondition == StopCondition::FilesChecked) && hasMetadata() && !isChecking())
2524 return;
2526 m_stopCondition = stopCondition;
2529 SSLParameters TorrentImpl::getSSLParameters() const
2531 return m_sslParams;
2534 void TorrentImpl::setSSLParameters(const SSLParameters &sslParams)
2536 if (sslParams == getSSLParameters())
2537 return;
2539 m_sslParams = sslParams;
2540 applySSLParameters();
2542 deferredRequestResumeData();
2545 bool TorrentImpl::applySSLParameters()
2547 if (!m_sslParams.isValid())
2548 return false;
2550 m_nativeHandle.set_ssl_certificate_buffer(m_sslParams.certificate.toPem().toStdString()
2551 , m_sslParams.privateKey.toPem().toStdString(), m_sslParams.dhParams.toStdString());
2552 return true;
2555 bool TorrentImpl::isMoveInProgress() const
2557 return m_storageIsMoving;
2560 void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
2562 const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
2564 if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
2565 updateProgress();
2567 updateState();
2569 m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
2570 , nativeStatus.upload_payload_rate});
2572 if (hasMetadata())
2574 // NOTE: Don't change the order of these conditionals!
2575 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2576 if (isChecking())
2577 m_unchecked = false;
2578 else if (isDownloading())
2579 m_unchecked = true;
2582 while (!m_statusUpdatedTriggers.isEmpty())
2583 std::invoke(m_statusUpdatedTriggers.dequeue());
2586 void TorrentImpl::updateProgress()
2588 Q_ASSERT(hasMetadata());
2589 if (!hasMetadata()) [[unlikely]]
2590 return;
2592 Q_ASSERT(!m_filesProgress.isEmpty());
2593 if (m_filesProgress.isEmpty()) [[unlikely]]
2594 m_filesProgress.resize(filesCount());
2596 const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
2597 const QBitArray newPieces = m_pieces ^ oldPieces;
2599 const int64_t pieceSize = m_torrentInfo.pieceLength();
2600 for (qsizetype index = 0; index < newPieces.size(); ++index)
2602 if (!newPieces.at(index))
2603 continue;
2605 int64_t size = m_torrentInfo.pieceLength(index);
2606 int64_t pieceOffset = index * pieceSize;
2608 for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
2610 const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
2611 const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
2613 m_filesProgress[fileIndex] += add;
2615 size -= add;
2616 if (size <= 0)
2617 break;
2619 pieceOffset += add;
2624 void TorrentImpl::setRatioLimit(qreal limit)
2626 if (limit < USE_GLOBAL_RATIO)
2627 limit = NO_RATIO_LIMIT;
2628 else if (limit > MAX_RATIO)
2629 limit = MAX_RATIO;
2631 if (m_ratioLimit != limit)
2633 m_ratioLimit = limit;
2634 deferredRequestResumeData();
2635 m_session->handleTorrentShareLimitChanged(this);
2639 void TorrentImpl::setSeedingTimeLimit(int limit)
2641 if (limit < USE_GLOBAL_SEEDING_TIME)
2642 limit = NO_SEEDING_TIME_LIMIT;
2643 else if (limit > MAX_SEEDING_TIME)
2644 limit = MAX_SEEDING_TIME;
2646 if (m_seedingTimeLimit != limit)
2648 m_seedingTimeLimit = limit;
2649 deferredRequestResumeData();
2650 m_session->handleTorrentShareLimitChanged(this);
2654 void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
2656 if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
2657 limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
2658 else if (limit > MAX_INACTIVE_SEEDING_TIME)
2659 limit = MAX_SEEDING_TIME;
2661 if (m_inactiveSeedingTimeLimit != limit)
2663 m_inactiveSeedingTimeLimit = limit;
2664 deferredRequestResumeData();
2665 m_session->handleTorrentShareLimitChanged(this);
2669 ShareLimitAction TorrentImpl::shareLimitAction() const
2671 return m_shareLimitAction;
2674 void TorrentImpl::setShareLimitAction(const ShareLimitAction action)
2676 if (m_shareLimitAction != action)
2678 m_shareLimitAction = action;
2679 deferredRequestResumeData();
2680 m_session->handleTorrentShareLimitChanged(this);
2684 void TorrentImpl::setUploadLimit(const int limit)
2686 const int cleanValue = cleanLimitValue(limit);
2687 if (cleanValue == uploadLimit())
2688 return;
2690 m_uploadLimit = cleanValue;
2691 m_nativeHandle.set_upload_limit(m_uploadLimit);
2692 deferredRequestResumeData();
2695 void TorrentImpl::setDownloadLimit(const int limit)
2697 const int cleanValue = cleanLimitValue(limit);
2698 if (cleanValue == downloadLimit())
2699 return;
2701 m_downloadLimit = cleanValue;
2702 m_nativeHandle.set_download_limit(m_downloadLimit);
2703 deferredRequestResumeData();
2706 void TorrentImpl::setSuperSeeding(const bool enable)
2708 if (enable == superSeeding())
2709 return;
2711 if (enable)
2712 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2713 else
2714 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2716 deferredRequestResumeData();
2719 void TorrentImpl::setDHTDisabled(const bool disable)
2721 if (disable == isDHTDisabled())
2722 return;
2724 if (disable)
2725 m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
2726 else
2727 m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
2729 deferredRequestResumeData();
2732 void TorrentImpl::setPEXDisabled(const bool disable)
2734 if (disable == isPEXDisabled())
2735 return;
2737 if (disable)
2738 m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
2739 else
2740 m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
2742 deferredRequestResumeData();
2745 void TorrentImpl::setLSDDisabled(const bool disable)
2747 if (disable == isLSDDisabled())
2748 return;
2750 if (disable)
2751 m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
2752 else
2753 m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
2755 deferredRequestResumeData();
2758 void TorrentImpl::flushCache() const
2760 m_nativeHandle.flush_cache();
2763 QString TorrentImpl::createMagnetURI() const
2765 QString ret = u"magnet:?"_s;
2767 const SHA1Hash infoHash1 = infoHash().v1();
2768 if (infoHash1.isValid())
2770 ret += u"xt=urn:btih:" + infoHash1.toString();
2773 const SHA256Hash infoHash2 = infoHash().v2();
2774 if (infoHash2.isValid())
2776 if (infoHash1.isValid())
2777 ret += u'&';
2778 ret += u"xt=urn:btmh:1220" + infoHash2.toString();
2781 const QString displayName = name();
2782 if (displayName != id().toString())
2784 ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
2787 for (const TrackerEntryStatus &tracker : asConst(trackers()))
2789 ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
2792 for (const QUrl &urlSeed : asConst(urlSeeds()))
2794 ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
2797 return ret;
2800 nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
2802 if (!hasMetadata())
2803 return nonstd::make_unexpected(tr("Missing metadata"));
2807 #ifdef QBT_USES_LIBTORRENT2
2808 const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
2809 const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
2810 #else
2811 const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
2812 #endif
2813 lt::create_torrent creator {*torrentInfo};
2815 for (const TrackerEntryStatus &status : asConst(trackers()))
2816 creator.add_tracker(status.url.toStdString(), status.tier);
2818 return creator.generate();
2820 catch (const lt::system_error &err)
2822 return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
2826 nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
2828 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2829 if (!preparationResult)
2830 return preparationResult.get_unexpected();
2832 // usually torrent size should be smaller than 1 MB,
2833 // however there are >100 MB v2/hybrid torrent files out in the wild
2834 QByteArray buffer;
2835 buffer.reserve(1024 * 1024);
2836 lt::bencode(std::back_inserter(buffer), preparationResult.value());
2837 return buffer;
2840 nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) const
2842 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2843 if (!preparationResult)
2844 return preparationResult.get_unexpected();
2846 const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
2847 if (!saveResult)
2848 return saveResult.get_unexpected();
2850 return {};
2853 void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
2855 invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
2859 std::vector<lt::peer_info> nativePeers;
2860 nativeHandle.get_peer_info(nativePeers);
2861 QVector<PeerInfo> peers;
2862 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
2863 for (const lt::peer_info &peer : nativePeers)
2864 peers.append(PeerInfo(peer, allPieces));
2865 return peers;
2867 catch (const std::exception &) {}
2869 return {};
2871 , std::move(resultHandler));
2874 void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
2876 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
2880 const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
2881 QVector<QUrl> urlSeeds;
2882 urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
2883 for (const std::string &urlSeed : currentSeeds)
2884 urlSeeds.append(QString::fromStdString(urlSeed));
2885 return urlSeeds;
2887 catch (const std::exception &) {}
2889 return {};
2891 , std::move(resultHandler));
2894 void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
2896 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
2900 std::vector<int> piecesAvailability;
2901 nativeHandle.piece_availability(piecesAvailability);
2902 return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
2904 catch (const std::exception &) {}
2906 return {};
2908 , std::move(resultHandler));
2911 void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
2913 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
2917 #ifdef QBT_USES_LIBTORRENT2
2918 const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
2919 #else
2920 std::vector<lt::partial_piece_info> queue;
2921 nativeHandle.get_download_queue(queue);
2922 #endif
2923 QBitArray result;
2924 result.resize(torrentInfo.piecesCount());
2925 for (const lt::partial_piece_info &info : queue)
2926 result.setBit(LT::toUnderlyingType(info.piece_index));
2927 return result;
2929 catch (const std::exception &) {}
2931 return {};
2933 , std::move(resultHandler));
2936 void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
2938 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
2940 if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
2941 return {};
2945 std::vector<int> piecesAvailability;
2946 nativeHandle.piece_availability(piecesAvailability);
2947 const int filesCount = torrentInfo.filesCount();
2948 // libtorrent returns empty array for seeding only torrents
2949 if (piecesAvailability.empty())
2950 return QVector<qreal>(filesCount, -1);
2952 QVector<qreal> result;
2953 result.reserve(filesCount);
2954 for (int i = 0; i < filesCount; ++i)
2956 const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
2958 int availablePieces = 0;
2959 for (const int piece : filePieces)
2960 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2962 const qreal availability = filePieces.isEmpty()
2963 ? 1 // the file has no pieces, so it is available by default
2964 : static_cast<qreal>(availablePieces) / filePieces.size();
2965 result.append(availability);
2967 return result;
2969 catch (const std::exception &) {}
2971 return {};
2973 , std::move(resultHandler));
2976 void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
2978 if (!hasMetadata())
2979 return;
2981 Q_ASSERT(priorities.size() == filesCount());
2983 // Reset 'm_hasSeedStatus' if needed in order to react again to
2984 // 'torrent_finished_alert' and eg show tray notifications
2985 const QVector<DownloadPriority> oldPriorities = filePriorities();
2986 for (int i = 0; i < oldPriorities.size(); ++i)
2988 if ((oldPriorities[i] == DownloadPriority::Ignored)
2989 && (priorities[i] > DownloadPriority::Ignored)
2990 && !m_completedFiles.at(i))
2992 m_hasFinishedStatus = false;
2993 break;
2997 const int internalFilesCount = m_torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2998 auto nativePriorities = std::vector<lt::download_priority_t>(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2999 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
3000 for (int i = 0; i < priorities.size(); ++i)
3001 nativePriorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(priorities[i]);
3003 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
3004 m_nativeHandle.prioritize_files(nativePriorities);
3006 m_filePriorities = priorities;
3007 // Restore first/last piece first option if necessary
3008 if (m_hasFirstLastPiecePriority)
3009 applyFirstLastPiecePriority(true);
3010 manageActualFilePaths();
3013 QVector<qreal> TorrentImpl::availableFileFractions() const
3015 Q_ASSERT(hasMetadata());
3017 const int filesCount = this->filesCount();
3018 if (filesCount <= 0) return {};
3020 const QVector<int> piecesAvailability = pieceAvailability();
3021 // libtorrent returns empty array for seeding only torrents
3022 if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1);
3024 QVector<qreal> res;
3025 res.reserve(filesCount);
3026 for (int i = 0; i < filesCount; ++i)
3028 const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
3030 int availablePieces = 0;
3031 for (const int piece : filePieces)
3032 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
3034 const qreal availability = filePieces.isEmpty()
3035 ? 1 // the file has no pieces, so it is available by default
3036 : static_cast<qreal>(availablePieces) / filePieces.size();
3037 res.push_back(availability);
3039 return res;
3042 template <typename Func, typename Callback>
3043 void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
3045 m_session->invokeAsync([session = m_session
3046 , func = std::move(func)
3047 , resultHandler = std::move(resultHandler)
3048 , thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
3050 session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
3052 if (thisTorrent)
3053 resultHandler(result);