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>
56 #include <QStringList>
59 #include "base/exceptions.h"
60 #include "base/global.h"
61 #include "base/logger.h"
62 #include "base/preferences.h"
63 #include "base/types.h"
64 #include "base/utils/fs.h"
65 #include "base/utils/io.h"
67 #include "downloadpriority.h"
68 #include "extensiondata.h"
69 #include "loadtorrentparams.h"
70 #include "ltqbitarray.h"
71 #include "lttypecast.h"
72 #include "peeraddress.h"
74 #include "sessionimpl.h"
75 #include "trackerentry.h"
77 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
78 #include "base/utils/os.h"
79 #endif // Q_OS_MACOS || Q_OS_WIN
81 #ifndef QBT_USES_LIBTORRENT2
82 #include "customstorage.h"
85 using namespace BitTorrent
;
89 lt::announce_entry
makeNativeAnnounceEntry(const QString
&url
, const int tier
)
91 lt::announce_entry entry
{url
.toStdString()};
96 QString
toString(const lt::tcp::endpoint
<TCPEndpoint
)
98 static QCache
<lt::tcp::endpoint
, QString
> cache
;
100 if (const QString
*endpointName
= cache
.object(ltTCPEndpoint
))
101 return *endpointName
;
103 const std::string tmp
= (std::ostringstream() << ltTCPEndpoint
).str();
104 const auto endpointName
= QString::fromLatin1(tmp
.c_str(), tmp
.size());
105 cache
.insert(ltTCPEndpoint
, new QString(endpointName
));
109 template <typename FromLTTimePoint32Func
>
110 void updateTrackerEntryStatus(TrackerEntryStatus
&trackerEntryStatus
, const lt::announce_entry
&nativeEntry
111 , const QSet
<int> &btProtocols
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
112 , const FromLTTimePoint32Func
&fromLTTimePoint32
)
114 Q_ASSERT(trackerEntryStatus
.url
== QString::fromStdString(nativeEntry
.url
));
116 trackerEntryStatus
.tier
= nativeEntry
.tier
;
118 const auto numEndpoints
= static_cast<qsizetype
>(nativeEntry
.endpoints
.size()) * btProtocols
.size();
122 int numNotWorking
= 0;
123 int numTrackerError
= 0;
124 int numUnreachable
= 0;
126 for (const lt::announce_endpoint
<AnnounceEndpoint
: nativeEntry
.endpoints
)
128 const auto endpointName
= toString(ltAnnounceEndpoint
.local_endpoint
);
130 for (const auto protocolVersion
: btProtocols
)
132 #ifdef QBT_USES_LIBTORRENT2
133 Q_ASSERT((protocolVersion
== 1) || (protocolVersion
== 2));
134 const auto ltProtocolVersion
= (protocolVersion
== 1) ? lt::protocol_version::V1
: lt::protocol_version::V2
;
135 const lt::announce_infohash
<AnnounceInfo
= ltAnnounceEndpoint
.info_hashes
[ltProtocolVersion
];
137 Q_ASSERT(protocolVersion
== 1);
138 const lt::announce_endpoint
<AnnounceInfo
= ltAnnounceEndpoint
;
140 const QMap
<int, int> &endpointUpdateInfo
= updateInfo
[ltAnnounceEndpoint
.local_endpoint
];
141 TrackerEndpointStatus
&trackerEndpointStatus
= trackerEntryStatus
.endpoints
[std::make_pair(endpointName
, protocolVersion
)];
143 trackerEndpointStatus
.name
= endpointName
;
144 trackerEndpointStatus
.btVersion
= protocolVersion
;
145 trackerEndpointStatus
.numPeers
= endpointUpdateInfo
.value(protocolVersion
, trackerEndpointStatus
.numPeers
);
146 trackerEndpointStatus
.numSeeds
= ltAnnounceInfo
.scrape_complete
;
147 trackerEndpointStatus
.numLeeches
= ltAnnounceInfo
.scrape_incomplete
;
148 trackerEndpointStatus
.numDownloaded
= ltAnnounceInfo
.scrape_downloaded
;
149 trackerEndpointStatus
.nextAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.next_announce
);
150 trackerEndpointStatus
.minAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.min_announce
);
152 if (ltAnnounceInfo
.updating
)
154 trackerEndpointStatus
.state
= TrackerEndpointState::Updating
;
157 else if (ltAnnounceInfo
.fails
> 0)
159 if (ltAnnounceInfo
.last_error
== lt::errors::tracker_failure
)
161 trackerEndpointStatus
.state
= TrackerEndpointState::TrackerError
;
164 else if (ltAnnounceInfo
.last_error
== lt::errors::announce_skipped
)
166 trackerEndpointStatus
.state
= TrackerEndpointState::Unreachable
;
171 trackerEndpointStatus
.state
= TrackerEndpointState::NotWorking
;
175 else if (nativeEntry
.verified
)
177 trackerEndpointStatus
.state
= TrackerEndpointState::Working
;
182 trackerEndpointStatus
.state
= TrackerEndpointState::NotContacted
;
185 if (!ltAnnounceInfo
.message
.empty())
187 trackerEndpointStatus
.message
= QString::fromStdString(ltAnnounceInfo
.message
);
189 else if (ltAnnounceInfo
.last_error
)
191 trackerEndpointStatus
.message
= QString::fromLocal8Bit(ltAnnounceInfo
.last_error
.message());
195 trackerEndpointStatus
.message
.clear();
200 if (trackerEntryStatus
.endpoints
.size() > numEndpoints
)
202 // remove outdated endpoints
203 trackerEntryStatus
.endpoints
.removeIf([&nativeEntry
](const QHash
<std::pair
<QString
, int>, TrackerEndpointStatus
>::iterator
&iter
)
205 return std::none_of(nativeEntry
.endpoints
.cbegin(), nativeEntry
.endpoints
.cend()
206 , [&endpointName
= std::get
<0>(iter
.key())](const auto &existingEndpoint
)
208 return (endpointName
== toString(existingEndpoint
.local_endpoint
));
213 if (numEndpoints
> 0)
217 trackerEntryStatus
.state
= TrackerEndpointState::Updating
;
219 else if (numWorking
> 0)
221 trackerEntryStatus
.state
= TrackerEndpointState::Working
;
223 else if (numTrackerError
> 0)
225 trackerEntryStatus
.state
= TrackerEndpointState::TrackerError
;
227 else if (numUnreachable
== numEndpoints
)
229 trackerEntryStatus
.state
= TrackerEndpointState::Unreachable
;
231 else if ((numUnreachable
+ numNotWorking
) == numEndpoints
)
233 trackerEntryStatus
.state
= TrackerEndpointState::NotWorking
;
237 trackerEntryStatus
.numPeers
= -1;
238 trackerEntryStatus
.numSeeds
= -1;
239 trackerEntryStatus
.numLeeches
= -1;
240 trackerEntryStatus
.numDownloaded
= -1;
241 trackerEntryStatus
.nextAnnounceTime
= QDateTime();
242 trackerEntryStatus
.minAnnounceTime
= QDateTime();
243 trackerEntryStatus
.message
.clear();
245 for (const TrackerEndpointStatus
&endpointStatus
: asConst(trackerEntryStatus
.endpoints
))
247 trackerEntryStatus
.numPeers
= std::max(trackerEntryStatus
.numPeers
, endpointStatus
.numPeers
);
248 trackerEntryStatus
.numSeeds
= std::max(trackerEntryStatus
.numSeeds
, endpointStatus
.numSeeds
);
249 trackerEntryStatus
.numLeeches
= std::max(trackerEntryStatus
.numLeeches
, endpointStatus
.numLeeches
);
250 trackerEntryStatus
.numDownloaded
= std::max(trackerEntryStatus
.numDownloaded
, endpointStatus
.numDownloaded
);
252 if (endpointStatus
.state
== trackerEntryStatus
.state
)
254 if (!trackerEntryStatus
.nextAnnounceTime
.isValid() || (trackerEntryStatus
.nextAnnounceTime
> endpointStatus
.nextAnnounceTime
))
256 trackerEntryStatus
.nextAnnounceTime
= endpointStatus
.nextAnnounceTime
;
257 trackerEntryStatus
.minAnnounceTime
= endpointStatus
.minAnnounceTime
;
258 if ((endpointStatus
.state
!= TrackerEndpointState::Working
)
259 || !endpointStatus
.message
.isEmpty())
261 trackerEntryStatus
.message
= endpointStatus
.message
;
265 if (endpointStatus
.state
== TrackerEndpointState::Working
)
267 if (trackerEntryStatus
.message
.isEmpty())
268 trackerEntryStatus
.message
= endpointStatus
.message
;
274 template <typename Vector
>
275 Vector
resized(const Vector
&inVector
, const typename
Vector::size_type size
, const typename
Vector::value_type
&defaultValue
)
277 Vector outVector
= inVector
;
278 outVector
.resize(size
, defaultValue
);
282 // This is an imitation of limit normalization performed by libtorrent itself.
283 // We need perform it to keep cached values in line with the ones used by libtorrent.
284 int cleanLimitValue(const int value
)
286 return ((value
< 0) || (value
== std::numeric_limits
<int>::max())) ? 0 : value
;
292 TorrentImpl::TorrentImpl(SessionImpl
*session
, lt::session
*nativeSession
293 , const lt::torrent_handle
&nativeHandle
, const LoadTorrentParams
¶ms
)
296 , m_nativeSession(nativeSession
)
297 , m_nativeHandle(nativeHandle
)
298 #ifdef QBT_USES_LIBTORRENT2
299 , m_infoHash(m_nativeHandle
.info_hashes())
301 , m_infoHash(m_nativeHandle
.info_hash())
303 , m_name(params
.name
)
304 , m_savePath(params
.savePath
)
305 , m_downloadPath(params
.downloadPath
)
306 , m_category(params
.category
)
307 , m_tags(params
.tags
)
308 , m_ratioLimit(params
.ratioLimit
)
309 , m_seedingTimeLimit(params
.seedingTimeLimit
)
310 , m_inactiveSeedingTimeLimit(params
.inactiveSeedingTimeLimit
)
311 , m_shareLimitAction(params
.shareLimitAction
)
312 , m_operatingMode(params
.operatingMode
)
313 , m_contentLayout(params
.contentLayout
)
314 , m_hasFinishedStatus(params
.hasFinishedStatus
)
315 , m_hasFirstLastPiecePriority(params
.firstLastPiecePriority
)
316 , m_useAutoTMM(params
.useAutoTMM
)
317 , m_isStopped(params
.stopped
)
318 , m_sslParams(params
.sslParameters
)
319 , m_ltAddTorrentParams(params
.ltAddTorrentParams
)
320 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams
.download_limit
))
321 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams
.upload_limit
))
323 if (m_ltAddTorrentParams
.ti
)
325 // Initialize it only if torrent is added with metadata.
326 // Otherwise it should be initialized in "Metadata received" handler.
327 m_torrentInfo
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
329 Q_ASSERT(m_filePaths
.isEmpty());
330 Q_ASSERT(m_indexMap
.isEmpty());
331 const int filesCount
= m_torrentInfo
.filesCount();
332 m_filePaths
.reserve(filesCount
);
333 m_indexMap
.reserve(filesCount
);
334 m_filePriorities
.reserve(filesCount
);
335 const std::vector
<lt::download_priority_t
> filePriorities
=
336 resized(m_ltAddTorrentParams
.file_priorities
, m_ltAddTorrentParams
.ti
->num_files()
337 , LT::toNative(m_ltAddTorrentParams
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
339 m_completedFiles
.fill(static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
), filesCount
);
340 m_filesProgress
.resize(filesCount
);
342 for (int i
= 0; i
< filesCount
; ++i
)
344 const lt::file_index_t nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
345 m_indexMap
[nativeIndex
] = i
;
347 const auto fileIter
= m_ltAddTorrentParams
.renamed_files
.find(nativeIndex
);
348 const Path filePath
= ((fileIter
!= m_ltAddTorrentParams
.renamed_files
.end())
349 ? makeUserPath(Path(fileIter
->second
)) : m_torrentInfo
.filePath(i
));
350 m_filePaths
.append(filePath
);
352 const auto priority
= LT::fromNative(filePriorities
[LT::toUnderlyingType(nativeIndex
)]);
353 m_filePriorities
.append(priority
);
357 setStopCondition(params
.stopCondition
);
359 const auto *extensionData
= static_cast<ExtensionData
*>(m_ltAddTorrentParams
.userdata
);
360 m_trackerEntryStatuses
.reserve(static_cast<decltype(m_trackerEntryStatuses
)::size_type
>(extensionData
->trackers
.size()));
361 for (const lt::announce_entry
&announceEntry
: extensionData
->trackers
)
362 m_trackerEntryStatuses
.append({QString::fromStdString(announceEntry
.url
), announceEntry
.tier
});
363 m_urlSeeds
.reserve(static_cast<decltype(m_urlSeeds
)::size_type
>(extensionData
->urlSeeds
.size()));
364 for (const std::string
&urlSeed
: extensionData
->urlSeeds
)
365 m_urlSeeds
.append(QString::fromStdString(urlSeed
));
366 m_nativeStatus
= extensionData
->status
;
374 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority
);
377 TorrentImpl::~TorrentImpl() = default;
379 bool TorrentImpl::isValid() const
381 return m_nativeHandle
.is_valid();
384 Session
*TorrentImpl::session() const
389 InfoHash
TorrentImpl::infoHash() const
394 QString
TorrentImpl::name() const
396 if (!m_name
.isEmpty())
400 return m_torrentInfo
.name();
402 const QString name
= QString::fromStdString(m_nativeStatus
.name
);
406 return id().toString();
409 QDateTime
TorrentImpl::creationDate() const
414 const std::time_t date
= nativeTorrentInfo()->creation_date();
415 return ((date
!= 0) ? QDateTime::fromSecsSinceEpoch(date
) : QDateTime());
418 QString
TorrentImpl::creator() const
423 return QString::fromStdString(nativeTorrentInfo()->creator());
426 QString
TorrentImpl::comment() const
431 return QString::fromStdString(nativeTorrentInfo()->comment());
434 bool TorrentImpl::isPrivate() const
436 return m_torrentInfo
.isPrivate();
439 qlonglong
TorrentImpl::totalSize() const
441 return m_torrentInfo
.totalSize();
444 // size without the "don't download" files
445 qlonglong
TorrentImpl::wantedSize() const
447 return m_nativeStatus
.total_wanted
;
450 qlonglong
TorrentImpl::completedSize() const
452 return m_nativeStatus
.total_wanted_done
;
455 qlonglong
TorrentImpl::pieceLength() const
457 return m_torrentInfo
.pieceLength();
460 qlonglong
TorrentImpl::wastedSize() const
462 return (m_nativeStatus
.total_failed_bytes
+ m_nativeStatus
.total_redundant_bytes
);
465 QString
TorrentImpl::currentTracker() const
467 return QString::fromStdString(m_nativeStatus
.current_tracker
);
470 Path
TorrentImpl::savePath() const
472 return isAutoTMMEnabled() ? m_session
->categorySavePath(category()) : m_savePath
;
475 void TorrentImpl::setSavePath(const Path
&path
)
477 Q_ASSERT(!isAutoTMMEnabled());
478 if (isAutoTMMEnabled()) [[unlikely
]]
481 const Path basePath
= m_session
->useCategoryPathsInManualMode()
482 ? m_session
->categorySavePath(category()) : m_session
->savePath();
483 const Path resolvedPath
= (path
.isAbsolute() ? path
: (basePath
/ path
));
484 if (resolvedPath
== savePath())
487 if (isFinished() || m_hasFinishedStatus
|| downloadPath().isEmpty())
489 moveStorage(resolvedPath
, MoveStorageContext::ChangeSavePath
);
493 m_savePath
= resolvedPath
;
494 m_session
->handleTorrentSavePathChanged(this);
495 deferredRequestResumeData();
499 Path
TorrentImpl::downloadPath() const
501 return isAutoTMMEnabled() ? m_session
->categoryDownloadPath(category()) : m_downloadPath
;
504 void TorrentImpl::setDownloadPath(const Path
&path
)
506 Q_ASSERT(!isAutoTMMEnabled());
507 if (isAutoTMMEnabled()) [[unlikely
]]
510 const Path basePath
= m_session
->useCategoryPathsInManualMode()
511 ? m_session
->categoryDownloadPath(category()) : m_session
->downloadPath();
512 const Path resolvedPath
= (path
.isEmpty() || path
.isAbsolute()) ? path
: (basePath
/ path
);
513 if (resolvedPath
== m_downloadPath
)
516 const bool isIncomplete
= !(isFinished() || m_hasFinishedStatus
);
519 moveStorage((resolvedPath
.isEmpty() ? savePath() : resolvedPath
), MoveStorageContext::ChangeDownloadPath
);
523 m_downloadPath
= resolvedPath
;
524 m_session
->handleTorrentSavePathChanged(this);
525 deferredRequestResumeData();
529 Path
TorrentImpl::rootPath() const
534 const Path relativeRootPath
= Path::findRootFolder(filePaths());
535 if (relativeRootPath
.isEmpty())
538 return (actualStorageLocation() / relativeRootPath
);
541 Path
TorrentImpl::contentPath() const
546 if (filesCount() == 1)
547 return (actualStorageLocation() / filePath(0));
549 const Path rootPath
= this->rootPath();
550 return (rootPath
.isEmpty() ? actualStorageLocation() : rootPath
);
553 bool TorrentImpl::isAutoTMMEnabled() const
558 void TorrentImpl::setAutoTMMEnabled(bool enabled
)
560 if (m_useAutoTMM
== enabled
)
563 m_useAutoTMM
= enabled
;
566 m_savePath
= m_session
->categorySavePath(category());
567 m_downloadPath
= m_session
->categoryDownloadPath(category());
570 deferredRequestResumeData();
571 m_session
->handleTorrentSavingModeChanged(this);
573 adjustStorageLocation();
576 Path
TorrentImpl::actualStorageLocation() const
581 return Path(m_nativeStatus
.save_path
);
584 void TorrentImpl::setAutoManaged(const bool enable
)
587 m_nativeHandle
.set_flags(lt::torrent_flags::auto_managed
);
589 m_nativeHandle
.unset_flags(lt::torrent_flags::auto_managed
);
592 Path
TorrentImpl::makeActualPath(int index
, const Path
&path
) const
594 Path actualPath
= path
;
596 if (m_session
->isAppendExtensionEnabled()
597 && (fileSize(index
) > 0) && !m_completedFiles
.at(index
))
599 actualPath
+= QB_EXT
;
602 if (m_session
->isUnwantedFolderEnabled()
603 && (m_filePriorities
[index
] == DownloadPriority::Ignored
))
605 const Path parentPath
= actualPath
.parentPath();
606 const QString fileName
= actualPath
.filename();
607 actualPath
= parentPath
/ Path(UNWANTED_FOLDER_NAME
) / Path(fileName
);
613 Path
TorrentImpl::makeUserPath(const Path
&path
) const
615 Path userPath
= path
.removedExtension(QB_EXT
);
617 const Path parentRelPath
= userPath
.parentPath();
618 if (parentRelPath
.filename() == UNWANTED_FOLDER_NAME
)
620 const QString fileName
= userPath
.filename();
621 const Path relPath
= parentRelPath
.parentPath();
622 userPath
= relPath
/ Path(fileName
);
628 QList
<TrackerEntryStatus
> TorrentImpl::trackers() const
630 return m_trackerEntryStatuses
;
633 void TorrentImpl::addTrackers(QList
<TrackerEntry
> trackers
)
635 trackers
.removeIf([](const TrackerEntry
&trackerEntry
) { return trackerEntry
.url
.isEmpty(); });
637 QSet
<TrackerEntry
> currentTrackerSet
;
638 currentTrackerSet
.reserve(m_trackerEntryStatuses
.size());
639 for (const TrackerEntryStatus
&status
: asConst(m_trackerEntryStatuses
))
640 currentTrackerSet
.insert({.url
= status
.url
, .tier
= status
.tier
});
642 const auto newTrackerSet
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend()) - currentTrackerSet
;
643 if (newTrackerSet
.isEmpty())
646 trackers
= QList
<TrackerEntry
>(newTrackerSet
.cbegin(), newTrackerSet
.cend());
647 for (const TrackerEntry
&tracker
: asConst(trackers
))
649 m_nativeHandle
.add_tracker(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
650 m_trackerEntryStatuses
.append({tracker
.url
, tracker
.tier
});
652 std::sort(m_trackerEntryStatuses
.begin(), m_trackerEntryStatuses
.end()
653 , [](const TrackerEntryStatus
&left
, const TrackerEntryStatus
&right
) { return left
.tier
< right
.tier
; });
655 deferredRequestResumeData();
656 m_session
->handleTorrentTrackersAdded(this, trackers
);
659 void TorrentImpl::removeTrackers(const QStringList
&trackers
)
661 QStringList removedTrackers
= trackers
;
662 for (const QString
&tracker
: trackers
)
664 if (!m_trackerEntryStatuses
.removeOne({tracker
}))
665 removedTrackers
.removeOne(tracker
);
668 std::vector
<lt::announce_entry
> nativeTrackers
;
669 nativeTrackers
.reserve(m_trackerEntryStatuses
.size());
670 for (const TrackerEntryStatus
&tracker
: asConst(m_trackerEntryStatuses
))
671 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
673 if (!removedTrackers
.isEmpty())
675 m_nativeHandle
.replace_trackers(nativeTrackers
);
677 deferredRequestResumeData();
678 m_session
->handleTorrentTrackersRemoved(this, removedTrackers
);
682 void TorrentImpl::replaceTrackers(QList
<TrackerEntry
> trackers
)
684 trackers
.removeIf([](const TrackerEntry
&trackerEntry
) { return trackerEntry
.url
.isEmpty(); });
686 // Filter out duplicate trackers
687 const auto uniqueTrackers
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend());
688 trackers
= QList
<TrackerEntry
>(uniqueTrackers
.cbegin(), uniqueTrackers
.cend());
689 std::sort(trackers
.begin(), trackers
.end()
690 , [](const TrackerEntry
&left
, const TrackerEntry
&right
) { return left
.tier
< right
.tier
; });
692 std::vector
<lt::announce_entry
> nativeTrackers
;
693 nativeTrackers
.reserve(trackers
.size());
694 m_trackerEntryStatuses
.clear();
696 for (const TrackerEntry
&tracker
: trackers
)
698 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
699 m_trackerEntryStatuses
.append({tracker
.url
, tracker
.tier
});
702 m_nativeHandle
.replace_trackers(nativeTrackers
);
704 // Clear the peer list if it's a private torrent since
705 // we do not want to keep connecting with peers from old tracker.
709 deferredRequestResumeData();
710 m_session
->handleTorrentTrackersChanged(this);
713 QList
<QUrl
> TorrentImpl::urlSeeds() const
718 void TorrentImpl::addUrlSeeds(const QList
<QUrl
> &urlSeeds
)
720 m_session
->invokeAsync([urlSeeds
, session
= m_session
721 , nativeHandle
= m_nativeHandle
722 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
726 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
727 QList
<QUrl
> currentSeeds
;
728 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
729 for (const std::string
&urlSeed
: nativeSeeds
)
730 currentSeeds
.append(QString::fromStdString(urlSeed
));
732 QList
<QUrl
> addedUrlSeeds
;
733 addedUrlSeeds
.reserve(urlSeeds
.size());
735 for (const QUrl
&url
: urlSeeds
)
737 if (!currentSeeds
.contains(url
))
739 nativeHandle
.add_url_seed(url
.toString().toStdString());
740 addedUrlSeeds
.append(url
);
744 currentSeeds
.append(addedUrlSeeds
);
745 session
->invoke([session
, thisTorrent
, currentSeeds
, addedUrlSeeds
]
750 thisTorrent
->m_urlSeeds
= currentSeeds
;
751 if (!addedUrlSeeds
.isEmpty())
753 thisTorrent
->deferredRequestResumeData();
754 session
->handleTorrentUrlSeedsAdded(thisTorrent
, addedUrlSeeds
);
758 catch (const std::exception
&) {}
762 void TorrentImpl::removeUrlSeeds(const QList
<QUrl
> &urlSeeds
)
764 m_session
->invokeAsync([urlSeeds
, session
= m_session
765 , nativeHandle
= m_nativeHandle
766 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
770 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
771 QList
<QUrl
> currentSeeds
;
772 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
773 for (const std::string
&urlSeed
: nativeSeeds
)
774 currentSeeds
.append(QString::fromStdString(urlSeed
));
776 QList
<QUrl
> removedUrlSeeds
;
777 removedUrlSeeds
.reserve(urlSeeds
.size());
779 for (const QUrl
&url
: urlSeeds
)
781 if (currentSeeds
.removeOne(url
))
783 nativeHandle
.remove_url_seed(url
.toString().toStdString());
784 removedUrlSeeds
.append(url
);
788 session
->invoke([session
, thisTorrent
, currentSeeds
, removedUrlSeeds
]
793 thisTorrent
->m_urlSeeds
= currentSeeds
;
795 if (!removedUrlSeeds
.isEmpty())
797 thisTorrent
->deferredRequestResumeData();
798 session
->handleTorrentUrlSeedsRemoved(thisTorrent
, removedUrlSeeds
);
802 catch (const std::exception
&) {}
806 void TorrentImpl::clearPeers()
808 m_nativeHandle
.clear_peers();
811 bool TorrentImpl::connectPeer(const PeerAddress
&peerAddress
)
814 const lt::address addr
= lt::make_address(peerAddress
.ip
.toString().toStdString(), ec
);
815 if (ec
) return false;
817 const lt::tcp::endpoint
endpoint(addr
, peerAddress
.port
);
820 m_nativeHandle
.connect_peer(endpoint
);
822 catch (const lt::system_error
&err
)
824 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
825 .arg(peerAddress
.toString(), name(), QString::fromLocal8Bit(err
.what())), Log::WARNING
);
829 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress
.toString(), name()));
833 bool TorrentImpl::needSaveResumeData() const
835 return m_nativeStatus
.need_save_resume
;
838 void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags
)
840 m_nativeHandle
.save_resume_data(flags
);
841 m_deferredRequestResumeDataInvoked
= false;
843 m_session
->handleTorrentResumeDataRequested(this);
846 void TorrentImpl::deferredRequestResumeData()
848 if (!m_deferredRequestResumeDataInvoked
)
850 QMetaObject::invokeMethod(this, [this]
852 requestResumeData((m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
853 ? lt::torrent_handle::save_info_dict
: lt::resume_data_flags_t());
854 }, Qt::QueuedConnection
);
856 m_deferredRequestResumeDataInvoked
= true;
860 int TorrentImpl::filesCount() const
862 return m_torrentInfo
.filesCount();
865 int TorrentImpl::piecesCount() const
867 return m_torrentInfo
.piecesCount();
870 int TorrentImpl::piecesHave() const
872 return m_nativeStatus
.num_pieces
;
875 qreal
TorrentImpl::progress() const
878 return m_nativeStatus
.progress
;
880 if (m_nativeStatus
.total_wanted
== 0)
883 if (m_nativeStatus
.total_wanted_done
== m_nativeStatus
.total_wanted
)
886 const qreal progress
= static_cast<qreal
>(m_nativeStatus
.total_wanted_done
) / m_nativeStatus
.total_wanted
;
887 if ((progress
< 0.f
) || (progress
> 1.f
))
889 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
890 .arg(name(), QString::number(m_nativeStatus
.total_wanted
), QString::number(m_nativeStatus
.total_wanted_done
))
897 QString
TorrentImpl::category() const
902 bool TorrentImpl::belongsToCategory(const QString
&category
) const
904 if (m_category
.isEmpty())
905 return category
.isEmpty();
907 if (m_category
== category
)
910 return (m_session
->isSubcategoriesEnabled() && m_category
.startsWith(category
+ u
'/'));
913 TagSet
TorrentImpl::tags() const
918 bool TorrentImpl::hasTag(const Tag
&tag
) const
920 return m_tags
.contains(tag
);
923 bool TorrentImpl::addTag(const Tag
&tag
)
930 if (!m_session
->hasTag(tag
))
932 if (!m_session
->addTag(tag
))
936 deferredRequestResumeData();
937 m_session
->handleTorrentTagAdded(this, tag
);
941 bool TorrentImpl::removeTag(const Tag
&tag
)
943 if (m_tags
.remove(tag
))
945 deferredRequestResumeData();
946 m_session
->handleTorrentTagRemoved(this, tag
);
952 void TorrentImpl::removeAllTags()
954 for (const Tag
&tag
: asConst(tags()))
958 QDateTime
TorrentImpl::addedTime() const
960 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.added_time
);
963 qreal
TorrentImpl::ratioLimit() const
968 int TorrentImpl::seedingTimeLimit() const
970 return m_seedingTimeLimit
;
973 int TorrentImpl::inactiveSeedingTimeLimit() const
975 return m_inactiveSeedingTimeLimit
;
978 Path
TorrentImpl::filePath(const int index
) const
980 Q_ASSERT(index
>= 0);
981 Q_ASSERT(index
< m_filePaths
.size());
983 return m_filePaths
.value(index
, {});
986 Path
TorrentImpl::actualFilePath(const int index
) const
988 const QList
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
990 Q_ASSERT(index
>= 0);
991 Q_ASSERT(index
< nativeIndexes
.size());
992 if ((index
< 0) || (index
>= nativeIndexes
.size()))
995 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes
[index
]));
998 qlonglong
TorrentImpl::fileSize(const int index
) const
1000 return m_torrentInfo
.fileSize(index
);
1003 PathList
TorrentImpl::filePaths() const
1008 PathList
TorrentImpl::actualFilePaths() const
1014 paths
.reserve(filesCount());
1016 const lt::file_storage files
= nativeTorrentInfo()->files();
1017 for (const lt::file_index_t
&nativeIndex
: asConst(m_torrentInfo
.nativeIndexes()))
1018 paths
.emplaceBack(files
.file_path(nativeIndex
));
1023 QList
<DownloadPriority
> TorrentImpl::filePriorities() const
1025 return m_filePriorities
;
1028 TorrentInfo
TorrentImpl::info() const
1030 return m_torrentInfo
;
1033 bool TorrentImpl::isStopped() const
1038 bool TorrentImpl::isQueued() const
1040 if (!m_session
->isQueueingSystemEnabled())
1043 // Torrent is Queued if it isn't in Stopped state but paused internally
1044 return (!isStopped()
1045 && (m_nativeStatus
.flags
& lt::torrent_flags::auto_managed
)
1046 && (m_nativeStatus
.flags
& lt::torrent_flags::paused
));
1049 bool TorrentImpl::isChecking() const
1051 return ((m_nativeStatus
.state
== lt::torrent_status::checking_files
)
1052 || (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
));
1055 bool TorrentImpl::isDownloading() const
1059 case TorrentState::Downloading
:
1060 case TorrentState::DownloadingMetadata
:
1061 case TorrentState::ForcedDownloadingMetadata
:
1062 case TorrentState::StalledDownloading
:
1063 case TorrentState::CheckingDownloading
:
1064 case TorrentState::StoppedDownloading
:
1065 case TorrentState::QueuedDownloading
:
1066 case TorrentState::ForcedDownloading
:
1075 bool TorrentImpl::isMoving() const
1077 return m_state
== TorrentState::Moving
;
1080 bool TorrentImpl::isUploading() const
1084 case TorrentState::Uploading
:
1085 case TorrentState::StalledUploading
:
1086 case TorrentState::CheckingUploading
:
1087 case TorrentState::QueuedUploading
:
1088 case TorrentState::ForcedUploading
:
1097 bool TorrentImpl::isCompleted() const
1101 case TorrentState::Uploading
:
1102 case TorrentState::StalledUploading
:
1103 case TorrentState::CheckingUploading
:
1104 case TorrentState::StoppedUploading
:
1105 case TorrentState::QueuedUploading
:
1106 case TorrentState::ForcedUploading
:
1115 bool TorrentImpl::isActive() const
1119 case TorrentState::StalledDownloading
:
1120 return (uploadPayloadRate() > 0);
1122 case TorrentState::DownloadingMetadata
:
1123 case TorrentState::ForcedDownloadingMetadata
:
1124 case TorrentState::Downloading
:
1125 case TorrentState::ForcedDownloading
:
1126 case TorrentState::Uploading
:
1127 case TorrentState::ForcedUploading
:
1128 case TorrentState::Moving
:
1138 bool TorrentImpl::isInactive() const
1143 bool TorrentImpl::isErrored() const
1145 return ((m_state
== TorrentState::MissingFiles
)
1146 || (m_state
== TorrentState::Error
));
1149 bool TorrentImpl::isFinished() const
1151 return ((m_nativeStatus
.state
== lt::torrent_status::finished
)
1152 || (m_nativeStatus
.state
== lt::torrent_status::seeding
));
1155 bool TorrentImpl::isForced() const
1157 return (!isStopped() && (m_operatingMode
== TorrentOperatingMode::Forced
));
1160 bool TorrentImpl::isSequentialDownload() const
1162 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::sequential_download
);
1165 bool TorrentImpl::hasFirstLastPiecePriority() const
1167 return m_hasFirstLastPiecePriority
;
1170 TorrentState
TorrentImpl::state() const
1175 void TorrentImpl::updateState()
1177 if (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
)
1179 m_state
= TorrentState::CheckingResumeData
;
1181 else if (isMoveInProgress())
1183 m_state
= TorrentState::Moving
;
1185 else if (hasMissingFiles())
1187 m_state
= TorrentState::MissingFiles
;
1189 else if (hasError())
1191 m_state
= TorrentState::Error
;
1193 else if (!hasMetadata())
1196 m_state
= TorrentState::StoppedDownloading
;
1197 else if (isQueued())
1198 m_state
= TorrentState::QueuedDownloading
;
1200 m_state
= isForced() ? TorrentState::ForcedDownloadingMetadata
: TorrentState::DownloadingMetadata
;
1202 else if ((m_nativeStatus
.state
== lt::torrent_status::checking_files
) && !isStopped())
1204 // If the torrent is not just in the "checking" state, but is being actually checked
1205 m_state
= m_hasFinishedStatus
? TorrentState::CheckingUploading
: TorrentState::CheckingDownloading
;
1207 else if (isFinished())
1210 m_state
= TorrentState::StoppedUploading
;
1211 else if (isQueued())
1212 m_state
= TorrentState::QueuedUploading
;
1213 else if (isForced())
1214 m_state
= TorrentState::ForcedUploading
;
1215 else if (m_nativeStatus
.upload_payload_rate
> 0)
1216 m_state
= TorrentState::Uploading
;
1218 m_state
= TorrentState::StalledUploading
;
1223 m_state
= TorrentState::StoppedDownloading
;
1224 else if (isQueued())
1225 m_state
= TorrentState::QueuedDownloading
;
1226 else if (isForced())
1227 m_state
= TorrentState::ForcedDownloading
;
1228 else if (m_nativeStatus
.download_payload_rate
> 0)
1229 m_state
= TorrentState::Downloading
;
1231 m_state
= TorrentState::StalledDownloading
;
1235 bool TorrentImpl::hasMetadata() const
1237 return m_torrentInfo
.isValid();
1240 bool TorrentImpl::hasMissingFiles() const
1242 return m_hasMissingFiles
;
1245 bool TorrentImpl::hasError() const
1247 return (m_nativeStatus
.errc
|| (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
));
1250 int TorrentImpl::queuePosition() const
1252 return static_cast<int>(m_nativeStatus
.queue_position
);
1255 QString
TorrentImpl::error() const
1257 if (m_nativeStatus
.errc
)
1258 return QString::fromLocal8Bit(m_nativeStatus
.errc
.message().c_str());
1260 if (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
)
1262 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1263 .arg(QString::fromLocal8Bit(m_lastFileError
.error
.message().c_str()));
1269 qlonglong
TorrentImpl::totalDownload() const
1271 return m_nativeStatus
.all_time_download
;
1274 qlonglong
TorrentImpl::totalUpload() const
1276 return m_nativeStatus
.all_time_upload
;
1279 qlonglong
TorrentImpl::activeTime() const
1281 return lt::total_seconds(m_nativeStatus
.active_duration
);
1284 qlonglong
TorrentImpl::finishedTime() const
1286 return lt::total_seconds(m_nativeStatus
.finished_duration
);
1289 qlonglong
TorrentImpl::eta() const
1291 if (isStopped()) return MAX_ETA
;
1293 const SpeedSampleAvg speedAverage
= m_payloadRateMonitor
.average();
1297 const qreal maxRatioValue
= maxRatio();
1298 const int maxSeedingTimeValue
= maxSeedingTime();
1299 const int maxInactiveSeedingTimeValue
= maxInactiveSeedingTime();
1300 if ((maxRatioValue
< 0) && (maxSeedingTimeValue
< 0) && (maxInactiveSeedingTimeValue
< 0)) return MAX_ETA
;
1302 qlonglong ratioEta
= MAX_ETA
;
1304 if ((speedAverage
.upload
> 0) && (maxRatioValue
>= 0))
1307 qlonglong realDL
= totalDownload();
1309 realDL
= wantedSize();
1311 ratioEta
= ((realDL
* maxRatioValue
) - totalUpload()) / speedAverage
.upload
;
1314 qlonglong seedingTimeEta
= MAX_ETA
;
1316 if (maxSeedingTimeValue
>= 0)
1318 seedingTimeEta
= (maxSeedingTimeValue
* 60) - finishedTime();
1319 if (seedingTimeEta
< 0)
1323 qlonglong inactiveSeedingTimeEta
= MAX_ETA
;
1325 if (maxInactiveSeedingTimeValue
>= 0)
1327 inactiveSeedingTimeEta
= (maxInactiveSeedingTimeValue
* 60) - timeSinceActivity();
1328 inactiveSeedingTimeEta
= std::max
<qlonglong
>(inactiveSeedingTimeEta
, 0);
1331 return std::min({ratioEta
, seedingTimeEta
, inactiveSeedingTimeEta
});
1334 if (!speedAverage
.download
) return MAX_ETA
;
1336 return (wantedSize() - completedSize()) / speedAverage
.download
;
1339 QList
<qreal
> TorrentImpl::filesProgress() const
1344 const int count
= m_filesProgress
.size();
1345 Q_ASSERT(count
== filesCount());
1346 if (count
!= filesCount()) [[unlikely
]]
1349 if (m_completedFiles
.count(true) == count
)
1350 return QList
<qreal
>(count
, 1);
1352 QList
<qreal
> result
;
1353 result
.reserve(count
);
1354 for (int i
= 0; i
< count
; ++i
)
1356 const int64_t progress
= m_filesProgress
.at(i
);
1357 const int64_t size
= fileSize(i
);
1358 if ((size
<= 0) || (progress
== size
))
1361 result
<< (progress
/ static_cast<qreal
>(size
));
1367 int TorrentImpl::seedsCount() const
1369 return m_nativeStatus
.num_seeds
;
1372 int TorrentImpl::peersCount() const
1374 return m_nativeStatus
.num_peers
;
1377 int TorrentImpl::leechsCount() const
1379 return (m_nativeStatus
.num_peers
- m_nativeStatus
.num_seeds
);
1382 int TorrentImpl::totalSeedsCount() const
1384 return (m_nativeStatus
.num_complete
> -1) ? m_nativeStatus
.num_complete
: m_nativeStatus
.list_seeds
;
1387 int TorrentImpl::totalPeersCount() const
1389 const int peers
= m_nativeStatus
.num_complete
+ m_nativeStatus
.num_incomplete
;
1390 return (peers
> -1) ? peers
: m_nativeStatus
.list_peers
;
1393 int TorrentImpl::totalLeechersCount() const
1395 return (m_nativeStatus
.num_incomplete
> -1) ? m_nativeStatus
.num_incomplete
: (m_nativeStatus
.list_peers
- m_nativeStatus
.list_seeds
);
1398 QDateTime
TorrentImpl::lastSeenComplete() const
1400 if (m_nativeStatus
.last_seen_complete
> 0)
1401 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.last_seen_complete
);
1406 QDateTime
TorrentImpl::completedTime() const
1408 if (m_nativeStatus
.completed_time
> 0)
1409 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.completed_time
);
1414 qlonglong
TorrentImpl::timeSinceUpload() const
1416 if (m_nativeStatus
.last_upload
.time_since_epoch().count() == 0)
1418 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_upload
);
1421 qlonglong
TorrentImpl::timeSinceDownload() const
1423 if (m_nativeStatus
.last_download
.time_since_epoch().count() == 0)
1425 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_download
);
1428 qlonglong
TorrentImpl::timeSinceActivity() const
1430 const qlonglong upTime
= timeSinceUpload();
1431 const qlonglong downTime
= timeSinceDownload();
1432 return ((upTime
< 0) != (downTime
< 0))
1433 ? std::max(upTime
, downTime
)
1434 : std::min(upTime
, downTime
);
1437 int TorrentImpl::downloadLimit() const
1439 return m_downloadLimit
;;
1442 int TorrentImpl::uploadLimit() const
1444 return m_uploadLimit
;
1447 bool TorrentImpl::superSeeding() const
1449 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::super_seeding
);
1452 bool TorrentImpl::isDHTDisabled() const
1454 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_dht
);
1457 bool TorrentImpl::isPEXDisabled() const
1459 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_pex
);
1462 bool TorrentImpl::isLSDDisabled() const
1464 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_lsd
);
1467 QList
<PeerInfo
> TorrentImpl::peers() const
1469 std::vector
<lt::peer_info
> nativePeers
;
1470 m_nativeHandle
.get_peer_info(nativePeers
);
1472 QList
<PeerInfo
> peers
;
1473 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
1475 for (const lt::peer_info
&peer
: nativePeers
)
1476 peers
.append(PeerInfo(peer
, pieces()));
1481 QBitArray
TorrentImpl::pieces() const
1486 QBitArray
TorrentImpl::downloadingPieces() const
1491 std::vector
<lt::partial_piece_info
> queue
;
1492 m_nativeHandle
.get_download_queue(queue
);
1494 QBitArray result
{piecesCount()};
1495 for (const lt::partial_piece_info
&info
: queue
)
1496 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
1501 QList
<int> TorrentImpl::pieceAvailability() const
1503 std::vector
<int> avail
;
1504 m_nativeHandle
.piece_availability(avail
);
1506 return {avail
.cbegin(), avail
.cend()};
1509 qreal
TorrentImpl::distributedCopies() const
1511 return m_nativeStatus
.distributed_copies
;
1514 qreal
TorrentImpl::maxRatio() const
1516 if (m_ratioLimit
== USE_GLOBAL_RATIO
)
1517 return m_session
->globalMaxRatio();
1519 return m_ratioLimit
;
1522 int TorrentImpl::maxSeedingTime() const
1524 if (m_seedingTimeLimit
== USE_GLOBAL_SEEDING_TIME
)
1525 return m_session
->globalMaxSeedingMinutes();
1527 return m_seedingTimeLimit
;
1530 int TorrentImpl::maxInactiveSeedingTime() const
1532 if (m_inactiveSeedingTimeLimit
== USE_GLOBAL_INACTIVE_SEEDING_TIME
)
1533 return m_session
->globalMaxInactiveSeedingMinutes();
1535 return m_inactiveSeedingTimeLimit
;
1538 qreal
TorrentImpl::realRatio() const
1540 const int64_t upload
= m_nativeStatus
.all_time_upload
;
1541 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1542 const int64_t download
= (m_nativeStatus
.all_time_download
< (m_nativeStatus
.total_done
* 0.01))
1543 ? m_nativeStatus
.total_done
1544 : m_nativeStatus
.all_time_download
;
1547 return (upload
== 0) ? 0 : MAX_RATIO
;
1549 const qreal ratio
= upload
/ static_cast<qreal
>(download
);
1550 Q_ASSERT(ratio
>= 0);
1551 return (ratio
> MAX_RATIO
) ? MAX_RATIO
: ratio
;
1554 int TorrentImpl::uploadPayloadRate() const
1556 // workaround: suppress the speed for Stopped state
1557 return isStopped() ? 0 : m_nativeStatus
.upload_payload_rate
;
1560 int TorrentImpl::downloadPayloadRate() const
1562 // workaround: suppress the speed for Stopped state
1563 return isStopped() ? 0 : m_nativeStatus
.download_payload_rate
;
1566 qlonglong
TorrentImpl::totalPayloadUpload() const
1568 return m_nativeStatus
.total_payload_upload
;
1571 qlonglong
TorrentImpl::totalPayloadDownload() const
1573 return m_nativeStatus
.total_payload_download
;
1576 int TorrentImpl::connectionsCount() const
1578 return m_nativeStatus
.num_connections
;
1581 int TorrentImpl::connectionsLimit() const
1583 return m_nativeStatus
.connections_limit
;
1586 qlonglong
TorrentImpl::nextAnnounce() const
1588 return lt::total_seconds(m_nativeStatus
.next_announce
);
1591 qreal
TorrentImpl::popularity() const
1593 // in order to produce floating-point numbers using `std::chrono::duration_cast`,
1594 // we should use `qreal` as `Rep` to define the `months` duration
1595 using months
= std::chrono::duration
<qreal
, std::chrono::months::period
>;
1596 const auto activeMonths
= std::chrono::duration_cast
<months
>(m_nativeStatus
.active_duration
).count();
1597 return (activeMonths
> 0) ? (realRatio() / activeMonths
) : 0;
1600 void TorrentImpl::setName(const QString
&name
)
1605 deferredRequestResumeData();
1606 m_session
->handleTorrentNameChanged(this);
1610 bool TorrentImpl::setCategory(const QString
&category
)
1612 if (m_category
!= category
)
1614 if (!category
.isEmpty() && !m_session
->categories().contains(category
))
1617 const QString oldCategory
= m_category
;
1618 m_category
= category
;
1619 deferredRequestResumeData();
1620 m_session
->handleTorrentCategoryChanged(this, oldCategory
);
1624 if (!m_session
->isDisableAutoTMMWhenCategoryChanged())
1625 adjustStorageLocation();
1627 setAutoTMMEnabled(false);
1634 void TorrentImpl::forceReannounce(const int index
)
1636 m_nativeHandle
.force_reannounce(0, index
);
1639 void TorrentImpl::forceDHTAnnounce()
1641 m_nativeHandle
.force_dht_announce();
1644 void TorrentImpl::forceRecheck()
1649 m_nativeHandle
.force_recheck();
1650 // We have to force update the cached state, otherwise someone will be able to get
1651 // an incorrect one during the interval until the cached state is updated in a regular way.
1652 m_nativeStatus
.state
= lt::torrent_status::checking_resume_data
;
1654 if (m_hasMissingFiles
)
1656 m_hasMissingFiles
= false;
1659 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1660 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1661 m_nativeHandle
.resume();
1665 m_unchecked
= false;
1667 m_completedFiles
.fill(false);
1668 m_filesProgress
.fill(0);
1669 m_pieces
.fill(false);
1670 m_nativeStatus
.pieces
.clear_all();
1671 m_nativeStatus
.num_pieces
= 0;
1675 // When "force recheck" is applied on Stopped torrent, we start them to perform checking
1677 m_stopCondition
= StopCondition::FilesChecked
;
1681 void TorrentImpl::setSequentialDownload(const bool enable
)
1685 m_nativeHandle
.set_flags(lt::torrent_flags::sequential_download
);
1686 m_nativeStatus
.flags
|= lt::torrent_flags::sequential_download
; // prevent return cached value
1690 m_nativeHandle
.unset_flags(lt::torrent_flags::sequential_download
);
1691 m_nativeStatus
.flags
&= ~lt::torrent_flags::sequential_download
; // prevent return cached value
1694 deferredRequestResumeData();
1697 void TorrentImpl::setFirstLastPiecePriority(const bool enabled
)
1699 if (m_hasFirstLastPiecePriority
== enabled
)
1702 m_hasFirstLastPiecePriority
= enabled
;
1704 applyFirstLastPiecePriority(enabled
);
1706 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1707 .arg((enabled
? tr("On") : tr("Off")), name()));
1709 deferredRequestResumeData();
1712 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled
)
1714 Q_ASSERT(hasMetadata());
1716 // Download first and last pieces first for every file in the torrent
1718 auto piecePriorities
= std::vector
<lt::download_priority_t
>(m_torrentInfo
.piecesCount(), LT::toNative(DownloadPriority::Ignored
));
1720 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1721 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1722 for (int fileIndex
= 0; fileIndex
< m_filePriorities
.size(); ++fileIndex
)
1724 const DownloadPriority filePrio
= m_filePriorities
[fileIndex
];
1725 if (filePrio
<= DownloadPriority::Ignored
)
1728 // Determine the priority to set
1729 const lt::download_priority_t piecePrio
= LT::toNative(enabled
? DownloadPriority::Maximum
: filePrio
);
1730 const TorrentInfo::PieceRange pieceRange
= m_torrentInfo
.filePieces(fileIndex
);
1732 // worst case: AVI index = 1% of total file size (at the end of the file)
1733 const int numPieces
= std::ceil(fileSize(fileIndex
) * 0.01 / pieceLength());
1734 for (int i
= 0; i
< numPieces
; ++i
)
1736 piecePriorities
[pieceRange
.first() + i
] = piecePrio
;
1737 piecePriorities
[pieceRange
.last() - i
] = piecePrio
;
1740 const int firstPiece
= pieceRange
.first() + numPieces
;
1741 const int lastPiece
= pieceRange
.last() - numPieces
;
1742 for (int pieceIndex
= firstPiece
; pieceIndex
<= lastPiece
; ++pieceIndex
)
1743 piecePriorities
[pieceIndex
] = LT::toNative(filePrio
);
1746 m_nativeHandle
.prioritize_pieces(piecePriorities
);
1749 void TorrentImpl::fileSearchFinished(const Path
&savePath
, const PathList
&fileNames
)
1751 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
1752 endReceivedMetadataHandling(savePath
, fileNames
);
1755 TrackerEntryStatus
TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry
&announceEntry
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
1757 const auto it
= std::find_if(m_trackerEntryStatuses
.begin(), m_trackerEntryStatuses
.end()
1758 , [&announceEntry
](const TrackerEntryStatus
&trackerEntryStatus
)
1760 return (trackerEntryStatus
.url
== QString::fromStdString(announceEntry
.url
));
1763 Q_ASSERT(it
!= m_trackerEntryStatuses
.end());
1764 if (it
== m_trackerEntryStatuses
.end()) [[unlikely
]]
1767 #ifdef QBT_USES_LIBTORRENT2
1768 QSet
<int> btProtocols
;
1769 const auto &infoHashes
= nativeHandle().info_hashes();
1770 if (infoHashes
.has(lt::protocol_version::V1
))
1771 btProtocols
.insert(1);
1772 if (infoHashes
.has(lt::protocol_version::V2
))
1773 btProtocols
.insert(2);
1775 const QSet
<int> btProtocols
{1};
1778 const auto fromLTTimePoint32
= [this](const lt::time_point32
&timePoint
)
1780 return m_session
->fromLTTimePoint32(timePoint
);
1782 ::updateTrackerEntryStatus(*it
, announceEntry
, btProtocols
, updateInfo
, fromLTTimePoint32
);
1787 void TorrentImpl::resetTrackerEntryStatuses()
1789 for (TrackerEntryStatus
&status
: m_trackerEntryStatuses
)
1791 const QString tempUrl
= status
.url
;
1792 const int tempTier
= status
.tier
;
1795 status
.url
= tempUrl
;
1796 status
.tier
= tempTier
;
1800 std::shared_ptr
<const libtorrent::torrent_info
> TorrentImpl::nativeTorrentInfo() const
1802 Q_ASSERT(!m_nativeStatus
.torrent_file
.expired());
1804 return m_nativeStatus
.torrent_file
.lock();
1807 void TorrentImpl::endReceivedMetadataHandling(const Path
&savePath
, const PathList
&fileNames
)
1809 Q_ASSERT(m_maintenanceJob
== MaintenanceJob::HandleMetadata
);
1810 if (m_maintenanceJob
!= MaintenanceJob::HandleMetadata
) [[unlikely
]]
1813 Q_ASSERT(m_filePaths
.isEmpty());
1814 if (!m_filePaths
.isEmpty()) [[unlikely
]]
1815 m_filePaths
.clear();
1817 lt::add_torrent_params
&p
= m_ltAddTorrentParams
;
1819 const std::shared_ptr
<lt::torrent_info
> metadata
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1820 m_torrentInfo
= TorrentInfo(*metadata
);
1821 m_filePriorities
.reserve(filesCount());
1822 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
1823 p
.file_priorities
= resized(p
.file_priorities
, metadata
->files().num_files()
1824 , LT::toNative(p
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
1826 m_completedFiles
.fill(static_cast<bool>(p
.flags
& lt::torrent_flags::seed_mode
), filesCount());
1827 m_filesProgress
.resize(filesCount());
1830 for (int i
= 0; i
< fileNames
.size(); ++i
)
1832 const auto nativeIndex
= nativeIndexes
.at(i
);
1834 const Path
&actualFilePath
= fileNames
.at(i
);
1835 p
.renamed_files
[nativeIndex
] = actualFilePath
.toString().toStdString();
1837 const Path filePath
= actualFilePath
.removedExtension(QB_EXT
);
1838 m_filePaths
.append(filePath
);
1840 m_filePriorities
.append(LT::fromNative(p
.file_priorities
[LT::toUnderlyingType(nativeIndex
)]));
1843 m_session
->applyFilenameFilter(m_filePaths
, m_filePriorities
);
1844 for (int i
= 0; i
< m_filePriorities
.size(); ++i
)
1845 p
.file_priorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(m_filePriorities
[i
]);
1847 p
.save_path
= savePath
.toString().toStdString();
1850 if (stopCondition() == StopCondition::MetadataReceived
)
1852 m_stopCondition
= StopCondition::None
;
1855 p
.flags
|= lt::torrent_flags::paused
;
1856 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1858 m_session
->handleTorrentStopped(this);
1863 // If first/last piece priority was specified when adding this torrent,
1864 // we should apply it now that we have metadata:
1865 if (m_hasFirstLastPiecePriority
)
1866 applyFirstLastPiecePriority(true);
1868 m_maintenanceJob
= MaintenanceJob::None
;
1869 prepareResumeData(p
);
1871 m_session
->handleTorrentMetadataReceived(this);
1874 void TorrentImpl::reload()
1878 m_completedFiles
.fill(false);
1879 m_filesProgress
.fill(0);
1880 m_pieces
.fill(false);
1881 m_nativeStatus
.pieces
.clear_all();
1882 m_nativeStatus
.num_pieces
= 0;
1884 const auto queuePos
= m_nativeHandle
.queue_position();
1886 m_nativeSession
->remove_torrent(m_nativeHandle
, lt::session::delete_partfile
);
1888 lt::add_torrent_params p
= m_ltAddTorrentParams
;
1889 p
.flags
|= lt::torrent_flags::update_subscribe
1890 | lt::torrent_flags::override_trackers
1891 | lt::torrent_flags::override_web_seeds
;
1895 p
.flags
|= lt::torrent_flags::paused
;
1896 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1898 else if (m_operatingMode
== TorrentOperatingMode::AutoManaged
)
1900 p
.flags
|= (lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1904 p
.flags
&= ~(lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1907 auto *const extensionData
= new ExtensionData
;
1908 p
.userdata
= LTClientData(extensionData
);
1909 #ifndef QBT_USES_LIBTORRENT2
1910 p
.storage
= customStorageConstructor
;
1912 m_nativeHandle
= m_nativeSession
->add_torrent(p
);
1914 m_nativeStatus
= extensionData
->status
;
1916 if (queuePos
>= lt::queue_position_t
{})
1917 m_nativeHandle
.queue_position_set(queuePos
);
1918 m_nativeStatus
.queue_position
= queuePos
;
1922 catch (const lt::system_error
&err
)
1924 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1925 .arg(id().toString(), QString::fromLocal8Bit(err
.what())));
1929 void TorrentImpl::stop()
1933 m_stopCondition
= StopCondition::None
;
1935 deferredRequestResumeData();
1936 m_session
->handleTorrentStopped(this);
1939 if (m_maintenanceJob
== MaintenanceJob::None
)
1941 setAutoManaged(false);
1942 m_nativeHandle
.pause();
1944 m_payloadRateMonitor
.reset();
1948 void TorrentImpl::start(const TorrentOperatingMode mode
)
1952 m_nativeHandle
.clear_error();
1953 m_nativeHandle
.unset_flags(lt::torrent_flags::upload_mode
);
1956 m_operatingMode
= mode
;
1958 if (m_hasMissingFiles
)
1960 m_hasMissingFiles
= false;
1961 m_isStopped
= false;
1962 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1969 m_isStopped
= false;
1970 deferredRequestResumeData();
1971 m_session
->handleTorrentStarted(this);
1974 if (m_maintenanceJob
== MaintenanceJob::None
)
1976 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1977 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1978 m_nativeHandle
.resume();
1982 void TorrentImpl::moveStorage(const Path
&newPath
, const MoveStorageContext context
)
1986 if (context
== MoveStorageContext::ChangeSavePath
)
1988 m_savePath
= newPath
;
1989 m_session
->handleTorrentSavePathChanged(this);
1991 else if (context
== MoveStorageContext::ChangeDownloadPath
)
1993 m_downloadPath
= newPath
;
1994 m_session
->handleTorrentSavePathChanged(this);
2000 const auto mode
= (context
== MoveStorageContext::AdjustCurrentLocation
)
2001 ? MoveStorageMode::Overwrite
: MoveStorageMode::KeepExistingFiles
;
2002 if (m_session
->addMoveTorrentStorageJob(this, newPath
, mode
, context
))
2004 if (!m_storageIsMoving
)
2006 m_storageIsMoving
= true;
2008 m_session
->handleTorrentStorageMovingStateChanged(this);
2013 void TorrentImpl::renameFile(const int index
, const Path
&path
)
2015 Q_ASSERT((index
>= 0) && (index
< filesCount()));
2016 if ((index
< 0) || (index
>= filesCount())) [[unlikely
]]
2019 const Path targetActualPath
= makeActualPath(index
, path
);
2020 doRenameFile(index
, targetActualPath
);
2023 void TorrentImpl::handleStateUpdate(const lt::torrent_status
&nativeStatus
)
2025 updateStatus(nativeStatus
);
2028 void TorrentImpl::handleQueueingModeChanged()
2033 void TorrentImpl::handleMoveStorageJobFinished(const Path
&path
, const MoveStorageContext context
, const bool hasOutstandingJob
)
2035 if (context
== MoveStorageContext::ChangeSavePath
)
2037 else if (context
== MoveStorageContext::ChangeDownloadPath
)
2038 m_downloadPath
= path
;
2039 m_storageIsMoving
= hasOutstandingJob
;
2040 m_nativeStatus
.save_path
= path
.toString().toStdString();
2042 m_session
->handleTorrentSavePathChanged(this);
2043 deferredRequestResumeData();
2045 if (!m_storageIsMoving
)
2048 m_session
->handleTorrentStorageMovingStateChanged(this);
2050 if (m_hasMissingFiles
)
2052 // it can be moved to the proper location
2053 m_hasMissingFiles
= false;
2054 m_ltAddTorrentParams
.save_path
= m_nativeStatus
.save_path
;
2055 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
2059 while ((m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2060 std::invoke(m_moveFinishedTriggers
.dequeue());
2064 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused
]] const lt::torrent_checked_alert
*p
)
2068 // The torrent is checked due to metadata received, but we should not process
2069 // this event until the torrent is reloaded using the received metadata.
2073 if (stopCondition() == StopCondition::FilesChecked
)
2076 m_statusUpdatedTriggers
.enqueue([this]()
2078 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
2080 if (!m_hasMissingFiles
)
2082 if ((progress() < 1.0) && (wantedSize() > 0))
2083 m_hasFinishedStatus
= false;
2084 else if (progress() == 1.0)
2085 m_hasFinishedStatus
= true;
2087 adjustStorageLocation();
2088 manageActualFilePaths();
2092 // torrent is internally paused using NativeTorrentExtension after files checked
2093 // so we need to resume it if there is no corresponding "stop condition" set
2094 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
2095 if (m_operatingMode
== TorrentOperatingMode::Forced
)
2096 m_nativeHandle
.resume();
2100 if (m_nativeStatus
.need_save_resume
)
2101 deferredRequestResumeData();
2103 m_session
->handleTorrentChecked(this);
2107 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused
]] const lt::torrent_finished_alert
*p
)
2109 m_hasMissingFiles
= false;
2110 if (m_hasFinishedStatus
)
2113 m_statusUpdatedTriggers
.enqueue([this]()
2115 adjustStorageLocation();
2116 manageActualFilePaths();
2118 deferredRequestResumeData();
2120 const bool recheckTorrentsOnCompletion
= Preferences::instance()->recheckTorrentsOnCompletion();
2121 if (recheckTorrentsOnCompletion
&& m_unchecked
)
2127 m_hasFinishedStatus
= true;
2129 if (isMoveInProgress() || (m_renameCount
> 0))
2130 m_moveFinishedTriggers
.enqueue([this]() { m_session
->handleTorrentFinished(this); });
2132 m_session
->handleTorrentFinished(this);
2137 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused
]] const lt::torrent_paused_alert
*p
)
2141 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused
]] const lt::torrent_resumed_alert
*p
)
2145 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert
*p
)
2147 if (m_ltAddTorrentParams
.url_seeds
!= p
->params
.url_seeds
)
2149 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2150 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2151 // than the list we usually cache, so we have to request it from the appropriate source.
2152 fetchURLSeeds([this](const QList
<QUrl
> &urlSeeds
) { m_urlSeeds
= urlSeeds
; });
2155 if ((m_maintenanceJob
== MaintenanceJob::HandleMetadata
) && p
->params
.ti
)
2157 Q_ASSERT(m_indexMap
.isEmpty());
2159 const auto isSeedMode
= static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
);
2160 m_ltAddTorrentParams
= p
->params
;
2162 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2164 m_ltAddTorrentParams
.have_pieces
.clear();
2165 m_ltAddTorrentParams
.verified_pieces
.clear();
2167 m_nativeStatus
.torrent_file
= m_ltAddTorrentParams
.ti
;
2169 const auto metadata
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
2171 const auto &renamedFiles
= m_ltAddTorrentParams
.renamed_files
;
2172 PathList filePaths
= metadata
.filePaths();
2173 if (renamedFiles
.empty() && (m_contentLayout
!= TorrentContentLayout::Original
))
2175 const Path originalRootFolder
= Path::findRootFolder(filePaths
);
2176 const auto originalContentLayout
= (originalRootFolder
.isEmpty()
2177 ? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder
);
2178 if (m_contentLayout
!= originalContentLayout
)
2180 if (m_contentLayout
== TorrentContentLayout::NoSubfolder
)
2181 Path::stripRootFolder(filePaths
);
2183 Path::addRootFolder(filePaths
, filePaths
.at(0).removedExtension());
2187 const auto nativeIndexes
= metadata
.nativeIndexes();
2188 m_indexMap
.reserve(filePaths
.size());
2189 for (int i
= 0; i
< filePaths
.size(); ++i
)
2191 const auto nativeIndex
= nativeIndexes
.at(i
);
2192 m_indexMap
[nativeIndex
] = i
;
2194 if (const auto it
= renamedFiles
.find(nativeIndex
); it
!= renamedFiles
.cend())
2195 filePaths
[i
] = Path(it
->second
);
2198 m_session
->findIncompleteFiles(metadata
, savePath(), downloadPath(), filePaths
);
2202 prepareResumeData(p
->params
);
2206 void TorrentImpl::prepareResumeData(const lt::add_torrent_params
¶ms
)
2208 if (m_hasMissingFiles
)
2210 const auto havePieces
= m_ltAddTorrentParams
.have_pieces
;
2211 const auto unfinishedPieces
= m_ltAddTorrentParams
.unfinished_pieces
;
2212 const auto verifiedPieces
= m_ltAddTorrentParams
.verified_pieces
;
2214 // Update recent resume data but preserve existing progress
2215 m_ltAddTorrentParams
= params
;
2216 m_ltAddTorrentParams
.have_pieces
= havePieces
;
2217 m_ltAddTorrentParams
.unfinished_pieces
= unfinishedPieces
;
2218 m_ltAddTorrentParams
.verified_pieces
= verifiedPieces
;
2222 const bool preserveSeedMode
= (!hasMetadata() && (m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
));
2223 // Update recent resume data
2224 m_ltAddTorrentParams
= params
;
2225 if (preserveSeedMode
)
2226 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2229 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2230 m_ltAddTorrentParams
.flags
&= ~lt::torrent_flags::upload_mode
;
2232 const LoadTorrentParams resumeData
2234 .ltAddTorrentParams
= m_ltAddTorrentParams
,
2236 .category
= m_category
,
2238 .savePath
= (!m_useAutoTMM
? m_savePath
: Path()),
2239 .downloadPath
= (!m_useAutoTMM
? m_downloadPath
: Path()),
2240 .contentLayout
= m_contentLayout
,
2241 .operatingMode
= m_operatingMode
,
2242 .useAutoTMM
= m_useAutoTMM
,
2243 .firstLastPiecePriority
= m_hasFirstLastPiecePriority
,
2244 .hasFinishedStatus
= m_hasFinishedStatus
,
2245 .stopped
= m_isStopped
,
2246 .stopCondition
= m_stopCondition
,
2247 .addToQueueTop
= false,
2248 .ratioLimit
= m_ratioLimit
,
2249 .seedingTimeLimit
= m_seedingTimeLimit
,
2250 .inactiveSeedingTimeLimit
= m_inactiveSeedingTimeLimit
,
2251 .shareLimitAction
= m_shareLimitAction
,
2252 .sslParameters
= m_sslParams
2255 m_session
->handleTorrentResumeDataReady(this, resumeData
);
2258 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert
*p
)
2260 if (p
->error
!= lt::errors::resume_data_not_modified
)
2262 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2263 .arg(name(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::CRITICAL
);
2267 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert
*p
)
2269 // Files were probably moved or storage isn't accessible
2270 m_hasMissingFiles
= true;
2271 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2272 .arg(name(), QString::fromStdString(p
->message())), Log::WARNING
);
2275 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert
*p
)
2277 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2278 Q_ASSERT(fileIndex
>= 0);
2280 const Path newActualFilePath
{QString::fromUtf8(p
->new_name())};
2282 const Path oldFilePath
= m_filePaths
.at(fileIndex
);
2283 const Path newFilePath
= makeUserPath(newActualFilePath
);
2285 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2286 // We should compare path in a case sensitive manner even on case insensitive
2287 // platforms since it can be renamed by only changing case of some character(s)
2288 if (oldFilePath
.data() == newFilePath
.data())
2290 // Remove empty ".unwanted" folders
2291 #ifdef QBT_USES_LIBTORRENT2
2292 const Path oldActualFilePath
{QString::fromUtf8(p
->old_name())};
2294 const Path oldActualFilePath
;
2296 const Path oldActualParentPath
= oldActualFilePath
.parentPath();
2297 const Path newActualParentPath
= newActualFilePath
.parentPath();
2298 if (newActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2300 if (oldActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2303 const std::wstring winPath
= (actualStorageLocation() / newActualParentPath
).toString().toStdWString();
2304 const DWORD dwAttrs
= ::GetFileAttributesW(winPath
.c_str());
2305 ::SetFileAttributesW(winPath
.c_str(), (dwAttrs
| FILE_ATTRIBUTE_HIDDEN
));
2309 #ifdef QBT_USES_LIBTORRENT2
2310 else if (oldActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2312 if (newActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2313 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath
);
2318 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath
/ Path(UNWANTED_FOLDER_NAME
));
2324 m_filePaths
[fileIndex
] = newFilePath
;
2326 // Remove empty leftover folders
2327 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2328 // be removed if they are empty
2329 Path oldParentPath
= oldFilePath
.parentPath();
2330 const Path commonBasePath
= Path::commonPath(oldParentPath
, newFilePath
.parentPath());
2331 while (oldParentPath
!= commonBasePath
)
2333 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath
);
2334 oldParentPath
= oldParentPath
.parentPath();
2339 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2340 m_moveFinishedTriggers
.takeFirst()();
2342 deferredRequestResumeData();
2345 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
*p
)
2347 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2348 Q_ASSERT(fileIndex
>= 0);
2350 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2351 .arg(name(), filePath(fileIndex
).toString(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::WARNING
);
2354 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2355 m_moveFinishedTriggers
.takeFirst()();
2357 deferredRequestResumeData();
2360 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert
*p
)
2362 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2365 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2366 Q_ASSERT(fileIndex
>= 0);
2368 m_completedFiles
.setBit(fileIndex
);
2370 const Path actualPath
= actualFilePath(fileIndex
);
2372 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2373 // only apply Mark-of-the-Web to new download files
2374 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2376 const Path fullpath
= actualStorageLocation() / actualPath
;
2377 Utils::OS::applyMarkOfTheWeb(fullpath
);
2379 #endif // Q_OS_MACOS || Q_OS_WIN
2381 if (m_session
->isAppendExtensionEnabled())
2383 const Path path
= filePath(fileIndex
);
2384 if (actualPath
!= path
)
2386 qDebug("Renaming %s to %s", qUtf8Printable(actualPath
.toString()), qUtf8Printable(path
.toString()));
2387 doRenameFile(fileIndex
, path
);
2392 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert
*p
)
2394 m_lastFileError
= {p
->error
, p
->op
};
2397 #ifdef QBT_USES_LIBTORRENT2
2398 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert
*)
2400 deferredRequestResumeData();
2404 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused
]] const lt::metadata_received_alert
*p
)
2406 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2408 #ifdef QBT_USES_LIBTORRENT2
2409 const InfoHash prevInfoHash
= infoHash();
2410 m_infoHash
= InfoHash(m_nativeHandle
.info_hashes());
2411 if (prevInfoHash
!= infoHash())
2412 m_session
->handleTorrentInfoHashChanged(this, prevInfoHash
);
2415 m_maintenanceJob
= MaintenanceJob::HandleMetadata
;
2416 deferredRequestResumeData();
2419 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert
*p
) const
2421 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
))
2425 void TorrentImpl::handleCategoryOptionsChanged()
2428 adjustStorageLocation();
2431 void TorrentImpl::handleAppendExtensionToggled()
2436 manageActualFilePaths();
2439 void TorrentImpl::handleUnwantedFolderToggled()
2444 manageActualFilePaths();
2447 void TorrentImpl::handleAlert(const lt::alert
*a
)
2451 #ifdef QBT_USES_LIBTORRENT2
2452 case lt::file_prio_alert::alert_type
:
2453 handleFilePrioAlert(static_cast<const lt::file_prio_alert
*>(a
));
2456 case lt::file_renamed_alert::alert_type
:
2457 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert
*>(a
));
2459 case lt::file_rename_failed_alert::alert_type
:
2460 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert
*>(a
));
2462 case lt::file_completed_alert::alert_type
:
2463 handleFileCompletedAlert(static_cast<const lt::file_completed_alert
*>(a
));
2465 case lt::file_error_alert::alert_type
:
2466 handleFileErrorAlert(static_cast<const lt::file_error_alert
*>(a
));
2468 case lt::torrent_finished_alert::alert_type
:
2469 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert
*>(a
));
2471 case lt::save_resume_data_alert::alert_type
:
2472 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert
*>(a
));
2474 case lt::save_resume_data_failed_alert::alert_type
:
2475 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert
*>(a
));
2477 case lt::torrent_paused_alert::alert_type
:
2478 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert
*>(a
));
2480 case lt::torrent_resumed_alert::alert_type
:
2481 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert
*>(a
));
2483 case lt::metadata_received_alert::alert_type
:
2484 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(a
));
2486 case lt::fastresume_rejected_alert::alert_type
:
2487 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert
*>(a
));
2489 case lt::torrent_checked_alert::alert_type
:
2490 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert
*>(a
));
2492 case lt::performance_alert::alert_type
:
2493 handlePerformanceAlert(static_cast<const lt::performance_alert
*>(a
));
2498 void TorrentImpl::manageActualFilePaths()
2500 const std::shared_ptr
<const lt::torrent_info
> nativeInfo
= nativeTorrentInfo();
2501 const lt::file_storage
&nativeFiles
= nativeInfo
->files();
2503 for (int i
= 0; i
< filesCount(); ++i
)
2505 const Path path
= filePath(i
);
2507 const auto nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
2508 const Path actualPath
{nativeFiles
.file_path(nativeIndex
)};
2509 const Path targetActualPath
= makeActualPath(i
, path
);
2510 if (actualPath
!= targetActualPath
)
2512 qDebug() << "Renaming" << actualPath
.toString() << "to" << targetActualPath
.toString();
2513 doRenameFile(i
, targetActualPath
);
2518 void TorrentImpl::adjustStorageLocation()
2520 const Path downloadPath
= this->downloadPath();
2521 const Path targetPath
= ((isFinished() || m_hasFinishedStatus
|| downloadPath
.isEmpty()) ? savePath() : downloadPath
);
2523 if ((targetPath
!= actualStorageLocation()) || isMoveInProgress())
2524 moveStorage(targetPath
, MoveStorageContext::AdjustCurrentLocation
);
2527 void TorrentImpl::doRenameFile(const int index
, const Path
&path
)
2529 const QList
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
2531 Q_ASSERT(index
>= 0);
2532 Q_ASSERT(index
< nativeIndexes
.size());
2533 if ((index
< 0) || (index
>= nativeIndexes
.size())) [[unlikely
]]
2537 m_nativeHandle
.rename_file(nativeIndexes
[index
], path
.toString().toStdString());
2540 lt::torrent_handle
TorrentImpl::nativeHandle() const
2542 return m_nativeHandle
;
2545 void TorrentImpl::setMetadata(const TorrentInfo
&torrentInfo
)
2550 m_session
->invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
]
2554 #ifdef QBT_USES_LIBTORRENT2
2555 nativeHandle
.set_metadata(torrentInfo
.nativeInfo()->info_section());
2557 const std::shared_ptr
<lt::torrent_info
> nativeInfo
= torrentInfo
.nativeInfo();
2558 nativeHandle
.set_metadata(lt::span
<const char>(nativeInfo
->metadata().get(), nativeInfo
->metadata_size()));
2561 catch (const std::exception
&) {}
2565 Torrent::StopCondition
TorrentImpl::stopCondition() const
2567 return m_stopCondition
;
2570 void TorrentImpl::setStopCondition(const StopCondition stopCondition
)
2572 if (stopCondition
== m_stopCondition
)
2578 if ((stopCondition
== StopCondition::MetadataReceived
) && hasMetadata())
2581 if ((stopCondition
== StopCondition::FilesChecked
) && hasMetadata() && !isChecking())
2584 m_stopCondition
= stopCondition
;
2587 SSLParameters
TorrentImpl::getSSLParameters() const
2592 void TorrentImpl::setSSLParameters(const SSLParameters
&sslParams
)
2594 if (sslParams
== getSSLParameters())
2597 m_sslParams
= sslParams
;
2598 applySSLParameters();
2600 deferredRequestResumeData();
2603 bool TorrentImpl::applySSLParameters()
2605 if (!m_sslParams
.isValid())
2608 m_nativeHandle
.set_ssl_certificate_buffer(m_sslParams
.certificate
.toPem().toStdString()
2609 , m_sslParams
.privateKey
.toPem().toStdString(), m_sslParams
.dhParams
.toStdString());
2613 bool TorrentImpl::isMoveInProgress() const
2615 return m_storageIsMoving
;
2618 void TorrentImpl::updateStatus(const lt::torrent_status
&nativeStatus
)
2620 const lt::torrent_status oldStatus
= std::exchange(m_nativeStatus
, nativeStatus
);
2622 if (m_nativeStatus
.num_pieces
!= oldStatus
.num_pieces
)
2627 m_payloadRateMonitor
.addSample({nativeStatus
.download_payload_rate
2628 , nativeStatus
.upload_payload_rate
});
2632 // NOTE: Don't change the order of these conditionals!
2633 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2635 m_unchecked
= false;
2636 else if (isDownloading())
2640 while (!m_statusUpdatedTriggers
.isEmpty())
2641 std::invoke(m_statusUpdatedTriggers
.dequeue());
2644 void TorrentImpl::updateProgress()
2646 Q_ASSERT(hasMetadata());
2647 if (!hasMetadata()) [[unlikely
]]
2650 Q_ASSERT(!m_filesProgress
.isEmpty());
2651 if (m_filesProgress
.isEmpty()) [[unlikely
]]
2652 m_filesProgress
.resize(filesCount());
2654 const QBitArray oldPieces
= std::exchange(m_pieces
, LT::toQBitArray(m_nativeStatus
.pieces
));
2655 const QBitArray newPieces
= m_pieces
^ oldPieces
;
2657 const int64_t pieceSize
= m_torrentInfo
.pieceLength();
2658 for (qsizetype index
= 0; index
< newPieces
.size(); ++index
)
2660 if (!newPieces
.at(index
))
2663 int64_t size
= m_torrentInfo
.pieceLength(index
);
2664 int64_t pieceOffset
= index
* pieceSize
;
2666 for (const int fileIndex
: asConst(m_torrentInfo
.fileIndicesForPiece(index
)))
2668 const int64_t fileOffsetInPiece
= pieceOffset
- m_torrentInfo
.fileOffset(fileIndex
);
2669 const int64_t add
= std::min
<int64_t>((m_torrentInfo
.fileSize(fileIndex
) - fileOffsetInPiece
), size
);
2671 m_filesProgress
[fileIndex
] += add
;
2682 void TorrentImpl::setRatioLimit(qreal limit
)
2684 if (limit
< USE_GLOBAL_RATIO
)
2685 limit
= NO_RATIO_LIMIT
;
2686 else if (limit
> MAX_RATIO
)
2689 if (m_ratioLimit
!= limit
)
2691 m_ratioLimit
= limit
;
2692 deferredRequestResumeData();
2693 m_session
->handleTorrentShareLimitChanged(this);
2697 void TorrentImpl::setSeedingTimeLimit(int limit
)
2699 if (limit
< USE_GLOBAL_SEEDING_TIME
)
2700 limit
= NO_SEEDING_TIME_LIMIT
;
2701 else if (limit
> MAX_SEEDING_TIME
)
2702 limit
= MAX_SEEDING_TIME
;
2704 if (m_seedingTimeLimit
!= limit
)
2706 m_seedingTimeLimit
= limit
;
2707 deferredRequestResumeData();
2708 m_session
->handleTorrentShareLimitChanged(this);
2712 void TorrentImpl::setInactiveSeedingTimeLimit(int limit
)
2714 if (limit
< USE_GLOBAL_INACTIVE_SEEDING_TIME
)
2715 limit
= NO_INACTIVE_SEEDING_TIME_LIMIT
;
2716 else if (limit
> MAX_INACTIVE_SEEDING_TIME
)
2717 limit
= MAX_SEEDING_TIME
;
2719 if (m_inactiveSeedingTimeLimit
!= limit
)
2721 m_inactiveSeedingTimeLimit
= limit
;
2722 deferredRequestResumeData();
2723 m_session
->handleTorrentShareLimitChanged(this);
2727 ShareLimitAction
TorrentImpl::shareLimitAction() const
2729 return m_shareLimitAction
;
2732 void TorrentImpl::setShareLimitAction(const ShareLimitAction action
)
2734 if (m_shareLimitAction
!= action
)
2736 m_shareLimitAction
= action
;
2737 deferredRequestResumeData();
2738 m_session
->handleTorrentShareLimitChanged(this);
2742 void TorrentImpl::setUploadLimit(const int limit
)
2744 const int cleanValue
= cleanLimitValue(limit
);
2745 if (cleanValue
== uploadLimit())
2748 m_uploadLimit
= cleanValue
;
2749 m_nativeHandle
.set_upload_limit(m_uploadLimit
);
2750 deferredRequestResumeData();
2753 void TorrentImpl::setDownloadLimit(const int limit
)
2755 const int cleanValue
= cleanLimitValue(limit
);
2756 if (cleanValue
== downloadLimit())
2759 m_downloadLimit
= cleanValue
;
2760 m_nativeHandle
.set_download_limit(m_downloadLimit
);
2761 deferredRequestResumeData();
2764 void TorrentImpl::setSuperSeeding(const bool enable
)
2766 if (enable
== superSeeding())
2770 m_nativeHandle
.set_flags(lt::torrent_flags::super_seeding
);
2772 m_nativeHandle
.unset_flags(lt::torrent_flags::super_seeding
);
2774 deferredRequestResumeData();
2777 void TorrentImpl::setDHTDisabled(const bool disable
)
2779 if (disable
== isDHTDisabled())
2783 m_nativeHandle
.set_flags(lt::torrent_flags::disable_dht
);
2785 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_dht
);
2787 deferredRequestResumeData();
2790 void TorrentImpl::setPEXDisabled(const bool disable
)
2792 if (disable
== isPEXDisabled())
2796 m_nativeHandle
.set_flags(lt::torrent_flags::disable_pex
);
2798 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_pex
);
2800 deferredRequestResumeData();
2803 void TorrentImpl::setLSDDisabled(const bool disable
)
2805 if (disable
== isLSDDisabled())
2809 m_nativeHandle
.set_flags(lt::torrent_flags::disable_lsd
);
2811 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_lsd
);
2813 deferredRequestResumeData();
2816 void TorrentImpl::flushCache() const
2818 m_nativeHandle
.flush_cache();
2821 QString
TorrentImpl::createMagnetURI() const
2823 QString ret
= u
"magnet:?"_s
;
2825 const SHA1Hash infoHash1
= infoHash().v1();
2826 if (infoHash1
.isValid())
2828 ret
+= u
"xt=urn:btih:" + infoHash1
.toString();
2831 const SHA256Hash infoHash2
= infoHash().v2();
2832 if (infoHash2
.isValid())
2834 if (infoHash1
.isValid())
2836 ret
+= u
"xt=urn:btmh:1220" + infoHash2
.toString();
2839 const QString displayName
= name();
2840 if (displayName
!= id().toString())
2842 ret
+= u
"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName
));
2845 for (const TrackerEntryStatus
&tracker
: asConst(trackers()))
2847 ret
+= u
"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker
.url
));
2850 for (const QUrl
&urlSeed
: asConst(urlSeeds()))
2852 ret
+= u
"&ws=" + QString::fromLatin1(urlSeed
.toEncoded());
2858 nonstd::expected
<lt::entry
, QString
> TorrentImpl::exportTorrent() const
2861 return nonstd::make_unexpected(tr("Missing metadata"));
2865 #ifdef QBT_USES_LIBTORRENT2
2866 const std::shared_ptr
<lt::torrent_info
> completeTorrentInfo
= m_nativeHandle
.torrent_file_with_hashes();
2867 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= (completeTorrentInfo
? completeTorrentInfo
: info().nativeInfo());
2869 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= info().nativeInfo();
2871 lt::create_torrent creator
{*torrentInfo
};
2873 for (const TrackerEntryStatus
&status
: asConst(trackers()))
2874 creator
.add_tracker(status
.url
.toStdString(), status
.tier
);
2876 return creator
.generate();
2878 catch (const lt::system_error
&err
)
2880 return nonstd::make_unexpected(QString::fromLocal8Bit(err
.what()));
2884 nonstd::expected
<QByteArray
, QString
> TorrentImpl::exportToBuffer() const
2886 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2887 if (!preparationResult
)
2888 return preparationResult
.get_unexpected();
2890 // usually torrent size should be smaller than 1 MB,
2891 // however there are >100 MB v2/hybrid torrent files out in the wild
2893 buffer
.reserve(1024 * 1024);
2894 lt::bencode(std::back_inserter(buffer
), preparationResult
.value());
2898 nonstd::expected
<void, QString
> TorrentImpl::exportToFile(const Path
&path
) const
2900 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2901 if (!preparationResult
)
2902 return preparationResult
.get_unexpected();
2904 const nonstd::expected
<void, QString
> saveResult
= Utils::IO::saveToFile(path
, preparationResult
.value());
2906 return saveResult
.get_unexpected();
2911 void TorrentImpl::fetchPeerInfo(std::function
<void (QList
<PeerInfo
>)> resultHandler
) const
2913 invokeAsync([nativeHandle
= m_nativeHandle
, allPieces
= pieces()]() -> QList
<PeerInfo
>
2917 std::vector
<lt::peer_info
> nativePeers
;
2918 nativeHandle
.get_peer_info(nativePeers
);
2919 QList
<PeerInfo
> peers
;
2920 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
2921 for (const lt::peer_info
&peer
: nativePeers
)
2922 peers
.append(PeerInfo(peer
, allPieces
));
2925 catch (const std::exception
&) {}
2929 , std::move(resultHandler
));
2932 void TorrentImpl::fetchURLSeeds(std::function
<void (QList
<QUrl
>)> resultHandler
) const
2934 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QList
<QUrl
>
2938 const std::set
<std::string
> currentSeeds
= nativeHandle
.url_seeds();
2939 QList
<QUrl
> urlSeeds
;
2940 urlSeeds
.reserve(static_cast<decltype(urlSeeds
)::size_type
>(currentSeeds
.size()));
2941 for (const std::string
&urlSeed
: currentSeeds
)
2942 urlSeeds
.append(QString::fromStdString(urlSeed
));
2945 catch (const std::exception
&) {}
2949 , std::move(resultHandler
));
2952 void TorrentImpl::fetchPieceAvailability(std::function
<void (QList
<int>)> resultHandler
) const
2954 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QList
<int>
2958 std::vector
<int> piecesAvailability
;
2959 nativeHandle
.piece_availability(piecesAvailability
);
2960 return QList
<int>(piecesAvailability
.cbegin(), piecesAvailability
.cend());
2962 catch (const std::exception
&) {}
2966 , std::move(resultHandler
));
2969 void TorrentImpl::fetchDownloadingPieces(std::function
<void (QBitArray
)> resultHandler
) const
2971 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QBitArray
2975 #ifdef QBT_USES_LIBTORRENT2
2976 const std::vector
<lt::partial_piece_info
> queue
= nativeHandle
.get_download_queue();
2978 std::vector
<lt::partial_piece_info
> queue
;
2979 nativeHandle
.get_download_queue(queue
);
2982 result
.resize(torrentInfo
.piecesCount());
2983 for (const lt::partial_piece_info
&info
: queue
)
2984 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
2987 catch (const std::exception
&) {}
2991 , std::move(resultHandler
));
2994 void TorrentImpl::fetchAvailableFileFractions(std::function
<void (QList
<qreal
>)> resultHandler
) const
2996 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QList
<qreal
>
2998 if (!torrentInfo
.isValid() || (torrentInfo
.filesCount() <= 0))
3003 std::vector
<int> piecesAvailability
;
3004 nativeHandle
.piece_availability(piecesAvailability
);
3005 const int filesCount
= torrentInfo
.filesCount();
3006 // libtorrent returns empty array for seeding only torrents
3007 if (piecesAvailability
.empty())
3008 return QList
<qreal
>(filesCount
, -1);
3010 QList
<qreal
> result
;
3011 result
.reserve(filesCount
);
3012 for (int i
= 0; i
< filesCount
; ++i
)
3014 const TorrentInfo::PieceRange filePieces
= torrentInfo
.filePieces(i
);
3016 int availablePieces
= 0;
3017 for (const int piece
: filePieces
)
3018 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
3020 const qreal availability
= filePieces
.isEmpty()
3021 ? 1 // the file has no pieces, so it is available by default
3022 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
3023 result
.append(availability
);
3027 catch (const std::exception
&) {}
3031 , std::move(resultHandler
));
3034 void TorrentImpl::prioritizeFiles(const QList
<DownloadPriority
> &priorities
)
3039 Q_ASSERT(priorities
.size() == filesCount());
3041 // Reset 'm_hasSeedStatus' if needed in order to react again to
3042 // 'torrent_finished_alert' and eg show tray notifications
3043 const QList
<DownloadPriority
> oldPriorities
= filePriorities();
3044 for (int i
= 0; i
< oldPriorities
.size(); ++i
)
3046 if ((oldPriorities
[i
] == DownloadPriority::Ignored
)
3047 && (priorities
[i
] > DownloadPriority::Ignored
)
3048 && !m_completedFiles
.at(i
))
3050 m_hasFinishedStatus
= false;
3055 const int internalFilesCount
= m_torrentInfo
.nativeInfo()->files().num_files(); // including .pad files
3056 auto nativePriorities
= std::vector
<lt::download_priority_t
>(internalFilesCount
, LT::toNative(DownloadPriority::Normal
));
3057 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
3058 for (int i
= 0; i
< priorities
.size(); ++i
)
3059 nativePriorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(priorities
[i
]);
3061 qDebug() << Q_FUNC_INFO
<< "Changing files priorities...";
3062 m_nativeHandle
.prioritize_files(nativePriorities
);
3064 m_filePriorities
= priorities
;
3065 // Restore first/last piece first option if necessary
3066 if (m_hasFirstLastPiecePriority
)
3067 applyFirstLastPiecePriority(true);
3068 manageActualFilePaths();
3071 QList
<qreal
> TorrentImpl::availableFileFractions() const
3073 Q_ASSERT(hasMetadata());
3075 const int filesCount
= this->filesCount();
3076 if (filesCount
<= 0) return {};
3078 const QList
<int> piecesAvailability
= pieceAvailability();
3079 // libtorrent returns empty array for seeding only torrents
3080 if (piecesAvailability
.empty()) return QList
<qreal
>(filesCount
, -1);
3083 res
.reserve(filesCount
);
3084 for (int i
= 0; i
< filesCount
; ++i
)
3086 const TorrentInfo::PieceRange filePieces
= m_torrentInfo
.filePieces(i
);
3088 int availablePieces
= 0;
3089 for (const int piece
: filePieces
)
3090 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
3092 const qreal availability
= filePieces
.isEmpty()
3093 ? 1 // the file has no pieces, so it is available by default
3094 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
3095 res
.push_back(availability
);
3100 template <typename Func
, typename Callback
>
3101 void TorrentImpl::invokeAsync(Func func
, Callback resultHandler
) const
3103 m_session
->invokeAsync([session
= m_session
3104 , func
= std::move(func
)
3105 , resultHandler
= std::move(resultHandler
)
3106 , thisTorrent
= QPointer
<const TorrentImpl
>(this)]() mutable
3108 session
->invoke([result
= func(), thisTorrent
, resultHandler
= std::move(resultHandler
)]
3111 resultHandler(result
);