2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "torrentimpl.h"
39 #include <libtorrent/address.hpp>
40 #include <libtorrent/alert_types.hpp>
41 #include <libtorrent/create_torrent.hpp>
42 #include <libtorrent/session.hpp>
43 #include <libtorrent/storage_defs.hpp>
44 #include <libtorrent/time.hpp>
46 #ifdef QBT_USES_LIBTORRENT2
47 #include <libtorrent/info_hash.hpp>
50 #include <QtSystemDetection>
55 #include <QStringList>
58 #include "base/exceptions.h"
59 #include "base/global.h"
60 #include "base/logger.h"
61 #include "base/preferences.h"
62 #include "base/types.h"
63 #include "base/utils/fs.h"
64 #include "base/utils/io.h"
66 #include "downloadpriority.h"
67 #include "extensiondata.h"
68 #include "loadtorrentparams.h"
69 #include "ltqbitarray.h"
70 #include "lttypecast.h"
71 #include "peeraddress.h"
73 #include "sessionimpl.h"
74 #include "trackerentry.h"
76 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
77 #include "base/utils/os.h"
78 #endif // Q_OS_MACOS || Q_OS_WIN
80 using namespace BitTorrent
;
84 lt::announce_entry
makeNativeAnnounceEntry(const QString
&url
, const int tier
)
86 lt::announce_entry entry
{url
.toStdString()};
91 QDateTime
fromLTTimePoint32(const lt::time_point32
&timePoint
)
93 const auto ltNow
= lt::clock_type::now();
94 const auto qNow
= QDateTime::currentDateTime();
95 const auto secsSinceNow
= lt::duration_cast
<lt::seconds
>(timePoint
- ltNow
+ lt::milliseconds(500)).count();
97 return qNow
.addSecs(secsSinceNow
);
100 QString
toString(const lt::tcp::endpoint
<TCPEndpoint
)
102 return QString::fromStdString((std::stringstream() << ltTCPEndpoint
).str());
105 void updateTrackerEntryStatus(TrackerEntryStatus
&trackerEntryStatus
, const lt::announce_entry
&nativeEntry
106 , const QSet
<int> &btProtocols
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
108 Q_ASSERT(trackerEntryStatus
.url
== QString::fromStdString(nativeEntry
.url
));
110 trackerEntryStatus
.tier
= nativeEntry
.tier
;
112 // remove outdated endpoints
113 trackerEntryStatus
.endpoints
.removeIf([&nativeEntry
](const QHash
<std::pair
<QString
, int>, TrackerEndpointStatus
>::iterator
&iter
)
115 return std::none_of(nativeEntry
.endpoints
.cbegin(), nativeEntry
.endpoints
.cend()
116 , [&endpointName
= std::get
<0>(iter
.key())](const auto &existingEndpoint
)
118 return (endpointName
== toString(existingEndpoint
.local_endpoint
));
122 const auto numEndpoints
= static_cast<qsizetype
>(nativeEntry
.endpoints
.size()) * btProtocols
.size();
126 int numNotWorking
= 0;
127 int numTrackerError
= 0;
128 int numUnreachable
= 0;
130 for (const lt::announce_endpoint
<AnnounceEndpoint
: nativeEntry
.endpoints
)
132 const auto endpointName
= toString(ltAnnounceEndpoint
.local_endpoint
);
134 for (const auto protocolVersion
: btProtocols
)
136 #ifdef QBT_USES_LIBTORRENT2
137 Q_ASSERT((protocolVersion
== 1) || (protocolVersion
== 2));
138 const auto ltProtocolVersion
= (protocolVersion
== 1) ? lt::protocol_version::V1
: lt::protocol_version::V2
;
139 const lt::announce_infohash
<AnnounceInfo
= ltAnnounceEndpoint
.info_hashes
[ltProtocolVersion
];
141 Q_ASSERT(protocolVersion
== 1);
142 const lt::announce_endpoint
<AnnounceInfo
= ltAnnounceEndpoint
;
144 const QMap
<int, int> &endpointUpdateInfo
= updateInfo
[ltAnnounceEndpoint
.local_endpoint
];
145 TrackerEndpointStatus
&trackerEndpointStatus
= trackerEntryStatus
.endpoints
[std::make_pair(endpointName
, protocolVersion
)];
147 trackerEndpointStatus
.name
= endpointName
;
148 trackerEndpointStatus
.btVersion
= protocolVersion
;
149 trackerEndpointStatus
.numPeers
= endpointUpdateInfo
.value(protocolVersion
, trackerEndpointStatus
.numPeers
);
150 trackerEndpointStatus
.numSeeds
= ltAnnounceInfo
.scrape_complete
;
151 trackerEndpointStatus
.numLeeches
= ltAnnounceInfo
.scrape_incomplete
;
152 trackerEndpointStatus
.numDownloaded
= ltAnnounceInfo
.scrape_downloaded
;
153 trackerEndpointStatus
.nextAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.next_announce
);
154 trackerEndpointStatus
.minAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.min_announce
);
156 if (ltAnnounceInfo
.updating
)
158 trackerEndpointStatus
.state
= TrackerEndpointState::Updating
;
161 else if (ltAnnounceInfo
.fails
> 0)
163 if (ltAnnounceInfo
.last_error
== lt::errors::tracker_failure
)
165 trackerEndpointStatus
.state
= TrackerEndpointState::TrackerError
;
168 else if (ltAnnounceInfo
.last_error
== lt::errors::announce_skipped
)
170 trackerEndpointStatus
.state
= TrackerEndpointState::Unreachable
;
175 trackerEndpointStatus
.state
= TrackerEndpointState::NotWorking
;
179 else if (nativeEntry
.verified
)
181 trackerEndpointStatus
.state
= TrackerEndpointState::Working
;
186 trackerEndpointStatus
.state
= TrackerEndpointState::NotContacted
;
189 if (!ltAnnounceInfo
.message
.empty())
191 trackerEndpointStatus
.message
= QString::fromStdString(ltAnnounceInfo
.message
);
193 else if (ltAnnounceInfo
.last_error
)
195 trackerEndpointStatus
.message
= QString::fromLocal8Bit(ltAnnounceInfo
.last_error
.message());
199 trackerEndpointStatus
.message
.clear();
204 if (numEndpoints
> 0)
208 trackerEntryStatus
.state
= TrackerEndpointState::Updating
;
210 else if (numWorking
> 0)
212 trackerEntryStatus
.state
= TrackerEndpointState::Working
;
214 else if (numTrackerError
> 0)
216 trackerEntryStatus
.state
= TrackerEndpointState::TrackerError
;
218 else if (numUnreachable
== numEndpoints
)
220 trackerEntryStatus
.state
= TrackerEndpointState::Unreachable
;
222 else if ((numUnreachable
+ numNotWorking
) == numEndpoints
)
224 trackerEntryStatus
.state
= TrackerEndpointState::NotWorking
;
228 trackerEntryStatus
.numPeers
= -1;
229 trackerEntryStatus
.numSeeds
= -1;
230 trackerEntryStatus
.numLeeches
= -1;
231 trackerEntryStatus
.numDownloaded
= -1;
232 trackerEntryStatus
.nextAnnounceTime
= QDateTime();
233 trackerEntryStatus
.minAnnounceTime
= QDateTime();
234 trackerEntryStatus
.message
.clear();
236 for (const TrackerEndpointStatus
&endpointStatus
: asConst(trackerEntryStatus
.endpoints
))
238 trackerEntryStatus
.numPeers
= std::max(trackerEntryStatus
.numPeers
, endpointStatus
.numPeers
);
239 trackerEntryStatus
.numSeeds
= std::max(trackerEntryStatus
.numSeeds
, endpointStatus
.numSeeds
);
240 trackerEntryStatus
.numLeeches
= std::max(trackerEntryStatus
.numLeeches
, endpointStatus
.numLeeches
);
241 trackerEntryStatus
.numDownloaded
= std::max(trackerEntryStatus
.numDownloaded
, endpointStatus
.numDownloaded
);
243 if (endpointStatus
.state
== trackerEntryStatus
.state
)
245 if (!trackerEntryStatus
.nextAnnounceTime
.isValid() || (trackerEntryStatus
.nextAnnounceTime
> endpointStatus
.nextAnnounceTime
))
247 trackerEntryStatus
.nextAnnounceTime
= endpointStatus
.nextAnnounceTime
;
248 trackerEntryStatus
.minAnnounceTime
= endpointStatus
.minAnnounceTime
;
249 if ((endpointStatus
.state
!= TrackerEndpointState::Working
)
250 || !endpointStatus
.message
.isEmpty())
252 trackerEntryStatus
.message
= endpointStatus
.message
;
256 if (endpointStatus
.state
== TrackerEndpointState::Working
)
258 if (trackerEntryStatus
.message
.isEmpty())
259 trackerEntryStatus
.message
= endpointStatus
.message
;
265 template <typename Vector
>
266 Vector
resized(const Vector
&inVector
, const typename
Vector::size_type size
, const typename
Vector::value_type
&defaultValue
)
268 Vector outVector
= inVector
;
269 outVector
.resize(size
, defaultValue
);
273 // This is an imitation of limit normalization performed by libtorrent itself.
274 // We need perform it to keep cached values in line with the ones used by libtorrent.
275 int cleanLimitValue(const int value
)
277 return ((value
< 0) || (value
== std::numeric_limits
<int>::max())) ? 0 : value
;
283 TorrentImpl::TorrentImpl(SessionImpl
*session
, lt::session
*nativeSession
284 , const lt::torrent_handle
&nativeHandle
, const LoadTorrentParams
¶ms
)
287 , m_nativeSession(nativeSession
)
288 , m_nativeHandle(nativeHandle
)
289 #ifdef QBT_USES_LIBTORRENT2
290 , m_infoHash(m_nativeHandle
.info_hashes())
292 , m_infoHash(m_nativeHandle
.info_hash())
294 , m_name(params
.name
)
295 , m_savePath(params
.savePath
)
296 , m_downloadPath(params
.downloadPath
)
297 , m_category(params
.category
)
298 , m_tags(params
.tags
)
299 , m_ratioLimit(params
.ratioLimit
)
300 , m_seedingTimeLimit(params
.seedingTimeLimit
)
301 , m_inactiveSeedingTimeLimit(params
.inactiveSeedingTimeLimit
)
302 , m_shareLimitAction(params
.shareLimitAction
)
303 , m_operatingMode(params
.operatingMode
)
304 , m_contentLayout(params
.contentLayout
)
305 , m_hasFinishedStatus(params
.hasFinishedStatus
)
306 , m_hasFirstLastPiecePriority(params
.firstLastPiecePriority
)
307 , m_useAutoTMM(params
.useAutoTMM
)
308 , m_isStopped(params
.stopped
)
309 , m_sslParams(params
.sslParameters
)
310 , m_ltAddTorrentParams(params
.ltAddTorrentParams
)
311 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams
.download_limit
))
312 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams
.upload_limit
))
314 if (m_ltAddTorrentParams
.ti
)
316 // Initialize it only if torrent is added with metadata.
317 // Otherwise it should be initialized in "Metadata received" handler.
318 m_torrentInfo
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
320 Q_ASSERT(m_filePaths
.isEmpty());
321 Q_ASSERT(m_indexMap
.isEmpty());
322 const int filesCount
= m_torrentInfo
.filesCount();
323 m_filePaths
.reserve(filesCount
);
324 m_indexMap
.reserve(filesCount
);
325 m_filePriorities
.reserve(filesCount
);
326 const std::vector
<lt::download_priority_t
> filePriorities
=
327 resized(m_ltAddTorrentParams
.file_priorities
, m_ltAddTorrentParams
.ti
->num_files()
328 , LT::toNative(m_ltAddTorrentParams
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
330 m_completedFiles
.fill(static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
), filesCount
);
331 m_filesProgress
.resize(filesCount
);
333 for (int i
= 0; i
< filesCount
; ++i
)
335 const lt::file_index_t nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
336 m_indexMap
[nativeIndex
] = i
;
338 const auto fileIter
= m_ltAddTorrentParams
.renamed_files
.find(nativeIndex
);
339 const Path filePath
= ((fileIter
!= m_ltAddTorrentParams
.renamed_files
.end())
340 ? makeUserPath(Path(fileIter
->second
)) : m_torrentInfo
.filePath(i
));
341 m_filePaths
.append(filePath
);
343 const auto priority
= LT::fromNative(filePriorities
[LT::toUnderlyingType(nativeIndex
)]);
344 m_filePriorities
.append(priority
);
348 setStopCondition(params
.stopCondition
);
350 const auto *extensionData
= static_cast<ExtensionData
*>(m_ltAddTorrentParams
.userdata
);
351 m_trackerEntryStatuses
.reserve(static_cast<decltype(m_trackerEntryStatuses
)::size_type
>(extensionData
->trackers
.size()));
352 for (const lt::announce_entry
&announceEntry
: extensionData
->trackers
)
353 m_trackerEntryStatuses
.append({QString::fromStdString(announceEntry
.url
), announceEntry
.tier
});
354 m_urlSeeds
.reserve(static_cast<decltype(m_urlSeeds
)::size_type
>(extensionData
->urlSeeds
.size()));
355 for (const std::string
&urlSeed
: extensionData
->urlSeeds
)
356 m_urlSeeds
.append(QString::fromStdString(urlSeed
));
357 m_nativeStatus
= extensionData
->status
;
365 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority
);
368 TorrentImpl::~TorrentImpl() = default;
370 bool TorrentImpl::isValid() const
372 return m_nativeHandle
.is_valid();
375 Session
*TorrentImpl::session() const
380 InfoHash
TorrentImpl::infoHash() const
385 QString
TorrentImpl::name() const
387 if (!m_name
.isEmpty())
391 return m_torrentInfo
.name();
393 const QString name
= QString::fromStdString(m_nativeStatus
.name
);
397 return id().toString();
400 QDateTime
TorrentImpl::creationDate() const
402 return m_torrentInfo
.creationDate();
405 QString
TorrentImpl::creator() const
407 return m_torrentInfo
.creator();
410 QString
TorrentImpl::comment() const
412 return m_torrentInfo
.comment();
415 bool TorrentImpl::isPrivate() const
417 return m_torrentInfo
.isPrivate();
420 qlonglong
TorrentImpl::totalSize() const
422 return m_torrentInfo
.totalSize();
425 // size without the "don't download" files
426 qlonglong
TorrentImpl::wantedSize() const
428 return m_nativeStatus
.total_wanted
;
431 qlonglong
TorrentImpl::completedSize() const
433 return m_nativeStatus
.total_wanted_done
;
436 qlonglong
TorrentImpl::pieceLength() const
438 return m_torrentInfo
.pieceLength();
441 qlonglong
TorrentImpl::wastedSize() const
443 return (m_nativeStatus
.total_failed_bytes
+ m_nativeStatus
.total_redundant_bytes
);
446 QString
TorrentImpl::currentTracker() const
448 return QString::fromStdString(m_nativeStatus
.current_tracker
);
451 Path
TorrentImpl::savePath() const
453 return isAutoTMMEnabled() ? m_session
->categorySavePath(category()) : m_savePath
;
456 void TorrentImpl::setSavePath(const Path
&path
)
458 Q_ASSERT(!isAutoTMMEnabled());
460 const Path basePath
= m_session
->useCategoryPathsInManualMode()
461 ? m_session
->categorySavePath(category()) : m_session
->savePath();
462 const Path resolvedPath
= (path
.isAbsolute() ? path
: (basePath
/ path
));
463 if (resolvedPath
== savePath())
466 if (isFinished() || m_hasFinishedStatus
|| downloadPath().isEmpty())
468 moveStorage(resolvedPath
, MoveStorageContext::ChangeSavePath
);
472 m_savePath
= resolvedPath
;
473 m_session
->handleTorrentSavePathChanged(this);
474 deferredRequestResumeData();
478 Path
TorrentImpl::downloadPath() const
480 return isAutoTMMEnabled() ? m_session
->categoryDownloadPath(category()) : m_downloadPath
;
483 void TorrentImpl::setDownloadPath(const Path
&path
)
485 Q_ASSERT(!isAutoTMMEnabled());
487 const Path basePath
= m_session
->useCategoryPathsInManualMode()
488 ? m_session
->categoryDownloadPath(category()) : m_session
->downloadPath();
489 const Path resolvedPath
= (path
.isEmpty() || path
.isAbsolute()) ? path
: (basePath
/ path
);
490 if (resolvedPath
== m_downloadPath
)
493 const bool isIncomplete
= !(isFinished() || m_hasFinishedStatus
);
496 moveStorage((resolvedPath
.isEmpty() ? savePath() : resolvedPath
), MoveStorageContext::ChangeDownloadPath
);
500 m_downloadPath
= resolvedPath
;
501 m_session
->handleTorrentSavePathChanged(this);
502 deferredRequestResumeData();
506 Path
TorrentImpl::rootPath() const
511 const Path relativeRootPath
= Path::findRootFolder(filePaths());
512 if (relativeRootPath
.isEmpty())
515 return (actualStorageLocation() / relativeRootPath
);
518 Path
TorrentImpl::contentPath() const
523 if (filesCount() == 1)
524 return (actualStorageLocation() / filePath(0));
526 const Path rootPath
= this->rootPath();
527 return (rootPath
.isEmpty() ? actualStorageLocation() : rootPath
);
530 bool TorrentImpl::isAutoTMMEnabled() const
535 void TorrentImpl::setAutoTMMEnabled(bool enabled
)
537 if (m_useAutoTMM
== enabled
)
540 m_useAutoTMM
= enabled
;
543 m_savePath
= m_session
->categorySavePath(category());
544 m_downloadPath
= m_session
->categoryDownloadPath(category());
547 deferredRequestResumeData();
548 m_session
->handleTorrentSavingModeChanged(this);
550 adjustStorageLocation();
553 Path
TorrentImpl::actualStorageLocation() const
558 return Path(m_nativeStatus
.save_path
);
561 void TorrentImpl::setAutoManaged(const bool enable
)
564 m_nativeHandle
.set_flags(lt::torrent_flags::auto_managed
);
566 m_nativeHandle
.unset_flags(lt::torrent_flags::auto_managed
);
569 Path
TorrentImpl::makeActualPath(int index
, const Path
&path
) const
571 Path actualPath
= path
;
573 if (m_session
->isAppendExtensionEnabled()
574 && (fileSize(index
) > 0) && !m_completedFiles
.at(index
))
576 actualPath
+= QB_EXT
;
579 if (m_session
->isUnwantedFolderEnabled()
580 && (m_filePriorities
[index
] == DownloadPriority::Ignored
))
582 const Path parentPath
= actualPath
.parentPath();
583 const QString fileName
= actualPath
.filename();
584 actualPath
= parentPath
/ Path(UNWANTED_FOLDER_NAME
) / Path(fileName
);
590 Path
TorrentImpl::makeUserPath(const Path
&path
) const
592 Path userPath
= path
.removedExtension(QB_EXT
);
594 const Path parentRelPath
= userPath
.parentPath();
595 if (parentRelPath
.filename() == UNWANTED_FOLDER_NAME
)
597 const QString fileName
= userPath
.filename();
598 const Path relPath
= parentRelPath
.parentPath();
599 userPath
= relPath
/ Path(fileName
);
605 QVector
<TrackerEntryStatus
> TorrentImpl::trackers() const
607 return m_trackerEntryStatuses
;
610 void TorrentImpl::addTrackers(QVector
<TrackerEntry
> trackers
)
612 trackers
.removeIf([](const TrackerEntry
&trackerEntry
) { return trackerEntry
.url
.isEmpty(); });
614 QSet
<TrackerEntry
> currentTrackerSet
;
615 currentTrackerSet
.reserve(m_trackerEntryStatuses
.size());
616 for (const TrackerEntryStatus
&status
: asConst(m_trackerEntryStatuses
))
617 currentTrackerSet
.insert({.url
= status
.url
, .tier
= status
.tier
});
619 const auto newTrackerSet
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend()) - currentTrackerSet
;
620 if (newTrackerSet
.isEmpty())
623 trackers
= QVector
<TrackerEntry
>(newTrackerSet
.cbegin(), newTrackerSet
.cend());
624 for (const TrackerEntry
&tracker
: asConst(trackers
))
626 m_nativeHandle
.add_tracker(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
627 m_trackerEntryStatuses
.append({tracker
.url
, tracker
.tier
});
629 std::sort(m_trackerEntryStatuses
.begin(), m_trackerEntryStatuses
.end()
630 , [](const TrackerEntryStatus
&left
, const TrackerEntryStatus
&right
) { return left
.tier
< right
.tier
; });
632 deferredRequestResumeData();
633 m_session
->handleTorrentTrackersAdded(this, trackers
);
636 void TorrentImpl::removeTrackers(const QStringList
&trackers
)
638 QStringList removedTrackers
= trackers
;
639 for (const QString
&tracker
: trackers
)
641 if (!m_trackerEntryStatuses
.removeOne({tracker
}))
642 removedTrackers
.removeOne(tracker
);
645 std::vector
<lt::announce_entry
> nativeTrackers
;
646 nativeTrackers
.reserve(m_trackerEntryStatuses
.size());
647 for (const TrackerEntryStatus
&tracker
: asConst(m_trackerEntryStatuses
))
648 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
650 if (!removedTrackers
.isEmpty())
652 m_nativeHandle
.replace_trackers(nativeTrackers
);
654 deferredRequestResumeData();
655 m_session
->handleTorrentTrackersRemoved(this, removedTrackers
);
659 void TorrentImpl::replaceTrackers(QVector
<TrackerEntry
> trackers
)
661 trackers
.removeIf([](const TrackerEntry
&trackerEntry
) { return trackerEntry
.url
.isEmpty(); });
663 // Filter out duplicate trackers
664 const auto uniqueTrackers
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend());
665 trackers
= QVector
<TrackerEntry
>(uniqueTrackers
.cbegin(), uniqueTrackers
.cend());
666 std::sort(trackers
.begin(), trackers
.end()
667 , [](const TrackerEntry
&left
, const TrackerEntry
&right
) { return left
.tier
< right
.tier
; });
669 std::vector
<lt::announce_entry
> nativeTrackers
;
670 nativeTrackers
.reserve(trackers
.size());
671 m_trackerEntryStatuses
.clear();
673 for (const TrackerEntry
&tracker
: trackers
)
675 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
676 m_trackerEntryStatuses
.append({tracker
.url
, tracker
.tier
});
679 m_nativeHandle
.replace_trackers(nativeTrackers
);
681 // Clear the peer list if it's a private torrent since
682 // we do not want to keep connecting with peers from old tracker.
686 deferredRequestResumeData();
687 m_session
->handleTorrentTrackersChanged(this);
690 QVector
<QUrl
> TorrentImpl::urlSeeds() const
695 void TorrentImpl::addUrlSeeds(const QVector
<QUrl
> &urlSeeds
)
697 m_session
->invokeAsync([urlSeeds
, session
= m_session
698 , nativeHandle
= m_nativeHandle
699 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
703 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
704 QVector
<QUrl
> currentSeeds
;
705 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
706 for (const std::string
&urlSeed
: nativeSeeds
)
707 currentSeeds
.append(QString::fromStdString(urlSeed
));
709 QVector
<QUrl
> addedUrlSeeds
;
710 addedUrlSeeds
.reserve(urlSeeds
.size());
712 for (const QUrl
&url
: urlSeeds
)
714 if (!currentSeeds
.contains(url
))
716 nativeHandle
.add_url_seed(url
.toString().toStdString());
717 addedUrlSeeds
.append(url
);
721 currentSeeds
.append(addedUrlSeeds
);
722 session
->invoke([session
, thisTorrent
, currentSeeds
, addedUrlSeeds
]
727 thisTorrent
->m_urlSeeds
= currentSeeds
;
728 if (!addedUrlSeeds
.isEmpty())
730 thisTorrent
->deferredRequestResumeData();
731 session
->handleTorrentUrlSeedsAdded(thisTorrent
, addedUrlSeeds
);
735 catch (const std::exception
&) {}
739 void TorrentImpl::removeUrlSeeds(const QVector
<QUrl
> &urlSeeds
)
741 m_session
->invokeAsync([urlSeeds
, session
= m_session
742 , nativeHandle
= m_nativeHandle
743 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
747 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
748 QVector
<QUrl
> currentSeeds
;
749 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
750 for (const std::string
&urlSeed
: nativeSeeds
)
751 currentSeeds
.append(QString::fromStdString(urlSeed
));
753 QVector
<QUrl
> removedUrlSeeds
;
754 removedUrlSeeds
.reserve(urlSeeds
.size());
756 for (const QUrl
&url
: urlSeeds
)
758 if (currentSeeds
.removeOne(url
))
760 nativeHandle
.remove_url_seed(url
.toString().toStdString());
761 removedUrlSeeds
.append(url
);
765 session
->invoke([session
, thisTorrent
, currentSeeds
, removedUrlSeeds
]
770 thisTorrent
->m_urlSeeds
= currentSeeds
;
772 if (!removedUrlSeeds
.isEmpty())
774 thisTorrent
->deferredRequestResumeData();
775 session
->handleTorrentUrlSeedsRemoved(thisTorrent
, removedUrlSeeds
);
779 catch (const std::exception
&) {}
783 void TorrentImpl::clearPeers()
785 m_nativeHandle
.clear_peers();
788 bool TorrentImpl::connectPeer(const PeerAddress
&peerAddress
)
791 const lt::address addr
= lt::make_address(peerAddress
.ip
.toString().toStdString(), ec
);
792 if (ec
) return false;
794 const lt::tcp::endpoint
endpoint(addr
, peerAddress
.port
);
797 m_nativeHandle
.connect_peer(endpoint
);
799 catch (const lt::system_error
&err
)
801 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
802 .arg(peerAddress
.toString(), name(), QString::fromLocal8Bit(err
.what())), Log::WARNING
);
806 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress
.toString(), name()));
810 bool TorrentImpl::needSaveResumeData() const
812 return m_nativeStatus
.need_save_resume
;
815 void TorrentImpl::requestResumeData(const lt::resume_data_flags_t flags
)
817 m_nativeHandle
.save_resume_data(flags
);
818 m_deferredRequestResumeDataInvoked
= false;
820 m_session
->handleTorrentResumeDataRequested(this);
823 void TorrentImpl::deferredRequestResumeData()
825 if (!m_deferredRequestResumeDataInvoked
)
827 QMetaObject::invokeMethod(this, [this]
829 requestResumeData((m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
830 ? lt::torrent_handle::save_info_dict
: lt::resume_data_flags_t());
831 }, Qt::QueuedConnection
);
833 m_deferredRequestResumeDataInvoked
= true;
837 int TorrentImpl::filesCount() const
839 return m_torrentInfo
.filesCount();
842 int TorrentImpl::piecesCount() const
844 return m_torrentInfo
.piecesCount();
847 int TorrentImpl::piecesHave() const
849 return m_nativeStatus
.num_pieces
;
852 qreal
TorrentImpl::progress() const
855 return m_nativeStatus
.progress
;
857 if (m_nativeStatus
.total_wanted
== 0)
860 if (m_nativeStatus
.total_wanted_done
== m_nativeStatus
.total_wanted
)
863 const qreal progress
= static_cast<qreal
>(m_nativeStatus
.total_wanted_done
) / m_nativeStatus
.total_wanted
;
864 if ((progress
< 0.f
) || (progress
> 1.f
))
866 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
867 .arg(name(), QString::number(m_nativeStatus
.total_wanted
), QString::number(m_nativeStatus
.total_wanted_done
))
874 QString
TorrentImpl::category() const
879 bool TorrentImpl::belongsToCategory(const QString
&category
) const
881 if (m_category
.isEmpty())
882 return category
.isEmpty();
884 if (m_category
== category
)
887 return (m_session
->isSubcategoriesEnabled() && m_category
.startsWith(category
+ u
'/'));
890 TagSet
TorrentImpl::tags() const
895 bool TorrentImpl::hasTag(const Tag
&tag
) const
897 return m_tags
.contains(tag
);
900 bool TorrentImpl::addTag(const Tag
&tag
)
907 if (!m_session
->hasTag(tag
))
909 if (!m_session
->addTag(tag
))
913 deferredRequestResumeData();
914 m_session
->handleTorrentTagAdded(this, tag
);
918 bool TorrentImpl::removeTag(const Tag
&tag
)
920 if (m_tags
.remove(tag
))
922 deferredRequestResumeData();
923 m_session
->handleTorrentTagRemoved(this, tag
);
929 void TorrentImpl::removeAllTags()
931 for (const Tag
&tag
: asConst(tags()))
935 QDateTime
TorrentImpl::addedTime() const
937 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.added_time
);
940 qreal
TorrentImpl::ratioLimit() const
945 int TorrentImpl::seedingTimeLimit() const
947 return m_seedingTimeLimit
;
950 int TorrentImpl::inactiveSeedingTimeLimit() const
952 return m_inactiveSeedingTimeLimit
;
955 Path
TorrentImpl::filePath(const int index
) const
957 Q_ASSERT(index
>= 0);
958 Q_ASSERT(index
< m_filePaths
.size());
960 return m_filePaths
.value(index
, {});
963 Path
TorrentImpl::actualFilePath(const int index
) const
965 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
967 Q_ASSERT(index
>= 0);
968 Q_ASSERT(index
< nativeIndexes
.size());
969 if ((index
< 0) || (index
>= nativeIndexes
.size()))
972 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes
[index
]));
975 qlonglong
TorrentImpl::fileSize(const int index
) const
977 return m_torrentInfo
.fileSize(index
);
980 PathList
TorrentImpl::filePaths() const
985 QVector
<DownloadPriority
> TorrentImpl::filePriorities() const
987 return m_filePriorities
;
990 TorrentInfo
TorrentImpl::info() const
992 return m_torrentInfo
;
995 bool TorrentImpl::isStopped() const
1000 bool TorrentImpl::isQueued() const
1002 if (!m_session
->isQueueingSystemEnabled())
1005 // Torrent is Queued if it isn't in Stopped state but paused internally
1006 return (!isStopped()
1007 && (m_nativeStatus
.flags
& lt::torrent_flags::auto_managed
)
1008 && (m_nativeStatus
.flags
& lt::torrent_flags::paused
));
1011 bool TorrentImpl::isChecking() const
1013 return ((m_nativeStatus
.state
== lt::torrent_status::checking_files
)
1014 || (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
));
1017 bool TorrentImpl::isDownloading() const
1021 case TorrentState::Downloading
:
1022 case TorrentState::DownloadingMetadata
:
1023 case TorrentState::ForcedDownloadingMetadata
:
1024 case TorrentState::StalledDownloading
:
1025 case TorrentState::CheckingDownloading
:
1026 case TorrentState::StoppedDownloading
:
1027 case TorrentState::QueuedDownloading
:
1028 case TorrentState::ForcedDownloading
:
1037 bool TorrentImpl::isMoving() const
1039 return m_state
== TorrentState::Moving
;
1042 bool TorrentImpl::isUploading() const
1046 case TorrentState::Uploading
:
1047 case TorrentState::StalledUploading
:
1048 case TorrentState::CheckingUploading
:
1049 case TorrentState::QueuedUploading
:
1050 case TorrentState::ForcedUploading
:
1059 bool TorrentImpl::isCompleted() const
1063 case TorrentState::Uploading
:
1064 case TorrentState::StalledUploading
:
1065 case TorrentState::CheckingUploading
:
1066 case TorrentState::StoppedUploading
:
1067 case TorrentState::QueuedUploading
:
1068 case TorrentState::ForcedUploading
:
1077 bool TorrentImpl::isActive() const
1081 case TorrentState::StalledDownloading
:
1082 return (uploadPayloadRate() > 0);
1084 case TorrentState::DownloadingMetadata
:
1085 case TorrentState::ForcedDownloadingMetadata
:
1086 case TorrentState::Downloading
:
1087 case TorrentState::ForcedDownloading
:
1088 case TorrentState::Uploading
:
1089 case TorrentState::ForcedUploading
:
1090 case TorrentState::Moving
:
1100 bool TorrentImpl::isInactive() const
1105 bool TorrentImpl::isErrored() const
1107 return ((m_state
== TorrentState::MissingFiles
)
1108 || (m_state
== TorrentState::Error
));
1111 bool TorrentImpl::isFinished() const
1113 return ((m_nativeStatus
.state
== lt::torrent_status::finished
)
1114 || (m_nativeStatus
.state
== lt::torrent_status::seeding
));
1117 bool TorrentImpl::isForced() const
1119 return (!isStopped() && (m_operatingMode
== TorrentOperatingMode::Forced
));
1122 bool TorrentImpl::isSequentialDownload() const
1124 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::sequential_download
);
1127 bool TorrentImpl::hasFirstLastPiecePriority() const
1129 return m_hasFirstLastPiecePriority
;
1132 TorrentState
TorrentImpl::state() const
1137 void TorrentImpl::updateState()
1139 if (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
)
1141 m_state
= TorrentState::CheckingResumeData
;
1143 else if (isMoveInProgress())
1145 m_state
= TorrentState::Moving
;
1147 else if (hasMissingFiles())
1149 m_state
= TorrentState::MissingFiles
;
1151 else if (hasError())
1153 m_state
= TorrentState::Error
;
1155 else if (!hasMetadata())
1158 m_state
= TorrentState::StoppedDownloading
;
1159 else if (isQueued())
1160 m_state
= TorrentState::QueuedDownloading
;
1162 m_state
= isForced() ? TorrentState::ForcedDownloadingMetadata
: TorrentState::DownloadingMetadata
;
1164 else if ((m_nativeStatus
.state
== lt::torrent_status::checking_files
) && !isStopped())
1166 // If the torrent is not just in the "checking" state, but is being actually checked
1167 m_state
= m_hasFinishedStatus
? TorrentState::CheckingUploading
: TorrentState::CheckingDownloading
;
1169 else if (isFinished())
1172 m_state
= TorrentState::StoppedUploading
;
1173 else if (isQueued())
1174 m_state
= TorrentState::QueuedUploading
;
1175 else if (isForced())
1176 m_state
= TorrentState::ForcedUploading
;
1177 else if (m_nativeStatus
.upload_payload_rate
> 0)
1178 m_state
= TorrentState::Uploading
;
1180 m_state
= TorrentState::StalledUploading
;
1185 m_state
= TorrentState::StoppedDownloading
;
1186 else if (isQueued())
1187 m_state
= TorrentState::QueuedDownloading
;
1188 else if (isForced())
1189 m_state
= TorrentState::ForcedDownloading
;
1190 else if (m_nativeStatus
.download_payload_rate
> 0)
1191 m_state
= TorrentState::Downloading
;
1193 m_state
= TorrentState::StalledDownloading
;
1197 bool TorrentImpl::hasMetadata() const
1199 return m_torrentInfo
.isValid();
1202 bool TorrentImpl::hasMissingFiles() const
1204 return m_hasMissingFiles
;
1207 bool TorrentImpl::hasError() const
1209 return (m_nativeStatus
.errc
|| (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
));
1212 int TorrentImpl::queuePosition() const
1214 return static_cast<int>(m_nativeStatus
.queue_position
);
1217 QString
TorrentImpl::error() const
1219 if (m_nativeStatus
.errc
)
1220 return QString::fromLocal8Bit(m_nativeStatus
.errc
.message().c_str());
1222 if (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
)
1224 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1225 .arg(QString::fromLocal8Bit(m_lastFileError
.error
.message().c_str()));
1231 qlonglong
TorrentImpl::totalDownload() const
1233 return m_nativeStatus
.all_time_download
;
1236 qlonglong
TorrentImpl::totalUpload() const
1238 return m_nativeStatus
.all_time_upload
;
1241 qlonglong
TorrentImpl::activeTime() const
1243 return lt::total_seconds(m_nativeStatus
.active_duration
);
1246 qlonglong
TorrentImpl::finishedTime() const
1248 return lt::total_seconds(m_nativeStatus
.finished_duration
);
1251 qlonglong
TorrentImpl::eta() const
1253 if (isStopped()) return MAX_ETA
;
1255 const SpeedSampleAvg speedAverage
= m_payloadRateMonitor
.average();
1259 const qreal maxRatioValue
= maxRatio();
1260 const int maxSeedingTimeValue
= maxSeedingTime();
1261 const int maxInactiveSeedingTimeValue
= maxInactiveSeedingTime();
1262 if ((maxRatioValue
< 0) && (maxSeedingTimeValue
< 0) && (maxInactiveSeedingTimeValue
< 0)) return MAX_ETA
;
1264 qlonglong ratioEta
= MAX_ETA
;
1266 if ((speedAverage
.upload
> 0) && (maxRatioValue
>= 0))
1269 qlonglong realDL
= totalDownload();
1271 realDL
= wantedSize();
1273 ratioEta
= ((realDL
* maxRatioValue
) - totalUpload()) / speedAverage
.upload
;
1276 qlonglong seedingTimeEta
= MAX_ETA
;
1278 if (maxSeedingTimeValue
>= 0)
1280 seedingTimeEta
= (maxSeedingTimeValue
* 60) - finishedTime();
1281 if (seedingTimeEta
< 0)
1285 qlonglong inactiveSeedingTimeEta
= MAX_ETA
;
1287 if (maxInactiveSeedingTimeValue
>= 0)
1289 inactiveSeedingTimeEta
= (maxInactiveSeedingTimeValue
* 60) - timeSinceActivity();
1290 inactiveSeedingTimeEta
= std::max
<qlonglong
>(inactiveSeedingTimeEta
, 0);
1293 return std::min({ratioEta
, seedingTimeEta
, inactiveSeedingTimeEta
});
1296 if (!speedAverage
.download
) return MAX_ETA
;
1298 return (wantedSize() - completedSize()) / speedAverage
.download
;
1301 QVector
<qreal
> TorrentImpl::filesProgress() const
1306 const int count
= m_filesProgress
.size();
1307 Q_ASSERT(count
== filesCount());
1308 if (count
!= filesCount()) [[unlikely
]]
1311 if (m_completedFiles
.count(true) == count
)
1312 return QVector
<qreal
>(count
, 1);
1314 QVector
<qreal
> result
;
1315 result
.reserve(count
);
1316 for (int i
= 0; i
< count
; ++i
)
1318 const int64_t progress
= m_filesProgress
.at(i
);
1319 const int64_t size
= fileSize(i
);
1320 if ((size
<= 0) || (progress
== size
))
1323 result
<< (progress
/ static_cast<qreal
>(size
));
1329 int TorrentImpl::seedsCount() const
1331 return m_nativeStatus
.num_seeds
;
1334 int TorrentImpl::peersCount() const
1336 return m_nativeStatus
.num_peers
;
1339 int TorrentImpl::leechsCount() const
1341 return (m_nativeStatus
.num_peers
- m_nativeStatus
.num_seeds
);
1344 int TorrentImpl::totalSeedsCount() const
1346 return (m_nativeStatus
.num_complete
> -1) ? m_nativeStatus
.num_complete
: m_nativeStatus
.list_seeds
;
1349 int TorrentImpl::totalPeersCount() const
1351 const int peers
= m_nativeStatus
.num_complete
+ m_nativeStatus
.num_incomplete
;
1352 return (peers
> -1) ? peers
: m_nativeStatus
.list_peers
;
1355 int TorrentImpl::totalLeechersCount() const
1357 return (m_nativeStatus
.num_incomplete
> -1) ? m_nativeStatus
.num_incomplete
: (m_nativeStatus
.list_peers
- m_nativeStatus
.list_seeds
);
1360 QDateTime
TorrentImpl::lastSeenComplete() const
1362 if (m_nativeStatus
.last_seen_complete
> 0)
1363 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.last_seen_complete
);
1368 QDateTime
TorrentImpl::completedTime() const
1370 if (m_nativeStatus
.completed_time
> 0)
1371 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.completed_time
);
1376 qlonglong
TorrentImpl::timeSinceUpload() const
1378 if (m_nativeStatus
.last_upload
.time_since_epoch().count() == 0)
1380 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_upload
);
1383 qlonglong
TorrentImpl::timeSinceDownload() const
1385 if (m_nativeStatus
.last_download
.time_since_epoch().count() == 0)
1387 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_download
);
1390 qlonglong
TorrentImpl::timeSinceActivity() const
1392 const qlonglong upTime
= timeSinceUpload();
1393 const qlonglong downTime
= timeSinceDownload();
1394 return ((upTime
< 0) != (downTime
< 0))
1395 ? std::max(upTime
, downTime
)
1396 : std::min(upTime
, downTime
);
1399 int TorrentImpl::downloadLimit() const
1401 return m_downloadLimit
;;
1404 int TorrentImpl::uploadLimit() const
1406 return m_uploadLimit
;
1409 bool TorrentImpl::superSeeding() const
1411 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::super_seeding
);
1414 bool TorrentImpl::isDHTDisabled() const
1416 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_dht
);
1419 bool TorrentImpl::isPEXDisabled() const
1421 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_pex
);
1424 bool TorrentImpl::isLSDDisabled() const
1426 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_lsd
);
1429 QVector
<PeerInfo
> TorrentImpl::peers() const
1431 std::vector
<lt::peer_info
> nativePeers
;
1432 m_nativeHandle
.get_peer_info(nativePeers
);
1434 QVector
<PeerInfo
> peers
;
1435 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
1437 for (const lt::peer_info
&peer
: nativePeers
)
1438 peers
.append(PeerInfo(peer
, pieces()));
1443 QBitArray
TorrentImpl::pieces() const
1448 QBitArray
TorrentImpl::downloadingPieces() const
1450 QBitArray
result(piecesCount());
1452 std::vector
<lt::partial_piece_info
> queue
;
1453 m_nativeHandle
.get_download_queue(queue
);
1455 for (const lt::partial_piece_info
&info
: queue
)
1456 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
1461 QVector
<int> TorrentImpl::pieceAvailability() const
1463 std::vector
<int> avail
;
1464 m_nativeHandle
.piece_availability(avail
);
1466 return {avail
.cbegin(), avail
.cend()};
1469 qreal
TorrentImpl::distributedCopies() const
1471 return m_nativeStatus
.distributed_copies
;
1474 qreal
TorrentImpl::maxRatio() const
1476 if (m_ratioLimit
== USE_GLOBAL_RATIO
)
1477 return m_session
->globalMaxRatio();
1479 return m_ratioLimit
;
1482 int TorrentImpl::maxSeedingTime() const
1484 if (m_seedingTimeLimit
== USE_GLOBAL_SEEDING_TIME
)
1485 return m_session
->globalMaxSeedingMinutes();
1487 return m_seedingTimeLimit
;
1490 int TorrentImpl::maxInactiveSeedingTime() const
1492 if (m_inactiveSeedingTimeLimit
== USE_GLOBAL_INACTIVE_SEEDING_TIME
)
1493 return m_session
->globalMaxInactiveSeedingMinutes();
1495 return m_inactiveSeedingTimeLimit
;
1498 qreal
TorrentImpl::realRatio() const
1500 const int64_t upload
= m_nativeStatus
.all_time_upload
;
1501 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1502 const int64_t download
= (m_nativeStatus
.all_time_download
< (m_nativeStatus
.total_done
* 0.01))
1503 ? m_nativeStatus
.total_done
1504 : m_nativeStatus
.all_time_download
;
1507 return (upload
== 0) ? 0 : MAX_RATIO
;
1509 const qreal ratio
= upload
/ static_cast<qreal
>(download
);
1510 Q_ASSERT(ratio
>= 0);
1511 return (ratio
> MAX_RATIO
) ? MAX_RATIO
: ratio
;
1514 int TorrentImpl::uploadPayloadRate() const
1516 // workaround: suppress the speed for Stopped state
1517 return isStopped() ? 0 : m_nativeStatus
.upload_payload_rate
;
1520 int TorrentImpl::downloadPayloadRate() const
1522 // workaround: suppress the speed for Stopped state
1523 return isStopped() ? 0 : m_nativeStatus
.download_payload_rate
;
1526 qlonglong
TorrentImpl::totalPayloadUpload() const
1528 return m_nativeStatus
.total_payload_upload
;
1531 qlonglong
TorrentImpl::totalPayloadDownload() const
1533 return m_nativeStatus
.total_payload_download
;
1536 int TorrentImpl::connectionsCount() const
1538 return m_nativeStatus
.num_connections
;
1541 int TorrentImpl::connectionsLimit() const
1543 return m_nativeStatus
.connections_limit
;
1546 qlonglong
TorrentImpl::nextAnnounce() const
1548 return lt::total_seconds(m_nativeStatus
.next_announce
);
1551 qreal
TorrentImpl::popularity() const
1553 // in order to produce floating-point numbers using `std::chrono::duration_cast`,
1554 // we should use `qreal` as `Rep` to define the `months` duration
1555 using months
= std::chrono::duration
<qreal
, std::chrono::months::period
>;
1556 const auto activeMonths
= std::chrono::duration_cast
<months
>(m_nativeStatus
.active_duration
).count();
1557 return (activeMonths
> 0) ? (realRatio() / activeMonths
) : 0;
1560 void TorrentImpl::setName(const QString
&name
)
1565 deferredRequestResumeData();
1566 m_session
->handleTorrentNameChanged(this);
1570 bool TorrentImpl::setCategory(const QString
&category
)
1572 if (m_category
!= category
)
1574 if (!category
.isEmpty() && !m_session
->categories().contains(category
))
1577 const QString oldCategory
= m_category
;
1578 m_category
= category
;
1579 deferredRequestResumeData();
1580 m_session
->handleTorrentCategoryChanged(this, oldCategory
);
1584 if (!m_session
->isDisableAutoTMMWhenCategoryChanged())
1585 adjustStorageLocation();
1587 setAutoTMMEnabled(false);
1594 void TorrentImpl::forceReannounce(const int index
)
1596 m_nativeHandle
.force_reannounce(0, index
);
1599 void TorrentImpl::forceDHTAnnounce()
1601 m_nativeHandle
.force_dht_announce();
1604 void TorrentImpl::forceRecheck()
1609 m_nativeHandle
.force_recheck();
1610 // We have to force update the cached state, otherwise someone will be able to get
1611 // an incorrect one during the interval until the cached state is updated in a regular way.
1612 m_nativeStatus
.state
= lt::torrent_status::checking_resume_data
;
1614 if (m_hasMissingFiles
)
1616 m_hasMissingFiles
= false;
1619 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1620 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1621 m_nativeHandle
.resume();
1625 m_unchecked
= false;
1627 m_completedFiles
.fill(false);
1628 m_filesProgress
.fill(0);
1629 m_pieces
.fill(false);
1630 m_nativeStatus
.pieces
.clear_all();
1631 m_nativeStatus
.num_pieces
= 0;
1635 // When "force recheck" is applied on Stopped torrent, we start them to perform checking
1637 m_stopCondition
= StopCondition::FilesChecked
;
1641 void TorrentImpl::setSequentialDownload(const bool enable
)
1645 m_nativeHandle
.set_flags(lt::torrent_flags::sequential_download
);
1646 m_nativeStatus
.flags
|= lt::torrent_flags::sequential_download
; // prevent return cached value
1650 m_nativeHandle
.unset_flags(lt::torrent_flags::sequential_download
);
1651 m_nativeStatus
.flags
&= ~lt::torrent_flags::sequential_download
; // prevent return cached value
1654 deferredRequestResumeData();
1657 void TorrentImpl::setFirstLastPiecePriority(const bool enabled
)
1659 if (m_hasFirstLastPiecePriority
== enabled
)
1662 m_hasFirstLastPiecePriority
= enabled
;
1664 applyFirstLastPiecePriority(enabled
);
1666 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1667 .arg((enabled
? tr("On") : tr("Off")), name()));
1669 deferredRequestResumeData();
1672 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled
)
1674 Q_ASSERT(hasMetadata());
1676 // Download first and last pieces first for every file in the torrent
1678 auto piecePriorities
= std::vector
<lt::download_priority_t
>(m_torrentInfo
.piecesCount(), LT::toNative(DownloadPriority::Ignored
));
1680 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1681 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1682 for (int fileIndex
= 0; fileIndex
< m_filePriorities
.size(); ++fileIndex
)
1684 const DownloadPriority filePrio
= m_filePriorities
[fileIndex
];
1685 if (filePrio
<= DownloadPriority::Ignored
)
1688 // Determine the priority to set
1689 const lt::download_priority_t piecePrio
= LT::toNative(enabled
? DownloadPriority::Maximum
: filePrio
);
1690 const TorrentInfo::PieceRange pieceRange
= m_torrentInfo
.filePieces(fileIndex
);
1692 // worst case: AVI index = 1% of total file size (at the end of the file)
1693 const int numPieces
= std::ceil(fileSize(fileIndex
) * 0.01 / pieceLength());
1694 for (int i
= 0; i
< numPieces
; ++i
)
1696 piecePriorities
[pieceRange
.first() + i
] = piecePrio
;
1697 piecePriorities
[pieceRange
.last() - i
] = piecePrio
;
1700 const int firstPiece
= pieceRange
.first() + numPieces
;
1701 const int lastPiece
= pieceRange
.last() - numPieces
;
1702 for (int pieceIndex
= firstPiece
; pieceIndex
<= lastPiece
; ++pieceIndex
)
1703 piecePriorities
[pieceIndex
] = LT::toNative(filePrio
);
1706 m_nativeHandle
.prioritize_pieces(piecePriorities
);
1709 void TorrentImpl::fileSearchFinished(const Path
&savePath
, const PathList
&fileNames
)
1711 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
1712 endReceivedMetadataHandling(savePath
, fileNames
);
1715 TrackerEntryStatus
TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry
&announceEntry
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
1717 const auto it
= std::find_if(m_trackerEntryStatuses
.begin(), m_trackerEntryStatuses
.end()
1718 , [&announceEntry
](const TrackerEntryStatus
&trackerEntryStatus
)
1720 return (trackerEntryStatus
.url
== QString::fromStdString(announceEntry
.url
));
1723 Q_ASSERT(it
!= m_trackerEntryStatuses
.end());
1724 if (it
== m_trackerEntryStatuses
.end()) [[unlikely
]]
1727 #ifdef QBT_USES_LIBTORRENT2
1728 QSet
<int> btProtocols
;
1729 const auto &infoHashes
= nativeHandle().info_hashes();
1730 if (infoHashes
.has(lt::protocol_version::V1
))
1731 btProtocols
.insert(1);
1732 if (infoHashes
.has(lt::protocol_version::V2
))
1733 btProtocols
.insert(2);
1735 const QSet
<int> btProtocols
{1};
1737 ::updateTrackerEntryStatus(*it
, announceEntry
, btProtocols
, updateInfo
);
1741 void TorrentImpl::resetTrackerEntryStatuses()
1743 for (TrackerEntryStatus
&status
: m_trackerEntryStatuses
)
1745 const QString tempUrl
= status
.url
;
1746 const int tempTier
= status
.tier
;
1749 status
.url
= tempUrl
;
1750 status
.tier
= tempTier
;
1754 std::shared_ptr
<const libtorrent::torrent_info
> TorrentImpl::nativeTorrentInfo() const
1756 Q_ASSERT(!m_nativeStatus
.torrent_file
.expired());
1758 return m_nativeStatus
.torrent_file
.lock();
1761 void TorrentImpl::endReceivedMetadataHandling(const Path
&savePath
, const PathList
&fileNames
)
1763 Q_ASSERT(m_maintenanceJob
== MaintenanceJob::HandleMetadata
);
1764 if (m_maintenanceJob
!= MaintenanceJob::HandleMetadata
) [[unlikely
]]
1767 Q_ASSERT(m_filePaths
.isEmpty());
1768 if (!m_filePaths
.isEmpty()) [[unlikely
]]
1769 m_filePaths
.clear();
1771 lt::add_torrent_params
&p
= m_ltAddTorrentParams
;
1773 const std::shared_ptr
<lt::torrent_info
> metadata
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1774 m_torrentInfo
= TorrentInfo(*metadata
);
1775 m_filePriorities
.reserve(filesCount());
1776 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
1777 p
.file_priorities
= resized(p
.file_priorities
, metadata
->files().num_files()
1778 , LT::toNative(p
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
1780 m_completedFiles
.fill(static_cast<bool>(p
.flags
& lt::torrent_flags::seed_mode
), filesCount());
1781 m_filesProgress
.resize(filesCount());
1784 for (int i
= 0; i
< fileNames
.size(); ++i
)
1786 const auto nativeIndex
= nativeIndexes
.at(i
);
1788 const Path
&actualFilePath
= fileNames
.at(i
);
1789 p
.renamed_files
[nativeIndex
] = actualFilePath
.toString().toStdString();
1791 const Path filePath
= actualFilePath
.removedExtension(QB_EXT
);
1792 m_filePaths
.append(filePath
);
1794 m_filePriorities
.append(LT::fromNative(p
.file_priorities
[LT::toUnderlyingType(nativeIndex
)]));
1797 m_session
->applyFilenameFilter(fileNames
, m_filePriorities
);
1798 for (int i
= 0; i
< m_filePriorities
.size(); ++i
)
1799 p
.file_priorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(m_filePriorities
[i
]);
1801 p
.save_path
= savePath
.toString().toStdString();
1804 if (stopCondition() == StopCondition::MetadataReceived
)
1806 m_stopCondition
= StopCondition::None
;
1809 p
.flags
|= lt::torrent_flags::paused
;
1810 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1812 m_session
->handleTorrentStopped(this);
1817 // If first/last piece priority was specified when adding this torrent,
1818 // we should apply it now that we have metadata:
1819 if (m_hasFirstLastPiecePriority
)
1820 applyFirstLastPiecePriority(true);
1822 m_maintenanceJob
= MaintenanceJob::None
;
1823 prepareResumeData(p
);
1825 m_session
->handleTorrentMetadataReceived(this);
1828 void TorrentImpl::reload()
1832 m_completedFiles
.fill(false);
1833 m_filesProgress
.fill(0);
1834 m_pieces
.fill(false);
1835 m_nativeStatus
.pieces
.clear_all();
1836 m_nativeStatus
.num_pieces
= 0;
1838 const auto queuePos
= m_nativeHandle
.queue_position();
1840 m_nativeSession
->remove_torrent(m_nativeHandle
, lt::session::delete_partfile
);
1842 lt::add_torrent_params p
= m_ltAddTorrentParams
;
1843 p
.flags
|= lt::torrent_flags::update_subscribe
1844 | lt::torrent_flags::override_trackers
1845 | lt::torrent_flags::override_web_seeds
;
1849 p
.flags
|= lt::torrent_flags::paused
;
1850 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1852 else if (m_operatingMode
== TorrentOperatingMode::AutoManaged
)
1854 p
.flags
|= (lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1858 p
.flags
&= ~(lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1861 auto *const extensionData
= new ExtensionData
;
1862 p
.userdata
= LTClientData(extensionData
);
1863 m_nativeHandle
= m_nativeSession
->add_torrent(p
);
1865 m_nativeStatus
= extensionData
->status
;
1867 if (queuePos
>= lt::queue_position_t
{})
1868 m_nativeHandle
.queue_position_set(queuePos
);
1869 m_nativeStatus
.queue_position
= queuePos
;
1873 catch (const lt::system_error
&err
)
1875 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1876 .arg(id().toString(), QString::fromLocal8Bit(err
.what())));
1880 void TorrentImpl::stop()
1884 m_stopCondition
= StopCondition::None
;
1886 deferredRequestResumeData();
1887 m_session
->handleTorrentStopped(this);
1890 if (m_maintenanceJob
== MaintenanceJob::None
)
1892 setAutoManaged(false);
1893 m_nativeHandle
.pause();
1895 m_payloadRateMonitor
.reset();
1899 void TorrentImpl::start(const TorrentOperatingMode mode
)
1903 m_nativeHandle
.clear_error();
1904 m_nativeHandle
.unset_flags(lt::torrent_flags::upload_mode
);
1907 m_operatingMode
= mode
;
1909 if (m_hasMissingFiles
)
1911 m_hasMissingFiles
= false;
1912 m_isStopped
= false;
1913 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1920 m_isStopped
= false;
1921 deferredRequestResumeData();
1922 m_session
->handleTorrentStarted(this);
1925 if (m_maintenanceJob
== MaintenanceJob::None
)
1927 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1928 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1929 m_nativeHandle
.resume();
1933 void TorrentImpl::moveStorage(const Path
&newPath
, const MoveStorageContext context
)
1937 m_savePath
= newPath
;
1938 m_session
->handleTorrentSavePathChanged(this);
1942 const auto mode
= (context
== MoveStorageContext::AdjustCurrentLocation
)
1943 ? MoveStorageMode::Overwrite
: MoveStorageMode::KeepExistingFiles
;
1944 if (m_session
->addMoveTorrentStorageJob(this, newPath
, mode
, context
))
1946 if (!m_storageIsMoving
)
1948 m_storageIsMoving
= true;
1950 m_session
->handleTorrentStorageMovingStateChanged(this);
1955 void TorrentImpl::renameFile(const int index
, const Path
&path
)
1957 Q_ASSERT((index
>= 0) && (index
< filesCount()));
1958 if ((index
< 0) || (index
>= filesCount())) [[unlikely
]]
1961 const Path targetActualPath
= makeActualPath(index
, path
);
1962 doRenameFile(index
, targetActualPath
);
1965 void TorrentImpl::handleStateUpdate(const lt::torrent_status
&nativeStatus
)
1967 updateStatus(nativeStatus
);
1970 void TorrentImpl::handleQueueingModeChanged()
1975 void TorrentImpl::handleMoveStorageJobFinished(const Path
&path
, const MoveStorageContext context
, const bool hasOutstandingJob
)
1977 if (context
== MoveStorageContext::ChangeSavePath
)
1979 else if (context
== MoveStorageContext::ChangeDownloadPath
)
1980 m_downloadPath
= path
;
1981 m_storageIsMoving
= hasOutstandingJob
;
1982 m_nativeStatus
.save_path
= path
.toString().toStdString();
1984 m_session
->handleTorrentSavePathChanged(this);
1985 deferredRequestResumeData();
1987 if (!m_storageIsMoving
)
1990 m_session
->handleTorrentStorageMovingStateChanged(this);
1992 if (m_hasMissingFiles
)
1994 // it can be moved to the proper location
1995 m_hasMissingFiles
= false;
1996 m_ltAddTorrentParams
.save_path
= m_nativeStatus
.save_path
;
1997 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
2001 while ((m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2002 std::invoke(m_moveFinishedTriggers
.dequeue());
2006 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused
]] const lt::torrent_checked_alert
*p
)
2010 // The torrent is checked due to metadata received, but we should not process
2011 // this event until the torrent is reloaded using the received metadata.
2015 if (stopCondition() == StopCondition::FilesChecked
)
2018 m_statusUpdatedTriggers
.enqueue([this]()
2020 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
2022 if (!m_hasMissingFiles
)
2024 if ((progress() < 1.0) && (wantedSize() > 0))
2025 m_hasFinishedStatus
= false;
2026 else if (progress() == 1.0)
2027 m_hasFinishedStatus
= true;
2029 adjustStorageLocation();
2030 manageActualFilePaths();
2034 // torrent is internally paused using NativeTorrentExtension after files checked
2035 // so we need to resume it if there is no corresponding "stop condition" set
2036 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
2037 if (m_operatingMode
== TorrentOperatingMode::Forced
)
2038 m_nativeHandle
.resume();
2042 if (m_nativeStatus
.need_save_resume
)
2043 deferredRequestResumeData();
2045 m_session
->handleTorrentChecked(this);
2049 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused
]] const lt::torrent_finished_alert
*p
)
2051 m_hasMissingFiles
= false;
2052 if (m_hasFinishedStatus
)
2055 m_statusUpdatedTriggers
.enqueue([this]()
2057 adjustStorageLocation();
2058 manageActualFilePaths();
2060 deferredRequestResumeData();
2062 const bool recheckTorrentsOnCompletion
= Preferences::instance()->recheckTorrentsOnCompletion();
2063 if (recheckTorrentsOnCompletion
&& m_unchecked
)
2069 m_hasFinishedStatus
= true;
2071 if (isMoveInProgress() || (m_renameCount
> 0))
2072 m_moveFinishedTriggers
.enqueue([this]() { m_session
->handleTorrentFinished(this); });
2074 m_session
->handleTorrentFinished(this);
2079 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused
]] const lt::torrent_paused_alert
*p
)
2083 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused
]] const lt::torrent_resumed_alert
*p
)
2087 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert
*p
)
2089 if (m_ltAddTorrentParams
.url_seeds
!= p
->params
.url_seeds
)
2091 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2092 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2093 // than the list we usually cache, so we have to request it from the appropriate source.
2094 fetchURLSeeds([this](const QVector
<QUrl
> &urlSeeds
) { m_urlSeeds
= urlSeeds
; });
2097 if ((m_maintenanceJob
== MaintenanceJob::HandleMetadata
) && p
->params
.ti
)
2099 Q_ASSERT(m_indexMap
.isEmpty());
2101 const auto isSeedMode
= static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
);
2102 m_ltAddTorrentParams
= p
->params
;
2104 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2106 m_ltAddTorrentParams
.have_pieces
.clear();
2107 m_ltAddTorrentParams
.verified_pieces
.clear();
2109 m_nativeStatus
.torrent_file
= m_ltAddTorrentParams
.ti
;
2111 const auto metadata
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
2113 const auto &renamedFiles
= m_ltAddTorrentParams
.renamed_files
;
2114 PathList filePaths
= metadata
.filePaths();
2115 if (renamedFiles
.empty() && (m_contentLayout
!= TorrentContentLayout::Original
))
2117 const Path originalRootFolder
= Path::findRootFolder(filePaths
);
2118 const auto originalContentLayout
= (originalRootFolder
.isEmpty()
2119 ? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder
);
2120 if (m_contentLayout
!= originalContentLayout
)
2122 if (m_contentLayout
== TorrentContentLayout::NoSubfolder
)
2123 Path::stripRootFolder(filePaths
);
2125 Path::addRootFolder(filePaths
, filePaths
.at(0).removedExtension());
2129 const auto nativeIndexes
= metadata
.nativeIndexes();
2130 m_indexMap
.reserve(filePaths
.size());
2131 for (int i
= 0; i
< filePaths
.size(); ++i
)
2133 const auto nativeIndex
= nativeIndexes
.at(i
);
2134 m_indexMap
[nativeIndex
] = i
;
2136 if (const auto it
= renamedFiles
.find(nativeIndex
); it
!= renamedFiles
.cend())
2137 filePaths
[i
] = Path(it
->second
);
2140 m_session
->findIncompleteFiles(metadata
, savePath(), downloadPath(), filePaths
);
2144 prepareResumeData(p
->params
);
2148 void TorrentImpl::prepareResumeData(const lt::add_torrent_params
¶ms
)
2150 if (m_hasMissingFiles
)
2152 const auto havePieces
= m_ltAddTorrentParams
.have_pieces
;
2153 const auto unfinishedPieces
= m_ltAddTorrentParams
.unfinished_pieces
;
2154 const auto verifiedPieces
= m_ltAddTorrentParams
.verified_pieces
;
2156 // Update recent resume data but preserve existing progress
2157 m_ltAddTorrentParams
= params
;
2158 m_ltAddTorrentParams
.have_pieces
= havePieces
;
2159 m_ltAddTorrentParams
.unfinished_pieces
= unfinishedPieces
;
2160 m_ltAddTorrentParams
.verified_pieces
= verifiedPieces
;
2164 const bool preserveSeedMode
= (!hasMetadata() && (m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
));
2165 // Update recent resume data
2166 m_ltAddTorrentParams
= params
;
2167 if (preserveSeedMode
)
2168 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2171 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2172 m_ltAddTorrentParams
.flags
&= ~lt::torrent_flags::upload_mode
;
2174 const LoadTorrentParams resumeData
2176 .ltAddTorrentParams
= m_ltAddTorrentParams
,
2178 .category
= m_category
,
2180 .savePath
= (!m_useAutoTMM
? m_savePath
: Path()),
2181 .downloadPath
= (!m_useAutoTMM
? m_downloadPath
: Path()),
2182 .contentLayout
= m_contentLayout
,
2183 .operatingMode
= m_operatingMode
,
2184 .useAutoTMM
= m_useAutoTMM
,
2185 .firstLastPiecePriority
= m_hasFirstLastPiecePriority
,
2186 .hasFinishedStatus
= m_hasFinishedStatus
,
2187 .stopped
= m_isStopped
,
2188 .stopCondition
= m_stopCondition
,
2189 .addToQueueTop
= false,
2190 .ratioLimit
= m_ratioLimit
,
2191 .seedingTimeLimit
= m_seedingTimeLimit
,
2192 .inactiveSeedingTimeLimit
= m_inactiveSeedingTimeLimit
,
2193 .shareLimitAction
= m_shareLimitAction
,
2194 .sslParameters
= m_sslParams
2197 m_session
->handleTorrentResumeDataReady(this, resumeData
);
2200 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert
*p
)
2202 if (p
->error
!= lt::errors::resume_data_not_modified
)
2204 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2205 .arg(name(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::CRITICAL
);
2209 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert
*p
)
2211 // Files were probably moved or storage isn't accessible
2212 m_hasMissingFiles
= true;
2213 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2214 .arg(name(), QString::fromStdString(p
->message())), Log::WARNING
);
2217 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert
*p
)
2219 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2220 Q_ASSERT(fileIndex
>= 0);
2222 const Path newActualFilePath
{QString::fromUtf8(p
->new_name())};
2224 const Path oldFilePath
= m_filePaths
.at(fileIndex
);
2225 const Path newFilePath
= makeUserPath(newActualFilePath
);
2227 // Check if ".!qB" extension or ".unwanted" folder was just added or removed
2228 // We should compare path in a case sensitive manner even on case insensitive
2229 // platforms since it can be renamed by only changing case of some character(s)
2230 if (oldFilePath
.data() == newFilePath
.data())
2232 // Remove empty ".unwanted" folders
2233 #ifdef QBT_USES_LIBTORRENT2
2234 const Path oldActualFilePath
{QString::fromUtf8(p
->old_name())};
2236 const Path oldActualFilePath
;
2238 const Path oldActualParentPath
= oldActualFilePath
.parentPath();
2239 const Path newActualParentPath
= newActualFilePath
.parentPath();
2240 if (newActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2242 if (oldActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2245 const std::wstring winPath
= (actualStorageLocation() / newActualParentPath
).toString().toStdWString();
2246 const DWORD dwAttrs
= ::GetFileAttributesW(winPath
.c_str());
2247 ::SetFileAttributesW(winPath
.c_str(), (dwAttrs
| FILE_ATTRIBUTE_HIDDEN
));
2251 #ifdef QBT_USES_LIBTORRENT2
2252 else if (oldActualParentPath
.filename() == UNWANTED_FOLDER_NAME
)
2254 if (newActualParentPath
.filename() != UNWANTED_FOLDER_NAME
)
2255 Utils::Fs::rmdir(actualStorageLocation() / oldActualParentPath
);
2260 Utils::Fs::rmdir(actualStorageLocation() / newActualParentPath
/ Path(UNWANTED_FOLDER_NAME
));
2266 m_filePaths
[fileIndex
] = newFilePath
;
2268 // Remove empty leftover folders
2269 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2270 // be removed if they are empty
2271 Path oldParentPath
= oldFilePath
.parentPath();
2272 const Path commonBasePath
= Path::commonPath(oldParentPath
, newFilePath
.parentPath());
2273 while (oldParentPath
!= commonBasePath
)
2275 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath
);
2276 oldParentPath
= oldParentPath
.parentPath();
2281 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2282 m_moveFinishedTriggers
.takeFirst()();
2284 deferredRequestResumeData();
2287 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
*p
)
2289 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2290 Q_ASSERT(fileIndex
>= 0);
2292 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2293 .arg(name(), filePath(fileIndex
).toString(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::WARNING
);
2296 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2297 m_moveFinishedTriggers
.takeFirst()();
2299 deferredRequestResumeData();
2302 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert
*p
)
2304 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2307 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2308 Q_ASSERT(fileIndex
>= 0);
2310 m_completedFiles
.setBit(fileIndex
);
2312 const Path actualPath
= actualFilePath(fileIndex
);
2314 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2315 // only apply Mark-of-the-Web to new download files
2316 if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
2318 const Path fullpath
= actualStorageLocation() / actualPath
;
2319 Utils::OS::applyMarkOfTheWeb(fullpath
);
2321 #endif // Q_OS_MACOS || Q_OS_WIN
2323 if (m_session
->isAppendExtensionEnabled())
2325 const Path path
= filePath(fileIndex
);
2326 if (actualPath
!= path
)
2328 qDebug("Renaming %s to %s", qUtf8Printable(actualPath
.toString()), qUtf8Printable(path
.toString()));
2329 doRenameFile(fileIndex
, path
);
2334 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert
*p
)
2336 m_lastFileError
= {p
->error
, p
->op
};
2339 #ifdef QBT_USES_LIBTORRENT2
2340 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert
*)
2342 deferredRequestResumeData();
2346 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused
]] const lt::metadata_received_alert
*p
)
2348 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2350 #ifdef QBT_USES_LIBTORRENT2
2351 const InfoHash prevInfoHash
= infoHash();
2352 m_infoHash
= InfoHash(m_nativeHandle
.info_hashes());
2353 if (prevInfoHash
!= infoHash())
2354 m_session
->handleTorrentInfoHashChanged(this, prevInfoHash
);
2357 m_maintenanceJob
= MaintenanceJob::HandleMetadata
;
2358 deferredRequestResumeData();
2361 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert
*p
) const
2363 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
))
2367 void TorrentImpl::handleCategoryOptionsChanged()
2370 adjustStorageLocation();
2373 void TorrentImpl::handleAppendExtensionToggled()
2378 manageActualFilePaths();
2381 void TorrentImpl::handleUnwantedFolderToggled()
2386 manageActualFilePaths();
2389 void TorrentImpl::handleAlert(const lt::alert
*a
)
2393 #ifdef QBT_USES_LIBTORRENT2
2394 case lt::file_prio_alert::alert_type
:
2395 handleFilePrioAlert(static_cast<const lt::file_prio_alert
*>(a
));
2398 case lt::file_renamed_alert::alert_type
:
2399 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert
*>(a
));
2401 case lt::file_rename_failed_alert::alert_type
:
2402 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert
*>(a
));
2404 case lt::file_completed_alert::alert_type
:
2405 handleFileCompletedAlert(static_cast<const lt::file_completed_alert
*>(a
));
2407 case lt::file_error_alert::alert_type
:
2408 handleFileErrorAlert(static_cast<const lt::file_error_alert
*>(a
));
2410 case lt::torrent_finished_alert::alert_type
:
2411 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert
*>(a
));
2413 case lt::save_resume_data_alert::alert_type
:
2414 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert
*>(a
));
2416 case lt::save_resume_data_failed_alert::alert_type
:
2417 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert
*>(a
));
2419 case lt::torrent_paused_alert::alert_type
:
2420 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert
*>(a
));
2422 case lt::torrent_resumed_alert::alert_type
:
2423 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert
*>(a
));
2425 case lt::metadata_received_alert::alert_type
:
2426 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(a
));
2428 case lt::fastresume_rejected_alert::alert_type
:
2429 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert
*>(a
));
2431 case lt::torrent_checked_alert::alert_type
:
2432 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert
*>(a
));
2434 case lt::performance_alert::alert_type
:
2435 handlePerformanceAlert(static_cast<const lt::performance_alert
*>(a
));
2440 void TorrentImpl::manageActualFilePaths()
2442 const std::shared_ptr
<const lt::torrent_info
> nativeInfo
= nativeTorrentInfo();
2443 const lt::file_storage
&nativeFiles
= nativeInfo
->files();
2445 for (int i
= 0; i
< filesCount(); ++i
)
2447 const Path path
= filePath(i
);
2449 const auto nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
2450 const Path actualPath
{nativeFiles
.file_path(nativeIndex
)};
2451 const Path targetActualPath
= makeActualPath(i
, path
);
2452 if (actualPath
!= targetActualPath
)
2454 qDebug() << "Renaming" << actualPath
.toString() << "to" << targetActualPath
.toString();
2455 doRenameFile(i
, targetActualPath
);
2460 void TorrentImpl::adjustStorageLocation()
2462 const Path downloadPath
= this->downloadPath();
2463 const Path targetPath
= ((isFinished() || m_hasFinishedStatus
|| downloadPath
.isEmpty()) ? savePath() : downloadPath
);
2465 if ((targetPath
!= actualStorageLocation()) || isMoveInProgress())
2466 moveStorage(targetPath
, MoveStorageContext::AdjustCurrentLocation
);
2469 void TorrentImpl::doRenameFile(const int index
, const Path
&path
)
2471 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
2473 Q_ASSERT(index
>= 0);
2474 Q_ASSERT(index
< nativeIndexes
.size());
2475 if ((index
< 0) || (index
>= nativeIndexes
.size())) [[unlikely
]]
2479 m_nativeHandle
.rename_file(nativeIndexes
[index
], path
.toString().toStdString());
2482 lt::torrent_handle
TorrentImpl::nativeHandle() const
2484 return m_nativeHandle
;
2487 void TorrentImpl::setMetadata(const TorrentInfo
&torrentInfo
)
2492 m_session
->invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
]
2496 #ifdef QBT_USES_LIBTORRENT2
2497 nativeHandle
.set_metadata(torrentInfo
.nativeInfo()->info_section());
2499 const std::shared_ptr
<lt::torrent_info
> nativeInfo
= torrentInfo
.nativeInfo();
2500 nativeHandle
.set_metadata(lt::span
<const char>(nativeInfo
->metadata().get(), nativeInfo
->metadata_size()));
2503 catch (const std::exception
&) {}
2507 Torrent::StopCondition
TorrentImpl::stopCondition() const
2509 return m_stopCondition
;
2512 void TorrentImpl::setStopCondition(const StopCondition stopCondition
)
2514 if (stopCondition
== m_stopCondition
)
2520 if ((stopCondition
== StopCondition::MetadataReceived
) && hasMetadata())
2523 if ((stopCondition
== StopCondition::FilesChecked
) && hasMetadata() && !isChecking())
2526 m_stopCondition
= stopCondition
;
2529 SSLParameters
TorrentImpl::getSSLParameters() const
2534 void TorrentImpl::setSSLParameters(const SSLParameters
&sslParams
)
2536 if (sslParams
== getSSLParameters())
2539 m_sslParams
= sslParams
;
2540 applySSLParameters();
2542 deferredRequestResumeData();
2545 bool TorrentImpl::applySSLParameters()
2547 if (!m_sslParams
.isValid())
2550 m_nativeHandle
.set_ssl_certificate_buffer(m_sslParams
.certificate
.toPem().toStdString()
2551 , m_sslParams
.privateKey
.toPem().toStdString(), m_sslParams
.dhParams
.toStdString());
2555 bool TorrentImpl::isMoveInProgress() const
2557 return m_storageIsMoving
;
2560 void TorrentImpl::updateStatus(const lt::torrent_status
&nativeStatus
)
2562 const lt::torrent_status oldStatus
= std::exchange(m_nativeStatus
, nativeStatus
);
2564 if (m_nativeStatus
.num_pieces
!= oldStatus
.num_pieces
)
2569 m_payloadRateMonitor
.addSample({nativeStatus
.download_payload_rate
2570 , nativeStatus
.upload_payload_rate
});
2574 // NOTE: Don't change the order of these conditionals!
2575 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2577 m_unchecked
= false;
2578 else if (isDownloading())
2582 while (!m_statusUpdatedTriggers
.isEmpty())
2583 std::invoke(m_statusUpdatedTriggers
.dequeue());
2586 void TorrentImpl::updateProgress()
2588 Q_ASSERT(hasMetadata());
2589 if (!hasMetadata()) [[unlikely
]]
2592 Q_ASSERT(!m_filesProgress
.isEmpty());
2593 if (m_filesProgress
.isEmpty()) [[unlikely
]]
2594 m_filesProgress
.resize(filesCount());
2596 const QBitArray oldPieces
= std::exchange(m_pieces
, LT::toQBitArray(m_nativeStatus
.pieces
));
2597 const QBitArray newPieces
= m_pieces
^ oldPieces
;
2599 const int64_t pieceSize
= m_torrentInfo
.pieceLength();
2600 for (qsizetype index
= 0; index
< newPieces
.size(); ++index
)
2602 if (!newPieces
.at(index
))
2605 int64_t size
= m_torrentInfo
.pieceLength(index
);
2606 int64_t pieceOffset
= index
* pieceSize
;
2608 for (const int fileIndex
: asConst(m_torrentInfo
.fileIndicesForPiece(index
)))
2610 const int64_t fileOffsetInPiece
= pieceOffset
- m_torrentInfo
.fileOffset(fileIndex
);
2611 const int64_t add
= std::min
<int64_t>((m_torrentInfo
.fileSize(fileIndex
) - fileOffsetInPiece
), size
);
2613 m_filesProgress
[fileIndex
] += add
;
2624 void TorrentImpl::setRatioLimit(qreal limit
)
2626 if (limit
< USE_GLOBAL_RATIO
)
2627 limit
= NO_RATIO_LIMIT
;
2628 else if (limit
> MAX_RATIO
)
2631 if (m_ratioLimit
!= limit
)
2633 m_ratioLimit
= limit
;
2634 deferredRequestResumeData();
2635 m_session
->handleTorrentShareLimitChanged(this);
2639 void TorrentImpl::setSeedingTimeLimit(int limit
)
2641 if (limit
< USE_GLOBAL_SEEDING_TIME
)
2642 limit
= NO_SEEDING_TIME_LIMIT
;
2643 else if (limit
> MAX_SEEDING_TIME
)
2644 limit
= MAX_SEEDING_TIME
;
2646 if (m_seedingTimeLimit
!= limit
)
2648 m_seedingTimeLimit
= limit
;
2649 deferredRequestResumeData();
2650 m_session
->handleTorrentShareLimitChanged(this);
2654 void TorrentImpl::setInactiveSeedingTimeLimit(int limit
)
2656 if (limit
< USE_GLOBAL_INACTIVE_SEEDING_TIME
)
2657 limit
= NO_INACTIVE_SEEDING_TIME_LIMIT
;
2658 else if (limit
> MAX_INACTIVE_SEEDING_TIME
)
2659 limit
= MAX_SEEDING_TIME
;
2661 if (m_inactiveSeedingTimeLimit
!= limit
)
2663 m_inactiveSeedingTimeLimit
= limit
;
2664 deferredRequestResumeData();
2665 m_session
->handleTorrentShareLimitChanged(this);
2669 ShareLimitAction
TorrentImpl::shareLimitAction() const
2671 return m_shareLimitAction
;
2674 void TorrentImpl::setShareLimitAction(const ShareLimitAction action
)
2676 if (m_shareLimitAction
!= action
)
2678 m_shareLimitAction
= action
;
2679 deferredRequestResumeData();
2680 m_session
->handleTorrentShareLimitChanged(this);
2684 void TorrentImpl::setUploadLimit(const int limit
)
2686 const int cleanValue
= cleanLimitValue(limit
);
2687 if (cleanValue
== uploadLimit())
2690 m_uploadLimit
= cleanValue
;
2691 m_nativeHandle
.set_upload_limit(m_uploadLimit
);
2692 deferredRequestResumeData();
2695 void TorrentImpl::setDownloadLimit(const int limit
)
2697 const int cleanValue
= cleanLimitValue(limit
);
2698 if (cleanValue
== downloadLimit())
2701 m_downloadLimit
= cleanValue
;
2702 m_nativeHandle
.set_download_limit(m_downloadLimit
);
2703 deferredRequestResumeData();
2706 void TorrentImpl::setSuperSeeding(const bool enable
)
2708 if (enable
== superSeeding())
2712 m_nativeHandle
.set_flags(lt::torrent_flags::super_seeding
);
2714 m_nativeHandle
.unset_flags(lt::torrent_flags::super_seeding
);
2716 deferredRequestResumeData();
2719 void TorrentImpl::setDHTDisabled(const bool disable
)
2721 if (disable
== isDHTDisabled())
2725 m_nativeHandle
.set_flags(lt::torrent_flags::disable_dht
);
2727 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_dht
);
2729 deferredRequestResumeData();
2732 void TorrentImpl::setPEXDisabled(const bool disable
)
2734 if (disable
== isPEXDisabled())
2738 m_nativeHandle
.set_flags(lt::torrent_flags::disable_pex
);
2740 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_pex
);
2742 deferredRequestResumeData();
2745 void TorrentImpl::setLSDDisabled(const bool disable
)
2747 if (disable
== isLSDDisabled())
2751 m_nativeHandle
.set_flags(lt::torrent_flags::disable_lsd
);
2753 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_lsd
);
2755 deferredRequestResumeData();
2758 void TorrentImpl::flushCache() const
2760 m_nativeHandle
.flush_cache();
2763 QString
TorrentImpl::createMagnetURI() const
2765 QString ret
= u
"magnet:?"_s
;
2767 const SHA1Hash infoHash1
= infoHash().v1();
2768 if (infoHash1
.isValid())
2770 ret
+= u
"xt=urn:btih:" + infoHash1
.toString();
2773 const SHA256Hash infoHash2
= infoHash().v2();
2774 if (infoHash2
.isValid())
2776 if (infoHash1
.isValid())
2778 ret
+= u
"xt=urn:btmh:1220" + infoHash2
.toString();
2781 const QString displayName
= name();
2782 if (displayName
!= id().toString())
2784 ret
+= u
"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName
));
2787 for (const TrackerEntryStatus
&tracker
: asConst(trackers()))
2789 ret
+= u
"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker
.url
));
2792 for (const QUrl
&urlSeed
: asConst(urlSeeds()))
2794 ret
+= u
"&ws=" + QString::fromLatin1(urlSeed
.toEncoded());
2800 nonstd::expected
<lt::entry
, QString
> TorrentImpl::exportTorrent() const
2803 return nonstd::make_unexpected(tr("Missing metadata"));
2807 #ifdef QBT_USES_LIBTORRENT2
2808 const std::shared_ptr
<lt::torrent_info
> completeTorrentInfo
= m_nativeHandle
.torrent_file_with_hashes();
2809 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= (completeTorrentInfo
? completeTorrentInfo
: info().nativeInfo());
2811 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= info().nativeInfo();
2813 lt::create_torrent creator
{*torrentInfo
};
2815 for (const TrackerEntryStatus
&status
: asConst(trackers()))
2816 creator
.add_tracker(status
.url
.toStdString(), status
.tier
);
2818 return creator
.generate();
2820 catch (const lt::system_error
&err
)
2822 return nonstd::make_unexpected(QString::fromLocal8Bit(err
.what()));
2826 nonstd::expected
<QByteArray
, QString
> TorrentImpl::exportToBuffer() const
2828 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2829 if (!preparationResult
)
2830 return preparationResult
.get_unexpected();
2832 // usually torrent size should be smaller than 1 MB,
2833 // however there are >100 MB v2/hybrid torrent files out in the wild
2835 buffer
.reserve(1024 * 1024);
2836 lt::bencode(std::back_inserter(buffer
), preparationResult
.value());
2840 nonstd::expected
<void, QString
> TorrentImpl::exportToFile(const Path
&path
) const
2842 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2843 if (!preparationResult
)
2844 return preparationResult
.get_unexpected();
2846 const nonstd::expected
<void, QString
> saveResult
= Utils::IO::saveToFile(path
, preparationResult
.value());
2848 return saveResult
.get_unexpected();
2853 void TorrentImpl::fetchPeerInfo(std::function
<void (QVector
<PeerInfo
>)> resultHandler
) const
2855 invokeAsync([nativeHandle
= m_nativeHandle
, allPieces
= pieces()]() -> QVector
<PeerInfo
>
2859 std::vector
<lt::peer_info
> nativePeers
;
2860 nativeHandle
.get_peer_info(nativePeers
);
2861 QVector
<PeerInfo
> peers
;
2862 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
2863 for (const lt::peer_info
&peer
: nativePeers
)
2864 peers
.append(PeerInfo(peer
, allPieces
));
2867 catch (const std::exception
&) {}
2871 , std::move(resultHandler
));
2874 void TorrentImpl::fetchURLSeeds(std::function
<void (QVector
<QUrl
>)> resultHandler
) const
2876 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<QUrl
>
2880 const std::set
<std::string
> currentSeeds
= nativeHandle
.url_seeds();
2881 QVector
<QUrl
> urlSeeds
;
2882 urlSeeds
.reserve(static_cast<decltype(urlSeeds
)::size_type
>(currentSeeds
.size()));
2883 for (const std::string
&urlSeed
: currentSeeds
)
2884 urlSeeds
.append(QString::fromStdString(urlSeed
));
2887 catch (const std::exception
&) {}
2891 , std::move(resultHandler
));
2894 void TorrentImpl::fetchPieceAvailability(std::function
<void (QVector
<int>)> resultHandler
) const
2896 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<int>
2900 std::vector
<int> piecesAvailability
;
2901 nativeHandle
.piece_availability(piecesAvailability
);
2902 return QVector
<int>(piecesAvailability
.cbegin(), piecesAvailability
.cend());
2904 catch (const std::exception
&) {}
2908 , std::move(resultHandler
));
2911 void TorrentImpl::fetchDownloadingPieces(std::function
<void (QBitArray
)> resultHandler
) const
2913 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QBitArray
2917 #ifdef QBT_USES_LIBTORRENT2
2918 const std::vector
<lt::partial_piece_info
> queue
= nativeHandle
.get_download_queue();
2920 std::vector
<lt::partial_piece_info
> queue
;
2921 nativeHandle
.get_download_queue(queue
);
2924 result
.resize(torrentInfo
.piecesCount());
2925 for (const lt::partial_piece_info
&info
: queue
)
2926 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
2929 catch (const std::exception
&) {}
2933 , std::move(resultHandler
));
2936 void TorrentImpl::fetchAvailableFileFractions(std::function
<void (QVector
<qreal
>)> resultHandler
) const
2938 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QVector
<qreal
>
2940 if (!torrentInfo
.isValid() || (torrentInfo
.filesCount() <= 0))
2945 std::vector
<int> piecesAvailability
;
2946 nativeHandle
.piece_availability(piecesAvailability
);
2947 const int filesCount
= torrentInfo
.filesCount();
2948 // libtorrent returns empty array for seeding only torrents
2949 if (piecesAvailability
.empty())
2950 return QVector
<qreal
>(filesCount
, -1);
2952 QVector
<qreal
> result
;
2953 result
.reserve(filesCount
);
2954 for (int i
= 0; i
< filesCount
; ++i
)
2956 const TorrentInfo::PieceRange filePieces
= torrentInfo
.filePieces(i
);
2958 int availablePieces
= 0;
2959 for (const int piece
: filePieces
)
2960 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2962 const qreal availability
= filePieces
.isEmpty()
2963 ? 1 // the file has no pieces, so it is available by default
2964 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2965 result
.append(availability
);
2969 catch (const std::exception
&) {}
2973 , std::move(resultHandler
));
2976 void TorrentImpl::prioritizeFiles(const QVector
<DownloadPriority
> &priorities
)
2981 Q_ASSERT(priorities
.size() == filesCount());
2983 // Reset 'm_hasSeedStatus' if needed in order to react again to
2984 // 'torrent_finished_alert' and eg show tray notifications
2985 const QVector
<DownloadPriority
> oldPriorities
= filePriorities();
2986 for (int i
= 0; i
< oldPriorities
.size(); ++i
)
2988 if ((oldPriorities
[i
] == DownloadPriority::Ignored
)
2989 && (priorities
[i
] > DownloadPriority::Ignored
)
2990 && !m_completedFiles
.at(i
))
2992 m_hasFinishedStatus
= false;
2997 const int internalFilesCount
= m_torrentInfo
.nativeInfo()->files().num_files(); // including .pad files
2998 auto nativePriorities
= std::vector
<lt::download_priority_t
>(internalFilesCount
, LT::toNative(DownloadPriority::Normal
));
2999 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
3000 for (int i
= 0; i
< priorities
.size(); ++i
)
3001 nativePriorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(priorities
[i
]);
3003 qDebug() << Q_FUNC_INFO
<< "Changing files priorities...";
3004 m_nativeHandle
.prioritize_files(nativePriorities
);
3006 m_filePriorities
= priorities
;
3007 // Restore first/last piece first option if necessary
3008 if (m_hasFirstLastPiecePriority
)
3009 applyFirstLastPiecePriority(true);
3010 manageActualFilePaths();
3013 QVector
<qreal
> TorrentImpl::availableFileFractions() const
3015 Q_ASSERT(hasMetadata());
3017 const int filesCount
= this->filesCount();
3018 if (filesCount
<= 0) return {};
3020 const QVector
<int> piecesAvailability
= pieceAvailability();
3021 // libtorrent returns empty array for seeding only torrents
3022 if (piecesAvailability
.empty()) return QVector
<qreal
>(filesCount
, -1);
3025 res
.reserve(filesCount
);
3026 for (int i
= 0; i
< filesCount
; ++i
)
3028 const TorrentInfo::PieceRange filePieces
= m_torrentInfo
.filePieces(i
);
3030 int availablePieces
= 0;
3031 for (const int piece
: filePieces
)
3032 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
3034 const qreal availability
= filePieces
.isEmpty()
3035 ? 1 // the file has no pieces, so it is available by default
3036 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
3037 res
.push_back(availability
);
3042 template <typename Func
, typename Callback
>
3043 void TorrentImpl::invokeAsync(Func func
, Callback resultHandler
) const
3045 m_session
->invokeAsync([session
= m_session
3046 , func
= std::move(func
)
3047 , resultHandler
= std::move(resultHandler
)
3048 , thisTorrent
= QPointer
<const TorrentImpl
>(this)]() mutable
3050 session
->invoke([result
= func(), thisTorrent
, resultHandler
= std::move(resultHandler
)]
3053 resultHandler(result
);