2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "torrentimpl.h"
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"
75 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
76 #include "base/utils/os.h"
77 #endif // Q_OS_MACOS || Q_OS_WIN
79 using namespace BitTorrent
;
83 lt::announce_entry
makeNativeAnnounceEntry(const QString
&url
, const int tier
)
85 lt::announce_entry entry
{url
.toStdString()};
90 QDateTime
fromLTTimePoint32(const lt::time_point32
&timePoint
)
92 const auto ltNow
= lt::clock_type::now();
93 const auto qNow
= QDateTime::currentDateTime();
94 const auto secsSinceNow
= lt::duration_cast
<lt::seconds
>(timePoint
- ltNow
+ lt::milliseconds(500)).count();
96 return qNow
.addSecs(secsSinceNow
);
99 QString
toString(const lt::tcp::endpoint
<TCPEndpoint
)
101 return QString::fromStdString((std::stringstream() << ltTCPEndpoint
).str());
104 void updateTrackerEntry(TrackerEntry
&trackerEntry
, const lt::announce_entry
&nativeEntry
105 , const QSet
<int> &btProtocols
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
107 Q_ASSERT(trackerEntry
.url
== QString::fromStdString(nativeEntry
.url
));
109 trackerEntry
.tier
= nativeEntry
.tier
;
111 // remove outdated endpoints
112 trackerEntry
.endpointEntries
.removeIf([&nativeEntry
](const QHash
<std::pair
<QString
, int>, TrackerEndpointEntry
>::iterator
&iter
)
114 return std::none_of(nativeEntry
.endpoints
.cbegin(), nativeEntry
.endpoints
.cend()
115 , [&endpointName
= std::get
<0>(iter
.key())](const auto &existingEndpoint
)
117 return (endpointName
== toString(existingEndpoint
.local_endpoint
));
121 const auto numEndpoints
= static_cast<qsizetype
>(nativeEntry
.endpoints
.size()) * btProtocols
.size();
125 int numNotWorking
= 0;
126 int numTrackerError
= 0;
127 int numUnreachable
= 0;
129 for (const lt::announce_endpoint
<AnnounceEndpoint
: nativeEntry
.endpoints
)
131 const auto endpointName
= toString(ltAnnounceEndpoint
.local_endpoint
);
133 for (const auto protocolVersion
: btProtocols
)
135 #ifdef QBT_USES_LIBTORRENT2
136 Q_ASSERT((protocolVersion
== 1) || (protocolVersion
== 2));
137 const auto ltProtocolVersion
= (protocolVersion
== 1) ? lt::protocol_version::V1
: lt::protocol_version::V2
;
138 const lt::announce_infohash
<AnnounceInfo
= ltAnnounceEndpoint
.info_hashes
[ltProtocolVersion
];
140 Q_ASSERT(protocolVersion
== 1);
141 const lt::announce_endpoint
<AnnounceInfo
= ltAnnounceEndpoint
;
143 const QMap
<int, int> &endpointUpdateInfo
= updateInfo
[ltAnnounceEndpoint
.local_endpoint
];
144 TrackerEndpointEntry
&trackerEndpointEntry
= trackerEntry
.endpointEntries
[std::make_pair(endpointName
, protocolVersion
)];
146 trackerEndpointEntry
.name
= endpointName
;
147 trackerEndpointEntry
.btVersion
= protocolVersion
;
148 trackerEndpointEntry
.numPeers
= endpointUpdateInfo
.value(protocolVersion
, trackerEndpointEntry
.numPeers
);
149 trackerEndpointEntry
.numSeeds
= ltAnnounceInfo
.scrape_complete
;
150 trackerEndpointEntry
.numLeeches
= ltAnnounceInfo
.scrape_incomplete
;
151 trackerEndpointEntry
.numDownloaded
= ltAnnounceInfo
.scrape_downloaded
;
152 trackerEndpointEntry
.nextAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.next_announce
);
153 trackerEndpointEntry
.minAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.min_announce
);
155 if (ltAnnounceInfo
.updating
)
157 trackerEndpointEntry
.status
= TrackerEntryStatus::Updating
;
160 else if (ltAnnounceInfo
.fails
> 0)
162 if (ltAnnounceInfo
.last_error
== lt::errors::tracker_failure
)
164 trackerEndpointEntry
.status
= TrackerEntryStatus::TrackerError
;
167 else if (ltAnnounceInfo
.last_error
== lt::errors::announce_skipped
)
169 trackerEndpointEntry
.status
= TrackerEntryStatus::Unreachable
;
174 trackerEndpointEntry
.status
= TrackerEntryStatus::NotWorking
;
178 else if (nativeEntry
.verified
)
180 trackerEndpointEntry
.status
= TrackerEntryStatus::Working
;
185 trackerEndpointEntry
.status
= TrackerEntryStatus::NotContacted
;
188 if (!ltAnnounceInfo
.message
.empty())
190 trackerEndpointEntry
.message
= QString::fromStdString(ltAnnounceInfo
.message
);
192 else if (ltAnnounceInfo
.last_error
)
194 trackerEndpointEntry
.message
= QString::fromLocal8Bit(ltAnnounceInfo
.last_error
.message());
198 trackerEndpointEntry
.message
.clear();
203 if (numEndpoints
> 0)
207 trackerEntry
.status
= TrackerEntryStatus::Updating
;
209 else if (numWorking
> 0)
211 trackerEntry
.status
= TrackerEntryStatus::Working
;
213 else if (numTrackerError
> 0)
215 trackerEntry
.status
= TrackerEntryStatus::TrackerError
;
217 else if (numUnreachable
== numEndpoints
)
219 trackerEntry
.status
= TrackerEntryStatus::Unreachable
;
221 else if ((numUnreachable
+ numNotWorking
) == numEndpoints
)
223 trackerEntry
.status
= TrackerEntryStatus::NotWorking
;
227 trackerEntry
.numPeers
= -1;
228 trackerEntry
.numSeeds
= -1;
229 trackerEntry
.numLeeches
= -1;
230 trackerEntry
.numDownloaded
= -1;
231 trackerEntry
.nextAnnounceTime
= QDateTime();
232 trackerEntry
.minAnnounceTime
= QDateTime();
233 trackerEntry
.message
.clear();
235 for (const TrackerEndpointEntry
&endpointEntry
: asConst(trackerEntry
.endpointEntries
))
237 trackerEntry
.numPeers
= std::max(trackerEntry
.numPeers
, endpointEntry
.numPeers
);
238 trackerEntry
.numSeeds
= std::max(trackerEntry
.numSeeds
, endpointEntry
.numSeeds
);
239 trackerEntry
.numLeeches
= std::max(trackerEntry
.numLeeches
, endpointEntry
.numLeeches
);
240 trackerEntry
.numDownloaded
= std::max(trackerEntry
.numDownloaded
, endpointEntry
.numDownloaded
);
242 if (endpointEntry
.status
== trackerEntry
.status
)
244 if (!trackerEntry
.nextAnnounceTime
.isValid() || (trackerEntry
.nextAnnounceTime
> endpointEntry
.nextAnnounceTime
))
246 trackerEntry
.nextAnnounceTime
= endpointEntry
.nextAnnounceTime
;
247 trackerEntry
.minAnnounceTime
= endpointEntry
.minAnnounceTime
;
248 if ((endpointEntry
.status
!= TrackerEntryStatus::Working
)
249 || !endpointEntry
.message
.isEmpty())
251 trackerEntry
.message
= endpointEntry
.message
;
255 if (endpointEntry
.status
== TrackerEntryStatus::Working
)
257 if (trackerEntry
.message
.isEmpty())
258 trackerEntry
.message
= endpointEntry
.message
;
264 template <typename Vector
>
265 Vector
resized(const Vector
&inVector
, const typename
Vector::size_type size
, const typename
Vector::value_type
&defaultValue
)
267 Vector outVector
= inVector
;
268 outVector
.resize(size
, defaultValue
);
272 // This is an imitation of limit normalization performed by libtorrent itself.
273 // We need perform it to keep cached values in line with the ones used by libtorrent.
274 int cleanLimitValue(const int value
)
276 return ((value
< 0) || (value
== std::numeric_limits
<int>::max())) ? 0 : value
;
282 TorrentImpl::TorrentImpl(SessionImpl
*session
, lt::session
*nativeSession
283 , const lt::torrent_handle
&nativeHandle
, const LoadTorrentParams
¶ms
)
286 , m_nativeSession(nativeSession
)
287 , m_nativeHandle(nativeHandle
)
288 #ifdef QBT_USES_LIBTORRENT2
289 , m_infoHash(m_nativeHandle
.info_hashes())
291 , m_infoHash(m_nativeHandle
.info_hash())
293 , m_name(params
.name
)
294 , m_savePath(params
.savePath
)
295 , m_downloadPath(params
.downloadPath
)
296 , m_category(params
.category
)
297 , m_tags(params
.tags
)
298 , m_ratioLimit(params
.ratioLimit
)
299 , m_seedingTimeLimit(params
.seedingTimeLimit
)
300 , m_inactiveSeedingTimeLimit(params
.inactiveSeedingTimeLimit
)
301 , m_operatingMode(params
.operatingMode
)
302 , m_contentLayout(params
.contentLayout
)
303 , m_hasFinishedStatus(params
.hasFinishedStatus
)
304 , m_hasFirstLastPiecePriority(params
.firstLastPiecePriority
)
305 , m_useAutoTMM(params
.useAutoTMM
)
306 , m_isStopped(params
.stopped
)
307 , m_ltAddTorrentParams(params
.ltAddTorrentParams
)
308 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams
.download_limit
))
309 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams
.upload_limit
))
311 if (m_ltAddTorrentParams
.ti
)
313 // Initialize it only if torrent is added with metadata.
314 // Otherwise it should be initialized in "Metadata received" handler.
315 m_torrentInfo
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
317 Q_ASSERT(m_filePaths
.isEmpty());
318 Q_ASSERT(m_indexMap
.isEmpty());
319 const int filesCount
= m_torrentInfo
.filesCount();
320 m_filePaths
.reserve(filesCount
);
321 m_indexMap
.reserve(filesCount
);
322 m_filePriorities
.reserve(filesCount
);
323 const std::vector
<lt::download_priority_t
> filePriorities
=
324 resized(m_ltAddTorrentParams
.file_priorities
, m_ltAddTorrentParams
.ti
->num_files()
325 , LT::toNative(m_ltAddTorrentParams
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
327 m_completedFiles
.fill(static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
), filesCount
);
328 m_filesProgress
.resize(filesCount
);
330 for (int i
= 0; i
< filesCount
; ++i
)
332 const lt::file_index_t nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
333 m_indexMap
[nativeIndex
] = i
;
335 const auto fileIter
= m_ltAddTorrentParams
.renamed_files
.find(nativeIndex
);
336 const Path filePath
= ((fileIter
!= m_ltAddTorrentParams
.renamed_files
.end())
337 ? makeUserPath(Path(fileIter
->second
)) : m_torrentInfo
.filePath(i
));
338 m_filePaths
.append(filePath
);
340 const auto priority
= LT::fromNative(filePriorities
[LT::toUnderlyingType(nativeIndex
)]);
341 m_filePriorities
.append(priority
);
345 setStopCondition(params
.stopCondition
);
347 const auto *extensionData
= static_cast<ExtensionData
*>(m_ltAddTorrentParams
.userdata
);
348 m_trackerEntries
.reserve(static_cast<decltype(m_trackerEntries
)::size_type
>(extensionData
->trackers
.size()));
349 for (const lt::announce_entry
&announceEntry
: extensionData
->trackers
)
350 m_trackerEntries
.append({QString::fromStdString(announceEntry
.url
), announceEntry
.tier
});
351 m_urlSeeds
.reserve(static_cast<decltype(m_urlSeeds
)::size_type
>(extensionData
->urlSeeds
.size()));
352 for (const std::string
&urlSeed
: extensionData
->urlSeeds
)
353 m_urlSeeds
.append(QString::fromStdString(urlSeed
));
354 m_nativeStatus
= extensionData
->status
;
362 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority
);
365 TorrentImpl::~TorrentImpl() = default;
367 bool TorrentImpl::isValid() const
369 return m_nativeHandle
.is_valid();
372 Session
*TorrentImpl::session() const
377 InfoHash
TorrentImpl::infoHash() const
382 QString
TorrentImpl::name() const
384 if (!m_name
.isEmpty())
388 return m_torrentInfo
.name();
390 const QString name
= QString::fromStdString(m_nativeStatus
.name
);
394 return id().toString();
397 QDateTime
TorrentImpl::creationDate() const
399 return m_torrentInfo
.creationDate();
402 QString
TorrentImpl::creator() const
404 return m_torrentInfo
.creator();
407 QString
TorrentImpl::comment() const
409 return m_torrentInfo
.comment();
412 bool TorrentImpl::isPrivate() const
414 return m_torrentInfo
.isPrivate();
417 qlonglong
TorrentImpl::totalSize() const
419 return m_torrentInfo
.totalSize();
422 // size without the "don't download" files
423 qlonglong
TorrentImpl::wantedSize() const
425 return m_nativeStatus
.total_wanted
;
428 qlonglong
TorrentImpl::completedSize() const
430 return m_nativeStatus
.total_wanted_done
;
433 qlonglong
TorrentImpl::pieceLength() const
435 return m_torrentInfo
.pieceLength();
438 qlonglong
TorrentImpl::wastedSize() const
440 return (m_nativeStatus
.total_failed_bytes
+ m_nativeStatus
.total_redundant_bytes
);
443 QString
TorrentImpl::currentTracker() const
445 return QString::fromStdString(m_nativeStatus
.current_tracker
);
448 Path
TorrentImpl::savePath() const
450 return isAutoTMMEnabled() ? m_session
->categorySavePath(category()) : m_savePath
;
453 void TorrentImpl::setSavePath(const Path
&path
)
455 Q_ASSERT(!isAutoTMMEnabled());
457 const Path basePath
= m_session
->useCategoryPathsInManualMode()
458 ? m_session
->categorySavePath(category()) : m_session
->savePath();
459 const Path resolvedPath
= (path
.isAbsolute() ? path
: (basePath
/ path
));
460 if (resolvedPath
== savePath())
463 if (isFinished() || m_hasFinishedStatus
|| downloadPath().isEmpty())
465 moveStorage(resolvedPath
, MoveStorageContext::ChangeSavePath
);
469 m_savePath
= resolvedPath
;
470 m_session
->handleTorrentSavePathChanged(this);
471 deferredRequestResumeData();
475 Path
TorrentImpl::downloadPath() const
477 return isAutoTMMEnabled() ? m_session
->categoryDownloadPath(category()) : m_downloadPath
;
480 void TorrentImpl::setDownloadPath(const Path
&path
)
482 Q_ASSERT(!isAutoTMMEnabled());
484 const Path basePath
= m_session
->useCategoryPathsInManualMode()
485 ? m_session
->categoryDownloadPath(category()) : m_session
->downloadPath();
486 const Path resolvedPath
= (path
.isEmpty() || path
.isAbsolute()) ? path
: (basePath
/ path
);
487 if (resolvedPath
== m_downloadPath
)
490 const bool isIncomplete
= !(isFinished() || m_hasFinishedStatus
);
493 moveStorage((resolvedPath
.isEmpty() ? savePath() : resolvedPath
), MoveStorageContext::ChangeDownloadPath
);
497 m_downloadPath
= resolvedPath
;
498 m_session
->handleTorrentSavePathChanged(this);
499 deferredRequestResumeData();
503 Path
TorrentImpl::rootPath() const
508 const Path relativeRootPath
= Path::findRootFolder(filePaths());
509 if (relativeRootPath
.isEmpty())
512 return (actualStorageLocation() / relativeRootPath
);
515 Path
TorrentImpl::contentPath() const
520 if (filesCount() == 1)
521 return (actualStorageLocation() / filePath(0));
523 const Path rootPath
= this->rootPath();
524 return (rootPath
.isEmpty() ? actualStorageLocation() : rootPath
);
527 bool TorrentImpl::isAutoTMMEnabled() const
532 void TorrentImpl::setAutoTMMEnabled(bool enabled
)
534 if (m_useAutoTMM
== enabled
)
537 m_useAutoTMM
= enabled
;
540 m_savePath
= m_session
->categorySavePath(category());
541 m_downloadPath
= m_session
->categoryDownloadPath(category());
544 deferredRequestResumeData();
545 m_session
->handleTorrentSavingModeChanged(this);
547 adjustStorageLocation();
550 Path
TorrentImpl::actualStorageLocation() const
555 return Path(m_nativeStatus
.save_path
);
558 void TorrentImpl::setAutoManaged(const bool enable
)
561 m_nativeHandle
.set_flags(lt::torrent_flags::auto_managed
);
563 m_nativeHandle
.unset_flags(lt::torrent_flags::auto_managed
);
566 Path
TorrentImpl::makeActualPath(int index
, const Path
&path
) const
568 Path actualPath
= path
;
570 if (m_session
->isAppendExtensionEnabled()
571 && (fileSize(index
) > 0) && !m_completedFiles
.at(index
))
573 actualPath
+= QB_EXT
;
576 if (m_session
->isUnwantedFolderEnabled()
577 && (m_filePriorities
[index
] == DownloadPriority::Ignored
))
579 const Path parentPath
= actualPath
.parentPath();
580 const QString fileName
= actualPath
.filename();
581 actualPath
= parentPath
/ Path(UNWANTED_FOLDER_NAME
) / Path(fileName
);
587 Path
TorrentImpl::makeUserPath(const Path
&path
) const
589 Path userPath
= path
.removedExtension(QB_EXT
);
591 const Path parentRelPath
= userPath
.parentPath();
592 if (parentRelPath
.filename() == UNWANTED_FOLDER_NAME
)
594 const QString fileName
= userPath
.filename();
595 const Path relPath
= parentRelPath
.parentPath();
596 userPath
= relPath
/ Path(fileName
);
602 QVector
<TrackerEntry
> TorrentImpl::trackers() const
604 return m_trackerEntries
;
607 void TorrentImpl::addTrackers(QVector
<TrackerEntry
> trackers
)
609 trackers
.removeIf([](const TrackerEntry
&entry
) { return entry
.url
.isEmpty(); });
611 const auto newTrackers
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend())
612 - QSet
<TrackerEntry
>(m_trackerEntries
.cbegin(), m_trackerEntries
.cend());
613 if (newTrackers
.isEmpty())
616 trackers
= QVector
<TrackerEntry
>(newTrackers
.cbegin(), newTrackers
.cend());
617 for (const TrackerEntry
&tracker
: trackers
)
618 m_nativeHandle
.add_tracker(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
620 m_trackerEntries
.append(trackers
);
621 std::sort(m_trackerEntries
.begin(), m_trackerEntries
.end()
622 , [](const TrackerEntry
&lhs
, const TrackerEntry
&rhs
) { return lhs
.tier
< rhs
.tier
; });
624 deferredRequestResumeData();
625 m_session
->handleTorrentTrackersAdded(this, trackers
);
628 void TorrentImpl::removeTrackers(const QStringList
&trackers
)
630 QStringList removedTrackers
= trackers
;
631 for (const QString
&tracker
: trackers
)
633 if (!m_trackerEntries
.removeOne({tracker
}))
634 removedTrackers
.removeOne(tracker
);
637 std::vector
<lt::announce_entry
> nativeTrackers
;
638 nativeTrackers
.reserve(m_trackerEntries
.size());
639 for (const TrackerEntry
&tracker
: asConst(m_trackerEntries
))
640 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
642 if (!removedTrackers
.isEmpty())
644 m_nativeHandle
.replace_trackers(nativeTrackers
);
646 deferredRequestResumeData();
647 m_session
->handleTorrentTrackersRemoved(this, removedTrackers
);
651 void TorrentImpl::replaceTrackers(QVector
<TrackerEntry
> trackers
)
653 trackers
.removeIf([](const TrackerEntry
&entry
) { return entry
.url
.isEmpty(); });
654 // Filter out duplicate trackers
655 const auto uniqueTrackers
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend());
656 trackers
= QVector
<TrackerEntry
>(uniqueTrackers
.cbegin(), uniqueTrackers
.cend());
657 std::sort(trackers
.begin(), trackers
.end()
658 , [](const TrackerEntry
&lhs
, const TrackerEntry
&rhs
) { return lhs
.tier
< rhs
.tier
; });
660 std::vector
<lt::announce_entry
> nativeTrackers
;
661 nativeTrackers
.reserve(trackers
.size());
662 for (const TrackerEntry
&tracker
: trackers
)
663 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
665 m_nativeHandle
.replace_trackers(nativeTrackers
);
666 m_trackerEntries
= trackers
;
668 // Clear the peer list if it's a private torrent since
669 // we do not want to keep connecting with peers from old tracker.
673 deferredRequestResumeData();
674 m_session
->handleTorrentTrackersChanged(this);
677 QVector
<QUrl
> TorrentImpl::urlSeeds() const
682 void TorrentImpl::addUrlSeeds(const QVector
<QUrl
> &urlSeeds
)
684 m_session
->invokeAsync([urlSeeds
, session
= m_session
685 , nativeHandle
= m_nativeHandle
686 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
690 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
691 QVector
<QUrl
> currentSeeds
;
692 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
693 for (const std::string
&urlSeed
: nativeSeeds
)
694 currentSeeds
.append(QString::fromStdString(urlSeed
));
696 QVector
<QUrl
> addedUrlSeeds
;
697 addedUrlSeeds
.reserve(urlSeeds
.size());
699 for (const QUrl
&url
: urlSeeds
)
701 if (!currentSeeds
.contains(url
))
703 nativeHandle
.add_url_seed(url
.toString().toStdString());
704 addedUrlSeeds
.append(url
);
708 currentSeeds
.append(addedUrlSeeds
);
709 session
->invoke([session
, thisTorrent
, currentSeeds
, addedUrlSeeds
]
714 thisTorrent
->m_urlSeeds
= currentSeeds
;
715 if (!addedUrlSeeds
.isEmpty())
717 thisTorrent
->deferredRequestResumeData();
718 session
->handleTorrentUrlSeedsAdded(thisTorrent
, addedUrlSeeds
);
722 catch (const std::exception
&) {}
726 void TorrentImpl::removeUrlSeeds(const QVector
<QUrl
> &urlSeeds
)
728 m_session
->invokeAsync([urlSeeds
, session
= m_session
729 , nativeHandle
= m_nativeHandle
730 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
734 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
735 QVector
<QUrl
> currentSeeds
;
736 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
737 for (const std::string
&urlSeed
: nativeSeeds
)
738 currentSeeds
.append(QString::fromStdString(urlSeed
));
740 QVector
<QUrl
> removedUrlSeeds
;
741 removedUrlSeeds
.reserve(urlSeeds
.size());
743 for (const QUrl
&url
: urlSeeds
)
745 if (currentSeeds
.removeOne(url
))
747 nativeHandle
.remove_url_seed(url
.toString().toStdString());
748 removedUrlSeeds
.append(url
);
752 session
->invoke([session
, thisTorrent
, currentSeeds
, removedUrlSeeds
]
757 thisTorrent
->m_urlSeeds
= currentSeeds
;
759 if (!removedUrlSeeds
.isEmpty())
761 thisTorrent
->deferredRequestResumeData();
762 session
->handleTorrentUrlSeedsRemoved(thisTorrent
, removedUrlSeeds
);
766 catch (const std::exception
&) {}
770 void TorrentImpl::clearPeers()
772 m_nativeHandle
.clear_peers();
775 bool TorrentImpl::connectPeer(const PeerAddress
&peerAddress
)
778 const lt::address addr
= lt::make_address(peerAddress
.ip
.toString().toStdString(), ec
);
779 if (ec
) return false;
781 const lt::tcp::endpoint
endpoint(addr
, peerAddress
.port
);
784 m_nativeHandle
.connect_peer(endpoint
);
786 catch (const lt::system_error
&err
)
788 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
789 .arg(peerAddress
.toString(), name(), QString::fromLocal8Bit(err
.what())), Log::WARNING
);
793 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress
.toString(), name()));
797 bool TorrentImpl::needSaveResumeData() const
799 return m_nativeStatus
.need_save_resume
;
802 void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags
)
804 m_nativeHandle
.save_resume_data(flags
);
805 m_deferredRequestResumeDataInvoked
= false;
807 m_session
->handleTorrentResumeDataRequested(this);
810 void TorrentImpl::deferredRequestResumeData()
812 if (!m_deferredRequestResumeDataInvoked
)
814 QMetaObject::invokeMethod(this, [this]
816 requestResumeData((m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
817 ? lt::torrent_handle::save_info_dict
: lt::resume_data_flags_t());
818 }, Qt::QueuedConnection
);
820 m_deferredRequestResumeDataInvoked
= true;
824 int TorrentImpl::filesCount() const
826 return m_torrentInfo
.filesCount();
829 int TorrentImpl::piecesCount() const
831 return m_torrentInfo
.piecesCount();
834 int TorrentImpl::piecesHave() const
836 return m_nativeStatus
.num_pieces
;
839 qreal
TorrentImpl::progress() const
842 return m_nativeStatus
.progress
;
844 if (m_nativeStatus
.total_wanted
== 0)
847 if (m_nativeStatus
.total_wanted_done
== m_nativeStatus
.total_wanted
)
850 const qreal progress
= static_cast<qreal
>(m_nativeStatus
.total_wanted_done
) / m_nativeStatus
.total_wanted
;
851 if ((progress
< 0.f
) || (progress
> 1.f
))
853 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
854 .arg(name(), QString::number(m_nativeStatus
.total_wanted
), QString::number(m_nativeStatus
.total_wanted_done
))
861 QString
TorrentImpl::category() const
866 bool TorrentImpl::belongsToCategory(const QString
&category
) const
868 if (m_category
.isEmpty())
869 return category
.isEmpty();
871 if (m_category
== category
)
874 return (m_session
->isSubcategoriesEnabled() && m_category
.startsWith(category
+ u
'/'));
877 TagSet
TorrentImpl::tags() const
882 bool TorrentImpl::hasTag(const Tag
&tag
) const
884 return m_tags
.contains(tag
);
887 bool TorrentImpl::addTag(const Tag
&tag
)
894 if (!m_session
->hasTag(tag
))
896 if (!m_session
->addTag(tag
))
900 deferredRequestResumeData();
901 m_session
->handleTorrentTagAdded(this, tag
);
905 bool TorrentImpl::removeTag(const Tag
&tag
)
907 if (m_tags
.remove(tag
))
909 deferredRequestResumeData();
910 m_session
->handleTorrentTagRemoved(this, tag
);
916 void TorrentImpl::removeAllTags()
918 for (const Tag
&tag
: asConst(tags()))
922 QDateTime
TorrentImpl::addedTime() const
924 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.added_time
);
927 qreal
TorrentImpl::ratioLimit() const
932 int TorrentImpl::seedingTimeLimit() const
934 return m_seedingTimeLimit
;
937 int TorrentImpl::inactiveSeedingTimeLimit() const
939 return m_inactiveSeedingTimeLimit
;
942 Path
TorrentImpl::filePath(const int index
) const
944 Q_ASSERT(index
>= 0);
945 Q_ASSERT(index
< m_filePaths
.size());
947 return m_filePaths
.value(index
, {});
950 Path
TorrentImpl::actualFilePath(const int index
) const
952 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
954 Q_ASSERT(index
>= 0);
955 Q_ASSERT(index
< nativeIndexes
.size());
956 if ((index
< 0) || (index
>= nativeIndexes
.size()))
959 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes
[index
]));
962 qlonglong
TorrentImpl::fileSize(const int index
) const
964 return m_torrentInfo
.fileSize(index
);
967 PathList
TorrentImpl::filePaths() const
972 QVector
<DownloadPriority
> TorrentImpl::filePriorities() const
974 return m_filePriorities
;
977 TorrentInfo
TorrentImpl::info() const
979 return m_torrentInfo
;
982 bool TorrentImpl::isPaused() const
987 bool TorrentImpl::isQueued() const
989 // Torrent is Queued if it isn't in Paused state but paused internally
991 && (m_nativeStatus
.flags
& lt::torrent_flags::auto_managed
)
992 && (m_nativeStatus
.flags
& lt::torrent_flags::paused
));
995 bool TorrentImpl::isChecking() const
997 return ((m_nativeStatus
.state
== lt::torrent_status::checking_files
)
998 || (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
));
1001 bool TorrentImpl::isDownloading() const
1005 case TorrentState::Downloading
:
1006 case TorrentState::DownloadingMetadata
:
1007 case TorrentState::ForcedDownloadingMetadata
:
1008 case TorrentState::StalledDownloading
:
1009 case TorrentState::CheckingDownloading
:
1010 case TorrentState::PausedDownloading
:
1011 case TorrentState::QueuedDownloading
:
1012 case TorrentState::ForcedDownloading
:
1021 bool TorrentImpl::isMoving() const
1023 return m_state
== TorrentState::Moving
;
1026 bool TorrentImpl::isUploading() const
1030 case TorrentState::Uploading
:
1031 case TorrentState::StalledUploading
:
1032 case TorrentState::CheckingUploading
:
1033 case TorrentState::QueuedUploading
:
1034 case TorrentState::ForcedUploading
:
1043 bool TorrentImpl::isCompleted() const
1047 case TorrentState::Uploading
:
1048 case TorrentState::StalledUploading
:
1049 case TorrentState::CheckingUploading
:
1050 case TorrentState::PausedUploading
:
1051 case TorrentState::QueuedUploading
:
1052 case TorrentState::ForcedUploading
:
1061 bool TorrentImpl::isActive() const
1065 case TorrentState::StalledDownloading
:
1066 return (uploadPayloadRate() > 0);
1068 case TorrentState::DownloadingMetadata
:
1069 case TorrentState::ForcedDownloadingMetadata
:
1070 case TorrentState::Downloading
:
1071 case TorrentState::ForcedDownloading
:
1072 case TorrentState::Uploading
:
1073 case TorrentState::ForcedUploading
:
1074 case TorrentState::Moving
:
1084 bool TorrentImpl::isInactive() const
1089 bool TorrentImpl::isErrored() const
1091 return ((m_state
== TorrentState::MissingFiles
)
1092 || (m_state
== TorrentState::Error
));
1095 bool TorrentImpl::isFinished() const
1097 return ((m_nativeStatus
.state
== lt::torrent_status::finished
)
1098 || (m_nativeStatus
.state
== lt::torrent_status::seeding
));
1101 bool TorrentImpl::isForced() const
1103 return (!isPaused() && (m_operatingMode
== TorrentOperatingMode::Forced
));
1106 bool TorrentImpl::isSequentialDownload() const
1108 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::sequential_download
);
1111 bool TorrentImpl::hasFirstLastPiecePriority() const
1113 return m_hasFirstLastPiecePriority
;
1116 TorrentState
TorrentImpl::state() const
1121 void TorrentImpl::updateState()
1123 if (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
)
1125 m_state
= TorrentState::CheckingResumeData
;
1127 else if (isMoveInProgress())
1129 m_state
= TorrentState::Moving
;
1131 else if (hasMissingFiles())
1133 m_state
= TorrentState::MissingFiles
;
1135 else if (hasError())
1137 m_state
= TorrentState::Error
;
1139 else if (!hasMetadata())
1142 m_state
= TorrentState::PausedDownloading
;
1143 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1144 m_state
= TorrentState::QueuedDownloading
;
1146 m_state
= isForced() ? TorrentState::ForcedDownloadingMetadata
: TorrentState::DownloadingMetadata
;
1148 else if ((m_nativeStatus
.state
== lt::torrent_status::checking_files
) && !isPaused())
1150 // If the torrent is not just in the "checking" state, but is being actually checked
1151 m_state
= m_hasFinishedStatus
? TorrentState::CheckingUploading
: TorrentState::CheckingDownloading
;
1153 else if (isFinished())
1156 m_state
= TorrentState::PausedUploading
;
1157 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1158 m_state
= TorrentState::QueuedUploading
;
1159 else if (isForced())
1160 m_state
= TorrentState::ForcedUploading
;
1161 else if (m_nativeStatus
.upload_payload_rate
> 0)
1162 m_state
= TorrentState::Uploading
;
1164 m_state
= TorrentState::StalledUploading
;
1169 m_state
= TorrentState::PausedDownloading
;
1170 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1171 m_state
= TorrentState::QueuedDownloading
;
1172 else if (isForced())
1173 m_state
= TorrentState::ForcedDownloading
;
1174 else if (m_nativeStatus
.download_payload_rate
> 0)
1175 m_state
= TorrentState::Downloading
;
1177 m_state
= TorrentState::StalledDownloading
;
1181 bool TorrentImpl::hasMetadata() const
1183 return m_torrentInfo
.isValid();
1186 bool TorrentImpl::hasMissingFiles() const
1188 return m_hasMissingFiles
;
1191 bool TorrentImpl::hasError() const
1193 return (m_nativeStatus
.errc
|| (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
));
1196 int TorrentImpl::queuePosition() const
1198 return static_cast<int>(m_nativeStatus
.queue_position
);
1201 QString
TorrentImpl::error() const
1203 if (m_nativeStatus
.errc
)
1204 return QString::fromLocal8Bit(m_nativeStatus
.errc
.message().c_str());
1206 if (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
)
1208 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1209 .arg(QString::fromLocal8Bit(m_lastFileError
.error
.message().c_str()));
1215 qlonglong
TorrentImpl::totalDownload() const
1217 return m_nativeStatus
.all_time_download
;
1220 qlonglong
TorrentImpl::totalUpload() const
1222 return m_nativeStatus
.all_time_upload
;
1225 qlonglong
TorrentImpl::activeTime() const
1227 return lt::total_seconds(m_nativeStatus
.active_duration
);
1230 qlonglong
TorrentImpl::finishedTime() const
1232 return lt::total_seconds(m_nativeStatus
.finished_duration
);
1235 qlonglong
TorrentImpl::eta() const
1237 if (isPaused()) return MAX_ETA
;
1239 const SpeedSampleAvg speedAverage
= m_payloadRateMonitor
.average();
1243 const qreal maxRatioValue
= maxRatio();
1244 const int maxSeedingTimeValue
= maxSeedingTime();
1245 const int maxInactiveSeedingTimeValue
= maxInactiveSeedingTime();
1246 if ((maxRatioValue
< 0) && (maxSeedingTimeValue
< 0) && (maxInactiveSeedingTimeValue
< 0)) return MAX_ETA
;
1248 qlonglong ratioEta
= MAX_ETA
;
1250 if ((speedAverage
.upload
> 0) && (maxRatioValue
>= 0))
1253 qlonglong realDL
= totalDownload();
1255 realDL
= wantedSize();
1257 ratioEta
= ((realDL
* maxRatioValue
) - totalUpload()) / speedAverage
.upload
;
1260 qlonglong seedingTimeEta
= MAX_ETA
;
1262 if (maxSeedingTimeValue
>= 0)
1264 seedingTimeEta
= (maxSeedingTimeValue
* 60) - finishedTime();
1265 if (seedingTimeEta
< 0)
1269 qlonglong inactiveSeedingTimeEta
= MAX_ETA
;
1271 if (maxInactiveSeedingTimeValue
>= 0)
1273 inactiveSeedingTimeEta
= (maxInactiveSeedingTimeValue
* 60) - timeSinceActivity();
1274 inactiveSeedingTimeEta
= std::max
<qlonglong
>(inactiveSeedingTimeEta
, 0);
1277 return std::min({ratioEta
, seedingTimeEta
, inactiveSeedingTimeEta
});
1280 if (!speedAverage
.download
) return MAX_ETA
;
1282 return (wantedSize() - completedSize()) / speedAverage
.download
;
1285 QVector
<qreal
> TorrentImpl::filesProgress() const
1290 const int count
= m_filesProgress
.size();
1291 Q_ASSERT(count
== filesCount());
1292 if (count
!= filesCount()) [[unlikely
]]
1295 if (m_completedFiles
.count(true) == count
)
1296 return QVector
<qreal
>(count
, 1);
1298 QVector
<qreal
> result
;
1299 result
.reserve(count
);
1300 for (int i
= 0; i
< count
; ++i
)
1302 const int64_t progress
= m_filesProgress
.at(i
);
1303 const int64_t size
= fileSize(i
);
1304 if ((size
<= 0) || (progress
== size
))
1307 result
<< (progress
/ static_cast<qreal
>(size
));
1313 int TorrentImpl::seedsCount() const
1315 return m_nativeStatus
.num_seeds
;
1318 int TorrentImpl::peersCount() const
1320 return m_nativeStatus
.num_peers
;
1323 int TorrentImpl::leechsCount() const
1325 return (m_nativeStatus
.num_peers
- m_nativeStatus
.num_seeds
);
1328 int TorrentImpl::totalSeedsCount() const
1330 return (m_nativeStatus
.num_complete
> -1) ? m_nativeStatus
.num_complete
: m_nativeStatus
.list_seeds
;
1333 int TorrentImpl::totalPeersCount() const
1335 const int peers
= m_nativeStatus
.num_complete
+ m_nativeStatus
.num_incomplete
;
1336 return (peers
> -1) ? peers
: m_nativeStatus
.list_peers
;
1339 int TorrentImpl::totalLeechersCount() const
1341 return (m_nativeStatus
.num_incomplete
> -1) ? m_nativeStatus
.num_incomplete
: (m_nativeStatus
.list_peers
- m_nativeStatus
.list_seeds
);
1344 QDateTime
TorrentImpl::lastSeenComplete() const
1346 if (m_nativeStatus
.last_seen_complete
> 0)
1347 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.last_seen_complete
);
1352 QDateTime
TorrentImpl::completedTime() const
1354 if (m_nativeStatus
.completed_time
> 0)
1355 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.completed_time
);
1360 qlonglong
TorrentImpl::timeSinceUpload() const
1362 if (m_nativeStatus
.last_upload
.time_since_epoch().count() == 0)
1364 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_upload
);
1367 qlonglong
TorrentImpl::timeSinceDownload() const
1369 if (m_nativeStatus
.last_download
.time_since_epoch().count() == 0)
1371 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_download
);
1374 qlonglong
TorrentImpl::timeSinceActivity() const
1376 const qlonglong upTime
= timeSinceUpload();
1377 const qlonglong downTime
= timeSinceDownload();
1378 return ((upTime
< 0) != (downTime
< 0))
1379 ? std::max(upTime
, downTime
)
1380 : std::min(upTime
, downTime
);
1383 int TorrentImpl::downloadLimit() const
1385 return m_downloadLimit
;;
1388 int TorrentImpl::uploadLimit() const
1390 return m_uploadLimit
;
1393 bool TorrentImpl::superSeeding() const
1395 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::super_seeding
);
1398 bool TorrentImpl::isDHTDisabled() const
1400 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_dht
);
1403 bool TorrentImpl::isPEXDisabled() const
1405 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_pex
);
1408 bool TorrentImpl::isLSDDisabled() const
1410 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_lsd
);
1413 QVector
<PeerInfo
> TorrentImpl::peers() const
1415 std::vector
<lt::peer_info
> nativePeers
;
1416 m_nativeHandle
.get_peer_info(nativePeers
);
1418 QVector
<PeerInfo
> peers
;
1419 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
1421 for (const lt::peer_info
&peer
: nativePeers
)
1422 peers
.append(PeerInfo(peer
, pieces()));
1427 QBitArray
TorrentImpl::pieces() const
1432 QBitArray
TorrentImpl::downloadingPieces() const
1434 QBitArray
result(piecesCount());
1436 std::vector
<lt::partial_piece_info
> queue
;
1437 m_nativeHandle
.get_download_queue(queue
);
1439 for (const lt::partial_piece_info
&info
: queue
)
1440 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
1445 QVector
<int> TorrentImpl::pieceAvailability() const
1447 std::vector
<int> avail
;
1448 m_nativeHandle
.piece_availability(avail
);
1450 return {avail
.cbegin(), avail
.cend()};
1453 qreal
TorrentImpl::distributedCopies() const
1455 return m_nativeStatus
.distributed_copies
;
1458 qreal
TorrentImpl::maxRatio() const
1460 if (m_ratioLimit
== USE_GLOBAL_RATIO
)
1461 return m_session
->globalMaxRatio();
1463 return m_ratioLimit
;
1466 int TorrentImpl::maxSeedingTime() const
1468 if (m_seedingTimeLimit
== USE_GLOBAL_SEEDING_TIME
)
1469 return m_session
->globalMaxSeedingMinutes();
1471 return m_seedingTimeLimit
;
1474 int TorrentImpl::maxInactiveSeedingTime() const
1476 if (m_inactiveSeedingTimeLimit
== USE_GLOBAL_INACTIVE_SEEDING_TIME
)
1477 return m_session
->globalMaxInactiveSeedingMinutes();
1479 return m_inactiveSeedingTimeLimit
;
1482 qreal
TorrentImpl::realRatio() const
1484 const int64_t upload
= m_nativeStatus
.all_time_upload
;
1485 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1486 const int64_t download
= (m_nativeStatus
.all_time_download
< (m_nativeStatus
.total_done
* 0.01))
1487 ? m_nativeStatus
.total_done
1488 : m_nativeStatus
.all_time_download
;
1491 return (upload
== 0) ? 0 : MAX_RATIO
;
1493 const qreal ratio
= upload
/ static_cast<qreal
>(download
);
1494 Q_ASSERT(ratio
>= 0);
1495 return (ratio
> MAX_RATIO
) ? MAX_RATIO
: ratio
;
1498 int TorrentImpl::uploadPayloadRate() const
1500 // workaround: suppress the speed for paused state
1501 return isPaused() ? 0 : m_nativeStatus
.upload_payload_rate
;
1504 int TorrentImpl::downloadPayloadRate() const
1506 // workaround: suppress the speed for paused state
1507 return isPaused() ? 0 : m_nativeStatus
.download_payload_rate
;
1510 qlonglong
TorrentImpl::totalPayloadUpload() const
1512 return m_nativeStatus
.total_payload_upload
;
1515 qlonglong
TorrentImpl::totalPayloadDownload() const
1517 return m_nativeStatus
.total_payload_download
;
1520 int TorrentImpl::connectionsCount() const
1522 return m_nativeStatus
.num_connections
;
1525 int TorrentImpl::connectionsLimit() const
1527 return m_nativeStatus
.connections_limit
;
1530 qlonglong
TorrentImpl::nextAnnounce() const
1532 return lt::total_seconds(m_nativeStatus
.next_announce
);
1535 void TorrentImpl::setName(const QString
&name
)
1540 deferredRequestResumeData();
1541 m_session
->handleTorrentNameChanged(this);
1545 bool TorrentImpl::setCategory(const QString
&category
)
1547 if (m_category
!= category
)
1549 if (!category
.isEmpty() && !m_session
->categories().contains(category
))
1552 const QString oldCategory
= m_category
;
1553 m_category
= category
;
1554 deferredRequestResumeData();
1555 m_session
->handleTorrentCategoryChanged(this, oldCategory
);
1559 if (!m_session
->isDisableAutoTMMWhenCategoryChanged())
1560 adjustStorageLocation();
1562 setAutoTMMEnabled(false);
1569 void TorrentImpl::forceReannounce(const int index
)
1571 m_nativeHandle
.force_reannounce(0, index
);
1574 void TorrentImpl::forceDHTAnnounce()
1576 m_nativeHandle
.force_dht_announce();
1579 void TorrentImpl::forceRecheck()
1584 m_nativeHandle
.force_recheck();
1585 // We have to force update the cached state, otherwise someone will be able to get
1586 // an incorrect one during the interval until the cached state is updated in a regular way.
1587 m_nativeStatus
.state
= lt::torrent_status::checking_resume_data
;
1589 m_hasMissingFiles
= false;
1590 m_unchecked
= false;
1592 m_completedFiles
.fill(false);
1593 m_filesProgress
.fill(0);
1594 m_pieces
.fill(false);
1595 m_nativeStatus
.pieces
.clear_all();
1596 m_nativeStatus
.num_pieces
= 0;
1600 // When "force recheck" is applied on paused torrent, we temporarily resume it
1602 m_stopCondition
= StopCondition::FilesChecked
;
1606 void TorrentImpl::setSequentialDownload(const bool enable
)
1610 m_nativeHandle
.set_flags(lt::torrent_flags::sequential_download
);
1611 m_nativeStatus
.flags
|= lt::torrent_flags::sequential_download
; // prevent return cached value
1615 m_nativeHandle
.unset_flags(lt::torrent_flags::sequential_download
);
1616 m_nativeStatus
.flags
&= ~lt::torrent_flags::sequential_download
; // prevent return cached value
1619 deferredRequestResumeData();
1622 void TorrentImpl::setFirstLastPiecePriority(const bool enabled
)
1624 if (m_hasFirstLastPiecePriority
== enabled
)
1627 m_hasFirstLastPiecePriority
= enabled
;
1629 applyFirstLastPiecePriority(enabled
);
1631 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1632 .arg((enabled
? tr("On") : tr("Off")), name()));
1634 deferredRequestResumeData();
1637 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled
)
1639 Q_ASSERT(hasMetadata());
1641 // Download first and last pieces first for every file in the torrent
1643 auto piecePriorities
= std::vector
<lt::download_priority_t
>(m_torrentInfo
.piecesCount(), LT::toNative(DownloadPriority::Ignored
));
1645 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1646 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1647 for (int fileIndex
= 0; fileIndex
< m_filePriorities
.size(); ++fileIndex
)
1649 const DownloadPriority filePrio
= m_filePriorities
[fileIndex
];
1650 if (filePrio
<= DownloadPriority::Ignored
)
1653 // Determine the priority to set
1654 const lt::download_priority_t piecePrio
= LT::toNative(enabled
? DownloadPriority::Maximum
: filePrio
);
1655 const TorrentInfo::PieceRange pieceRange
= m_torrentInfo
.filePieces(fileIndex
);
1657 // worst case: AVI index = 1% of total file size (at the end of the file)
1658 const int numPieces
= std::ceil(fileSize(fileIndex
) * 0.01 / pieceLength());
1659 for (int i
= 0; i
< numPieces
; ++i
)
1661 piecePriorities
[pieceRange
.first() + i
] = piecePrio
;
1662 piecePriorities
[pieceRange
.last() - i
] = piecePrio
;
1665 const int firstPiece
= pieceRange
.first() + numPieces
;
1666 const int lastPiece
= pieceRange
.last() - numPieces
;
1667 for (int pieceIndex
= firstPiece
; pieceIndex
<= lastPiece
; ++pieceIndex
)
1668 piecePriorities
[pieceIndex
] = LT::toNative(filePrio
);
1671 m_nativeHandle
.prioritize_pieces(piecePriorities
);
1674 void TorrentImpl::fileSearchFinished(const Path
&savePath
, const PathList
&fileNames
)
1676 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
1677 endReceivedMetadataHandling(savePath
, fileNames
);
1680 TrackerEntry
TorrentImpl::updateTrackerEntry(const lt::announce_entry
&announceEntry
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
1682 const auto it
= std::find_if(m_trackerEntries
.begin(), m_trackerEntries
.end()
1683 , [&announceEntry
](const TrackerEntry
&trackerEntry
)
1685 return (trackerEntry
.url
== QString::fromStdString(announceEntry
.url
));
1688 Q_ASSERT(it
!= m_trackerEntries
.end());
1689 if (it
== m_trackerEntries
.end()) [[unlikely
]]
1692 #ifdef QBT_USES_LIBTORRENT2
1693 QSet
<int> btProtocols
;
1694 const auto &infoHashes
= nativeHandle().info_hashes();
1695 if (infoHashes
.has(lt::protocol_version::V1
))
1696 btProtocols
.insert(1);
1697 if (infoHashes
.has(lt::protocol_version::V2
))
1698 btProtocols
.insert(2);
1700 const QSet
<int> btProtocols
{1};
1702 ::updateTrackerEntry(*it
, announceEntry
, btProtocols
, updateInfo
);
1706 void TorrentImpl::resetTrackerEntries()
1708 for (auto &trackerEntry
: m_trackerEntries
)
1709 trackerEntry
= {trackerEntry
.url
, trackerEntry
.tier
};
1712 std::shared_ptr
<const libtorrent::torrent_info
> TorrentImpl::nativeTorrentInfo() const
1714 Q_ASSERT(!m_nativeStatus
.torrent_file
.expired());
1716 return m_nativeStatus
.torrent_file
.lock();
1719 void TorrentImpl::endReceivedMetadataHandling(const Path
&savePath
, const PathList
&fileNames
)
1721 Q_ASSERT(m_maintenanceJob
== MaintenanceJob::HandleMetadata
);
1722 if (m_maintenanceJob
!= MaintenanceJob::HandleMetadata
) [[unlikely
]]
1725 Q_ASSERT(m_filePaths
.isEmpty());
1726 if (!m_filePaths
.isEmpty()) [[unlikely
]]
1727 m_filePaths
.clear();
1729 lt::add_torrent_params
&p
= m_ltAddTorrentParams
;
1731 const std::shared_ptr
<lt::torrent_info
> metadata
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1732 m_torrentInfo
= TorrentInfo(*metadata
);
1733 m_filePriorities
.reserve(filesCount());
1734 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
1735 p
.file_priorities
= resized(p
.file_priorities
, metadata
->files().num_files()
1736 , LT::toNative(p
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
1738 m_completedFiles
.fill(static_cast<bool>(p
.flags
& lt::torrent_flags::seed_mode
), filesCount());
1739 m_filesProgress
.resize(filesCount());
1742 for (int i
= 0; i
< fileNames
.size(); ++i
)
1744 const auto nativeIndex
= nativeIndexes
.at(i
);
1746 const Path
&actualFilePath
= fileNames
.at(i
);
1747 p
.renamed_files
[nativeIndex
] = actualFilePath
.toString().toStdString();
1749 const Path filePath
= actualFilePath
.removedExtension(QB_EXT
);
1750 m_filePaths
.append(filePath
);
1752 lt::download_priority_t
&nativePriority
= p
.file_priorities
[LT::toUnderlyingType(nativeIndex
)];
1753 if ((nativePriority
!= lt::dont_download
) && m_session
->isFilenameExcluded(filePath
.filename()))
1754 nativePriority
= lt::dont_download
;
1755 const auto priority
= LT::fromNative(nativePriority
);
1756 m_filePriorities
.append(priority
);
1758 p
.save_path
= savePath
.toString().toStdString();
1761 if (stopCondition() == StopCondition::MetadataReceived
)
1763 m_stopCondition
= StopCondition::None
;
1766 p
.flags
|= lt::torrent_flags::paused
;
1767 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1769 m_session
->handleTorrentPaused(this);
1774 // If first/last piece priority was specified when adding this torrent,
1775 // we should apply it now that we have metadata:
1776 if (m_hasFirstLastPiecePriority
)
1777 applyFirstLastPiecePriority(true);
1779 m_maintenanceJob
= MaintenanceJob::None
;
1780 prepareResumeData(p
);
1782 m_session
->handleTorrentMetadataReceived(this);
1785 void TorrentImpl::reload()
1789 m_completedFiles
.fill(false);
1790 m_filesProgress
.fill(0);
1791 m_pieces
.fill(false);
1792 m_nativeStatus
.pieces
.clear_all();
1793 m_nativeStatus
.num_pieces
= 0;
1795 const auto queuePos
= m_nativeHandle
.queue_position();
1797 m_nativeSession
->remove_torrent(m_nativeHandle
, lt::session::delete_partfile
);
1799 lt::add_torrent_params p
= m_ltAddTorrentParams
;
1800 p
.flags
|= lt::torrent_flags::update_subscribe
1801 | lt::torrent_flags::override_trackers
1802 | lt::torrent_flags::override_web_seeds
;
1806 p
.flags
|= lt::torrent_flags::paused
;
1807 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1809 else if (m_operatingMode
== TorrentOperatingMode::AutoManaged
)
1811 p
.flags
|= (lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1815 p
.flags
&= ~(lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1818 auto *const extensionData
= new ExtensionData
;
1819 p
.userdata
= LTClientData(extensionData
);
1820 m_nativeHandle
= m_nativeSession
->add_torrent(p
);
1822 m_nativeStatus
= extensionData
->status
;
1824 if (queuePos
>= lt::queue_position_t
{})
1825 m_nativeHandle
.queue_position_set(queuePos
);
1826 m_nativeStatus
.queue_position
= queuePos
;
1830 catch (const lt::system_error
&err
)
1832 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1833 .arg(id().toString(), QString::fromLocal8Bit(err
.what())));
1837 void TorrentImpl::pause()
1841 m_stopCondition
= StopCondition::None
;
1843 deferredRequestResumeData();
1844 m_session
->handleTorrentPaused(this);
1847 if (m_maintenanceJob
== MaintenanceJob::None
)
1849 setAutoManaged(false);
1850 m_nativeHandle
.pause();
1852 m_payloadRateMonitor
.reset();
1856 void TorrentImpl::resume(const TorrentOperatingMode mode
)
1860 m_nativeHandle
.clear_error();
1861 m_nativeHandle
.unset_flags(lt::torrent_flags::upload_mode
);
1864 m_operatingMode
= mode
;
1866 if (m_hasMissingFiles
)
1868 m_hasMissingFiles
= false;
1869 m_isStopped
= false;
1870 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1877 m_isStopped
= false;
1878 deferredRequestResumeData();
1879 m_session
->handleTorrentResumed(this);
1882 if (m_maintenanceJob
== MaintenanceJob::None
)
1884 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1885 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1886 m_nativeHandle
.resume();
1890 void TorrentImpl::moveStorage(const Path
&newPath
, const MoveStorageContext context
)
1894 m_savePath
= newPath
;
1895 m_session
->handleTorrentSavePathChanged(this);
1899 const auto mode
= (context
== MoveStorageContext::AdjustCurrentLocation
)
1900 ? MoveStorageMode::Overwrite
: MoveStorageMode::KeepExistingFiles
;
1901 if (m_session
->addMoveTorrentStorageJob(this, newPath
, mode
, context
))
1903 if (!m_storageIsMoving
)
1905 m_storageIsMoving
= true;
1907 m_session
->handleTorrentStorageMovingStateChanged(this);
1912 void TorrentImpl::renameFile(const int index
, const Path
&path
)
1914 Q_ASSERT((index
>= 0) && (index
< filesCount()));
1915 if ((index
< 0) || (index
>= filesCount())) [[unlikely
]]
1918 const Path targetActualPath
= makeActualPath(index
, path
);
1919 doRenameFile(index
, targetActualPath
);
1922 void TorrentImpl::handleStateUpdate(const lt::torrent_status
&nativeStatus
)
1924 updateStatus(nativeStatus
);
1927 void TorrentImpl::handleMoveStorageJobFinished(const Path
&path
, const MoveStorageContext context
, const bool hasOutstandingJob
)
1929 if (context
== MoveStorageContext::ChangeSavePath
)
1931 else if (context
== MoveStorageContext::ChangeDownloadPath
)
1932 m_downloadPath
= path
;
1933 m_storageIsMoving
= hasOutstandingJob
;
1934 m_nativeStatus
.save_path
= path
.toString().toStdString();
1936 m_session
->handleTorrentSavePathChanged(this);
1937 deferredRequestResumeData();
1939 if (!m_storageIsMoving
)
1942 m_session
->handleTorrentStorageMovingStateChanged(this);
1944 if (m_hasMissingFiles
)
1946 // it can be moved to the proper location
1947 m_hasMissingFiles
= false;
1948 m_ltAddTorrentParams
.save_path
= m_nativeStatus
.save_path
;
1949 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1953 while ((m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
1954 std::invoke(m_moveFinishedTriggers
.dequeue());
1958 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused
]] const lt::torrent_checked_alert
*p
)
1962 // The torrent is checked due to metadata received, but we should not process
1963 // this event until the torrent is reloaded using the received metadata.
1967 if (stopCondition() == StopCondition::FilesChecked
)
1970 m_statusUpdatedTriggers
.enqueue([this]()
1972 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1974 if (!m_hasMissingFiles
)
1976 if ((progress() < 1.0) && (wantedSize() > 0))
1977 m_hasFinishedStatus
= false;
1978 else if (progress() == 1.0)
1979 m_hasFinishedStatus
= true;
1981 adjustStorageLocation();
1982 manageActualFilePaths();
1986 // torrent is internally paused using NativeTorrentExtension after files checked
1987 // so we need to resume it if there is no corresponding "stop condition" set
1988 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1989 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1990 m_nativeHandle
.resume();
1994 if (m_nativeStatus
.need_save_resume
)
1995 deferredRequestResumeData();
1997 m_session
->handleTorrentChecked(this);
2001 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused
]] const lt::torrent_finished_alert
*p
)
2003 m_hasMissingFiles
= false;
2004 if (m_hasFinishedStatus
)
2007 m_statusUpdatedTriggers
.enqueue([this]()
2009 adjustStorageLocation();
2010 manageActualFilePaths();
2012 deferredRequestResumeData();
2014 const bool recheckTorrentsOnCompletion
= Preferences::instance()->recheckTorrentsOnCompletion();
2015 if (recheckTorrentsOnCompletion
&& m_unchecked
)
2021 m_hasFinishedStatus
= true;
2023 if (isMoveInProgress() || (m_renameCount
> 0))
2024 m_moveFinishedTriggers
.enqueue([this]() { m_session
->handleTorrentFinished(this); });
2026 m_session
->handleTorrentFinished(this);
2031 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused
]] const lt::torrent_paused_alert
*p
)
2035 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused
]] const lt::torrent_resumed_alert
*p
)
2039 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert
*p
)
2041 if (m_ltAddTorrentParams
.url_seeds
!= p
->params
.url_seeds
)
2043 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2044 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2045 // than the list we usually cache, so we have to request it from the appropriate source.
2046 fetchURLSeeds([this](const QVector
<QUrl
> &urlSeeds
) { m_urlSeeds
= urlSeeds
; });
2049 if ((m_maintenanceJob
== MaintenanceJob::HandleMetadata
) && p
->params
.ti
)
2051 Q_ASSERT(m_indexMap
.isEmpty());
2053 const auto isSeedMode
= static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
);
2054 m_ltAddTorrentParams
= p
->params
;
2056 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2058 m_ltAddTorrentParams
.have_pieces
.clear();
2059 m_ltAddTorrentParams
.verified_pieces
.clear();
2061 m_nativeStatus
.torrent_file
= m_ltAddTorrentParams
.ti
;
2063 const auto metadata
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
2065 const auto &renamedFiles
= m_ltAddTorrentParams
.renamed_files
;
2066 PathList filePaths
= metadata
.filePaths();
2067 if (renamedFiles
.empty() && (m_contentLayout
!= TorrentContentLayout::Original
))
2069 const Path originalRootFolder
= Path::findRootFolder(filePaths
);
2070 const auto originalContentLayout
= (originalRootFolder
.isEmpty()
2071 ? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder
);
2072 if (m_contentLayout
!= originalContentLayout
)
2074 if (m_contentLayout
== TorrentContentLayout::NoSubfolder
)
2075 Path::stripRootFolder(filePaths
);
2077 Path::addRootFolder(filePaths
, filePaths
.at(0).removedExtension());
2081 const auto nativeIndexes
= metadata
.nativeIndexes();
2082 m_indexMap
.reserve(filePaths
.size());
2083 for (int i
= 0; i
< filePaths
.size(); ++i
)
2085 const auto nativeIndex
= nativeIndexes
.at(i
);
2086 m_indexMap
[nativeIndex
] = i
;
2088 if (const auto it
= renamedFiles
.find(nativeIndex
); it
!= renamedFiles
.cend())
2089 filePaths
[i
] = Path(it
->second
);
2092 m_session
->findIncompleteFiles(metadata
, savePath(), downloadPath(), filePaths
);
2096 prepareResumeData(p
->params
);
2100 void TorrentImpl::prepareResumeData(const lt::add_torrent_params
¶ms
)
2102 if (m_hasMissingFiles
)
2104 const auto havePieces
= m_ltAddTorrentParams
.have_pieces
;
2105 const auto unfinishedPieces
= m_ltAddTorrentParams
.unfinished_pieces
;
2106 const auto verifiedPieces
= m_ltAddTorrentParams
.verified_pieces
;
2108 // Update recent resume data but preserve existing progress
2109 m_ltAddTorrentParams
= params
;
2110 m_ltAddTorrentParams
.have_pieces
= havePieces
;
2111 m_ltAddTorrentParams
.unfinished_pieces
= unfinishedPieces
;
2112 m_ltAddTorrentParams
.verified_pieces
= verifiedPieces
;
2116 const bool preserveSeedMode
= (!hasMetadata() && (m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
));
2117 // Update recent resume data
2118 m_ltAddTorrentParams
= params
;
2119 if (preserveSeedMode
)
2120 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2123 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2124 m_ltAddTorrentParams
.flags
&= ~lt::torrent_flags::upload_mode
;
2126 LoadTorrentParams resumeData
;
2127 resumeData
.name
= m_name
;
2128 resumeData
.category
= m_category
;
2129 resumeData
.tags
= m_tags
;
2130 resumeData
.contentLayout
= m_contentLayout
;
2131 resumeData
.ratioLimit
= m_ratioLimit
;
2132 resumeData
.seedingTimeLimit
= m_seedingTimeLimit
;
2133 resumeData
.inactiveSeedingTimeLimit
= m_inactiveSeedingTimeLimit
;
2134 resumeData
.firstLastPiecePriority
= m_hasFirstLastPiecePriority
;
2135 resumeData
.hasFinishedStatus
= m_hasFinishedStatus
;
2136 resumeData
.stopped
= m_isStopped
;
2137 resumeData
.stopCondition
= m_stopCondition
;
2138 resumeData
.operatingMode
= m_operatingMode
;
2139 resumeData
.ltAddTorrentParams
= m_ltAddTorrentParams
;
2140 resumeData
.useAutoTMM
= m_useAutoTMM
;
2141 if (!resumeData
.useAutoTMM
)
2143 resumeData
.savePath
= m_savePath
;
2144 resumeData
.downloadPath
= m_downloadPath
;
2147 m_session
->handleTorrentResumeDataReady(this, resumeData
);
2150 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert
*p
)
2152 if (p
->error
!= lt::errors::resume_data_not_modified
)
2154 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2155 .arg(name(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::CRITICAL
);
2159 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert
*p
)
2161 // Files were probably moved or storage isn't accessible
2162 m_hasMissingFiles
= true;
2163 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2164 .arg(name(), QString::fromStdString(p
->message())), Log::WARNING
);
2167 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert
*p
)
2169 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2170 Q_ASSERT(fileIndex
>= 0);
2172 const Path newActualFilePath
{QString::fromUtf8(p
->new_name())};
2174 const Path oldFilePath
= m_filePaths
.at(fileIndex
);
2175 const Path newFilePath
= makeUserPath(newActualFilePath
);
2177 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2178 // We should compare path in a case sensitive manner even on case insensitive
2179 // platforms since it can be renamed by only changing case of some character(s)
2180 if (oldFilePath
.data() == newFilePath
.data())
2182 // Remove empty ".unwanted" folders
2183 #ifdef QBT_USES_LIBTORRENT2
2184 const Path oldActualFilePath
{QString::fromUtf8(p
->old_name())};
2186 const Path oldActualFilePath
;
2188 const Path oldActualParentPath
= oldActualFilePath
.parentPath();
2189 const Path newActualParentPath
= newActualFilePath
.parentPath();
2190 if (newActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2192 if (oldActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2195 const std::wstring winPath
= (actualStorageLocation() / newActualParentPath
).toString().toStdWString();
2196 const DWORD dwAttrs
= ::GetFileAttributesW(winPath
.c_str());
2197 ::SetFileAttributesW(winPath
.c_str(), (dwAttrs
| FILE_ATTRIBUTE_HIDDEN
));
2201 #ifdef QBT_USES_LIBTORRENT2
2202 else if (oldActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2204 if (newActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2205 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath
);
2210 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath
/ Path(UNWANTED_FOLDER_NAME
));
2216 m_filePaths
[fileIndex
] = newFilePath
;
2218 // Remove empty leftover folders
2219 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2220 // be removed if they are empty
2221 Path oldParentPath
= oldFilePath
.parentPath();
2222 const Path commonBasePath
= Path::commonPath(oldParentPath
, newFilePath
.parentPath());
2223 while (oldParentPath
!= commonBasePath
)
2225 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath
);
2226 oldParentPath
= oldParentPath
.parentPath();
2231 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2232 m_moveFinishedTriggers
.takeFirst()();
2234 deferredRequestResumeData();
2237 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
*p
)
2239 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2240 Q_ASSERT(fileIndex
>= 0);
2242 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2243 .arg(name(), filePath(fileIndex
).toString(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::WARNING
);
2246 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2247 m_moveFinishedTriggers
.takeFirst()();
2249 deferredRequestResumeData();
2252 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert
*p
)
2254 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2257 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2258 Q_ASSERT(fileIndex
>= 0);
2260 m_completedFiles
.setBit(fileIndex
);
2262 const Path actualPath
= actualFilePath(fileIndex
);
2264 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2265 // only apply Mark-of-the-Web to new download files
2266 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2268 const Path fullpath
= actualStorageLocation() / actualPath
;
2269 Utils::OS::applyMarkOfTheWeb(fullpath
);
2271 #endif // Q_OS_MACOS || Q_OS_WIN
2273 if (m_session
->isAppendExtensionEnabled())
2275 const Path path
= filePath(fileIndex
);
2276 if (actualPath
!= path
)
2278 qDebug("Renaming %s to %s", qUtf8Printable(actualPath
.toString()), qUtf8Printable(path
.toString()));
2279 doRenameFile(fileIndex
, path
);
2284 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert
*p
)
2286 m_lastFileError
= {p
->error
, p
->op
};
2289 #ifdef QBT_USES_LIBTORRENT2
2290 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert
*)
2292 deferredRequestResumeData();
2296 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused
]] const lt::metadata_received_alert
*p
)
2298 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2300 #ifdef QBT_USES_LIBTORRENT2
2301 const InfoHash prevInfoHash
= infoHash();
2302 m_infoHash
= InfoHash(m_nativeHandle
.info_hashes());
2303 if (prevInfoHash
!= infoHash())
2304 m_session
->handleTorrentInfoHashChanged(this, prevInfoHash
);
2307 m_maintenanceJob
= MaintenanceJob::HandleMetadata
;
2308 deferredRequestResumeData();
2311 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert
*p
) const
2313 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
))
2317 void TorrentImpl::handleCategoryOptionsChanged()
2320 adjustStorageLocation();
2323 void TorrentImpl::handleAppendExtensionToggled()
2328 manageActualFilePaths();
2331 void TorrentImpl::handleUnwantedFolderToggled()
2336 manageActualFilePaths();
2339 void TorrentImpl::handleAlert(const lt::alert
*a
)
2343 #ifdef QBT_USES_LIBTORRENT2
2344 case lt::file_prio_alert::alert_type
:
2345 handleFilePrioAlert(static_cast<const lt::file_prio_alert
*>(a
));
2348 case lt::file_renamed_alert::alert_type
:
2349 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert
*>(a
));
2351 case lt::file_rename_failed_alert::alert_type
:
2352 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert
*>(a
));
2354 case lt::file_completed_alert::alert_type
:
2355 handleFileCompletedAlert(static_cast<const lt::file_completed_alert
*>(a
));
2357 case lt::file_error_alert::alert_type
:
2358 handleFileErrorAlert(static_cast<const lt::file_error_alert
*>(a
));
2360 case lt::torrent_finished_alert::alert_type
:
2361 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert
*>(a
));
2363 case lt::save_resume_data_alert::alert_type
:
2364 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert
*>(a
));
2366 case lt::save_resume_data_failed_alert::alert_type
:
2367 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert
*>(a
));
2369 case lt::torrent_paused_alert::alert_type
:
2370 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert
*>(a
));
2372 case lt::torrent_resumed_alert::alert_type
:
2373 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert
*>(a
));
2375 case lt::metadata_received_alert::alert_type
:
2376 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(a
));
2378 case lt::fastresume_rejected_alert::alert_type
:
2379 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert
*>(a
));
2381 case lt::torrent_checked_alert::alert_type
:
2382 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert
*>(a
));
2384 case lt::performance_alert::alert_type
:
2385 handlePerformanceAlert(static_cast<const lt::performance_alert
*>(a
));
2390 void TorrentImpl::manageActualFilePaths()
2392 const std::shared_ptr
<const lt::torrent_info
> nativeInfo
= nativeTorrentInfo();
2393 const lt::file_storage
&nativeFiles
= nativeInfo
->files();
2395 for (int i
= 0; i
< filesCount(); ++i
)
2397 const Path path
= filePath(i
);
2399 const auto nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
2400 const Path actualPath
{nativeFiles
.file_path(nativeIndex
)};
2401 const Path targetActualPath
= makeActualPath(i
, path
);
2402 if (actualPath
!= targetActualPath
)
2404 qDebug() << "Renaming" << actualPath
.toString() << "to" << targetActualPath
.toString();
2405 doRenameFile(i
, targetActualPath
);
2410 void TorrentImpl::adjustStorageLocation()
2412 const Path downloadPath
= this->downloadPath();
2413 const Path targetPath
= ((isFinished() || m_hasFinishedStatus
|| downloadPath
.isEmpty()) ? savePath() : downloadPath
);
2415 if ((targetPath
!= actualStorageLocation()) || isMoveInProgress())
2416 moveStorage(targetPath
, MoveStorageContext::AdjustCurrentLocation
);
2419 void TorrentImpl::doRenameFile(const int index
, const Path
&path
)
2421 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
2423 Q_ASSERT(index
>= 0);
2424 Q_ASSERT(index
< nativeIndexes
.size());
2425 if ((index
< 0) || (index
>= nativeIndexes
.size())) [[unlikely
]]
2429 m_nativeHandle
.rename_file(nativeIndexes
[index
], path
.toString().toStdString());
2432 lt::torrent_handle
TorrentImpl::nativeHandle() const
2434 return m_nativeHandle
;
2437 void TorrentImpl::setMetadata(const TorrentInfo
&torrentInfo
)
2442 m_session
->invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
]
2446 #ifdef QBT_USES_LIBTORRENT2
2447 nativeHandle
.set_metadata(torrentInfo
.nativeInfo()->info_section());
2449 const std::shared_ptr
<lt::torrent_info
> nativeInfo
= torrentInfo
.nativeInfo();
2450 nativeHandle
.set_metadata(lt::span
<const char>(nativeInfo
->metadata().get(), nativeInfo
->metadata_size()));
2453 catch (const std::exception
&) {}
2457 Torrent::StopCondition
TorrentImpl::stopCondition() const
2459 return m_stopCondition
;
2462 void TorrentImpl::setStopCondition(const StopCondition stopCondition
)
2464 if (stopCondition
== m_stopCondition
)
2470 if ((stopCondition
== StopCondition::MetadataReceived
) && hasMetadata())
2473 if ((stopCondition
== StopCondition::FilesChecked
) && hasMetadata() && !isChecking())
2476 m_stopCondition
= stopCondition
;
2479 bool TorrentImpl::isMoveInProgress() const
2481 return m_storageIsMoving
;
2484 void TorrentImpl::updateStatus(const lt::torrent_status
&nativeStatus
)
2486 const lt::torrent_status oldStatus
= std::exchange(m_nativeStatus
, nativeStatus
);
2488 if (m_nativeStatus
.num_pieces
!= oldStatus
.num_pieces
)
2493 m_payloadRateMonitor
.addSample({nativeStatus
.download_payload_rate
2494 , nativeStatus
.upload_payload_rate
});
2498 // NOTE: Don't change the order of these conditionals!
2499 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2501 m_unchecked
= false;
2502 else if (isDownloading())
2506 while (!m_statusUpdatedTriggers
.isEmpty())
2507 std::invoke(m_statusUpdatedTriggers
.dequeue());
2510 void TorrentImpl::updateProgress()
2512 Q_ASSERT(hasMetadata());
2513 if (!hasMetadata()) [[unlikely
]]
2516 Q_ASSERT(!m_filesProgress
.isEmpty());
2517 if (m_filesProgress
.isEmpty()) [[unlikely
]]
2518 m_filesProgress
.resize(filesCount());
2520 const QBitArray oldPieces
= std::exchange(m_pieces
, LT::toQBitArray(m_nativeStatus
.pieces
));
2521 const QBitArray newPieces
= m_pieces
^ oldPieces
;
2523 const int64_t pieceSize
= m_torrentInfo
.pieceLength();
2524 for (qsizetype index
= 0; index
< newPieces
.size(); ++index
)
2526 if (!newPieces
.at(index
))
2529 int64_t size
= m_torrentInfo
.pieceLength(index
);
2530 int64_t pieceOffset
= index
* pieceSize
;
2532 for (const int fileIndex
: asConst(m_torrentInfo
.fileIndicesForPiece(index
)))
2534 const int64_t fileOffsetInPiece
= pieceOffset
- m_torrentInfo
.fileOffset(fileIndex
);
2535 const int64_t add
= std::min
<int64_t>((m_torrentInfo
.fileSize(fileIndex
) - fileOffsetInPiece
), size
);
2537 m_filesProgress
[fileIndex
] += add
;
2548 void TorrentImpl::setRatioLimit(qreal limit
)
2550 if (limit
< USE_GLOBAL_RATIO
)
2551 limit
= NO_RATIO_LIMIT
;
2552 else if (limit
> MAX_RATIO
)
2555 if (m_ratioLimit
!= limit
)
2557 m_ratioLimit
= limit
;
2558 deferredRequestResumeData();
2559 m_session
->handleTorrentShareLimitChanged(this);
2563 void TorrentImpl::setSeedingTimeLimit(int limit
)
2565 if (limit
< USE_GLOBAL_SEEDING_TIME
)
2566 limit
= NO_SEEDING_TIME_LIMIT
;
2567 else if (limit
> MAX_SEEDING_TIME
)
2568 limit
= MAX_SEEDING_TIME
;
2570 if (m_seedingTimeLimit
!= limit
)
2572 m_seedingTimeLimit
= limit
;
2573 deferredRequestResumeData();
2574 m_session
->handleTorrentShareLimitChanged(this);
2578 void TorrentImpl::setInactiveSeedingTimeLimit(int limit
)
2580 if (limit
< USE_GLOBAL_INACTIVE_SEEDING_TIME
)
2581 limit
= NO_INACTIVE_SEEDING_TIME_LIMIT
;
2582 else if (limit
> MAX_INACTIVE_SEEDING_TIME
)
2583 limit
= MAX_SEEDING_TIME
;
2585 if (m_inactiveSeedingTimeLimit
!= limit
)
2587 m_inactiveSeedingTimeLimit
= limit
;
2588 deferredRequestResumeData();
2589 m_session
->handleTorrentShareLimitChanged(this);
2593 void TorrentImpl::setUploadLimit(const int limit
)
2595 const int cleanValue
= cleanLimitValue(limit
);
2596 if (cleanValue
== uploadLimit())
2599 m_uploadLimit
= cleanValue
;
2600 m_nativeHandle
.set_upload_limit(m_uploadLimit
);
2601 deferredRequestResumeData();
2604 void TorrentImpl::setDownloadLimit(const int limit
)
2606 const int cleanValue
= cleanLimitValue(limit
);
2607 if (cleanValue
== downloadLimit())
2610 m_downloadLimit
= cleanValue
;
2611 m_nativeHandle
.set_download_limit(m_downloadLimit
);
2612 deferredRequestResumeData();
2615 void TorrentImpl::setSuperSeeding(const bool enable
)
2617 if (enable
== superSeeding())
2621 m_nativeHandle
.set_flags(lt::torrent_flags::super_seeding
);
2623 m_nativeHandle
.unset_flags(lt::torrent_flags::super_seeding
);
2625 deferredRequestResumeData();
2628 void TorrentImpl::setDHTDisabled(const bool disable
)
2630 if (disable
== isDHTDisabled())
2634 m_nativeHandle
.set_flags(lt::torrent_flags::disable_dht
);
2636 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_dht
);
2638 deferredRequestResumeData();
2641 void TorrentImpl::setPEXDisabled(const bool disable
)
2643 if (disable
== isPEXDisabled())
2647 m_nativeHandle
.set_flags(lt::torrent_flags::disable_pex
);
2649 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_pex
);
2651 deferredRequestResumeData();
2654 void TorrentImpl::setLSDDisabled(const bool disable
)
2656 if (disable
== isLSDDisabled())
2660 m_nativeHandle
.set_flags(lt::torrent_flags::disable_lsd
);
2662 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_lsd
);
2664 deferredRequestResumeData();
2667 void TorrentImpl::flushCache() const
2669 m_nativeHandle
.flush_cache();
2672 QString
TorrentImpl::createMagnetURI() const
2674 QString ret
= u
"magnet:?"_s
;
2676 const SHA1Hash infoHash1
= infoHash().v1();
2677 if (infoHash1
.isValid())
2679 ret
+= u
"xt=urn:btih:" + infoHash1
.toString();
2682 const SHA256Hash infoHash2
= infoHash().v2();
2683 if (infoHash2
.isValid())
2685 if (infoHash1
.isValid())
2687 ret
+= u
"xt=urn:btmh:1220" + infoHash2
.toString();
2690 const QString displayName
= name();
2691 if (displayName
!= id().toString())
2693 ret
+= u
"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName
));
2696 for (const TrackerEntry
&tracker
: asConst(trackers()))
2698 ret
+= u
"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker
.url
));
2701 for (const QUrl
&urlSeed
: asConst(urlSeeds()))
2703 ret
+= u
"&ws=" + QString::fromLatin1(urlSeed
.toEncoded());
2709 nonstd::expected
<lt::entry
, QString
> TorrentImpl::exportTorrent() const
2712 return nonstd::make_unexpected(tr("Missing metadata"));
2716 #ifdef QBT_USES_LIBTORRENT2
2717 const std::shared_ptr
<lt::torrent_info
> completeTorrentInfo
= m_nativeHandle
.torrent_file_with_hashes();
2718 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= (completeTorrentInfo
? completeTorrentInfo
: info().nativeInfo());
2720 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= info().nativeInfo();
2722 lt::create_torrent creator
{*torrentInfo
};
2724 for (const TrackerEntry
&entry
: asConst(trackers()))
2725 creator
.add_tracker(entry
.url
.toStdString(), entry
.tier
);
2727 return creator
.generate();
2729 catch (const lt::system_error
&err
)
2731 return nonstd::make_unexpected(QString::fromLocal8Bit(err
.what()));
2735 nonstd::expected
<QByteArray
, QString
> TorrentImpl::exportToBuffer() const
2737 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2738 if (!preparationResult
)
2739 return preparationResult
.get_unexpected();
2741 // usually torrent size should be smaller than 1 MB,
2742 // however there are >100 MB v2/hybrid torrent files out in the wild
2744 buffer
.reserve(1024 * 1024);
2745 lt::bencode(std::back_inserter(buffer
), preparationResult
.value());
2749 nonstd::expected
<void, QString
> TorrentImpl::exportToFile(const Path
&path
) const
2751 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2752 if (!preparationResult
)
2753 return preparationResult
.get_unexpected();
2755 const nonstd::expected
<void, QString
> saveResult
= Utils::IO::saveToFile(path
, preparationResult
.value());
2757 return saveResult
.get_unexpected();
2762 void TorrentImpl::fetchPeerInfo(std::function
<void (QVector
<PeerInfo
>)> resultHandler
) const
2764 invokeAsync([nativeHandle
= m_nativeHandle
, allPieces
= pieces()]() -> QVector
<PeerInfo
>
2768 std::vector
<lt::peer_info
> nativePeers
;
2769 nativeHandle
.get_peer_info(nativePeers
);
2770 QVector
<PeerInfo
> peers
;
2771 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
2772 for (const lt::peer_info
&peer
: nativePeers
)
2773 peers
.append(PeerInfo(peer
, allPieces
));
2776 catch (const std::exception
&) {}
2780 , std::move(resultHandler
));
2783 void TorrentImpl::fetchURLSeeds(std::function
<void (QVector
<QUrl
>)> resultHandler
) const
2785 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<QUrl
>
2789 const std::set
<std::string
> currentSeeds
= nativeHandle
.url_seeds();
2790 QVector
<QUrl
> urlSeeds
;
2791 urlSeeds
.reserve(static_cast<decltype(urlSeeds
)::size_type
>(currentSeeds
.size()));
2792 for (const std::string
&urlSeed
: currentSeeds
)
2793 urlSeeds
.append(QString::fromStdString(urlSeed
));
2796 catch (const std::exception
&) {}
2800 , std::move(resultHandler
));
2803 void TorrentImpl::fetchPieceAvailability(std::function
<void (QVector
<int>)> resultHandler
) const
2805 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<int>
2809 std::vector
<int> piecesAvailability
;
2810 nativeHandle
.piece_availability(piecesAvailability
);
2811 return QVector
<int>(piecesAvailability
.cbegin(), piecesAvailability
.cend());
2813 catch (const std::exception
&) {}
2817 , std::move(resultHandler
));
2820 void TorrentImpl::fetchDownloadingPieces(std::function
<void (QBitArray
)> resultHandler
) const
2822 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QBitArray
2826 #ifdef QBT_USES_LIBTORRENT2
2827 const std::vector
<lt::partial_piece_info
> queue
= nativeHandle
.get_download_queue();
2829 std::vector
<lt::partial_piece_info
> queue
;
2830 nativeHandle
.get_download_queue(queue
);
2833 result
.resize(torrentInfo
.piecesCount());
2834 for (const lt::partial_piece_info
&info
: queue
)
2835 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
2838 catch (const std::exception
&) {}
2842 , std::move(resultHandler
));
2845 void TorrentImpl::fetchAvailableFileFractions(std::function
<void (QVector
<qreal
>)> resultHandler
) const
2847 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QVector
<qreal
>
2849 if (!torrentInfo
.isValid() || (torrentInfo
.filesCount() <= 0))
2854 std::vector
<int> piecesAvailability
;
2855 nativeHandle
.piece_availability(piecesAvailability
);
2856 const int filesCount
= torrentInfo
.filesCount();
2857 // libtorrent returns empty array for seeding only torrents
2858 if (piecesAvailability
.empty())
2859 return QVector
<qreal
>(filesCount
, -1);
2861 QVector
<qreal
> result
;
2862 result
.reserve(filesCount
);
2863 for (int i
= 0; i
< filesCount
; ++i
)
2865 const TorrentInfo::PieceRange filePieces
= torrentInfo
.filePieces(i
);
2867 int availablePieces
= 0;
2868 for (const int piece
: filePieces
)
2869 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2871 const qreal availability
= filePieces
.isEmpty()
2872 ? 1 // the file has no pieces, so it is available by default
2873 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2874 result
.append(availability
);
2878 catch (const std::exception
&) {}
2882 , std::move(resultHandler
));
2885 void TorrentImpl::prioritizeFiles(const QVector
<DownloadPriority
> &priorities
)
2890 Q_ASSERT(priorities
.size() == filesCount());
2892 // Reset 'm_hasSeedStatus' if needed in order to react again to
2893 // 'torrent_finished_alert' and eg show tray notifications
2894 const QVector
<DownloadPriority
> oldPriorities
= filePriorities();
2895 for (int i
= 0; i
< oldPriorities
.size(); ++i
)
2897 if ((oldPriorities
[i
] == DownloadPriority::Ignored
)
2898 && (priorities
[i
] > DownloadPriority::Ignored
)
2899 && !m_completedFiles
.at(i
))
2901 m_hasFinishedStatus
= false;
2906 const int internalFilesCount
= m_torrentInfo
.nativeInfo()->files().num_files(); // including .pad files
2907 auto nativePriorities
= std::vector
<lt::download_priority_t
>(internalFilesCount
, LT::toNative(DownloadPriority::Normal
));
2908 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
2909 for (int i
= 0; i
< priorities
.size(); ++i
)
2910 nativePriorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(priorities
[i
]);
2912 qDebug() << Q_FUNC_INFO
<< "Changing files priorities...";
2913 m_nativeHandle
.prioritize_files(nativePriorities
);
2915 m_filePriorities
= priorities
;
2916 // Restore first/last piece first option if necessary
2917 if (m_hasFirstLastPiecePriority
)
2918 applyFirstLastPiecePriority(true);
2919 manageActualFilePaths();
2922 QVector
<qreal
> TorrentImpl::availableFileFractions() const
2924 Q_ASSERT(hasMetadata());
2926 const int filesCount
= this->filesCount();
2927 if (filesCount
<= 0) return {};
2929 const QVector
<int> piecesAvailability
= pieceAvailability();
2930 // libtorrent returns empty array for seeding only torrents
2931 if (piecesAvailability
.empty()) return QVector
<qreal
>(filesCount
, -1);
2934 res
.reserve(filesCount
);
2935 for (int i
= 0; i
< filesCount
; ++i
)
2937 const TorrentInfo::PieceRange filePieces
= m_torrentInfo
.filePieces(i
);
2939 int availablePieces
= 0;
2940 for (const int piece
: filePieces
)
2941 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2943 const qreal availability
= filePieces
.isEmpty()
2944 ? 1 // the file has no pieces, so it is available by default
2945 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2946 res
.push_back(availability
);
2951 template <typename Func
, typename Callback
>
2952 void TorrentImpl::invokeAsync(Func func
, Callback resultHandler
) const
2954 m_session
->invokeAsync([session
= m_session
2955 , func
= std::move(func
)
2956 , resultHandler
= std::move(resultHandler
)
2957 , thisTorrent
= QPointer
<const TorrentImpl
>(this)]() mutable
2959 session
->invoke([result
= func(), thisTorrent
, resultHandler
= std::move(resultHandler
)]
2962 resultHandler(result
);