Correctly handle changing save path of torrent w/o metadata
[qBittorrent.git] / src / base / bittorrent / torrentimpl.cpp
blobe954185ae4ced34047a073f693b67c99f13b38bd
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 <QtSystemDetection>
47 #include <QByteArray>
48 #include <QDebug>
49 #include <QPointer>
50 #include <QSet>
51 #include <QStringList>
52 #include <QUrl>
54 #include "base/exceptions.h"
55 #include "base/global.h"
56 #include "base/logger.h"
57 #include "base/preferences.h"
58 #include "base/types.h"
59 #include "base/utils/fs.h"
60 #include "base/utils/io.h"
61 #include "common.h"
62 #include "downloadpriority.h"
63 #include "extensiondata.h"
64 #include "loadtorrentparams.h"
65 #include "ltqbitarray.h"
66 #include "lttypecast.h"
67 #include "peeraddress.h"
68 #include "peerinfo.h"
69 #include "sessionimpl.h"
71 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
72 #include "base/utils/misc.h"
73 #endif // Q_OS_MACOS || Q_OS_WIN
75 using namespace BitTorrent;
77 namespace
79 lt::announce_entry makeNativeAnnounceEntry(const QString &url, const int tier)
81 lt::announce_entry entry {url.toStdString()};
82 entry.tier = tier;
83 return entry;
86 QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint)
88 const auto ltNow = lt::clock_type::now();
89 const auto qNow = QDateTime::currentDateTime();
90 const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - ltNow + lt::milliseconds(500)).count();
92 return qNow.addSecs(secsSinceNow);
95 QString toString(const lt::tcp::endpoint &ltTCPEndpoint)
97 return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str());
100 void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
101 , const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
103 Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
105 trackerEntry.tier = nativeEntry.tier;
107 // remove outdated endpoints
108 trackerEntry.endpointEntries.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointEntry>::iterator &iter)
110 return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
111 , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
113 return (endpointName == toString(existingEndpoint.local_endpoint));
117 const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * btProtocols.size();
119 int numUpdating = 0;
120 int numWorking = 0;
121 int numNotWorking = 0;
122 int numTrackerError = 0;
123 int numUnreachable = 0;
125 for (const lt::announce_endpoint &ltAnnounceEndpoint : nativeEntry.endpoints)
127 const auto endpointName = toString(ltAnnounceEndpoint.local_endpoint);
129 for (const auto protocolVersion : btProtocols)
131 #ifdef QBT_USES_LIBTORRENT2
132 Q_ASSERT((protocolVersion == 1) || (protocolVersion == 2));
133 const auto ltProtocolVersion = (protocolVersion == 1) ? lt::protocol_version::V1 : lt::protocol_version::V2;
134 const lt::announce_infohash &ltAnnounceInfo = ltAnnounceEndpoint.info_hashes[ltProtocolVersion];
135 #else
136 Q_ASSERT(protocolVersion == 1);
137 const lt::announce_endpoint &ltAnnounceInfo = ltAnnounceEndpoint;
138 #endif
139 const QMap<int, int> &endpointUpdateInfo = updateInfo[ltAnnounceEndpoint.local_endpoint];
140 TrackerEndpointEntry &trackerEndpointEntry = trackerEntry.endpointEntries[std::make_pair(endpointName, protocolVersion)];
142 trackerEndpointEntry.name = endpointName;
143 trackerEndpointEntry.btVersion = protocolVersion;
144 trackerEndpointEntry.numPeers = endpointUpdateInfo.value(protocolVersion, trackerEndpointEntry.numPeers);
145 trackerEndpointEntry.numSeeds = ltAnnounceInfo.scrape_complete;
146 trackerEndpointEntry.numLeeches = ltAnnounceInfo.scrape_incomplete;
147 trackerEndpointEntry.numDownloaded = ltAnnounceInfo.scrape_downloaded;
148 trackerEndpointEntry.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
149 trackerEndpointEntry.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
151 if (ltAnnounceInfo.updating)
153 trackerEndpointEntry.status = TrackerEntryStatus::Updating;
154 ++numUpdating;
156 else if (ltAnnounceInfo.fails > 0)
158 if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
160 trackerEndpointEntry.status = TrackerEntryStatus::TrackerError;
161 ++numTrackerError;
163 else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
165 trackerEndpointEntry.status = TrackerEntryStatus::Unreachable;
166 ++numUnreachable;
168 else
170 trackerEndpointEntry.status = TrackerEntryStatus::NotWorking;
171 ++numNotWorking;
174 else if (nativeEntry.verified)
176 trackerEndpointEntry.status = TrackerEntryStatus::Working;
177 ++numWorking;
179 else
181 trackerEndpointEntry.status = TrackerEntryStatus::NotContacted;
184 if (!ltAnnounceInfo.message.empty())
186 trackerEndpointEntry.message = QString::fromStdString(ltAnnounceInfo.message);
188 else if (ltAnnounceInfo.last_error)
190 trackerEndpointEntry.message = QString::fromLocal8Bit(ltAnnounceInfo.last_error.message());
192 else
194 trackerEndpointEntry.message.clear();
199 if (numEndpoints > 0)
201 if (numUpdating > 0)
203 trackerEntry.status = TrackerEntryStatus::Updating;
205 else if (numWorking > 0)
207 trackerEntry.status = TrackerEntryStatus::Working;
209 else if (numTrackerError > 0)
211 trackerEntry.status = TrackerEntryStatus::TrackerError;
213 else if (numUnreachable == numEndpoints)
215 trackerEntry.status = TrackerEntryStatus::Unreachable;
217 else if ((numUnreachable + numNotWorking) == numEndpoints)
219 trackerEntry.status = TrackerEntryStatus::NotWorking;
223 trackerEntry.numPeers = -1;
224 trackerEntry.numSeeds = -1;
225 trackerEntry.numLeeches = -1;
226 trackerEntry.numDownloaded = -1;
227 trackerEntry.nextAnnounceTime = QDateTime();
228 trackerEntry.minAnnounceTime = QDateTime();
229 trackerEntry.message.clear();
231 for (const TrackerEndpointEntry &endpointEntry : asConst(trackerEntry.endpointEntries))
233 trackerEntry.numPeers = std::max(trackerEntry.numPeers, endpointEntry.numPeers);
234 trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, endpointEntry.numSeeds);
235 trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, endpointEntry.numLeeches);
236 trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, endpointEntry.numDownloaded);
238 if (endpointEntry.status == trackerEntry.status)
240 if (!trackerEntry.nextAnnounceTime.isValid() || (trackerEntry.nextAnnounceTime > endpointEntry.nextAnnounceTime))
242 trackerEntry.nextAnnounceTime = endpointEntry.nextAnnounceTime;
243 trackerEntry.minAnnounceTime = endpointEntry.minAnnounceTime;
244 if ((endpointEntry.status != TrackerEntryStatus::Working)
245 || !endpointEntry.message.isEmpty())
247 trackerEntry.message = endpointEntry.message;
251 if (endpointEntry.status == TrackerEntryStatus::Working)
253 if (trackerEntry.message.isEmpty())
254 trackerEntry.message = endpointEntry.message;
260 template <typename Vector>
261 Vector resized(const Vector &inVector, const typename Vector::size_type size, const typename Vector::value_type &defaultValue)
263 Vector outVector = inVector;
264 outVector.resize(size, defaultValue);
265 return outVector;
268 // This is an imitation of limit normalization performed by libtorrent itself.
269 // We need perform it to keep cached values in line with the ones used by libtorrent.
270 int cleanLimitValue(const int value)
272 return ((value < 0) || (value == std::numeric_limits<int>::max())) ? 0 : value;
276 // TorrentImpl
278 TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
279 , const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
280 : Torrent(session)
281 , m_session(session)
282 , m_nativeSession(nativeSession)
283 , m_nativeHandle(nativeHandle)
284 #ifdef QBT_USES_LIBTORRENT2
285 , m_infoHash(m_nativeHandle.info_hashes())
286 #else
287 , m_infoHash(m_nativeHandle.info_hash())
288 #endif
289 , m_name(params.name)
290 , m_savePath(params.savePath)
291 , m_downloadPath(params.downloadPath)
292 , m_category(params.category)
293 , m_tags(params.tags)
294 , m_ratioLimit(params.ratioLimit)
295 , m_seedingTimeLimit(params.seedingTimeLimit)
296 , m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit)
297 , m_operatingMode(params.operatingMode)
298 , m_contentLayout(params.contentLayout)
299 , m_hasFinishedStatus(params.hasFinishedStatus)
300 , m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
301 , m_useAutoTMM(params.useAutoTMM)
302 , m_isStopped(params.stopped)
303 , m_ltAddTorrentParams(params.ltAddTorrentParams)
304 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit))
305 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit))
307 if (m_ltAddTorrentParams.ti)
309 // Initialize it only if torrent is added with metadata.
310 // Otherwise it should be initialized in "Metadata received" handler.
311 m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
313 Q_ASSERT(m_filePaths.isEmpty());
314 Q_ASSERT(m_indexMap.isEmpty());
315 const int filesCount = m_torrentInfo.filesCount();
316 m_filePaths.reserve(filesCount);
317 m_indexMap.reserve(filesCount);
318 m_filePriorities.reserve(filesCount);
319 const std::vector<lt::download_priority_t> filePriorities =
320 resized(m_ltAddTorrentParams.file_priorities, m_ltAddTorrentParams.ti->num_files()
321 , LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
323 m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
324 m_filesProgress.resize(filesCount);
326 for (int i = 0; i < filesCount; ++i)
328 const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
329 m_indexMap[nativeIndex] = i;
331 const auto fileIter = m_ltAddTorrentParams.renamed_files.find(nativeIndex);
332 const Path filePath = ((fileIter != m_ltAddTorrentParams.renamed_files.end())
333 ? Path(fileIter->second).removedExtension(QB_EXT) : m_torrentInfo.filePath(i));
334 m_filePaths.append(filePath);
336 const auto priority = LT::fromNative(filePriorities[LT::toUnderlyingType(nativeIndex)]);
337 m_filePriorities.append(priority);
341 setStopCondition(params.stopCondition);
343 const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
344 m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
345 for (const lt::announce_entry &announceEntry : extensionData->trackers)
346 m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
347 m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
348 for (const std::string &urlSeed : extensionData->urlSeeds)
349 m_urlSeeds.append(QString::fromStdString(urlSeed));
350 m_nativeStatus = extensionData->status;
352 if (hasMetadata())
353 updateProgress();
355 updateState();
357 if (hasMetadata())
358 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
360 // TODO: Remove the following upgrade code in v4.4
361 // == BEGIN UPGRADE CODE ==
362 const Path spath = actualStorageLocation();
363 for (int i = 0; i < filesCount(); ++i)
365 const Path filepath = filePath(i);
366 // Move "unwanted" files back to their original folder
367 const Path parentRelPath = filepath.parentPath();
368 if (parentRelPath.filename() == u".unwanted")
370 const QString oldName = filepath.filename();
371 const Path newRelPath = parentRelPath.parentPath();
372 renameFile(i, (newRelPath / Path(oldName)));
374 // Remove .unwanted directory if empty
375 const Path newPath = spath / newRelPath;
376 qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath / Path(u".unwanted"_s)).toString();
377 Utils::Fs::rmdir(newPath / Path(u".unwanted"_s));
380 // == END UPGRADE CODE ==
383 TorrentImpl::~TorrentImpl() = default;
385 bool TorrentImpl::isValid() const
387 return m_nativeHandle.is_valid();
390 Session *TorrentImpl::session() const
392 return m_session;
395 InfoHash TorrentImpl::infoHash() const
397 return m_infoHash;
400 QString TorrentImpl::name() const
402 if (!m_name.isEmpty())
403 return m_name;
405 if (hasMetadata())
406 return m_torrentInfo.name();
408 const QString name = QString::fromStdString(m_nativeStatus.name);
409 if (!name.isEmpty())
410 return name;
412 return id().toString();
415 QDateTime TorrentImpl::creationDate() const
417 return m_torrentInfo.creationDate();
420 QString TorrentImpl::creator() const
422 return m_torrentInfo.creator();
425 QString TorrentImpl::comment() const
427 return m_torrentInfo.comment();
430 bool TorrentImpl::isPrivate() const
432 return m_torrentInfo.isPrivate();
435 qlonglong TorrentImpl::totalSize() const
437 return m_torrentInfo.totalSize();
440 // size without the "don't download" files
441 qlonglong TorrentImpl::wantedSize() const
443 return m_nativeStatus.total_wanted;
446 qlonglong TorrentImpl::completedSize() const
448 return m_nativeStatus.total_wanted_done;
451 qlonglong TorrentImpl::pieceLength() const
453 return m_torrentInfo.pieceLength();
456 qlonglong TorrentImpl::wastedSize() const
458 return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes);
461 QString TorrentImpl::currentTracker() const
463 return QString::fromStdString(m_nativeStatus.current_tracker);
466 Path TorrentImpl::savePath() const
468 return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath;
471 void TorrentImpl::setSavePath(const Path &path)
473 Q_ASSERT(!isAutoTMMEnabled());
475 const Path basePath = m_session->useCategoryPathsInManualMode()
476 ? m_session->categorySavePath(category()) : m_session->savePath();
477 const Path resolvedPath = (path.isAbsolute() ? path : (basePath / path));
478 if (resolvedPath == savePath())
479 return;
481 if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
483 moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
485 else
487 m_savePath = resolvedPath;
488 m_session->handleTorrentSavePathChanged(this);
489 m_session->handleTorrentNeedSaveResumeData(this);
493 Path TorrentImpl::downloadPath() const
495 return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath;
498 void TorrentImpl::setDownloadPath(const Path &path)
500 Q_ASSERT(!isAutoTMMEnabled());
502 const Path basePath = m_session->useCategoryPathsInManualMode()
503 ? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
504 const Path resolvedPath = (path.isEmpty() || path.isAbsolute()) ? path : (basePath / path);
505 if (resolvedPath == m_downloadPath)
506 return;
508 const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
509 if (isIncomplete)
511 moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
513 else
515 m_downloadPath = resolvedPath;
516 m_session->handleTorrentSavePathChanged(this);
517 m_session->handleTorrentNeedSaveResumeData(this);
521 Path TorrentImpl::rootPath() const
523 if (!hasMetadata())
524 return {};
526 const Path relativeRootPath = Path::findRootFolder(filePaths());
527 if (relativeRootPath.isEmpty())
528 return {};
530 return (actualStorageLocation() / relativeRootPath);
533 Path TorrentImpl::contentPath() const
535 if (!hasMetadata())
536 return {};
538 if (filesCount() == 1)
539 return (actualStorageLocation() / filePath(0));
541 const Path rootPath = this->rootPath();
542 return (rootPath.isEmpty() ? actualStorageLocation() : rootPath);
545 bool TorrentImpl::isAutoTMMEnabled() const
547 return m_useAutoTMM;
550 void TorrentImpl::setAutoTMMEnabled(bool enabled)
552 if (m_useAutoTMM == enabled)
553 return;
555 m_useAutoTMM = enabled;
556 if (!m_useAutoTMM)
558 m_savePath = m_session->categorySavePath(category());
559 m_downloadPath = m_session->categoryDownloadPath(category());
562 m_session->handleTorrentNeedSaveResumeData(this);
563 m_session->handleTorrentSavingModeChanged(this);
565 adjustStorageLocation();
568 Path TorrentImpl::actualStorageLocation() const
570 if (!hasMetadata())
571 return {};
573 return Path(m_nativeStatus.save_path);
576 void TorrentImpl::setAutoManaged(const bool enable)
578 if (enable)
579 m_nativeHandle.set_flags(lt::torrent_flags::auto_managed);
580 else
581 m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
584 Path TorrentImpl::wantedActualPath(int index, const Path &path) const
586 if (m_session->isAppendExtensionEnabled()
587 && (fileSize(index) > 0) && !m_completedFiles.at(index))
589 return path + QB_EXT;
592 return path;
595 QVector<TrackerEntry> TorrentImpl::trackers() const
597 return m_trackerEntries;
600 void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
602 trackers.removeIf([](const TrackerEntry &entry) { return entry.url.isEmpty(); });
604 const auto newTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend())
605 - QSet<TrackerEntry>(m_trackerEntries.cbegin(), m_trackerEntries.cend());
606 if (newTrackers.isEmpty())
607 return;
609 trackers = QVector<TrackerEntry>(newTrackers.cbegin(), newTrackers.cend());
610 for (const TrackerEntry &tracker : trackers)
611 m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
613 m_trackerEntries.append(trackers);
614 std::sort(m_trackerEntries.begin(), m_trackerEntries.end()
615 , [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
617 m_session->handleTorrentNeedSaveResumeData(this);
618 m_session->handleTorrentTrackersAdded(this, trackers);
621 void TorrentImpl::removeTrackers(const QStringList &trackers)
623 QStringList removedTrackers = trackers;
624 for (const QString &tracker : trackers)
626 if (!m_trackerEntries.removeOne({tracker}))
627 removedTrackers.removeOne(tracker);
630 std::vector<lt::announce_entry> nativeTrackers;
631 nativeTrackers.reserve(m_trackerEntries.size());
632 for (const TrackerEntry &tracker : asConst(m_trackerEntries))
633 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
635 if (!removedTrackers.isEmpty())
637 m_nativeHandle.replace_trackers(nativeTrackers);
639 m_session->handleTorrentNeedSaveResumeData(this);
640 m_session->handleTorrentTrackersRemoved(this, removedTrackers);
644 void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
646 trackers.removeIf([](const TrackerEntry &entry) { return entry.url.isEmpty(); });
647 // Filter out duplicate trackers
648 const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
649 trackers = QVector<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
650 std::sort(trackers.begin(), trackers.end()
651 , [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
653 std::vector<lt::announce_entry> nativeTrackers;
654 nativeTrackers.reserve(trackers.size());
655 for (const TrackerEntry &tracker : trackers)
656 nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
658 m_nativeHandle.replace_trackers(nativeTrackers);
659 m_trackerEntries = trackers;
661 // Clear the peer list if it's a private torrent since
662 // we do not want to keep connecting with peers from old tracker.
663 if (isPrivate())
664 clearPeers();
666 m_session->handleTorrentNeedSaveResumeData(this);
667 m_session->handleTorrentTrackersChanged(this);
670 QVector<QUrl> TorrentImpl::urlSeeds() const
672 return m_urlSeeds;
675 void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
677 m_session->invokeAsync([urlSeeds, session = m_session
678 , nativeHandle = m_nativeHandle
679 , thisTorrent = QPointer<TorrentImpl>(this)]
683 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
684 QVector<QUrl> currentSeeds;
685 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
686 for (const std::string &urlSeed : nativeSeeds)
687 currentSeeds.append(QString::fromStdString(urlSeed));
689 QVector<QUrl> addedUrlSeeds;
690 addedUrlSeeds.reserve(urlSeeds.size());
692 for (const QUrl &url : urlSeeds)
694 if (!currentSeeds.contains(url))
696 nativeHandle.add_url_seed(url.toString().toStdString());
697 addedUrlSeeds.append(url);
701 currentSeeds.append(addedUrlSeeds);
702 session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
704 if (!thisTorrent)
705 return;
707 thisTorrent->m_urlSeeds = currentSeeds;
708 if (!addedUrlSeeds.isEmpty())
710 session->handleTorrentNeedSaveResumeData(thisTorrent);
711 session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
715 catch (const std::exception &) {}
719 void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
721 m_session->invokeAsync([urlSeeds, session = m_session
722 , nativeHandle = m_nativeHandle
723 , thisTorrent = QPointer<TorrentImpl>(this)]
727 const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
728 QVector<QUrl> currentSeeds;
729 currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
730 for (const std::string &urlSeed : nativeSeeds)
731 currentSeeds.append(QString::fromStdString(urlSeed));
733 QVector<QUrl> removedUrlSeeds;
734 removedUrlSeeds.reserve(urlSeeds.size());
736 for (const QUrl &url : urlSeeds)
738 if (currentSeeds.removeOne(url))
740 nativeHandle.remove_url_seed(url.toString().toStdString());
741 removedUrlSeeds.append(url);
745 session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
747 if (!thisTorrent)
748 return;
750 thisTorrent->m_urlSeeds = currentSeeds;
752 if (!removedUrlSeeds.isEmpty())
754 session->handleTorrentNeedSaveResumeData(thisTorrent);
755 session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
759 catch (const std::exception &) {}
763 void TorrentImpl::clearPeers()
765 m_nativeHandle.clear_peers();
768 bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
770 lt::error_code ec;
771 const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec);
772 if (ec) return false;
774 const lt::tcp::endpoint endpoint(addr, peerAddress.port);
777 m_nativeHandle.connect_peer(endpoint);
779 catch (const lt::system_error &err)
781 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
782 .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
783 return false;
786 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name()));
787 return true;
790 bool TorrentImpl::needSaveResumeData() const
792 return m_nativeStatus.need_save_resume;
795 void TorrentImpl::saveResumeData(lt::resume_data_flags_t flags)
797 m_nativeHandle.save_resume_data(flags);
798 m_session->handleTorrentSaveResumeDataRequested(this);
801 int TorrentImpl::filesCount() const
803 return m_torrentInfo.filesCount();
806 int TorrentImpl::piecesCount() const
808 return m_torrentInfo.piecesCount();
811 int TorrentImpl::piecesHave() const
813 return m_nativeStatus.num_pieces;
816 qreal TorrentImpl::progress() const
818 if (isChecking())
819 return m_nativeStatus.progress;
821 if (m_nativeStatus.total_wanted == 0)
822 return 0.;
824 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
825 return 1.;
827 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
828 if ((progress < 0.f) || (progress > 1.f))
830 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
831 .arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
832 , Log::WARNING);
835 return progress;
838 QString TorrentImpl::category() const
840 return m_category;
843 bool TorrentImpl::belongsToCategory(const QString &category) const
845 if (m_category.isEmpty())
846 return category.isEmpty();
848 if (m_category == category)
849 return true;
851 return (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + u'/'));
854 TagSet TorrentImpl::tags() const
856 return m_tags;
859 bool TorrentImpl::hasTag(const QString &tag) const
861 return m_tags.contains(tag);
864 bool TorrentImpl::addTag(const QString &tag)
866 if (!m_session->isValidTag(tag))
867 return false;
868 if (hasTag(tag))
869 return false;
871 if (!m_session->hasTag(tag))
873 if (!m_session->addTag(tag))
874 return false;
876 m_tags.insert(tag);
877 m_session->handleTorrentNeedSaveResumeData(this);
878 m_session->handleTorrentTagAdded(this, tag);
879 return true;
882 bool TorrentImpl::removeTag(const QString &tag)
884 if (m_tags.remove(tag))
886 m_session->handleTorrentNeedSaveResumeData(this);
887 m_session->handleTorrentTagRemoved(this, tag);
888 return true;
890 return false;
893 void TorrentImpl::removeAllTags()
895 for (const QString &tag : asConst(tags()))
896 removeTag(tag);
899 QDateTime TorrentImpl::addedTime() const
901 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
904 qreal TorrentImpl::ratioLimit() const
906 return m_ratioLimit;
909 int TorrentImpl::seedingTimeLimit() const
911 return m_seedingTimeLimit;
914 int TorrentImpl::inactiveSeedingTimeLimit() const
916 return m_inactiveSeedingTimeLimit;
919 Path TorrentImpl::filePath(const int index) const
921 Q_ASSERT(index >= 0);
922 Q_ASSERT(index < m_filePaths.size());
924 return m_filePaths.value(index, {});
927 Path TorrentImpl::actualFilePath(const int index) const
929 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
931 Q_ASSERT(index >= 0);
932 Q_ASSERT(index < nativeIndexes.size());
933 if ((index < 0) || (index >= nativeIndexes.size()))
934 return {};
936 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes[index]));
939 qlonglong TorrentImpl::fileSize(const int index) const
941 return m_torrentInfo.fileSize(index);
944 PathList TorrentImpl::filePaths() const
946 return m_filePaths;
949 QVector<DownloadPriority> TorrentImpl::filePriorities() const
951 return m_filePriorities;
954 TorrentInfo TorrentImpl::info() const
956 return m_torrentInfo;
959 bool TorrentImpl::isPaused() const
961 return m_isStopped;
964 bool TorrentImpl::isQueued() const
966 // Torrent is Queued if it isn't in Paused state but paused internally
967 return (!isPaused()
968 && (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
969 && (m_nativeStatus.flags & lt::torrent_flags::paused));
972 bool TorrentImpl::isChecking() const
974 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
975 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
978 bool TorrentImpl::isDownloading() const
980 switch (m_state)
982 case TorrentState::Downloading:
983 case TorrentState::DownloadingMetadata:
984 case TorrentState::ForcedDownloadingMetadata:
985 case TorrentState::StalledDownloading:
986 case TorrentState::CheckingDownloading:
987 case TorrentState::PausedDownloading:
988 case TorrentState::QueuedDownloading:
989 case TorrentState::ForcedDownloading:
990 return true;
991 default:
992 break;
995 return false;
998 bool TorrentImpl::isMoving() const
1000 return m_state == TorrentState::Moving;
1003 bool TorrentImpl::isUploading() const
1005 switch (m_state)
1007 case TorrentState::Uploading:
1008 case TorrentState::StalledUploading:
1009 case TorrentState::CheckingUploading:
1010 case TorrentState::QueuedUploading:
1011 case TorrentState::ForcedUploading:
1012 return true;
1013 default:
1014 break;
1017 return false;
1020 bool TorrentImpl::isCompleted() const
1022 switch (m_state)
1024 case TorrentState::Uploading:
1025 case TorrentState::StalledUploading:
1026 case TorrentState::CheckingUploading:
1027 case TorrentState::PausedUploading:
1028 case TorrentState::QueuedUploading:
1029 case TorrentState::ForcedUploading:
1030 return true;
1031 default:
1032 break;
1035 return false;
1038 bool TorrentImpl::isActive() const
1040 switch (m_state)
1042 case TorrentState::StalledDownloading:
1043 return (uploadPayloadRate() > 0);
1045 case TorrentState::DownloadingMetadata:
1046 case TorrentState::ForcedDownloadingMetadata:
1047 case TorrentState::Downloading:
1048 case TorrentState::ForcedDownloading:
1049 case TorrentState::Uploading:
1050 case TorrentState::ForcedUploading:
1051 case TorrentState::Moving:
1052 return true;
1054 default:
1055 break;
1058 return false;
1061 bool TorrentImpl::isInactive() const
1063 return !isActive();
1066 bool TorrentImpl::isErrored() const
1068 return ((m_state == TorrentState::MissingFiles)
1069 || (m_state == TorrentState::Error));
1072 bool TorrentImpl::isFinished() const
1074 return ((m_nativeStatus.state == lt::torrent_status::finished)
1075 || (m_nativeStatus.state == lt::torrent_status::seeding));
1078 bool TorrentImpl::isForced() const
1080 return (!isPaused() && (m_operatingMode == TorrentOperatingMode::Forced));
1083 bool TorrentImpl::isSequentialDownload() const
1085 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::sequential_download);
1088 bool TorrentImpl::hasFirstLastPiecePriority() const
1090 return m_hasFirstLastPiecePriority;
1093 TorrentState TorrentImpl::state() const
1095 return m_state;
1098 void TorrentImpl::updateState()
1100 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
1102 m_state = TorrentState::CheckingResumeData;
1104 else if (isMoveInProgress())
1106 m_state = TorrentState::Moving;
1108 else if (hasMissingFiles())
1110 m_state = TorrentState::MissingFiles;
1112 else if (hasError())
1114 m_state = TorrentState::Error;
1116 else if (!hasMetadata())
1118 if (isPaused())
1119 m_state = TorrentState::PausedDownloading;
1120 else if (m_session->isQueueingSystemEnabled() && isQueued())
1121 m_state = TorrentState::QueuedDownloading;
1122 else
1123 m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata;
1125 else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused())
1127 // If the torrent is not just in the "checking" state, but is being actually checked
1128 m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
1130 else if (isFinished())
1132 if (isPaused())
1133 m_state = TorrentState::PausedUploading;
1134 else if (m_session->isQueueingSystemEnabled() && isQueued())
1135 m_state = TorrentState::QueuedUploading;
1136 else if (isForced())
1137 m_state = TorrentState::ForcedUploading;
1138 else if (m_nativeStatus.upload_payload_rate > 0)
1139 m_state = TorrentState::Uploading;
1140 else
1141 m_state = TorrentState::StalledUploading;
1143 else
1145 if (isPaused())
1146 m_state = TorrentState::PausedDownloading;
1147 else if (m_session->isQueueingSystemEnabled() && isQueued())
1148 m_state = TorrentState::QueuedDownloading;
1149 else if (isForced())
1150 m_state = TorrentState::ForcedDownloading;
1151 else if (m_nativeStatus.download_payload_rate > 0)
1152 m_state = TorrentState::Downloading;
1153 else
1154 m_state = TorrentState::StalledDownloading;
1158 bool TorrentImpl::hasMetadata() const
1160 return m_torrentInfo.isValid();
1163 bool TorrentImpl::hasMissingFiles() const
1165 return m_hasMissingFiles;
1168 bool TorrentImpl::hasError() const
1170 return (m_nativeStatus.errc || (m_nativeStatus.flags & lt::torrent_flags::upload_mode));
1173 int TorrentImpl::queuePosition() const
1175 return static_cast<int>(m_nativeStatus.queue_position);
1178 QString TorrentImpl::error() const
1180 if (m_nativeStatus.errc)
1181 return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
1183 if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
1185 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1186 .arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
1189 return {};
1192 qlonglong TorrentImpl::totalDownload() const
1194 return m_nativeStatus.all_time_download;
1197 qlonglong TorrentImpl::totalUpload() const
1199 return m_nativeStatus.all_time_upload;
1202 qlonglong TorrentImpl::activeTime() const
1204 return lt::total_seconds(m_nativeStatus.active_duration);
1207 qlonglong TorrentImpl::finishedTime() const
1209 return lt::total_seconds(m_nativeStatus.finished_duration);
1212 qlonglong TorrentImpl::eta() const
1214 if (isPaused()) return MAX_ETA;
1216 const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
1218 if (isFinished())
1220 const qreal maxRatioValue = maxRatio();
1221 const int maxSeedingTimeValue = maxSeedingTime();
1222 const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
1223 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
1225 qlonglong ratioEta = MAX_ETA;
1227 if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
1230 qlonglong realDL = totalDownload();
1231 if (realDL <= 0)
1232 realDL = wantedSize();
1234 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1237 qlonglong seedingTimeEta = MAX_ETA;
1239 if (maxSeedingTimeValue >= 0)
1241 seedingTimeEta = (maxSeedingTimeValue * 60) - finishedTime();
1242 if (seedingTimeEta < 0)
1243 seedingTimeEta = 0;
1246 qlonglong inactiveSeedingTimeEta = MAX_ETA;
1248 if (maxInactiveSeedingTimeValue >= 0)
1250 inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
1251 inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
1254 return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
1257 if (!speedAverage.download) return MAX_ETA;
1259 return (wantedSize() - completedSize()) / speedAverage.download;
1262 QVector<qreal> TorrentImpl::filesProgress() const
1264 if (!hasMetadata())
1265 return {};
1267 const int count = m_filesProgress.size();
1268 Q_ASSERT(count == filesCount());
1269 if (count != filesCount()) [[unlikely]]
1270 return {};
1272 if (m_completedFiles.count(true) == count)
1273 return QVector<qreal>(count, 1);
1275 QVector<qreal> result;
1276 result.reserve(count);
1277 for (int i = 0; i < count; ++i)
1279 const int64_t progress = m_filesProgress.at(i);
1280 const int64_t size = fileSize(i);
1281 if ((size <= 0) || (progress == size))
1282 result << 1;
1283 else
1284 result << (progress / static_cast<qreal>(size));
1287 return result;
1290 int TorrentImpl::seedsCount() const
1292 return m_nativeStatus.num_seeds;
1295 int TorrentImpl::peersCount() const
1297 return m_nativeStatus.num_peers;
1300 int TorrentImpl::leechsCount() const
1302 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1305 int TorrentImpl::totalSeedsCount() const
1307 return (m_nativeStatus.num_complete > -1) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1310 int TorrentImpl::totalPeersCount() const
1312 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1313 return (peers > -1) ? peers : m_nativeStatus.list_peers;
1316 int TorrentImpl::totalLeechersCount() const
1318 return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1321 QDateTime TorrentImpl::lastSeenComplete() const
1323 if (m_nativeStatus.last_seen_complete > 0)
1324 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1325 else
1326 return {};
1329 QDateTime TorrentImpl::completedTime() const
1331 if (m_nativeStatus.completed_time > 0)
1332 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1333 else
1334 return {};
1337 qlonglong TorrentImpl::timeSinceUpload() const
1339 if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
1340 return -1;
1341 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1344 qlonglong TorrentImpl::timeSinceDownload() const
1346 if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
1347 return -1;
1348 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1351 qlonglong TorrentImpl::timeSinceActivity() const
1353 const qlonglong upTime = timeSinceUpload();
1354 const qlonglong downTime = timeSinceDownload();
1355 return ((upTime < 0) != (downTime < 0))
1356 ? std::max(upTime, downTime)
1357 : std::min(upTime, downTime);
1360 int TorrentImpl::downloadLimit() const
1362 return m_downloadLimit;;
1365 int TorrentImpl::uploadLimit() const
1367 return m_uploadLimit;
1370 bool TorrentImpl::superSeeding() const
1372 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::super_seeding);
1375 bool TorrentImpl::isDHTDisabled() const
1377 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_dht);
1380 bool TorrentImpl::isPEXDisabled() const
1382 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_pex);
1385 bool TorrentImpl::isLSDDisabled() const
1387 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
1390 QVector<PeerInfo> TorrentImpl::peers() const
1392 std::vector<lt::peer_info> nativePeers;
1393 m_nativeHandle.get_peer_info(nativePeers);
1395 QVector<PeerInfo> peers;
1396 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
1398 for (const lt::peer_info &peer : nativePeers)
1399 peers.append(PeerInfo(peer, pieces()));
1401 return peers;
1404 QBitArray TorrentImpl::pieces() const
1406 return m_pieces;
1409 QBitArray TorrentImpl::downloadingPieces() const
1411 QBitArray result(piecesCount());
1413 std::vector<lt::partial_piece_info> queue;
1414 m_nativeHandle.get_download_queue(queue);
1416 for (const lt::partial_piece_info &info : queue)
1417 result.setBit(LT::toUnderlyingType(info.piece_index));
1419 return result;
1422 QVector<int> TorrentImpl::pieceAvailability() const
1424 std::vector<int> avail;
1425 m_nativeHandle.piece_availability(avail);
1427 return {avail.cbegin(), avail.cend()};
1430 qreal TorrentImpl::distributedCopies() const
1432 return m_nativeStatus.distributed_copies;
1435 qreal TorrentImpl::maxRatio() const
1437 if (m_ratioLimit == USE_GLOBAL_RATIO)
1438 return m_session->globalMaxRatio();
1440 return m_ratioLimit;
1443 int TorrentImpl::maxSeedingTime() const
1445 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1446 return m_session->globalMaxSeedingMinutes();
1448 return m_seedingTimeLimit;
1451 int TorrentImpl::maxInactiveSeedingTime() const
1453 if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
1454 return m_session->globalMaxInactiveSeedingMinutes();
1456 return m_inactiveSeedingTimeLimit;
1459 qreal TorrentImpl::realRatio() const
1461 const int64_t upload = m_nativeStatus.all_time_upload;
1462 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1463 const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01))
1464 ? m_nativeStatus.total_done
1465 : m_nativeStatus.all_time_download;
1467 if (download == 0)
1468 return (upload == 0) ? 0 : MAX_RATIO;
1470 const qreal ratio = upload / static_cast<qreal>(download);
1471 Q_ASSERT(ratio >= 0);
1472 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1475 int TorrentImpl::uploadPayloadRate() const
1477 // workaround: suppress the speed for paused state
1478 return isPaused() ? 0 : m_nativeStatus.upload_payload_rate;
1481 int TorrentImpl::downloadPayloadRate() const
1483 // workaround: suppress the speed for paused state
1484 return isPaused() ? 0 : m_nativeStatus.download_payload_rate;
1487 qlonglong TorrentImpl::totalPayloadUpload() const
1489 return m_nativeStatus.total_payload_upload;
1492 qlonglong TorrentImpl::totalPayloadDownload() const
1494 return m_nativeStatus.total_payload_download;
1497 int TorrentImpl::connectionsCount() const
1499 return m_nativeStatus.num_connections;
1502 int TorrentImpl::connectionsLimit() const
1504 return m_nativeStatus.connections_limit;
1507 qlonglong TorrentImpl::nextAnnounce() const
1509 return lt::total_seconds(m_nativeStatus.next_announce);
1512 void TorrentImpl::setName(const QString &name)
1514 if (m_name != name)
1516 m_name = name;
1517 m_session->handleTorrentNeedSaveResumeData(this);
1518 m_session->handleTorrentNameChanged(this);
1522 bool TorrentImpl::setCategory(const QString &category)
1524 if (m_category != category)
1526 if (!category.isEmpty() && !m_session->categories().contains(category))
1527 return false;
1529 const QString oldCategory = m_category;
1530 m_category = category;
1531 m_session->handleTorrentNeedSaveResumeData(this);
1532 m_session->handleTorrentCategoryChanged(this, oldCategory);
1534 if (m_useAutoTMM)
1536 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1537 adjustStorageLocation();
1538 else
1539 setAutoTMMEnabled(false);
1543 return true;
1546 void TorrentImpl::forceReannounce(const int index)
1548 m_nativeHandle.force_reannounce(0, index);
1551 void TorrentImpl::forceDHTAnnounce()
1553 m_nativeHandle.force_dht_announce();
1556 void TorrentImpl::forceRecheck()
1558 if (!hasMetadata())
1559 return;
1561 m_nativeHandle.force_recheck();
1562 // We have to force update the cached state, otherwise someone will be able to get
1563 // an incorrect one during the interval until the cached state is updated in a regular way.
1564 m_nativeStatus.state = lt::torrent_status::checking_resume_data;
1566 m_hasMissingFiles = false;
1567 m_unchecked = false;
1569 m_completedFiles.fill(false);
1570 m_filesProgress.fill(0);
1571 m_pieces.fill(false);
1572 m_nativeStatus.pieces.clear_all();
1573 m_nativeStatus.num_pieces = 0;
1575 if (isPaused())
1577 // When "force recheck" is applied on paused torrent, we temporarily resume it
1578 resume();
1579 m_stopCondition = StopCondition::FilesChecked;
1583 void TorrentImpl::setSequentialDownload(const bool enable)
1585 if (enable)
1587 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1588 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1590 else
1592 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1593 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1596 m_session->handleTorrentNeedSaveResumeData(this);
1599 void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
1601 if (m_hasFirstLastPiecePriority == enabled)
1602 return;
1604 m_hasFirstLastPiecePriority = enabled;
1605 if (hasMetadata())
1606 applyFirstLastPiecePriority(enabled);
1608 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1609 .arg((enabled ? tr("On") : tr("Off")), name()));
1611 m_session->handleTorrentNeedSaveResumeData(this);
1614 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
1616 Q_ASSERT(hasMetadata());
1618 // Download first and last pieces first for every file in the torrent
1620 auto piecePriorities = std::vector<lt::download_priority_t>(m_torrentInfo.piecesCount(), LT::toNative(DownloadPriority::Ignored));
1622 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1623 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1624 for (int fileIndex = 0; fileIndex < m_filePriorities.size(); ++fileIndex)
1626 const DownloadPriority filePrio = m_filePriorities[fileIndex];
1627 if (filePrio <= DownloadPriority::Ignored)
1628 continue;
1630 // Determine the priority to set
1631 const lt::download_priority_t piecePrio = LT::toNative(enabled ? DownloadPriority::Maximum : filePrio);
1632 const TorrentInfo::PieceRange pieceRange = m_torrentInfo.filePieces(fileIndex);
1634 // worst case: AVI index = 1% of total file size (at the end of the file)
1635 const int numPieces = std::ceil(fileSize(fileIndex) * 0.01 / pieceLength());
1636 for (int i = 0; i < numPieces; ++i)
1638 piecePriorities[pieceRange.first() + i] = piecePrio;
1639 piecePriorities[pieceRange.last() - i] = piecePrio;
1642 const int firstPiece = pieceRange.first() + numPieces;
1643 const int lastPiece = pieceRange.last() - numPieces;
1644 for (int pieceIndex = firstPiece; pieceIndex <= lastPiece; ++pieceIndex)
1645 piecePriorities[pieceIndex] = LT::toNative(filePrio);
1648 m_nativeHandle.prioritize_pieces(piecePriorities);
1651 void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
1653 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1654 endReceivedMetadataHandling(savePath, fileNames);
1657 TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
1659 const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
1660 , [&announceEntry](const TrackerEntry &trackerEntry)
1662 return (trackerEntry.url == QString::fromStdString(announceEntry.url));
1665 Q_ASSERT(it != m_trackerEntries.end());
1666 if (it == m_trackerEntries.end()) [[unlikely]]
1667 return {};
1669 #ifdef QBT_USES_LIBTORRENT2
1670 QSet<int> btProtocols;
1671 const auto &infoHashes = nativeHandle().info_hashes();
1672 if (infoHashes.has(lt::protocol_version::V1))
1673 btProtocols.insert(1);
1674 if (infoHashes.has(lt::protocol_version::V2))
1675 btProtocols.insert(2);
1676 #else
1677 const QSet<int> btProtocols {1};
1678 #endif
1679 ::updateTrackerEntry(*it, announceEntry, btProtocols, updateInfo);
1680 return *it;
1683 void TorrentImpl::resetTrackerEntries()
1685 for (auto &trackerEntry : m_trackerEntries)
1686 trackerEntry = {trackerEntry.url, trackerEntry.tier};
1689 std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
1691 if (m_nativeStatus.torrent_file.expired())
1692 m_nativeStatus.torrent_file = m_nativeHandle.torrent_file();
1693 return m_nativeStatus.torrent_file.lock();
1696 void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
1698 Q_ASSERT(m_maintenanceJob == MaintenanceJob::HandleMetadata);
1699 if (m_maintenanceJob != MaintenanceJob::HandleMetadata) [[unlikely]]
1700 return;
1702 Q_ASSERT(m_filePaths.isEmpty());
1703 if (!m_filePaths.isEmpty()) [[unlikely]]
1704 m_filePaths.clear();
1706 lt::add_torrent_params &p = m_ltAddTorrentParams;
1708 const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1709 m_torrentInfo = TorrentInfo(*metadata);
1710 m_filePriorities.reserve(filesCount());
1711 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
1712 p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
1713 , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
1715 m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
1716 m_filesProgress.resize(filesCount());
1717 updateProgress();
1719 for (int i = 0; i < fileNames.size(); ++i)
1721 const auto nativeIndex = nativeIndexes.at(i);
1723 const Path &actualFilePath = fileNames.at(i);
1724 p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
1726 const Path filePath = actualFilePath.removedExtension(QB_EXT);
1727 m_filePaths.append(filePath);
1729 lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
1730 if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
1731 nativePriority = lt::dont_download;
1732 const auto priority = LT::fromNative(nativePriority);
1733 m_filePriorities.append(priority);
1735 p.save_path = savePath.toString().toStdString();
1736 p.ti = metadata;
1738 if (stopCondition() == StopCondition::MetadataReceived)
1740 m_stopCondition = StopCondition::None;
1742 m_isStopped = true;
1743 p.flags |= lt::torrent_flags::paused;
1744 p.flags &= ~lt::torrent_flags::auto_managed;
1746 m_session->handleTorrentPaused(this);
1749 reload();
1751 // If first/last piece priority was specified when adding this torrent,
1752 // we should apply it now that we have metadata:
1753 if (m_hasFirstLastPiecePriority)
1754 applyFirstLastPiecePriority(true);
1756 m_maintenanceJob = MaintenanceJob::None;
1757 prepareResumeData(p);
1759 m_session->handleTorrentMetadataReceived(this);
1762 void TorrentImpl::reload()
1765 m_completedFiles.fill(false);
1766 m_filesProgress.fill(0);
1767 m_pieces.fill(false);
1768 m_nativeStatus.pieces.clear_all();
1769 m_nativeStatus.num_pieces = 0;
1771 const auto queuePos = m_nativeHandle.queue_position();
1773 m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
1775 lt::add_torrent_params p = m_ltAddTorrentParams;
1776 p.flags |= lt::torrent_flags::update_subscribe
1777 | lt::torrent_flags::override_trackers
1778 | lt::torrent_flags::override_web_seeds;
1780 if (m_isStopped)
1782 p.flags |= lt::torrent_flags::paused;
1783 p.flags &= ~lt::torrent_flags::auto_managed;
1785 else if (m_operatingMode == TorrentOperatingMode::AutoManaged)
1787 p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1789 else
1791 p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1794 auto *const extensionData = new ExtensionData;
1795 p.userdata = LTClientData(extensionData);
1796 m_nativeHandle = m_nativeSession->add_torrent(p);
1798 m_nativeStatus = extensionData->status;
1800 if (queuePos >= lt::queue_position_t {})
1801 m_nativeHandle.queue_position_set(queuePos);
1802 m_nativeStatus.queue_position = queuePos;
1804 updateState();
1806 catch (const lt::system_error &err)
1808 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1809 .arg(id().toString(), QString::fromLocal8Bit(err.what())));
1812 void TorrentImpl::pause()
1814 if (!m_isStopped)
1816 m_stopCondition = StopCondition::None;
1817 m_isStopped = true;
1818 m_session->handleTorrentNeedSaveResumeData(this);
1819 m_session->handleTorrentPaused(this);
1822 if (m_maintenanceJob == MaintenanceJob::None)
1824 setAutoManaged(false);
1825 m_nativeHandle.pause();
1827 m_payloadRateMonitor.reset();
1831 void TorrentImpl::resume(const TorrentOperatingMode mode)
1833 if (hasError())
1835 m_nativeHandle.clear_error();
1836 m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode);
1839 m_operatingMode = mode;
1841 if (m_hasMissingFiles)
1843 m_hasMissingFiles = false;
1844 m_isStopped = false;
1845 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1846 reload();
1847 return;
1850 if (m_isStopped)
1852 m_isStopped = false;
1853 m_session->handleTorrentNeedSaveResumeData(this);
1854 m_session->handleTorrentResumed(this);
1857 if (m_maintenanceJob == MaintenanceJob::None)
1859 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1860 if (m_operatingMode == TorrentOperatingMode::Forced)
1861 m_nativeHandle.resume();
1865 void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
1867 if (!hasMetadata())
1869 m_savePath = newPath;
1870 m_session->handleTorrentSavePathChanged(this);
1871 return;
1874 const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
1875 ? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
1876 if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
1878 if (!m_storageIsMoving)
1880 m_storageIsMoving = true;
1881 updateState();
1882 m_session->handleTorrentStorageMovingStateChanged(this);
1887 void TorrentImpl::renameFile(const int index, const Path &path)
1889 Q_ASSERT((index >= 0) && (index < filesCount()));
1890 if ((index < 0) || (index >= filesCount())) [[unlikely]]
1891 return;
1893 const Path wantedPath = wantedActualPath(index, path);
1894 doRenameFile(index, wantedPath);
1897 void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
1899 updateStatus(nativeStatus);
1902 void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
1904 if (context == MoveStorageContext::ChangeSavePath)
1905 m_savePath = path;
1906 else if (context == MoveStorageContext::ChangeDownloadPath)
1907 m_downloadPath = path;
1908 m_storageIsMoving = hasOutstandingJob;
1909 m_nativeStatus.save_path = path.toString().toStdString();
1911 m_session->handleTorrentSavePathChanged(this);
1912 m_session->handleTorrentNeedSaveResumeData(this);
1914 if (!m_storageIsMoving)
1916 updateState();
1917 m_session->handleTorrentStorageMovingStateChanged(this);
1919 if (m_hasMissingFiles)
1921 // it can be moved to the proper location
1922 m_hasMissingFiles = false;
1923 m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
1924 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(nativeTorrentInfo());
1925 reload();
1928 while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1929 std::invoke(m_moveFinishedTriggers.dequeue());
1933 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_checked_alert *p)
1935 if (!hasMetadata())
1937 // The torrent is checked due to metadata received, but we should not process
1938 // this event until the torrent is reloaded using the received metadata.
1939 return;
1942 if (stopCondition() == StopCondition::FilesChecked)
1943 pause();
1945 m_statusUpdatedTriggers.enqueue([this]()
1947 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1949 if (!m_hasMissingFiles)
1951 if ((progress() < 1.0) && (wantedSize() > 0))
1952 m_hasFinishedStatus = false;
1953 else if (progress() == 1.0)
1954 m_hasFinishedStatus = true;
1956 adjustStorageLocation();
1957 manageIncompleteFiles();
1959 if (!isPaused())
1961 // torrent is internally paused using NativeTorrentExtension after files checked
1962 // so we need to resume it if there is no corresponding "stop condition" set
1963 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1964 if (m_operatingMode == TorrentOperatingMode::Forced)
1965 m_nativeHandle.resume();
1969 if (m_nativeStatus.need_save_resume)
1970 m_session->handleTorrentNeedSaveResumeData(this);
1972 m_session->handleTorrentChecked(this);
1976 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused]] const lt::torrent_finished_alert *p)
1978 m_hasMissingFiles = false;
1979 if (m_hasFinishedStatus)
1980 return;
1982 m_statusUpdatedTriggers.enqueue([this]()
1984 adjustStorageLocation();
1985 manageIncompleteFiles();
1987 m_session->handleTorrentNeedSaveResumeData(this);
1989 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
1990 if (recheckTorrentsOnCompletion && m_unchecked)
1992 forceRecheck();
1994 else
1996 m_hasFinishedStatus = true;
1998 if (isMoveInProgress() || (m_renameCount > 0))
1999 m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
2000 else
2001 m_session->handleTorrentFinished(this);
2006 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused]] const lt::torrent_paused_alert *p)
2010 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused]] const lt::torrent_resumed_alert *p)
2014 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
2016 if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
2018 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2019 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2020 // than the list we usually cache, so we have to request it from the appropriate source.
2021 fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
2024 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2026 Q_ASSERT(m_indexMap.isEmpty());
2028 const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
2029 m_ltAddTorrentParams = p->params;
2030 if (isSeedMode)
2031 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2033 m_ltAddTorrentParams.have_pieces.clear();
2034 m_ltAddTorrentParams.verified_pieces.clear();
2036 TorrentInfo metadata = TorrentInfo(*nativeTorrentInfo());
2038 const auto &renamedFiles = m_ltAddTorrentParams.renamed_files;
2039 PathList filePaths = metadata.filePaths();
2040 if (renamedFiles.empty() && (m_contentLayout != TorrentContentLayout::Original))
2042 const Path originalRootFolder = Path::findRootFolder(filePaths);
2043 const auto originalContentLayout = (originalRootFolder.isEmpty()
2044 ? TorrentContentLayout::NoSubfolder
2045 : TorrentContentLayout::Subfolder);
2046 if (m_contentLayout != originalContentLayout)
2048 if (m_contentLayout == TorrentContentLayout::NoSubfolder)
2049 Path::stripRootFolder(filePaths);
2050 else
2051 Path::addRootFolder(filePaths, filePaths.at(0).removedExtension());
2055 const auto nativeIndexes = metadata.nativeIndexes();
2056 m_indexMap.reserve(filePaths.size());
2057 for (int i = 0; i < filePaths.size(); ++i)
2059 const auto nativeIndex = nativeIndexes.at(i);
2060 m_indexMap[nativeIndex] = i;
2062 if (const auto it = renamedFiles.find(nativeIndex); it != renamedFiles.cend())
2063 filePaths[i] = Path(it->second);
2066 m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
2068 else
2070 prepareResumeData(p->params);
2074 void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
2076 if (m_hasMissingFiles)
2078 const auto havePieces = m_ltAddTorrentParams.have_pieces;
2079 const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
2080 const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
2082 // Update recent resume data but preserve existing progress
2083 m_ltAddTorrentParams = params;
2084 m_ltAddTorrentParams.have_pieces = havePieces;
2085 m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
2086 m_ltAddTorrentParams.verified_pieces = verifiedPieces;
2088 else
2090 const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
2091 // Update recent resume data
2092 m_ltAddTorrentParams = params;
2093 if (preserveSeedMode)
2094 m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
2097 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2098 m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
2100 LoadTorrentParams resumeData;
2101 resumeData.name = m_name;
2102 resumeData.category = m_category;
2103 resumeData.tags = m_tags;
2104 resumeData.contentLayout = m_contentLayout;
2105 resumeData.ratioLimit = m_ratioLimit;
2106 resumeData.seedingTimeLimit = m_seedingTimeLimit;
2107 resumeData.inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit;
2108 resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
2109 resumeData.hasFinishedStatus = m_hasFinishedStatus;
2110 resumeData.stopped = m_isStopped;
2111 resumeData.stopCondition = m_stopCondition;
2112 resumeData.operatingMode = m_operatingMode;
2113 resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
2114 resumeData.useAutoTMM = m_useAutoTMM;
2115 if (!resumeData.useAutoTMM)
2117 resumeData.savePath = m_savePath;
2118 resumeData.downloadPath = m_downloadPath;
2121 m_session->handleTorrentResumeDataReady(this, resumeData);
2124 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
2126 if (p->error != lt::errors::resume_data_not_modified)
2128 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2129 .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
2132 m_session->handleTorrentSaveResumeDataFailed(this);
2135 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
2137 // Files were probably moved or storage isn't accessible
2138 m_hasMissingFiles = true;
2139 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2140 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
2143 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
2145 const int fileIndex = m_indexMap.value(p->index, -1);
2146 Q_ASSERT(fileIndex >= 0);
2148 // Remove empty leftover folders
2149 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2150 // be removed if they are empty
2151 const Path oldFilePath = m_filePaths.at(fileIndex);
2152 const Path newFilePath = Path(QString::fromUtf8(p->new_name())).removedExtension(QB_EXT);
2154 // Check if ".!qB" extension was just added or removed
2155 // We should compare path in a case sensitive manner even on case insensitive
2156 // platforms since it can be renamed by only changing case of some character(s)
2157 if (oldFilePath.data() != newFilePath.data())
2159 m_filePaths[fileIndex] = newFilePath;
2161 Path oldParentPath = oldFilePath.parentPath();
2162 const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath());
2163 while (oldParentPath != commonBasePath)
2165 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath);
2166 oldParentPath = oldParentPath.parentPath();
2170 --m_renameCount;
2171 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2172 m_moveFinishedTriggers.takeFirst()();
2174 m_session->handleTorrentNeedSaveResumeData(this);
2177 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
2179 const int fileIndex = m_indexMap.value(p->index, -1);
2180 Q_ASSERT(fileIndex >= 0);
2182 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2183 .arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
2185 --m_renameCount;
2186 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
2187 m_moveFinishedTriggers.takeFirst()();
2189 m_session->handleTorrentNeedSaveResumeData(this);
2192 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
2194 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
2195 return;
2197 const int fileIndex = m_indexMap.value(p->index, -1);
2198 Q_ASSERT(fileIndex >= 0);
2200 m_completedFiles.setBit(fileIndex);
2202 const Path actualPath = actualFilePath(fileIndex);
2204 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2205 // only apply Mark-of-the-Web to new download files
2206 if (isDownloading())
2208 const Path fullpath = actualStorageLocation() / actualPath;
2209 Utils::Misc::applyMarkOfTheWeb(fullpath);
2211 #endif // Q_OS_MACOS || Q_OS_WIN
2213 if (m_session->isAppendExtensionEnabled())
2215 const Path path = filePath(fileIndex);
2216 if (actualPath != path)
2218 qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
2219 doRenameFile(fileIndex, path);
2224 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
2226 m_lastFileError = {p->error, p->op};
2229 #ifdef QBT_USES_LIBTORRENT2
2230 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
2232 m_session->handleTorrentNeedSaveResumeData(this);
2234 #endif
2236 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused]] const lt::metadata_received_alert *p)
2238 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2240 #ifdef QBT_USES_LIBTORRENT2
2241 const InfoHash prevInfoHash = infoHash();
2242 m_infoHash = InfoHash(m_nativeHandle.info_hashes());
2243 if (prevInfoHash != infoHash())
2244 m_session->handleTorrentInfoHashChanged(this, prevInfoHash);
2245 #endif
2247 m_maintenanceJob = MaintenanceJob::HandleMetadata;
2248 m_session->handleTorrentNeedSaveResumeData(this);
2251 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
2253 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))
2254 , Log::INFO);
2257 void TorrentImpl::handleCategoryOptionsChanged()
2259 if (m_useAutoTMM)
2260 adjustStorageLocation();
2263 void TorrentImpl::handleAppendExtensionToggled()
2265 if (!hasMetadata()) return;
2267 manageIncompleteFiles();
2270 void TorrentImpl::handleAlert(const lt::alert *a)
2272 switch (a->type())
2274 #ifdef QBT_USES_LIBTORRENT2
2275 case lt::file_prio_alert::alert_type:
2276 handleFilePrioAlert(static_cast<const lt::file_prio_alert*>(a));
2277 break;
2278 #endif
2279 case lt::file_renamed_alert::alert_type:
2280 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
2281 break;
2282 case lt::file_rename_failed_alert::alert_type:
2283 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
2284 break;
2285 case lt::file_completed_alert::alert_type:
2286 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
2287 break;
2288 case lt::file_error_alert::alert_type:
2289 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
2290 break;
2291 case lt::torrent_finished_alert::alert_type:
2292 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
2293 break;
2294 case lt::save_resume_data_alert::alert_type:
2295 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
2296 break;
2297 case lt::save_resume_data_failed_alert::alert_type:
2298 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
2299 break;
2300 case lt::torrent_paused_alert::alert_type:
2301 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
2302 break;
2303 case lt::torrent_resumed_alert::alert_type:
2304 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
2305 break;
2306 case lt::metadata_received_alert::alert_type:
2307 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2308 break;
2309 case lt::fastresume_rejected_alert::alert_type:
2310 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2311 break;
2312 case lt::torrent_checked_alert::alert_type:
2313 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2314 break;
2315 case lt::performance_alert::alert_type:
2316 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2317 break;
2321 void TorrentImpl::manageIncompleteFiles()
2323 const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
2324 const lt::file_storage &nativeFiles = nativeInfo->files();
2326 for (int i = 0; i < filesCount(); ++i)
2328 const Path path = filePath(i);
2330 const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
2331 const Path actualPath {nativeFiles.file_path(nativeIndex)};
2332 const Path wantedPath = wantedActualPath(i, path);
2333 if (actualPath != wantedPath)
2335 qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
2336 doRenameFile(i, wantedPath);
2341 void TorrentImpl::adjustStorageLocation()
2343 const Path downloadPath = this->downloadPath();
2344 const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
2346 if ((targetPath != actualStorageLocation()) || isMoveInProgress())
2347 moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
2350 void TorrentImpl::doRenameFile(const int index, const Path &path)
2352 const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
2354 Q_ASSERT(index >= 0);
2355 Q_ASSERT(index < nativeIndexes.size());
2356 if ((index < 0) || (index >= nativeIndexes.size())) [[unlikely]]
2357 return;
2359 ++m_renameCount;
2360 m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
2363 lt::torrent_handle TorrentImpl::nativeHandle() const
2365 return m_nativeHandle;
2368 void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
2370 if (hasMetadata())
2371 return;
2373 m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
2377 #ifdef QBT_USES_LIBTORRENT2
2378 nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
2379 #else
2380 const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
2381 nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
2382 #endif
2384 catch (const std::exception &) {}
2388 Torrent::StopCondition TorrentImpl::stopCondition() const
2390 return m_stopCondition;
2393 void TorrentImpl::setStopCondition(const StopCondition stopCondition)
2395 if (stopCondition == m_stopCondition)
2396 return;
2398 if (isPaused())
2399 return;
2401 if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata())
2402 return;
2404 if ((stopCondition == StopCondition::FilesChecked) && hasMetadata() && !isChecking())
2405 return;
2407 m_stopCondition = stopCondition;
2410 bool TorrentImpl::isMoveInProgress() const
2412 return m_storageIsMoving;
2415 void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
2417 const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
2419 if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
2420 updateProgress();
2422 updateState();
2424 m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
2425 , nativeStatus.upload_payload_rate});
2427 if (hasMetadata())
2429 // NOTE: Don't change the order of these conditionals!
2430 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2431 if (isChecking())
2432 m_unchecked = false;
2433 else if (isDownloading())
2434 m_unchecked = true;
2437 while (!m_statusUpdatedTriggers.isEmpty())
2438 std::invoke(m_statusUpdatedTriggers.dequeue());
2441 void TorrentImpl::updateProgress()
2443 Q_ASSERT(hasMetadata());
2444 if (!hasMetadata()) [[unlikely]]
2445 return;
2447 Q_ASSERT(!m_filesProgress.isEmpty());
2448 if (m_filesProgress.isEmpty()) [[unlikely]]
2449 m_filesProgress.resize(filesCount());
2451 const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
2452 const QBitArray newPieces = m_pieces ^ oldPieces;
2454 const int64_t pieceSize = m_torrentInfo.pieceLength();
2455 for (qsizetype index = 0; index < newPieces.size(); ++index)
2457 if (!newPieces.at(index))
2458 continue;
2460 int64_t size = m_torrentInfo.pieceLength(index);
2461 int64_t pieceOffset = index * pieceSize;
2463 for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
2465 const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
2466 const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
2468 m_filesProgress[fileIndex] += add;
2470 size -= add;
2471 if (size <= 0)
2472 break;
2474 pieceOffset += add;
2479 void TorrentImpl::setRatioLimit(qreal limit)
2481 if (limit < USE_GLOBAL_RATIO)
2482 limit = NO_RATIO_LIMIT;
2483 else if (limit > MAX_RATIO)
2484 limit = MAX_RATIO;
2486 if (m_ratioLimit != limit)
2488 m_ratioLimit = limit;
2489 m_session->handleTorrentNeedSaveResumeData(this);
2490 m_session->handleTorrentShareLimitChanged(this);
2494 void TorrentImpl::setSeedingTimeLimit(int limit)
2496 if (limit < USE_GLOBAL_SEEDING_TIME)
2497 limit = NO_SEEDING_TIME_LIMIT;
2498 else if (limit > MAX_SEEDING_TIME)
2499 limit = MAX_SEEDING_TIME;
2501 if (m_seedingTimeLimit != limit)
2503 m_seedingTimeLimit = limit;
2504 m_session->handleTorrentNeedSaveResumeData(this);
2505 m_session->handleTorrentShareLimitChanged(this);
2509 void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
2511 if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
2512 limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
2513 else if (limit > MAX_INACTIVE_SEEDING_TIME)
2514 limit = MAX_SEEDING_TIME;
2516 if (m_inactiveSeedingTimeLimit != limit)
2518 m_inactiveSeedingTimeLimit = limit;
2519 m_session->handleTorrentNeedSaveResumeData(this);
2520 m_session->handleTorrentShareLimitChanged(this);
2524 void TorrentImpl::setUploadLimit(const int limit)
2526 const int cleanValue = cleanLimitValue(limit);
2527 if (cleanValue == uploadLimit())
2528 return;
2530 m_uploadLimit = cleanValue;
2531 m_nativeHandle.set_upload_limit(m_uploadLimit);
2532 m_session->handleTorrentNeedSaveResumeData(this);
2535 void TorrentImpl::setDownloadLimit(const int limit)
2537 const int cleanValue = cleanLimitValue(limit);
2538 if (cleanValue == downloadLimit())
2539 return;
2541 m_downloadLimit = cleanValue;
2542 m_nativeHandle.set_download_limit(m_downloadLimit);
2543 m_session->handleTorrentNeedSaveResumeData(this);
2546 void TorrentImpl::setSuperSeeding(const bool enable)
2548 if (enable == superSeeding())
2549 return;
2551 if (enable)
2552 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2553 else
2554 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2556 m_session->handleTorrentNeedSaveResumeData(this);
2559 void TorrentImpl::setDHTDisabled(const bool disable)
2561 if (disable == isDHTDisabled())
2562 return;
2564 if (disable)
2565 m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
2566 else
2567 m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
2569 m_session->handleTorrentNeedSaveResumeData(this);
2572 void TorrentImpl::setPEXDisabled(const bool disable)
2574 if (disable == isPEXDisabled())
2575 return;
2577 if (disable)
2578 m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
2579 else
2580 m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
2582 m_session->handleTorrentNeedSaveResumeData(this);
2585 void TorrentImpl::setLSDDisabled(const bool disable)
2587 if (disable == isLSDDisabled())
2588 return;
2590 if (disable)
2591 m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
2592 else
2593 m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
2595 m_session->handleTorrentNeedSaveResumeData(this);
2598 void TorrentImpl::flushCache() const
2600 m_nativeHandle.flush_cache();
2603 QString TorrentImpl::createMagnetURI() const
2605 QString ret = u"magnet:?"_s;
2607 const SHA1Hash infoHash1 = infoHash().v1();
2608 if (infoHash1.isValid())
2610 ret += u"xt=urn:btih:" + infoHash1.toString();
2613 const SHA256Hash infoHash2 = infoHash().v2();
2614 if (infoHash2.isValid())
2616 if (infoHash1.isValid())
2617 ret += u'&';
2618 ret += u"xt=urn:btmh:1220" + infoHash2.toString();
2621 const QString displayName = name();
2622 if (displayName != id().toString())
2624 ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
2627 for (const TrackerEntry &tracker : asConst(trackers()))
2629 ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
2632 for (const QUrl &urlSeed : asConst(urlSeeds()))
2634 ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
2637 return ret;
2640 nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
2642 if (!hasMetadata())
2643 return nonstd::make_unexpected(tr("Missing metadata"));
2647 #ifdef QBT_USES_LIBTORRENT2
2648 const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
2649 const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
2650 #else
2651 const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
2652 #endif
2653 lt::create_torrent creator {*torrentInfo};
2655 for (const TrackerEntry &entry : asConst(trackers()))
2656 creator.add_tracker(entry.url.toStdString(), entry.tier);
2658 return creator.generate();
2660 catch (const lt::system_error &err)
2662 return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
2666 nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
2668 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2669 if (!preparationResult)
2670 return preparationResult.get_unexpected();
2672 // usually torrent size should be smaller than 1 MB,
2673 // however there are >100 MB v2/hybrid torrent files out in the wild
2674 QByteArray buffer;
2675 buffer.reserve(1024 * 1024);
2676 lt::bencode(std::back_inserter(buffer), preparationResult.value());
2677 return buffer;
2680 nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) const
2682 const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
2683 if (!preparationResult)
2684 return preparationResult.get_unexpected();
2686 const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
2687 if (!saveResult)
2688 return saveResult.get_unexpected();
2690 return {};
2693 void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
2695 invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
2699 std::vector<lt::peer_info> nativePeers;
2700 nativeHandle.get_peer_info(nativePeers);
2701 QVector<PeerInfo> peers;
2702 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
2703 for (const lt::peer_info &peer : nativePeers)
2704 peers.append(PeerInfo(peer, allPieces));
2705 return peers;
2707 catch (const std::exception &) {}
2709 return {};
2711 , std::move(resultHandler));
2714 void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
2716 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
2720 const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
2721 QVector<QUrl> urlSeeds;
2722 urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
2723 for (const std::string &urlSeed : currentSeeds)
2724 urlSeeds.append(QString::fromStdString(urlSeed));
2725 return urlSeeds;
2727 catch (const std::exception &) {}
2729 return {};
2731 , std::move(resultHandler));
2734 void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
2736 invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
2740 std::vector<int> piecesAvailability;
2741 nativeHandle.piece_availability(piecesAvailability);
2742 return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
2744 catch (const std::exception &) {}
2746 return {};
2748 , std::move(resultHandler));
2751 void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
2753 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
2757 #ifdef QBT_USES_LIBTORRENT2
2758 const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
2759 #else
2760 std::vector<lt::partial_piece_info> queue;
2761 nativeHandle.get_download_queue(queue);
2762 #endif
2763 QBitArray result;
2764 result.resize(torrentInfo.piecesCount());
2765 for (const lt::partial_piece_info &info : queue)
2766 result.setBit(LT::toUnderlyingType(info.piece_index));
2767 return result;
2769 catch (const std::exception &) {}
2771 return {};
2773 , std::move(resultHandler));
2776 void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
2778 invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
2780 if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
2781 return {};
2785 std::vector<int> piecesAvailability;
2786 nativeHandle.piece_availability(piecesAvailability);
2787 const int filesCount = torrentInfo.filesCount();
2788 // libtorrent returns empty array for seeding only torrents
2789 if (piecesAvailability.empty())
2790 return QVector<qreal>(filesCount, -1);
2792 QVector<qreal> result;
2793 result.reserve(filesCount);
2794 for (int i = 0; i < filesCount; ++i)
2796 const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
2798 int availablePieces = 0;
2799 for (const int piece : filePieces)
2800 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2802 const qreal availability = filePieces.isEmpty()
2803 ? 1 // the file has no pieces, so it is available by default
2804 : static_cast<qreal>(availablePieces) / filePieces.size();
2805 result.append(availability);
2807 return result;
2809 catch (const std::exception &) {}
2811 return {};
2813 , std::move(resultHandler));
2816 void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
2818 if (!hasMetadata()) return;
2820 Q_ASSERT(priorities.size() == filesCount());
2822 // Reset 'm_hasSeedStatus' if needed in order to react again to
2823 // 'torrent_finished_alert' and eg show tray notifications
2824 const QVector<DownloadPriority> oldPriorities = filePriorities();
2825 for (int i = 0; i < oldPriorities.size(); ++i)
2827 if ((oldPriorities[i] == DownloadPriority::Ignored)
2828 && (priorities[i] > DownloadPriority::Ignored)
2829 && !m_completedFiles.at(i))
2831 m_hasFinishedStatus = false;
2832 break;
2836 const int internalFilesCount = m_torrentInfo.nativeInfo()->files().num_files(); // including .pad files
2837 auto nativePriorities = std::vector<lt::download_priority_t>(internalFilesCount, LT::toNative(DownloadPriority::Normal));
2838 const auto nativeIndexes = m_torrentInfo.nativeIndexes();
2839 for (int i = 0; i < priorities.size(); ++i)
2840 nativePriorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(priorities[i]);
2842 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
2843 m_nativeHandle.prioritize_files(nativePriorities);
2845 m_filePriorities = priorities;
2846 // Restore first/last piece first option if necessary
2847 if (m_hasFirstLastPiecePriority)
2848 applyFirstLastPiecePriority(true);
2851 QVector<qreal> TorrentImpl::availableFileFractions() const
2853 Q_ASSERT(hasMetadata());
2855 const int filesCount = this->filesCount();
2856 if (filesCount <= 0) return {};
2858 const QVector<int> piecesAvailability = pieceAvailability();
2859 // libtorrent returns empty array for seeding only torrents
2860 if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1);
2862 QVector<qreal> res;
2863 res.reserve(filesCount);
2864 for (int i = 0; i < filesCount; ++i)
2866 const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
2868 int availablePieces = 0;
2869 for (const int piece : filePieces)
2870 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2872 const qreal availability = filePieces.isEmpty()
2873 ? 1 // the file has no pieces, so it is available by default
2874 : static_cast<qreal>(availablePieces) / filePieces.size();
2875 res.push_back(availability);
2877 return res;
2880 template <typename Func, typename Callback>
2881 void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
2883 m_session->invokeAsync([session = m_session
2884 , func = std::move(func)
2885 , resultHandler = std::move(resultHandler)
2886 , thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
2888 session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
2890 if (thisTorrent)
2891 resultHandler(result);