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>
46 #include <QtSystemDetection>
51 #include <QStringList>
54 #include "base/exceptions.h"
55 #include "base/global.h"
56 #include "base/logger.h"
57 #include "base/preferences.h"
58 #include "base/types.h"
59 #include "base/utils/fs.h"
60 #include "base/utils/io.h"
62 #include "downloadpriority.h"
63 #include "extensiondata.h"
64 #include "loadtorrentparams.h"
65 #include "ltqbitarray.h"
66 #include "lttypecast.h"
67 #include "peeraddress.h"
69 #include "sessionimpl.h"
71 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
72 #include "base/utils/misc.h"
73 #endif // Q_OS_MACOS || Q_OS_WIN
75 using namespace BitTorrent
;
79 lt::announce_entry
makeNativeAnnounceEntry(const QString
&url
, const int tier
)
81 lt::announce_entry entry
{url
.toStdString()};
86 QDateTime
fromLTTimePoint32(const lt::time_point32
&timePoint
)
88 const auto ltNow
= lt::clock_type::now();
89 const auto qNow
= QDateTime::currentDateTime();
90 const auto secsSinceNow
= lt::duration_cast
<lt::seconds
>(timePoint
- ltNow
+ lt::milliseconds(500)).count();
92 return qNow
.addSecs(secsSinceNow
);
95 QString
toString(const lt::tcp::endpoint
<TCPEndpoint
)
97 return QString::fromStdString((std::stringstream() << ltTCPEndpoint
).str());
100 void updateTrackerEntry(TrackerEntry
&trackerEntry
, const lt::announce_entry
&nativeEntry
101 , const QSet
<int> &btProtocols
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
103 Q_ASSERT(trackerEntry
.url
== QString::fromStdString(nativeEntry
.url
));
105 trackerEntry
.tier
= nativeEntry
.tier
;
107 // remove outdated endpoints
108 trackerEntry
.endpointEntries
.removeIf([&nativeEntry
](const QHash
<std::pair
<QString
, int>, TrackerEndpointEntry
>::iterator
&iter
)
110 return std::none_of(nativeEntry
.endpoints
.cbegin(), nativeEntry
.endpoints
.cend()
111 , [&endpointName
= std::get
<0>(iter
.key())](const auto &existingEndpoint
)
113 return (endpointName
== toString(existingEndpoint
.local_endpoint
));
117 const auto numEndpoints
= static_cast<qsizetype
>(nativeEntry
.endpoints
.size()) * btProtocols
.size();
121 int numNotWorking
= 0;
122 int numTrackerError
= 0;
123 int numUnreachable
= 0;
125 for (const lt::announce_endpoint
<AnnounceEndpoint
: nativeEntry
.endpoints
)
127 const auto endpointName
= toString(ltAnnounceEndpoint
.local_endpoint
);
129 for (const auto protocolVersion
: btProtocols
)
131 #ifdef QBT_USES_LIBTORRENT2
132 Q_ASSERT((protocolVersion
== 1) || (protocolVersion
== 2));
133 const auto ltProtocolVersion
= (protocolVersion
== 1) ? lt::protocol_version::V1
: lt::protocol_version::V2
;
134 const lt::announce_infohash
<AnnounceInfo
= ltAnnounceEndpoint
.info_hashes
[ltProtocolVersion
];
136 Q_ASSERT(protocolVersion
== 1);
137 const lt::announce_endpoint
<AnnounceInfo
= ltAnnounceEndpoint
;
139 const QMap
<int, int> &endpointUpdateInfo
= updateInfo
[ltAnnounceEndpoint
.local_endpoint
];
140 TrackerEndpointEntry
&trackerEndpointEntry
= trackerEntry
.endpointEntries
[std::make_pair(endpointName
, protocolVersion
)];
142 trackerEndpointEntry
.name
= endpointName
;
143 trackerEndpointEntry
.btVersion
= protocolVersion
;
144 trackerEndpointEntry
.numPeers
= endpointUpdateInfo
.value(protocolVersion
, trackerEndpointEntry
.numPeers
);
145 trackerEndpointEntry
.numSeeds
= ltAnnounceInfo
.scrape_complete
;
146 trackerEndpointEntry
.numLeeches
= ltAnnounceInfo
.scrape_incomplete
;
147 trackerEndpointEntry
.numDownloaded
= ltAnnounceInfo
.scrape_downloaded
;
148 trackerEndpointEntry
.nextAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.next_announce
);
149 trackerEndpointEntry
.minAnnounceTime
= fromLTTimePoint32(ltAnnounceInfo
.min_announce
);
151 if (ltAnnounceInfo
.updating
)
153 trackerEndpointEntry
.status
= TrackerEntryStatus::Updating
;
156 else if (ltAnnounceInfo
.fails
> 0)
158 if (ltAnnounceInfo
.last_error
== lt::errors::tracker_failure
)
160 trackerEndpointEntry
.status
= TrackerEntryStatus::TrackerError
;
163 else if (ltAnnounceInfo
.last_error
== lt::errors::announce_skipped
)
165 trackerEndpointEntry
.status
= TrackerEntryStatus::Unreachable
;
170 trackerEndpointEntry
.status
= TrackerEntryStatus::NotWorking
;
174 else if (nativeEntry
.verified
)
176 trackerEndpointEntry
.status
= TrackerEntryStatus::Working
;
181 trackerEndpointEntry
.status
= TrackerEntryStatus::NotContacted
;
184 if (!ltAnnounceInfo
.message
.empty())
186 trackerEndpointEntry
.message
= QString::fromStdString(ltAnnounceInfo
.message
);
188 else if (ltAnnounceInfo
.last_error
)
190 trackerEndpointEntry
.message
= QString::fromLocal8Bit(ltAnnounceInfo
.last_error
.message());
194 trackerEndpointEntry
.message
.clear();
199 if (numEndpoints
> 0)
203 trackerEntry
.status
= TrackerEntryStatus::Updating
;
205 else if (numWorking
> 0)
207 trackerEntry
.status
= TrackerEntryStatus::Working
;
209 else if (numTrackerError
> 0)
211 trackerEntry
.status
= TrackerEntryStatus::TrackerError
;
213 else if (numUnreachable
== numEndpoints
)
215 trackerEntry
.status
= TrackerEntryStatus::Unreachable
;
217 else if ((numUnreachable
+ numNotWorking
) == numEndpoints
)
219 trackerEntry
.status
= TrackerEntryStatus::NotWorking
;
223 trackerEntry
.numPeers
= -1;
224 trackerEntry
.numSeeds
= -1;
225 trackerEntry
.numLeeches
= -1;
226 trackerEntry
.numDownloaded
= -1;
227 trackerEntry
.nextAnnounceTime
= QDateTime();
228 trackerEntry
.minAnnounceTime
= QDateTime();
229 trackerEntry
.message
.clear();
231 for (const TrackerEndpointEntry
&endpointEntry
: asConst(trackerEntry
.endpointEntries
))
233 trackerEntry
.numPeers
= std::max(trackerEntry
.numPeers
, endpointEntry
.numPeers
);
234 trackerEntry
.numSeeds
= std::max(trackerEntry
.numSeeds
, endpointEntry
.numSeeds
);
235 trackerEntry
.numLeeches
= std::max(trackerEntry
.numLeeches
, endpointEntry
.numLeeches
);
236 trackerEntry
.numDownloaded
= std::max(trackerEntry
.numDownloaded
, endpointEntry
.numDownloaded
);
238 if (endpointEntry
.status
== trackerEntry
.status
)
240 if (!trackerEntry
.nextAnnounceTime
.isValid() || (trackerEntry
.nextAnnounceTime
> endpointEntry
.nextAnnounceTime
))
242 trackerEntry
.nextAnnounceTime
= endpointEntry
.nextAnnounceTime
;
243 trackerEntry
.minAnnounceTime
= endpointEntry
.minAnnounceTime
;
244 if ((endpointEntry
.status
!= TrackerEntryStatus::Working
)
245 || !endpointEntry
.message
.isEmpty())
247 trackerEntry
.message
= endpointEntry
.message
;
251 if (endpointEntry
.status
== TrackerEntryStatus::Working
)
253 if (trackerEntry
.message
.isEmpty())
254 trackerEntry
.message
= endpointEntry
.message
;
260 template <typename Vector
>
261 Vector
resized(const Vector
&inVector
, const typename
Vector::size_type size
, const typename
Vector::value_type
&defaultValue
)
263 Vector outVector
= inVector
;
264 outVector
.resize(size
, defaultValue
);
268 // This is an imitation of limit normalization performed by libtorrent itself.
269 // We need perform it to keep cached values in line with the ones used by libtorrent.
270 int cleanLimitValue(const int value
)
272 return ((value
< 0) || (value
== std::numeric_limits
<int>::max())) ? 0 : value
;
278 TorrentImpl::TorrentImpl(SessionImpl
*session
, lt::session
*nativeSession
279 , const lt::torrent_handle
&nativeHandle
, const LoadTorrentParams
¶ms
)
282 , m_nativeSession(nativeSession
)
283 , m_nativeHandle(nativeHandle
)
284 #ifdef QBT_USES_LIBTORRENT2
285 , m_infoHash(m_nativeHandle
.info_hashes())
287 , m_infoHash(m_nativeHandle
.info_hash())
289 , m_name(params
.name
)
290 , m_savePath(params
.savePath
)
291 , m_downloadPath(params
.downloadPath
)
292 , m_category(params
.category
)
293 , m_tags(params
.tags
)
294 , m_ratioLimit(params
.ratioLimit
)
295 , m_seedingTimeLimit(params
.seedingTimeLimit
)
296 , m_inactiveSeedingTimeLimit(params
.inactiveSeedingTimeLimit
)
297 , m_operatingMode(params
.operatingMode
)
298 , m_contentLayout(params
.contentLayout
)
299 , m_hasFinishedStatus(params
.hasFinishedStatus
)
300 , m_hasFirstLastPiecePriority(params
.firstLastPiecePriority
)
301 , m_useAutoTMM(params
.useAutoTMM
)
302 , m_isStopped(params
.stopped
)
303 , m_ltAddTorrentParams(params
.ltAddTorrentParams
)
304 , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams
.download_limit
))
305 , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams
.upload_limit
))
307 if (m_ltAddTorrentParams
.ti
)
309 // Initialize it only if torrent is added with metadata.
310 // Otherwise it should be initialized in "Metadata received" handler.
311 m_torrentInfo
= TorrentInfo(*m_ltAddTorrentParams
.ti
);
313 Q_ASSERT(m_filePaths
.isEmpty());
314 Q_ASSERT(m_indexMap
.isEmpty());
315 const int filesCount
= m_torrentInfo
.filesCount();
316 m_filePaths
.reserve(filesCount
);
317 m_indexMap
.reserve(filesCount
);
318 m_filePriorities
.reserve(filesCount
);
319 const std::vector
<lt::download_priority_t
> filePriorities
=
320 resized(m_ltAddTorrentParams
.file_priorities
, m_ltAddTorrentParams
.ti
->num_files()
321 , LT::toNative(m_ltAddTorrentParams
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
323 m_completedFiles
.fill(static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
), filesCount
);
324 m_filesProgress
.resize(filesCount
);
326 for (int i
= 0; i
< filesCount
; ++i
)
328 const lt::file_index_t nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
329 m_indexMap
[nativeIndex
] = i
;
331 const auto fileIter
= m_ltAddTorrentParams
.renamed_files
.find(nativeIndex
);
332 const Path filePath
= ((fileIter
!= m_ltAddTorrentParams
.renamed_files
.end())
333 ? Path(fileIter
->second
).removedExtension(QB_EXT
) : m_torrentInfo
.filePath(i
));
334 m_filePaths
.append(filePath
);
336 const auto priority
= LT::fromNative(filePriorities
[LT::toUnderlyingType(nativeIndex
)]);
337 m_filePriorities
.append(priority
);
341 setStopCondition(params
.stopCondition
);
343 const auto *extensionData
= static_cast<ExtensionData
*>(m_ltAddTorrentParams
.userdata
);
344 m_trackerEntries
.reserve(static_cast<decltype(m_trackerEntries
)::size_type
>(extensionData
->trackers
.size()));
345 for (const lt::announce_entry
&announceEntry
: extensionData
->trackers
)
346 m_trackerEntries
.append({QString::fromStdString(announceEntry
.url
), announceEntry
.tier
});
347 m_urlSeeds
.reserve(static_cast<decltype(m_urlSeeds
)::size_type
>(extensionData
->urlSeeds
.size()));
348 for (const std::string
&urlSeed
: extensionData
->urlSeeds
)
349 m_urlSeeds
.append(QString::fromStdString(urlSeed
));
350 m_nativeStatus
= extensionData
->status
;
358 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority
);
360 // TODO: Remove the following upgrade code in v4.4
361 // == BEGIN UPGRADE CODE ==
362 const Path spath
= actualStorageLocation();
363 for (int i
= 0; i
< filesCount(); ++i
)
365 const Path filepath
= filePath(i
);
366 // Move "unwanted" files back to their original folder
367 const Path parentRelPath
= filepath
.parentPath();
368 if (parentRelPath
.filename() == u
".unwanted")
370 const QString oldName
= filepath
.filename();
371 const Path newRelPath
= parentRelPath
.parentPath();
372 renameFile(i
, (newRelPath
/ Path(oldName
)));
374 // Remove .unwanted directory if empty
375 const Path newPath
= spath
/ newRelPath
;
376 qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath
/ Path(u
".unwanted"_s
)).toString();
377 Utils::Fs::rmdir(newPath
/ Path(u
".unwanted"_s
));
380 // == END UPGRADE CODE ==
383 TorrentImpl::~TorrentImpl() = default;
385 bool TorrentImpl::isValid() const
387 return m_nativeHandle
.is_valid();
390 Session
*TorrentImpl::session() const
395 InfoHash
TorrentImpl::infoHash() const
400 QString
TorrentImpl::name() const
402 if (!m_name
.isEmpty())
406 return m_torrentInfo
.name();
408 const QString name
= QString::fromStdString(m_nativeStatus
.name
);
412 return id().toString();
415 QDateTime
TorrentImpl::creationDate() const
417 return m_torrentInfo
.creationDate();
420 QString
TorrentImpl::creator() const
422 return m_torrentInfo
.creator();
425 QString
TorrentImpl::comment() const
427 return m_torrentInfo
.comment();
430 bool TorrentImpl::isPrivate() const
432 return m_torrentInfo
.isPrivate();
435 qlonglong
TorrentImpl::totalSize() const
437 return m_torrentInfo
.totalSize();
440 // size without the "don't download" files
441 qlonglong
TorrentImpl::wantedSize() const
443 return m_nativeStatus
.total_wanted
;
446 qlonglong
TorrentImpl::completedSize() const
448 return m_nativeStatus
.total_wanted_done
;
451 qlonglong
TorrentImpl::pieceLength() const
453 return m_torrentInfo
.pieceLength();
456 qlonglong
TorrentImpl::wastedSize() const
458 return (m_nativeStatus
.total_failed_bytes
+ m_nativeStatus
.total_redundant_bytes
);
461 QString
TorrentImpl::currentTracker() const
463 return QString::fromStdString(m_nativeStatus
.current_tracker
);
466 Path
TorrentImpl::savePath() const
468 return isAutoTMMEnabled() ? m_session
->categorySavePath(category()) : m_savePath
;
471 void TorrentImpl::setSavePath(const Path
&path
)
473 Q_ASSERT(!isAutoTMMEnabled());
475 const Path basePath
= m_session
->useCategoryPathsInManualMode()
476 ? m_session
->categorySavePath(category()) : m_session
->savePath();
477 const Path resolvedPath
= (path
.isAbsolute() ? path
: (basePath
/ path
));
478 if (resolvedPath
== savePath())
481 if (isFinished() || m_hasFinishedStatus
|| downloadPath().isEmpty())
483 moveStorage(resolvedPath
, MoveStorageContext::ChangeSavePath
);
487 m_savePath
= resolvedPath
;
488 m_session
->handleTorrentSavePathChanged(this);
489 m_session
->handleTorrentNeedSaveResumeData(this);
493 Path
TorrentImpl::downloadPath() const
495 return isAutoTMMEnabled() ? m_session
->categoryDownloadPath(category()) : m_downloadPath
;
498 void TorrentImpl::setDownloadPath(const Path
&path
)
500 Q_ASSERT(!isAutoTMMEnabled());
502 const Path basePath
= m_session
->useCategoryPathsInManualMode()
503 ? m_session
->categoryDownloadPath(category()) : m_session
->downloadPath();
504 const Path resolvedPath
= (path
.isEmpty() || path
.isAbsolute()) ? path
: (basePath
/ path
);
505 if (resolvedPath
== m_downloadPath
)
508 const bool isIncomplete
= !(isFinished() || m_hasFinishedStatus
);
511 moveStorage((resolvedPath
.isEmpty() ? savePath() : resolvedPath
), MoveStorageContext::ChangeDownloadPath
);
515 m_downloadPath
= resolvedPath
;
516 m_session
->handleTorrentSavePathChanged(this);
517 m_session
->handleTorrentNeedSaveResumeData(this);
521 Path
TorrentImpl::rootPath() const
526 const Path relativeRootPath
= Path::findRootFolder(filePaths());
527 if (relativeRootPath
.isEmpty())
530 return (actualStorageLocation() / relativeRootPath
);
533 Path
TorrentImpl::contentPath() const
538 if (filesCount() == 1)
539 return (actualStorageLocation() / filePath(0));
541 const Path rootPath
= this->rootPath();
542 return (rootPath
.isEmpty() ? actualStorageLocation() : rootPath
);
545 bool TorrentImpl::isAutoTMMEnabled() const
550 void TorrentImpl::setAutoTMMEnabled(bool enabled
)
552 if (m_useAutoTMM
== enabled
)
555 m_useAutoTMM
= enabled
;
558 m_savePath
= m_session
->categorySavePath(category());
559 m_downloadPath
= m_session
->categoryDownloadPath(category());
562 m_session
->handleTorrentNeedSaveResumeData(this);
563 m_session
->handleTorrentSavingModeChanged(this);
565 adjustStorageLocation();
568 Path
TorrentImpl::actualStorageLocation() const
573 return Path(m_nativeStatus
.save_path
);
576 void TorrentImpl::setAutoManaged(const bool enable
)
579 m_nativeHandle
.set_flags(lt::torrent_flags::auto_managed
);
581 m_nativeHandle
.unset_flags(lt::torrent_flags::auto_managed
);
584 Path
TorrentImpl::wantedActualPath(int index
, const Path
&path
) const
586 if (m_session
->isAppendExtensionEnabled()
587 && (fileSize(index
) > 0) && !m_completedFiles
.at(index
))
589 return path
+ QB_EXT
;
595 QVector
<TrackerEntry
> TorrentImpl::trackers() const
597 return m_trackerEntries
;
600 void TorrentImpl::addTrackers(QVector
<TrackerEntry
> trackers
)
602 trackers
.removeIf([](const TrackerEntry
&entry
) { return entry
.url
.isEmpty(); });
604 const auto newTrackers
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend())
605 - QSet
<TrackerEntry
>(m_trackerEntries
.cbegin(), m_trackerEntries
.cend());
606 if (newTrackers
.isEmpty())
609 trackers
= QVector
<TrackerEntry
>(newTrackers
.cbegin(), newTrackers
.cend());
610 for (const TrackerEntry
&tracker
: trackers
)
611 m_nativeHandle
.add_tracker(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
613 m_trackerEntries
.append(trackers
);
614 std::sort(m_trackerEntries
.begin(), m_trackerEntries
.end()
615 , [](const TrackerEntry
&lhs
, const TrackerEntry
&rhs
) { return lhs
.tier
< rhs
.tier
; });
617 m_session
->handleTorrentNeedSaveResumeData(this);
618 m_session
->handleTorrentTrackersAdded(this, trackers
);
621 void TorrentImpl::removeTrackers(const QStringList
&trackers
)
623 QStringList removedTrackers
= trackers
;
624 for (const QString
&tracker
: trackers
)
626 if (!m_trackerEntries
.removeOne({tracker
}))
627 removedTrackers
.removeOne(tracker
);
630 std::vector
<lt::announce_entry
> nativeTrackers
;
631 nativeTrackers
.reserve(m_trackerEntries
.size());
632 for (const TrackerEntry
&tracker
: asConst(m_trackerEntries
))
633 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
635 if (!removedTrackers
.isEmpty())
637 m_nativeHandle
.replace_trackers(nativeTrackers
);
639 m_session
->handleTorrentNeedSaveResumeData(this);
640 m_session
->handleTorrentTrackersRemoved(this, removedTrackers
);
644 void TorrentImpl::replaceTrackers(QVector
<TrackerEntry
> trackers
)
646 trackers
.removeIf([](const TrackerEntry
&entry
) { return entry
.url
.isEmpty(); });
647 // Filter out duplicate trackers
648 const auto uniqueTrackers
= QSet
<TrackerEntry
>(trackers
.cbegin(), trackers
.cend());
649 trackers
= QVector
<TrackerEntry
>(uniqueTrackers
.cbegin(), uniqueTrackers
.cend());
650 std::sort(trackers
.begin(), trackers
.end()
651 , [](const TrackerEntry
&lhs
, const TrackerEntry
&rhs
) { return lhs
.tier
< rhs
.tier
; });
653 std::vector
<lt::announce_entry
> nativeTrackers
;
654 nativeTrackers
.reserve(trackers
.size());
655 for (const TrackerEntry
&tracker
: trackers
)
656 nativeTrackers
.emplace_back(makeNativeAnnounceEntry(tracker
.url
, tracker
.tier
));
658 m_nativeHandle
.replace_trackers(nativeTrackers
);
659 m_trackerEntries
= trackers
;
661 // Clear the peer list if it's a private torrent since
662 // we do not want to keep connecting with peers from old tracker.
666 m_session
->handleTorrentNeedSaveResumeData(this);
667 m_session
->handleTorrentTrackersChanged(this);
670 QVector
<QUrl
> TorrentImpl::urlSeeds() const
675 void TorrentImpl::addUrlSeeds(const QVector
<QUrl
> &urlSeeds
)
677 m_session
->invokeAsync([urlSeeds
, session
= m_session
678 , nativeHandle
= m_nativeHandle
679 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
683 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
684 QVector
<QUrl
> currentSeeds
;
685 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
686 for (const std::string
&urlSeed
: nativeSeeds
)
687 currentSeeds
.append(QString::fromStdString(urlSeed
));
689 QVector
<QUrl
> addedUrlSeeds
;
690 addedUrlSeeds
.reserve(urlSeeds
.size());
692 for (const QUrl
&url
: urlSeeds
)
694 if (!currentSeeds
.contains(url
))
696 nativeHandle
.add_url_seed(url
.toString().toStdString());
697 addedUrlSeeds
.append(url
);
701 currentSeeds
.append(addedUrlSeeds
);
702 session
->invoke([session
, thisTorrent
, currentSeeds
, addedUrlSeeds
]
707 thisTorrent
->m_urlSeeds
= currentSeeds
;
708 if (!addedUrlSeeds
.isEmpty())
710 session
->handleTorrentNeedSaveResumeData(thisTorrent
);
711 session
->handleTorrentUrlSeedsAdded(thisTorrent
, addedUrlSeeds
);
715 catch (const std::exception
&) {}
719 void TorrentImpl::removeUrlSeeds(const QVector
<QUrl
> &urlSeeds
)
721 m_session
->invokeAsync([urlSeeds
, session
= m_session
722 , nativeHandle
= m_nativeHandle
723 , thisTorrent
= QPointer
<TorrentImpl
>(this)]
727 const std::set
<std::string
> nativeSeeds
= nativeHandle
.url_seeds();
728 QVector
<QUrl
> currentSeeds
;
729 currentSeeds
.reserve(static_cast<decltype(currentSeeds
)::size_type
>(nativeSeeds
.size()));
730 for (const std::string
&urlSeed
: nativeSeeds
)
731 currentSeeds
.append(QString::fromStdString(urlSeed
));
733 QVector
<QUrl
> removedUrlSeeds
;
734 removedUrlSeeds
.reserve(urlSeeds
.size());
736 for (const QUrl
&url
: urlSeeds
)
738 if (currentSeeds
.removeOne(url
))
740 nativeHandle
.remove_url_seed(url
.toString().toStdString());
741 removedUrlSeeds
.append(url
);
745 session
->invoke([session
, thisTorrent
, currentSeeds
, removedUrlSeeds
]
750 thisTorrent
->m_urlSeeds
= currentSeeds
;
752 if (!removedUrlSeeds
.isEmpty())
754 session
->handleTorrentNeedSaveResumeData(thisTorrent
);
755 session
->handleTorrentUrlSeedsRemoved(thisTorrent
, removedUrlSeeds
);
759 catch (const std::exception
&) {}
763 void TorrentImpl::clearPeers()
765 m_nativeHandle
.clear_peers();
768 bool TorrentImpl::connectPeer(const PeerAddress
&peerAddress
)
771 const lt::address addr
= lt::make_address(peerAddress
.ip
.toString().toStdString(), ec
);
772 if (ec
) return false;
774 const lt::tcp::endpoint
endpoint(addr
, peerAddress
.port
);
777 m_nativeHandle
.connect_peer(endpoint
);
779 catch (const lt::system_error
&err
)
781 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
782 .arg(peerAddress
.toString(), name(), QString::fromLocal8Bit(err
.what())), Log::WARNING
);
786 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress
.toString(), name()));
790 bool TorrentImpl::needSaveResumeData() const
792 return m_nativeStatus
.need_save_resume
;
795 void TorrentImpl::saveResumeData(lt::resume_data_flags_t flags
)
797 m_nativeHandle
.save_resume_data(flags
);
798 m_session
->handleTorrentSaveResumeDataRequested(this);
801 int TorrentImpl::filesCount() const
803 return m_torrentInfo
.filesCount();
806 int TorrentImpl::piecesCount() const
808 return m_torrentInfo
.piecesCount();
811 int TorrentImpl::piecesHave() const
813 return m_nativeStatus
.num_pieces
;
816 qreal
TorrentImpl::progress() const
819 return m_nativeStatus
.progress
;
821 if (m_nativeStatus
.total_wanted
== 0)
824 if (m_nativeStatus
.total_wanted_done
== m_nativeStatus
.total_wanted
)
827 const qreal progress
= static_cast<qreal
>(m_nativeStatus
.total_wanted_done
) / m_nativeStatus
.total_wanted
;
828 if ((progress
< 0.f
) || (progress
> 1.f
))
830 LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
831 .arg(name(), QString::number(m_nativeStatus
.total_wanted
), QString::number(m_nativeStatus
.total_wanted_done
))
838 QString
TorrentImpl::category() const
843 bool TorrentImpl::belongsToCategory(const QString
&category
) const
845 if (m_category
.isEmpty())
846 return category
.isEmpty();
848 if (m_category
== category
)
851 return (m_session
->isSubcategoriesEnabled() && m_category
.startsWith(category
+ u
'/'));
854 TagSet
TorrentImpl::tags() const
859 bool TorrentImpl::hasTag(const QString
&tag
) const
861 return m_tags
.contains(tag
);
864 bool TorrentImpl::addTag(const QString
&tag
)
866 if (!m_session
->isValidTag(tag
))
871 if (!m_session
->hasTag(tag
))
873 if (!m_session
->addTag(tag
))
877 m_session
->handleTorrentNeedSaveResumeData(this);
878 m_session
->handleTorrentTagAdded(this, tag
);
882 bool TorrentImpl::removeTag(const QString
&tag
)
884 if (m_tags
.remove(tag
))
886 m_session
->handleTorrentNeedSaveResumeData(this);
887 m_session
->handleTorrentTagRemoved(this, tag
);
893 void TorrentImpl::removeAllTags()
895 for (const QString
&tag
: asConst(tags()))
899 QDateTime
TorrentImpl::addedTime() const
901 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.added_time
);
904 qreal
TorrentImpl::ratioLimit() const
909 int TorrentImpl::seedingTimeLimit() const
911 return m_seedingTimeLimit
;
914 int TorrentImpl::inactiveSeedingTimeLimit() const
916 return m_inactiveSeedingTimeLimit
;
919 Path
TorrentImpl::filePath(const int index
) const
921 Q_ASSERT(index
>= 0);
922 Q_ASSERT(index
< m_filePaths
.size());
924 return m_filePaths
.value(index
, {});
927 Path
TorrentImpl::actualFilePath(const int index
) const
929 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
931 Q_ASSERT(index
>= 0);
932 Q_ASSERT(index
< nativeIndexes
.size());
933 if ((index
< 0) || (index
>= nativeIndexes
.size()))
936 return Path(nativeTorrentInfo()->files().file_path(nativeIndexes
[index
]));
939 qlonglong
TorrentImpl::fileSize(const int index
) const
941 return m_torrentInfo
.fileSize(index
);
944 PathList
TorrentImpl::filePaths() const
949 QVector
<DownloadPriority
> TorrentImpl::filePriorities() const
951 return m_filePriorities
;
954 TorrentInfo
TorrentImpl::info() const
956 return m_torrentInfo
;
959 bool TorrentImpl::isPaused() const
964 bool TorrentImpl::isQueued() const
966 // Torrent is Queued if it isn't in Paused state but paused internally
968 && (m_nativeStatus
.flags
& lt::torrent_flags::auto_managed
)
969 && (m_nativeStatus
.flags
& lt::torrent_flags::paused
));
972 bool TorrentImpl::isChecking() const
974 return ((m_nativeStatus
.state
== lt::torrent_status::checking_files
)
975 || (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
));
978 bool TorrentImpl::isDownloading() const
982 case TorrentState::Downloading
:
983 case TorrentState::DownloadingMetadata
:
984 case TorrentState::ForcedDownloadingMetadata
:
985 case TorrentState::StalledDownloading
:
986 case TorrentState::CheckingDownloading
:
987 case TorrentState::PausedDownloading
:
988 case TorrentState::QueuedDownloading
:
989 case TorrentState::ForcedDownloading
:
998 bool TorrentImpl::isMoving() const
1000 return m_state
== TorrentState::Moving
;
1003 bool TorrentImpl::isUploading() const
1007 case TorrentState::Uploading
:
1008 case TorrentState::StalledUploading
:
1009 case TorrentState::CheckingUploading
:
1010 case TorrentState::QueuedUploading
:
1011 case TorrentState::ForcedUploading
:
1020 bool TorrentImpl::isCompleted() const
1024 case TorrentState::Uploading
:
1025 case TorrentState::StalledUploading
:
1026 case TorrentState::CheckingUploading
:
1027 case TorrentState::PausedUploading
:
1028 case TorrentState::QueuedUploading
:
1029 case TorrentState::ForcedUploading
:
1038 bool TorrentImpl::isActive() const
1042 case TorrentState::StalledDownloading
:
1043 return (uploadPayloadRate() > 0);
1045 case TorrentState::DownloadingMetadata
:
1046 case TorrentState::ForcedDownloadingMetadata
:
1047 case TorrentState::Downloading
:
1048 case TorrentState::ForcedDownloading
:
1049 case TorrentState::Uploading
:
1050 case TorrentState::ForcedUploading
:
1051 case TorrentState::Moving
:
1061 bool TorrentImpl::isInactive() const
1066 bool TorrentImpl::isErrored() const
1068 return ((m_state
== TorrentState::MissingFiles
)
1069 || (m_state
== TorrentState::Error
));
1072 bool TorrentImpl::isFinished() const
1074 return ((m_nativeStatus
.state
== lt::torrent_status::finished
)
1075 || (m_nativeStatus
.state
== lt::torrent_status::seeding
));
1078 bool TorrentImpl::isForced() const
1080 return (!isPaused() && (m_operatingMode
== TorrentOperatingMode::Forced
));
1083 bool TorrentImpl::isSequentialDownload() const
1085 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::sequential_download
);
1088 bool TorrentImpl::hasFirstLastPiecePriority() const
1090 return m_hasFirstLastPiecePriority
;
1093 TorrentState
TorrentImpl::state() const
1098 void TorrentImpl::updateState()
1100 if (m_nativeStatus
.state
== lt::torrent_status::checking_resume_data
)
1102 m_state
= TorrentState::CheckingResumeData
;
1104 else if (isMoveInProgress())
1106 m_state
= TorrentState::Moving
;
1108 else if (hasMissingFiles())
1110 m_state
= TorrentState::MissingFiles
;
1112 else if (hasError())
1114 m_state
= TorrentState::Error
;
1116 else if (!hasMetadata())
1119 m_state
= TorrentState::PausedDownloading
;
1120 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1121 m_state
= TorrentState::QueuedDownloading
;
1123 m_state
= isForced() ? TorrentState::ForcedDownloadingMetadata
: TorrentState::DownloadingMetadata
;
1125 else if ((m_nativeStatus
.state
== lt::torrent_status::checking_files
) && !isPaused())
1127 // If the torrent is not just in the "checking" state, but is being actually checked
1128 m_state
= m_hasFinishedStatus
? TorrentState::CheckingUploading
: TorrentState::CheckingDownloading
;
1130 else if (isFinished())
1133 m_state
= TorrentState::PausedUploading
;
1134 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1135 m_state
= TorrentState::QueuedUploading
;
1136 else if (isForced())
1137 m_state
= TorrentState::ForcedUploading
;
1138 else if (m_nativeStatus
.upload_payload_rate
> 0)
1139 m_state
= TorrentState::Uploading
;
1141 m_state
= TorrentState::StalledUploading
;
1146 m_state
= TorrentState::PausedDownloading
;
1147 else if (m_session
->isQueueingSystemEnabled() && isQueued())
1148 m_state
= TorrentState::QueuedDownloading
;
1149 else if (isForced())
1150 m_state
= TorrentState::ForcedDownloading
;
1151 else if (m_nativeStatus
.download_payload_rate
> 0)
1152 m_state
= TorrentState::Downloading
;
1154 m_state
= TorrentState::StalledDownloading
;
1158 bool TorrentImpl::hasMetadata() const
1160 return m_torrentInfo
.isValid();
1163 bool TorrentImpl::hasMissingFiles() const
1165 return m_hasMissingFiles
;
1168 bool TorrentImpl::hasError() const
1170 return (m_nativeStatus
.errc
|| (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
));
1173 int TorrentImpl::queuePosition() const
1175 return static_cast<int>(m_nativeStatus
.queue_position
);
1178 QString
TorrentImpl::error() const
1180 if (m_nativeStatus
.errc
)
1181 return QString::fromLocal8Bit(m_nativeStatus
.errc
.message().c_str());
1183 if (m_nativeStatus
.flags
& lt::torrent_flags::upload_mode
)
1185 return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
1186 .arg(QString::fromLocal8Bit(m_lastFileError
.error
.message().c_str()));
1192 qlonglong
TorrentImpl::totalDownload() const
1194 return m_nativeStatus
.all_time_download
;
1197 qlonglong
TorrentImpl::totalUpload() const
1199 return m_nativeStatus
.all_time_upload
;
1202 qlonglong
TorrentImpl::activeTime() const
1204 return lt::total_seconds(m_nativeStatus
.active_duration
);
1207 qlonglong
TorrentImpl::finishedTime() const
1209 return lt::total_seconds(m_nativeStatus
.finished_duration
);
1212 qlonglong
TorrentImpl::eta() const
1214 if (isPaused()) return MAX_ETA
;
1216 const SpeedSampleAvg speedAverage
= m_payloadRateMonitor
.average();
1220 const qreal maxRatioValue
= maxRatio();
1221 const int maxSeedingTimeValue
= maxSeedingTime();
1222 const int maxInactiveSeedingTimeValue
= maxInactiveSeedingTime();
1223 if ((maxRatioValue
< 0) && (maxSeedingTimeValue
< 0) && (maxInactiveSeedingTimeValue
< 0)) return MAX_ETA
;
1225 qlonglong ratioEta
= MAX_ETA
;
1227 if ((speedAverage
.upload
> 0) && (maxRatioValue
>= 0))
1230 qlonglong realDL
= totalDownload();
1232 realDL
= wantedSize();
1234 ratioEta
= ((realDL
* maxRatioValue
) - totalUpload()) / speedAverage
.upload
;
1237 qlonglong seedingTimeEta
= MAX_ETA
;
1239 if (maxSeedingTimeValue
>= 0)
1241 seedingTimeEta
= (maxSeedingTimeValue
* 60) - finishedTime();
1242 if (seedingTimeEta
< 0)
1246 qlonglong inactiveSeedingTimeEta
= MAX_ETA
;
1248 if (maxInactiveSeedingTimeValue
>= 0)
1250 inactiveSeedingTimeEta
= (maxInactiveSeedingTimeValue
* 60) - timeSinceActivity();
1251 inactiveSeedingTimeEta
= std::max
<qlonglong
>(inactiveSeedingTimeEta
, 0);
1254 return std::min({ratioEta
, seedingTimeEta
, inactiveSeedingTimeEta
});
1257 if (!speedAverage
.download
) return MAX_ETA
;
1259 return (wantedSize() - completedSize()) / speedAverage
.download
;
1262 QVector
<qreal
> TorrentImpl::filesProgress() const
1267 const int count
= m_filesProgress
.size();
1268 Q_ASSERT(count
== filesCount());
1269 if (count
!= filesCount()) [[unlikely
]]
1272 if (m_completedFiles
.count(true) == count
)
1273 return QVector
<qreal
>(count
, 1);
1275 QVector
<qreal
> result
;
1276 result
.reserve(count
);
1277 for (int i
= 0; i
< count
; ++i
)
1279 const int64_t progress
= m_filesProgress
.at(i
);
1280 const int64_t size
= fileSize(i
);
1281 if ((size
<= 0) || (progress
== size
))
1284 result
<< (progress
/ static_cast<qreal
>(size
));
1290 int TorrentImpl::seedsCount() const
1292 return m_nativeStatus
.num_seeds
;
1295 int TorrentImpl::peersCount() const
1297 return m_nativeStatus
.num_peers
;
1300 int TorrentImpl::leechsCount() const
1302 return (m_nativeStatus
.num_peers
- m_nativeStatus
.num_seeds
);
1305 int TorrentImpl::totalSeedsCount() const
1307 return (m_nativeStatus
.num_complete
> -1) ? m_nativeStatus
.num_complete
: m_nativeStatus
.list_seeds
;
1310 int TorrentImpl::totalPeersCount() const
1312 const int peers
= m_nativeStatus
.num_complete
+ m_nativeStatus
.num_incomplete
;
1313 return (peers
> -1) ? peers
: m_nativeStatus
.list_peers
;
1316 int TorrentImpl::totalLeechersCount() const
1318 return (m_nativeStatus
.num_incomplete
> -1) ? m_nativeStatus
.num_incomplete
: (m_nativeStatus
.list_peers
- m_nativeStatus
.list_seeds
);
1321 QDateTime
TorrentImpl::lastSeenComplete() const
1323 if (m_nativeStatus
.last_seen_complete
> 0)
1324 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.last_seen_complete
);
1329 QDateTime
TorrentImpl::completedTime() const
1331 if (m_nativeStatus
.completed_time
> 0)
1332 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.completed_time
);
1337 qlonglong
TorrentImpl::timeSinceUpload() const
1339 if (m_nativeStatus
.last_upload
.time_since_epoch().count() == 0)
1341 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_upload
);
1344 qlonglong
TorrentImpl::timeSinceDownload() const
1346 if (m_nativeStatus
.last_download
.time_since_epoch().count() == 0)
1348 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_download
);
1351 qlonglong
TorrentImpl::timeSinceActivity() const
1353 const qlonglong upTime
= timeSinceUpload();
1354 const qlonglong downTime
= timeSinceDownload();
1355 return ((upTime
< 0) != (downTime
< 0))
1356 ? std::max(upTime
, downTime
)
1357 : std::min(upTime
, downTime
);
1360 int TorrentImpl::downloadLimit() const
1362 return m_downloadLimit
;;
1365 int TorrentImpl::uploadLimit() const
1367 return m_uploadLimit
;
1370 bool TorrentImpl::superSeeding() const
1372 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::super_seeding
);
1375 bool TorrentImpl::isDHTDisabled() const
1377 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_dht
);
1380 bool TorrentImpl::isPEXDisabled() const
1382 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_pex
);
1385 bool TorrentImpl::isLSDDisabled() const
1387 return static_cast<bool>(m_nativeStatus
.flags
& lt::torrent_flags::disable_lsd
);
1390 QVector
<PeerInfo
> TorrentImpl::peers() const
1392 std::vector
<lt::peer_info
> nativePeers
;
1393 m_nativeHandle
.get_peer_info(nativePeers
);
1395 QVector
<PeerInfo
> peers
;
1396 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
1398 for (const lt::peer_info
&peer
: nativePeers
)
1399 peers
.append(PeerInfo(peer
, pieces()));
1404 QBitArray
TorrentImpl::pieces() const
1409 QBitArray
TorrentImpl::downloadingPieces() const
1411 QBitArray
result(piecesCount());
1413 std::vector
<lt::partial_piece_info
> queue
;
1414 m_nativeHandle
.get_download_queue(queue
);
1416 for (const lt::partial_piece_info
&info
: queue
)
1417 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
1422 QVector
<int> TorrentImpl::pieceAvailability() const
1424 std::vector
<int> avail
;
1425 m_nativeHandle
.piece_availability(avail
);
1427 return {avail
.cbegin(), avail
.cend()};
1430 qreal
TorrentImpl::distributedCopies() const
1432 return m_nativeStatus
.distributed_copies
;
1435 qreal
TorrentImpl::maxRatio() const
1437 if (m_ratioLimit
== USE_GLOBAL_RATIO
)
1438 return m_session
->globalMaxRatio();
1440 return m_ratioLimit
;
1443 int TorrentImpl::maxSeedingTime() const
1445 if (m_seedingTimeLimit
== USE_GLOBAL_SEEDING_TIME
)
1446 return m_session
->globalMaxSeedingMinutes();
1448 return m_seedingTimeLimit
;
1451 int TorrentImpl::maxInactiveSeedingTime() const
1453 if (m_inactiveSeedingTimeLimit
== USE_GLOBAL_INACTIVE_SEEDING_TIME
)
1454 return m_session
->globalMaxInactiveSeedingMinutes();
1456 return m_inactiveSeedingTimeLimit
;
1459 qreal
TorrentImpl::realRatio() const
1461 const int64_t upload
= m_nativeStatus
.all_time_upload
;
1462 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1463 const int64_t download
= (m_nativeStatus
.all_time_download
< (m_nativeStatus
.total_done
* 0.01))
1464 ? m_nativeStatus
.total_done
1465 : m_nativeStatus
.all_time_download
;
1468 return (upload
== 0) ? 0 : MAX_RATIO
;
1470 const qreal ratio
= upload
/ static_cast<qreal
>(download
);
1471 Q_ASSERT(ratio
>= 0);
1472 return (ratio
> MAX_RATIO
) ? MAX_RATIO
: ratio
;
1475 int TorrentImpl::uploadPayloadRate() const
1477 // workaround: suppress the speed for paused state
1478 return isPaused() ? 0 : m_nativeStatus
.upload_payload_rate
;
1481 int TorrentImpl::downloadPayloadRate() const
1483 // workaround: suppress the speed for paused state
1484 return isPaused() ? 0 : m_nativeStatus
.download_payload_rate
;
1487 qlonglong
TorrentImpl::totalPayloadUpload() const
1489 return m_nativeStatus
.total_payload_upload
;
1492 qlonglong
TorrentImpl::totalPayloadDownload() const
1494 return m_nativeStatus
.total_payload_download
;
1497 int TorrentImpl::connectionsCount() const
1499 return m_nativeStatus
.num_connections
;
1502 int TorrentImpl::connectionsLimit() const
1504 return m_nativeStatus
.connections_limit
;
1507 qlonglong
TorrentImpl::nextAnnounce() const
1509 return lt::total_seconds(m_nativeStatus
.next_announce
);
1512 void TorrentImpl::setName(const QString
&name
)
1517 m_session
->handleTorrentNeedSaveResumeData(this);
1518 m_session
->handleTorrentNameChanged(this);
1522 bool TorrentImpl::setCategory(const QString
&category
)
1524 if (m_category
!= category
)
1526 if (!category
.isEmpty() && !m_session
->categories().contains(category
))
1529 const QString oldCategory
= m_category
;
1530 m_category
= category
;
1531 m_session
->handleTorrentNeedSaveResumeData(this);
1532 m_session
->handleTorrentCategoryChanged(this, oldCategory
);
1536 if (!m_session
->isDisableAutoTMMWhenCategoryChanged())
1537 adjustStorageLocation();
1539 setAutoTMMEnabled(false);
1546 void TorrentImpl::forceReannounce(const int index
)
1548 m_nativeHandle
.force_reannounce(0, index
);
1551 void TorrentImpl::forceDHTAnnounce()
1553 m_nativeHandle
.force_dht_announce();
1556 void TorrentImpl::forceRecheck()
1561 m_nativeHandle
.force_recheck();
1562 // We have to force update the cached state, otherwise someone will be able to get
1563 // an incorrect one during the interval until the cached state is updated in a regular way.
1564 m_nativeStatus
.state
= lt::torrent_status::checking_resume_data
;
1566 m_hasMissingFiles
= false;
1567 m_unchecked
= false;
1569 m_completedFiles
.fill(false);
1570 m_filesProgress
.fill(0);
1571 m_pieces
.fill(false);
1572 m_nativeStatus
.pieces
.clear_all();
1573 m_nativeStatus
.num_pieces
= 0;
1577 // When "force recheck" is applied on paused torrent, we temporarily resume it
1579 m_stopCondition
= StopCondition::FilesChecked
;
1583 void TorrentImpl::setSequentialDownload(const bool enable
)
1587 m_nativeHandle
.set_flags(lt::torrent_flags::sequential_download
);
1588 m_nativeStatus
.flags
|= lt::torrent_flags::sequential_download
; // prevent return cached value
1592 m_nativeHandle
.unset_flags(lt::torrent_flags::sequential_download
);
1593 m_nativeStatus
.flags
&= ~lt::torrent_flags::sequential_download
; // prevent return cached value
1596 m_session
->handleTorrentNeedSaveResumeData(this);
1599 void TorrentImpl::setFirstLastPiecePriority(const bool enabled
)
1601 if (m_hasFirstLastPiecePriority
== enabled
)
1604 m_hasFirstLastPiecePriority
= enabled
;
1606 applyFirstLastPiecePriority(enabled
);
1608 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1609 .arg((enabled
? tr("On") : tr("Off")), name()));
1611 m_session
->handleTorrentNeedSaveResumeData(this);
1614 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled
)
1616 Q_ASSERT(hasMetadata());
1618 // Download first and last pieces first for every file in the torrent
1620 auto piecePriorities
= std::vector
<lt::download_priority_t
>(m_torrentInfo
.piecesCount(), LT::toNative(DownloadPriority::Ignored
));
1622 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1623 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1624 for (int fileIndex
= 0; fileIndex
< m_filePriorities
.size(); ++fileIndex
)
1626 const DownloadPriority filePrio
= m_filePriorities
[fileIndex
];
1627 if (filePrio
<= DownloadPriority::Ignored
)
1630 // Determine the priority to set
1631 const lt::download_priority_t piecePrio
= LT::toNative(enabled
? DownloadPriority::Maximum
: filePrio
);
1632 const TorrentInfo::PieceRange pieceRange
= m_torrentInfo
.filePieces(fileIndex
);
1634 // worst case: AVI index = 1% of total file size (at the end of the file)
1635 const int numPieces
= std::ceil(fileSize(fileIndex
) * 0.01 / pieceLength());
1636 for (int i
= 0; i
< numPieces
; ++i
)
1638 piecePriorities
[pieceRange
.first() + i
] = piecePrio
;
1639 piecePriorities
[pieceRange
.last() - i
] = piecePrio
;
1642 const int firstPiece
= pieceRange
.first() + numPieces
;
1643 const int lastPiece
= pieceRange
.last() - numPieces
;
1644 for (int pieceIndex
= firstPiece
; pieceIndex
<= lastPiece
; ++pieceIndex
)
1645 piecePriorities
[pieceIndex
] = LT::toNative(filePrio
);
1648 m_nativeHandle
.prioritize_pieces(piecePriorities
);
1651 void TorrentImpl::fileSearchFinished(const Path
&savePath
, const PathList
&fileNames
)
1653 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
1654 endReceivedMetadataHandling(savePath
, fileNames
);
1657 TrackerEntry
TorrentImpl::updateTrackerEntry(const lt::announce_entry
&announceEntry
, const QHash
<lt::tcp::endpoint
, QMap
<int, int>> &updateInfo
)
1659 const auto it
= std::find_if(m_trackerEntries
.begin(), m_trackerEntries
.end()
1660 , [&announceEntry
](const TrackerEntry
&trackerEntry
)
1662 return (trackerEntry
.url
== QString::fromStdString(announceEntry
.url
));
1665 Q_ASSERT(it
!= m_trackerEntries
.end());
1666 if (it
== m_trackerEntries
.end()) [[unlikely
]]
1669 #ifdef QBT_USES_LIBTORRENT2
1670 QSet
<int> btProtocols
;
1671 const auto &infoHashes
= nativeHandle().info_hashes();
1672 if (infoHashes
.has(lt::protocol_version::V1
))
1673 btProtocols
.insert(1);
1674 if (infoHashes
.has(lt::protocol_version::V2
))
1675 btProtocols
.insert(2);
1677 const QSet
<int> btProtocols
{1};
1679 ::updateTrackerEntry(*it
, announceEntry
, btProtocols
, updateInfo
);
1683 void TorrentImpl::resetTrackerEntries()
1685 for (auto &trackerEntry
: m_trackerEntries
)
1686 trackerEntry
= {trackerEntry
.url
, trackerEntry
.tier
};
1689 std::shared_ptr
<const libtorrent::torrent_info
> TorrentImpl::nativeTorrentInfo() const
1691 if (m_nativeStatus
.torrent_file
.expired())
1692 m_nativeStatus
.torrent_file
= m_nativeHandle
.torrent_file();
1693 return m_nativeStatus
.torrent_file
.lock();
1696 void TorrentImpl::endReceivedMetadataHandling(const Path
&savePath
, const PathList
&fileNames
)
1698 Q_ASSERT(m_maintenanceJob
== MaintenanceJob::HandleMetadata
);
1699 if (m_maintenanceJob
!= MaintenanceJob::HandleMetadata
) [[unlikely
]]
1702 Q_ASSERT(m_filePaths
.isEmpty());
1703 if (!m_filePaths
.isEmpty()) [[unlikely
]]
1704 m_filePaths
.clear();
1706 lt::add_torrent_params
&p
= m_ltAddTorrentParams
;
1708 const std::shared_ptr
<lt::torrent_info
> metadata
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1709 m_torrentInfo
= TorrentInfo(*metadata
);
1710 m_filePriorities
.reserve(filesCount());
1711 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
1712 p
.file_priorities
= resized(p
.file_priorities
, metadata
->files().num_files()
1713 , LT::toNative(p
.file_priorities
.empty() ? DownloadPriority::Normal
: DownloadPriority::Ignored
));
1715 m_completedFiles
.fill(static_cast<bool>(p
.flags
& lt::torrent_flags::seed_mode
), filesCount());
1716 m_filesProgress
.resize(filesCount());
1719 for (int i
= 0; i
< fileNames
.size(); ++i
)
1721 const auto nativeIndex
= nativeIndexes
.at(i
);
1723 const Path
&actualFilePath
= fileNames
.at(i
);
1724 p
.renamed_files
[nativeIndex
] = actualFilePath
.toString().toStdString();
1726 const Path filePath
= actualFilePath
.removedExtension(QB_EXT
);
1727 m_filePaths
.append(filePath
);
1729 lt::download_priority_t
&nativePriority
= p
.file_priorities
[LT::toUnderlyingType(nativeIndex
)];
1730 if ((nativePriority
!= lt::dont_download
) && m_session
->isFilenameExcluded(filePath
.filename()))
1731 nativePriority
= lt::dont_download
;
1732 const auto priority
= LT::fromNative(nativePriority
);
1733 m_filePriorities
.append(priority
);
1735 p
.save_path
= savePath
.toString().toStdString();
1738 if (stopCondition() == StopCondition::MetadataReceived
)
1740 m_stopCondition
= StopCondition::None
;
1743 p
.flags
|= lt::torrent_flags::paused
;
1744 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1746 m_session
->handleTorrentPaused(this);
1751 // If first/last piece priority was specified when adding this torrent,
1752 // we should apply it now that we have metadata:
1753 if (m_hasFirstLastPiecePriority
)
1754 applyFirstLastPiecePriority(true);
1756 m_maintenanceJob
= MaintenanceJob::None
;
1757 prepareResumeData(p
);
1759 m_session
->handleTorrentMetadataReceived(this);
1762 void TorrentImpl::reload()
1765 m_completedFiles
.fill(false);
1766 m_filesProgress
.fill(0);
1767 m_pieces
.fill(false);
1768 m_nativeStatus
.pieces
.clear_all();
1769 m_nativeStatus
.num_pieces
= 0;
1771 const auto queuePos
= m_nativeHandle
.queue_position();
1773 m_nativeSession
->remove_torrent(m_nativeHandle
, lt::session::delete_partfile
);
1775 lt::add_torrent_params p
= m_ltAddTorrentParams
;
1776 p
.flags
|= lt::torrent_flags::update_subscribe
1777 | lt::torrent_flags::override_trackers
1778 | lt::torrent_flags::override_web_seeds
;
1782 p
.flags
|= lt::torrent_flags::paused
;
1783 p
.flags
&= ~lt::torrent_flags::auto_managed
;
1785 else if (m_operatingMode
== TorrentOperatingMode::AutoManaged
)
1787 p
.flags
|= (lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1791 p
.flags
&= ~(lt::torrent_flags::auto_managed
| lt::torrent_flags::paused
);
1794 auto *const extensionData
= new ExtensionData
;
1795 p
.userdata
= LTClientData(extensionData
);
1796 m_nativeHandle
= m_nativeSession
->add_torrent(p
);
1798 m_nativeStatus
= extensionData
->status
;
1800 if (queuePos
>= lt::queue_position_t
{})
1801 m_nativeHandle
.queue_position_set(queuePos
);
1802 m_nativeStatus
.queue_position
= queuePos
;
1806 catch (const lt::system_error
&err
)
1808 throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
1809 .arg(id().toString(), QString::fromLocal8Bit(err
.what())));
1812 void TorrentImpl::pause()
1816 m_stopCondition
= StopCondition::None
;
1818 m_session
->handleTorrentNeedSaveResumeData(this);
1819 m_session
->handleTorrentPaused(this);
1822 if (m_maintenanceJob
== MaintenanceJob::None
)
1824 setAutoManaged(false);
1825 m_nativeHandle
.pause();
1827 m_payloadRateMonitor
.reset();
1831 void TorrentImpl::resume(const TorrentOperatingMode mode
)
1835 m_nativeHandle
.clear_error();
1836 m_nativeHandle
.unset_flags(lt::torrent_flags::upload_mode
);
1839 m_operatingMode
= mode
;
1841 if (m_hasMissingFiles
)
1843 m_hasMissingFiles
= false;
1844 m_isStopped
= false;
1845 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1852 m_isStopped
= false;
1853 m_session
->handleTorrentNeedSaveResumeData(this);
1854 m_session
->handleTorrentResumed(this);
1857 if (m_maintenanceJob
== MaintenanceJob::None
)
1859 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1860 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1861 m_nativeHandle
.resume();
1865 void TorrentImpl::moveStorage(const Path
&newPath
, const MoveStorageContext context
)
1869 m_savePath
= newPath
;
1870 m_session
->handleTorrentSavePathChanged(this);
1874 const auto mode
= (context
== MoveStorageContext::AdjustCurrentLocation
)
1875 ? MoveStorageMode::Overwrite
: MoveStorageMode::KeepExistingFiles
;
1876 if (m_session
->addMoveTorrentStorageJob(this, newPath
, mode
, context
))
1878 if (!m_storageIsMoving
)
1880 m_storageIsMoving
= true;
1882 m_session
->handleTorrentStorageMovingStateChanged(this);
1887 void TorrentImpl::renameFile(const int index
, const Path
&path
)
1889 Q_ASSERT((index
>= 0) && (index
< filesCount()));
1890 if ((index
< 0) || (index
>= filesCount())) [[unlikely
]]
1893 const Path wantedPath
= wantedActualPath(index
, path
);
1894 doRenameFile(index
, wantedPath
);
1897 void TorrentImpl::handleStateUpdate(const lt::torrent_status
&nativeStatus
)
1899 updateStatus(nativeStatus
);
1902 void TorrentImpl::handleMoveStorageJobFinished(const Path
&path
, const MoveStorageContext context
, const bool hasOutstandingJob
)
1904 if (context
== MoveStorageContext::ChangeSavePath
)
1906 else if (context
== MoveStorageContext::ChangeDownloadPath
)
1907 m_downloadPath
= path
;
1908 m_storageIsMoving
= hasOutstandingJob
;
1909 m_nativeStatus
.save_path
= path
.toString().toStdString();
1911 m_session
->handleTorrentSavePathChanged(this);
1912 m_session
->handleTorrentNeedSaveResumeData(this);
1914 if (!m_storageIsMoving
)
1917 m_session
->handleTorrentStorageMovingStateChanged(this);
1919 if (m_hasMissingFiles
)
1921 // it can be moved to the proper location
1922 m_hasMissingFiles
= false;
1923 m_ltAddTorrentParams
.save_path
= m_nativeStatus
.save_path
;
1924 m_ltAddTorrentParams
.ti
= std::const_pointer_cast
<lt::torrent_info
>(nativeTorrentInfo());
1928 while ((m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
1929 std::invoke(m_moveFinishedTriggers
.dequeue());
1933 void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused
]] const lt::torrent_checked_alert
*p
)
1937 // The torrent is checked due to metadata received, but we should not process
1938 // this event until the torrent is reloaded using the received metadata.
1942 if (stopCondition() == StopCondition::FilesChecked
)
1945 m_statusUpdatedTriggers
.enqueue([this]()
1947 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1949 if (!m_hasMissingFiles
)
1951 if ((progress() < 1.0) && (wantedSize() > 0))
1952 m_hasFinishedStatus
= false;
1953 else if (progress() == 1.0)
1954 m_hasFinishedStatus
= true;
1956 adjustStorageLocation();
1957 manageIncompleteFiles();
1961 // torrent is internally paused using NativeTorrentExtension after files checked
1962 // so we need to resume it if there is no corresponding "stop condition" set
1963 setAutoManaged(m_operatingMode
== TorrentOperatingMode::AutoManaged
);
1964 if (m_operatingMode
== TorrentOperatingMode::Forced
)
1965 m_nativeHandle
.resume();
1969 if (m_nativeStatus
.need_save_resume
)
1970 m_session
->handleTorrentNeedSaveResumeData(this);
1972 m_session
->handleTorrentChecked(this);
1976 void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused
]] const lt::torrent_finished_alert
*p
)
1978 m_hasMissingFiles
= false;
1979 if (m_hasFinishedStatus
)
1982 m_statusUpdatedTriggers
.enqueue([this]()
1984 adjustStorageLocation();
1985 manageIncompleteFiles();
1987 m_session
->handleTorrentNeedSaveResumeData(this);
1989 const bool recheckTorrentsOnCompletion
= Preferences::instance()->recheckTorrentsOnCompletion();
1990 if (recheckTorrentsOnCompletion
&& m_unchecked
)
1996 m_hasFinishedStatus
= true;
1998 if (isMoveInProgress() || (m_renameCount
> 0))
1999 m_moveFinishedTriggers
.enqueue([this]() { m_session
->handleTorrentFinished(this); });
2001 m_session
->handleTorrentFinished(this);
2006 void TorrentImpl::handleTorrentPausedAlert([[maybe_unused
]] const lt::torrent_paused_alert
*p
)
2010 void TorrentImpl::handleTorrentResumedAlert([[maybe_unused
]] const lt::torrent_resumed_alert
*p
)
2014 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert
*p
)
2016 if (m_ltAddTorrentParams
.url_seeds
!= p
->params
.url_seeds
)
2018 // URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
2019 // Unfortunately, URL seed list containing in "resume data" is generated according to different rules
2020 // than the list we usually cache, so we have to request it from the appropriate source.
2021 fetchURLSeeds([this](const QVector
<QUrl
> &urlSeeds
) { m_urlSeeds
= urlSeeds
; });
2024 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2026 Q_ASSERT(m_indexMap
.isEmpty());
2028 const auto isSeedMode
= static_cast<bool>(m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
);
2029 m_ltAddTorrentParams
= p
->params
;
2031 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2033 m_ltAddTorrentParams
.have_pieces
.clear();
2034 m_ltAddTorrentParams
.verified_pieces
.clear();
2036 TorrentInfo metadata
= TorrentInfo(*nativeTorrentInfo());
2038 const auto &renamedFiles
= m_ltAddTorrentParams
.renamed_files
;
2039 PathList filePaths
= metadata
.filePaths();
2040 if (renamedFiles
.empty() && (m_contentLayout
!= TorrentContentLayout::Original
))
2042 const Path originalRootFolder
= Path::findRootFolder(filePaths
);
2043 const auto originalContentLayout
= (originalRootFolder
.isEmpty()
2044 ? TorrentContentLayout::NoSubfolder
2045 : TorrentContentLayout::Subfolder
);
2046 if (m_contentLayout
!= originalContentLayout
)
2048 if (m_contentLayout
== TorrentContentLayout::NoSubfolder
)
2049 Path::stripRootFolder(filePaths
);
2051 Path::addRootFolder(filePaths
, filePaths
.at(0).removedExtension());
2055 const auto nativeIndexes
= metadata
.nativeIndexes();
2056 m_indexMap
.reserve(filePaths
.size());
2057 for (int i
= 0; i
< filePaths
.size(); ++i
)
2059 const auto nativeIndex
= nativeIndexes
.at(i
);
2060 m_indexMap
[nativeIndex
] = i
;
2062 if (const auto it
= renamedFiles
.find(nativeIndex
); it
!= renamedFiles
.cend())
2063 filePaths
[i
] = Path(it
->second
);
2066 m_session
->findIncompleteFiles(metadata
, savePath(), downloadPath(), filePaths
);
2070 prepareResumeData(p
->params
);
2074 void TorrentImpl::prepareResumeData(const lt::add_torrent_params
¶ms
)
2076 if (m_hasMissingFiles
)
2078 const auto havePieces
= m_ltAddTorrentParams
.have_pieces
;
2079 const auto unfinishedPieces
= m_ltAddTorrentParams
.unfinished_pieces
;
2080 const auto verifiedPieces
= m_ltAddTorrentParams
.verified_pieces
;
2082 // Update recent resume data but preserve existing progress
2083 m_ltAddTorrentParams
= params
;
2084 m_ltAddTorrentParams
.have_pieces
= havePieces
;
2085 m_ltAddTorrentParams
.unfinished_pieces
= unfinishedPieces
;
2086 m_ltAddTorrentParams
.verified_pieces
= verifiedPieces
;
2090 const bool preserveSeedMode
= (!hasMetadata() && (m_ltAddTorrentParams
.flags
& lt::torrent_flags::seed_mode
));
2091 // Update recent resume data
2092 m_ltAddTorrentParams
= params
;
2093 if (preserveSeedMode
)
2094 m_ltAddTorrentParams
.flags
|= lt::torrent_flags::seed_mode
;
2097 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
2098 m_ltAddTorrentParams
.flags
&= ~lt::torrent_flags::upload_mode
;
2100 LoadTorrentParams resumeData
;
2101 resumeData
.name
= m_name
;
2102 resumeData
.category
= m_category
;
2103 resumeData
.tags
= m_tags
;
2104 resumeData
.contentLayout
= m_contentLayout
;
2105 resumeData
.ratioLimit
= m_ratioLimit
;
2106 resumeData
.seedingTimeLimit
= m_seedingTimeLimit
;
2107 resumeData
.inactiveSeedingTimeLimit
= m_inactiveSeedingTimeLimit
;
2108 resumeData
.firstLastPiecePriority
= m_hasFirstLastPiecePriority
;
2109 resumeData
.hasFinishedStatus
= m_hasFinishedStatus
;
2110 resumeData
.stopped
= m_isStopped
;
2111 resumeData
.stopCondition
= m_stopCondition
;
2112 resumeData
.operatingMode
= m_operatingMode
;
2113 resumeData
.ltAddTorrentParams
= m_ltAddTorrentParams
;
2114 resumeData
.useAutoTMM
= m_useAutoTMM
;
2115 if (!resumeData
.useAutoTMM
)
2117 resumeData
.savePath
= m_savePath
;
2118 resumeData
.downloadPath
= m_downloadPath
;
2121 m_session
->handleTorrentResumeDataReady(this, resumeData
);
2124 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert
*p
)
2126 if (p
->error
!= lt::errors::resume_data_not_modified
)
2128 LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
2129 .arg(name(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::CRITICAL
);
2132 m_session
->handleTorrentSaveResumeDataFailed(this);
2135 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert
*p
)
2137 // Files were probably moved or storage isn't accessible
2138 m_hasMissingFiles
= true;
2139 LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
2140 .arg(name(), QString::fromStdString(p
->message())), Log::WARNING
);
2143 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert
*p
)
2145 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2146 Q_ASSERT(fileIndex
>= 0);
2148 // Remove empty leftover folders
2149 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
2150 // be removed if they are empty
2151 const Path oldFilePath
= m_filePaths
.at(fileIndex
);
2152 const Path newFilePath
= Path(QString::fromUtf8(p
->new_name())).removedExtension(QB_EXT
);
2154 // Check if ".!qB" extension was just added or removed
2155 // We should compare path in a case sensitive manner even on case insensitive
2156 // platforms since it can be renamed by only changing case of some character(s)
2157 if (oldFilePath
.data() != newFilePath
.data())
2159 m_filePaths
[fileIndex
] = newFilePath
;
2161 Path oldParentPath
= oldFilePath
.parentPath();
2162 const Path commonBasePath
= Path::commonPath(oldParentPath
, newFilePath
.parentPath());
2163 while (oldParentPath
!= commonBasePath
)
2165 Utils::Fs::rmdir(actualStorageLocation() / oldParentPath
);
2166 oldParentPath
= oldParentPath
.parentPath();
2171 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2172 m_moveFinishedTriggers
.takeFirst()();
2174 m_session
->handleTorrentNeedSaveResumeData(this);
2177 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
*p
)
2179 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2180 Q_ASSERT(fileIndex
>= 0);
2182 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
2183 .arg(name(), filePath(fileIndex
).toString(), QString::fromLocal8Bit(p
->error
.message().c_str())), Log::WARNING
);
2186 while (!isMoveInProgress() && (m_renameCount
== 0) && !m_moveFinishedTriggers
.isEmpty())
2187 m_moveFinishedTriggers
.takeFirst()();
2189 m_session
->handleTorrentNeedSaveResumeData(this);
2192 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert
*p
)
2194 if (m_maintenanceJob
== MaintenanceJob::HandleMetadata
)
2197 const int fileIndex
= m_indexMap
.value(p
->index
, -1);
2198 Q_ASSERT(fileIndex
>= 0);
2200 m_completedFiles
.setBit(fileIndex
);
2202 const Path actualPath
= actualFilePath(fileIndex
);
2204 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
2205 // only apply Mark-of-the-Web to new download files
2206 if (isDownloading())
2208 const Path fullpath
= actualStorageLocation() / actualPath
;
2209 Utils::Misc::applyMarkOfTheWeb(fullpath
);
2211 #endif // Q_OS_MACOS || Q_OS_WIN
2213 if (m_session
->isAppendExtensionEnabled())
2215 const Path path
= filePath(fileIndex
);
2216 if (actualPath
!= path
)
2218 qDebug("Renaming %s to %s", qUtf8Printable(actualPath
.toString()), qUtf8Printable(path
.toString()));
2219 doRenameFile(fileIndex
, path
);
2224 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert
*p
)
2226 m_lastFileError
= {p
->error
, p
->op
};
2229 #ifdef QBT_USES_LIBTORRENT2
2230 void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert
*)
2232 m_session
->handleTorrentNeedSaveResumeData(this);
2236 void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused
]] const lt::metadata_received_alert
*p
)
2238 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
2240 #ifdef QBT_USES_LIBTORRENT2
2241 const InfoHash prevInfoHash
= infoHash();
2242 m_infoHash
= InfoHash(m_nativeHandle
.info_hashes());
2243 if (prevInfoHash
!= infoHash())
2244 m_session
->handleTorrentInfoHashChanged(this, prevInfoHash
);
2247 m_maintenanceJob
= MaintenanceJob::HandleMetadata
;
2248 m_session
->handleTorrentNeedSaveResumeData(this);
2251 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert
*p
) const
2253 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
))
2257 void TorrentImpl::handleCategoryOptionsChanged()
2260 adjustStorageLocation();
2263 void TorrentImpl::handleAppendExtensionToggled()
2265 if (!hasMetadata()) return;
2267 manageIncompleteFiles();
2270 void TorrentImpl::handleAlert(const lt::alert
*a
)
2274 #ifdef QBT_USES_LIBTORRENT2
2275 case lt::file_prio_alert::alert_type
:
2276 handleFilePrioAlert(static_cast<const lt::file_prio_alert
*>(a
));
2279 case lt::file_renamed_alert::alert_type
:
2280 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert
*>(a
));
2282 case lt::file_rename_failed_alert::alert_type
:
2283 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert
*>(a
));
2285 case lt::file_completed_alert::alert_type
:
2286 handleFileCompletedAlert(static_cast<const lt::file_completed_alert
*>(a
));
2288 case lt::file_error_alert::alert_type
:
2289 handleFileErrorAlert(static_cast<const lt::file_error_alert
*>(a
));
2291 case lt::torrent_finished_alert::alert_type
:
2292 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert
*>(a
));
2294 case lt::save_resume_data_alert::alert_type
:
2295 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert
*>(a
));
2297 case lt::save_resume_data_failed_alert::alert_type
:
2298 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert
*>(a
));
2300 case lt::torrent_paused_alert::alert_type
:
2301 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert
*>(a
));
2303 case lt::torrent_resumed_alert::alert_type
:
2304 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert
*>(a
));
2306 case lt::metadata_received_alert::alert_type
:
2307 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(a
));
2309 case lt::fastresume_rejected_alert::alert_type
:
2310 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert
*>(a
));
2312 case lt::torrent_checked_alert::alert_type
:
2313 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert
*>(a
));
2315 case lt::performance_alert::alert_type
:
2316 handlePerformanceAlert(static_cast<const lt::performance_alert
*>(a
));
2321 void TorrentImpl::manageIncompleteFiles()
2323 const std::shared_ptr
<const lt::torrent_info
> nativeInfo
= nativeTorrentInfo();
2324 const lt::file_storage
&nativeFiles
= nativeInfo
->files();
2326 for (int i
= 0; i
< filesCount(); ++i
)
2328 const Path path
= filePath(i
);
2330 const auto nativeIndex
= m_torrentInfo
.nativeIndexes().at(i
);
2331 const Path actualPath
{nativeFiles
.file_path(nativeIndex
)};
2332 const Path wantedPath
= wantedActualPath(i
, path
);
2333 if (actualPath
!= wantedPath
)
2335 qDebug() << "Renaming" << actualPath
.toString() << "to" << wantedPath
.toString();
2336 doRenameFile(i
, wantedPath
);
2341 void TorrentImpl::adjustStorageLocation()
2343 const Path downloadPath
= this->downloadPath();
2344 const Path targetPath
= ((isFinished() || m_hasFinishedStatus
|| downloadPath
.isEmpty()) ? savePath() : downloadPath
);
2346 if ((targetPath
!= actualStorageLocation()) || isMoveInProgress())
2347 moveStorage(targetPath
, MoveStorageContext::AdjustCurrentLocation
);
2350 void TorrentImpl::doRenameFile(const int index
, const Path
&path
)
2352 const QVector
<lt::file_index_t
> nativeIndexes
= m_torrentInfo
.nativeIndexes();
2354 Q_ASSERT(index
>= 0);
2355 Q_ASSERT(index
< nativeIndexes
.size());
2356 if ((index
< 0) || (index
>= nativeIndexes
.size())) [[unlikely
]]
2360 m_nativeHandle
.rename_file(nativeIndexes
[index
], path
.toString().toStdString());
2363 lt::torrent_handle
TorrentImpl::nativeHandle() const
2365 return m_nativeHandle
;
2368 void TorrentImpl::setMetadata(const TorrentInfo
&torrentInfo
)
2373 m_session
->invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
]
2377 #ifdef QBT_USES_LIBTORRENT2
2378 nativeHandle
.set_metadata(torrentInfo
.nativeInfo()->info_section());
2380 const std::shared_ptr
<lt::torrent_info
> nativeInfo
= torrentInfo
.nativeInfo();
2381 nativeHandle
.set_metadata(lt::span
<const char>(nativeInfo
->metadata().get(), nativeInfo
->metadata_size()));
2384 catch (const std::exception
&) {}
2388 Torrent::StopCondition
TorrentImpl::stopCondition() const
2390 return m_stopCondition
;
2393 void TorrentImpl::setStopCondition(const StopCondition stopCondition
)
2395 if (stopCondition
== m_stopCondition
)
2401 if ((stopCondition
== StopCondition::MetadataReceived
) && hasMetadata())
2404 if ((stopCondition
== StopCondition::FilesChecked
) && hasMetadata() && !isChecking())
2407 m_stopCondition
= stopCondition
;
2410 bool TorrentImpl::isMoveInProgress() const
2412 return m_storageIsMoving
;
2415 void TorrentImpl::updateStatus(const lt::torrent_status
&nativeStatus
)
2417 const lt::torrent_status oldStatus
= std::exchange(m_nativeStatus
, nativeStatus
);
2419 if (m_nativeStatus
.num_pieces
!= oldStatus
.num_pieces
)
2424 m_payloadRateMonitor
.addSample({nativeStatus
.download_payload_rate
2425 , nativeStatus
.upload_payload_rate
});
2429 // NOTE: Don't change the order of these conditionals!
2430 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2432 m_unchecked
= false;
2433 else if (isDownloading())
2437 while (!m_statusUpdatedTriggers
.isEmpty())
2438 std::invoke(m_statusUpdatedTriggers
.dequeue());
2441 void TorrentImpl::updateProgress()
2443 Q_ASSERT(hasMetadata());
2444 if (!hasMetadata()) [[unlikely
]]
2447 Q_ASSERT(!m_filesProgress
.isEmpty());
2448 if (m_filesProgress
.isEmpty()) [[unlikely
]]
2449 m_filesProgress
.resize(filesCount());
2451 const QBitArray oldPieces
= std::exchange(m_pieces
, LT::toQBitArray(m_nativeStatus
.pieces
));
2452 const QBitArray newPieces
= m_pieces
^ oldPieces
;
2454 const int64_t pieceSize
= m_torrentInfo
.pieceLength();
2455 for (qsizetype index
= 0; index
< newPieces
.size(); ++index
)
2457 if (!newPieces
.at(index
))
2460 int64_t size
= m_torrentInfo
.pieceLength(index
);
2461 int64_t pieceOffset
= index
* pieceSize
;
2463 for (const int fileIndex
: asConst(m_torrentInfo
.fileIndicesForPiece(index
)))
2465 const int64_t fileOffsetInPiece
= pieceOffset
- m_torrentInfo
.fileOffset(fileIndex
);
2466 const int64_t add
= std::min
<int64_t>((m_torrentInfo
.fileSize(fileIndex
) - fileOffsetInPiece
), size
);
2468 m_filesProgress
[fileIndex
] += add
;
2479 void TorrentImpl::setRatioLimit(qreal limit
)
2481 if (limit
< USE_GLOBAL_RATIO
)
2482 limit
= NO_RATIO_LIMIT
;
2483 else if (limit
> MAX_RATIO
)
2486 if (m_ratioLimit
!= limit
)
2488 m_ratioLimit
= limit
;
2489 m_session
->handleTorrentNeedSaveResumeData(this);
2490 m_session
->handleTorrentShareLimitChanged(this);
2494 void TorrentImpl::setSeedingTimeLimit(int limit
)
2496 if (limit
< USE_GLOBAL_SEEDING_TIME
)
2497 limit
= NO_SEEDING_TIME_LIMIT
;
2498 else if (limit
> MAX_SEEDING_TIME
)
2499 limit
= MAX_SEEDING_TIME
;
2501 if (m_seedingTimeLimit
!= limit
)
2503 m_seedingTimeLimit
= limit
;
2504 m_session
->handleTorrentNeedSaveResumeData(this);
2505 m_session
->handleTorrentShareLimitChanged(this);
2509 void TorrentImpl::setInactiveSeedingTimeLimit(int limit
)
2511 if (limit
< USE_GLOBAL_INACTIVE_SEEDING_TIME
)
2512 limit
= NO_INACTIVE_SEEDING_TIME_LIMIT
;
2513 else if (limit
> MAX_INACTIVE_SEEDING_TIME
)
2514 limit
= MAX_SEEDING_TIME
;
2516 if (m_inactiveSeedingTimeLimit
!= limit
)
2518 m_inactiveSeedingTimeLimit
= limit
;
2519 m_session
->handleTorrentNeedSaveResumeData(this);
2520 m_session
->handleTorrentShareLimitChanged(this);
2524 void TorrentImpl::setUploadLimit(const int limit
)
2526 const int cleanValue
= cleanLimitValue(limit
);
2527 if (cleanValue
== uploadLimit())
2530 m_uploadLimit
= cleanValue
;
2531 m_nativeHandle
.set_upload_limit(m_uploadLimit
);
2532 m_session
->handleTorrentNeedSaveResumeData(this);
2535 void TorrentImpl::setDownloadLimit(const int limit
)
2537 const int cleanValue
= cleanLimitValue(limit
);
2538 if (cleanValue
== downloadLimit())
2541 m_downloadLimit
= cleanValue
;
2542 m_nativeHandle
.set_download_limit(m_downloadLimit
);
2543 m_session
->handleTorrentNeedSaveResumeData(this);
2546 void TorrentImpl::setSuperSeeding(const bool enable
)
2548 if (enable
== superSeeding())
2552 m_nativeHandle
.set_flags(lt::torrent_flags::super_seeding
);
2554 m_nativeHandle
.unset_flags(lt::torrent_flags::super_seeding
);
2556 m_session
->handleTorrentNeedSaveResumeData(this);
2559 void TorrentImpl::setDHTDisabled(const bool disable
)
2561 if (disable
== isDHTDisabled())
2565 m_nativeHandle
.set_flags(lt::torrent_flags::disable_dht
);
2567 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_dht
);
2569 m_session
->handleTorrentNeedSaveResumeData(this);
2572 void TorrentImpl::setPEXDisabled(const bool disable
)
2574 if (disable
== isPEXDisabled())
2578 m_nativeHandle
.set_flags(lt::torrent_flags::disable_pex
);
2580 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_pex
);
2582 m_session
->handleTorrentNeedSaveResumeData(this);
2585 void TorrentImpl::setLSDDisabled(const bool disable
)
2587 if (disable
== isLSDDisabled())
2591 m_nativeHandle
.set_flags(lt::torrent_flags::disable_lsd
);
2593 m_nativeHandle
.unset_flags(lt::torrent_flags::disable_lsd
);
2595 m_session
->handleTorrentNeedSaveResumeData(this);
2598 void TorrentImpl::flushCache() const
2600 m_nativeHandle
.flush_cache();
2603 QString
TorrentImpl::createMagnetURI() const
2605 QString ret
= u
"magnet:?"_s
;
2607 const SHA1Hash infoHash1
= infoHash().v1();
2608 if (infoHash1
.isValid())
2610 ret
+= u
"xt=urn:btih:" + infoHash1
.toString();
2613 const SHA256Hash infoHash2
= infoHash().v2();
2614 if (infoHash2
.isValid())
2616 if (infoHash1
.isValid())
2618 ret
+= u
"xt=urn:btmh:1220" + infoHash2
.toString();
2621 const QString displayName
= name();
2622 if (displayName
!= id().toString())
2624 ret
+= u
"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName
));
2627 for (const TrackerEntry
&tracker
: asConst(trackers()))
2629 ret
+= u
"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker
.url
));
2632 for (const QUrl
&urlSeed
: asConst(urlSeeds()))
2634 ret
+= u
"&ws=" + QString::fromLatin1(urlSeed
.toEncoded());
2640 nonstd::expected
<lt::entry
, QString
> TorrentImpl::exportTorrent() const
2643 return nonstd::make_unexpected(tr("Missing metadata"));
2647 #ifdef QBT_USES_LIBTORRENT2
2648 const std::shared_ptr
<lt::torrent_info
> completeTorrentInfo
= m_nativeHandle
.torrent_file_with_hashes();
2649 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= (completeTorrentInfo
? completeTorrentInfo
: info().nativeInfo());
2651 const std::shared_ptr
<lt::torrent_info
> torrentInfo
= info().nativeInfo();
2653 lt::create_torrent creator
{*torrentInfo
};
2655 for (const TrackerEntry
&entry
: asConst(trackers()))
2656 creator
.add_tracker(entry
.url
.toStdString(), entry
.tier
);
2658 return creator
.generate();
2660 catch (const lt::system_error
&err
)
2662 return nonstd::make_unexpected(QString::fromLocal8Bit(err
.what()));
2666 nonstd::expected
<QByteArray
, QString
> TorrentImpl::exportToBuffer() const
2668 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2669 if (!preparationResult
)
2670 return preparationResult
.get_unexpected();
2672 // usually torrent size should be smaller than 1 MB,
2673 // however there are >100 MB v2/hybrid torrent files out in the wild
2675 buffer
.reserve(1024 * 1024);
2676 lt::bencode(std::back_inserter(buffer
), preparationResult
.value());
2680 nonstd::expected
<void, QString
> TorrentImpl::exportToFile(const Path
&path
) const
2682 const nonstd::expected
<lt::entry
, QString
> preparationResult
= exportTorrent();
2683 if (!preparationResult
)
2684 return preparationResult
.get_unexpected();
2686 const nonstd::expected
<void, QString
> saveResult
= Utils::IO::saveToFile(path
, preparationResult
.value());
2688 return saveResult
.get_unexpected();
2693 void TorrentImpl::fetchPeerInfo(std::function
<void (QVector
<PeerInfo
>)> resultHandler
) const
2695 invokeAsync([nativeHandle
= m_nativeHandle
, allPieces
= pieces()]() -> QVector
<PeerInfo
>
2699 std::vector
<lt::peer_info
> nativePeers
;
2700 nativeHandle
.get_peer_info(nativePeers
);
2701 QVector
<PeerInfo
> peers
;
2702 peers
.reserve(static_cast<decltype(peers
)::size_type
>(nativePeers
.size()));
2703 for (const lt::peer_info
&peer
: nativePeers
)
2704 peers
.append(PeerInfo(peer
, allPieces
));
2707 catch (const std::exception
&) {}
2711 , std::move(resultHandler
));
2714 void TorrentImpl::fetchURLSeeds(std::function
<void (QVector
<QUrl
>)> resultHandler
) const
2716 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<QUrl
>
2720 const std::set
<std::string
> currentSeeds
= nativeHandle
.url_seeds();
2721 QVector
<QUrl
> urlSeeds
;
2722 urlSeeds
.reserve(static_cast<decltype(urlSeeds
)::size_type
>(currentSeeds
.size()));
2723 for (const std::string
&urlSeed
: currentSeeds
)
2724 urlSeeds
.append(QString::fromStdString(urlSeed
));
2727 catch (const std::exception
&) {}
2731 , std::move(resultHandler
));
2734 void TorrentImpl::fetchPieceAvailability(std::function
<void (QVector
<int>)> resultHandler
) const
2736 invokeAsync([nativeHandle
= m_nativeHandle
]() -> QVector
<int>
2740 std::vector
<int> piecesAvailability
;
2741 nativeHandle
.piece_availability(piecesAvailability
);
2742 return QVector
<int>(piecesAvailability
.cbegin(), piecesAvailability
.cend());
2744 catch (const std::exception
&) {}
2748 , std::move(resultHandler
));
2751 void TorrentImpl::fetchDownloadingPieces(std::function
<void (QBitArray
)> resultHandler
) const
2753 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QBitArray
2757 #ifdef QBT_USES_LIBTORRENT2
2758 const std::vector
<lt::partial_piece_info
> queue
= nativeHandle
.get_download_queue();
2760 std::vector
<lt::partial_piece_info
> queue
;
2761 nativeHandle
.get_download_queue(queue
);
2764 result
.resize(torrentInfo
.piecesCount());
2765 for (const lt::partial_piece_info
&info
: queue
)
2766 result
.setBit(LT::toUnderlyingType(info
.piece_index
));
2769 catch (const std::exception
&) {}
2773 , std::move(resultHandler
));
2776 void TorrentImpl::fetchAvailableFileFractions(std::function
<void (QVector
<qreal
>)> resultHandler
) const
2778 invokeAsync([nativeHandle
= m_nativeHandle
, torrentInfo
= m_torrentInfo
]() -> QVector
<qreal
>
2780 if (!torrentInfo
.isValid() || (torrentInfo
.filesCount() <= 0))
2785 std::vector
<int> piecesAvailability
;
2786 nativeHandle
.piece_availability(piecesAvailability
);
2787 const int filesCount
= torrentInfo
.filesCount();
2788 // libtorrent returns empty array for seeding only torrents
2789 if (piecesAvailability
.empty())
2790 return QVector
<qreal
>(filesCount
, -1);
2792 QVector
<qreal
> result
;
2793 result
.reserve(filesCount
);
2794 for (int i
= 0; i
< filesCount
; ++i
)
2796 const TorrentInfo::PieceRange filePieces
= torrentInfo
.filePieces(i
);
2798 int availablePieces
= 0;
2799 for (const int piece
: filePieces
)
2800 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2802 const qreal availability
= filePieces
.isEmpty()
2803 ? 1 // the file has no pieces, so it is available by default
2804 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2805 result
.append(availability
);
2809 catch (const std::exception
&) {}
2813 , std::move(resultHandler
));
2816 void TorrentImpl::prioritizeFiles(const QVector
<DownloadPriority
> &priorities
)
2818 if (!hasMetadata()) return;
2820 Q_ASSERT(priorities
.size() == filesCount());
2822 // Reset 'm_hasSeedStatus' if needed in order to react again to
2823 // 'torrent_finished_alert' and eg show tray notifications
2824 const QVector
<DownloadPriority
> oldPriorities
= filePriorities();
2825 for (int i
= 0; i
< oldPriorities
.size(); ++i
)
2827 if ((oldPriorities
[i
] == DownloadPriority::Ignored
)
2828 && (priorities
[i
] > DownloadPriority::Ignored
)
2829 && !m_completedFiles
.at(i
))
2831 m_hasFinishedStatus
= false;
2836 const int internalFilesCount
= m_torrentInfo
.nativeInfo()->files().num_files(); // including .pad files
2837 auto nativePriorities
= std::vector
<lt::download_priority_t
>(internalFilesCount
, LT::toNative(DownloadPriority::Normal
));
2838 const auto nativeIndexes
= m_torrentInfo
.nativeIndexes();
2839 for (int i
= 0; i
< priorities
.size(); ++i
)
2840 nativePriorities
[LT::toUnderlyingType(nativeIndexes
[i
])] = LT::toNative(priorities
[i
]);
2842 qDebug() << Q_FUNC_INFO
<< "Changing files priorities...";
2843 m_nativeHandle
.prioritize_files(nativePriorities
);
2845 m_filePriorities
= priorities
;
2846 // Restore first/last piece first option if necessary
2847 if (m_hasFirstLastPiecePriority
)
2848 applyFirstLastPiecePriority(true);
2851 QVector
<qreal
> TorrentImpl::availableFileFractions() const
2853 Q_ASSERT(hasMetadata());
2855 const int filesCount
= this->filesCount();
2856 if (filesCount
<= 0) return {};
2858 const QVector
<int> piecesAvailability
= pieceAvailability();
2859 // libtorrent returns empty array for seeding only torrents
2860 if (piecesAvailability
.empty()) return QVector
<qreal
>(filesCount
, -1);
2863 res
.reserve(filesCount
);
2864 for (int i
= 0; i
< filesCount
; ++i
)
2866 const TorrentInfo::PieceRange filePieces
= m_torrentInfo
.filePieces(i
);
2868 int availablePieces
= 0;
2869 for (const int piece
: filePieces
)
2870 availablePieces
+= (piecesAvailability
[piece
] > 0) ? 1 : 0;
2872 const qreal availability
= filePieces
.isEmpty()
2873 ? 1 // the file has no pieces, so it is available by default
2874 : static_cast<qreal
>(availablePieces
) / filePieces
.size();
2875 res
.push_back(availability
);
2880 template <typename Func
, typename Callback
>
2881 void TorrentImpl::invokeAsync(Func func
, Callback resultHandler
) const
2883 m_session
->invokeAsync([session
= m_session
2884 , func
= std::move(func
)
2885 , resultHandler
= std::move(resultHandler
)
2886 , thisTorrent
= QPointer
<const TorrentImpl
>(this)]() mutable
2888 session
->invoke([result
= func(), thisTorrent
, resultHandler
= std::move(resultHandler
)]
2891 resultHandler(result
);