Display error message when unrecoverable error occurred
[qBittorrent.git] / src / base / bittorrent / torrentimpl.cpp
blob2aa85e76ab77c07d3587ff39975ed53fc7b176f7
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 #include <libtorrent/address.hpp>
36 #include <libtorrent/alert_types.hpp>
37 #include <libtorrent/create_torrent.hpp>
38 #include <libtorrent/session.hpp>
39 #include <libtorrent/storage_defs.hpp>
40 #include <libtorrent/time.hpp>
42 #ifdef QBT_USES_LIBTORRENT2
43 #include <libtorrent/info_hash.hpp>
44 #endif
46 #include <QByteArray>
47 #include <QDebug>
48 #include <QPointer>
49 #include <QSet>
50 #include <QStringList>
51 #include <QUrl>
53 #include "base/exceptions.h"
54 #include "base/global.h"
55 #include "base/logger.h"
56 #include "base/preferences.h"
57 #include "base/utils/fs.h"
58 #include "base/utils/io.h"
59 #include "base/utils/string.h"
60 #include "common.h"
61 #include "downloadpriority.h"
62 #include "extensiondata.h"
63 #include "loadtorrentparams.h"
64 #include "ltqbitarray.h"
65 #include "lttypecast.h"
66 #include "peeraddress.h"
67 #include "peerinfo.h"
68 #include "sessionimpl.h"
70 using namespace BitTorrent;
72 namespace
74 lt::announce_entry makeNativeAnnounceEntry(const QString &url, const int tier)
76 lt::announce_entry entry {url.toStdString()};
77 entry.tier = tier;
78 return entry;
81 #ifdef QBT_USES_LIBTORRENT2
82 void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
83 , const lt::info_hash_t &hashes, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
84 #else
85 void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
86 , const QMap<TrackerEntry::Endpoint, int> &updateInfo)
87 #endif
89 Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
91 trackerEntry.tier = nativeEntry.tier;
93 int numUpdating = 0;
94 int numWorking = 0;
95 int numNotWorking = 0;
96 QString firstTrackerMessage;
97 QString firstErrorMessage;
98 #ifdef QBT_USES_LIBTORRENT2
99 const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1);
100 for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
102 for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
104 if (hashes.has(protocolVersion))
106 const lt::announce_infohash &infoHash = endpoint.info_hashes[protocolVersion];
108 TrackerEntry::EndpointStats trackerEndpoint;
109 trackerEndpoint.numPeers = updateInfo.value(endpoint.local_endpoint, trackerEndpoint.numPeers);
110 trackerEndpoint.numSeeds = infoHash.scrape_complete;
111 trackerEndpoint.numLeeches = infoHash.scrape_incomplete;
112 trackerEndpoint.numDownloaded = infoHash.scrape_downloaded;
114 if (infoHash.updating)
116 trackerEndpoint.status = TrackerEntry::Updating;
117 ++numUpdating;
119 else if (infoHash.fails > 0)
121 trackerEndpoint.status = TrackerEntry::NotWorking;
122 ++numNotWorking;
124 else if (nativeEntry.verified)
126 trackerEndpoint.status = TrackerEntry::Working;
127 ++numWorking;
129 else
131 trackerEndpoint.status = TrackerEntry::NotContacted;
134 const QString trackerMessage = QString::fromStdString(infoHash.message);
135 const QString errorMessage = QString::fromLocal8Bit(infoHash.last_error.message().c_str());
136 trackerEndpoint.message = (!trackerMessage.isEmpty() ? trackerMessage : errorMessage);
138 trackerEntry.stats[endpoint.local_endpoint][(protocolVersion == lt::protocol_version::V1) ? 1 : 2] = trackerEndpoint;
139 trackerEntry.numPeers = std::max(trackerEntry.numPeers, trackerEndpoint.numPeers);
140 trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, trackerEndpoint.numSeeds);
141 trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, trackerEndpoint.numLeeches);
142 trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, trackerEndpoint.numDownloaded);
144 if (firstTrackerMessage.isEmpty())
145 firstTrackerMessage = trackerMessage;
146 if (firstErrorMessage.isEmpty())
147 firstErrorMessage = errorMessage;
151 #else
152 const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size());
153 for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
155 TrackerEntry::EndpointStats trackerEndpoint;
156 trackerEndpoint.numPeers = updateInfo.value(endpoint.local_endpoint, trackerEndpoint.numPeers);
157 trackerEndpoint.numSeeds = endpoint.scrape_complete;
158 trackerEndpoint.numLeeches = endpoint.scrape_incomplete;
159 trackerEndpoint.numDownloaded = endpoint.scrape_downloaded;
161 if (endpoint.updating)
163 trackerEndpoint.status = TrackerEntry::Updating;
164 ++numUpdating;
166 else if (endpoint.fails > 0)
168 trackerEndpoint.status = TrackerEntry::NotWorking;
169 ++numNotWorking;
171 else if (nativeEntry.verified)
173 trackerEndpoint.status = TrackerEntry::Working;
174 ++numWorking;
176 else
178 trackerEndpoint.status = TrackerEntry::NotContacted;
181 const QString trackerMessage = QString::fromStdString(endpoint.message);
182 const QString errorMessage = QString::fromLocal8Bit(endpoint.last_error.message().c_str());
183 trackerEndpoint.message = (!trackerMessage.isEmpty() ? trackerMessage : errorMessage);
185 trackerEntry.stats[endpoint.local_endpoint][1] = trackerEndpoint;
186 trackerEntry.numPeers = std::max(trackerEntry.numPeers, trackerEndpoint.numPeers);
187 trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, trackerEndpoint.numSeeds);
188 trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, trackerEndpoint.numLeeches);
189 trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, trackerEndpoint.numDownloaded);
191 if (firstTrackerMessage.isEmpty())
192 firstTrackerMessage = trackerMessage;
193 if (firstErrorMessage.isEmpty())
194 firstErrorMessage = errorMessage;
196 #endif
198 if (numEndpoints > 0)
200 if (numUpdating > 0)
202 trackerEntry.status = TrackerEntry::Updating;
204 else if (numWorking > 0)
206 trackerEntry.status = TrackerEntry::Working;
207 trackerEntry.message = firstTrackerMessage;
209 else if (numNotWorking == numEndpoints)
211 trackerEntry.status = TrackerEntry::NotWorking;
212 trackerEntry.message = (!firstTrackerMessage.isEmpty() ? firstTrackerMessage : firstErrorMessage);
217 template <typename Vector>
218 Vector resized(const Vector &inVector, const typename Vector::size_type size, const typename Vector::value_type &defaultValue)
220 Vector outVector = inVector;
221 outVector.resize(size, defaultValue);
222 return outVector;
225 // This is an imitation of limit normalization performed by libtorrent itself.
226 // We need perform it to keep cached values in line with the ones used by libtorrent.
227 int cleanLimitValue(const int value)
229 return ((value < 0) || (value == std::numeric_limits<int>::max())) ? 0 : value;
233 // TorrentImpl
235 TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
236 , const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
237 : Torrent(session)
238 , m_session(session)
239 , m_nativeSession(nativeSession)
240 , m_nativeHandle(nativeHandle)
241 #ifdef QBT_USES_LIBTORRENT2
242 , m_infoHash(m_nativeHandle.info_hashes())
243 #else
244 , m_infoHash(m_nativeHandle.info_hash())
245 #endif
246 , m_name(params.name)
247 , m_savePath(params.savePath)
248 , m_downloadPath(params.downloadPath)
249 , m_category(params.category)
250 , m_tags(params.tags)
251 , m_ratioLimit(params.ratioLimit)
252 , m_seedingTimeLimit(params.seedingTimeLimit)
253 , m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit)
254 , m_operatingMode(params.operatingMode)
255 , m_contentLayout(params.contentLayout)
256 , m_hasFinishedStatus(params.hasFinishedStatus)
257 , m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
258 , m_useAutoTMM(params.useAutoTMM)
259 , m_isStopped(params.stopped)
260 , m_ltAddTorrentParams(params.ltAddTorrentParams)
261 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit))
262 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit))
264 if (m_ltAddTorrentParams.ti)
266 // Initialize it only if torrent is added with metadata.
267 // Otherwise it should be initialized in "Metadata received" handler.
268 m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
270 Q_ASSERT(m_filePaths.isEmpty());
271 Q_ASSERT(m_indexMap.isEmpty());
272 const int filesCount = m_torrentInfo.filesCount();
273 m_filePaths.reserve(filesCount);
274 m_indexMap.reserve(filesCount);
275 m_filePriorities.reserve(filesCount);
276 const std::vector<lt::download_priority_t> filePriorities =
277 resized(m_ltAddTorrentParams.file_priorities, m_ltAddTorrentParams.ti->num_files()
278 , LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
280 m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
281 m_filesProgress.resize(filesCount);
283 for (int i = 0; i < filesCount; ++i)
285 const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
286 m_indexMap[nativeIndex] = i;
288 const auto fileIter = m_ltAddTorrentParams.renamed_files.find(nativeIndex);
289 const Path filePath = ((fileIter != m_ltAddTorrentParams.renamed_files.end())
290 ? Path(fileIter->second).removedExtension(QB_EXT) : m_torrentInfo.filePath(i));
291 m_filePaths.append(filePath);
293 const auto priority = LT::fromNative(filePriorities[LT::toUnderlyingType(nativeIndex)]);
294 m_filePriorities.append(priority);
298 setStopCondition(params.stopCondition);
300 const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
301 m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
302 for (const lt::announce_entry &announceEntry : extensionData->trackers)
303 m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
304 m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
305 for (const std::string &urlSeed : extensionData->urlSeeds)
306 m_urlSeeds.append(QString::fromStdString(urlSeed));
307 m_nativeStatus = extensionData->status;
309 if (hasMetadata())
310 updateProgress();
312 updateState();
314 if (hasMetadata())
315 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
317 // TODO: Remove the following upgrade code in v4.4
318 // == BEGIN UPGRADE CODE ==
319 const Path spath = actualStorageLocation();
320 for (int i = 0; i < filesCount(); ++i)
322 const Path filepath = filePath(i);
323 // Move "unwanted" files back to their original folder
324 const Path parentRelPath = filepath.parentPath();
325 if (parentRelPath.filename() == u".unwanted")
327 const QString oldName = filepath.filename();
328 const Path newRelPath = parentRelPath.parentPath();
329 renameFile(i, (newRelPath / Path(oldName)));
331 // Remove .unwanted directory if empty
332 const Path newPath = spath / newRelPath;
333 qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath / Path(u".unwanted"_s)).toString();
334 Utils::Fs::rmdir(newPath / Path(u".unwanted"_s));
337 // == END UPGRADE CODE ==
340 TorrentImpl::~TorrentImpl() = default;
342 bool TorrentImpl::isValid() const
344 return m_nativeHandle.is_valid();
347 InfoHash TorrentImpl::infoHash() const
349 return m_infoHash;
352 QString TorrentImpl::name() const
354 if (!m_name.isEmpty())
355 return m_name;
357 if (hasMetadata())
358 return m_torrentInfo.name();
360 const QString name = QString::fromStdString(m_nativeStatus.name);
361 if (!name.isEmpty())
362 return name;
364 return id().toString();
367 QDateTime TorrentImpl::creationDate() const
369 return m_torrentInfo.creationDate();
372 QString TorrentImpl::creator() const
374 return m_torrentInfo.creator();
377 QString TorrentImpl::comment() const
379 return m_torrentInfo.comment();
382 bool TorrentImpl::isPrivate() const
384 return m_torrentInfo.isPrivate();
387 qlonglong TorrentImpl::totalSize() const
389 return m_torrentInfo.totalSize();
392 // size without the "don't download" files
393 qlonglong TorrentImpl::wantedSize() const
395 return m_nativeStatus.total_wanted;
398 qlonglong TorrentImpl::completedSize() const
400 return m_nativeStatus.total_wanted_done;
403 qlonglong TorrentImpl::pieceLength() const
405 return m_torrentInfo.pieceLength();
408 qlonglong TorrentImpl::wastedSize() const
410 return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes);
413 QString TorrentImpl::currentTracker() const
415 return QString::fromStdString(m_nativeStatus.current_tracker);
418 Path TorrentImpl::savePath() const
420 return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath;
423 void TorrentImpl::setSavePath(const Path &path)
425 Q_ASSERT(!isAutoTMMEnabled());
427 const Path basePath = m_session->useCategoryPathsInManualMode()
428 ? m_session->categorySavePath(category()) : m_session->savePath();
429 const Path resolvedPath = (path.isAbsolute() ? path : (basePath / path));
430 if (resolvedPath == savePath())
431 return;
433 if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
435 moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
437 else
439 m_savePath = resolvedPath;
440 m_session->handleTorrentSavePathChanged(this);
441 m_session->handleTorrentNeedSaveResumeData(this);
445 Path TorrentImpl::downloadPath() const
447 return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath;
450 void TorrentImpl::setDownloadPath(const Path &path)
452 Q_ASSERT(!isAutoTMMEnabled());
454 const Path basePath = m_session->useCategoryPathsInManualMode()
455 ? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
456 const Path resolvedPath = (path.isEmpty() || path.isAbsolute()) ? path : (basePath / path);
457 if (resolvedPath == m_downloadPath)
458 return;
460 const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
461 if (isIncomplete)
463 moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
465 else
467 m_downloadPath = resolvedPath;
468 m_session->handleTorrentSavePathChanged(this);
469 m_session->handleTorrentNeedSaveResumeData(this);
473 Path TorrentImpl::rootPath() const
475 if (!hasMetadata())
476 return {};
478 const Path relativeRootPath = Path::findRootFolder(filePaths());
479 if (relativeRootPath.isEmpty())
480 return {};
482 return (actualStorageLocation() / relativeRootPath);
485 Path TorrentImpl::contentPath() const
487 if (!hasMetadata())
488 return {};
490 if (filesCount() == 1)
491 return (actualStorageLocation() / filePath(0));
493 const Path rootPath = this->rootPath();
494 return (rootPath.isEmpty() ? actualStorageLocation() : rootPath);
497 bool TorrentImpl::isAutoTMMEnabled() const
499 return m_useAutoTMM;
502 void TorrentImpl::setAutoTMMEnabled(bool enabled)
504 if (m_useAutoTMM == enabled)
505 return;
507 m_useAutoTMM = enabled;
508 if (!m_useAutoTMM)
510 m_savePath = m_session->categorySavePath(category());
511 m_downloadPath = m_session->categoryDownloadPath(category());
514 m_session->handleTorrentNeedSaveResumeData(this);
515 m_session->handleTorrentSavingModeChanged(this);
517 adjustStorageLocation();
520 Path TorrentImpl::actualStorageLocation() const
522 if (!hasMetadata())
523 return {};
525 return Path(m_nativeStatus.save_path);
528 void TorrentImpl::setAutoManaged(const bool enable)
530 if (enable)
531 m_nativeHandle.set_flags(lt::torrent_flags::auto_managed);
532 else
533 m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
536 Path TorrentImpl::wantedActualPath(int index, const Path &path) const
538 if (m_session->isAppendExtensionEnabled()
539 && (fileSize(index) > 0) && !m_completedFiles.at(index))
541 return path + QB_EXT;
544 return path;
547 QVector<TrackerEntry> TorrentImpl::trackers() const
549 return m_trackerEntries;
552 void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
554 trackers.removeIf([](const TrackerEntry &entry) { return entry.url.isEmpty(); });
556 const auto newTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend())
557 - QSet<TrackerEntry>(m_trackerEntries.cbegin(), m_trackerEntries.cend());
558 if (newTrackers.isEmpty())
559 return;
561 trackers = QVector<TrackerEntry>(newTrackers.cbegin(), newTrackers.cend());
562 for (const TrackerEntry &tracker : trackers)
563 m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
565 m_trackerEntries.append(trackers);
566 std::sort(m_trackerEntries.begin(), m_trackerEntries.end()
567 , [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
569 m_session->handleTorrentNeedSaveResumeData(this);
570 m_session->handleTorrentTrackersAdded(this, trackers);
573 void TorrentImpl::removeTrackers(const QStringList &trackers)
575 QStringList removedTrackers = trackers;
576 for (const QString &tracker : trackers)
578 if (!m_trackerEntries.removeOne({tracker}))
579 removedTrackers.removeOne(tracker);
582 std::vector<lt::announce_entry> nativeTrackers;
583 nativeTrackers.reserve(m_trackerEntries.size());
584 for (const TrackerEntry &tracker : asConst(m_trackerEntries))
585 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
587 if (!removedTrackers.isEmpty())
589 m_nativeHandle.replace_trackers(nativeTrackers);
591 m_session->handleTorrentNeedSaveResumeData(this);
592 m_session->handleTorrentTrackersRemoved(this, removedTrackers);
596 void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
598 trackers.removeIf([](const TrackerEntry &entry) { return entry.url.isEmpty(); });
599 std::sort(trackers.begin(), trackers.end()
600 , [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
602 std::vector<lt::announce_entry> nativeTrackers;
603 nativeTrackers.reserve(trackers.size());
604 for (const TrackerEntry &tracker : trackers)
605 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
607 m_nativeHandle.replace_trackers(nativeTrackers);
608 m_trackerEntries = trackers;
610 // Clear the peer list if it's a private torrent since
611 // we do not want to keep connecting with peers from old tracker.
612 if (isPrivate())
613 clearPeers();
615 m_session->handleTorrentNeedSaveResumeData(this);
616 m_session->handleTorrentTrackersChanged(this);
619 QVector<QUrl> TorrentImpl::urlSeeds() const
621 return m_urlSeeds;
624 void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
626 m_session->invokeAsync([urlSeeds, session = m_session
627 , nativeHandle = m_nativeHandle
628 , thisTorrent = QPointer<TorrentImpl>(this)]
632 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
633 QVector<QUrl> currentSeeds;
634 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
635 for (const std::string &urlSeed : nativeSeeds)
636 currentSeeds.append(QString::fromStdString(urlSeed));
638 QVector<QUrl> addedUrlSeeds;
639 addedUrlSeeds.reserve(urlSeeds.size());
641 for (const QUrl &url : urlSeeds)
643 if (!currentSeeds.contains(url))
645 nativeHandle.add_url_seed(url.toString().toStdString());
646 addedUrlSeeds.append(url);
650 currentSeeds.append(addedUrlSeeds);
651 session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
653 if (!thisTorrent)
654 return;
656 thisTorrent->m_urlSeeds = currentSeeds;
657 if (!addedUrlSeeds.isEmpty())
659 session->handleTorrentNeedSaveResumeData(thisTorrent);
660 session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
664 catch (const std::exception &) {}
668 void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
670 m_session->invokeAsync([urlSeeds, session = m_session
671 , nativeHandle = m_nativeHandle
672 , thisTorrent = QPointer<TorrentImpl>(this)]
676 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
677 QVector<QUrl> currentSeeds;
678 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
679 for (const std::string &urlSeed : nativeSeeds)
680 currentSeeds.append(QString::fromStdString(urlSeed));
682 QVector<QUrl> removedUrlSeeds;
683 removedUrlSeeds.reserve(urlSeeds.size());
685 for (const QUrl &url : urlSeeds)
687 if (currentSeeds.removeOne(url))
689 nativeHandle.remove_url_seed(url.toString().toStdString());
690 removedUrlSeeds.append(url);
694 session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
696 if (!thisTorrent)
697 return;
699 thisTorrent->m_urlSeeds = currentSeeds;
701 if (!removedUrlSeeds.isEmpty())
703 session->handleTorrentNeedSaveResumeData(thisTorrent);
704 session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
708 catch (const std::exception &) {}
712 void TorrentImpl::clearPeers()
714 m_nativeHandle.clear_peers();
717 bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
719 lt::error_code ec;
720 const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec);
721 if (ec) return false;
723 const lt::tcp::endpoint endpoint(addr, peerAddress.port);
726 m_nativeHandle.connect_peer(endpoint);
728 catch (const lt::system_error &err)
730 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
731 .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
732 return false;
735 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name()));
736 return true;
739 bool TorrentImpl::needSaveResumeData() const
741 return m_nativeStatus.need_save_resume;
744 void TorrentImpl::saveResumeData(lt::resume_data_flags_t flags)
746 m_nativeHandle.save_resume_data(flags);
747 m_session->handleTorrentSaveResumeDataRequested(this);
750 int TorrentImpl::filesCount() const
752 return m_torrentInfo.filesCount();
755 int TorrentImpl::piecesCount() const
757 return m_torrentInfo.piecesCount();
760 int TorrentImpl::piecesHave() const
762 return m_nativeStatus.num_pieces;
765 qreal TorrentImpl::progress() const
767 if (isChecking())
768 return m_nativeStatus.progress;
770 if (m_nativeStatus.total_wanted == 0)
771 return 0.;
773 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
774 return 1.;
776 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
777 if ((progress < 0.f) || (progress > 1.f))
779 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
780 .arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
781 , Log::WARNING);
784 return progress;
787 QString TorrentImpl::category() const
789 return m_category;
792 bool TorrentImpl::belongsToCategory(const QString &category) const
794 if (m_category.isEmpty())
795 return category.isEmpty();
797 if (m_category == category)
798 return true;
800 return (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + u'/'));
803 TagSet TorrentImpl::tags() const
805 return m_tags;
808 bool TorrentImpl::hasTag(const QString &tag) const
810 return m_tags.contains(tag);
813 bool TorrentImpl::addTag(const QString &tag)
815 if (!m_session->isValidTag(tag))
816 return false;
817 if (hasTag(tag))
818 return false;
820 if (!m_session->hasTag(tag))
822 if (!m_session->addTag(tag))
823 return false;
825 m_tags.insert(tag);
826 m_session->handleTorrentNeedSaveResumeData(this);
827 m_session->handleTorrentTagAdded(this, tag);
828 return true;
831 bool TorrentImpl::removeTag(const QString &tag)
833 if (m_tags.remove(tag))
835 m_session->handleTorrentNeedSaveResumeData(this);
836 m_session->handleTorrentTagRemoved(this, tag);
837 return true;
839 return false;
842 void TorrentImpl::removeAllTags()
844 for (const QString &tag : asConst(tags()))
845 removeTag(tag);
848 QDateTime TorrentImpl::addedTime() const
850 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
853 qreal TorrentImpl::ratioLimit() const
855 return m_ratioLimit;
858 int TorrentImpl::seedingTimeLimit() const
860 return m_seedingTimeLimit;
863 int TorrentImpl::inactiveSeedingTimeLimit() const
865 return m_inactiveSeedingTimeLimit;
868 Path TorrentImpl::filePath(const int index) const
870 Q_ASSERT(index >= 0);
871 Q_ASSERT(index < m_filePaths.size());
873 return m_filePaths.value(index, {});
876 Path TorrentImpl::actualFilePath(const int index) const
878 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
880 Q_ASSERT(index >= 0);
881 Q_ASSERT(index < nativeIndexes.size());
882 if ((index < 0) || (index >= nativeIndexes.size()))
883 return {};
885 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes[index]));
888 qlonglong TorrentImpl::fileSize(const int index) const
890 return m_torrentInfo.fileSize(index);
893 PathList TorrentImpl::filePaths() const
895 return m_filePaths;
898 QVector<DownloadPriority> TorrentImpl::filePriorities() const
900 return m_filePriorities;
903 TorrentInfo TorrentImpl::info() const
905 return m_torrentInfo;
908 bool TorrentImpl::isPaused() const
910 return m_isStopped;
913 bool TorrentImpl::isQueued() const
915 // Torrent is Queued if it isn't in Paused state but paused internally
916 return (!isPaused()
917 && (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
918 && (m_nativeStatus.flags & lt::torrent_flags::paused));
921 bool TorrentImpl::isChecking() const
923 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
924 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
927 bool TorrentImpl::isDownloading() const
929 switch (m_state)
931 case TorrentState::Downloading:
932 case TorrentState::DownloadingMetadata:
933 case TorrentState::ForcedDownloadingMetadata:
934 case TorrentState::StalledDownloading:
935 case TorrentState::CheckingDownloading:
936 case TorrentState::PausedDownloading:
937 case TorrentState::QueuedDownloading:
938 case TorrentState::ForcedDownloading:
939 return true;
940 default:
941 break;
944 return false;
947 bool TorrentImpl::isMoving() const
949 return m_state == TorrentState::Moving;
952 bool TorrentImpl::isUploading() const
954 switch (m_state)
956 case TorrentState::Uploading:
957 case TorrentState::StalledUploading:
958 case TorrentState::CheckingUploading:
959 case TorrentState::QueuedUploading:
960 case TorrentState::ForcedUploading:
961 return true;
962 default:
963 break;
966 return false;
969 bool TorrentImpl::isCompleted() const
971 switch (m_state)
973 case TorrentState::Uploading:
974 case TorrentState::StalledUploading:
975 case TorrentState::CheckingUploading:
976 case TorrentState::PausedUploading:
977 case TorrentState::QueuedUploading:
978 case TorrentState::ForcedUploading:
979 return true;
980 default:
981 break;
984 return false;
987 bool TorrentImpl::isActive() const
989 switch (m_state)
991 case TorrentState::StalledDownloading:
992 return (uploadPayloadRate() > 0);
994 case TorrentState::DownloadingMetadata:
995 case TorrentState::ForcedDownloadingMetadata:
996 case TorrentState::Downloading:
997 case TorrentState::ForcedDownloading:
998 case TorrentState::Uploading:
999 case TorrentState::ForcedUploading:
1000 case TorrentState::Moving:
1001 return true;
1003 default:
1004 break;
1007 return false;
1010 bool TorrentImpl::isInactive() const
1012 return !isActive();
1015 bool TorrentImpl::isErrored() const
1017 return ((m_state == TorrentState::MissingFiles)
1018 || (m_state == TorrentState::Error));
1021 bool TorrentImpl::isFinished() const
1023 return ((m_nativeStatus.state == lt::torrent_status::finished)
1024 || (m_nativeStatus.state == lt::torrent_status::seeding));
1027 bool TorrentImpl::isForced() const
1029 return (!isPaused() && (m_operatingMode == TorrentOperatingMode::Forced));
1032 bool TorrentImpl::isSequentialDownload() const
1034 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::sequential_download);
1037 bool TorrentImpl::hasFirstLastPiecePriority() const
1039 return m_hasFirstLastPiecePriority;
1042 TorrentState TorrentImpl::state() const
1044 return m_state;
1047 void TorrentImpl::updateState()
1049 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
1051 m_state = TorrentState::CheckingResumeData;
1053 else if (isMoveInProgress())
1055 m_state = TorrentState::Moving;
1057 else if (hasMissingFiles())
1059 m_state = TorrentState::MissingFiles;
1061 else if (hasError())
1063 m_state = TorrentState::Error;
1065 else if (!hasMetadata())
1067 if (isPaused())
1068 m_state = TorrentState::PausedDownloading;
1069 else if (m_session->isQueueingSystemEnabled() && isQueued())
1070 m_state = TorrentState::QueuedDownloading;
1071 else
1072 m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata;
1074 else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused())
1076 // If the torrent is not just in the "checking" state, but is being actually checked
1077 m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
1079 else if (isFinished())
1081 if (isPaused())
1082 m_state = TorrentState::PausedUploading;
1083 else if (m_session->isQueueingSystemEnabled() && isQueued())
1084 m_state = TorrentState::QueuedUploading;
1085 else if (isForced())
1086 m_state = TorrentState::ForcedUploading;
1087 else if (m_nativeStatus.upload_payload_rate > 0)
1088 m_state = TorrentState::Uploading;
1089 else
1090 m_state = TorrentState::StalledUploading;
1092 else
1094 if (isPaused())
1095 m_state = TorrentState::PausedDownloading;
1096 else if (m_session->isQueueingSystemEnabled() && isQueued())
1097 m_state = TorrentState::QueuedDownloading;
1098 else if (isForced())
1099 m_state = TorrentState::ForcedDownloading;
1100 else if (m_nativeStatus.download_payload_rate > 0)
1101 m_state = TorrentState::Downloading;
1102 else
1103 m_state = TorrentState::StalledDownloading;
1107 bool TorrentImpl::hasMetadata() const
1109 return m_torrentInfo.isValid();
1112 bool TorrentImpl::hasMissingFiles() const
1114 return m_hasMissingFiles;
1117 bool TorrentImpl::hasError() const
1119 return (m_nativeStatus.errc || (m_nativeStatus.flags & lt::torrent_flags::upload_mode));
1122 int TorrentImpl::queuePosition() const
1124 return static_cast<int>(m_nativeStatus.queue_position);
1127 QString TorrentImpl::error() const
1129 if (m_nativeStatus.errc)
1130 return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
1132 if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
1134 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1135 .arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
1138 return {};
1141 qlonglong TorrentImpl::totalDownload() const
1143 return m_nativeStatus.all_time_download;
1146 qlonglong TorrentImpl::totalUpload() const
1148 return m_nativeStatus.all_time_upload;
1151 qlonglong TorrentImpl::activeTime() const
1153 return lt::total_seconds(m_nativeStatus.active_duration);
1156 qlonglong TorrentImpl::finishedTime() const
1158 return lt::total_seconds(m_nativeStatus.finished_duration);
1161 qlonglong TorrentImpl::eta() const
1163 if (isPaused()) return MAX_ETA;
1165 const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
1167 if (isFinished())
1169 const qreal maxRatioValue = maxRatio();
1170 const int maxSeedingTimeValue = maxSeedingTime();
1171 const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
1172 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
1174 qlonglong ratioEta = MAX_ETA;
1176 if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
1179 qlonglong realDL = totalDownload();
1180 if (realDL <= 0)
1181 realDL = wantedSize();
1183 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1186 qlonglong seedingTimeEta = MAX_ETA;
1188 if (maxSeedingTimeValue >= 0)
1190 seedingTimeEta = (maxSeedingTimeValue * 60) - finishedTime();
1191 if (seedingTimeEta < 0)
1192 seedingTimeEta = 0;
1195 qlonglong inactiveSeedingTimeEta = MAX_ETA;
1197 if (maxInactiveSeedingTimeValue >= 0)
1199 inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
1200 inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
1203 return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
1206 if (!speedAverage.download) return MAX_ETA;
1208 return (wantedSize() - completedSize()) / speedAverage.download;
1211 QVector<qreal> TorrentImpl::filesProgress() const
1213 if (!hasMetadata())
1214 return {};
1216 const int count = m_filesProgress.size();
1217 Q_ASSERT(count == filesCount());
1218 if (count != filesCount()) [[unlikely]]
1219 return {};
1221 if (m_completedFiles.count(true) == count)
1222 return QVector<qreal>(count, 1);
1224 QVector<qreal> result;
1225 result.reserve(count);
1226 for (int i = 0; i < count; ++i)
1228 const int64_t progress = m_filesProgress.at(i);
1229 const int64_t size = fileSize(i);
1230 if ((size <= 0) || (progress == size))
1231 result << 1;
1232 else
1233 result << (progress / static_cast<qreal>(size));
1236 return result;
1239 int TorrentImpl::seedsCount() const
1241 return m_nativeStatus.num_seeds;
1244 int TorrentImpl::peersCount() const
1246 return m_nativeStatus.num_peers;
1249 int TorrentImpl::leechsCount() const
1251 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1254 int TorrentImpl::totalSeedsCount() const
1256 return (m_nativeStatus.num_complete > -1) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1259 int TorrentImpl::totalPeersCount() const
1261 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1262 return (peers > -1) ? peers : m_nativeStatus.list_peers;
1265 int TorrentImpl::totalLeechersCount() const
1267 return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1270 QDateTime TorrentImpl::lastSeenComplete() const
1272 if (m_nativeStatus.last_seen_complete > 0)
1273 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1274 else
1275 return {};
1278 QDateTime TorrentImpl::completedTime() const
1280 if (m_nativeStatus.completed_time > 0)
1281 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1282 else
1283 return {};
1286 qlonglong TorrentImpl::timeSinceUpload() const
1288 if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
1289 return -1;
1290 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1293 qlonglong TorrentImpl::timeSinceDownload() const
1295 if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
1296 return -1;
1297 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1300 qlonglong TorrentImpl::timeSinceActivity() const
1302 const qlonglong upTime = timeSinceUpload();
1303 const qlonglong downTime = timeSinceDownload();
1304 return ((upTime < 0) != (downTime < 0))
1305 ? std::max(upTime, downTime)
1306 : std::min(upTime, downTime);
1309 int TorrentImpl::downloadLimit() const
1311 return m_downloadLimit;;
1314 int TorrentImpl::uploadLimit() const
1316 return m_uploadLimit;
1319 bool TorrentImpl::superSeeding() const
1321 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::super_seeding);
1324 bool TorrentImpl::isDHTDisabled() const
1326 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_dht);
1329 bool TorrentImpl::isPEXDisabled() const
1331 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_pex);
1334 bool TorrentImpl::isLSDDisabled() const
1336 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
1339 QVector<PeerInfo> TorrentImpl::peers() const
1341 std::vector<lt::peer_info> nativePeers;
1342 m_nativeHandle.get_peer_info(nativePeers);
1344 QVector<PeerInfo> peers;
1345 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
1347 for (const lt::peer_info &peer : nativePeers)
1348 peers.append(PeerInfo(peer, pieces()));
1350 return peers;
1353 QBitArray TorrentImpl::pieces() const
1355 return m_pieces;
1358 QBitArray TorrentImpl::downloadingPieces() const
1360 QBitArray result(piecesCount());
1362 std::vector<lt::partial_piece_info> queue;
1363 m_nativeHandle.get_download_queue(queue);
1365 for (const lt::partial_piece_info &info : queue)
1366 result.setBit(LT::toUnderlyingType(info.piece_index));
1368 return result;
1371 QVector<int> TorrentImpl::pieceAvailability() const
1373 std::vector<int> avail;
1374 m_nativeHandle.piece_availability(avail);
1376 return {avail.cbegin(), avail.cend()};
1379 qreal TorrentImpl::distributedCopies() const
1381 return m_nativeStatus.distributed_copies;
1384 qreal TorrentImpl::maxRatio() const
1386 if (m_ratioLimit == USE_GLOBAL_RATIO)
1387 return m_session->globalMaxRatio();
1389 return m_ratioLimit;
1392 int TorrentImpl::maxSeedingTime() const
1394 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1395 return m_session->globalMaxSeedingMinutes();
1397 return m_seedingTimeLimit;
1400 int TorrentImpl::maxInactiveSeedingTime() const
1402 if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
1403 return m_session->globalMaxInactiveSeedingMinutes();
1405 return m_inactiveSeedingTimeLimit;
1408 qreal TorrentImpl::realRatio() const
1410 const int64_t upload = m_nativeStatus.all_time_upload;
1411 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1412 const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01))
1413 ? m_nativeStatus.total_done
1414 : m_nativeStatus.all_time_download;
1416 if (download == 0)
1417 return (upload == 0) ? 0 : MAX_RATIO;
1419 const qreal ratio = upload / static_cast<qreal>(download);
1420 Q_ASSERT(ratio >= 0);
1421 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1424 int TorrentImpl::uploadPayloadRate() const
1426 // workaround: suppress the speed for paused state
1427 return isPaused() ? 0 : m_nativeStatus.upload_payload_rate;
1430 int TorrentImpl::downloadPayloadRate() const
1432 // workaround: suppress the speed for paused state
1433 return isPaused() ? 0 : m_nativeStatus.download_payload_rate;
1436 qlonglong TorrentImpl::totalPayloadUpload() const
1438 return m_nativeStatus.total_payload_upload;
1441 qlonglong TorrentImpl::totalPayloadDownload() const
1443 return m_nativeStatus.total_payload_download;
1446 int TorrentImpl::connectionsCount() const
1448 return m_nativeStatus.num_connections;
1451 int TorrentImpl::connectionsLimit() const
1453 return m_nativeStatus.connections_limit;
1456 qlonglong TorrentImpl::nextAnnounce() const
1458 return lt::total_seconds(m_nativeStatus.next_announce);
1461 void TorrentImpl::setName(const QString &name)
1463 if (m_name != name)
1465 m_name = name;
1466 m_session->handleTorrentNeedSaveResumeData(this);
1467 m_session->handleTorrentNameChanged(this);
1471 bool TorrentImpl::setCategory(const QString &category)
1473 if (m_category != category)
1475 if (!category.isEmpty() && !m_session->categories().contains(category))
1476 return false;
1478 const QString oldCategory = m_category;
1479 m_category = category;
1480 m_session->handleTorrentNeedSaveResumeData(this);
1481 m_session->handleTorrentCategoryChanged(this, oldCategory);
1483 if (m_useAutoTMM)
1485 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1486 adjustStorageLocation();
1487 else
1488 setAutoTMMEnabled(false);
1492 return true;
1495 void TorrentImpl::forceReannounce(const int index)
1497 m_nativeHandle.force_reannounce(0, index);
1500 void TorrentImpl::forceDHTAnnounce()
1502 m_nativeHandle.force_dht_announce();
1505 void TorrentImpl::forceRecheck()
1507 if (!hasMetadata())
1508 return;
1510 m_nativeHandle.force_recheck();
1511 // We have to force update the cached state, otherwise someone will be able to get
1512 // an incorrect one during the interval until the cached state is updated in a regular way.
1513 m_nativeStatus.state = lt::torrent_status::checking_resume_data;
1515 m_hasMissingFiles = false;
1516 m_unchecked = false;
1518 m_completedFiles.fill(false);
1519 m_filesProgress.fill(0);
1520 m_pieces.fill(false);
1521 m_nativeStatus.pieces.clear_all();
1522 m_nativeStatus.num_pieces = 0;
1524 if (isPaused())
1526 // When "force recheck" is applied on paused torrent, we temporarily resume it
1527 resume();
1528 m_stopCondition = StopCondition::FilesChecked;
1532 void TorrentImpl::setSequentialDownload(const bool enable)
1534 if (enable)
1536 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1537 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1539 else
1541 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1542 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1545 m_session->handleTorrentNeedSaveResumeData(this);
1548 void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
1550 if (m_hasFirstLastPiecePriority == enabled)
1551 return;
1553 m_hasFirstLastPiecePriority = enabled;
1554 if (hasMetadata())
1555 applyFirstLastPiecePriority(enabled);
1557 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1558 .arg((enabled ? tr("On") : tr("Off")), name()));
1560 m_session->handleTorrentNeedSaveResumeData(this);
1563 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
1565 Q_ASSERT(hasMetadata());
1567 // Download first and last pieces first for every file in the torrent
1569 auto piecePriorities = std::vector<lt::download_priority_t>(m_torrentInfo.piecesCount(), LT::toNative(DownloadPriority::Ignored));
1571 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1572 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1573 for (int fileIndex = 0; fileIndex < m_filePriorities.size(); ++fileIndex)
1575 const DownloadPriority filePrio = m_filePriorities[fileIndex];
1576 if (filePrio <= DownloadPriority::Ignored)
1577 continue;
1579 // Determine the priority to set
1580 const lt::download_priority_t piecePrio = LT::toNative(enabled ? DownloadPriority::Maximum : filePrio);
1581 const TorrentInfo::PieceRange pieceRange = m_torrentInfo.filePieces(fileIndex);
1583 // worst case: AVI index = 1% of total file size (at the end of the file)
1584 const int numPieces = std::ceil(fileSize(fileIndex) * 0.01 / pieceLength());
1585 for (int i = 0; i < numPieces; ++i)
1587 piecePriorities[pieceRange.first() + i] = piecePrio;
1588 piecePriorities[pieceRange.last() - i] = piecePrio;
1591 const int firstPiece = pieceRange.first() + numPieces;
1592 const int lastPiece = pieceRange.last() - numPieces;
1593 for (int pieceIndex = firstPiece; pieceIndex <= lastPiece; ++pieceIndex)
1594 piecePriorities[pieceIndex] = LT::toNative(filePrio);
1597 m_nativeHandle.prioritize_pieces(piecePriorities);
1600 void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
1602 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1603 endReceivedMetadataHandling(savePath, fileNames);
1606 TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
1608 const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
1609 , [&announceEntry](const TrackerEntry &trackerEntry)
1611 return (trackerEntry.url == QString::fromStdString(announceEntry.url));
1614 Q_ASSERT(it != m_trackerEntries.end());
1615 if (it == m_trackerEntries.end()) [[unlikely]]
1616 return {};
1618 #ifdef QBT_USES_LIBTORRENT2
1619 ::updateTrackerEntry(*it, announceEntry, nativeHandle().info_hashes(), updateInfo);
1620 #else
1621 ::updateTrackerEntry(*it, announceEntry, updateInfo);
1622 #endif
1623 return *it;
1626 std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
1628 if (m_nativeStatus.torrent_file.expired())
1629 m_nativeStatus.torrent_file = m_nativeHandle.torrent_file();
1630 return m_nativeStatus.torrent_file.lock();
1633 void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
1635 Q_ASSERT(m_maintenanceJob == MaintenanceJob::HandleMetadata);
1636 if (m_maintenanceJob != MaintenanceJob::HandleMetadata) [[unlikely]]
1637 return;
1639 Q_ASSERT(m_filePaths.isEmpty());
1640 if (!m_filePaths.isEmpty()) [[unlikely]]
1641 m_filePaths.clear();
1643 lt::add_torrent_params &p = m_ltAddTorrentParams;
1645 const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1646 m_torrentInfo = TorrentInfo(*metadata);
1647 m_filePriorities.reserve(filesCount());
1648 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
1649 p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
1650 , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
1652 m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
1653 m_filesProgress.resize(filesCount());
1654 updateProgress();
1656 for (int i = 0; i < fileNames.size(); ++i)
1658 const auto nativeIndex = nativeIndexes.at(i);
1660 const Path &actualFilePath = fileNames.at(i);
1661 p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
1663 const Path filePath = actualFilePath.removedExtension(QB_EXT);
1664 m_filePaths.append(filePath);
1666 lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
1667 if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
1668 nativePriority = lt::dont_download;
1669 const auto priority = LT::fromNative(nativePriority);
1670 m_filePriorities.append(priority);
1672 p.save_path = savePath.toString().toStdString();
1673 p.ti = metadata;
1675 if (stopCondition() == StopCondition::MetadataReceived)
1677 m_stopCondition = StopCondition::None;
1679 m_isStopped = true;
1680 p.flags |= lt::torrent_flags::paused;
1681 p.flags &= ~lt::torrent_flags::auto_managed;
1683 m_session->handleTorrentPaused(this);
1686 reload();
1688 // If first/last piece priority was specified when adding this torrent,
1689 // we should apply it now that we have metadata:
1690 if (m_hasFirstLastPiecePriority)
1691 applyFirstLastPiecePriority(true);
1693 m_maintenanceJob = MaintenanceJob::None;
1694 prepareResumeData(p);
1696 m_session->handleTorrentMetadataReceived(this);
1699 void TorrentImpl::reload()
1702 m_completedFiles.fill(false);
1703 m_filesProgress.fill(0);
1704 m_pieces.fill(false);
1705 m_nativeStatus.pieces.clear_all();
1706 m_nativeStatus.num_pieces = 0;
1708 const auto queuePos = m_nativeHandle.queue_position();
1710 m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
1712 lt::add_torrent_params p = m_ltAddTorrentParams;
1713 p.flags |= lt::torrent_flags::update_subscribe
1714 | lt::torrent_flags::override_trackers
1715 | lt::torrent_flags::override_web_seeds;
1717 if (m_isStopped)
1719 p.flags |= lt::torrent_flags::paused;
1720 p.flags &= ~lt::torrent_flags::auto_managed;
1722 else if (m_operatingMode == TorrentOperatingMode::AutoManaged)
1724 p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1726 else
1728 p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1731 auto *const extensionData = new ExtensionData;
1732 p.userdata = LTClientData(extensionData);
1733 m_nativeHandle = m_nativeSession->add_torrent(p);
1735 m_nativeStatus = extensionData->status;
1737 if (queuePos >= lt::queue_position_t {})
1738 m_nativeHandle.queue_position_set(queuePos);
1739 m_nativeStatus.queue_position = queuePos;
1741 updateState();
1743 catch (const lt::system_error &err)
1745 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1746 .arg(id().toString(), QString::fromLocal8Bit(err.what())));
1749 void TorrentImpl::pause()
1751 if (!m_isStopped)
1753 m_stopCondition = StopCondition::None;
1754 m_isStopped = true;
1755 m_session->handleTorrentNeedSaveResumeData(this);
1756 m_session->handleTorrentPaused(this);
1759 if (m_maintenanceJob == MaintenanceJob::None)
1761 setAutoManaged(false);
1762 m_nativeHandle.pause();
1764 m_payloadRateMonitor.reset();
1768 void TorrentImpl::resume(const TorrentOperatingMode mode)
1770 if (hasError())
1772 m_nativeHandle.clear_error();
1773 m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode);
1776 m_operatingMode = mode;
1778 if (m_hasMissingFiles)
1780 m_hasMissingFiles = false;
1781 m_isStopped = false;
1782 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1783 reload();
1784 return;
1787 if (m_isStopped)
1789 m_isStopped = false;
1790 m_session->handleTorrentNeedSaveResumeData(this);
1791 m_session->handleTorrentResumed(this);
1794 if (m_maintenanceJob == MaintenanceJob::None)
1796 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1797 if (m_operatingMode == TorrentOperatingMode::Forced)
1798 m_nativeHandle.resume();
1802 void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
1804 if (!hasMetadata())
1806 m_session->handleTorrentSavePathChanged(this);
1807 return;
1810 const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
1811 ? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
1812 if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
1814 if (!m_storageIsMoving)
1816 m_storageIsMoving = true;
1817 updateState();
1818 m_session->handleTorrentStorageMovingStateChanged(this);
1823 void TorrentImpl::renameFile(const int index, const Path &path)
1825 Q_ASSERT((index >= 0) && (index < filesCount()));
1826 if ((index < 0) || (index >= filesCount())) [[unlikely]]
1827 return;
1829 const Path wantedPath = wantedActualPath(index, path);
1830 doRenameFile(index, wantedPath);
1833 void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
1835 updateStatus(nativeStatus);
1838 void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
1840 if (context == MoveStorageContext::ChangeSavePath)
1841 m_savePath = path;
1842 else if (context == MoveStorageContext::ChangeDownloadPath)
1843 m_downloadPath = path;
1844 m_storageIsMoving = hasOutstandingJob;
1845 m_nativeStatus.save_path = path.toString().toStdString();
1847 m_session->handleTorrentSavePathChanged(this);
1848 m_session->handleTorrentNeedSaveResumeData(this);
1850 if (!m_storageIsMoving)
1852 updateState();
1853 m_session->handleTorrentStorageMovingStateChanged(this);
1855 if (m_hasMissingFiles)
1857 // it can be moved to the proper location
1858 m_hasMissingFiles = false;
1859 m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
1860 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1861 reload();
1864 while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1865 std::invoke(m_moveFinishedTriggers.dequeue());
1869 void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
1871 Q_UNUSED(p);
1873 if (!hasMetadata())
1875 // The torrent is checked due to metadata received, but we should not process
1876 // this event until the torrent is reloaded using the received metadata.
1877 return;
1880 if (stopCondition() == StopCondition::FilesChecked)
1881 pause();
1883 m_statusUpdatedTriggers.enqueue([this]()
1885 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1887 if (!m_hasMissingFiles)
1889 if ((progress() < 1.0) && (wantedSize() > 0))
1890 m_hasFinishedStatus = false;
1891 else if (progress() == 1.0)
1892 m_hasFinishedStatus = true;
1894 adjustStorageLocation();
1895 manageIncompleteFiles();
1897 if (!isPaused())
1899 // torrent is internally paused using NativeTorrentExtension after files checked
1900 // so we need to resume it if there is no corresponding "stop condition" set
1901 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1902 if (m_operatingMode == TorrentOperatingMode::Forced)
1903 m_nativeHandle.resume();
1907 if (m_nativeStatus.need_save_resume)
1908 m_session->handleTorrentNeedSaveResumeData(this);
1910 m_session->handleTorrentChecked(this);
1914 void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p)
1916 Q_UNUSED(p);
1918 m_hasMissingFiles = false;
1919 if (m_hasFinishedStatus)
1920 return;
1922 m_statusUpdatedTriggers.enqueue([this]()
1924 adjustStorageLocation();
1925 manageIncompleteFiles();
1927 m_session->handleTorrentNeedSaveResumeData(this);
1929 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
1930 if (recheckTorrentsOnCompletion && m_unchecked)
1932 forceRecheck();
1934 else
1936 m_hasFinishedStatus = true;
1938 if (isMoveInProgress() || (m_renameCount > 0))
1939 m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
1940 else
1941 m_session->handleTorrentFinished(this);
1946 void TorrentImpl::handleTorrentPausedAlert(const lt::torrent_paused_alert *p)
1948 Q_UNUSED(p);
1951 void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
1953 Q_UNUSED(p);
1956 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
1958 if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
1960 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
1961 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
1962 // than the list we usually cache, so we have to request it from the appropriate source.
1963 fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
1966 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1968 Q_ASSERT(m_indexMap.isEmpty());
1970 const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
1971 m_ltAddTorrentParams = p->params;
1972 if (isSeedMode)
1973 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
1975 m_ltAddTorrentParams.have_pieces.clear();
1976 m_ltAddTorrentParams.verified_pieces.clear();
1978 TorrentInfo metadata = TorrentInfo(*nativeTorrentInfo());
1980 const auto &renamedFiles = m_ltAddTorrentParams.renamed_files;
1981 PathList filePaths = metadata.filePaths();
1982 if (renamedFiles.empty() && (m_contentLayout != TorrentContentLayout::Original))
1984 const Path originalRootFolder = Path::findRootFolder(filePaths);
1985 const auto originalContentLayout = (originalRootFolder.isEmpty()
1986 ? TorrentContentLayout::NoSubfolder
1987 : TorrentContentLayout::Subfolder);
1988 if (m_contentLayout != originalContentLayout)
1990 if (m_contentLayout == TorrentContentLayout::NoSubfolder)
1991 Path::stripRootFolder(filePaths);
1992 else
1993 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
1997 const auto nativeIndexes = metadata.nativeIndexes();
1998 m_indexMap.reserve(filePaths.size());
1999 for (int i = 0; i < filePaths.size(); ++i)
2001 const auto nativeIndex = nativeIndexes.at(i);
2002 m_indexMap[nativeIndex] = i;
2004 if (const auto it = renamedFiles.find(nativeIndex); it != renamedFiles.cend())
2005 filePaths[i] = Path(it->second);
2008 m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
2010 else
2012 prepareResumeData(p->params);
2016 void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
2018 if (m_hasMissingFiles)
2020 const auto havePieces = m_ltAddTorrentParams.have_pieces;
2021 const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
2022 const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
2024 // Update recent resume data but preserve existing progress
2025 m_ltAddTorrentParams = params;
2026 m_ltAddTorrentParams.have_pieces = havePieces;
2027 m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
2028 m_ltAddTorrentParams.verified_pieces = verifiedPieces;
2030 else
2032 const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
2033 // Update recent resume data
2034 m_ltAddTorrentParams = params;
2035 if (preserveSeedMode)
2036 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2039 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2040 m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
2042 LoadTorrentParams resumeData;
2043 resumeData.name = m_name;
2044 resumeData.category = m_category;
2045 resumeData.tags = m_tags;
2046 resumeData.contentLayout = m_contentLayout;
2047 resumeData.ratioLimit = m_ratioLimit;
2048 resumeData.seedingTimeLimit = m_seedingTimeLimit;
2049 resumeData.inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit;
2050 resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
2051 resumeData.hasFinishedStatus = m_hasFinishedStatus;
2052 resumeData.stopped = m_isStopped;
2053 resumeData.stopCondition = m_stopCondition;
2054 resumeData.operatingMode = m_operatingMode;
2055 resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
2056 resumeData.useAutoTMM = m_useAutoTMM;
2057 if (!resumeData.useAutoTMM)
2059 resumeData.savePath = m_savePath;
2060 resumeData.downloadPath = m_downloadPath;
2063 m_session->handleTorrentResumeDataReady(this, resumeData);
2066 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
2068 if (p->error != lt::errors::resume_data_not_modified)
2070 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2071 .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
2074 m_session->handleTorrentSaveResumeDataFailed(this);
2077 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
2079 // Files were probably moved or storage isn't accessible
2080 m_hasMissingFiles = true;
2081 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2082 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
2085 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
2087 const int fileIndex = m_indexMap.value(p->index, -1);
2088 Q_ASSERT(fileIndex >= 0);
2090 // Remove empty leftover folders
2091 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2092 // be removed if they are empty
2093 const Path oldFilePath = m_filePaths.at(fileIndex);
2094 const Path newFilePath = Path(QString::fromUtf8(p->new_name())).removedExtension(QB_EXT);
2096 // Check if ".!qB" extension was just added or removed
2097 // We should compare path in a case sensitive manner even on case insensitive
2098 // platforms since it can be renamed by only changing case of some character(s)
2099 if (oldFilePath.data() != newFilePath.data())
2101 m_filePaths[fileIndex] = newFilePath;
2103 Path oldParentPath = oldFilePath.parentPath();
2104 const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath());
2105 while (oldParentPath != commonBasePath)
2107 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath);
2108 oldParentPath = oldParentPath.parentPath();
2112 --m_renameCount;
2113 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2114 m_moveFinishedTriggers.takeFirst()();
2116 m_session->handleTorrentNeedSaveResumeData(this);
2119 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
2121 const int fileIndex = m_indexMap.value(p->index, -1);
2122 Q_ASSERT(fileIndex >= 0);
2124 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2125 .arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
2127 --m_renameCount;
2128 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2129 m_moveFinishedTriggers.takeFirst()();
2131 m_session->handleTorrentNeedSaveResumeData(this);
2134 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
2136 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2137 return;
2139 const int fileIndex = m_indexMap.value(p->index, -1);
2140 Q_ASSERT(fileIndex >= 0);
2142 m_completedFiles.setBit(fileIndex);
2144 if (m_session->isAppendExtensionEnabled())
2146 const Path path = filePath(fileIndex);
2147 const Path actualPath = actualFilePath(fileIndex);
2148 if (actualPath != path)
2150 qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
2151 doRenameFile(fileIndex, path);
2156 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
2158 m_lastFileError = {p->error, p->op};
2161 #ifdef QBT_USES_LIBTORRENT2
2162 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
2164 m_session->handleTorrentNeedSaveResumeData(this);
2166 #endif
2168 void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
2170 Q_UNUSED(p);
2171 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2173 #ifdef QBT_USES_LIBTORRENT2
2174 const InfoHash prevInfoHash = infoHash();
2175 m_infoHash = InfoHash(m_nativeHandle.info_hashes());
2176 if (prevInfoHash != infoHash())
2177 m_session->handleTorrentInfoHashChanged(this, prevInfoHash);
2178 #endif
2180 m_maintenanceJob = MaintenanceJob::HandleMetadata;
2181 m_session->handleTorrentNeedSaveResumeData(this);
2184 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
2186 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))
2187 , Log::INFO);
2190 void TorrentImpl::handleCategoryOptionsChanged()
2192 if (m_useAutoTMM)
2193 adjustStorageLocation();
2196 void TorrentImpl::handleAppendExtensionToggled()
2198 if (!hasMetadata()) return;
2200 manageIncompleteFiles();
2203 void TorrentImpl::handleAlert(const lt::alert *a)
2205 switch (a->type())
2207 #ifdef QBT_USES_LIBTORRENT2
2208 case lt::file_prio_alert::alert_type:
2209 handleFilePrioAlert(static_cast<const lt::file_prio_alert*>(a));
2210 break;
2211 #endif
2212 case lt::file_renamed_alert::alert_type:
2213 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
2214 break;
2215 case lt::file_rename_failed_alert::alert_type:
2216 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
2217 break;
2218 case lt::file_completed_alert::alert_type:
2219 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
2220 break;
2221 case lt::file_error_alert::alert_type:
2222 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
2223 break;
2224 case lt::torrent_finished_alert::alert_type:
2225 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
2226 break;
2227 case lt::save_resume_data_alert::alert_type:
2228 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
2229 break;
2230 case lt::save_resume_data_failed_alert::alert_type:
2231 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
2232 break;
2233 case lt::torrent_paused_alert::alert_type:
2234 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
2235 break;
2236 case lt::torrent_resumed_alert::alert_type:
2237 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
2238 break;
2239 case lt::metadata_received_alert::alert_type:
2240 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2241 break;
2242 case lt::fastresume_rejected_alert::alert_type:
2243 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2244 break;
2245 case lt::torrent_checked_alert::alert_type:
2246 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2247 break;
2248 case lt::performance_alert::alert_type:
2249 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2250 break;
2254 void TorrentImpl::manageIncompleteFiles()
2256 const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
2257 const lt::file_storage &nativeFiles = nativeInfo->files();
2259 for (int i = 0; i < filesCount(); ++i)
2261 const Path path = filePath(i);
2263 const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
2264 const Path actualPath {nativeFiles.file_path(nativeIndex)};
2265 const Path wantedPath = wantedActualPath(i, path);
2266 if (actualPath != wantedPath)
2268 qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
2269 doRenameFile(i, wantedPath);
2274 void TorrentImpl::adjustStorageLocation()
2276 const Path downloadPath = this->downloadPath();
2277 const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
2279 if ((targetPath != actualStorageLocation()) || isMoveInProgress())
2280 moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
2283 void TorrentImpl::doRenameFile(int index, const Path &path)
2285 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
2287 Q_ASSERT(index >= 0);
2288 Q_ASSERT(index < nativeIndexes.size());
2289 if ((index < 0) || (index >= nativeIndexes.size())) [[unlikely]]
2290 return;
2292 ++m_renameCount;
2293 m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
2296 lt::torrent_handle TorrentImpl::nativeHandle() const
2298 return m_nativeHandle;
2301 void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
2303 if (hasMetadata())
2304 return;
2306 m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
2310 #ifdef QBT_USES_LIBTORRENT2
2311 nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
2312 #else
2313 const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
2314 nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
2315 #endif
2317 catch (const std::exception &) {}
2321 Torrent::StopCondition TorrentImpl::stopCondition() const
2323 return m_stopCondition;
2326 void TorrentImpl::setStopCondition(const StopCondition stopCondition)
2328 if (stopCondition == m_stopCondition)
2329 return;
2331 if (isPaused())
2332 return;
2334 if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata())
2335 return;
2337 if ((stopCondition == StopCondition::FilesChecked) && hasMetadata() && !isChecking())
2338 return;
2340 m_stopCondition = stopCondition;
2343 bool TorrentImpl::isMoveInProgress() const
2345 return m_storageIsMoving;
2348 void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
2350 const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
2352 if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
2353 updateProgress();
2355 updateState();
2357 m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
2358 , nativeStatus.upload_payload_rate});
2360 if (hasMetadata())
2362 // NOTE: Don't change the order of these conditionals!
2363 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2364 if (isChecking())
2365 m_unchecked = false;
2366 else if (isDownloading())
2367 m_unchecked = true;
2370 while (!m_statusUpdatedTriggers.isEmpty())
2371 std::invoke(m_statusUpdatedTriggers.dequeue());
2374 void TorrentImpl::updateProgress()
2376 Q_ASSERT(hasMetadata());
2377 if (!hasMetadata()) [[unlikely]]
2378 return;
2380 Q_ASSERT(!m_filesProgress.isEmpty());
2381 if (m_filesProgress.isEmpty()) [[unlikely]]
2382 m_filesProgress.resize(filesCount());
2384 const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
2385 const QBitArray newPieces = m_pieces ^ oldPieces;
2387 const int64_t pieceSize = m_torrentInfo.pieceLength();
2388 for (qsizetype index = 0; index < newPieces.size(); ++index)
2390 if (!newPieces.at(index))
2391 continue;
2393 int64_t size = m_torrentInfo.pieceLength(index);
2394 int64_t pieceOffset = index * pieceSize;
2396 for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
2398 const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
2399 const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
2401 m_filesProgress[fileIndex] += add;
2403 size -= add;
2404 if (size <= 0)
2405 break;
2407 pieceOffset += add;
2412 void TorrentImpl::setRatioLimit(qreal limit)
2414 if (limit < USE_GLOBAL_RATIO)
2415 limit = NO_RATIO_LIMIT;
2416 else if (limit > MAX_RATIO)
2417 limit = MAX_RATIO;
2419 if (m_ratioLimit != limit)
2421 m_ratioLimit = limit;
2422 m_session->handleTorrentNeedSaveResumeData(this);
2423 m_session->handleTorrentShareLimitChanged(this);
2427 void TorrentImpl::setSeedingTimeLimit(int limit)
2429 if (limit < USE_GLOBAL_SEEDING_TIME)
2430 limit = NO_SEEDING_TIME_LIMIT;
2431 else if (limit > MAX_SEEDING_TIME)
2432 limit = MAX_SEEDING_TIME;
2434 if (m_seedingTimeLimit != limit)
2436 m_seedingTimeLimit = limit;
2437 m_session->handleTorrentNeedSaveResumeData(this);
2438 m_session->handleTorrentShareLimitChanged(this);
2442 void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
2444 if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
2445 limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
2446 else if (limit > MAX_INACTIVE_SEEDING_TIME)
2447 limit = MAX_SEEDING_TIME;
2449 if (m_inactiveSeedingTimeLimit != limit)
2451 m_inactiveSeedingTimeLimit = limit;
2452 m_session->handleTorrentNeedSaveResumeData(this);
2453 m_session->handleTorrentShareLimitChanged(this);
2457 void TorrentImpl::setUploadLimit(const int limit)
2459 const int cleanValue = cleanLimitValue(limit);
2460 if (cleanValue == uploadLimit())
2461 return;
2463 m_uploadLimit = cleanValue;
2464 m_nativeHandle.set_upload_limit(m_uploadLimit);
2465 m_session->handleTorrentNeedSaveResumeData(this);
2468 void TorrentImpl::setDownloadLimit(const int limit)
2470 const int cleanValue = cleanLimitValue(limit);
2471 if (cleanValue == downloadLimit())
2472 return;
2474 m_downloadLimit = cleanValue;
2475 m_nativeHandle.set_download_limit(m_downloadLimit);
2476 m_session->handleTorrentNeedSaveResumeData(this);
2479 void TorrentImpl::setSuperSeeding(const bool enable)
2481 if (enable == superSeeding())
2482 return;
2484 if (enable)
2485 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2486 else
2487 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2489 m_session->handleTorrentNeedSaveResumeData(this);
2492 void TorrentImpl::setDHTDisabled(const bool disable)
2494 if (disable == isDHTDisabled())
2495 return;
2497 if (disable)
2498 m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
2499 else
2500 m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
2502 m_session->handleTorrentNeedSaveResumeData(this);
2505 void TorrentImpl::setPEXDisabled(const bool disable)
2507 if (disable == isPEXDisabled())
2508 return;
2510 if (disable)
2511 m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
2512 else
2513 m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
2515 m_session->handleTorrentNeedSaveResumeData(this);
2518 void TorrentImpl::setLSDDisabled(const bool disable)
2520 if (disable == isLSDDisabled())
2521 return;
2523 if (disable)
2524 m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
2525 else
2526 m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
2528 m_session->handleTorrentNeedSaveResumeData(this);
2531 void TorrentImpl::flushCache() const
2533 m_nativeHandle.flush_cache();
2536 QString TorrentImpl::createMagnetURI() const
2538 QString ret = u"magnet:?"_s;
2540 const SHA1Hash infoHash1 = infoHash().v1();
2541 if (infoHash1.isValid())
2543 ret += u"xt=urn:btih:" + infoHash1.toString();
2546 const SHA256Hash infoHash2 = infoHash().v2();
2547 if (infoHash2.isValid())
2549 if (infoHash1.isValid())
2550 ret += u'&';
2551 ret += u"xt=urn:btmh:1220" + infoHash2.toString();
2554 const QString displayName = name();
2555 if (displayName != id().toString())
2557 ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
2560 for (const TrackerEntry &tracker : asConst(trackers()))
2562 ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
2565 for (const QUrl &urlSeed : asConst(urlSeeds()))
2567 ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
2570 return ret;
2573 nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
2575 if (!hasMetadata())
2576 return nonstd::make_unexpected(tr("Missing metadata"));
2580 #ifdef QBT_USES_LIBTORRENT2
2581 const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
2582 const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
2583 #else
2584 const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
2585 #endif
2586 lt::create_torrent creator {*torrentInfo};
2588 for (const TrackerEntry &entry : asConst(trackers()))
2589 creator.add_tracker(entry.url.toStdString(), entry.tier);
2591 return creator.generate();
2593 catch (const lt::system_error &err)
2595 return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
2599 nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
2601 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2602 if (!preparationResult)
2603 return preparationResult.get_unexpected();
2605 // usually torrent size should be smaller than 1 MB,
2606 // however there are >100 MB v2/hybrid torrent files out in the wild
2607 QByteArray buffer;
2608 buffer.reserve(1024 * 1024);
2609 lt::bencode(std::back_inserter(buffer), preparationResult.value());
2610 return buffer;
2613 nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) const
2615 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2616 if (!preparationResult)
2617 return preparationResult.get_unexpected();
2619 const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
2620 if (!saveResult)
2621 return saveResult.get_unexpected();
2623 return {};
2626 void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
2628 invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
2632 std::vector<lt::peer_info> nativePeers;
2633 nativeHandle.get_peer_info(nativePeers);
2634 QVector<PeerInfo> peers;
2635 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
2636 for (const lt::peer_info &peer : nativePeers)
2637 peers.append(PeerInfo(peer, allPieces));
2638 return peers;
2640 catch (const std::exception &) {}
2642 return {};
2644 , std::move(resultHandler));
2647 void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
2649 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
2653 const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
2654 QVector<QUrl> urlSeeds;
2655 urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
2656 for (const std::string &urlSeed : currentSeeds)
2657 urlSeeds.append(QString::fromStdString(urlSeed));
2658 return urlSeeds;
2660 catch (const std::exception &) {}
2662 return {};
2664 , std::move(resultHandler));
2667 void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
2669 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
2673 std::vector<int> piecesAvailability;
2674 nativeHandle.piece_availability(piecesAvailability);
2675 return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
2677 catch (const std::exception &) {}
2679 return {};
2681 , std::move(resultHandler));
2684 void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
2686 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
2690 #ifdef QBT_USES_LIBTORRENT2
2691 const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
2692 #else
2693 std::vector<lt::partial_piece_info> queue;
2694 nativeHandle.get_download_queue(queue);
2695 #endif
2696 QBitArray result;
2697 result.resize(torrentInfo.piecesCount());
2698 for (const lt::partial_piece_info &info : queue)
2699 result.setBit(LT::toUnderlyingType(info.piece_index));
2700 return result;
2702 catch (const std::exception &) {}
2704 return {};
2706 , std::move(resultHandler));
2709 void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
2711 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
2713 if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
2714 return {};
2718 std::vector<int> piecesAvailability;
2719 nativeHandle.piece_availability(piecesAvailability);
2720 const int filesCount = torrentInfo.filesCount();
2721 // libtorrent returns empty array for seeding only torrents
2722 if (piecesAvailability.empty())
2723 return QVector<qreal>(filesCount, -1);
2725 QVector<qreal> result;
2726 result.reserve(filesCount);
2727 for (int i = 0; i < filesCount; ++i)
2729 const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
2731 int availablePieces = 0;
2732 for (const int piece : filePieces)
2733 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2735 const qreal availability = filePieces.isEmpty()
2736 ? 1 // the file has no pieces, so it is available by default
2737 : static_cast<qreal>(availablePieces) / filePieces.size();
2738 result.append(availability);
2740 return result;
2742 catch (const std::exception &) {}
2744 return {};
2746 , std::move(resultHandler));
2749 void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
2751 if (!hasMetadata()) return;
2753 Q_ASSERT(priorities.size() == filesCount());
2755 // Reset 'm_hasSeedStatus' if needed in order to react again to
2756 // 'torrent_finished_alert' and eg show tray notifications
2757 const QVector<DownloadPriority> oldPriorities = filePriorities();
2758 for (int i = 0; i < oldPriorities.size(); ++i)
2760 if ((oldPriorities[i] == DownloadPriority::Ignored)
2761 && (priorities[i] > DownloadPriority::Ignored)
2762 && !m_completedFiles.at(i))
2764 m_hasFinishedStatus = false;
2765 break;
2769 const int internalFilesCount = m_torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2770 auto nativePriorities = std::vector<lt::download_priority_t>(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2771 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
2772 for (int i = 0; i < priorities.size(); ++i)
2773 nativePriorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(priorities[i]);
2775 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
2776 m_nativeHandle.prioritize_files(nativePriorities);
2778 m_filePriorities = priorities;
2779 // Restore first/last piece first option if necessary
2780 if (m_hasFirstLastPiecePriority)
2781 applyFirstLastPiecePriority(true);
2784 QVector<qreal> TorrentImpl::availableFileFractions() const
2786 Q_ASSERT(hasMetadata());
2788 const int filesCount = this->filesCount();
2789 if (filesCount <= 0) return {};
2791 const QVector<int> piecesAvailability = pieceAvailability();
2792 // libtorrent returns empty array for seeding only torrents
2793 if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1);
2795 QVector<qreal> res;
2796 res.reserve(filesCount);
2797 for (int i = 0; i < filesCount; ++i)
2799 const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
2801 int availablePieces = 0;
2802 for (const int piece : filePieces)
2803 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2805 const qreal availability = filePieces.isEmpty()
2806 ? 1 // the file has no pieces, so it is available by default
2807 : static_cast<qreal>(availablePieces) / filePieces.size();
2808 res.push_back(availability);
2810 return res;
2813 template <typename Func, typename Callback>
2814 void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
2816 m_session->invokeAsync([session = m_session
2817 , func = std::move(func)
2818 , resultHandler = std::move(resultHandler)
2819 , thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
2821 session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
2823 if (thisTorrent)
2824 resultHandler(result);