2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "torrentimpl.h"
39 #include <libtorrent/address.hpp>
40 #include <libtorrent/alert_types.hpp>
41 #include <libtorrent/create_torrent.hpp>
42 #include <libtorrent/session.hpp>
43 #include <libtorrent/storage_defs.hpp>
44 #include <libtorrent/time.hpp>
46 #ifdef QBT_USES_LIBTORRENT2
47 #include <libtorrent/info_hash.hpp>
50 #include <QtSystemDetection>
55 #include <QStringList>
58 #include "base/exceptions.h"
59 #include "base/global.h"
60 #include "base/logger.h"
61 #include "base/preferences.h"
62 #include "base/types.h"
63 #include "base/utils/fs.h"
64 #include "base/utils/io.h"
66 #include "downloadpriority.h"
67 #include "extensiondata.h"
68 #include "loadtorrentparams.h"
69 #include "ltqbitarray.h"
70 #include "lttypecast.h"
71 #include "peeraddress.h"
73 #include "sessionimpl.h"
74 #include "trackerentry.h"
76 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
77 #include "base/utils/os.h"
78 #endif // Q_OS_MACOS || Q_OS_WIN
80 #ifndef QBT_USES_LIBTORRENT2
81 #include "customstorage.h"
84 using namespace BitTorrent
;
88 lt::announce_entry
makeNativeAnnounceEntry(const QString
&url
, const int tier
)
90 lt::announce_entry entry
{url
.toStdString()};
95 QDateTime
fromLTTimePoint32(const lt::time_point32
&timePoint
)
97 const auto ltNow
= lt::clock_type::now();
98 const auto qNow
= QDateTime::currentDateTime();
99 const auto secsSinceNow
= lt::duration_cast
<lt::seconds
>(timePoint
- ltNow
+ lt::milliseconds(500)).count();
101 return qNow
.addSecs(secsSinceNow
);
104 QString
toString(const lt::tcp::endpoint
<TCPEndpoint
)
106 return QString::fromStdString((std::stringstream() << ltTCPEndpoint
).str());
109 void updateTrackerEntryStatus(TrackerEntryStatus
&trackerEntryStatus
, const lt::announce_entry
&nativeEntry
110 , const QSet
<int> &btProtocols
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
112 Q_ASSERT(trackerEntryStatus
.url
== QString::fromStdString(nativeEntry
.url
));
114 trackerEntryStatus
.tier
= nativeEntry
.tier
;
116 // remove outdated endpoints
117 trackerEntryStatus
.endpoints
.removeIf([&nativeEntry
](const QHash
<std::pair
<QString
, int>, TrackerEndpointStatus
>::iterator
&iter
)
119 return std::none_of(nativeEntry
.endpoints
.cbegin(), nativeEntry
.endpoints
.cend()
120 , [&endpointName
= std::get
<0>(iter
.key())](const auto &existingEndpoint
)
122 return (endpointName
== toString(existingEndpoint
.local_endpoint
));
126 const auto numEndpoints
= static_cast<qsizetype
>(nativeEntry
.endpoints
.size()) * btProtocols
.size();
130 int numNotWorking
= 0;
131 int numTrackerError
= 0;
132 int numUnreachable
= 0;
134 for (const lt::announce_endpoint
<AnnounceEndpoint
: nativeEntry
.endpoints
)
136 const auto endpointName
= toString(ltAnnounceEndpoint
.local_endpoint
);
138 for (const auto protocolVersion
: btProtocols
)
140 #ifdef QBT_USES_LIBTORRENT2
141 Q_ASSERT((protocolVersion
== 1) || (protocolVersion
== 2));
142 const auto ltProtocolVersion
= (protocolVersion
== 1) ? lt::protocol_version::V1
: lt::protocol_version::V2
;
143 const lt::announce_infohash
<AnnounceInfo
= ltAnnounceEndpoint
.info_hashes
[ltProtocolVersion
];
145 Q_ASSERT(protocolVersion
== 1);
146 const lt::announce_endpoint
<AnnounceInfo
= ltAnnounceEndpoint
;
148 const QMap
<int, int> &endpointUpdateInfo
= updateInfo
[ltAnnounceEndpoint
.local_endpoint
];
149 TrackerEndpointStatus
&trackerEndpointStatus
= trackerEntryStatus
.endpoints
[std::make_pair(endpointName
, protocolVersion
)];
151 trackerEndpointStatus
.name
= endpointName
;
152 trackerEndpointStatus
.btVersion
= protocolVersion
;
153 trackerEndpointStatus
.numPeers
= endpointUpdateInfo
.value(protocolVersion
, trackerEndpointStatus
.numPeers
);
154 trackerEndpointStatus
.numSeeds
= ltAnnounceInfo
.scrape_complete
;
155 trackerEndpointStatus
.numLeeches
= ltAnnounceInfo
.scrape_incomplete
;
156 trackerEndpointStatus
.numDownloaded
= ltAnnounceInfo
.scrape_downloaded
;
157 trackerEndpointStatus
.nextAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.next_announce
);
158 trackerEndpointStatus
.minAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.min_announce
);
160 if (ltAnnounceInfo
.updating
)
162 trackerEndpointStatus
.state
= TrackerEndpointState::Updating
;
165 else if (ltAnnounceInfo
.fails
> 0)
167 if (ltAnnounceInfo
.last_error
== lt::errors::tracker_failure
)
169 trackerEndpointStatus
.state
= TrackerEndpointState::TrackerError
;
172 else if (ltAnnounceInfo
.last_error
== lt::errors::announce_skipped
)
174 trackerEndpointStatus
.state
= TrackerEndpointState::Unreachable
;
179 trackerEndpointStatus
.state
= TrackerEndpointState::NotWorking
;
183 else if (nativeEntry
.verified
)
185 trackerEndpointStatus
.state
= TrackerEndpointState::Working
;
190 trackerEndpointStatus
.state
= TrackerEndpointState::NotContacted
;
193 if (!ltAnnounceInfo
.message
.empty())
195 trackerEndpointStatus
.message
= QString::fromStdString(ltAnnounceInfo
.message
);
197 else if (ltAnnounceInfo
.last_error
)
199 trackerEndpointStatus
.message
= QString::fromLocal8Bit(ltAnnounceInfo
.last_error
.message());
203 trackerEndpointStatus
.message
.clear();
208 if (numEndpoints
> 0)
212 trackerEntryStatus
.state
= TrackerEndpointState::Updating
;
214 else if (numWorking
> 0)
216 trackerEntryStatus
.state
= TrackerEndpointState::Working
;
218 else if (numTrackerError
> 0)
220 trackerEntryStatus
.state
= TrackerEndpointState::TrackerError
;
222 else if (numUnreachable
== numEndpoints
)
224 trackerEntryStatus
.state
= TrackerEndpointState::Unreachable
;
226 else if ((numUnreachable
+ numNotWorking
) == numEndpoints
)
228 trackerEntryStatus
.state
= TrackerEndpointState::NotWorking
;
232 trackerEntryStatus
.numPeers
= -1;
233 trackerEntryStatus
.numSeeds
= -1;
234 trackerEntryStatus
.numLeeches
= -1;
235 trackerEntryStatus
.numDownloaded
= -1;
236 trackerEntryStatus
.nextAnnounceTime
= QDateTime();
237 trackerEntryStatus
.minAnnounceTime
= QDateTime();
238 trackerEntryStatus
.message
.clear();
240 for (const TrackerEndpointStatus
&endpointStatus
: asConst(trackerEntryStatus
.endpoints
))
242 trackerEntryStatus
.numPeers
= std::max(trackerEntryStatus
.numPeers
, endpointStatus
.numPeers
);
243 trackerEntryStatus
.numSeeds
= std::max(trackerEntryStatus
.numSeeds
, endpointStatus
.numSeeds
);
244 trackerEntryStatus
.numLeeches
= std::max(trackerEntryStatus
.numLeeches
, endpointStatus
.numLeeches
);
245 trackerEntryStatus
.numDownloaded
= std::max(trackerEntryStatus
.numDownloaded
, endpointStatus
.numDownloaded
);
247 if (endpointStatus
.state
== trackerEntryStatus
.state
)
249 if (!trackerEntryStatus
.nextAnnounceTime
.isValid() || (trackerEntryStatus
.nextAnnounceTime
> endpointStatus
.nextAnnounceTime
))
251 trackerEntryStatus
.nextAnnounceTime
= endpointStatus
.nextAnnounceTime
;
252 trackerEntryStatus
.minAnnounceTime
= endpointStatus
.minAnnounceTime
;
253 if ((endpointStatus
.state
!= TrackerEndpointState::Working
)
254 || !endpointStatus
.message
.isEmpty())
256 trackerEntryStatus
.message
= endpointStatus
.message
;
260 if (endpointStatus
.state
== TrackerEndpointState::Working
)
262 if (trackerEntryStatus
.message
.isEmpty())
263 trackerEntryStatus
.message
= endpointStatus
.message
;
269 template <typename Vector
>
270 Vector
resized(const Vector
&inVector
, const typename
Vector::size_type size
, const typename
Vector::value_type
&defaultValue
)
272 Vector outVector
= inVector
;
273 outVector
.resize(size
, defaultValue
);
277 // This is an imitation of limit normalization performed by libtorrent itself.
278 // We need perform it to keep cached values in line with the ones used by libtorrent.
279 int cleanLimitValue(const int value
)
281 return ((value
< 0) || (value
== std::numeric_limits
<int>::max())) ? 0 : value
;
287 TorrentImpl::TorrentImpl(SessionImpl
*session
, lt::session
*nativeSession
288 , const lt::torrent_handle
&nativeHandle
, const LoadTorrentParams
¶ms
)
291 , m_nativeSession(nativeSession
)
292 , m_nativeHandle(nativeHandle
)
293 #ifdef QBT_USES_LIBTORRENT2
294 , m_infoHash(m_nativeHandle
.info_hashes())
296 , m_infoHash(m_nativeHandle
.info_hash())
298 , m_name(params
.name
)
299 , m_savePath(params
.savePath
)
300 , m_downloadPath(params
.downloadPath
)
301 , m_category(params
.category
)
302 , m_tags(params
.tags
)
303 , m_ratioLimit(params
.ratioLimit
)
304 , m_seedingTimeLimit(params
.seedingTimeLimit
)
305 , m_inactiveSeedingTimeLimit(params
.inactiveSeedingTimeLimit
)
306 , m_shareLimitAction(params
.shareLimitAction
)
307 , m_operatingMode(params
.operatingMode
)
308 , m_contentLayout(params
.contentLayout
)
309 , m_hasFinishedStatus(params
.hasFinishedStatus
)
310 , m_hasFirstLastPiecePriority(params
.firstLastPiecePriority
)
311 , m_useAutoTMM(params
.useAutoTMM
)
312 , m_isStopped(params
.stopped
)
313 , m_sslParams(params
.sslParameters
)
314 , m_ltAddTorrentParams(params
.ltAddTorrentParams
)
315 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams
.download_limit
))
316 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams
.upload_limit
))
318 if (m_ltAddTorrentParams
.ti
)
320 // Initialize it only if torrent is added with metadata.
321 // Otherwise it should be initialized in "Metadata received" handler.
322 m_torrentInfo
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
324 Q_ASSERT(m_filePaths
.isEmpty());
325 Q_ASSERT(m_indexMap
.isEmpty());
326 const int filesCount
= m_torrentInfo
.filesCount();
327 m_filePaths
.reserve(filesCount
);
328 m_indexMap
.reserve(filesCount
);
329 m_filePriorities
.reserve(filesCount
);
330 const std::vector
<lt::download_priority_t
> filePriorities
=
331 resized(m_ltAddTorrentParams
.file_priorities
, m_ltAddTorrentParams
.ti
->num_files()
332 , LT::toNative(m_ltAddTorrentParams
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
334 m_completedFiles
.fill(static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
), filesCount
);
335 m_filesProgress
.resize(filesCount
);
337 for (int i
= 0; i
< filesCount
; ++i
)
339 const lt::file_index_t nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
340 m_indexMap
[nativeIndex
] = i
;
342 const auto fileIter
= m_ltAddTorrentParams
.renamed_files
.find(nativeIndex
);
343 const Path filePath
= ((fileIter
!= m_ltAddTorrentParams
.renamed_files
.end())
344 ? makeUserPath(Path(fileIter
->second
)) : m_torrentInfo
.filePath(i
));
345 m_filePaths
.append(filePath
);
347 const auto priority
= LT::fromNative(filePriorities
[LT::toUnderlyingType(nativeIndex
)]);
348 m_filePriorities
.append(priority
);
352 setStopCondition(params
.stopCondition
);
354 const auto *extensionData
= static_cast<ExtensionData
*>(m_ltAddTorrentParams
.userdata
);
355 m_trackerEntryStatuses
.reserve(static_cast<decltype(m_trackerEntryStatuses
)::size_type
>(extensionData
->trackers
.size()));
356 for (const lt::announce_entry
&announceEntry
: extensionData
->trackers
)
357 m_trackerEntryStatuses
.append({QString::fromStdString(announceEntry
.url
), announceEntry
.tier
});
358 m_urlSeeds
.reserve(static_cast<decltype(m_urlSeeds
)::size_type
>(extensionData
->urlSeeds
.size()));
359 for (const std::string
&urlSeed
: extensionData
->urlSeeds
)
360 m_urlSeeds
.append(QString::fromStdString(urlSeed
));
361 m_nativeStatus
= extensionData
->status
;
369 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority
);
372 TorrentImpl::~TorrentImpl() = default;
374 bool TorrentImpl::isValid() const
376 return m_nativeHandle
.is_valid();
379 Session
*TorrentImpl::session() const
384 InfoHash
TorrentImpl::infoHash() const
389 QString
TorrentImpl::name() const
391 if (!m_name
.isEmpty())
395 return m_torrentInfo
.name();
397 const QString name
= QString::fromStdString(m_nativeStatus
.name
);
401 return id().toString();
404 QDateTime
TorrentImpl::creationDate() const
409 const std::time_t date
= nativeTorrentInfo()->creation_date();
410 return ((date
!= 0) ? QDateTime::fromSecsSinceEpoch(date
) : QDateTime());
413 QString
TorrentImpl::creator() const
418 return QString::fromStdString(nativeTorrentInfo()->creator());
421 QString
TorrentImpl::comment() const
426 return QString::fromStdString(nativeTorrentInfo()->comment());
429 bool TorrentImpl::isPrivate() const
431 return m_torrentInfo
.isPrivate();
434 qlonglong
TorrentImpl::totalSize() const
436 return m_torrentInfo
.totalSize();
439 // size without the "don't download" files
440 qlonglong
TorrentImpl::wantedSize() const
442 return m_nativeStatus
.total_wanted
;
445 qlonglong
TorrentImpl::completedSize() const
447 return m_nativeStatus
.total_wanted_done
;
450 qlonglong
TorrentImpl::pieceLength() const
452 return m_torrentInfo
.pieceLength();
455 qlonglong
TorrentImpl::wastedSize() const
457 return (m_nativeStatus
.total_failed_bytes
+ m_nativeStatus
.total_redundant_bytes
);
460 QString
TorrentImpl::currentTracker() const
462 return QString::fromStdString(m_nativeStatus
.current_tracker
);
465 Path
TorrentImpl::savePath() const
467 return isAutoTMMEnabled() ? m_session
->categorySavePath(category()) : m_savePath
;
470 void TorrentImpl::setSavePath(const Path
&path
)
472 Q_ASSERT(!isAutoTMMEnabled());
473 if (isAutoTMMEnabled()) [[unlikely
]]
476 const Path basePath
= m_session
->useCategoryPathsInManualMode()
477 ? m_session
->categorySavePath(category()) : m_session
->savePath();
478 const Path resolvedPath
= (path
.isAbsolute() ? path
: (basePath
/ path
));
479 if (resolvedPath
== savePath())
482 if (isFinished() || m_hasFinishedStatus
|| downloadPath().isEmpty())
484 moveStorage(resolvedPath
, MoveStorageContext::ChangeSavePath
);
488 m_savePath
= resolvedPath
;
489 m_session
->handleTorrentSavePathChanged(this);
490 deferredRequestResumeData();
494 Path
TorrentImpl::downloadPath() const
496 return isAutoTMMEnabled() ? m_session
->categoryDownloadPath(category()) : m_downloadPath
;
499 void TorrentImpl::setDownloadPath(const Path
&path
)
501 Q_ASSERT(!isAutoTMMEnabled());
502 if (isAutoTMMEnabled()) [[unlikely
]]
505 const Path basePath
= m_session
->useCategoryPathsInManualMode()
506 ? m_session
->categoryDownloadPath(category()) : m_session
->downloadPath();
507 const Path resolvedPath
= (path
.isEmpty() || path
.isAbsolute()) ? path
: (basePath
/ path
);
508 if (resolvedPath
== m_downloadPath
)
511 const bool isIncomplete
= !(isFinished() || m_hasFinishedStatus
);
514 moveStorage((resolvedPath
.isEmpty() ? savePath() : resolvedPath
), MoveStorageContext::ChangeDownloadPath
);
518 m_downloadPath
= resolvedPath
;
519 m_session
->handleTorrentSavePathChanged(this);
520 deferredRequestResumeData();
524 Path
TorrentImpl::rootPath() const
529 const Path relativeRootPath
= Path::findRootFolder(filePaths());
530 if (relativeRootPath
.isEmpty())
533 return (actualStorageLocation() / relativeRootPath
);
536 Path
TorrentImpl::contentPath() const
541 if (filesCount() == 1)
542 return (actualStorageLocation() / filePath(0));
544 const Path rootPath
= this->rootPath();
545 return (rootPath
.isEmpty() ? actualStorageLocation() : rootPath
);
548 bool TorrentImpl::isAutoTMMEnabled() const
553 void TorrentImpl::setAutoTMMEnabled(bool enabled
)
555 if (m_useAutoTMM
== enabled
)
558 m_useAutoTMM
= enabled
;
561 m_savePath
= m_session
->categorySavePath(category());
562 m_downloadPath
= m_session
->categoryDownloadPath(category());
565 deferredRequestResumeData();
566 m_session
->handleTorrentSavingModeChanged(this);
568 adjustStorageLocation();
571 Path
TorrentImpl::actualStorageLocation() const
576 return Path(m_nativeStatus
.save_path
);
579 void TorrentImpl::setAutoManaged(const bool enable
)
582 m_nativeHandle
.set_flags(lt::torrent_flags::auto_managed
);
584 m_nativeHandle
.unset_flags(lt::torrent_flags::auto_managed
);
587 Path
TorrentImpl::makeActualPath(int index
, const Path
&path
) const
589 Path actualPath
= path
;
591 if (m_session
->isAppendExtensionEnabled()
592 && (fileSize(index
) > 0) && !m_completedFiles
.at(index
))
594 actualPath
+= QB_EXT
;
597 if (m_session
->isUnwantedFolderEnabled()
598 && (m_filePriorities
[index
] == DownloadPriority::Ignored
))
600 const Path parentPath
= actualPath
.parentPath();
601 const QString fileName
= actualPath
.filename();
602 actualPath
= parentPath
/ Path(UNWANTED_FOLDER_NAME
) / Path(fileName
);
608 Path
TorrentImpl::makeUserPath(const Path
&path
) const
610 Path userPath
= path
.removedExtension(QB_EXT
);
612 const Path parentRelPath
= userPath
.parentPath();
613 if (parentRelPath
.filename() == UNWANTED_FOLDER_NAME
)
615 const QString fileName
= userPath
.filename();
616 const Path relPath
= parentRelPath
.parentPath();
617 userPath
= relPath
/ Path(fileName
);
623 QList
<TrackerEntryStatus
> TorrentImpl::trackers() const
625 return m_trackerEntryStatuses
;
628 void TorrentImpl::addTrackers(QList
<TrackerEntry
> trackers
)
630 trackers
.removeIf([](const TrackerEntry
&trackerEntry
) { return trackerEntry
.url
.isEmpty(); });
632 QSet
<TrackerEntry
> currentTrackerSet
;
633 currentTrackerSet
.reserve(m_trackerEntryStatuses
.size());
634 for (const TrackerEntryStatus
&status
: asConst(m_trackerEntryStatuses
))
635 currentTrackerSet
.insert({.url
= status
.url
, .tier
= status
.tier
});
637 const auto newTrackerSet
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend()) - currentTrackerSet
;
638 if (newTrackerSet
.isEmpty())
641 trackers
= QList
<TrackerEntry
>(newTrackerSet
.cbegin(), newTrackerSet
.cend());
642 for (const TrackerEntry
&tracker
: asConst(trackers
))
644 m_nativeHandle
.add_tracker(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
645 m_trackerEntryStatuses
.append({tracker
.url
, tracker
.tier
});
647 std::sort(m_trackerEntryStatuses
.begin(), m_trackerEntryStatuses
.end()
648 , [](const TrackerEntryStatus
&left
, const TrackerEntryStatus
&right
) { return left
.tier
< right
.tier
; });
650 deferredRequestResumeData();
651 m_session
->handleTorrentTrackersAdded(this, trackers
);
654 void TorrentImpl::removeTrackers(const QStringList
&trackers
)
656 QStringList removedTrackers
= trackers
;
657 for (const QString
&tracker
: trackers
)
659 if (!m_trackerEntryStatuses
.removeOne({tracker
}))
660 removedTrackers
.removeOne(tracker
);
663 std::vector
<lt::announce_entry
> nativeTrackers
;
664 nativeTrackers
.reserve(m_trackerEntryStatuses
.size());
665 for (const TrackerEntryStatus
&tracker
: asConst(m_trackerEntryStatuses
))
666 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
668 if (!removedTrackers
.isEmpty())
670 m_nativeHandle
.replace_trackers(nativeTrackers
);
672 deferredRequestResumeData();
673 m_session
->handleTorrentTrackersRemoved(this, removedTrackers
);
677 void TorrentImpl::replaceTrackers(QList
<TrackerEntry
> trackers
)
679 trackers
.removeIf([](const TrackerEntry
&trackerEntry
) { return trackerEntry
.url
.isEmpty(); });
681 // Filter out duplicate trackers
682 const auto uniqueTrackers
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend());
683 trackers
= QList
<TrackerEntry
>(uniqueTrackers
.cbegin(), uniqueTrackers
.cend());
684 std::sort(trackers
.begin(), trackers
.end()
685 , [](const TrackerEntry
&left
, const TrackerEntry
&right
) { return left
.tier
< right
.tier
; });
687 std::vector
<lt::announce_entry
> nativeTrackers
;
688 nativeTrackers
.reserve(trackers
.size());
689 m_trackerEntryStatuses
.clear();
691 for (const TrackerEntry
&tracker
: trackers
)
693 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
694 m_trackerEntryStatuses
.append({tracker
.url
, tracker
.tier
});
697 m_nativeHandle
.replace_trackers(nativeTrackers
);
699 // Clear the peer list if it's a private torrent since
700 // we do not want to keep connecting with peers from old tracker.
704 deferredRequestResumeData();
705 m_session
->handleTorrentTrackersChanged(this);
708 QList
<QUrl
> TorrentImpl::urlSeeds() const
713 void TorrentImpl::addUrlSeeds(const QList
<QUrl
> &urlSeeds
)
715 m_session
->invokeAsync([urlSeeds
, session
= m_session
716 , nativeHandle
= m_nativeHandle
717 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
721 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
722 QList
<QUrl
> currentSeeds
;
723 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
724 for (const std::string
&urlSeed
: nativeSeeds
)
725 currentSeeds
.append(QString::fromStdString(urlSeed
));
727 QList
<QUrl
> addedUrlSeeds
;
728 addedUrlSeeds
.reserve(urlSeeds
.size());
730 for (const QUrl
&url
: urlSeeds
)
732 if (!currentSeeds
.contains(url
))
734 nativeHandle
.add_url_seed(url
.toString().toStdString());
735 addedUrlSeeds
.append(url
);
739 currentSeeds
.append(addedUrlSeeds
);
740 session
->invoke([session
, thisTorrent
, currentSeeds
, addedUrlSeeds
]
745 thisTorrent
->m_urlSeeds
= currentSeeds
;
746 if (!addedUrlSeeds
.isEmpty())
748 thisTorrent
->deferredRequestResumeData();
749 session
->handleTorrentUrlSeedsAdded(thisTorrent
, addedUrlSeeds
);
753 catch (const std::exception
&) {}
757 void TorrentImpl::removeUrlSeeds(const QList
<QUrl
> &urlSeeds
)
759 m_session
->invokeAsync([urlSeeds
, session
= m_session
760 , nativeHandle
= m_nativeHandle
761 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
765 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
766 QList
<QUrl
> currentSeeds
;
767 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
768 for (const std::string
&urlSeed
: nativeSeeds
)
769 currentSeeds
.append(QString::fromStdString(urlSeed
));
771 QList
<QUrl
> removedUrlSeeds
;
772 removedUrlSeeds
.reserve(urlSeeds
.size());
774 for (const QUrl
&url
: urlSeeds
)
776 if (currentSeeds
.removeOne(url
))
778 nativeHandle
.remove_url_seed(url
.toString().toStdString());
779 removedUrlSeeds
.append(url
);
783 session
->invoke([session
, thisTorrent
, currentSeeds
, removedUrlSeeds
]
788 thisTorrent
->m_urlSeeds
= currentSeeds
;
790 if (!removedUrlSeeds
.isEmpty())
792 thisTorrent
->deferredRequestResumeData();
793 session
->handleTorrentUrlSeedsRemoved(thisTorrent
, removedUrlSeeds
);
797 catch (const std::exception
&) {}
801 void TorrentImpl::clearPeers()
803 m_nativeHandle
.clear_peers();
806 bool TorrentImpl::connectPeer(const PeerAddress
&peerAddress
)
809 const lt::address addr
= lt::make_address(peerAddress
.ip
.toString().toStdString(), ec
);
810 if (ec
) return false;
812 const lt::tcp::endpoint
endpoint(addr
, peerAddress
.port
);
815 m_nativeHandle
.connect_peer(endpoint
);
817 catch (const lt::system_error
&err
)
819 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
820 .arg(peerAddress
.toString(), name(), QString::fromLocal8Bit(err
.what())), Log::WARNING
);
824 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress
.toString(), name()));
828 bool TorrentImpl::needSaveResumeData() const
830 return m_nativeStatus
.need_save_resume
;
833 void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags
)
835 m_nativeHandle
.save_resume_data(flags
);
836 m_deferredRequestResumeDataInvoked
= false;
838 m_session
->handleTorrentResumeDataRequested(this);
841 void TorrentImpl::deferredRequestResumeData()
843 if (!m_deferredRequestResumeDataInvoked
)
845 QMetaObject::invokeMethod(this, [this]
847 requestResumeData((m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
848 ? lt::torrent_handle::save_info_dict
: lt::resume_data_flags_t());
849 }, Qt::QueuedConnection
);
851 m_deferredRequestResumeDataInvoked
= true;
855 int TorrentImpl::filesCount() const
857 return m_torrentInfo
.filesCount();
860 int TorrentImpl::piecesCount() const
862 return m_torrentInfo
.piecesCount();
865 int TorrentImpl::piecesHave() const
867 return m_nativeStatus
.num_pieces
;
870 qreal
TorrentImpl::progress() const
873 return m_nativeStatus
.progress
;
875 if (m_nativeStatus
.total_wanted
== 0)
878 if (m_nativeStatus
.total_wanted_done
== m_nativeStatus
.total_wanted
)
881 const qreal progress
= static_cast<qreal
>(m_nativeStatus
.total_wanted_done
) / m_nativeStatus
.total_wanted
;
882 if ((progress
< 0.f
) || (progress
> 1.f
))
884 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
885 .arg(name(), QString::number(m_nativeStatus
.total_wanted
), QString::number(m_nativeStatus
.total_wanted_done
))
892 QString
TorrentImpl::category() const
897 bool TorrentImpl::belongsToCategory(const QString
&category
) const
899 if (m_category
.isEmpty())
900 return category
.isEmpty();
902 if (m_category
== category
)
905 return (m_session
->isSubcategoriesEnabled() && m_category
.startsWith(category
+ u
'/'));
908 TagSet
TorrentImpl::tags() const
913 bool TorrentImpl::hasTag(const Tag
&tag
) const
915 return m_tags
.contains(tag
);
918 bool TorrentImpl::addTag(const Tag
&tag
)
925 if (!m_session
->hasTag(tag
))
927 if (!m_session
->addTag(tag
))
931 deferredRequestResumeData();
932 m_session
->handleTorrentTagAdded(this, tag
);
936 bool TorrentImpl::removeTag(const Tag
&tag
)
938 if (m_tags
.remove(tag
))
940 deferredRequestResumeData();
941 m_session
->handleTorrentTagRemoved(this, tag
);
947 void TorrentImpl::removeAllTags()
949 for (const Tag
&tag
: asConst(tags()))
953 QDateTime
TorrentImpl::addedTime() const
955 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.added_time
);
958 qreal
TorrentImpl::ratioLimit() const
963 int TorrentImpl::seedingTimeLimit() const
965 return m_seedingTimeLimit
;
968 int TorrentImpl::inactiveSeedingTimeLimit() const
970 return m_inactiveSeedingTimeLimit
;
973 Path
TorrentImpl::filePath(const int index
) const
975 Q_ASSERT(index
>= 0);
976 Q_ASSERT(index
< m_filePaths
.size());
978 return m_filePaths
.value(index
, {});
981 Path
TorrentImpl::actualFilePath(const int index
) const
983 const QList
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
985 Q_ASSERT(index
>= 0);
986 Q_ASSERT(index
< nativeIndexes
.size());
987 if ((index
< 0) || (index
>= nativeIndexes
.size()))
990 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes
[index
]));
993 qlonglong
TorrentImpl::fileSize(const int index
) const
995 return m_torrentInfo
.fileSize(index
);
998 PathList
TorrentImpl::filePaths() const
1003 PathList
TorrentImpl::actualFilePaths() const
1009 paths
.reserve(filesCount());
1011 const lt::file_storage files
= nativeTorrentInfo()->files();
1012 for (const lt::file_index_t
&nativeIndex
: asConst(m_torrentInfo
.nativeIndexes()))
1013 paths
.emplaceBack(files
.file_path(nativeIndex
));
1018 QList
<DownloadPriority
> TorrentImpl::filePriorities() const
1020 return m_filePriorities
;
1023 TorrentInfo
TorrentImpl::info() const
1025 return m_torrentInfo
;
1028 bool TorrentImpl::isStopped() const
1033 bool TorrentImpl::isQueued() const
1035 if (!m_session
->isQueueingSystemEnabled())
1038 // Torrent is Queued if it isn't in Stopped state but paused internally
1039 return (!isStopped()
1040 && (m_nativeStatus
.flags
& lt::torrent_flags::auto_managed
)
1041 && (m_nativeStatus
.flags
& lt::torrent_flags::paused
));
1044 bool TorrentImpl::isChecking() const
1046 return ((m_nativeStatus
.state
== lt::torrent_status::checking_files
)
1047 || (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
));
1050 bool TorrentImpl::isDownloading() const
1054 case TorrentState::Downloading
:
1055 case TorrentState::DownloadingMetadata
:
1056 case TorrentState::ForcedDownloadingMetadata
:
1057 case TorrentState::StalledDownloading
:
1058 case TorrentState::CheckingDownloading
:
1059 case TorrentState::StoppedDownloading
:
1060 case TorrentState::QueuedDownloading
:
1061 case TorrentState::ForcedDownloading
:
1070 bool TorrentImpl::isMoving() const
1072 return m_state
== TorrentState::Moving
;
1075 bool TorrentImpl::isUploading() const
1079 case TorrentState::Uploading
:
1080 case TorrentState::StalledUploading
:
1081 case TorrentState::CheckingUploading
:
1082 case TorrentState::QueuedUploading
:
1083 case TorrentState::ForcedUploading
:
1092 bool TorrentImpl::isCompleted() const
1096 case TorrentState::Uploading
:
1097 case TorrentState::StalledUploading
:
1098 case TorrentState::CheckingUploading
:
1099 case TorrentState::StoppedUploading
:
1100 case TorrentState::QueuedUploading
:
1101 case TorrentState::ForcedUploading
:
1110 bool TorrentImpl::isActive() const
1114 case TorrentState::StalledDownloading
:
1115 return (uploadPayloadRate() > 0);
1117 case TorrentState::DownloadingMetadata
:
1118 case TorrentState::ForcedDownloadingMetadata
:
1119 case TorrentState::Downloading
:
1120 case TorrentState::ForcedDownloading
:
1121 case TorrentState::Uploading
:
1122 case TorrentState::ForcedUploading
:
1123 case TorrentState::Moving
:
1133 bool TorrentImpl::isInactive() const
1138 bool TorrentImpl::isErrored() const
1140 return ((m_state
== TorrentState::MissingFiles
)
1141 || (m_state
== TorrentState::Error
));
1144 bool TorrentImpl::isFinished() const
1146 return ((m_nativeStatus
.state
== lt::torrent_status::finished
)
1147 || (m_nativeStatus
.state
== lt::torrent_status::seeding
));
1150 bool TorrentImpl::isForced() const
1152 return (!isStopped() && (m_operatingMode
== TorrentOperatingMode::Forced
));
1155 bool TorrentImpl::isSequentialDownload() const
1157 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::sequential_download
);
1160 bool TorrentImpl::hasFirstLastPiecePriority() const
1162 return m_hasFirstLastPiecePriority
;
1165 TorrentState
TorrentImpl::state() const
1170 void TorrentImpl::updateState()
1172 if (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
)
1174 m_state
= TorrentState::CheckingResumeData
;
1176 else if (isMoveInProgress())
1178 m_state
= TorrentState::Moving
;
1180 else if (hasMissingFiles())
1182 m_state
= TorrentState::MissingFiles
;
1184 else if (hasError())
1186 m_state
= TorrentState::Error
;
1188 else if (!hasMetadata())
1191 m_state
= TorrentState::StoppedDownloading
;
1192 else if (isQueued())
1193 m_state
= TorrentState::QueuedDownloading
;
1195 m_state
= isForced() ? TorrentState::ForcedDownloadingMetadata
: TorrentState::DownloadingMetadata
;
1197 else if ((m_nativeStatus
.state
== lt::torrent_status::checking_files
) && !isStopped())
1199 // If the torrent is not just in the "checking" state, but is being actually checked
1200 m_state
= m_hasFinishedStatus
? TorrentState::CheckingUploading
: TorrentState::CheckingDownloading
;
1202 else if (isFinished())
1205 m_state
= TorrentState::StoppedUploading
;
1206 else if (isQueued())
1207 m_state
= TorrentState::QueuedUploading
;
1208 else if (isForced())
1209 m_state
= TorrentState::ForcedUploading
;
1210 else if (m_nativeStatus
.upload_payload_rate
> 0)
1211 m_state
= TorrentState::Uploading
;
1213 m_state
= TorrentState::StalledUploading
;
1218 m_state
= TorrentState::StoppedDownloading
;
1219 else if (isQueued())
1220 m_state
= TorrentState::QueuedDownloading
;
1221 else if (isForced())
1222 m_state
= TorrentState::ForcedDownloading
;
1223 else if (m_nativeStatus
.download_payload_rate
> 0)
1224 m_state
= TorrentState::Downloading
;
1226 m_state
= TorrentState::StalledDownloading
;
1230 bool TorrentImpl::hasMetadata() const
1232 return m_torrentInfo
.isValid();
1235 bool TorrentImpl::hasMissingFiles() const
1237 return m_hasMissingFiles
;
1240 bool TorrentImpl::hasError() const
1242 return (m_nativeStatus
.errc
|| (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
));
1245 int TorrentImpl::queuePosition() const
1247 return static_cast<int>(m_nativeStatus
.queue_position
);
1250 QString
TorrentImpl::error() const
1252 if (m_nativeStatus
.errc
)
1253 return QString::fromLocal8Bit(m_nativeStatus
.errc
.message().c_str());
1255 if (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
)
1257 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1258 .arg(QString::fromLocal8Bit(m_lastFileError
.error
.message().c_str()));
1264 qlonglong
TorrentImpl::totalDownload() const
1266 return m_nativeStatus
.all_time_download
;
1269 qlonglong
TorrentImpl::totalUpload() const
1271 return m_nativeStatus
.all_time_upload
;
1274 qlonglong
TorrentImpl::activeTime() const
1276 return lt::total_seconds(m_nativeStatus
.active_duration
);
1279 qlonglong
TorrentImpl::finishedTime() const
1281 return lt::total_seconds(m_nativeStatus
.finished_duration
);
1284 qlonglong
TorrentImpl::eta() const
1286 if (isStopped()) return MAX_ETA
;
1288 const SpeedSampleAvg speedAverage
= m_payloadRateMonitor
.average();
1292 const qreal maxRatioValue
= maxRatio();
1293 const int maxSeedingTimeValue
= maxSeedingTime();
1294 const int maxInactiveSeedingTimeValue
= maxInactiveSeedingTime();
1295 if ((maxRatioValue
< 0) && (maxSeedingTimeValue
< 0) && (maxInactiveSeedingTimeValue
< 0)) return MAX_ETA
;
1297 qlonglong ratioEta
= MAX_ETA
;
1299 if ((speedAverage
.upload
> 0) && (maxRatioValue
>= 0))
1302 qlonglong realDL
= totalDownload();
1304 realDL
= wantedSize();
1306 ratioEta
= ((realDL
* maxRatioValue
) - totalUpload()) / speedAverage
.upload
;
1309 qlonglong seedingTimeEta
= MAX_ETA
;
1311 if (maxSeedingTimeValue
>= 0)
1313 seedingTimeEta
= (maxSeedingTimeValue
* 60) - finishedTime();
1314 if (seedingTimeEta
< 0)
1318 qlonglong inactiveSeedingTimeEta
= MAX_ETA
;
1320 if (maxInactiveSeedingTimeValue
>= 0)
1322 inactiveSeedingTimeEta
= (maxInactiveSeedingTimeValue
* 60) - timeSinceActivity();
1323 inactiveSeedingTimeEta
= std::max
<qlonglong
>(inactiveSeedingTimeEta
, 0);
1326 return std::min({ratioEta
, seedingTimeEta
, inactiveSeedingTimeEta
});
1329 if (!speedAverage
.download
) return MAX_ETA
;
1331 return (wantedSize() - completedSize()) / speedAverage
.download
;
1334 QList
<qreal
> TorrentImpl::filesProgress() const
1339 const int count
= m_filesProgress
.size();
1340 Q_ASSERT(count
== filesCount());
1341 if (count
!= filesCount()) [[unlikely
]]
1344 if (m_completedFiles
.count(true) == count
)
1345 return QList
<qreal
>(count
, 1);
1347 QList
<qreal
> result
;
1348 result
.reserve(count
);
1349 for (int i
= 0; i
< count
; ++i
)
1351 const int64_t progress
= m_filesProgress
.at(i
);
1352 const int64_t size
= fileSize(i
);
1353 if ((size
<= 0) || (progress
== size
))
1356 result
<< (progress
/ static_cast<qreal
>(size
));
1362 int TorrentImpl::seedsCount() const
1364 return m_nativeStatus
.num_seeds
;
1367 int TorrentImpl::peersCount() const
1369 return m_nativeStatus
.num_peers
;
1372 int TorrentImpl::leechsCount() const
1374 return (m_nativeStatus
.num_peers
- m_nativeStatus
.num_seeds
);
1377 int TorrentImpl::totalSeedsCount() const
1379 return (m_nativeStatus
.num_complete
> -1) ? m_nativeStatus
.num_complete
: m_nativeStatus
.list_seeds
;
1382 int TorrentImpl::totalPeersCount() const
1384 const int peers
= m_nativeStatus
.num_complete
+ m_nativeStatus
.num_incomplete
;
1385 return (peers
> -1) ? peers
: m_nativeStatus
.list_peers
;
1388 int TorrentImpl::totalLeechersCount() const
1390 return (m_nativeStatus
.num_incomplete
> -1) ? m_nativeStatus
.num_incomplete
: (m_nativeStatus
.list_peers
- m_nativeStatus
.list_seeds
);
1393 QDateTime
TorrentImpl::lastSeenComplete() const
1395 if (m_nativeStatus
.last_seen_complete
> 0)
1396 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.last_seen_complete
);
1401 QDateTime
TorrentImpl::completedTime() const
1403 if (m_nativeStatus
.completed_time
> 0)
1404 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.completed_time
);
1409 qlonglong
TorrentImpl::timeSinceUpload() const
1411 if (m_nativeStatus
.last_upload
.time_since_epoch().count() == 0)
1413 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_upload
);
1416 qlonglong
TorrentImpl::timeSinceDownload() const
1418 if (m_nativeStatus
.last_download
.time_since_epoch().count() == 0)
1420 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_download
);
1423 qlonglong
TorrentImpl::timeSinceActivity() const
1425 const qlonglong upTime
= timeSinceUpload();
1426 const qlonglong downTime
= timeSinceDownload();
1427 return ((upTime
< 0) != (downTime
< 0))
1428 ? std::max(upTime
, downTime
)
1429 : std::min(upTime
, downTime
);
1432 int TorrentImpl::downloadLimit() const
1434 return m_downloadLimit
;;
1437 int TorrentImpl::uploadLimit() const
1439 return m_uploadLimit
;
1442 bool TorrentImpl::superSeeding() const
1444 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::super_seeding
);
1447 bool TorrentImpl::isDHTDisabled() const
1449 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_dht
);
1452 bool TorrentImpl::isPEXDisabled() const
1454 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_pex
);
1457 bool TorrentImpl::isLSDDisabled() const
1459 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_lsd
);
1462 QList
<PeerInfo
> TorrentImpl::peers() const
1464 std::vector
<lt::peer_info
> nativePeers
;
1465 m_nativeHandle
.get_peer_info(nativePeers
);
1467 QList
<PeerInfo
> peers
;
1468 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
1470 for (const lt::peer_info
&peer
: nativePeers
)
1471 peers
.append(PeerInfo(peer
, pieces()));
1476 QBitArray
TorrentImpl::pieces() const
1481 QBitArray
TorrentImpl::downloadingPieces() const
1486 std::vector
<lt::partial_piece_info
> queue
;
1487 m_nativeHandle
.get_download_queue(queue
);
1489 QBitArray result
{piecesCount()};
1490 for (const lt::partial_piece_info
&info
: queue
)
1491 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
1496 QList
<int> TorrentImpl::pieceAvailability() const
1498 std::vector
<int> avail
;
1499 m_nativeHandle
.piece_availability(avail
);
1501 return {avail
.cbegin(), avail
.cend()};
1504 qreal
TorrentImpl::distributedCopies() const
1506 return m_nativeStatus
.distributed_copies
;
1509 qreal
TorrentImpl::maxRatio() const
1511 if (m_ratioLimit
== USE_GLOBAL_RATIO
)
1512 return m_session
->globalMaxRatio();
1514 return m_ratioLimit
;
1517 int TorrentImpl::maxSeedingTime() const
1519 if (m_seedingTimeLimit
== USE_GLOBAL_SEEDING_TIME
)
1520 return m_session
->globalMaxSeedingMinutes();
1522 return m_seedingTimeLimit
;
1525 int TorrentImpl::maxInactiveSeedingTime() const
1527 if (m_inactiveSeedingTimeLimit
== USE_GLOBAL_INACTIVE_SEEDING_TIME
)
1528 return m_session
->globalMaxInactiveSeedingMinutes();
1530 return m_inactiveSeedingTimeLimit
;
1533 qreal
TorrentImpl::realRatio() const
1535 const int64_t upload
= m_nativeStatus
.all_time_upload
;
1536 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1537 const int64_t download
= (m_nativeStatus
.all_time_download
< (m_nativeStatus
.total_done
* 0.01))
1538 ? m_nativeStatus
.total_done
1539 : m_nativeStatus
.all_time_download
;
1542 return (upload
== 0) ? 0 : MAX_RATIO
;
1544 const qreal ratio
= upload
/ static_cast<qreal
>(download
);
1545 Q_ASSERT(ratio
>= 0);
1546 return (ratio
> MAX_RATIO
) ? MAX_RATIO
: ratio
;
1549 int TorrentImpl::uploadPayloadRate() const
1551 // workaround: suppress the speed for Stopped state
1552 return isStopped() ? 0 : m_nativeStatus
.upload_payload_rate
;
1555 int TorrentImpl::downloadPayloadRate() const
1557 // workaround: suppress the speed for Stopped state
1558 return isStopped() ? 0 : m_nativeStatus
.download_payload_rate
;
1561 qlonglong
TorrentImpl::totalPayloadUpload() const
1563 return m_nativeStatus
.total_payload_upload
;
1566 qlonglong
TorrentImpl::totalPayloadDownload() const
1568 return m_nativeStatus
.total_payload_download
;
1571 int TorrentImpl::connectionsCount() const
1573 return m_nativeStatus
.num_connections
;
1576 int TorrentImpl::connectionsLimit() const
1578 return m_nativeStatus
.connections_limit
;
1581 qlonglong
TorrentImpl::nextAnnounce() const
1583 return lt::total_seconds(m_nativeStatus
.next_announce
);
1586 qreal
TorrentImpl::popularity() const
1588 // in order to produce floating-point numbers using `std::chrono::duration_cast`,
1589 // we should use `qreal` as `Rep` to define the `months` duration
1590 using months
= std::chrono::duration
<qreal
, std::chrono::months::period
>;
1591 const auto activeMonths
= std::chrono::duration_cast
<months
>(m_nativeStatus
.active_duration
).count();
1592 return (activeMonths
> 0) ? (realRatio() / activeMonths
) : 0;
1595 void TorrentImpl::setName(const QString
&name
)
1600 deferredRequestResumeData();
1601 m_session
->handleTorrentNameChanged(this);
1605 bool TorrentImpl::setCategory(const QString
&category
)
1607 if (m_category
!= category
)
1609 if (!category
.isEmpty() && !m_session
->categories().contains(category
))
1612 const QString oldCategory
= m_category
;
1613 m_category
= category
;
1614 deferredRequestResumeData();
1615 m_session
->handleTorrentCategoryChanged(this, oldCategory
);
1619 if (!m_session
->isDisableAutoTMMWhenCategoryChanged())
1620 adjustStorageLocation();
1622 setAutoTMMEnabled(false);
1629 void TorrentImpl::forceReannounce(const int index
)
1631 m_nativeHandle
.force_reannounce(0, index
);
1634 void TorrentImpl::forceDHTAnnounce()
1636 m_nativeHandle
.force_dht_announce();
1639 void TorrentImpl::forceRecheck()
1644 m_nativeHandle
.force_recheck();
1645 // We have to force update the cached state, otherwise someone will be able to get
1646 // an incorrect one during the interval until the cached state is updated in a regular way.
1647 m_nativeStatus
.state
= lt::torrent_status::checking_resume_data
;
1649 if (m_hasMissingFiles
)
1651 m_hasMissingFiles
= false;
1654 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1655 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1656 m_nativeHandle
.resume();
1660 m_unchecked
= false;
1662 m_completedFiles
.fill(false);
1663 m_filesProgress
.fill(0);
1664 m_pieces
.fill(false);
1665 m_nativeStatus
.pieces
.clear_all();
1666 m_nativeStatus
.num_pieces
= 0;
1670 // When "force recheck" is applied on Stopped torrent, we start them to perform checking
1672 m_stopCondition
= StopCondition::FilesChecked
;
1676 void TorrentImpl::setSequentialDownload(const bool enable
)
1680 m_nativeHandle
.set_flags(lt::torrent_flags::sequential_download
);
1681 m_nativeStatus
.flags
|= lt::torrent_flags::sequential_download
; // prevent return cached value
1685 m_nativeHandle
.unset_flags(lt::torrent_flags::sequential_download
);
1686 m_nativeStatus
.flags
&= ~lt::torrent_flags::sequential_download
; // prevent return cached value
1689 deferredRequestResumeData();
1692 void TorrentImpl::setFirstLastPiecePriority(const bool enabled
)
1694 if (m_hasFirstLastPiecePriority
== enabled
)
1697 m_hasFirstLastPiecePriority
= enabled
;
1699 applyFirstLastPiecePriority(enabled
);
1701 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1702 .arg((enabled
? tr("On") : tr("Off")), name()));
1704 deferredRequestResumeData();
1707 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled
)
1709 Q_ASSERT(hasMetadata());
1711 // Download first and last pieces first for every file in the torrent
1713 auto piecePriorities
= std::vector
<lt::download_priority_t
>(m_torrentInfo
.piecesCount(), LT::toNative(DownloadPriority::Ignored
));
1715 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1716 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1717 for (int fileIndex
= 0; fileIndex
< m_filePriorities
.size(); ++fileIndex
)
1719 const DownloadPriority filePrio
= m_filePriorities
[fileIndex
];
1720 if (filePrio
<= DownloadPriority::Ignored
)
1723 // Determine the priority to set
1724 const lt::download_priority_t piecePrio
= LT::toNative(enabled
? DownloadPriority::Maximum
: filePrio
);
1725 const TorrentInfo::PieceRange pieceRange
= m_torrentInfo
.filePieces(fileIndex
);
1727 // worst case: AVI index = 1% of total file size (at the end of the file)
1728 const int numPieces
= std::ceil(fileSize(fileIndex
) * 0.01 / pieceLength());
1729 for (int i
= 0; i
< numPieces
; ++i
)
1731 piecePriorities
[pieceRange
.first() + i
] = piecePrio
;
1732 piecePriorities
[pieceRange
.last() - i
] = piecePrio
;
1735 const int firstPiece
= pieceRange
.first() + numPieces
;
1736 const int lastPiece
= pieceRange
.last() - numPieces
;
1737 for (int pieceIndex
= firstPiece
; pieceIndex
<= lastPiece
; ++pieceIndex
)
1738 piecePriorities
[pieceIndex
] = LT::toNative(filePrio
);
1741 m_nativeHandle
.prioritize_pieces(piecePriorities
);
1744 void TorrentImpl::fileSearchFinished(const Path
&savePath
, const PathList
&fileNames
)
1746 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
1747 endReceivedMetadataHandling(savePath
, fileNames
);
1750 TrackerEntryStatus
TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry
&announceEntry
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
1752 const auto it
= std::find_if(m_trackerEntryStatuses
.begin(), m_trackerEntryStatuses
.end()
1753 , [&announceEntry
](const TrackerEntryStatus
&trackerEntryStatus
)
1755 return (trackerEntryStatus
.url
== QString::fromStdString(announceEntry
.url
));
1758 Q_ASSERT(it
!= m_trackerEntryStatuses
.end());
1759 if (it
== m_trackerEntryStatuses
.end()) [[unlikely
]]
1762 #ifdef QBT_USES_LIBTORRENT2
1763 QSet
<int> btProtocols
;
1764 const auto &infoHashes
= nativeHandle().info_hashes();
1765 if (infoHashes
.has(lt::protocol_version::V1
))
1766 btProtocols
.insert(1);
1767 if (infoHashes
.has(lt::protocol_version::V2
))
1768 btProtocols
.insert(2);
1770 const QSet
<int> btProtocols
{1};
1772 ::updateTrackerEntryStatus(*it
, announceEntry
, btProtocols
, updateInfo
);
1776 void TorrentImpl::resetTrackerEntryStatuses()
1778 for (TrackerEntryStatus
&status
: m_trackerEntryStatuses
)
1780 const QString tempUrl
= status
.url
;
1781 const int tempTier
= status
.tier
;
1784 status
.url
= tempUrl
;
1785 status
.tier
= tempTier
;
1789 std::shared_ptr
<const libtorrent::torrent_info
> TorrentImpl::nativeTorrentInfo() const
1791 Q_ASSERT(!m_nativeStatus
.torrent_file
.expired());
1793 return m_nativeStatus
.torrent_file
.lock();
1796 void TorrentImpl::endReceivedMetadataHandling(const Path
&savePath
, const PathList
&fileNames
)
1798 Q_ASSERT(m_maintenanceJob
== MaintenanceJob::HandleMetadata
);
1799 if (m_maintenanceJob
!= MaintenanceJob::HandleMetadata
) [[unlikely
]]
1802 Q_ASSERT(m_filePaths
.isEmpty());
1803 if (!m_filePaths
.isEmpty()) [[unlikely
]]
1804 m_filePaths
.clear();
1806 lt::add_torrent_params
&p
= m_ltAddTorrentParams
;
1808 const std::shared_ptr
<lt::torrent_info
> metadata
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1809 m_torrentInfo
= TorrentInfo(*metadata
);
1810 m_filePriorities
.reserve(filesCount());
1811 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
1812 p
.file_priorities
= resized(p
.file_priorities
, metadata
->files().num_files()
1813 , LT::toNative(p
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
1815 m_completedFiles
.fill(static_cast<bool>(p
.flags
& lt::torrent_flags::seed_mode
), filesCount());
1816 m_filesProgress
.resize(filesCount());
1819 for (int i
= 0; i
< fileNames
.size(); ++i
)
1821 const auto nativeIndex
= nativeIndexes
.at(i
);
1823 const Path
&actualFilePath
= fileNames
.at(i
);
1824 p
.renamed_files
[nativeIndex
] = actualFilePath
.toString().toStdString();
1826 const Path filePath
= actualFilePath
.removedExtension(QB_EXT
);
1827 m_filePaths
.append(filePath
);
1829 m_filePriorities
.append(LT::fromNative(p
.file_priorities
[LT::toUnderlyingType(nativeIndex
)]));
1832 m_session
->applyFilenameFilter(fileNames
, m_filePriorities
);
1833 for (int i
= 0; i
< m_filePriorities
.size(); ++i
)
1834 p
.file_priorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(m_filePriorities
[i
]);
1836 p
.save_path
= savePath
.toString().toStdString();
1839 if (stopCondition() == StopCondition::MetadataReceived
)
1841 m_stopCondition
= StopCondition::None
;
1844 p
.flags
|= lt::torrent_flags::paused
;
1845 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1847 m_session
->handleTorrentStopped(this);
1852 // If first/last piece priority was specified when adding this torrent,
1853 // we should apply it now that we have metadata:
1854 if (m_hasFirstLastPiecePriority
)
1855 applyFirstLastPiecePriority(true);
1857 m_maintenanceJob
= MaintenanceJob::None
;
1858 prepareResumeData(p
);
1860 m_session
->handleTorrentMetadataReceived(this);
1863 void TorrentImpl::reload()
1867 m_completedFiles
.fill(false);
1868 m_filesProgress
.fill(0);
1869 m_pieces
.fill(false);
1870 m_nativeStatus
.pieces
.clear_all();
1871 m_nativeStatus
.num_pieces
= 0;
1873 const auto queuePos
= m_nativeHandle
.queue_position();
1875 m_nativeSession
->remove_torrent(m_nativeHandle
, lt::session::delete_partfile
);
1877 lt::add_torrent_params p
= m_ltAddTorrentParams
;
1878 p
.flags
|= lt::torrent_flags::update_subscribe
1879 | lt::torrent_flags::override_trackers
1880 | lt::torrent_flags::override_web_seeds
;
1884 p
.flags
|= lt::torrent_flags::paused
;
1885 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1887 else if (m_operatingMode
== TorrentOperatingMode::AutoManaged
)
1889 p
.flags
|= (lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1893 p
.flags
&= ~(lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1896 auto *const extensionData
= new ExtensionData
;
1897 p
.userdata
= LTClientData(extensionData
);
1898 #ifndef QBT_USES_LIBTORRENT2
1899 p
.storage
= customStorageConstructor
;
1901 m_nativeHandle
= m_nativeSession
->add_torrent(p
);
1903 m_nativeStatus
= extensionData
->status
;
1905 if (queuePos
>= lt::queue_position_t
{})
1906 m_nativeHandle
.queue_position_set(queuePos
);
1907 m_nativeStatus
.queue_position
= queuePos
;
1911 catch (const lt::system_error
&err
)
1913 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1914 .arg(id().toString(), QString::fromLocal8Bit(err
.what())));
1918 void TorrentImpl::stop()
1922 m_stopCondition
= StopCondition::None
;
1924 deferredRequestResumeData();
1925 m_session
->handleTorrentStopped(this);
1928 if (m_maintenanceJob
== MaintenanceJob::None
)
1930 setAutoManaged(false);
1931 m_nativeHandle
.pause();
1933 m_payloadRateMonitor
.reset();
1937 void TorrentImpl::start(const TorrentOperatingMode mode
)
1941 m_nativeHandle
.clear_error();
1942 m_nativeHandle
.unset_flags(lt::torrent_flags::upload_mode
);
1945 m_operatingMode
= mode
;
1947 if (m_hasMissingFiles
)
1949 m_hasMissingFiles
= false;
1950 m_isStopped
= false;
1951 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1958 m_isStopped
= false;
1959 deferredRequestResumeData();
1960 m_session
->handleTorrentStarted(this);
1963 if (m_maintenanceJob
== MaintenanceJob::None
)
1965 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1966 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1967 m_nativeHandle
.resume();
1971 void TorrentImpl::moveStorage(const Path
&newPath
, const MoveStorageContext context
)
1975 if (context
== MoveStorageContext::ChangeSavePath
)
1977 m_savePath
= newPath
;
1978 m_session
->handleTorrentSavePathChanged(this);
1980 else if (context
== MoveStorageContext::ChangeDownloadPath
)
1982 m_downloadPath
= newPath
;
1983 m_session
->handleTorrentSavePathChanged(this);
1989 const auto mode
= (context
== MoveStorageContext::AdjustCurrentLocation
)
1990 ? MoveStorageMode::Overwrite
: MoveStorageMode::KeepExistingFiles
;
1991 if (m_session
->addMoveTorrentStorageJob(this, newPath
, mode
, context
))
1993 if (!m_storageIsMoving
)
1995 m_storageIsMoving
= true;
1997 m_session
->handleTorrentStorageMovingStateChanged(this);
2002 void TorrentImpl::renameFile(const int index
, const Path
&path
)
2004 Q_ASSERT((index
>= 0) && (index
< filesCount()));
2005 if ((index
< 0) || (index
>= filesCount())) [[unlikely
]]
2008 const Path targetActualPath
= makeActualPath(index
, path
);
2009 doRenameFile(index
, targetActualPath
);
2012 void TorrentImpl::handleStateUpdate(const lt::torrent_status
&nativeStatus
)
2014 updateStatus(nativeStatus
);
2017 void TorrentImpl::handleQueueingModeChanged()
2022 void TorrentImpl::handleMoveStorageJobFinished(const Path
&path
, const MoveStorageContext context
, const bool hasOutstandingJob
)
2024 if (context
== MoveStorageContext::ChangeSavePath
)
2026 else if (context
== MoveStorageContext::ChangeDownloadPath
)
2027 m_downloadPath
= path
;
2028 m_storageIsMoving
= hasOutstandingJob
;
2029 m_nativeStatus
.save_path
= path
.toString().toStdString();
2031 m_session
->handleTorrentSavePathChanged(this);
2032 deferredRequestResumeData();
2034 if (!m_storageIsMoving
)
2037 m_session
->handleTorrentStorageMovingStateChanged(this);
2039 if (m_hasMissingFiles
)
2041 // it can be moved to the proper location
2042 m_hasMissingFiles
= false;
2043 m_ltAddTorrentParams
.save_path
= m_nativeStatus
.save_path
;
2044 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
2048 while ((m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2049 std::invoke(m_moveFinishedTriggers
.dequeue());
2053 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused
]] const lt::torrent_checked_alert
*p
)
2057 // The torrent is checked due to metadata received, but we should not process
2058 // this event until the torrent is reloaded using the received metadata.
2062 if (stopCondition() == StopCondition::FilesChecked
)
2065 m_statusUpdatedTriggers
.enqueue([this]()
2067 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
2069 if (!m_hasMissingFiles
)
2071 if ((progress() < 1.0) && (wantedSize() > 0))
2072 m_hasFinishedStatus
= false;
2073 else if (progress() == 1.0)
2074 m_hasFinishedStatus
= true;
2076 adjustStorageLocation();
2077 manageActualFilePaths();
2081 // torrent is internally paused using NativeTorrentExtension after files checked
2082 // so we need to resume it if there is no corresponding "stop condition" set
2083 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
2084 if (m_operatingMode
== TorrentOperatingMode::Forced
)
2085 m_nativeHandle
.resume();
2089 if (m_nativeStatus
.need_save_resume
)
2090 deferredRequestResumeData();
2092 m_session
->handleTorrentChecked(this);
2096 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused
]] const lt::torrent_finished_alert
*p
)
2098 m_hasMissingFiles
= false;
2099 if (m_hasFinishedStatus
)
2102 m_statusUpdatedTriggers
.enqueue([this]()
2104 adjustStorageLocation();
2105 manageActualFilePaths();
2107 deferredRequestResumeData();
2109 const bool recheckTorrentsOnCompletion
= Preferences::instance()->recheckTorrentsOnCompletion();
2110 if (recheckTorrentsOnCompletion
&& m_unchecked
)
2116 m_hasFinishedStatus
= true;
2118 if (isMoveInProgress() || (m_renameCount
> 0))
2119 m_moveFinishedTriggers
.enqueue([this]() { m_session
->handleTorrentFinished(this); });
2121 m_session
->handleTorrentFinished(this);
2126 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused
]] const lt::torrent_paused_alert
*p
)
2130 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused
]] const lt::torrent_resumed_alert
*p
)
2134 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert
*p
)
2136 if (m_ltAddTorrentParams
.url_seeds
!= p
->params
.url_seeds
)
2138 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2139 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2140 // than the list we usually cache, so we have to request it from the appropriate source.
2141 fetchURLSeeds([this](const QList
<QUrl
> &urlSeeds
) { m_urlSeeds
= urlSeeds
; });
2144 if ((m_maintenanceJob
== MaintenanceJob::HandleMetadata
) && p
->params
.ti
)
2146 Q_ASSERT(m_indexMap
.isEmpty());
2148 const auto isSeedMode
= static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
);
2149 m_ltAddTorrentParams
= p
->params
;
2151 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2153 m_ltAddTorrentParams
.have_pieces
.clear();
2154 m_ltAddTorrentParams
.verified_pieces
.clear();
2156 m_nativeStatus
.torrent_file
= m_ltAddTorrentParams
.ti
;
2158 const auto metadata
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
2160 const auto &renamedFiles
= m_ltAddTorrentParams
.renamed_files
;
2161 PathList filePaths
= metadata
.filePaths();
2162 if (renamedFiles
.empty() && (m_contentLayout
!= TorrentContentLayout::Original
))
2164 const Path originalRootFolder
= Path::findRootFolder(filePaths
);
2165 const auto originalContentLayout
= (originalRootFolder
.isEmpty()
2166 ? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder
);
2167 if (m_contentLayout
!= originalContentLayout
)
2169 if (m_contentLayout
== TorrentContentLayout::NoSubfolder
)
2170 Path::stripRootFolder(filePaths
);
2172 Path::addRootFolder(filePaths
, filePaths
.at(0).removedExtension());
2176 const auto nativeIndexes
= metadata
.nativeIndexes();
2177 m_indexMap
.reserve(filePaths
.size());
2178 for (int i
= 0; i
< filePaths
.size(); ++i
)
2180 const auto nativeIndex
= nativeIndexes
.at(i
);
2181 m_indexMap
[nativeIndex
] = i
;
2183 if (const auto it
= renamedFiles
.find(nativeIndex
); it
!= renamedFiles
.cend())
2184 filePaths
[i
] = Path(it
->second
);
2187 m_session
->findIncompleteFiles(metadata
, savePath(), downloadPath(), filePaths
);
2191 prepareResumeData(p
->params
);
2195 void TorrentImpl::prepareResumeData(const lt::add_torrent_params
¶ms
)
2197 if (m_hasMissingFiles
)
2199 const auto havePieces
= m_ltAddTorrentParams
.have_pieces
;
2200 const auto unfinishedPieces
= m_ltAddTorrentParams
.unfinished_pieces
;
2201 const auto verifiedPieces
= m_ltAddTorrentParams
.verified_pieces
;
2203 // Update recent resume data but preserve existing progress
2204 m_ltAddTorrentParams
= params
;
2205 m_ltAddTorrentParams
.have_pieces
= havePieces
;
2206 m_ltAddTorrentParams
.unfinished_pieces
= unfinishedPieces
;
2207 m_ltAddTorrentParams
.verified_pieces
= verifiedPieces
;
2211 const bool preserveSeedMode
= (!hasMetadata() && (m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
));
2212 // Update recent resume data
2213 m_ltAddTorrentParams
= params
;
2214 if (preserveSeedMode
)
2215 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2218 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2219 m_ltAddTorrentParams
.flags
&= ~lt::torrent_flags::upload_mode
;
2221 const LoadTorrentParams resumeData
2223 .ltAddTorrentParams
= m_ltAddTorrentParams
,
2225 .category
= m_category
,
2227 .savePath
= (!m_useAutoTMM
? m_savePath
: Path()),
2228 .downloadPath
= (!m_useAutoTMM
? m_downloadPath
: Path()),
2229 .contentLayout
= m_contentLayout
,
2230 .operatingMode
= m_operatingMode
,
2231 .useAutoTMM
= m_useAutoTMM
,
2232 .firstLastPiecePriority
= m_hasFirstLastPiecePriority
,
2233 .hasFinishedStatus
= m_hasFinishedStatus
,
2234 .stopped
= m_isStopped
,
2235 .stopCondition
= m_stopCondition
,
2236 .addToQueueTop
= false,
2237 .ratioLimit
= m_ratioLimit
,
2238 .seedingTimeLimit
= m_seedingTimeLimit
,
2239 .inactiveSeedingTimeLimit
= m_inactiveSeedingTimeLimit
,
2240 .shareLimitAction
= m_shareLimitAction
,
2241 .sslParameters
= m_sslParams
2244 m_session
->handleTorrentResumeDataReady(this, resumeData
);
2247 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert
*p
)
2249 if (p
->error
!= lt::errors::resume_data_not_modified
)
2251 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2252 .arg(name(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::CRITICAL
);
2256 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert
*p
)
2258 // Files were probably moved or storage isn't accessible
2259 m_hasMissingFiles
= true;
2260 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2261 .arg(name(), QString::fromStdString(p
->message())), Log::WARNING
);
2264 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert
*p
)
2266 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2267 Q_ASSERT(fileIndex
>= 0);
2269 const Path newActualFilePath
{QString::fromUtf8(p
->new_name())};
2271 const Path oldFilePath
= m_filePaths
.at(fileIndex
);
2272 const Path newFilePath
= makeUserPath(newActualFilePath
);
2274 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2275 // We should compare path in a case sensitive manner even on case insensitive
2276 // platforms since it can be renamed by only changing case of some character(s)
2277 if (oldFilePath
.data() == newFilePath
.data())
2279 // Remove empty ".unwanted" folders
2280 #ifdef QBT_USES_LIBTORRENT2
2281 const Path oldActualFilePath
{QString::fromUtf8(p
->old_name())};
2283 const Path oldActualFilePath
;
2285 const Path oldActualParentPath
= oldActualFilePath
.parentPath();
2286 const Path newActualParentPath
= newActualFilePath
.parentPath();
2287 if (newActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2289 if (oldActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2292 const std::wstring winPath
= (actualStorageLocation() / newActualParentPath
).toString().toStdWString();
2293 const DWORD dwAttrs
= ::GetFileAttributesW(winPath
.c_str());
2294 ::SetFileAttributesW(winPath
.c_str(), (dwAttrs
| FILE_ATTRIBUTE_HIDDEN
));
2298 #ifdef QBT_USES_LIBTORRENT2
2299 else if (oldActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2301 if (newActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2302 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath
);
2307 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath
/ Path(UNWANTED_FOLDER_NAME
));
2313 m_filePaths
[fileIndex
] = newFilePath
;
2315 // Remove empty leftover folders
2316 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2317 // be removed if they are empty
2318 Path oldParentPath
= oldFilePath
.parentPath();
2319 const Path commonBasePath
= Path::commonPath(oldParentPath
, newFilePath
.parentPath());
2320 while (oldParentPath
!= commonBasePath
)
2322 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath
);
2323 oldParentPath
= oldParentPath
.parentPath();
2328 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2329 m_moveFinishedTriggers
.takeFirst()();
2331 deferredRequestResumeData();
2334 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
*p
)
2336 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2337 Q_ASSERT(fileIndex
>= 0);
2339 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2340 .arg(name(), filePath(fileIndex
).toString(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::WARNING
);
2343 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2344 m_moveFinishedTriggers
.takeFirst()();
2346 deferredRequestResumeData();
2349 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert
*p
)
2351 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2354 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2355 Q_ASSERT(fileIndex
>= 0);
2357 m_completedFiles
.setBit(fileIndex
);
2359 const Path actualPath
= actualFilePath(fileIndex
);
2361 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2362 // only apply Mark-of-the-Web to new download files
2363 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2365 const Path fullpath
= actualStorageLocation() / actualPath
;
2366 Utils::OS::applyMarkOfTheWeb(fullpath
);
2368 #endif // Q_OS_MACOS || Q_OS_WIN
2370 if (m_session
->isAppendExtensionEnabled())
2372 const Path path
= filePath(fileIndex
);
2373 if (actualPath
!= path
)
2375 qDebug("Renaming %s to %s", qUtf8Printable(actualPath
.toString()), qUtf8Printable(path
.toString()));
2376 doRenameFile(fileIndex
, path
);
2381 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert
*p
)
2383 m_lastFileError
= {p
->error
, p
->op
};
2386 #ifdef QBT_USES_LIBTORRENT2
2387 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert
*)
2389 deferredRequestResumeData();
2393 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused
]] const lt::metadata_received_alert
*p
)
2395 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2397 #ifdef QBT_USES_LIBTORRENT2
2398 const InfoHash prevInfoHash
= infoHash();
2399 m_infoHash
= InfoHash(m_nativeHandle
.info_hashes());
2400 if (prevInfoHash
!= infoHash())
2401 m_session
->handleTorrentInfoHashChanged(this, prevInfoHash
);
2404 m_maintenanceJob
= MaintenanceJob::HandleMetadata
;
2405 deferredRequestResumeData();
2408 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert
*p
) const
2410 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
))
2414 void TorrentImpl::handleCategoryOptionsChanged()
2417 adjustStorageLocation();
2420 void TorrentImpl::handleAppendExtensionToggled()
2425 manageActualFilePaths();
2428 void TorrentImpl::handleUnwantedFolderToggled()
2433 manageActualFilePaths();
2436 void TorrentImpl::handleAlert(const lt::alert
*a
)
2440 #ifdef QBT_USES_LIBTORRENT2
2441 case lt::file_prio_alert::alert_type
:
2442 handleFilePrioAlert(static_cast<const lt::file_prio_alert
*>(a
));
2445 case lt::file_renamed_alert::alert_type
:
2446 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert
*>(a
));
2448 case lt::file_rename_failed_alert::alert_type
:
2449 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert
*>(a
));
2451 case lt::file_completed_alert::alert_type
:
2452 handleFileCompletedAlert(static_cast<const lt::file_completed_alert
*>(a
));
2454 case lt::file_error_alert::alert_type
:
2455 handleFileErrorAlert(static_cast<const lt::file_error_alert
*>(a
));
2457 case lt::torrent_finished_alert::alert_type
:
2458 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert
*>(a
));
2460 case lt::save_resume_data_alert::alert_type
:
2461 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert
*>(a
));
2463 case lt::save_resume_data_failed_alert::alert_type
:
2464 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert
*>(a
));
2466 case lt::torrent_paused_alert::alert_type
:
2467 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert
*>(a
));
2469 case lt::torrent_resumed_alert::alert_type
:
2470 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert
*>(a
));
2472 case lt::metadata_received_alert::alert_type
:
2473 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(a
));
2475 case lt::fastresume_rejected_alert::alert_type
:
2476 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert
*>(a
));
2478 case lt::torrent_checked_alert::alert_type
:
2479 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert
*>(a
));
2481 case lt::performance_alert::alert_type
:
2482 handlePerformanceAlert(static_cast<const lt::performance_alert
*>(a
));
2487 void TorrentImpl::manageActualFilePaths()
2489 const std::shared_ptr
<const lt::torrent_info
> nativeInfo
= nativeTorrentInfo();
2490 const lt::file_storage
&nativeFiles
= nativeInfo
->files();
2492 for (int i
= 0; i
< filesCount(); ++i
)
2494 const Path path
= filePath(i
);
2496 const auto nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
2497 const Path actualPath
{nativeFiles
.file_path(nativeIndex
)};
2498 const Path targetActualPath
= makeActualPath(i
, path
);
2499 if (actualPath
!= targetActualPath
)
2501 qDebug() << "Renaming" << actualPath
.toString() << "to" << targetActualPath
.toString();
2502 doRenameFile(i
, targetActualPath
);
2507 void TorrentImpl::adjustStorageLocation()
2509 const Path downloadPath
= this->downloadPath();
2510 const Path targetPath
= ((isFinished() || m_hasFinishedStatus
|| downloadPath
.isEmpty()) ? savePath() : downloadPath
);
2512 if ((targetPath
!= actualStorageLocation()) || isMoveInProgress())
2513 moveStorage(targetPath
, MoveStorageContext::AdjustCurrentLocation
);
2516 void TorrentImpl::doRenameFile(const int index
, const Path
&path
)
2518 const QList
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
2520 Q_ASSERT(index
>= 0);
2521 Q_ASSERT(index
< nativeIndexes
.size());
2522 if ((index
< 0) || (index
>= nativeIndexes
.size())) [[unlikely
]]
2526 m_nativeHandle
.rename_file(nativeIndexes
[index
], path
.toString().toStdString());
2529 lt::torrent_handle
TorrentImpl::nativeHandle() const
2531 return m_nativeHandle
;
2534 void TorrentImpl::setMetadata(const TorrentInfo
&torrentInfo
)
2539 m_session
->invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
]
2543 #ifdef QBT_USES_LIBTORRENT2
2544 nativeHandle
.set_metadata(torrentInfo
.nativeInfo()->info_section());
2546 const std::shared_ptr
<lt::torrent_info
> nativeInfo
= torrentInfo
.nativeInfo();
2547 nativeHandle
.set_metadata(lt::span
<const char>(nativeInfo
->metadata().get(), nativeInfo
->metadata_size()));
2550 catch (const std::exception
&) {}
2554 Torrent::StopCondition
TorrentImpl::stopCondition() const
2556 return m_stopCondition
;
2559 void TorrentImpl::setStopCondition(const StopCondition stopCondition
)
2561 if (stopCondition
== m_stopCondition
)
2567 if ((stopCondition
== StopCondition::MetadataReceived
) && hasMetadata())
2570 if ((stopCondition
== StopCondition::FilesChecked
) && hasMetadata() && !isChecking())
2573 m_stopCondition
= stopCondition
;
2576 SSLParameters
TorrentImpl::getSSLParameters() const
2581 void TorrentImpl::setSSLParameters(const SSLParameters
&sslParams
)
2583 if (sslParams
== getSSLParameters())
2586 m_sslParams
= sslParams
;
2587 applySSLParameters();
2589 deferredRequestResumeData();
2592 bool TorrentImpl::applySSLParameters()
2594 if (!m_sslParams
.isValid())
2597 m_nativeHandle
.set_ssl_certificate_buffer(m_sslParams
.certificate
.toPem().toStdString()
2598 , m_sslParams
.privateKey
.toPem().toStdString(), m_sslParams
.dhParams
.toStdString());
2602 bool TorrentImpl::isMoveInProgress() const
2604 return m_storageIsMoving
;
2607 void TorrentImpl::updateStatus(const lt::torrent_status
&nativeStatus
)
2609 const lt::torrent_status oldStatus
= std::exchange(m_nativeStatus
, nativeStatus
);
2611 if (m_nativeStatus
.num_pieces
!= oldStatus
.num_pieces
)
2616 m_payloadRateMonitor
.addSample({nativeStatus
.download_payload_rate
2617 , nativeStatus
.upload_payload_rate
});
2621 // NOTE: Don't change the order of these conditionals!
2622 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2624 m_unchecked
= false;
2625 else if (isDownloading())
2629 while (!m_statusUpdatedTriggers
.isEmpty())
2630 std::invoke(m_statusUpdatedTriggers
.dequeue());
2633 void TorrentImpl::updateProgress()
2635 Q_ASSERT(hasMetadata());
2636 if (!hasMetadata()) [[unlikely
]]
2639 Q_ASSERT(!m_filesProgress
.isEmpty());
2640 if (m_filesProgress
.isEmpty()) [[unlikely
]]
2641 m_filesProgress
.resize(filesCount());
2643 const QBitArray oldPieces
= std::exchange(m_pieces
, LT::toQBitArray(m_nativeStatus
.pieces
));
2644 const QBitArray newPieces
= m_pieces
^ oldPieces
;
2646 const int64_t pieceSize
= m_torrentInfo
.pieceLength();
2647 for (qsizetype index
= 0; index
< newPieces
.size(); ++index
)
2649 if (!newPieces
.at(index
))
2652 int64_t size
= m_torrentInfo
.pieceLength(index
);
2653 int64_t pieceOffset
= index
* pieceSize
;
2655 for (const int fileIndex
: asConst(m_torrentInfo
.fileIndicesForPiece(index
)))
2657 const int64_t fileOffsetInPiece
= pieceOffset
- m_torrentInfo
.fileOffset(fileIndex
);
2658 const int64_t add
= std::min
<int64_t>((m_torrentInfo
.fileSize(fileIndex
) - fileOffsetInPiece
), size
);
2660 m_filesProgress
[fileIndex
] += add
;
2671 void TorrentImpl::setRatioLimit(qreal limit
)
2673 if (limit
< USE_GLOBAL_RATIO
)
2674 limit
= NO_RATIO_LIMIT
;
2675 else if (limit
> MAX_RATIO
)
2678 if (m_ratioLimit
!= limit
)
2680 m_ratioLimit
= limit
;
2681 deferredRequestResumeData();
2682 m_session
->handleTorrentShareLimitChanged(this);
2686 void TorrentImpl::setSeedingTimeLimit(int limit
)
2688 if (limit
< USE_GLOBAL_SEEDING_TIME
)
2689 limit
= NO_SEEDING_TIME_LIMIT
;
2690 else if (limit
> MAX_SEEDING_TIME
)
2691 limit
= MAX_SEEDING_TIME
;
2693 if (m_seedingTimeLimit
!= limit
)
2695 m_seedingTimeLimit
= limit
;
2696 deferredRequestResumeData();
2697 m_session
->handleTorrentShareLimitChanged(this);
2701 void TorrentImpl::setInactiveSeedingTimeLimit(int limit
)
2703 if (limit
< USE_GLOBAL_INACTIVE_SEEDING_TIME
)
2704 limit
= NO_INACTIVE_SEEDING_TIME_LIMIT
;
2705 else if (limit
> MAX_INACTIVE_SEEDING_TIME
)
2706 limit
= MAX_SEEDING_TIME
;
2708 if (m_inactiveSeedingTimeLimit
!= limit
)
2710 m_inactiveSeedingTimeLimit
= limit
;
2711 deferredRequestResumeData();
2712 m_session
->handleTorrentShareLimitChanged(this);
2716 ShareLimitAction
TorrentImpl::shareLimitAction() const
2718 return m_shareLimitAction
;
2721 void TorrentImpl::setShareLimitAction(const ShareLimitAction action
)
2723 if (m_shareLimitAction
!= action
)
2725 m_shareLimitAction
= action
;
2726 deferredRequestResumeData();
2727 m_session
->handleTorrentShareLimitChanged(this);
2731 void TorrentImpl::setUploadLimit(const int limit
)
2733 const int cleanValue
= cleanLimitValue(limit
);
2734 if (cleanValue
== uploadLimit())
2737 m_uploadLimit
= cleanValue
;
2738 m_nativeHandle
.set_upload_limit(m_uploadLimit
);
2739 deferredRequestResumeData();
2742 void TorrentImpl::setDownloadLimit(const int limit
)
2744 const int cleanValue
= cleanLimitValue(limit
);
2745 if (cleanValue
== downloadLimit())
2748 m_downloadLimit
= cleanValue
;
2749 m_nativeHandle
.set_download_limit(m_downloadLimit
);
2750 deferredRequestResumeData();
2753 void TorrentImpl::setSuperSeeding(const bool enable
)
2755 if (enable
== superSeeding())
2759 m_nativeHandle
.set_flags(lt::torrent_flags::super_seeding
);
2761 m_nativeHandle
.unset_flags(lt::torrent_flags::super_seeding
);
2763 deferredRequestResumeData();
2766 void TorrentImpl::setDHTDisabled(const bool disable
)
2768 if (disable
== isDHTDisabled())
2772 m_nativeHandle
.set_flags(lt::torrent_flags::disable_dht
);
2774 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_dht
);
2776 deferredRequestResumeData();
2779 void TorrentImpl::setPEXDisabled(const bool disable
)
2781 if (disable
== isPEXDisabled())
2785 m_nativeHandle
.set_flags(lt::torrent_flags::disable_pex
);
2787 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_pex
);
2789 deferredRequestResumeData();
2792 void TorrentImpl::setLSDDisabled(const bool disable
)
2794 if (disable
== isLSDDisabled())
2798 m_nativeHandle
.set_flags(lt::torrent_flags::disable_lsd
);
2800 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_lsd
);
2802 deferredRequestResumeData();
2805 void TorrentImpl::flushCache() const
2807 m_nativeHandle
.flush_cache();
2810 QString
TorrentImpl::createMagnetURI() const
2812 QString ret
= u
"magnet:?"_s
;
2814 const SHA1Hash infoHash1
= infoHash().v1();
2815 if (infoHash1
.isValid())
2817 ret
+= u
"xt=urn:btih:" + infoHash1
.toString();
2820 const SHA256Hash infoHash2
= infoHash().v2();
2821 if (infoHash2
.isValid())
2823 if (infoHash1
.isValid())
2825 ret
+= u
"xt=urn:btmh:1220" + infoHash2
.toString();
2828 const QString displayName
= name();
2829 if (displayName
!= id().toString())
2831 ret
+= u
"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName
));
2834 for (const TrackerEntryStatus
&tracker
: asConst(trackers()))
2836 ret
+= u
"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker
.url
));
2839 for (const QUrl
&urlSeed
: asConst(urlSeeds()))
2841 ret
+= u
"&ws=" + QString::fromLatin1(urlSeed
.toEncoded());
2847 nonstd::expected
<lt::entry
, QString
> TorrentImpl::exportTorrent() const
2850 return nonstd::make_unexpected(tr("Missing metadata"));
2854 #ifdef QBT_USES_LIBTORRENT2
2855 const std::shared_ptr
<lt::torrent_info
> completeTorrentInfo
= m_nativeHandle
.torrent_file_with_hashes();
2856 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= (completeTorrentInfo
? completeTorrentInfo
: info().nativeInfo());
2858 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= info().nativeInfo();
2860 lt::create_torrent creator
{*torrentInfo
};
2862 for (const TrackerEntryStatus
&status
: asConst(trackers()))
2863 creator
.add_tracker(status
.url
.toStdString(), status
.tier
);
2865 return creator
.generate();
2867 catch (const lt::system_error
&err
)
2869 return nonstd::make_unexpected(QString::fromLocal8Bit(err
.what()));
2873 nonstd::expected
<QByteArray
, QString
> TorrentImpl::exportToBuffer() const
2875 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2876 if (!preparationResult
)
2877 return preparationResult
.get_unexpected();
2879 // usually torrent size should be smaller than 1 MB,
2880 // however there are >100 MB v2/hybrid torrent files out in the wild
2882 buffer
.reserve(1024 * 1024);
2883 lt::bencode(std::back_inserter(buffer
), preparationResult
.value());
2887 nonstd::expected
<void, QString
> TorrentImpl::exportToFile(const Path
&path
) const
2889 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2890 if (!preparationResult
)
2891 return preparationResult
.get_unexpected();
2893 const nonstd::expected
<void, QString
> saveResult
= Utils::IO::saveToFile(path
, preparationResult
.value());
2895 return saveResult
.get_unexpected();
2900 void TorrentImpl::fetchPeerInfo(std::function
<void (QList
<PeerInfo
>)> resultHandler
) const
2902 invokeAsync([nativeHandle
= m_nativeHandle
, allPieces
= pieces()]() -> QList
<PeerInfo
>
2906 std::vector
<lt::peer_info
> nativePeers
;
2907 nativeHandle
.get_peer_info(nativePeers
);
2908 QList
<PeerInfo
> peers
;
2909 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
2910 for (const lt::peer_info
&peer
: nativePeers
)
2911 peers
.append(PeerInfo(peer
, allPieces
));
2914 catch (const std::exception
&) {}
2918 , std::move(resultHandler
));
2921 void TorrentImpl::fetchURLSeeds(std::function
<void (QList
<QUrl
>)> resultHandler
) const
2923 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QList
<QUrl
>
2927 const std::set
<std::string
> currentSeeds
= nativeHandle
.url_seeds();
2928 QList
<QUrl
> urlSeeds
;
2929 urlSeeds
.reserve(static_cast<decltype(urlSeeds
)::size_type
>(currentSeeds
.size()));
2930 for (const std::string
&urlSeed
: currentSeeds
)
2931 urlSeeds
.append(QString::fromStdString(urlSeed
));
2934 catch (const std::exception
&) {}
2938 , std::move(resultHandler
));
2941 void TorrentImpl::fetchPieceAvailability(std::function
<void (QList
<int>)> resultHandler
) const
2943 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QList
<int>
2947 std::vector
<int> piecesAvailability
;
2948 nativeHandle
.piece_availability(piecesAvailability
);
2949 return QList
<int>(piecesAvailability
.cbegin(), piecesAvailability
.cend());
2951 catch (const std::exception
&) {}
2955 , std::move(resultHandler
));
2958 void TorrentImpl::fetchDownloadingPieces(std::function
<void (QBitArray
)> resultHandler
) const
2960 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QBitArray
2964 #ifdef QBT_USES_LIBTORRENT2
2965 const std::vector
<lt::partial_piece_info
> queue
= nativeHandle
.get_download_queue();
2967 std::vector
<lt::partial_piece_info
> queue
;
2968 nativeHandle
.get_download_queue(queue
);
2971 result
.resize(torrentInfo
.piecesCount());
2972 for (const lt::partial_piece_info
&info
: queue
)
2973 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
2976 catch (const std::exception
&) {}
2980 , std::move(resultHandler
));
2983 void TorrentImpl::fetchAvailableFileFractions(std::function
<void (QList
<qreal
>)> resultHandler
) const
2985 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QList
<qreal
>
2987 if (!torrentInfo
.isValid() || (torrentInfo
.filesCount() <= 0))
2992 std::vector
<int> piecesAvailability
;
2993 nativeHandle
.piece_availability(piecesAvailability
);
2994 const int filesCount
= torrentInfo
.filesCount();
2995 // libtorrent returns empty array for seeding only torrents
2996 if (piecesAvailability
.empty())
2997 return QList
<qreal
>(filesCount
, -1);
2999 QList
<qreal
> result
;
3000 result
.reserve(filesCount
);
3001 for (int i
= 0; i
< filesCount
; ++i
)
3003 const TorrentInfo::PieceRange filePieces
= torrentInfo
.filePieces(i
);
3005 int availablePieces
= 0;
3006 for (const int piece
: filePieces
)
3007 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
3009 const qreal availability
= filePieces
.isEmpty()
3010 ? 1 // the file has no pieces, so it is available by default
3011 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
3012 result
.append(availability
);
3016 catch (const std::exception
&) {}
3020 , std::move(resultHandler
));
3023 void TorrentImpl::prioritizeFiles(const QList
<DownloadPriority
> &priorities
)
3028 Q_ASSERT(priorities
.size() == filesCount());
3030 // Reset 'm_hasSeedStatus' if needed in order to react again to
3031 // 'torrent_finished_alert' and eg show tray notifications
3032 const QList
<DownloadPriority
> oldPriorities
= filePriorities();
3033 for (int i
= 0; i
< oldPriorities
.size(); ++i
)
3035 if ((oldPriorities
[i
] == DownloadPriority::Ignored
)
3036 && (priorities
[i
] > DownloadPriority::Ignored
)
3037 && !m_completedFiles
.at(i
))
3039 m_hasFinishedStatus
= false;
3044 const int internalFilesCount
= m_torrentInfo
.nativeInfo()->files().num_files(); // including .pad files
3045 auto nativePriorities
= std::vector
<lt::download_priority_t
>(internalFilesCount
, LT::toNative(DownloadPriority::Normal
));
3046 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
3047 for (int i
= 0; i
< priorities
.size(); ++i
)
3048 nativePriorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(priorities
[i
]);
3050 qDebug() << Q_FUNC_INFO
<< "Changing files priorities...";
3051 m_nativeHandle
.prioritize_files(nativePriorities
);
3053 m_filePriorities
= priorities
;
3054 // Restore first/last piece first option if necessary
3055 if (m_hasFirstLastPiecePriority
)
3056 applyFirstLastPiecePriority(true);
3057 manageActualFilePaths();
3060 QList
<qreal
> TorrentImpl::availableFileFractions() const
3062 Q_ASSERT(hasMetadata());
3064 const int filesCount
= this->filesCount();
3065 if (filesCount
<= 0) return {};
3067 const QList
<int> piecesAvailability
= pieceAvailability();
3068 // libtorrent returns empty array for seeding only torrents
3069 if (piecesAvailability
.empty()) return QList
<qreal
>(filesCount
, -1);
3072 res
.reserve(filesCount
);
3073 for (int i
= 0; i
< filesCount
; ++i
)
3075 const TorrentInfo::PieceRange filePieces
= m_torrentInfo
.filePieces(i
);
3077 int availablePieces
= 0;
3078 for (const int piece
: filePieces
)
3079 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
3081 const qreal availability
= filePieces
.isEmpty()
3082 ? 1 // the file has no pieces, so it is available by default
3083 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
3084 res
.push_back(availability
);
3089 template <typename Func
, typename Callback
>
3090 void TorrentImpl::invokeAsync(Func func
, Callback resultHandler
) const
3092 m_session
->invokeAsync([session
= m_session
3093 , func
= std::move(func
)
3094 , resultHandler
= std::move(resultHandler
)
3095 , thisTorrent
= QPointer
<const TorrentImpl
>(this)]() mutable
3097 session
->invoke([result
= func(), thisTorrent
, resultHandler
= std::move(resultHandler
)]
3100 resultHandler(result
);