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 m_session
->handleTorrentNeedSaveResumeData(this);
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 m_session
->handleTorrentNeedSaveResumeData(this);
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 m_session
->handleTorrentNeedSaveResumeData(this);
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 m_session
->handleTorrentNeedSaveResumeData(this);
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 m_session
->handleTorrentNeedSaveResumeData(this);
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 m_session
->handleTorrentNeedSaveResumeData(this);
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 session
->handleTorrentNeedSaveResumeData(thisTorrent
);
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 session
->handleTorrentNeedSaveResumeData(thisTorrent
);
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::saveResumeData(lt::resume_data_flags_t flags
)
804 m_nativeHandle
.save_resume_data(flags
);
805 m_session
->handleTorrentSaveResumeDataRequested(this);
808 int TorrentImpl::filesCount() const
810 return m_torrentInfo
.filesCount();
813 int TorrentImpl::piecesCount() const
815 return m_torrentInfo
.piecesCount();
818 int TorrentImpl::piecesHave() const
820 return m_nativeStatus
.num_pieces
;
823 qreal
TorrentImpl::progress() const
826 return m_nativeStatus
.progress
;
828 if (m_nativeStatus
.total_wanted
== 0)
831 if (m_nativeStatus
.total_wanted_done
== m_nativeStatus
.total_wanted
)
834 const qreal progress
= static_cast<qreal
>(m_nativeStatus
.total_wanted_done
) / m_nativeStatus
.total_wanted
;
835 if ((progress
< 0.f
) || (progress
> 1.f
))
837 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
838 .arg(name(), QString::number(m_nativeStatus
.total_wanted
), QString::number(m_nativeStatus
.total_wanted_done
))
845 QString
TorrentImpl::category() const
850 bool TorrentImpl::belongsToCategory(const QString
&category
) const
852 if (m_category
.isEmpty())
853 return category
.isEmpty();
855 if (m_category
== category
)
858 return (m_session
->isSubcategoriesEnabled() && m_category
.startsWith(category
+ u
'/'));
861 TagSet
TorrentImpl::tags() const
866 bool TorrentImpl::hasTag(const Tag
&tag
) const
868 return m_tags
.contains(tag
);
871 bool TorrentImpl::addTag(const Tag
&tag
)
878 if (!m_session
->hasTag(tag
))
880 if (!m_session
->addTag(tag
))
884 m_session
->handleTorrentNeedSaveResumeData(this);
885 m_session
->handleTorrentTagAdded(this, tag
);
889 bool TorrentImpl::removeTag(const Tag
&tag
)
891 if (m_tags
.remove(tag
))
893 m_session
->handleTorrentNeedSaveResumeData(this);
894 m_session
->handleTorrentTagRemoved(this, tag
);
900 void TorrentImpl::removeAllTags()
902 for (const Tag
&tag
: asConst(tags()))
906 QDateTime
TorrentImpl::addedTime() const
908 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.added_time
);
911 qreal
TorrentImpl::ratioLimit() const
916 int TorrentImpl::seedingTimeLimit() const
918 return m_seedingTimeLimit
;
921 int TorrentImpl::inactiveSeedingTimeLimit() const
923 return m_inactiveSeedingTimeLimit
;
926 Path
TorrentImpl::filePath(const int index
) const
928 Q_ASSERT(index
>= 0);
929 Q_ASSERT(index
< m_filePaths
.size());
931 return m_filePaths
.value(index
, {});
934 Path
TorrentImpl::actualFilePath(const int index
) const
936 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
938 Q_ASSERT(index
>= 0);
939 Q_ASSERT(index
< nativeIndexes
.size());
940 if ((index
< 0) || (index
>= nativeIndexes
.size()))
943 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes
[index
]));
946 qlonglong
TorrentImpl::fileSize(const int index
) const
948 return m_torrentInfo
.fileSize(index
);
951 PathList
TorrentImpl::filePaths() const
956 QVector
<DownloadPriority
> TorrentImpl::filePriorities() const
958 return m_filePriorities
;
961 TorrentInfo
TorrentImpl::info() const
963 return m_torrentInfo
;
966 bool TorrentImpl::isPaused() const
971 bool TorrentImpl::isQueued() const
973 // Torrent is Queued if it isn't in Paused state but paused internally
975 && (m_nativeStatus
.flags
& lt::torrent_flags::auto_managed
)
976 && (m_nativeStatus
.flags
& lt::torrent_flags::paused
));
979 bool TorrentImpl::isChecking() const
981 return ((m_nativeStatus
.state
== lt::torrent_status::checking_files
)
982 || (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
));
985 bool TorrentImpl::isDownloading() const
989 case TorrentState::Downloading
:
990 case TorrentState::DownloadingMetadata
:
991 case TorrentState::ForcedDownloadingMetadata
:
992 case TorrentState::StalledDownloading
:
993 case TorrentState::CheckingDownloading
:
994 case TorrentState::PausedDownloading
:
995 case TorrentState::QueuedDownloading
:
996 case TorrentState::ForcedDownloading
:
1005 bool TorrentImpl::isMoving() const
1007 return m_state
== TorrentState::Moving
;
1010 bool TorrentImpl::isUploading() const
1014 case TorrentState::Uploading
:
1015 case TorrentState::StalledUploading
:
1016 case TorrentState::CheckingUploading
:
1017 case TorrentState::QueuedUploading
:
1018 case TorrentState::ForcedUploading
:
1027 bool TorrentImpl::isCompleted() const
1031 case TorrentState::Uploading
:
1032 case TorrentState::StalledUploading
:
1033 case TorrentState::CheckingUploading
:
1034 case TorrentState::PausedUploading
:
1035 case TorrentState::QueuedUploading
:
1036 case TorrentState::ForcedUploading
:
1045 bool TorrentImpl::isActive() const
1049 case TorrentState::StalledDownloading
:
1050 return (uploadPayloadRate() > 0);
1052 case TorrentState::DownloadingMetadata
:
1053 case TorrentState::ForcedDownloadingMetadata
:
1054 case TorrentState::Downloading
:
1055 case TorrentState::ForcedDownloading
:
1056 case TorrentState::Uploading
:
1057 case TorrentState::ForcedUploading
:
1058 case TorrentState::Moving
:
1068 bool TorrentImpl::isInactive() const
1073 bool TorrentImpl::isErrored() const
1075 return ((m_state
== TorrentState::MissingFiles
)
1076 || (m_state
== TorrentState::Error
));
1079 bool TorrentImpl::isFinished() const
1081 return ((m_nativeStatus
.state
== lt::torrent_status::finished
)
1082 || (m_nativeStatus
.state
== lt::torrent_status::seeding
));
1085 bool TorrentImpl::isForced() const
1087 return (!isPaused() && (m_operatingMode
== TorrentOperatingMode::Forced
));
1090 bool TorrentImpl::isSequentialDownload() const
1092 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::sequential_download
);
1095 bool TorrentImpl::hasFirstLastPiecePriority() const
1097 return m_hasFirstLastPiecePriority
;
1100 TorrentState
TorrentImpl::state() const
1105 void TorrentImpl::updateState()
1107 if (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
)
1109 m_state
= TorrentState::CheckingResumeData
;
1111 else if (isMoveInProgress())
1113 m_state
= TorrentState::Moving
;
1115 else if (hasMissingFiles())
1117 m_state
= TorrentState::MissingFiles
;
1119 else if (hasError())
1121 m_state
= TorrentState::Error
;
1123 else if (!hasMetadata())
1126 m_state
= TorrentState::PausedDownloading
;
1127 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1128 m_state
= TorrentState::QueuedDownloading
;
1130 m_state
= isForced() ? TorrentState::ForcedDownloadingMetadata
: TorrentState::DownloadingMetadata
;
1132 else if ((m_nativeStatus
.state
== lt::torrent_status::checking_files
) && !isPaused())
1134 // If the torrent is not just in the "checking" state, but is being actually checked
1135 m_state
= m_hasFinishedStatus
? TorrentState::CheckingUploading
: TorrentState::CheckingDownloading
;
1137 else if (isFinished())
1140 m_state
= TorrentState::PausedUploading
;
1141 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1142 m_state
= TorrentState::QueuedUploading
;
1143 else if (isForced())
1144 m_state
= TorrentState::ForcedUploading
;
1145 else if (m_nativeStatus
.upload_payload_rate
> 0)
1146 m_state
= TorrentState::Uploading
;
1148 m_state
= TorrentState::StalledUploading
;
1153 m_state
= TorrentState::PausedDownloading
;
1154 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1155 m_state
= TorrentState::QueuedDownloading
;
1156 else if (isForced())
1157 m_state
= TorrentState::ForcedDownloading
;
1158 else if (m_nativeStatus
.download_payload_rate
> 0)
1159 m_state
= TorrentState::Downloading
;
1161 m_state
= TorrentState::StalledDownloading
;
1165 bool TorrentImpl::hasMetadata() const
1167 return m_torrentInfo
.isValid();
1170 bool TorrentImpl::hasMissingFiles() const
1172 return m_hasMissingFiles
;
1175 bool TorrentImpl::hasError() const
1177 return (m_nativeStatus
.errc
|| (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
));
1180 int TorrentImpl::queuePosition() const
1182 return static_cast<int>(m_nativeStatus
.queue_position
);
1185 QString
TorrentImpl::error() const
1187 if (m_nativeStatus
.errc
)
1188 return QString::fromLocal8Bit(m_nativeStatus
.errc
.message().c_str());
1190 if (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
)
1192 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1193 .arg(QString::fromLocal8Bit(m_lastFileError
.error
.message().c_str()));
1199 qlonglong
TorrentImpl::totalDownload() const
1201 return m_nativeStatus
.all_time_download
;
1204 qlonglong
TorrentImpl::totalUpload() const
1206 return m_nativeStatus
.all_time_upload
;
1209 qlonglong
TorrentImpl::activeTime() const
1211 return lt::total_seconds(m_nativeStatus
.active_duration
);
1214 qlonglong
TorrentImpl::finishedTime() const
1216 return lt::total_seconds(m_nativeStatus
.finished_duration
);
1219 qlonglong
TorrentImpl::eta() const
1221 if (isPaused()) return MAX_ETA
;
1223 const SpeedSampleAvg speedAverage
= m_payloadRateMonitor
.average();
1227 const qreal maxRatioValue
= maxRatio();
1228 const int maxSeedingTimeValue
= maxSeedingTime();
1229 const int maxInactiveSeedingTimeValue
= maxInactiveSeedingTime();
1230 if ((maxRatioValue
< 0) && (maxSeedingTimeValue
< 0) && (maxInactiveSeedingTimeValue
< 0)) return MAX_ETA
;
1232 qlonglong ratioEta
= MAX_ETA
;
1234 if ((speedAverage
.upload
> 0) && (maxRatioValue
>= 0))
1237 qlonglong realDL
= totalDownload();
1239 realDL
= wantedSize();
1241 ratioEta
= ((realDL
* maxRatioValue
) - totalUpload()) / speedAverage
.upload
;
1244 qlonglong seedingTimeEta
= MAX_ETA
;
1246 if (maxSeedingTimeValue
>= 0)
1248 seedingTimeEta
= (maxSeedingTimeValue
* 60) - finishedTime();
1249 if (seedingTimeEta
< 0)
1253 qlonglong inactiveSeedingTimeEta
= MAX_ETA
;
1255 if (maxInactiveSeedingTimeValue
>= 0)
1257 inactiveSeedingTimeEta
= (maxInactiveSeedingTimeValue
* 60) - timeSinceActivity();
1258 inactiveSeedingTimeEta
= std::max
<qlonglong
>(inactiveSeedingTimeEta
, 0);
1261 return std::min({ratioEta
, seedingTimeEta
, inactiveSeedingTimeEta
});
1264 if (!speedAverage
.download
) return MAX_ETA
;
1266 return (wantedSize() - completedSize()) / speedAverage
.download
;
1269 QVector
<qreal
> TorrentImpl::filesProgress() const
1274 const int count
= m_filesProgress
.size();
1275 Q_ASSERT(count
== filesCount());
1276 if (count
!= filesCount()) [[unlikely
]]
1279 if (m_completedFiles
.count(true) == count
)
1280 return QVector
<qreal
>(count
, 1);
1282 QVector
<qreal
> result
;
1283 result
.reserve(count
);
1284 for (int i
= 0; i
< count
; ++i
)
1286 const int64_t progress
= m_filesProgress
.at(i
);
1287 const int64_t size
= fileSize(i
);
1288 if ((size
<= 0) || (progress
== size
))
1291 result
<< (progress
/ static_cast<qreal
>(size
));
1297 int TorrentImpl::seedsCount() const
1299 return m_nativeStatus
.num_seeds
;
1302 int TorrentImpl::peersCount() const
1304 return m_nativeStatus
.num_peers
;
1307 int TorrentImpl::leechsCount() const
1309 return (m_nativeStatus
.num_peers
- m_nativeStatus
.num_seeds
);
1312 int TorrentImpl::totalSeedsCount() const
1314 return (m_nativeStatus
.num_complete
> -1) ? m_nativeStatus
.num_complete
: m_nativeStatus
.list_seeds
;
1317 int TorrentImpl::totalPeersCount() const
1319 const int peers
= m_nativeStatus
.num_complete
+ m_nativeStatus
.num_incomplete
;
1320 return (peers
> -1) ? peers
: m_nativeStatus
.list_peers
;
1323 int TorrentImpl::totalLeechersCount() const
1325 return (m_nativeStatus
.num_incomplete
> -1) ? m_nativeStatus
.num_incomplete
: (m_nativeStatus
.list_peers
- m_nativeStatus
.list_seeds
);
1328 QDateTime
TorrentImpl::lastSeenComplete() const
1330 if (m_nativeStatus
.last_seen_complete
> 0)
1331 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.last_seen_complete
);
1336 QDateTime
TorrentImpl::completedTime() const
1338 if (m_nativeStatus
.completed_time
> 0)
1339 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.completed_time
);
1344 qlonglong
TorrentImpl::timeSinceUpload() const
1346 if (m_nativeStatus
.last_upload
.time_since_epoch().count() == 0)
1348 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_upload
);
1351 qlonglong
TorrentImpl::timeSinceDownload() const
1353 if (m_nativeStatus
.last_download
.time_since_epoch().count() == 0)
1355 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_download
);
1358 qlonglong
TorrentImpl::timeSinceActivity() const
1360 const qlonglong upTime
= timeSinceUpload();
1361 const qlonglong downTime
= timeSinceDownload();
1362 return ((upTime
< 0) != (downTime
< 0))
1363 ? std::max(upTime
, downTime
)
1364 : std::min(upTime
, downTime
);
1367 int TorrentImpl::downloadLimit() const
1369 return m_downloadLimit
;;
1372 int TorrentImpl::uploadLimit() const
1374 return m_uploadLimit
;
1377 bool TorrentImpl::superSeeding() const
1379 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::super_seeding
);
1382 bool TorrentImpl::isDHTDisabled() const
1384 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_dht
);
1387 bool TorrentImpl::isPEXDisabled() const
1389 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_pex
);
1392 bool TorrentImpl::isLSDDisabled() const
1394 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_lsd
);
1397 QVector
<PeerInfo
> TorrentImpl::peers() const
1399 std::vector
<lt::peer_info
> nativePeers
;
1400 m_nativeHandle
.get_peer_info(nativePeers
);
1402 QVector
<PeerInfo
> peers
;
1403 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
1405 for (const lt::peer_info
&peer
: nativePeers
)
1406 peers
.append(PeerInfo(peer
, pieces()));
1411 QBitArray
TorrentImpl::pieces() const
1416 QBitArray
TorrentImpl::downloadingPieces() const
1418 QBitArray
result(piecesCount());
1420 std::vector
<lt::partial_piece_info
> queue
;
1421 m_nativeHandle
.get_download_queue(queue
);
1423 for (const lt::partial_piece_info
&info
: queue
)
1424 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
1429 QVector
<int> TorrentImpl::pieceAvailability() const
1431 std::vector
<int> avail
;
1432 m_nativeHandle
.piece_availability(avail
);
1434 return {avail
.cbegin(), avail
.cend()};
1437 qreal
TorrentImpl::distributedCopies() const
1439 return m_nativeStatus
.distributed_copies
;
1442 qreal
TorrentImpl::maxRatio() const
1444 if (m_ratioLimit
== USE_GLOBAL_RATIO
)
1445 return m_session
->globalMaxRatio();
1447 return m_ratioLimit
;
1450 int TorrentImpl::maxSeedingTime() const
1452 if (m_seedingTimeLimit
== USE_GLOBAL_SEEDING_TIME
)
1453 return m_session
->globalMaxSeedingMinutes();
1455 return m_seedingTimeLimit
;
1458 int TorrentImpl::maxInactiveSeedingTime() const
1460 if (m_inactiveSeedingTimeLimit
== USE_GLOBAL_INACTIVE_SEEDING_TIME
)
1461 return m_session
->globalMaxInactiveSeedingMinutes();
1463 return m_inactiveSeedingTimeLimit
;
1466 qreal
TorrentImpl::realRatio() const
1468 const int64_t upload
= m_nativeStatus
.all_time_upload
;
1469 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1470 const int64_t download
= (m_nativeStatus
.all_time_download
< (m_nativeStatus
.total_done
* 0.01))
1471 ? m_nativeStatus
.total_done
1472 : m_nativeStatus
.all_time_download
;
1475 return (upload
== 0) ? 0 : MAX_RATIO
;
1477 const qreal ratio
= upload
/ static_cast<qreal
>(download
);
1478 Q_ASSERT(ratio
>= 0);
1479 return (ratio
> MAX_RATIO
) ? MAX_RATIO
: ratio
;
1482 int TorrentImpl::uploadPayloadRate() const
1484 // workaround: suppress the speed for paused state
1485 return isPaused() ? 0 : m_nativeStatus
.upload_payload_rate
;
1488 int TorrentImpl::downloadPayloadRate() const
1490 // workaround: suppress the speed for paused state
1491 return isPaused() ? 0 : m_nativeStatus
.download_payload_rate
;
1494 qlonglong
TorrentImpl::totalPayloadUpload() const
1496 return m_nativeStatus
.total_payload_upload
;
1499 qlonglong
TorrentImpl::totalPayloadDownload() const
1501 return m_nativeStatus
.total_payload_download
;
1504 int TorrentImpl::connectionsCount() const
1506 return m_nativeStatus
.num_connections
;
1509 int TorrentImpl::connectionsLimit() const
1511 return m_nativeStatus
.connections_limit
;
1514 qlonglong
TorrentImpl::nextAnnounce() const
1516 return lt::total_seconds(m_nativeStatus
.next_announce
);
1519 void TorrentImpl::setName(const QString
&name
)
1524 m_session
->handleTorrentNeedSaveResumeData(this);
1525 m_session
->handleTorrentNameChanged(this);
1529 bool TorrentImpl::setCategory(const QString
&category
)
1531 if (m_category
!= category
)
1533 if (!category
.isEmpty() && !m_session
->categories().contains(category
))
1536 const QString oldCategory
= m_category
;
1537 m_category
= category
;
1538 m_session
->handleTorrentNeedSaveResumeData(this);
1539 m_session
->handleTorrentCategoryChanged(this, oldCategory
);
1543 if (!m_session
->isDisableAutoTMMWhenCategoryChanged())
1544 adjustStorageLocation();
1546 setAutoTMMEnabled(false);
1553 void TorrentImpl::forceReannounce(const int index
)
1555 m_nativeHandle
.force_reannounce(0, index
);
1558 void TorrentImpl::forceDHTAnnounce()
1560 m_nativeHandle
.force_dht_announce();
1563 void TorrentImpl::forceRecheck()
1568 m_nativeHandle
.force_recheck();
1569 // We have to force update the cached state, otherwise someone will be able to get
1570 // an incorrect one during the interval until the cached state is updated in a regular way.
1571 m_nativeStatus
.state
= lt::torrent_status::checking_resume_data
;
1573 m_hasMissingFiles
= false;
1574 m_unchecked
= false;
1576 m_completedFiles
.fill(false);
1577 m_filesProgress
.fill(0);
1578 m_pieces
.fill(false);
1579 m_nativeStatus
.pieces
.clear_all();
1580 m_nativeStatus
.num_pieces
= 0;
1584 // When "force recheck" is applied on paused torrent, we temporarily resume it
1586 m_stopCondition
= StopCondition::FilesChecked
;
1590 void TorrentImpl::setSequentialDownload(const bool enable
)
1594 m_nativeHandle
.set_flags(lt::torrent_flags::sequential_download
);
1595 m_nativeStatus
.flags
|= lt::torrent_flags::sequential_download
; // prevent return cached value
1599 m_nativeHandle
.unset_flags(lt::torrent_flags::sequential_download
);
1600 m_nativeStatus
.flags
&= ~lt::torrent_flags::sequential_download
; // prevent return cached value
1603 m_session
->handleTorrentNeedSaveResumeData(this);
1606 void TorrentImpl::setFirstLastPiecePriority(const bool enabled
)
1608 if (m_hasFirstLastPiecePriority
== enabled
)
1611 m_hasFirstLastPiecePriority
= enabled
;
1613 applyFirstLastPiecePriority(enabled
);
1615 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1616 .arg((enabled
? tr("On") : tr("Off")), name()));
1618 m_session
->handleTorrentNeedSaveResumeData(this);
1621 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled
)
1623 Q_ASSERT(hasMetadata());
1625 // Download first and last pieces first for every file in the torrent
1627 auto piecePriorities
= std::vector
<lt::download_priority_t
>(m_torrentInfo
.piecesCount(), LT::toNative(DownloadPriority::Ignored
));
1629 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1630 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1631 for (int fileIndex
= 0; fileIndex
< m_filePriorities
.size(); ++fileIndex
)
1633 const DownloadPriority filePrio
= m_filePriorities
[fileIndex
];
1634 if (filePrio
<= DownloadPriority::Ignored
)
1637 // Determine the priority to set
1638 const lt::download_priority_t piecePrio
= LT::toNative(enabled
? DownloadPriority::Maximum
: filePrio
);
1639 const TorrentInfo::PieceRange pieceRange
= m_torrentInfo
.filePieces(fileIndex
);
1641 // worst case: AVI index = 1% of total file size (at the end of the file)
1642 const int numPieces
= std::ceil(fileSize(fileIndex
) * 0.01 / pieceLength());
1643 for (int i
= 0; i
< numPieces
; ++i
)
1645 piecePriorities
[pieceRange
.first() + i
] = piecePrio
;
1646 piecePriorities
[pieceRange
.last() - i
] = piecePrio
;
1649 const int firstPiece
= pieceRange
.first() + numPieces
;
1650 const int lastPiece
= pieceRange
.last() - numPieces
;
1651 for (int pieceIndex
= firstPiece
; pieceIndex
<= lastPiece
; ++pieceIndex
)
1652 piecePriorities
[pieceIndex
] = LT::toNative(filePrio
);
1655 m_nativeHandle
.prioritize_pieces(piecePriorities
);
1658 void TorrentImpl::fileSearchFinished(const Path
&savePath
, const PathList
&fileNames
)
1660 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
1661 endReceivedMetadataHandling(savePath
, fileNames
);
1664 TrackerEntry
TorrentImpl::updateTrackerEntry(const lt::announce_entry
&announceEntry
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
1666 const auto it
= std::find_if(m_trackerEntries
.begin(), m_trackerEntries
.end()
1667 , [&announceEntry
](const TrackerEntry
&trackerEntry
)
1669 return (trackerEntry
.url
== QString::fromStdString(announceEntry
.url
));
1672 Q_ASSERT(it
!= m_trackerEntries
.end());
1673 if (it
== m_trackerEntries
.end()) [[unlikely
]]
1676 #ifdef QBT_USES_LIBTORRENT2
1677 QSet
<int> btProtocols
;
1678 const auto &infoHashes
= nativeHandle().info_hashes();
1679 if (infoHashes
.has(lt::protocol_version::V1
))
1680 btProtocols
.insert(1);
1681 if (infoHashes
.has(lt::protocol_version::V2
))
1682 btProtocols
.insert(2);
1684 const QSet
<int> btProtocols
{1};
1686 ::updateTrackerEntry(*it
, announceEntry
, btProtocols
, updateInfo
);
1690 void TorrentImpl::resetTrackerEntries()
1692 for (auto &trackerEntry
: m_trackerEntries
)
1693 trackerEntry
= {trackerEntry
.url
, trackerEntry
.tier
};
1696 std::shared_ptr
<const libtorrent::torrent_info
> TorrentImpl::nativeTorrentInfo() const
1698 if (m_nativeStatus
.torrent_file
.expired())
1699 m_nativeStatus
.torrent_file
= m_nativeHandle
.torrent_file();
1700 return m_nativeStatus
.torrent_file
.lock();
1703 void TorrentImpl::endReceivedMetadataHandling(const Path
&savePath
, const PathList
&fileNames
)
1705 Q_ASSERT(m_maintenanceJob
== MaintenanceJob::HandleMetadata
);
1706 if (m_maintenanceJob
!= MaintenanceJob::HandleMetadata
) [[unlikely
]]
1709 Q_ASSERT(m_filePaths
.isEmpty());
1710 if (!m_filePaths
.isEmpty()) [[unlikely
]]
1711 m_filePaths
.clear();
1713 lt::add_torrent_params
&p
= m_ltAddTorrentParams
;
1715 const std::shared_ptr
<lt::torrent_info
> metadata
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1716 m_torrentInfo
= TorrentInfo(*metadata
);
1717 m_filePriorities
.reserve(filesCount());
1718 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
1719 p
.file_priorities
= resized(p
.file_priorities
, metadata
->files().num_files()
1720 , LT::toNative(p
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
1722 m_completedFiles
.fill(static_cast<bool>(p
.flags
& lt::torrent_flags::seed_mode
), filesCount());
1723 m_filesProgress
.resize(filesCount());
1726 for (int i
= 0; i
< fileNames
.size(); ++i
)
1728 const auto nativeIndex
= nativeIndexes
.at(i
);
1730 const Path
&actualFilePath
= fileNames
.at(i
);
1731 p
.renamed_files
[nativeIndex
] = actualFilePath
.toString().toStdString();
1733 const Path filePath
= actualFilePath
.removedExtension(QB_EXT
);
1734 m_filePaths
.append(filePath
);
1736 lt::download_priority_t
&nativePriority
= p
.file_priorities
[LT::toUnderlyingType(nativeIndex
)];
1737 if ((nativePriority
!= lt::dont_download
) && m_session
->isFilenameExcluded(filePath
.filename()))
1738 nativePriority
= lt::dont_download
;
1739 const auto priority
= LT::fromNative(nativePriority
);
1740 m_filePriorities
.append(priority
);
1742 p
.save_path
= savePath
.toString().toStdString();
1745 if (stopCondition() == StopCondition::MetadataReceived
)
1747 m_stopCondition
= StopCondition::None
;
1750 p
.flags
|= lt::torrent_flags::paused
;
1751 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1753 m_session
->handleTorrentPaused(this);
1758 // If first/last piece priority was specified when adding this torrent,
1759 // we should apply it now that we have metadata:
1760 if (m_hasFirstLastPiecePriority
)
1761 applyFirstLastPiecePriority(true);
1763 m_maintenanceJob
= MaintenanceJob::None
;
1764 prepareResumeData(p
);
1766 m_session
->handleTorrentMetadataReceived(this);
1769 void TorrentImpl::reload()
1773 m_completedFiles
.fill(false);
1774 m_filesProgress
.fill(0);
1775 m_pieces
.fill(false);
1776 m_nativeStatus
.pieces
.clear_all();
1777 m_nativeStatus
.num_pieces
= 0;
1779 const auto queuePos
= m_nativeHandle
.queue_position();
1781 m_nativeSession
->remove_torrent(m_nativeHandle
, lt::session::delete_partfile
);
1783 lt::add_torrent_params p
= m_ltAddTorrentParams
;
1784 p
.flags
|= lt::torrent_flags::update_subscribe
1785 | lt::torrent_flags::override_trackers
1786 | lt::torrent_flags::override_web_seeds
;
1790 p
.flags
|= lt::torrent_flags::paused
;
1791 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1793 else if (m_operatingMode
== TorrentOperatingMode::AutoManaged
)
1795 p
.flags
|= (lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1799 p
.flags
&= ~(lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1802 auto *const extensionData
= new ExtensionData
;
1803 p
.userdata
= LTClientData(extensionData
);
1804 m_nativeHandle
= m_nativeSession
->add_torrent(p
);
1806 m_nativeStatus
= extensionData
->status
;
1808 if (queuePos
>= lt::queue_position_t
{})
1809 m_nativeHandle
.queue_position_set(queuePos
);
1810 m_nativeStatus
.queue_position
= queuePos
;
1814 catch (const lt::system_error
&err
)
1816 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1817 .arg(id().toString(), QString::fromLocal8Bit(err
.what())));
1821 void TorrentImpl::pause()
1825 m_stopCondition
= StopCondition::None
;
1827 m_session
->handleTorrentNeedSaveResumeData(this);
1828 m_session
->handleTorrentPaused(this);
1831 if (m_maintenanceJob
== MaintenanceJob::None
)
1833 setAutoManaged(false);
1834 m_nativeHandle
.pause();
1836 m_payloadRateMonitor
.reset();
1840 void TorrentImpl::resume(const TorrentOperatingMode mode
)
1844 m_nativeHandle
.clear_error();
1845 m_nativeHandle
.unset_flags(lt::torrent_flags::upload_mode
);
1848 m_operatingMode
= mode
;
1850 if (m_hasMissingFiles
)
1852 m_hasMissingFiles
= false;
1853 m_isStopped
= false;
1854 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1861 m_isStopped
= false;
1862 m_session
->handleTorrentNeedSaveResumeData(this);
1863 m_session
->handleTorrentResumed(this);
1866 if (m_maintenanceJob
== MaintenanceJob::None
)
1868 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1869 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1870 m_nativeHandle
.resume();
1874 void TorrentImpl::moveStorage(const Path
&newPath
, const MoveStorageContext context
)
1878 m_savePath
= newPath
;
1879 m_session
->handleTorrentSavePathChanged(this);
1883 const auto mode
= (context
== MoveStorageContext::AdjustCurrentLocation
)
1884 ? MoveStorageMode::Overwrite
: MoveStorageMode::KeepExistingFiles
;
1885 if (m_session
->addMoveTorrentStorageJob(this, newPath
, mode
, context
))
1887 if (!m_storageIsMoving
)
1889 m_storageIsMoving
= true;
1891 m_session
->handleTorrentStorageMovingStateChanged(this);
1896 void TorrentImpl::renameFile(const int index
, const Path
&path
)
1898 Q_ASSERT((index
>= 0) && (index
< filesCount()));
1899 if ((index
< 0) || (index
>= filesCount())) [[unlikely
]]
1902 const Path targetActualPath
= makeActualPath(index
, path
);
1903 doRenameFile(index
, targetActualPath
);
1906 void TorrentImpl::handleStateUpdate(const lt::torrent_status
&nativeStatus
)
1908 updateStatus(nativeStatus
);
1911 void TorrentImpl::handleMoveStorageJobFinished(const Path
&path
, const MoveStorageContext context
, const bool hasOutstandingJob
)
1913 if (context
== MoveStorageContext::ChangeSavePath
)
1915 else if (context
== MoveStorageContext::ChangeDownloadPath
)
1916 m_downloadPath
= path
;
1917 m_storageIsMoving
= hasOutstandingJob
;
1918 m_nativeStatus
.save_path
= path
.toString().toStdString();
1920 m_session
->handleTorrentSavePathChanged(this);
1921 m_session
->handleTorrentNeedSaveResumeData(this);
1923 if (!m_storageIsMoving
)
1926 m_session
->handleTorrentStorageMovingStateChanged(this);
1928 if (m_hasMissingFiles
)
1930 // it can be moved to the proper location
1931 m_hasMissingFiles
= false;
1932 m_ltAddTorrentParams
.save_path
= m_nativeStatus
.save_path
;
1933 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1937 while ((m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
1938 std::invoke(m_moveFinishedTriggers
.dequeue());
1942 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused
]] const lt::torrent_checked_alert
*p
)
1946 // The torrent is checked due to metadata received, but we should not process
1947 // this event until the torrent is reloaded using the received metadata.
1951 if (stopCondition() == StopCondition::FilesChecked
)
1954 m_statusUpdatedTriggers
.enqueue([this]()
1956 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1958 if (!m_hasMissingFiles
)
1960 if ((progress() < 1.0) && (wantedSize() > 0))
1961 m_hasFinishedStatus
= false;
1962 else if (progress() == 1.0)
1963 m_hasFinishedStatus
= true;
1965 adjustStorageLocation();
1966 manageActualFilePaths();
1970 // torrent is internally paused using NativeTorrentExtension after files checked
1971 // so we need to resume it if there is no corresponding "stop condition" set
1972 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1973 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1974 m_nativeHandle
.resume();
1978 if (m_nativeStatus
.need_save_resume
)
1979 m_session
->handleTorrentNeedSaveResumeData(this);
1981 m_session
->handleTorrentChecked(this);
1985 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused
]] const lt::torrent_finished_alert
*p
)
1987 m_hasMissingFiles
= false;
1988 if (m_hasFinishedStatus
)
1991 m_statusUpdatedTriggers
.enqueue([this]()
1993 adjustStorageLocation();
1994 manageActualFilePaths();
1996 m_session
->handleTorrentNeedSaveResumeData(this);
1998 const bool recheckTorrentsOnCompletion
= Preferences::instance()->recheckTorrentsOnCompletion();
1999 if (recheckTorrentsOnCompletion
&& m_unchecked
)
2005 m_hasFinishedStatus
= true;
2007 if (isMoveInProgress() || (m_renameCount
> 0))
2008 m_moveFinishedTriggers
.enqueue([this]() { m_session
->handleTorrentFinished(this); });
2010 m_session
->handleTorrentFinished(this);
2015 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused
]] const lt::torrent_paused_alert
*p
)
2019 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused
]] const lt::torrent_resumed_alert
*p
)
2023 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert
*p
)
2025 if (m_ltAddTorrentParams
.url_seeds
!= p
->params
.url_seeds
)
2027 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2028 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2029 // than the list we usually cache, so we have to request it from the appropriate source.
2030 fetchURLSeeds([this](const QVector
<QUrl
> &urlSeeds
) { m_urlSeeds
= urlSeeds
; });
2033 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2035 Q_ASSERT(m_indexMap
.isEmpty());
2037 const auto isSeedMode
= static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
);
2038 m_ltAddTorrentParams
= p
->params
;
2040 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2042 m_ltAddTorrentParams
.have_pieces
.clear();
2043 m_ltAddTorrentParams
.verified_pieces
.clear();
2045 TorrentInfo metadata
= TorrentInfo(*nativeTorrentInfo());
2047 const auto &renamedFiles
= m_ltAddTorrentParams
.renamed_files
;
2048 PathList filePaths
= metadata
.filePaths();
2049 if (renamedFiles
.empty() && (m_contentLayout
!= TorrentContentLayout::Original
))
2051 const Path originalRootFolder
= Path::findRootFolder(filePaths
);
2052 const auto originalContentLayout
= (originalRootFolder
.isEmpty()
2053 ? TorrentContentLayout::NoSubfolder
2054 : TorrentContentLayout::Subfolder
);
2055 if (m_contentLayout
!= originalContentLayout
)
2057 if (m_contentLayout
== TorrentContentLayout::NoSubfolder
)
2058 Path::stripRootFolder(filePaths
);
2060 Path::addRootFolder(filePaths
, filePaths
.at(0).removedExtension());
2064 const auto nativeIndexes
= metadata
.nativeIndexes();
2065 m_indexMap
.reserve(filePaths
.size());
2066 for (int i
= 0; i
< filePaths
.size(); ++i
)
2068 const auto nativeIndex
= nativeIndexes
.at(i
);
2069 m_indexMap
[nativeIndex
] = i
;
2071 if (const auto it
= renamedFiles
.find(nativeIndex
); it
!= renamedFiles
.cend())
2072 filePaths
[i
] = Path(it
->second
);
2075 m_session
->findIncompleteFiles(metadata
, savePath(), downloadPath(), filePaths
);
2079 prepareResumeData(p
->params
);
2083 void TorrentImpl::prepareResumeData(const lt::add_torrent_params
¶ms
)
2085 if (m_hasMissingFiles
)
2087 const auto havePieces
= m_ltAddTorrentParams
.have_pieces
;
2088 const auto unfinishedPieces
= m_ltAddTorrentParams
.unfinished_pieces
;
2089 const auto verifiedPieces
= m_ltAddTorrentParams
.verified_pieces
;
2091 // Update recent resume data but preserve existing progress
2092 m_ltAddTorrentParams
= params
;
2093 m_ltAddTorrentParams
.have_pieces
= havePieces
;
2094 m_ltAddTorrentParams
.unfinished_pieces
= unfinishedPieces
;
2095 m_ltAddTorrentParams
.verified_pieces
= verifiedPieces
;
2099 const bool preserveSeedMode
= (!hasMetadata() && (m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
));
2100 // Update recent resume data
2101 m_ltAddTorrentParams
= params
;
2102 if (preserveSeedMode
)
2103 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2106 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2107 m_ltAddTorrentParams
.flags
&= ~lt::torrent_flags::upload_mode
;
2109 LoadTorrentParams resumeData
;
2110 resumeData
.name
= m_name
;
2111 resumeData
.category
= m_category
;
2112 resumeData
.tags
= m_tags
;
2113 resumeData
.contentLayout
= m_contentLayout
;
2114 resumeData
.ratioLimit
= m_ratioLimit
;
2115 resumeData
.seedingTimeLimit
= m_seedingTimeLimit
;
2116 resumeData
.inactiveSeedingTimeLimit
= m_inactiveSeedingTimeLimit
;
2117 resumeData
.firstLastPiecePriority
= m_hasFirstLastPiecePriority
;
2118 resumeData
.hasFinishedStatus
= m_hasFinishedStatus
;
2119 resumeData
.stopped
= m_isStopped
;
2120 resumeData
.stopCondition
= m_stopCondition
;
2121 resumeData
.operatingMode
= m_operatingMode
;
2122 resumeData
.ltAddTorrentParams
= m_ltAddTorrentParams
;
2123 resumeData
.useAutoTMM
= m_useAutoTMM
;
2124 if (!resumeData
.useAutoTMM
)
2126 resumeData
.savePath
= m_savePath
;
2127 resumeData
.downloadPath
= m_downloadPath
;
2130 m_session
->handleTorrentResumeDataReady(this, resumeData
);
2133 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert
*p
)
2135 if (p
->error
!= lt::errors::resume_data_not_modified
)
2137 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2138 .arg(name(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::CRITICAL
);
2141 m_session
->handleTorrentSaveResumeDataFailed(this);
2144 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert
*p
)
2146 // Files were probably moved or storage isn't accessible
2147 m_hasMissingFiles
= true;
2148 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2149 .arg(name(), QString::fromStdString(p
->message())), Log::WARNING
);
2152 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert
*p
)
2154 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2155 Q_ASSERT(fileIndex
>= 0);
2157 const Path newActualFilePath
{QString::fromUtf8(p
->new_name())};
2159 const Path oldFilePath
= m_filePaths
.at(fileIndex
);
2160 const Path newFilePath
= makeUserPath(newActualFilePath
);
2162 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2163 // We should compare path in a case sensitive manner even on case insensitive
2164 // platforms since it can be renamed by only changing case of some character(s)
2165 if (oldFilePath
.data() == newFilePath
.data())
2167 // Remove empty ".unwanted" folders
2168 #ifdef QBT_USES_LIBTORRENT2
2169 const Path oldActualFilePath
{QString::fromUtf8(p
->old_name())};
2171 const Path oldActualFilePath
;
2173 const Path oldActualParentPath
= oldActualFilePath
.parentPath();
2174 const Path newActualParentPath
= newActualFilePath
.parentPath();
2175 if (newActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2177 if (oldActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2180 const std::wstring winPath
= (actualStorageLocation() / newActualParentPath
).toString().toStdWString();
2181 const DWORD dwAttrs
= ::GetFileAttributesW(winPath
.c_str());
2182 ::SetFileAttributesW(winPath
.c_str(), (dwAttrs
| FILE_ATTRIBUTE_HIDDEN
));
2186 #ifdef QBT_USES_LIBTORRENT2
2187 else if (oldActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2189 if (newActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2190 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath
);
2195 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath
/ Path(UNWANTED_FOLDER_NAME
));
2201 m_filePaths
[fileIndex
] = newFilePath
;
2203 // Remove empty leftover folders
2204 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2205 // be removed if they are empty
2206 Path oldParentPath
= oldFilePath
.parentPath();
2207 const Path commonBasePath
= Path::commonPath(oldParentPath
, newFilePath
.parentPath());
2208 while (oldParentPath
!= commonBasePath
)
2210 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath
);
2211 oldParentPath
= oldParentPath
.parentPath();
2216 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2217 m_moveFinishedTriggers
.takeFirst()();
2219 m_session
->handleTorrentNeedSaveResumeData(this);
2222 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
*p
)
2224 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2225 Q_ASSERT(fileIndex
>= 0);
2227 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2228 .arg(name(), filePath(fileIndex
).toString(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::WARNING
);
2231 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2232 m_moveFinishedTriggers
.takeFirst()();
2234 m_session
->handleTorrentNeedSaveResumeData(this);
2237 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert
*p
)
2239 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2242 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2243 Q_ASSERT(fileIndex
>= 0);
2245 m_completedFiles
.setBit(fileIndex
);
2247 const Path actualPath
= actualFilePath(fileIndex
);
2249 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2250 // only apply Mark-of-the-Web to new download files
2251 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2253 const Path fullpath
= actualStorageLocation() / actualPath
;
2254 Utils::OS::applyMarkOfTheWeb(fullpath
);
2256 #endif // Q_OS_MACOS || Q_OS_WIN
2258 if (m_session
->isAppendExtensionEnabled())
2260 const Path path
= filePath(fileIndex
);
2261 if (actualPath
!= path
)
2263 qDebug("Renaming %s to %s", qUtf8Printable(actualPath
.toString()), qUtf8Printable(path
.toString()));
2264 doRenameFile(fileIndex
, path
);
2269 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert
*p
)
2271 m_lastFileError
= {p
->error
, p
->op
};
2274 #ifdef QBT_USES_LIBTORRENT2
2275 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert
*)
2277 m_session
->handleTorrentNeedSaveResumeData(this);
2281 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused
]] const lt::metadata_received_alert
*p
)
2283 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2285 #ifdef QBT_USES_LIBTORRENT2
2286 const InfoHash prevInfoHash
= infoHash();
2287 m_infoHash
= InfoHash(m_nativeHandle
.info_hashes());
2288 if (prevInfoHash
!= infoHash())
2289 m_session
->handleTorrentInfoHashChanged(this, prevInfoHash
);
2292 m_maintenanceJob
= MaintenanceJob::HandleMetadata
;
2293 m_session
->handleTorrentNeedSaveResumeData(this);
2296 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert
*p
) const
2298 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
))
2302 void TorrentImpl::handleCategoryOptionsChanged()
2305 adjustStorageLocation();
2308 void TorrentImpl::handleAppendExtensionToggled()
2313 manageActualFilePaths();
2316 void TorrentImpl::handleUnwantedFolderToggled()
2321 manageActualFilePaths();
2324 void TorrentImpl::handleAlert(const lt::alert
*a
)
2328 #ifdef QBT_USES_LIBTORRENT2
2329 case lt::file_prio_alert::alert_type
:
2330 handleFilePrioAlert(static_cast<const lt::file_prio_alert
*>(a
));
2333 case lt::file_renamed_alert::alert_type
:
2334 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert
*>(a
));
2336 case lt::file_rename_failed_alert::alert_type
:
2337 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert
*>(a
));
2339 case lt::file_completed_alert::alert_type
:
2340 handleFileCompletedAlert(static_cast<const lt::file_completed_alert
*>(a
));
2342 case lt::file_error_alert::alert_type
:
2343 handleFileErrorAlert(static_cast<const lt::file_error_alert
*>(a
));
2345 case lt::torrent_finished_alert::alert_type
:
2346 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert
*>(a
));
2348 case lt::save_resume_data_alert::alert_type
:
2349 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert
*>(a
));
2351 case lt::save_resume_data_failed_alert::alert_type
:
2352 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert
*>(a
));
2354 case lt::torrent_paused_alert::alert_type
:
2355 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert
*>(a
));
2357 case lt::torrent_resumed_alert::alert_type
:
2358 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert
*>(a
));
2360 case lt::metadata_received_alert::alert_type
:
2361 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(a
));
2363 case lt::fastresume_rejected_alert::alert_type
:
2364 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert
*>(a
));
2366 case lt::torrent_checked_alert::alert_type
:
2367 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert
*>(a
));
2369 case lt::performance_alert::alert_type
:
2370 handlePerformanceAlert(static_cast<const lt::performance_alert
*>(a
));
2375 void TorrentImpl::manageActualFilePaths()
2377 const std::shared_ptr
<const lt::torrent_info
> nativeInfo
= nativeTorrentInfo();
2378 const lt::file_storage
&nativeFiles
= nativeInfo
->files();
2380 for (int i
= 0; i
< filesCount(); ++i
)
2382 const Path path
= filePath(i
);
2384 const auto nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
2385 const Path actualPath
{nativeFiles
.file_path(nativeIndex
)};
2386 const Path targetActualPath
= makeActualPath(i
, path
);
2387 if (actualPath
!= targetActualPath
)
2389 qDebug() << "Renaming" << actualPath
.toString() << "to" << targetActualPath
.toString();
2390 doRenameFile(i
, targetActualPath
);
2395 void TorrentImpl::adjustStorageLocation()
2397 const Path downloadPath
= this->downloadPath();
2398 const Path targetPath
= ((isFinished() || m_hasFinishedStatus
|| downloadPath
.isEmpty()) ? savePath() : downloadPath
);
2400 if ((targetPath
!= actualStorageLocation()) || isMoveInProgress())
2401 moveStorage(targetPath
, MoveStorageContext::AdjustCurrentLocation
);
2404 void TorrentImpl::doRenameFile(const int index
, const Path
&path
)
2406 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
2408 Q_ASSERT(index
>= 0);
2409 Q_ASSERT(index
< nativeIndexes
.size());
2410 if ((index
< 0) || (index
>= nativeIndexes
.size())) [[unlikely
]]
2414 m_nativeHandle
.rename_file(nativeIndexes
[index
], path
.toString().toStdString());
2417 lt::torrent_handle
TorrentImpl::nativeHandle() const
2419 return m_nativeHandle
;
2422 void TorrentImpl::setMetadata(const TorrentInfo
&torrentInfo
)
2427 m_session
->invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
]
2431 #ifdef QBT_USES_LIBTORRENT2
2432 nativeHandle
.set_metadata(torrentInfo
.nativeInfo()->info_section());
2434 const std::shared_ptr
<lt::torrent_info
> nativeInfo
= torrentInfo
.nativeInfo();
2435 nativeHandle
.set_metadata(lt::span
<const char>(nativeInfo
->metadata().get(), nativeInfo
->metadata_size()));
2438 catch (const std::exception
&) {}
2442 Torrent::StopCondition
TorrentImpl::stopCondition() const
2444 return m_stopCondition
;
2447 void TorrentImpl::setStopCondition(const StopCondition stopCondition
)
2449 if (stopCondition
== m_stopCondition
)
2455 if ((stopCondition
== StopCondition::MetadataReceived
) && hasMetadata())
2458 if ((stopCondition
== StopCondition::FilesChecked
) && hasMetadata() && !isChecking())
2461 m_stopCondition
= stopCondition
;
2464 bool TorrentImpl::isMoveInProgress() const
2466 return m_storageIsMoving
;
2469 void TorrentImpl::updateStatus(const lt::torrent_status
&nativeStatus
)
2471 const lt::torrent_status oldStatus
= std::exchange(m_nativeStatus
, nativeStatus
);
2473 if (m_nativeStatus
.num_pieces
!= oldStatus
.num_pieces
)
2478 m_payloadRateMonitor
.addSample({nativeStatus
.download_payload_rate
2479 , nativeStatus
.upload_payload_rate
});
2483 // NOTE: Don't change the order of these conditionals!
2484 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2486 m_unchecked
= false;
2487 else if (isDownloading())
2491 while (!m_statusUpdatedTriggers
.isEmpty())
2492 std::invoke(m_statusUpdatedTriggers
.dequeue());
2495 void TorrentImpl::updateProgress()
2497 Q_ASSERT(hasMetadata());
2498 if (!hasMetadata()) [[unlikely
]]
2501 Q_ASSERT(!m_filesProgress
.isEmpty());
2502 if (m_filesProgress
.isEmpty()) [[unlikely
]]
2503 m_filesProgress
.resize(filesCount());
2505 const QBitArray oldPieces
= std::exchange(m_pieces
, LT::toQBitArray(m_nativeStatus
.pieces
));
2506 const QBitArray newPieces
= m_pieces
^ oldPieces
;
2508 const int64_t pieceSize
= m_torrentInfo
.pieceLength();
2509 for (qsizetype index
= 0; index
< newPieces
.size(); ++index
)
2511 if (!newPieces
.at(index
))
2514 int64_t size
= m_torrentInfo
.pieceLength(index
);
2515 int64_t pieceOffset
= index
* pieceSize
;
2517 for (const int fileIndex
: asConst(m_torrentInfo
.fileIndicesForPiece(index
)))
2519 const int64_t fileOffsetInPiece
= pieceOffset
- m_torrentInfo
.fileOffset(fileIndex
);
2520 const int64_t add
= std::min
<int64_t>((m_torrentInfo
.fileSize(fileIndex
) - fileOffsetInPiece
), size
);
2522 m_filesProgress
[fileIndex
] += add
;
2533 void TorrentImpl::setRatioLimit(qreal limit
)
2535 if (limit
< USE_GLOBAL_RATIO
)
2536 limit
= NO_RATIO_LIMIT
;
2537 else if (limit
> MAX_RATIO
)
2540 if (m_ratioLimit
!= limit
)
2542 m_ratioLimit
= limit
;
2543 m_session
->handleTorrentNeedSaveResumeData(this);
2544 m_session
->handleTorrentShareLimitChanged(this);
2548 void TorrentImpl::setSeedingTimeLimit(int limit
)
2550 if (limit
< USE_GLOBAL_SEEDING_TIME
)
2551 limit
= NO_SEEDING_TIME_LIMIT
;
2552 else if (limit
> MAX_SEEDING_TIME
)
2553 limit
= MAX_SEEDING_TIME
;
2555 if (m_seedingTimeLimit
!= limit
)
2557 m_seedingTimeLimit
= limit
;
2558 m_session
->handleTorrentNeedSaveResumeData(this);
2559 m_session
->handleTorrentShareLimitChanged(this);
2563 void TorrentImpl::setInactiveSeedingTimeLimit(int limit
)
2565 if (limit
< USE_GLOBAL_INACTIVE_SEEDING_TIME
)
2566 limit
= NO_INACTIVE_SEEDING_TIME_LIMIT
;
2567 else if (limit
> MAX_INACTIVE_SEEDING_TIME
)
2568 limit
= MAX_SEEDING_TIME
;
2570 if (m_inactiveSeedingTimeLimit
!= limit
)
2572 m_inactiveSeedingTimeLimit
= limit
;
2573 m_session
->handleTorrentNeedSaveResumeData(this);
2574 m_session
->handleTorrentShareLimitChanged(this);
2578 void TorrentImpl::setUploadLimit(const int limit
)
2580 const int cleanValue
= cleanLimitValue(limit
);
2581 if (cleanValue
== uploadLimit())
2584 m_uploadLimit
= cleanValue
;
2585 m_nativeHandle
.set_upload_limit(m_uploadLimit
);
2586 m_session
->handleTorrentNeedSaveResumeData(this);
2589 void TorrentImpl::setDownloadLimit(const int limit
)
2591 const int cleanValue
= cleanLimitValue(limit
);
2592 if (cleanValue
== downloadLimit())
2595 m_downloadLimit
= cleanValue
;
2596 m_nativeHandle
.set_download_limit(m_downloadLimit
);
2597 m_session
->handleTorrentNeedSaveResumeData(this);
2600 void TorrentImpl::setSuperSeeding(const bool enable
)
2602 if (enable
== superSeeding())
2606 m_nativeHandle
.set_flags(lt::torrent_flags::super_seeding
);
2608 m_nativeHandle
.unset_flags(lt::torrent_flags::super_seeding
);
2610 m_session
->handleTorrentNeedSaveResumeData(this);
2613 void TorrentImpl::setDHTDisabled(const bool disable
)
2615 if (disable
== isDHTDisabled())
2619 m_nativeHandle
.set_flags(lt::torrent_flags::disable_dht
);
2621 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_dht
);
2623 m_session
->handleTorrentNeedSaveResumeData(this);
2626 void TorrentImpl::setPEXDisabled(const bool disable
)
2628 if (disable
== isPEXDisabled())
2632 m_nativeHandle
.set_flags(lt::torrent_flags::disable_pex
);
2634 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_pex
);
2636 m_session
->handleTorrentNeedSaveResumeData(this);
2639 void TorrentImpl::setLSDDisabled(const bool disable
)
2641 if (disable
== isLSDDisabled())
2645 m_nativeHandle
.set_flags(lt::torrent_flags::disable_lsd
);
2647 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_lsd
);
2649 m_session
->handleTorrentNeedSaveResumeData(this);
2652 void TorrentImpl::flushCache() const
2654 m_nativeHandle
.flush_cache();
2657 QString
TorrentImpl::createMagnetURI() const
2659 QString ret
= u
"magnet:?"_s
;
2661 const SHA1Hash infoHash1
= infoHash().v1();
2662 if (infoHash1
.isValid())
2664 ret
+= u
"xt=urn:btih:" + infoHash1
.toString();
2667 const SHA256Hash infoHash2
= infoHash().v2();
2668 if (infoHash2
.isValid())
2670 if (infoHash1
.isValid())
2672 ret
+= u
"xt=urn:btmh:1220" + infoHash2
.toString();
2675 const QString displayName
= name();
2676 if (displayName
!= id().toString())
2678 ret
+= u
"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName
));
2681 for (const TrackerEntry
&tracker
: asConst(trackers()))
2683 ret
+= u
"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker
.url
));
2686 for (const QUrl
&urlSeed
: asConst(urlSeeds()))
2688 ret
+= u
"&ws=" + QString::fromLatin1(urlSeed
.toEncoded());
2694 nonstd::expected
<lt::entry
, QString
> TorrentImpl::exportTorrent() const
2697 return nonstd::make_unexpected(tr("Missing metadata"));
2701 #ifdef QBT_USES_LIBTORRENT2
2702 const std::shared_ptr
<lt::torrent_info
> completeTorrentInfo
= m_nativeHandle
.torrent_file_with_hashes();
2703 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= (completeTorrentInfo
? completeTorrentInfo
: info().nativeInfo());
2705 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= info().nativeInfo();
2707 lt::create_torrent creator
{*torrentInfo
};
2709 for (const TrackerEntry
&entry
: asConst(trackers()))
2710 creator
.add_tracker(entry
.url
.toStdString(), entry
.tier
);
2712 return creator
.generate();
2714 catch (const lt::system_error
&err
)
2716 return nonstd::make_unexpected(QString::fromLocal8Bit(err
.what()));
2720 nonstd::expected
<QByteArray
, QString
> TorrentImpl::exportToBuffer() const
2722 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2723 if (!preparationResult
)
2724 return preparationResult
.get_unexpected();
2726 // usually torrent size should be smaller than 1 MB,
2727 // however there are >100 MB v2/hybrid torrent files out in the wild
2729 buffer
.reserve(1024 * 1024);
2730 lt::bencode(std::back_inserter(buffer
), preparationResult
.value());
2734 nonstd::expected
<void, QString
> TorrentImpl::exportToFile(const Path
&path
) const
2736 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2737 if (!preparationResult
)
2738 return preparationResult
.get_unexpected();
2740 const nonstd::expected
<void, QString
> saveResult
= Utils::IO::saveToFile(path
, preparationResult
.value());
2742 return saveResult
.get_unexpected();
2747 void TorrentImpl::fetchPeerInfo(std::function
<void (QVector
<PeerInfo
>)> resultHandler
) const
2749 invokeAsync([nativeHandle
= m_nativeHandle
, allPieces
= pieces()]() -> QVector
<PeerInfo
>
2753 std::vector
<lt::peer_info
> nativePeers
;
2754 nativeHandle
.get_peer_info(nativePeers
);
2755 QVector
<PeerInfo
> peers
;
2756 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
2757 for (const lt::peer_info
&peer
: nativePeers
)
2758 peers
.append(PeerInfo(peer
, allPieces
));
2761 catch (const std::exception
&) {}
2765 , std::move(resultHandler
));
2768 void TorrentImpl::fetchURLSeeds(std::function
<void (QVector
<QUrl
>)> resultHandler
) const
2770 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<QUrl
>
2774 const std::set
<std::string
> currentSeeds
= nativeHandle
.url_seeds();
2775 QVector
<QUrl
> urlSeeds
;
2776 urlSeeds
.reserve(static_cast<decltype(urlSeeds
)::size_type
>(currentSeeds
.size()));
2777 for (const std::string
&urlSeed
: currentSeeds
)
2778 urlSeeds
.append(QString::fromStdString(urlSeed
));
2781 catch (const std::exception
&) {}
2785 , std::move(resultHandler
));
2788 void TorrentImpl::fetchPieceAvailability(std::function
<void (QVector
<int>)> resultHandler
) const
2790 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<int>
2794 std::vector
<int> piecesAvailability
;
2795 nativeHandle
.piece_availability(piecesAvailability
);
2796 return QVector
<int>(piecesAvailability
.cbegin(), piecesAvailability
.cend());
2798 catch (const std::exception
&) {}
2802 , std::move(resultHandler
));
2805 void TorrentImpl::fetchDownloadingPieces(std::function
<void (QBitArray
)> resultHandler
) const
2807 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QBitArray
2811 #ifdef QBT_USES_LIBTORRENT2
2812 const std::vector
<lt::partial_piece_info
> queue
= nativeHandle
.get_download_queue();
2814 std::vector
<lt::partial_piece_info
> queue
;
2815 nativeHandle
.get_download_queue(queue
);
2818 result
.resize(torrentInfo
.piecesCount());
2819 for (const lt::partial_piece_info
&info
: queue
)
2820 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
2823 catch (const std::exception
&) {}
2827 , std::move(resultHandler
));
2830 void TorrentImpl::fetchAvailableFileFractions(std::function
<void (QVector
<qreal
>)> resultHandler
) const
2832 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QVector
<qreal
>
2834 if (!torrentInfo
.isValid() || (torrentInfo
.filesCount() <= 0))
2839 std::vector
<int> piecesAvailability
;
2840 nativeHandle
.piece_availability(piecesAvailability
);
2841 const int filesCount
= torrentInfo
.filesCount();
2842 // libtorrent returns empty array for seeding only torrents
2843 if (piecesAvailability
.empty())
2844 return QVector
<qreal
>(filesCount
, -1);
2846 QVector
<qreal
> result
;
2847 result
.reserve(filesCount
);
2848 for (int i
= 0; i
< filesCount
; ++i
)
2850 const TorrentInfo::PieceRange filePieces
= torrentInfo
.filePieces(i
);
2852 int availablePieces
= 0;
2853 for (const int piece
: filePieces
)
2854 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2856 const qreal availability
= filePieces
.isEmpty()
2857 ? 1 // the file has no pieces, so it is available by default
2858 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2859 result
.append(availability
);
2863 catch (const std::exception
&) {}
2867 , std::move(resultHandler
));
2870 void TorrentImpl::prioritizeFiles(const QVector
<DownloadPriority
> &priorities
)
2875 Q_ASSERT(priorities
.size() == filesCount());
2877 // Reset 'm_hasSeedStatus' if needed in order to react again to
2878 // 'torrent_finished_alert' and eg show tray notifications
2879 const QVector
<DownloadPriority
> oldPriorities
= filePriorities();
2880 for (int i
= 0; i
< oldPriorities
.size(); ++i
)
2882 if ((oldPriorities
[i
] == DownloadPriority::Ignored
)
2883 && (priorities
[i
] > DownloadPriority::Ignored
)
2884 && !m_completedFiles
.at(i
))
2886 m_hasFinishedStatus
= false;
2891 const int internalFilesCount
= m_torrentInfo
.nativeInfo()->files().num_files(); // including .pad files
2892 auto nativePriorities
= std::vector
<lt::download_priority_t
>(internalFilesCount
, LT::toNative(DownloadPriority::Normal
));
2893 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
2894 for (int i
= 0; i
< priorities
.size(); ++i
)
2895 nativePriorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(priorities
[i
]);
2897 qDebug() << Q_FUNC_INFO
<< "Changing files priorities...";
2898 m_nativeHandle
.prioritize_files(nativePriorities
);
2900 m_filePriorities
= priorities
;
2901 // Restore first/last piece first option if necessary
2902 if (m_hasFirstLastPiecePriority
)
2903 applyFirstLastPiecePriority(true);
2904 manageActualFilePaths();
2907 QVector
<qreal
> TorrentImpl::availableFileFractions() const
2909 Q_ASSERT(hasMetadata());
2911 const int filesCount
= this->filesCount();
2912 if (filesCount
<= 0) return {};
2914 const QVector
<int> piecesAvailability
= pieceAvailability();
2915 // libtorrent returns empty array for seeding only torrents
2916 if (piecesAvailability
.empty()) return QVector
<qreal
>(filesCount
, -1);
2919 res
.reserve(filesCount
);
2920 for (int i
= 0; i
< filesCount
; ++i
)
2922 const TorrentInfo::PieceRange filePieces
= m_torrentInfo
.filePieces(i
);
2924 int availablePieces
= 0;
2925 for (const int piece
: filePieces
)
2926 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2928 const qreal availability
= filePieces
.isEmpty()
2929 ? 1 // the file has no pieces, so it is available by default
2930 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2931 res
.push_back(availability
);
2936 template <typename Func
, typename Callback
>
2937 void TorrentImpl::invokeAsync(Func func
, Callback resultHandler
) const
2939 m_session
->invokeAsync([session
= m_session
2940 , func
= std::move(func
)
2941 , resultHandler
= std::move(resultHandler
)
2942 , thisTorrent
= QPointer
<const TorrentImpl
>(this)]() mutable
2944 session
->invoke([result
= func(), thisTorrent
, resultHandler
= std::move(resultHandler
)]
2947 resultHandler(result
);