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"
35 #include <libtorrent/address.hpp>
36 #include <libtorrent/alert_types.hpp>
37 #include <libtorrent/create_torrent.hpp>
38 #include <libtorrent/session.hpp>
39 #include <libtorrent/storage_defs.hpp>
40 #include <libtorrent/time.hpp>
42 #ifdef QBT_USES_LIBTORRENT2
43 #include <libtorrent/info_hash.hpp>
50 #include <QStringList>
53 #include "base/exceptions.h"
54 #include "base/global.h"
55 #include "base/logger.h"
56 #include "base/preferences.h"
57 #include "base/utils/fs.h"
58 #include "base/utils/io.h"
59 #include "base/utils/string.h"
61 #include "downloadpriority.h"
62 #include "extensiondata.h"
63 #include "loadtorrentparams.h"
64 #include "ltqbitarray.h"
65 #include "lttypecast.h"
66 #include "peeraddress.h"
68 #include "sessionimpl.h"
70 using namespace BitTorrent
;
74 lt::announce_entry
makeNativeAnnounceEntry(const QString
&url
, const int tier
)
76 lt::announce_entry entry
{url
.toStdString()};
81 #ifdef QBT_USES_LIBTORRENT2
82 void updateTrackerEntry(TrackerEntry
&trackerEntry
, const lt::announce_entry
&nativeEntry
83 , const lt::info_hash_t
&hashes
, const QMap
<TrackerEntry::Endpoint
, int> &updateInfo
)
85 void updateTrackerEntry(TrackerEntry
&trackerEntry
, const lt::announce_entry
&nativeEntry
86 , const QMap
<TrackerEntry::Endpoint
, int> &updateInfo
)
89 Q_ASSERT(trackerEntry
.url
== QString::fromStdString(nativeEntry
.url
));
91 trackerEntry
.tier
= nativeEntry
.tier
;
95 int numNotWorking
= 0;
96 QString firstTrackerMessage
;
97 QString firstErrorMessage
;
98 #ifdef QBT_USES_LIBTORRENT2
99 const auto numEndpoints
= static_cast<qsizetype
>(nativeEntry
.endpoints
.size()) * ((hashes
.has_v1() && hashes
.has_v2()) ? 2 : 1);
100 for (const lt::announce_endpoint
&endpoint
: nativeEntry
.endpoints
)
102 for (const auto protocolVersion
: {lt::protocol_version::V1
, lt::protocol_version::V2
})
104 if (hashes
.has(protocolVersion
))
106 const lt::announce_infohash
&infoHash
= endpoint
.info_hashes
[protocolVersion
];
108 TrackerEntry::EndpointStats trackerEndpoint
;
109 trackerEndpoint
.numPeers
= updateInfo
.value(endpoint
.local_endpoint
, trackerEndpoint
.numPeers
);
110 trackerEndpoint
.numSeeds
= infoHash
.scrape_complete
;
111 trackerEndpoint
.numLeeches
= infoHash
.scrape_incomplete
;
112 trackerEndpoint
.numDownloaded
= infoHash
.scrape_downloaded
;
114 if (infoHash
.updating
)
116 trackerEndpoint
.status
= TrackerEntry::Updating
;
119 else if (infoHash
.fails
> 0)
121 trackerEndpoint
.status
= TrackerEntry::NotWorking
;
124 else if (nativeEntry
.verified
)
126 trackerEndpoint
.status
= TrackerEntry::Working
;
131 trackerEndpoint
.status
= TrackerEntry::NotContacted
;
134 const QString trackerMessage
= QString::fromStdString(infoHash
.message
);
135 const QString errorMessage
= QString::fromLocal8Bit(infoHash
.last_error
.message().c_str());
136 trackerEndpoint
.message
= (!trackerMessage
.isEmpty() ? trackerMessage
: errorMessage
);
138 trackerEntry
.stats
[endpoint
.local_endpoint
][(protocolVersion
== lt::protocol_version::V1
) ? 1 : 2] = trackerEndpoint
;
139 trackerEntry
.numPeers
= std::max(trackerEntry
.numPeers
, trackerEndpoint
.numPeers
);
140 trackerEntry
.numSeeds
= std::max(trackerEntry
.numSeeds
, trackerEndpoint
.numSeeds
);
141 trackerEntry
.numLeeches
= std::max(trackerEntry
.numLeeches
, trackerEndpoint
.numLeeches
);
142 trackerEntry
.numDownloaded
= std::max(trackerEntry
.numDownloaded
, trackerEndpoint
.numDownloaded
);
144 if (firstTrackerMessage
.isEmpty())
145 firstTrackerMessage
= trackerMessage
;
146 if (firstErrorMessage
.isEmpty())
147 firstErrorMessage
= errorMessage
;
152 const auto numEndpoints
= static_cast<qsizetype
>(nativeEntry
.endpoints
.size());
153 for (const lt::announce_endpoint
&endpoint
: nativeEntry
.endpoints
)
155 TrackerEntry::EndpointStats trackerEndpoint
;
156 trackerEndpoint
.numPeers
= updateInfo
.value(endpoint
.local_endpoint
, trackerEndpoint
.numPeers
);
157 trackerEndpoint
.numSeeds
= endpoint
.scrape_complete
;
158 trackerEndpoint
.numLeeches
= endpoint
.scrape_incomplete
;
159 trackerEndpoint
.numDownloaded
= endpoint
.scrape_downloaded
;
161 if (endpoint
.updating
)
163 trackerEndpoint
.status
= TrackerEntry::Updating
;
166 else if (endpoint
.fails
> 0)
168 trackerEndpoint
.status
= TrackerEntry::NotWorking
;
171 else if (nativeEntry
.verified
)
173 trackerEndpoint
.status
= TrackerEntry::Working
;
178 trackerEndpoint
.status
= TrackerEntry::NotContacted
;
181 const QString trackerMessage
= QString::fromStdString(endpoint
.message
);
182 const QString errorMessage
= QString::fromLocal8Bit(endpoint
.last_error
.message().c_str());
183 trackerEndpoint
.message
= (!trackerMessage
.isEmpty() ? trackerMessage
: errorMessage
);
185 trackerEntry
.stats
[endpoint
.local_endpoint
][1] = trackerEndpoint
;
186 trackerEntry
.numPeers
= std::max(trackerEntry
.numPeers
, trackerEndpoint
.numPeers
);
187 trackerEntry
.numSeeds
= std::max(trackerEntry
.numSeeds
, trackerEndpoint
.numSeeds
);
188 trackerEntry
.numLeeches
= std::max(trackerEntry
.numLeeches
, trackerEndpoint
.numLeeches
);
189 trackerEntry
.numDownloaded
= std::max(trackerEntry
.numDownloaded
, trackerEndpoint
.numDownloaded
);
191 if (firstTrackerMessage
.isEmpty())
192 firstTrackerMessage
= trackerMessage
;
193 if (firstErrorMessage
.isEmpty())
194 firstErrorMessage
= errorMessage
;
198 if (numEndpoints
> 0)
202 trackerEntry
.status
= TrackerEntry::Updating
;
204 else if (numWorking
> 0)
206 trackerEntry
.status
= TrackerEntry::Working
;
207 trackerEntry
.message
= firstTrackerMessage
;
209 else if (numNotWorking
== numEndpoints
)
211 trackerEntry
.status
= TrackerEntry::NotWorking
;
212 trackerEntry
.message
= (!firstTrackerMessage
.isEmpty() ? firstTrackerMessage
: firstErrorMessage
);
217 template <typename Vector
>
218 Vector
resized(const Vector
&inVector
, const typename
Vector::size_type size
, const typename
Vector::value_type
&defaultValue
)
220 Vector outVector
= inVector
;
221 outVector
.resize(size
, defaultValue
);
225 // This is an imitation of limit normalization performed by libtorrent itself.
226 // We need perform it to keep cached values in line with the ones used by libtorrent.
227 int cleanLimitValue(const int value
)
229 return ((value
< 0) || (value
== std::numeric_limits
<int>::max())) ? 0 : value
;
235 TorrentImpl::TorrentImpl(SessionImpl
*session
, lt::session
*nativeSession
236 , const lt::torrent_handle
&nativeHandle
, const LoadTorrentParams
¶ms
)
239 , m_nativeSession(nativeSession
)
240 , m_nativeHandle(nativeHandle
)
241 #ifdef QBT_USES_LIBTORRENT2
242 , m_infoHash(m_nativeHandle
.info_hashes())
244 , m_infoHash(m_nativeHandle
.info_hash())
246 , m_name(params
.name
)
247 , m_savePath(params
.savePath
)
248 , m_downloadPath(params
.downloadPath
)
249 , m_category(params
.category
)
250 , m_tags(params
.tags
)
251 , m_ratioLimit(params
.ratioLimit
)
252 , m_seedingTimeLimit(params
.seedingTimeLimit
)
253 , m_inactiveSeedingTimeLimit(params
.inactiveSeedingTimeLimit
)
254 , m_operatingMode(params
.operatingMode
)
255 , m_contentLayout(params
.contentLayout
)
256 , m_hasFinishedStatus(params
.hasFinishedStatus
)
257 , m_hasFirstLastPiecePriority(params
.firstLastPiecePriority
)
258 , m_useAutoTMM(params
.useAutoTMM
)
259 , m_isStopped(params
.stopped
)
260 , m_ltAddTorrentParams(params
.ltAddTorrentParams
)
261 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams
.download_limit
))
262 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams
.upload_limit
))
264 if (m_ltAddTorrentParams
.ti
)
266 // Initialize it only if torrent is added with metadata.
267 // Otherwise it should be initialized in "Metadata received" handler.
268 m_torrentInfo
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
270 Q_ASSERT(m_filePaths
.isEmpty());
271 Q_ASSERT(m_indexMap
.isEmpty());
272 const int filesCount
= m_torrentInfo
.filesCount();
273 m_filePaths
.reserve(filesCount
);
274 m_indexMap
.reserve(filesCount
);
275 m_filePriorities
.reserve(filesCount
);
276 const std::vector
<lt::download_priority_t
> filePriorities
=
277 resized(m_ltAddTorrentParams
.file_priorities
, m_ltAddTorrentParams
.ti
->num_files()
278 , LT::toNative(m_ltAddTorrentParams
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
280 m_completedFiles
.fill(static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
), filesCount
);
281 m_filesProgress
.resize(filesCount
);
283 for (int i
= 0; i
< filesCount
; ++i
)
285 const lt::file_index_t nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
286 m_indexMap
[nativeIndex
] = i
;
288 const auto fileIter
= m_ltAddTorrentParams
.renamed_files
.find(nativeIndex
);
289 const Path filePath
= ((fileIter
!= m_ltAddTorrentParams
.renamed_files
.end())
290 ? Path(fileIter
->second
).removedExtension(QB_EXT
) : m_torrentInfo
.filePath(i
));
291 m_filePaths
.append(filePath
);
293 const auto priority
= LT::fromNative(filePriorities
[LT::toUnderlyingType(nativeIndex
)]);
294 m_filePriorities
.append(priority
);
298 setStopCondition(params
.stopCondition
);
300 const auto *extensionData
= static_cast<ExtensionData
*>(m_ltAddTorrentParams
.userdata
);
301 m_trackerEntries
.reserve(static_cast<decltype(m_trackerEntries
)::size_type
>(extensionData
->trackers
.size()));
302 for (const lt::announce_entry
&announceEntry
: extensionData
->trackers
)
303 m_trackerEntries
.append({QString::fromStdString(announceEntry
.url
), announceEntry
.tier
});
304 m_urlSeeds
.reserve(static_cast<decltype(m_urlSeeds
)::size_type
>(extensionData
->urlSeeds
.size()));
305 for (const std::string
&urlSeed
: extensionData
->urlSeeds
)
306 m_urlSeeds
.append(QString::fromStdString(urlSeed
));
307 m_nativeStatus
= extensionData
->status
;
315 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority
);
317 // TODO: Remove the following upgrade code in v4.4
318 // == BEGIN UPGRADE CODE ==
319 const Path spath
= actualStorageLocation();
320 for (int i
= 0; i
< filesCount(); ++i
)
322 const Path filepath
= filePath(i
);
323 // Move "unwanted" files back to their original folder
324 const Path parentRelPath
= filepath
.parentPath();
325 if (parentRelPath
.filename() == u
".unwanted")
327 const QString oldName
= filepath
.filename();
328 const Path newRelPath
= parentRelPath
.parentPath();
329 renameFile(i
, (newRelPath
/ Path(oldName
)));
331 // Remove .unwanted directory if empty
332 const Path newPath
= spath
/ newRelPath
;
333 qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath
/ Path(u
".unwanted"_s
)).toString();
334 Utils::Fs::rmdir(newPath
/ Path(u
".unwanted"_s
));
337 // == END UPGRADE CODE ==
340 TorrentImpl::~TorrentImpl() = default;
342 bool TorrentImpl::isValid() const
344 return m_nativeHandle
.is_valid();
347 InfoHash
TorrentImpl::infoHash() const
352 QString
TorrentImpl::name() const
354 if (!m_name
.isEmpty())
358 return m_torrentInfo
.name();
360 const QString name
= QString::fromStdString(m_nativeStatus
.name
);
364 return id().toString();
367 QDateTime
TorrentImpl::creationDate() const
369 return m_torrentInfo
.creationDate();
372 QString
TorrentImpl::creator() const
374 return m_torrentInfo
.creator();
377 QString
TorrentImpl::comment() const
379 return m_torrentInfo
.comment();
382 bool TorrentImpl::isPrivate() const
384 return m_torrentInfo
.isPrivate();
387 qlonglong
TorrentImpl::totalSize() const
389 return m_torrentInfo
.totalSize();
392 // size without the "don't download" files
393 qlonglong
TorrentImpl::wantedSize() const
395 return m_nativeStatus
.total_wanted
;
398 qlonglong
TorrentImpl::completedSize() const
400 return m_nativeStatus
.total_wanted_done
;
403 qlonglong
TorrentImpl::pieceLength() const
405 return m_torrentInfo
.pieceLength();
408 qlonglong
TorrentImpl::wastedSize() const
410 return (m_nativeStatus
.total_failed_bytes
+ m_nativeStatus
.total_redundant_bytes
);
413 QString
TorrentImpl::currentTracker() const
415 return QString::fromStdString(m_nativeStatus
.current_tracker
);
418 Path
TorrentImpl::savePath() const
420 return isAutoTMMEnabled() ? m_session
->categorySavePath(category()) : m_savePath
;
423 void TorrentImpl::setSavePath(const Path
&path
)
425 Q_ASSERT(!isAutoTMMEnabled());
427 const Path basePath
= m_session
->useCategoryPathsInManualMode()
428 ? m_session
->categorySavePath(category()) : m_session
->savePath();
429 const Path resolvedPath
= (path
.isAbsolute() ? path
: (basePath
/ path
));
430 if (resolvedPath
== savePath())
433 if (isFinished() || m_hasFinishedStatus
|| downloadPath().isEmpty())
435 moveStorage(resolvedPath
, MoveStorageContext::ChangeSavePath
);
439 m_savePath
= resolvedPath
;
440 m_session
->handleTorrentSavePathChanged(this);
441 m_session
->handleTorrentNeedSaveResumeData(this);
445 Path
TorrentImpl::downloadPath() const
447 return isAutoTMMEnabled() ? m_session
->categoryDownloadPath(category()) : m_downloadPath
;
450 void TorrentImpl::setDownloadPath(const Path
&path
)
452 Q_ASSERT(!isAutoTMMEnabled());
454 const Path basePath
= m_session
->useCategoryPathsInManualMode()
455 ? m_session
->categoryDownloadPath(category()) : m_session
->downloadPath();
456 const Path resolvedPath
= (path
.isEmpty() || path
.isAbsolute()) ? path
: (basePath
/ path
);
457 if (resolvedPath
== m_downloadPath
)
460 const bool isIncomplete
= !(isFinished() || m_hasFinishedStatus
);
463 moveStorage((resolvedPath
.isEmpty() ? savePath() : resolvedPath
), MoveStorageContext::ChangeDownloadPath
);
467 m_downloadPath
= resolvedPath
;
468 m_session
->handleTorrentSavePathChanged(this);
469 m_session
->handleTorrentNeedSaveResumeData(this);
473 Path
TorrentImpl::rootPath() const
478 const Path relativeRootPath
= Path::findRootFolder(filePaths());
479 if (relativeRootPath
.isEmpty())
482 return (actualStorageLocation() / relativeRootPath
);
485 Path
TorrentImpl::contentPath() const
490 if (filesCount() == 1)
491 return (actualStorageLocation() / filePath(0));
493 const Path rootPath
= this->rootPath();
494 return (rootPath
.isEmpty() ? actualStorageLocation() : rootPath
);
497 bool TorrentImpl::isAutoTMMEnabled() const
502 void TorrentImpl::setAutoTMMEnabled(bool enabled
)
504 if (m_useAutoTMM
== enabled
)
507 m_useAutoTMM
= enabled
;
510 m_savePath
= m_session
->categorySavePath(category());
511 m_downloadPath
= m_session
->categoryDownloadPath(category());
514 m_session
->handleTorrentNeedSaveResumeData(this);
515 m_session
->handleTorrentSavingModeChanged(this);
517 adjustStorageLocation();
520 Path
TorrentImpl::actualStorageLocation() const
525 return Path(m_nativeStatus
.save_path
);
528 void TorrentImpl::setAutoManaged(const bool enable
)
531 m_nativeHandle
.set_flags(lt::torrent_flags::auto_managed
);
533 m_nativeHandle
.unset_flags(lt::torrent_flags::auto_managed
);
536 Path
TorrentImpl::wantedActualPath(int index
, const Path
&path
) const
538 if (m_session
->isAppendExtensionEnabled()
539 && (fileSize(index
) > 0) && !m_completedFiles
.at(index
))
541 return path
+ QB_EXT
;
547 QVector
<TrackerEntry
> TorrentImpl::trackers() const
549 return m_trackerEntries
;
552 void TorrentImpl::addTrackers(QVector
<TrackerEntry
> trackers
)
554 trackers
.removeIf([](const TrackerEntry
&entry
) { return entry
.url
.isEmpty(); });
556 const auto newTrackers
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend())
557 - QSet
<TrackerEntry
>(m_trackerEntries
.cbegin(), m_trackerEntries
.cend());
558 if (newTrackers
.isEmpty())
561 trackers
= QVector
<TrackerEntry
>(newTrackers
.cbegin(), newTrackers
.cend());
562 for (const TrackerEntry
&tracker
: trackers
)
563 m_nativeHandle
.add_tracker(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
565 m_trackerEntries
.append(trackers
);
566 std::sort(m_trackerEntries
.begin(), m_trackerEntries
.end()
567 , [](const TrackerEntry
&lhs
, const TrackerEntry
&rhs
) { return lhs
.tier
< rhs
.tier
; });
569 m_session
->handleTorrentNeedSaveResumeData(this);
570 m_session
->handleTorrentTrackersAdded(this, trackers
);
573 void TorrentImpl::removeTrackers(const QStringList
&trackers
)
575 QStringList removedTrackers
= trackers
;
576 for (const QString
&tracker
: trackers
)
578 if (!m_trackerEntries
.removeOne({tracker
}))
579 removedTrackers
.removeOne(tracker
);
582 std::vector
<lt::announce_entry
> nativeTrackers
;
583 nativeTrackers
.reserve(m_trackerEntries
.size());
584 for (const TrackerEntry
&tracker
: asConst(m_trackerEntries
))
585 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
587 if (!removedTrackers
.isEmpty())
589 m_nativeHandle
.replace_trackers(nativeTrackers
);
591 m_session
->handleTorrentNeedSaveResumeData(this);
592 m_session
->handleTorrentTrackersRemoved(this, removedTrackers
);
596 void TorrentImpl::replaceTrackers(QVector
<TrackerEntry
> trackers
)
598 trackers
.removeIf([](const TrackerEntry
&entry
) { return entry
.url
.isEmpty(); });
599 std::sort(trackers
.begin(), trackers
.end()
600 , [](const TrackerEntry
&lhs
, const TrackerEntry
&rhs
) { return lhs
.tier
< rhs
.tier
; });
602 std::vector
<lt::announce_entry
> nativeTrackers
;
603 nativeTrackers
.reserve(trackers
.size());
604 for (const TrackerEntry
&tracker
: trackers
)
605 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
607 m_nativeHandle
.replace_trackers(nativeTrackers
);
608 m_trackerEntries
= trackers
;
610 // Clear the peer list if it's a private torrent since
611 // we do not want to keep connecting with peers from old tracker.
615 m_session
->handleTorrentNeedSaveResumeData(this);
616 m_session
->handleTorrentTrackersChanged(this);
619 QVector
<QUrl
> TorrentImpl::urlSeeds() const
624 void TorrentImpl::addUrlSeeds(const QVector
<QUrl
> &urlSeeds
)
626 m_session
->invokeAsync([urlSeeds
, session
= m_session
627 , nativeHandle
= m_nativeHandle
628 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
632 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
633 QVector
<QUrl
> currentSeeds
;
634 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
635 for (const std::string
&urlSeed
: nativeSeeds
)
636 currentSeeds
.append(QString::fromStdString(urlSeed
));
638 QVector
<QUrl
> addedUrlSeeds
;
639 addedUrlSeeds
.reserve(urlSeeds
.size());
641 for (const QUrl
&url
: urlSeeds
)
643 if (!currentSeeds
.contains(url
))
645 nativeHandle
.add_url_seed(url
.toString().toStdString());
646 addedUrlSeeds
.append(url
);
650 currentSeeds
.append(addedUrlSeeds
);
651 session
->invoke([session
, thisTorrent
, currentSeeds
, addedUrlSeeds
]
656 thisTorrent
->m_urlSeeds
= currentSeeds
;
657 if (!addedUrlSeeds
.isEmpty())
659 session
->handleTorrentNeedSaveResumeData(thisTorrent
);
660 session
->handleTorrentUrlSeedsAdded(thisTorrent
, addedUrlSeeds
);
664 catch (const std::exception
&) {}
668 void TorrentImpl::removeUrlSeeds(const QVector
<QUrl
> &urlSeeds
)
670 m_session
->invokeAsync([urlSeeds
, session
= m_session
671 , nativeHandle
= m_nativeHandle
672 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
676 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
677 QVector
<QUrl
> currentSeeds
;
678 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
679 for (const std::string
&urlSeed
: nativeSeeds
)
680 currentSeeds
.append(QString::fromStdString(urlSeed
));
682 QVector
<QUrl
> removedUrlSeeds
;
683 removedUrlSeeds
.reserve(urlSeeds
.size());
685 for (const QUrl
&url
: urlSeeds
)
687 if (currentSeeds
.removeOne(url
))
689 nativeHandle
.remove_url_seed(url
.toString().toStdString());
690 removedUrlSeeds
.append(url
);
694 session
->invoke([session
, thisTorrent
, currentSeeds
, removedUrlSeeds
]
699 thisTorrent
->m_urlSeeds
= currentSeeds
;
701 if (!removedUrlSeeds
.isEmpty())
703 session
->handleTorrentNeedSaveResumeData(thisTorrent
);
704 session
->handleTorrentUrlSeedsRemoved(thisTorrent
, removedUrlSeeds
);
708 catch (const std::exception
&) {}
712 void TorrentImpl::clearPeers()
714 m_nativeHandle
.clear_peers();
717 bool TorrentImpl::connectPeer(const PeerAddress
&peerAddress
)
720 const lt::address addr
= lt::make_address(peerAddress
.ip
.toString().toStdString(), ec
);
721 if (ec
) return false;
723 const lt::tcp::endpoint
endpoint(addr
, peerAddress
.port
);
726 m_nativeHandle
.connect_peer(endpoint
);
728 catch (const lt::system_error
&err
)
730 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
731 .arg(peerAddress
.toString(), name(), QString::fromLocal8Bit(err
.what())), Log::WARNING
);
735 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress
.toString(), name()));
739 bool TorrentImpl::needSaveResumeData() const
741 return m_nativeStatus
.need_save_resume
;
744 void TorrentImpl::saveResumeData(lt::resume_data_flags_t flags
)
746 m_nativeHandle
.save_resume_data(flags
);
747 m_session
->handleTorrentSaveResumeDataRequested(this);
750 int TorrentImpl::filesCount() const
752 return m_torrentInfo
.filesCount();
755 int TorrentImpl::piecesCount() const
757 return m_torrentInfo
.piecesCount();
760 int TorrentImpl::piecesHave() const
762 return m_nativeStatus
.num_pieces
;
765 qreal
TorrentImpl::progress() const
768 return m_nativeStatus
.progress
;
770 if (m_nativeStatus
.total_wanted
== 0)
773 if (m_nativeStatus
.total_wanted_done
== m_nativeStatus
.total_wanted
)
776 const qreal progress
= static_cast<qreal
>(m_nativeStatus
.total_wanted_done
) / m_nativeStatus
.total_wanted
;
777 if ((progress
< 0.f
) || (progress
> 1.f
))
779 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
780 .arg(name(), QString::number(m_nativeStatus
.total_wanted
), QString::number(m_nativeStatus
.total_wanted_done
))
787 QString
TorrentImpl::category() const
792 bool TorrentImpl::belongsToCategory(const QString
&category
) const
794 if (m_category
.isEmpty())
795 return category
.isEmpty();
797 if (m_category
== category
)
800 return (m_session
->isSubcategoriesEnabled() && m_category
.startsWith(category
+ u
'/'));
803 TagSet
TorrentImpl::tags() const
808 bool TorrentImpl::hasTag(const QString
&tag
) const
810 return m_tags
.contains(tag
);
813 bool TorrentImpl::addTag(const QString
&tag
)
815 if (!m_session
->isValidTag(tag
))
820 if (!m_session
->hasTag(tag
))
822 if (!m_session
->addTag(tag
))
826 m_session
->handleTorrentNeedSaveResumeData(this);
827 m_session
->handleTorrentTagAdded(this, tag
);
831 bool TorrentImpl::removeTag(const QString
&tag
)
833 if (m_tags
.remove(tag
))
835 m_session
->handleTorrentNeedSaveResumeData(this);
836 m_session
->handleTorrentTagRemoved(this, tag
);
842 void TorrentImpl::removeAllTags()
844 for (const QString
&tag
: asConst(tags()))
848 QDateTime
TorrentImpl::addedTime() const
850 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.added_time
);
853 qreal
TorrentImpl::ratioLimit() const
858 int TorrentImpl::seedingTimeLimit() const
860 return m_seedingTimeLimit
;
863 int TorrentImpl::inactiveSeedingTimeLimit() const
865 return m_inactiveSeedingTimeLimit
;
868 Path
TorrentImpl::filePath(const int index
) const
870 Q_ASSERT(index
>= 0);
871 Q_ASSERT(index
< m_filePaths
.size());
873 return m_filePaths
.value(index
, {});
876 Path
TorrentImpl::actualFilePath(const int index
) const
878 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
880 Q_ASSERT(index
>= 0);
881 Q_ASSERT(index
< nativeIndexes
.size());
882 if ((index
< 0) || (index
>= nativeIndexes
.size()))
885 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes
[index
]));
888 qlonglong
TorrentImpl::fileSize(const int index
) const
890 return m_torrentInfo
.fileSize(index
);
893 PathList
TorrentImpl::filePaths() const
898 QVector
<DownloadPriority
> TorrentImpl::filePriorities() const
900 return m_filePriorities
;
903 TorrentInfo
TorrentImpl::info() const
905 return m_torrentInfo
;
908 bool TorrentImpl::isPaused() const
913 bool TorrentImpl::isQueued() const
915 // Torrent is Queued if it isn't in Paused state but paused internally
917 && (m_nativeStatus
.flags
& lt::torrent_flags::auto_managed
)
918 && (m_nativeStatus
.flags
& lt::torrent_flags::paused
));
921 bool TorrentImpl::isChecking() const
923 return ((m_nativeStatus
.state
== lt::torrent_status::checking_files
)
924 || (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
));
927 bool TorrentImpl::isDownloading() const
931 case TorrentState::Downloading
:
932 case TorrentState::DownloadingMetadata
:
933 case TorrentState::ForcedDownloadingMetadata
:
934 case TorrentState::StalledDownloading
:
935 case TorrentState::CheckingDownloading
:
936 case TorrentState::PausedDownloading
:
937 case TorrentState::QueuedDownloading
:
938 case TorrentState::ForcedDownloading
:
947 bool TorrentImpl::isMoving() const
949 return m_state
== TorrentState::Moving
;
952 bool TorrentImpl::isUploading() const
956 case TorrentState::Uploading
:
957 case TorrentState::StalledUploading
:
958 case TorrentState::CheckingUploading
:
959 case TorrentState::QueuedUploading
:
960 case TorrentState::ForcedUploading
:
969 bool TorrentImpl::isCompleted() const
973 case TorrentState::Uploading
:
974 case TorrentState::StalledUploading
:
975 case TorrentState::CheckingUploading
:
976 case TorrentState::PausedUploading
:
977 case TorrentState::QueuedUploading
:
978 case TorrentState::ForcedUploading
:
987 bool TorrentImpl::isActive() const
991 case TorrentState::StalledDownloading
:
992 return (uploadPayloadRate() > 0);
994 case TorrentState::DownloadingMetadata
:
995 case TorrentState::ForcedDownloadingMetadata
:
996 case TorrentState::Downloading
:
997 case TorrentState::ForcedDownloading
:
998 case TorrentState::Uploading
:
999 case TorrentState::ForcedUploading
:
1000 case TorrentState::Moving
:
1010 bool TorrentImpl::isInactive() const
1015 bool TorrentImpl::isErrored() const
1017 return ((m_state
== TorrentState::MissingFiles
)
1018 || (m_state
== TorrentState::Error
));
1021 bool TorrentImpl::isFinished() const
1023 return ((m_nativeStatus
.state
== lt::torrent_status::finished
)
1024 || (m_nativeStatus
.state
== lt::torrent_status::seeding
));
1027 bool TorrentImpl::isForced() const
1029 return (!isPaused() && (m_operatingMode
== TorrentOperatingMode::Forced
));
1032 bool TorrentImpl::isSequentialDownload() const
1034 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::sequential_download
);
1037 bool TorrentImpl::hasFirstLastPiecePriority() const
1039 return m_hasFirstLastPiecePriority
;
1042 TorrentState
TorrentImpl::state() const
1047 void TorrentImpl::updateState()
1049 if (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
)
1051 m_state
= TorrentState::CheckingResumeData
;
1053 else if (isMoveInProgress())
1055 m_state
= TorrentState::Moving
;
1057 else if (hasMissingFiles())
1059 m_state
= TorrentState::MissingFiles
;
1061 else if (hasError())
1063 m_state
= TorrentState::Error
;
1065 else if (!hasMetadata())
1068 m_state
= TorrentState::PausedDownloading
;
1069 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1070 m_state
= TorrentState::QueuedDownloading
;
1072 m_state
= isForced() ? TorrentState::ForcedDownloadingMetadata
: TorrentState::DownloadingMetadata
;
1074 else if ((m_nativeStatus
.state
== lt::torrent_status::checking_files
) && !isPaused())
1076 // If the torrent is not just in the "checking" state, but is being actually checked
1077 m_state
= m_hasFinishedStatus
? TorrentState::CheckingUploading
: TorrentState::CheckingDownloading
;
1079 else if (isFinished())
1082 m_state
= TorrentState::PausedUploading
;
1083 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1084 m_state
= TorrentState::QueuedUploading
;
1085 else if (isForced())
1086 m_state
= TorrentState::ForcedUploading
;
1087 else if (m_nativeStatus
.upload_payload_rate
> 0)
1088 m_state
= TorrentState::Uploading
;
1090 m_state
= TorrentState::StalledUploading
;
1095 m_state
= TorrentState::PausedDownloading
;
1096 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1097 m_state
= TorrentState::QueuedDownloading
;
1098 else if (isForced())
1099 m_state
= TorrentState::ForcedDownloading
;
1100 else if (m_nativeStatus
.download_payload_rate
> 0)
1101 m_state
= TorrentState::Downloading
;
1103 m_state
= TorrentState::StalledDownloading
;
1107 bool TorrentImpl::hasMetadata() const
1109 return m_torrentInfo
.isValid();
1112 bool TorrentImpl::hasMissingFiles() const
1114 return m_hasMissingFiles
;
1117 bool TorrentImpl::hasError() const
1119 return (m_nativeStatus
.errc
|| (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
));
1122 int TorrentImpl::queuePosition() const
1124 return static_cast<int>(m_nativeStatus
.queue_position
);
1127 QString
TorrentImpl::error() const
1129 if (m_nativeStatus
.errc
)
1130 return QString::fromLocal8Bit(m_nativeStatus
.errc
.message().c_str());
1132 if (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
)
1134 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1135 .arg(QString::fromLocal8Bit(m_lastFileError
.error
.message().c_str()));
1141 qlonglong
TorrentImpl::totalDownload() const
1143 return m_nativeStatus
.all_time_download
;
1146 qlonglong
TorrentImpl::totalUpload() const
1148 return m_nativeStatus
.all_time_upload
;
1151 qlonglong
TorrentImpl::activeTime() const
1153 return lt::total_seconds(m_nativeStatus
.active_duration
);
1156 qlonglong
TorrentImpl::finishedTime() const
1158 return lt::total_seconds(m_nativeStatus
.finished_duration
);
1161 qlonglong
TorrentImpl::eta() const
1163 if (isPaused()) return MAX_ETA
;
1165 const SpeedSampleAvg speedAverage
= m_payloadRateMonitor
.average();
1169 const qreal maxRatioValue
= maxRatio();
1170 const int maxSeedingTimeValue
= maxSeedingTime();
1171 const int maxInactiveSeedingTimeValue
= maxInactiveSeedingTime();
1172 if ((maxRatioValue
< 0) && (maxSeedingTimeValue
< 0) && (maxInactiveSeedingTimeValue
< 0)) return MAX_ETA
;
1174 qlonglong ratioEta
= MAX_ETA
;
1176 if ((speedAverage
.upload
> 0) && (maxRatioValue
>= 0))
1179 qlonglong realDL
= totalDownload();
1181 realDL
= wantedSize();
1183 ratioEta
= ((realDL
* maxRatioValue
) - totalUpload()) / speedAverage
.upload
;
1186 qlonglong seedingTimeEta
= MAX_ETA
;
1188 if (maxSeedingTimeValue
>= 0)
1190 seedingTimeEta
= (maxSeedingTimeValue
* 60) - finishedTime();
1191 if (seedingTimeEta
< 0)
1195 qlonglong inactiveSeedingTimeEta
= MAX_ETA
;
1197 if (maxInactiveSeedingTimeValue
>= 0)
1199 inactiveSeedingTimeEta
= (maxInactiveSeedingTimeValue
* 60) - timeSinceActivity();
1200 inactiveSeedingTimeEta
= std::max
<qlonglong
>(inactiveSeedingTimeEta
, 0);
1203 return std::min({ratioEta
, seedingTimeEta
, inactiveSeedingTimeEta
});
1206 if (!speedAverage
.download
) return MAX_ETA
;
1208 return (wantedSize() - completedSize()) / speedAverage
.download
;
1211 QVector
<qreal
> TorrentImpl::filesProgress() const
1216 const int count
= m_filesProgress
.size();
1217 Q_ASSERT(count
== filesCount());
1218 if (count
!= filesCount()) [[unlikely
]]
1221 if (m_completedFiles
.count(true) == count
)
1222 return QVector
<qreal
>(count
, 1);
1224 QVector
<qreal
> result
;
1225 result
.reserve(count
);
1226 for (int i
= 0; i
< count
; ++i
)
1228 const int64_t progress
= m_filesProgress
.at(i
);
1229 const int64_t size
= fileSize(i
);
1230 if ((size
<= 0) || (progress
== size
))
1233 result
<< (progress
/ static_cast<qreal
>(size
));
1239 int TorrentImpl::seedsCount() const
1241 return m_nativeStatus
.num_seeds
;
1244 int TorrentImpl::peersCount() const
1246 return m_nativeStatus
.num_peers
;
1249 int TorrentImpl::leechsCount() const
1251 return (m_nativeStatus
.num_peers
- m_nativeStatus
.num_seeds
);
1254 int TorrentImpl::totalSeedsCount() const
1256 return (m_nativeStatus
.num_complete
> -1) ? m_nativeStatus
.num_complete
: m_nativeStatus
.list_seeds
;
1259 int TorrentImpl::totalPeersCount() const
1261 const int peers
= m_nativeStatus
.num_complete
+ m_nativeStatus
.num_incomplete
;
1262 return (peers
> -1) ? peers
: m_nativeStatus
.list_peers
;
1265 int TorrentImpl::totalLeechersCount() const
1267 return (m_nativeStatus
.num_incomplete
> -1) ? m_nativeStatus
.num_incomplete
: (m_nativeStatus
.list_peers
- m_nativeStatus
.list_seeds
);
1270 QDateTime
TorrentImpl::lastSeenComplete() const
1272 if (m_nativeStatus
.last_seen_complete
> 0)
1273 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.last_seen_complete
);
1278 QDateTime
TorrentImpl::completedTime() const
1280 if (m_nativeStatus
.completed_time
> 0)
1281 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.completed_time
);
1286 qlonglong
TorrentImpl::timeSinceUpload() const
1288 if (m_nativeStatus
.last_upload
.time_since_epoch().count() == 0)
1290 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_upload
);
1293 qlonglong
TorrentImpl::timeSinceDownload() const
1295 if (m_nativeStatus
.last_download
.time_since_epoch().count() == 0)
1297 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_download
);
1300 qlonglong
TorrentImpl::timeSinceActivity() const
1302 const qlonglong upTime
= timeSinceUpload();
1303 const qlonglong downTime
= timeSinceDownload();
1304 return ((upTime
< 0) != (downTime
< 0))
1305 ? std::max(upTime
, downTime
)
1306 : std::min(upTime
, downTime
);
1309 int TorrentImpl::downloadLimit() const
1311 return m_downloadLimit
;;
1314 int TorrentImpl::uploadLimit() const
1316 return m_uploadLimit
;
1319 bool TorrentImpl::superSeeding() const
1321 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::super_seeding
);
1324 bool TorrentImpl::isDHTDisabled() const
1326 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_dht
);
1329 bool TorrentImpl::isPEXDisabled() const
1331 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_pex
);
1334 bool TorrentImpl::isLSDDisabled() const
1336 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_lsd
);
1339 QVector
<PeerInfo
> TorrentImpl::peers() const
1341 std::vector
<lt::peer_info
> nativePeers
;
1342 m_nativeHandle
.get_peer_info(nativePeers
);
1344 QVector
<PeerInfo
> peers
;
1345 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
1347 for (const lt::peer_info
&peer
: nativePeers
)
1348 peers
.append(PeerInfo(peer
, pieces()));
1353 QBitArray
TorrentImpl::pieces() const
1358 QBitArray
TorrentImpl::downloadingPieces() const
1360 QBitArray
result(piecesCount());
1362 std::vector
<lt::partial_piece_info
> queue
;
1363 m_nativeHandle
.get_download_queue(queue
);
1365 for (const lt::partial_piece_info
&info
: queue
)
1366 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
1371 QVector
<int> TorrentImpl::pieceAvailability() const
1373 std::vector
<int> avail
;
1374 m_nativeHandle
.piece_availability(avail
);
1376 return {avail
.cbegin(), avail
.cend()};
1379 qreal
TorrentImpl::distributedCopies() const
1381 return m_nativeStatus
.distributed_copies
;
1384 qreal
TorrentImpl::maxRatio() const
1386 if (m_ratioLimit
== USE_GLOBAL_RATIO
)
1387 return m_session
->globalMaxRatio();
1389 return m_ratioLimit
;
1392 int TorrentImpl::maxSeedingTime() const
1394 if (m_seedingTimeLimit
== USE_GLOBAL_SEEDING_TIME
)
1395 return m_session
->globalMaxSeedingMinutes();
1397 return m_seedingTimeLimit
;
1400 int TorrentImpl::maxInactiveSeedingTime() const
1402 if (m_inactiveSeedingTimeLimit
== USE_GLOBAL_INACTIVE_SEEDING_TIME
)
1403 return m_session
->globalMaxInactiveSeedingMinutes();
1405 return m_inactiveSeedingTimeLimit
;
1408 qreal
TorrentImpl::realRatio() const
1410 const int64_t upload
= m_nativeStatus
.all_time_upload
;
1411 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1412 const int64_t download
= (m_nativeStatus
.all_time_download
< (m_nativeStatus
.total_done
* 0.01))
1413 ? m_nativeStatus
.total_done
1414 : m_nativeStatus
.all_time_download
;
1417 return (upload
== 0) ? 0 : MAX_RATIO
;
1419 const qreal ratio
= upload
/ static_cast<qreal
>(download
);
1420 Q_ASSERT(ratio
>= 0);
1421 return (ratio
> MAX_RATIO
) ? MAX_RATIO
: ratio
;
1424 int TorrentImpl::uploadPayloadRate() const
1426 // workaround: suppress the speed for paused state
1427 return isPaused() ? 0 : m_nativeStatus
.upload_payload_rate
;
1430 int TorrentImpl::downloadPayloadRate() const
1432 // workaround: suppress the speed for paused state
1433 return isPaused() ? 0 : m_nativeStatus
.download_payload_rate
;
1436 qlonglong
TorrentImpl::totalPayloadUpload() const
1438 return m_nativeStatus
.total_payload_upload
;
1441 qlonglong
TorrentImpl::totalPayloadDownload() const
1443 return m_nativeStatus
.total_payload_download
;
1446 int TorrentImpl::connectionsCount() const
1448 return m_nativeStatus
.num_connections
;
1451 int TorrentImpl::connectionsLimit() const
1453 return m_nativeStatus
.connections_limit
;
1456 qlonglong
TorrentImpl::nextAnnounce() const
1458 return lt::total_seconds(m_nativeStatus
.next_announce
);
1461 void TorrentImpl::setName(const QString
&name
)
1466 m_session
->handleTorrentNeedSaveResumeData(this);
1467 m_session
->handleTorrentNameChanged(this);
1471 bool TorrentImpl::setCategory(const QString
&category
)
1473 if (m_category
!= category
)
1475 if (!category
.isEmpty() && !m_session
->categories().contains(category
))
1478 const QString oldCategory
= m_category
;
1479 m_category
= category
;
1480 m_session
->handleTorrentNeedSaveResumeData(this);
1481 m_session
->handleTorrentCategoryChanged(this, oldCategory
);
1485 if (!m_session
->isDisableAutoTMMWhenCategoryChanged())
1486 adjustStorageLocation();
1488 setAutoTMMEnabled(false);
1495 void TorrentImpl::forceReannounce(const int index
)
1497 m_nativeHandle
.force_reannounce(0, index
);
1500 void TorrentImpl::forceDHTAnnounce()
1502 m_nativeHandle
.force_dht_announce();
1505 void TorrentImpl::forceRecheck()
1510 m_nativeHandle
.force_recheck();
1511 // We have to force update the cached state, otherwise someone will be able to get
1512 // an incorrect one during the interval until the cached state is updated in a regular way.
1513 m_nativeStatus
.state
= lt::torrent_status::checking_resume_data
;
1515 m_hasMissingFiles
= false;
1516 m_unchecked
= false;
1518 m_completedFiles
.fill(false);
1519 m_filesProgress
.fill(0);
1520 m_pieces
.fill(false);
1521 m_nativeStatus
.pieces
.clear_all();
1522 m_nativeStatus
.num_pieces
= 0;
1526 // When "force recheck" is applied on paused torrent, we temporarily resume it
1528 m_stopCondition
= StopCondition::FilesChecked
;
1532 void TorrentImpl::setSequentialDownload(const bool enable
)
1536 m_nativeHandle
.set_flags(lt::torrent_flags::sequential_download
);
1537 m_nativeStatus
.flags
|= lt::torrent_flags::sequential_download
; // prevent return cached value
1541 m_nativeHandle
.unset_flags(lt::torrent_flags::sequential_download
);
1542 m_nativeStatus
.flags
&= ~lt::torrent_flags::sequential_download
; // prevent return cached value
1545 m_session
->handleTorrentNeedSaveResumeData(this);
1548 void TorrentImpl::setFirstLastPiecePriority(const bool enabled
)
1550 if (m_hasFirstLastPiecePriority
== enabled
)
1553 m_hasFirstLastPiecePriority
= enabled
;
1555 applyFirstLastPiecePriority(enabled
);
1557 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1558 .arg((enabled
? tr("On") : tr("Off")), name()));
1560 m_session
->handleTorrentNeedSaveResumeData(this);
1563 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled
)
1565 Q_ASSERT(hasMetadata());
1567 // Download first and last pieces first for every file in the torrent
1569 auto piecePriorities
= std::vector
<lt::download_priority_t
>(m_torrentInfo
.piecesCount(), LT::toNative(DownloadPriority::Ignored
));
1571 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1572 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1573 for (int fileIndex
= 0; fileIndex
< m_filePriorities
.size(); ++fileIndex
)
1575 const DownloadPriority filePrio
= m_filePriorities
[fileIndex
];
1576 if (filePrio
<= DownloadPriority::Ignored
)
1579 // Determine the priority to set
1580 const lt::download_priority_t piecePrio
= LT::toNative(enabled
? DownloadPriority::Maximum
: filePrio
);
1581 const TorrentInfo::PieceRange pieceRange
= m_torrentInfo
.filePieces(fileIndex
);
1583 // worst case: AVI index = 1% of total file size (at the end of the file)
1584 const int numPieces
= std::ceil(fileSize(fileIndex
) * 0.01 / pieceLength());
1585 for (int i
= 0; i
< numPieces
; ++i
)
1587 piecePriorities
[pieceRange
.first() + i
] = piecePrio
;
1588 piecePriorities
[pieceRange
.last() - i
] = piecePrio
;
1591 const int firstPiece
= pieceRange
.first() + numPieces
;
1592 const int lastPiece
= pieceRange
.last() - numPieces
;
1593 for (int pieceIndex
= firstPiece
; pieceIndex
<= lastPiece
; ++pieceIndex
)
1594 piecePriorities
[pieceIndex
] = LT::toNative(filePrio
);
1597 m_nativeHandle
.prioritize_pieces(piecePriorities
);
1600 void TorrentImpl::fileSearchFinished(const Path
&savePath
, const PathList
&fileNames
)
1602 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
1603 endReceivedMetadataHandling(savePath
, fileNames
);
1606 TrackerEntry
TorrentImpl::updateTrackerEntry(const lt::announce_entry
&announceEntry
, const QMap
<TrackerEntry::Endpoint
, int> &updateInfo
)
1608 const auto it
= std::find_if(m_trackerEntries
.begin(), m_trackerEntries
.end()
1609 , [&announceEntry
](const TrackerEntry
&trackerEntry
)
1611 return (trackerEntry
.url
== QString::fromStdString(announceEntry
.url
));
1614 Q_ASSERT(it
!= m_trackerEntries
.end());
1615 if (it
== m_trackerEntries
.end()) [[unlikely
]]
1618 #ifdef QBT_USES_LIBTORRENT2
1619 ::updateTrackerEntry(*it
, announceEntry
, nativeHandle().info_hashes(), updateInfo
);
1621 ::updateTrackerEntry(*it
, announceEntry
, updateInfo
);
1626 std::shared_ptr
<const libtorrent::torrent_info
> TorrentImpl::nativeTorrentInfo() const
1628 if (m_nativeStatus
.torrent_file
.expired())
1629 m_nativeStatus
.torrent_file
= m_nativeHandle
.torrent_file();
1630 return m_nativeStatus
.torrent_file
.lock();
1633 void TorrentImpl::endReceivedMetadataHandling(const Path
&savePath
, const PathList
&fileNames
)
1635 Q_ASSERT(m_maintenanceJob
== MaintenanceJob::HandleMetadata
);
1636 if (m_maintenanceJob
!= MaintenanceJob::HandleMetadata
) [[unlikely
]]
1639 Q_ASSERT(m_filePaths
.isEmpty());
1640 if (!m_filePaths
.isEmpty()) [[unlikely
]]
1641 m_filePaths
.clear();
1643 lt::add_torrent_params
&p
= m_ltAddTorrentParams
;
1645 const std::shared_ptr
<lt::torrent_info
> metadata
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1646 m_torrentInfo
= TorrentInfo(*metadata
);
1647 m_filePriorities
.reserve(filesCount());
1648 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
1649 p
.file_priorities
= resized(p
.file_priorities
, metadata
->files().num_files()
1650 , LT::toNative(p
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
1652 m_completedFiles
.fill(static_cast<bool>(p
.flags
& lt::torrent_flags::seed_mode
), filesCount());
1653 m_filesProgress
.resize(filesCount());
1656 for (int i
= 0; i
< fileNames
.size(); ++i
)
1658 const auto nativeIndex
= nativeIndexes
.at(i
);
1660 const Path
&actualFilePath
= fileNames
.at(i
);
1661 p
.renamed_files
[nativeIndex
] = actualFilePath
.toString().toStdString();
1663 const Path filePath
= actualFilePath
.removedExtension(QB_EXT
);
1664 m_filePaths
.append(filePath
);
1666 lt::download_priority_t
&nativePriority
= p
.file_priorities
[LT::toUnderlyingType(nativeIndex
)];
1667 if ((nativePriority
!= lt::dont_download
) && m_session
->isFilenameExcluded(filePath
.filename()))
1668 nativePriority
= lt::dont_download
;
1669 const auto priority
= LT::fromNative(nativePriority
);
1670 m_filePriorities
.append(priority
);
1672 p
.save_path
= savePath
.toString().toStdString();
1675 if (stopCondition() == StopCondition::MetadataReceived
)
1677 m_stopCondition
= StopCondition::None
;
1680 p
.flags
|= lt::torrent_flags::paused
;
1681 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1683 m_session
->handleTorrentPaused(this);
1688 // If first/last piece priority was specified when adding this torrent,
1689 // we should apply it now that we have metadata:
1690 if (m_hasFirstLastPiecePriority
)
1691 applyFirstLastPiecePriority(true);
1693 m_maintenanceJob
= MaintenanceJob::None
;
1694 prepareResumeData(p
);
1696 m_session
->handleTorrentMetadataReceived(this);
1699 void TorrentImpl::reload()
1702 m_completedFiles
.fill(false);
1703 m_filesProgress
.fill(0);
1704 m_pieces
.fill(false);
1705 m_nativeStatus
.pieces
.clear_all();
1706 m_nativeStatus
.num_pieces
= 0;
1708 const auto queuePos
= m_nativeHandle
.queue_position();
1710 m_nativeSession
->remove_torrent(m_nativeHandle
, lt::session::delete_partfile
);
1712 lt::add_torrent_params p
= m_ltAddTorrentParams
;
1713 p
.flags
|= lt::torrent_flags::update_subscribe
1714 | lt::torrent_flags::override_trackers
1715 | lt::torrent_flags::override_web_seeds
;
1719 p
.flags
|= lt::torrent_flags::paused
;
1720 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1722 else if (m_operatingMode
== TorrentOperatingMode::AutoManaged
)
1724 p
.flags
|= (lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1728 p
.flags
&= ~(lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1731 auto *const extensionData
= new ExtensionData
;
1732 p
.userdata
= LTClientData(extensionData
);
1733 m_nativeHandle
= m_nativeSession
->add_torrent(p
);
1735 m_nativeStatus
= extensionData
->status
;
1737 if (queuePos
>= lt::queue_position_t
{})
1738 m_nativeHandle
.queue_position_set(queuePos
);
1739 m_nativeStatus
.queue_position
= queuePos
;
1743 catch (const lt::system_error
&err
)
1745 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1746 .arg(id().toString(), QString::fromLocal8Bit(err
.what())));
1749 void TorrentImpl::pause()
1753 m_stopCondition
= StopCondition::None
;
1755 m_session
->handleTorrentNeedSaveResumeData(this);
1756 m_session
->handleTorrentPaused(this);
1759 if (m_maintenanceJob
== MaintenanceJob::None
)
1761 setAutoManaged(false);
1762 m_nativeHandle
.pause();
1764 m_payloadRateMonitor
.reset();
1768 void TorrentImpl::resume(const TorrentOperatingMode mode
)
1772 m_nativeHandle
.clear_error();
1773 m_nativeHandle
.unset_flags(lt::torrent_flags::upload_mode
);
1776 m_operatingMode
= mode
;
1778 if (m_hasMissingFiles
)
1780 m_hasMissingFiles
= false;
1781 m_isStopped
= false;
1782 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1789 m_isStopped
= false;
1790 m_session
->handleTorrentNeedSaveResumeData(this);
1791 m_session
->handleTorrentResumed(this);
1794 if (m_maintenanceJob
== MaintenanceJob::None
)
1796 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1797 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1798 m_nativeHandle
.resume();
1802 void TorrentImpl::moveStorage(const Path
&newPath
, const MoveStorageContext context
)
1806 m_session
->handleTorrentSavePathChanged(this);
1810 const auto mode
= (context
== MoveStorageContext::AdjustCurrentLocation
)
1811 ? MoveStorageMode::Overwrite
: MoveStorageMode::KeepExistingFiles
;
1812 if (m_session
->addMoveTorrentStorageJob(this, newPath
, mode
, context
))
1814 if (!m_storageIsMoving
)
1816 m_storageIsMoving
= true;
1818 m_session
->handleTorrentStorageMovingStateChanged(this);
1823 void TorrentImpl::renameFile(const int index
, const Path
&path
)
1825 Q_ASSERT((index
>= 0) && (index
< filesCount()));
1826 if ((index
< 0) || (index
>= filesCount())) [[unlikely
]]
1829 const Path wantedPath
= wantedActualPath(index
, path
);
1830 doRenameFile(index
, wantedPath
);
1833 void TorrentImpl::handleStateUpdate(const lt::torrent_status
&nativeStatus
)
1835 updateStatus(nativeStatus
);
1838 void TorrentImpl::handleMoveStorageJobFinished(const Path
&path
, const MoveStorageContext context
, const bool hasOutstandingJob
)
1840 if (context
== MoveStorageContext::ChangeSavePath
)
1842 else if (context
== MoveStorageContext::ChangeDownloadPath
)
1843 m_downloadPath
= path
;
1844 m_storageIsMoving
= hasOutstandingJob
;
1845 m_nativeStatus
.save_path
= path
.toString().toStdString();
1847 m_session
->handleTorrentSavePathChanged(this);
1848 m_session
->handleTorrentNeedSaveResumeData(this);
1850 if (!m_storageIsMoving
)
1853 m_session
->handleTorrentStorageMovingStateChanged(this);
1855 if (m_hasMissingFiles
)
1857 // it can be moved to the proper location
1858 m_hasMissingFiles
= false;
1859 m_ltAddTorrentParams
.save_path
= m_nativeStatus
.save_path
;
1860 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1864 while ((m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
1865 std::invoke(m_moveFinishedTriggers
.dequeue());
1869 void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert
*p
)
1875 // The torrent is checked due to metadata received, but we should not process
1876 // this event until the torrent is reloaded using the received metadata.
1880 if (stopCondition() == StopCondition::FilesChecked
)
1883 m_statusUpdatedTriggers
.enqueue([this]()
1885 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1887 if (!m_hasMissingFiles
)
1889 if ((progress() < 1.0) && (wantedSize() > 0))
1890 m_hasFinishedStatus
= false;
1891 else if (progress() == 1.0)
1892 m_hasFinishedStatus
= true;
1894 adjustStorageLocation();
1895 manageIncompleteFiles();
1899 // torrent is internally paused using NativeTorrentExtension after files checked
1900 // so we need to resume it if there is no corresponding "stop condition" set
1901 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1902 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1903 m_nativeHandle
.resume();
1907 if (m_nativeStatus
.need_save_resume
)
1908 m_session
->handleTorrentNeedSaveResumeData(this);
1910 m_session
->handleTorrentChecked(this);
1914 void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert
*p
)
1918 m_hasMissingFiles
= false;
1919 if (m_hasFinishedStatus
)
1922 m_statusUpdatedTriggers
.enqueue([this]()
1924 adjustStorageLocation();
1925 manageIncompleteFiles();
1927 m_session
->handleTorrentNeedSaveResumeData(this);
1929 const bool recheckTorrentsOnCompletion
= Preferences::instance()->recheckTorrentsOnCompletion();
1930 if (recheckTorrentsOnCompletion
&& m_unchecked
)
1936 m_hasFinishedStatus
= true;
1938 if (isMoveInProgress() || (m_renameCount
> 0))
1939 m_moveFinishedTriggers
.enqueue([this]() { m_session
->handleTorrentFinished(this); });
1941 m_session
->handleTorrentFinished(this);
1946 void TorrentImpl::handleTorrentPausedAlert(const lt::torrent_paused_alert
*p
)
1951 void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert
*p
)
1956 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert
*p
)
1958 if (m_ltAddTorrentParams
.url_seeds
!= p
->params
.url_seeds
)
1960 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
1961 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
1962 // than the list we usually cache, so we have to request it from the appropriate source.
1963 fetchURLSeeds([this](const QVector
<QUrl
> &urlSeeds
) { m_urlSeeds
= urlSeeds
; });
1966 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
1968 Q_ASSERT(m_indexMap
.isEmpty());
1970 const auto isSeedMode
= static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
);
1971 m_ltAddTorrentParams
= p
->params
;
1973 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
1975 m_ltAddTorrentParams
.have_pieces
.clear();
1976 m_ltAddTorrentParams
.verified_pieces
.clear();
1978 TorrentInfo metadata
= TorrentInfo(*nativeTorrentInfo());
1980 const auto &renamedFiles
= m_ltAddTorrentParams
.renamed_files
;
1981 PathList filePaths
= metadata
.filePaths();
1982 if (renamedFiles
.empty() && (m_contentLayout
!= TorrentContentLayout::Original
))
1984 const Path originalRootFolder
= Path::findRootFolder(filePaths
);
1985 const auto originalContentLayout
= (originalRootFolder
.isEmpty()
1986 ? TorrentContentLayout::NoSubfolder
1987 : TorrentContentLayout::Subfolder
);
1988 if (m_contentLayout
!= originalContentLayout
)
1990 if (m_contentLayout
== TorrentContentLayout::NoSubfolder
)
1991 Path::stripRootFolder(filePaths
);
1993 Path::addRootFolder(filePaths
, filePaths
.at(0).removedExtension());
1997 const auto nativeIndexes
= metadata
.nativeIndexes();
1998 m_indexMap
.reserve(filePaths
.size());
1999 for (int i
= 0; i
< filePaths
.size(); ++i
)
2001 const auto nativeIndex
= nativeIndexes
.at(i
);
2002 m_indexMap
[nativeIndex
] = i
;
2004 if (const auto it
= renamedFiles
.find(nativeIndex
); it
!= renamedFiles
.cend())
2005 filePaths
[i
] = Path(it
->second
);
2008 m_session
->findIncompleteFiles(metadata
, savePath(), downloadPath(), filePaths
);
2012 prepareResumeData(p
->params
);
2016 void TorrentImpl::prepareResumeData(const lt::add_torrent_params
¶ms
)
2018 if (m_hasMissingFiles
)
2020 const auto havePieces
= m_ltAddTorrentParams
.have_pieces
;
2021 const auto unfinishedPieces
= m_ltAddTorrentParams
.unfinished_pieces
;
2022 const auto verifiedPieces
= m_ltAddTorrentParams
.verified_pieces
;
2024 // Update recent resume data but preserve existing progress
2025 m_ltAddTorrentParams
= params
;
2026 m_ltAddTorrentParams
.have_pieces
= havePieces
;
2027 m_ltAddTorrentParams
.unfinished_pieces
= unfinishedPieces
;
2028 m_ltAddTorrentParams
.verified_pieces
= verifiedPieces
;
2032 const bool preserveSeedMode
= (!hasMetadata() && (m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
));
2033 // Update recent resume data
2034 m_ltAddTorrentParams
= params
;
2035 if (preserveSeedMode
)
2036 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2039 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2040 m_ltAddTorrentParams
.flags
&= ~lt::torrent_flags::upload_mode
;
2042 LoadTorrentParams resumeData
;
2043 resumeData
.name
= m_name
;
2044 resumeData
.category
= m_category
;
2045 resumeData
.tags
= m_tags
;
2046 resumeData
.contentLayout
= m_contentLayout
;
2047 resumeData
.ratioLimit
= m_ratioLimit
;
2048 resumeData
.seedingTimeLimit
= m_seedingTimeLimit
;
2049 resumeData
.inactiveSeedingTimeLimit
= m_inactiveSeedingTimeLimit
;
2050 resumeData
.firstLastPiecePriority
= m_hasFirstLastPiecePriority
;
2051 resumeData
.hasFinishedStatus
= m_hasFinishedStatus
;
2052 resumeData
.stopped
= m_isStopped
;
2053 resumeData
.stopCondition
= m_stopCondition
;
2054 resumeData
.operatingMode
= m_operatingMode
;
2055 resumeData
.ltAddTorrentParams
= m_ltAddTorrentParams
;
2056 resumeData
.useAutoTMM
= m_useAutoTMM
;
2057 if (!resumeData
.useAutoTMM
)
2059 resumeData
.savePath
= m_savePath
;
2060 resumeData
.downloadPath
= m_downloadPath
;
2063 m_session
->handleTorrentResumeDataReady(this, resumeData
);
2066 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert
*p
)
2068 if (p
->error
!= lt::errors::resume_data_not_modified
)
2070 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2071 .arg(name(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::CRITICAL
);
2074 m_session
->handleTorrentSaveResumeDataFailed(this);
2077 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert
*p
)
2079 // Files were probably moved or storage isn't accessible
2080 m_hasMissingFiles
= true;
2081 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2082 .arg(name(), QString::fromStdString(p
->message())), Log::WARNING
);
2085 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert
*p
)
2087 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2088 Q_ASSERT(fileIndex
>= 0);
2090 // Remove empty leftover folders
2091 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2092 // be removed if they are empty
2093 const Path oldFilePath
= m_filePaths
.at(fileIndex
);
2094 const Path newFilePath
= Path(QString::fromUtf8(p
->new_name())).removedExtension(QB_EXT
);
2096 // Check if ".!qB" extension was just added or removed
2097 // We should compare path in a case sensitive manner even on case insensitive
2098 // platforms since it can be renamed by only changing case of some character(s)
2099 if (oldFilePath
.data() != newFilePath
.data())
2101 m_filePaths
[fileIndex
] = newFilePath
;
2103 Path oldParentPath
= oldFilePath
.parentPath();
2104 const Path commonBasePath
= Path::commonPath(oldParentPath
, newFilePath
.parentPath());
2105 while (oldParentPath
!= commonBasePath
)
2107 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath
);
2108 oldParentPath
= oldParentPath
.parentPath();
2113 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2114 m_moveFinishedTriggers
.takeFirst()();
2116 m_session
->handleTorrentNeedSaveResumeData(this);
2119 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
*p
)
2121 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2122 Q_ASSERT(fileIndex
>= 0);
2124 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2125 .arg(name(), filePath(fileIndex
).toString(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::WARNING
);
2128 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2129 m_moveFinishedTriggers
.takeFirst()();
2131 m_session
->handleTorrentNeedSaveResumeData(this);
2134 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert
*p
)
2136 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2139 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2140 Q_ASSERT(fileIndex
>= 0);
2142 m_completedFiles
.setBit(fileIndex
);
2144 if (m_session
->isAppendExtensionEnabled())
2146 const Path path
= filePath(fileIndex
);
2147 const Path actualPath
= actualFilePath(fileIndex
);
2148 if (actualPath
!= path
)
2150 qDebug("Renaming %s to %s", qUtf8Printable(actualPath
.toString()), qUtf8Printable(path
.toString()));
2151 doRenameFile(fileIndex
, path
);
2156 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert
*p
)
2158 m_lastFileError
= {p
->error
, p
->op
};
2161 #ifdef QBT_USES_LIBTORRENT2
2162 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert
*)
2164 m_session
->handleTorrentNeedSaveResumeData(this);
2168 void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
*p
)
2171 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2173 #ifdef QBT_USES_LIBTORRENT2
2174 const InfoHash prevInfoHash
= infoHash();
2175 m_infoHash
= InfoHash(m_nativeHandle
.info_hashes());
2176 if (prevInfoHash
!= infoHash())
2177 m_session
->handleTorrentInfoHashChanged(this, prevInfoHash
);
2180 m_maintenanceJob
= MaintenanceJob::HandleMetadata
;
2181 m_session
->handleTorrentNeedSaveResumeData(this);
2184 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert
*p
) const
2186 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
))
2190 void TorrentImpl::handleCategoryOptionsChanged()
2193 adjustStorageLocation();
2196 void TorrentImpl::handleAppendExtensionToggled()
2198 if (!hasMetadata()) return;
2200 manageIncompleteFiles();
2203 void TorrentImpl::handleAlert(const lt::alert
*a
)
2207 #ifdef QBT_USES_LIBTORRENT2
2208 case lt::file_prio_alert::alert_type
:
2209 handleFilePrioAlert(static_cast<const lt::file_prio_alert
*>(a
));
2212 case lt::file_renamed_alert::alert_type
:
2213 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert
*>(a
));
2215 case lt::file_rename_failed_alert::alert_type
:
2216 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert
*>(a
));
2218 case lt::file_completed_alert::alert_type
:
2219 handleFileCompletedAlert(static_cast<const lt::file_completed_alert
*>(a
));
2221 case lt::file_error_alert::alert_type
:
2222 handleFileErrorAlert(static_cast<const lt::file_error_alert
*>(a
));
2224 case lt::torrent_finished_alert::alert_type
:
2225 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert
*>(a
));
2227 case lt::save_resume_data_alert::alert_type
:
2228 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert
*>(a
));
2230 case lt::save_resume_data_failed_alert::alert_type
:
2231 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert
*>(a
));
2233 case lt::torrent_paused_alert::alert_type
:
2234 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert
*>(a
));
2236 case lt::torrent_resumed_alert::alert_type
:
2237 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert
*>(a
));
2239 case lt::metadata_received_alert::alert_type
:
2240 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(a
));
2242 case lt::fastresume_rejected_alert::alert_type
:
2243 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert
*>(a
));
2245 case lt::torrent_checked_alert::alert_type
:
2246 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert
*>(a
));
2248 case lt::performance_alert::alert_type
:
2249 handlePerformanceAlert(static_cast<const lt::performance_alert
*>(a
));
2254 void TorrentImpl::manageIncompleteFiles()
2256 const std::shared_ptr
<const lt::torrent_info
> nativeInfo
= nativeTorrentInfo();
2257 const lt::file_storage
&nativeFiles
= nativeInfo
->files();
2259 for (int i
= 0; i
< filesCount(); ++i
)
2261 const Path path
= filePath(i
);
2263 const auto nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
2264 const Path actualPath
{nativeFiles
.file_path(nativeIndex
)};
2265 const Path wantedPath
= wantedActualPath(i
, path
);
2266 if (actualPath
!= wantedPath
)
2268 qDebug() << "Renaming" << actualPath
.toString() << "to" << wantedPath
.toString();
2269 doRenameFile(i
, wantedPath
);
2274 void TorrentImpl::adjustStorageLocation()
2276 const Path downloadPath
= this->downloadPath();
2277 const Path targetPath
= ((isFinished() || m_hasFinishedStatus
|| downloadPath
.isEmpty()) ? savePath() : downloadPath
);
2279 if ((targetPath
!= actualStorageLocation()) || isMoveInProgress())
2280 moveStorage(targetPath
, MoveStorageContext::AdjustCurrentLocation
);
2283 void TorrentImpl::doRenameFile(int index
, const Path
&path
)
2285 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
2287 Q_ASSERT(index
>= 0);
2288 Q_ASSERT(index
< nativeIndexes
.size());
2289 if ((index
< 0) || (index
>= nativeIndexes
.size())) [[unlikely
]]
2293 m_nativeHandle
.rename_file(nativeIndexes
[index
], path
.toString().toStdString());
2296 lt::torrent_handle
TorrentImpl::nativeHandle() const
2298 return m_nativeHandle
;
2301 void TorrentImpl::setMetadata(const TorrentInfo
&torrentInfo
)
2306 m_session
->invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
]
2310 #ifdef QBT_USES_LIBTORRENT2
2311 nativeHandle
.set_metadata(torrentInfo
.nativeInfo()->info_section());
2313 const std::shared_ptr
<lt::torrent_info
> nativeInfo
= torrentInfo
.nativeInfo();
2314 nativeHandle
.set_metadata(lt::span
<const char>(nativeInfo
->metadata().get(), nativeInfo
->metadata_size()));
2317 catch (const std::exception
&) {}
2321 Torrent::StopCondition
TorrentImpl::stopCondition() const
2323 return m_stopCondition
;
2326 void TorrentImpl::setStopCondition(const StopCondition stopCondition
)
2328 if (stopCondition
== m_stopCondition
)
2334 if ((stopCondition
== StopCondition::MetadataReceived
) && hasMetadata())
2337 if ((stopCondition
== StopCondition::FilesChecked
) && hasMetadata() && !isChecking())
2340 m_stopCondition
= stopCondition
;
2343 bool TorrentImpl::isMoveInProgress() const
2345 return m_storageIsMoving
;
2348 void TorrentImpl::updateStatus(const lt::torrent_status
&nativeStatus
)
2350 const lt::torrent_status oldStatus
= std::exchange(m_nativeStatus
, nativeStatus
);
2352 if (m_nativeStatus
.num_pieces
!= oldStatus
.num_pieces
)
2357 m_payloadRateMonitor
.addSample({nativeStatus
.download_payload_rate
2358 , nativeStatus
.upload_payload_rate
});
2362 // NOTE: Don't change the order of these conditionals!
2363 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2365 m_unchecked
= false;
2366 else if (isDownloading())
2370 while (!m_statusUpdatedTriggers
.isEmpty())
2371 std::invoke(m_statusUpdatedTriggers
.dequeue());
2374 void TorrentImpl::updateProgress()
2376 Q_ASSERT(hasMetadata());
2377 if (!hasMetadata()) [[unlikely
]]
2380 Q_ASSERT(!m_filesProgress
.isEmpty());
2381 if (m_filesProgress
.isEmpty()) [[unlikely
]]
2382 m_filesProgress
.resize(filesCount());
2384 const QBitArray oldPieces
= std::exchange(m_pieces
, LT::toQBitArray(m_nativeStatus
.pieces
));
2385 const QBitArray newPieces
= m_pieces
^ oldPieces
;
2387 const int64_t pieceSize
= m_torrentInfo
.pieceLength();
2388 for (qsizetype index
= 0; index
< newPieces
.size(); ++index
)
2390 if (!newPieces
.at(index
))
2393 int64_t size
= m_torrentInfo
.pieceLength(index
);
2394 int64_t pieceOffset
= index
* pieceSize
;
2396 for (const int fileIndex
: asConst(m_torrentInfo
.fileIndicesForPiece(index
)))
2398 const int64_t fileOffsetInPiece
= pieceOffset
- m_torrentInfo
.fileOffset(fileIndex
);
2399 const int64_t add
= std::min
<int64_t>((m_torrentInfo
.fileSize(fileIndex
) - fileOffsetInPiece
), size
);
2401 m_filesProgress
[fileIndex
] += add
;
2412 void TorrentImpl::setRatioLimit(qreal limit
)
2414 if (limit
< USE_GLOBAL_RATIO
)
2415 limit
= NO_RATIO_LIMIT
;
2416 else if (limit
> MAX_RATIO
)
2419 if (m_ratioLimit
!= limit
)
2421 m_ratioLimit
= limit
;
2422 m_session
->handleTorrentNeedSaveResumeData(this);
2423 m_session
->handleTorrentShareLimitChanged(this);
2427 void TorrentImpl::setSeedingTimeLimit(int limit
)
2429 if (limit
< USE_GLOBAL_SEEDING_TIME
)
2430 limit
= NO_SEEDING_TIME_LIMIT
;
2431 else if (limit
> MAX_SEEDING_TIME
)
2432 limit
= MAX_SEEDING_TIME
;
2434 if (m_seedingTimeLimit
!= limit
)
2436 m_seedingTimeLimit
= limit
;
2437 m_session
->handleTorrentNeedSaveResumeData(this);
2438 m_session
->handleTorrentShareLimitChanged(this);
2442 void TorrentImpl::setInactiveSeedingTimeLimit(int limit
)
2444 if (limit
< USE_GLOBAL_INACTIVE_SEEDING_TIME
)
2445 limit
= NO_INACTIVE_SEEDING_TIME_LIMIT
;
2446 else if (limit
> MAX_INACTIVE_SEEDING_TIME
)
2447 limit
= MAX_SEEDING_TIME
;
2449 if (m_inactiveSeedingTimeLimit
!= limit
)
2451 m_inactiveSeedingTimeLimit
= limit
;
2452 m_session
->handleTorrentNeedSaveResumeData(this);
2453 m_session
->handleTorrentShareLimitChanged(this);
2457 void TorrentImpl::setUploadLimit(const int limit
)
2459 const int cleanValue
= cleanLimitValue(limit
);
2460 if (cleanValue
== uploadLimit())
2463 m_uploadLimit
= cleanValue
;
2464 m_nativeHandle
.set_upload_limit(m_uploadLimit
);
2465 m_session
->handleTorrentNeedSaveResumeData(this);
2468 void TorrentImpl::setDownloadLimit(const int limit
)
2470 const int cleanValue
= cleanLimitValue(limit
);
2471 if (cleanValue
== downloadLimit())
2474 m_downloadLimit
= cleanValue
;
2475 m_nativeHandle
.set_download_limit(m_downloadLimit
);
2476 m_session
->handleTorrentNeedSaveResumeData(this);
2479 void TorrentImpl::setSuperSeeding(const bool enable
)
2481 if (enable
== superSeeding())
2485 m_nativeHandle
.set_flags(lt::torrent_flags::super_seeding
);
2487 m_nativeHandle
.unset_flags(lt::torrent_flags::super_seeding
);
2489 m_session
->handleTorrentNeedSaveResumeData(this);
2492 void TorrentImpl::setDHTDisabled(const bool disable
)
2494 if (disable
== isDHTDisabled())
2498 m_nativeHandle
.set_flags(lt::torrent_flags::disable_dht
);
2500 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_dht
);
2502 m_session
->handleTorrentNeedSaveResumeData(this);
2505 void TorrentImpl::setPEXDisabled(const bool disable
)
2507 if (disable
== isPEXDisabled())
2511 m_nativeHandle
.set_flags(lt::torrent_flags::disable_pex
);
2513 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_pex
);
2515 m_session
->handleTorrentNeedSaveResumeData(this);
2518 void TorrentImpl::setLSDDisabled(const bool disable
)
2520 if (disable
== isLSDDisabled())
2524 m_nativeHandle
.set_flags(lt::torrent_flags::disable_lsd
);
2526 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_lsd
);
2528 m_session
->handleTorrentNeedSaveResumeData(this);
2531 void TorrentImpl::flushCache() const
2533 m_nativeHandle
.flush_cache();
2536 QString
TorrentImpl::createMagnetURI() const
2538 QString ret
= u
"magnet:?"_s
;
2540 const SHA1Hash infoHash1
= infoHash().v1();
2541 if (infoHash1
.isValid())
2543 ret
+= u
"xt=urn:btih:" + infoHash1
.toString();
2546 const SHA256Hash infoHash2
= infoHash().v2();
2547 if (infoHash2
.isValid())
2549 if (infoHash1
.isValid())
2551 ret
+= u
"xt=urn:btmh:1220" + infoHash2
.toString();
2554 const QString displayName
= name();
2555 if (displayName
!= id().toString())
2557 ret
+= u
"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName
));
2560 for (const TrackerEntry
&tracker
: asConst(trackers()))
2562 ret
+= u
"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker
.url
));
2565 for (const QUrl
&urlSeed
: asConst(urlSeeds()))
2567 ret
+= u
"&ws=" + QString::fromLatin1(urlSeed
.toEncoded());
2573 nonstd::expected
<lt::entry
, QString
> TorrentImpl::exportTorrent() const
2576 return nonstd::make_unexpected(tr("Missing metadata"));
2580 #ifdef QBT_USES_LIBTORRENT2
2581 const std::shared_ptr
<lt::torrent_info
> completeTorrentInfo
= m_nativeHandle
.torrent_file_with_hashes();
2582 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= (completeTorrentInfo
? completeTorrentInfo
: info().nativeInfo());
2584 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= info().nativeInfo();
2586 lt::create_torrent creator
{*torrentInfo
};
2588 for (const TrackerEntry
&entry
: asConst(trackers()))
2589 creator
.add_tracker(entry
.url
.toStdString(), entry
.tier
);
2591 return creator
.generate();
2593 catch (const lt::system_error
&err
)
2595 return nonstd::make_unexpected(QString::fromLocal8Bit(err
.what()));
2599 nonstd::expected
<QByteArray
, QString
> TorrentImpl::exportToBuffer() const
2601 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2602 if (!preparationResult
)
2603 return preparationResult
.get_unexpected();
2605 // usually torrent size should be smaller than 1 MB,
2606 // however there are >100 MB v2/hybrid torrent files out in the wild
2608 buffer
.reserve(1024 * 1024);
2609 lt::bencode(std::back_inserter(buffer
), preparationResult
.value());
2613 nonstd::expected
<void, QString
> TorrentImpl::exportToFile(const Path
&path
) const
2615 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2616 if (!preparationResult
)
2617 return preparationResult
.get_unexpected();
2619 const nonstd::expected
<void, QString
> saveResult
= Utils::IO::saveToFile(path
, preparationResult
.value());
2621 return saveResult
.get_unexpected();
2626 void TorrentImpl::fetchPeerInfo(std::function
<void (QVector
<PeerInfo
>)> resultHandler
) const
2628 invokeAsync([nativeHandle
= m_nativeHandle
, allPieces
= pieces()]() -> QVector
<PeerInfo
>
2632 std::vector
<lt::peer_info
> nativePeers
;
2633 nativeHandle
.get_peer_info(nativePeers
);
2634 QVector
<PeerInfo
> peers
;
2635 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
2636 for (const lt::peer_info
&peer
: nativePeers
)
2637 peers
.append(PeerInfo(peer
, allPieces
));
2640 catch (const std::exception
&) {}
2644 , std::move(resultHandler
));
2647 void TorrentImpl::fetchURLSeeds(std::function
<void (QVector
<QUrl
>)> resultHandler
) const
2649 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<QUrl
>
2653 const std::set
<std::string
> currentSeeds
= nativeHandle
.url_seeds();
2654 QVector
<QUrl
> urlSeeds
;
2655 urlSeeds
.reserve(static_cast<decltype(urlSeeds
)::size_type
>(currentSeeds
.size()));
2656 for (const std::string
&urlSeed
: currentSeeds
)
2657 urlSeeds
.append(QString::fromStdString(urlSeed
));
2660 catch (const std::exception
&) {}
2664 , std::move(resultHandler
));
2667 void TorrentImpl::fetchPieceAvailability(std::function
<void (QVector
<int>)> resultHandler
) const
2669 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<int>
2673 std::vector
<int> piecesAvailability
;
2674 nativeHandle
.piece_availability(piecesAvailability
);
2675 return QVector
<int>(piecesAvailability
.cbegin(), piecesAvailability
.cend());
2677 catch (const std::exception
&) {}
2681 , std::move(resultHandler
));
2684 void TorrentImpl::fetchDownloadingPieces(std::function
<void (QBitArray
)> resultHandler
) const
2686 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QBitArray
2690 #ifdef QBT_USES_LIBTORRENT2
2691 const std::vector
<lt::partial_piece_info
> queue
= nativeHandle
.get_download_queue();
2693 std::vector
<lt::partial_piece_info
> queue
;
2694 nativeHandle
.get_download_queue(queue
);
2697 result
.resize(torrentInfo
.piecesCount());
2698 for (const lt::partial_piece_info
&info
: queue
)
2699 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
2702 catch (const std::exception
&) {}
2706 , std::move(resultHandler
));
2709 void TorrentImpl::fetchAvailableFileFractions(std::function
<void (QVector
<qreal
>)> resultHandler
) const
2711 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QVector
<qreal
>
2713 if (!torrentInfo
.isValid() || (torrentInfo
.filesCount() <= 0))
2718 std::vector
<int> piecesAvailability
;
2719 nativeHandle
.piece_availability(piecesAvailability
);
2720 const int filesCount
= torrentInfo
.filesCount();
2721 // libtorrent returns empty array for seeding only torrents
2722 if (piecesAvailability
.empty())
2723 return QVector
<qreal
>(filesCount
, -1);
2725 QVector
<qreal
> result
;
2726 result
.reserve(filesCount
);
2727 for (int i
= 0; i
< filesCount
; ++i
)
2729 const TorrentInfo::PieceRange filePieces
= torrentInfo
.filePieces(i
);
2731 int availablePieces
= 0;
2732 for (const int piece
: filePieces
)
2733 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2735 const qreal availability
= filePieces
.isEmpty()
2736 ? 1 // the file has no pieces, so it is available by default
2737 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2738 result
.append(availability
);
2742 catch (const std::exception
&) {}
2746 , std::move(resultHandler
));
2749 void TorrentImpl::prioritizeFiles(const QVector
<DownloadPriority
> &priorities
)
2751 if (!hasMetadata()) return;
2753 Q_ASSERT(priorities
.size() == filesCount());
2755 // Reset 'm_hasSeedStatus' if needed in order to react again to
2756 // 'torrent_finished_alert' and eg show tray notifications
2757 const QVector
<DownloadPriority
> oldPriorities
= filePriorities();
2758 for (int i
= 0; i
< oldPriorities
.size(); ++i
)
2760 if ((oldPriorities
[i
] == DownloadPriority::Ignored
)
2761 && (priorities
[i
] > DownloadPriority::Ignored
)
2762 && !m_completedFiles
.at(i
))
2764 m_hasFinishedStatus
= false;
2769 const int internalFilesCount
= m_torrentInfo
.nativeInfo()->files().num_files(); // including .pad files
2770 auto nativePriorities
= std::vector
<lt::download_priority_t
>(internalFilesCount
, LT::toNative(DownloadPriority::Normal
));
2771 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
2772 for (int i
= 0; i
< priorities
.size(); ++i
)
2773 nativePriorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(priorities
[i
]);
2775 qDebug() << Q_FUNC_INFO
<< "Changing files priorities...";
2776 m_nativeHandle
.prioritize_files(nativePriorities
);
2778 m_filePriorities
= priorities
;
2779 // Restore first/last piece first option if necessary
2780 if (m_hasFirstLastPiecePriority
)
2781 applyFirstLastPiecePriority(true);
2784 QVector
<qreal
> TorrentImpl::availableFileFractions() const
2786 Q_ASSERT(hasMetadata());
2788 const int filesCount
= this->filesCount();
2789 if (filesCount
<= 0) return {};
2791 const QVector
<int> piecesAvailability
= pieceAvailability();
2792 // libtorrent returns empty array for seeding only torrents
2793 if (piecesAvailability
.empty()) return QVector
<qreal
>(filesCount
, -1);
2796 res
.reserve(filesCount
);
2797 for (int i
= 0; i
< filesCount
; ++i
)
2799 const TorrentInfo::PieceRange filePieces
= m_torrentInfo
.filePieces(i
);
2801 int availablePieces
= 0;
2802 for (const int piece
: filePieces
)
2803 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2805 const qreal availability
= filePieces
.isEmpty()
2806 ? 1 // the file has no pieces, so it is available by default
2807 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2808 res
.push_back(availability
);
2813 template <typename Func
, typename Callback
>
2814 void TorrentImpl::invokeAsync(Func func
, Callback resultHandler
) const
2816 m_session
->invokeAsync([session
= m_session
2817 , func
= std::move(func
)
2818 , resultHandler
= std::move(resultHandler
)
2819 , thisTorrent
= QPointer
<const TorrentImpl
>(this)]() mutable
2821 session
->invoke([result
= func(), thisTorrent
, resultHandler
= std::move(resultHandler
)]
2824 resultHandler(result
);