diff --git a/src/base/bittorrent/torrentcontenthandler.h b/src/base/bittorrent/torrentcontenthandler.h index 6046265de..1604cbe52 100644 --- a/src/base/bittorrent/torrentcontenthandler.h +++ b/src/base/bittorrent/torrentcontenthandler.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2022-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -46,7 +46,6 @@ namespace BitTorrent virtual Path actualFilePath(int fileIndex) const = 0; virtual QVector filePriorities() const = 0; virtual QVector filesProgress() const = 0; - virtual void fetchFilesProgress(std::function)> resultHandler) const = 0; /** * @brief fraction of file pieces that are available at least from one peer * diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 32082fae4..e1b5dd86a 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -279,6 +279,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession , LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored)); m_completedFiles.fill(static_cast(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount); + m_filesProgress.resize(filesCount); for (int i = 0; i < filesCount; ++i) { @@ -306,6 +307,9 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession m_urlSeeds.append(QString::fromStdString(urlSeed)); m_nativeStatus = extensionData->status; + if (hasMetadata()) + updateProgress(); + updateState(); if (hasMetadata()) @@ -1184,20 +1188,20 @@ QVector TorrentImpl::filesProgress() const if (!hasMetadata()) return {}; - if (m_completedFiles.count(true) == filesCount()) - return QVector(filesCount(), 1); + const int count = m_filesProgress.size(); + Q_ASSERT(count == filesCount()); + if (Q_UNLIKELY(count != filesCount())) + return {}; - std::vector fp; - m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity); + if (m_completedFiles.count(true) == count) + return QVector(count, 1); - const int count = filesCount(); - const auto nativeIndexes = m_torrentInfo.nativeIndexes(); QVector result; result.reserve(count); for (int i = 0; i < count; ++i) { - const int64_t progress = fp[LT::toUnderlyingType(nativeIndexes[i])]; - const qlonglong size = fileSize(i); + const int64_t progress = m_filesProgress.at(i); + const int64_t size = fileSize(i); if ((size <= 0) || (progress == size)) result << 1; else @@ -1323,8 +1327,6 @@ QVector TorrentImpl::peers() const QBitArray TorrentImpl::pieces() const { - if (m_pieces.isEmpty()) - m_pieces = LT::toQBitArray(m_nativeStatus.pieces); return m_pieces; } @@ -1469,7 +1471,8 @@ void TorrentImpl::forceDHTAnnounce() void TorrentImpl::forceRecheck() { - if (!hasMetadata()) return; + if (!hasMetadata()) + return; m_nativeHandle.force_recheck(); // We have to force update the cached state, otherwise someone will be able to get @@ -1478,7 +1481,12 @@ void TorrentImpl::forceRecheck() m_hasMissingFiles = false; m_unchecked = false; + m_completedFiles.fill(false); + m_filesProgress.fill(0); + m_pieces.fill(false); + m_nativeStatus.pieces.clear_all(); + m_nativeStatus.num_pieces = 0; if (isPaused()) { @@ -1603,6 +1611,8 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored)); m_completedFiles.fill(static_cast(p.flags & lt::torrent_flags::seed_mode), filesCount()); + m_filesProgress.resize(filesCount()); + updateProgress(); for (int i = 0; i < fileNames.size(); ++i) { @@ -1650,7 +1660,10 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi void TorrentImpl::reload() { m_completedFiles.fill(false); - m_pieces.clear(); + m_filesProgress.fill(0); + m_pieces.fill(false); + m_nativeStatus.pieces.clear_all(); + m_nativeStatus.num_pieces = 0; const auto queuePos = m_nativeHandle.queue_position(); @@ -2279,8 +2292,11 @@ bool TorrentImpl::isMoveInProgress() const void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus) { - m_pieces.clear(); - m_nativeStatus = nativeStatus; + const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus); + + if (m_nativeStatus.num_pieces != oldStatus.num_pieces) + updateProgress(); + updateState(); m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate @@ -2300,6 +2316,44 @@ void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus) std::invoke(m_statusUpdatedTriggers.dequeue()); } +void TorrentImpl::updateProgress() +{ + Q_ASSERT(hasMetadata()); + if (Q_UNLIKELY(!hasMetadata())) + return; + + Q_ASSERT(!m_filesProgress.isEmpty()); + if (Q_UNLIKELY(m_filesProgress.isEmpty())) + m_filesProgress.resize(filesCount()); + + const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces)); + const QBitArray newPieces = m_pieces ^ oldPieces; + + const int64_t pieceSize = m_torrentInfo.pieceLength(); + for (qsizetype index = 0; index < newPieces.size(); ++index) + { + if (!newPieces.at(index)) + continue; + + int64_t size = m_torrentInfo.pieceLength(index); + int64_t pieceOffset = index * pieceSize; + + for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index))) + { + const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex); + const int64_t add = std::min((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size); + + m_filesProgress[fileIndex] += add; + + size -= add; + if (size <= 0) + break; + + pieceOffset += add; + } + } +} + void TorrentImpl::setRatioLimit(qreal limit) { if (limit < USE_GLOBAL_RATIO) @@ -2540,48 +2594,6 @@ void TorrentImpl::fetchURLSeeds(std::function)> resultHandle , std::move(resultHandler)); } -void TorrentImpl::fetchFilesProgress(std::function)> resultHandler) const -{ - invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo - , completedFiles = m_completedFiles]() -> QVector - { - if (!torrentInfo.isValid()) - return {}; - - const int filesCount = torrentInfo.filesCount(); - if (completedFiles.count(true) == filesCount) - return QVector(filesCount, 1); - - try - { -#ifdef QBT_USES_LIBTORRENT2 - const std::vector fp = nativeHandle.file_progress(lt::torrent_handle::piece_granularity); -#else - std::vector fp; - nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity); -#endif - - const auto nativeIndexes = torrentInfo.nativeIndexes(); - QVector result; - result.reserve(filesCount); - for (int i = 0; i < filesCount; ++i) - { - const int64_t progress = fp[LT::toUnderlyingType(nativeIndexes[i])]; - const qlonglong size = torrentInfo.fileSize(i); - if ((size <= 0) || (progress == size)) - result.append(1); - else - result.append(progress / static_cast(size)); - } - return result; - } - catch (const std::exception &) {} - - return {}; - } - , std::move(resultHandler)); -} - void TorrentImpl::fetchPieceAvailability(std::function)> resultHandler) const { invokeAsync([nativeHandle = m_nativeHandle]() -> QVector diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index eeccd37a0..e5afb5cc6 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2022 Vladimir Golovnev + * Copyright (C) 2015-2023 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -238,7 +238,6 @@ namespace BitTorrent void fetchPeerInfo(std::function)> resultHandler) const override; void fetchURLSeeds(std::function)> resultHandler) const override; - void fetchFilesProgress(std::function)> resultHandler) const override; void fetchPieceAvailability(std::function)> resultHandler) const override; void fetchDownloadingPieces(std::function resultHandler) const override; void fetchAvailableFileFractions(std::function)> resultHandler) const override; @@ -263,6 +262,7 @@ namespace BitTorrent std::shared_ptr nativeTorrentInfo() const; void updateStatus(const lt::torrent_status &nativeStatus); + void updateProgress(); void updateState(); void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p); @@ -354,6 +354,7 @@ namespace BitTorrent int m_downloadLimit = 0; int m_uploadLimit = 0; - mutable QBitArray m_pieces; + QBitArray m_pieces; + QVector m_filesProgress; }; } diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 4219e934b..342fec91e 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2022-2023 Vladimir Golovnev * Copyright (C) 2012 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -237,11 +237,6 @@ public: return QVector(filesCount(), 0); } - void fetchFilesProgress(std::function)> resultHandler) const override - { - resultHandler(filesProgress()); - } - QVector availableFileFractions() const override { return QVector(filesCount(), 0); diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index ff76d6833..2644cb455 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2022-2023 Vladimir Golovnev * Copyright (C) 2006-2012 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -216,23 +216,17 @@ void TorrentContentModel::updateFilesProgress() { Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata()); - using HandlerPtr = QPointer; - m_contentHandler->fetchFilesProgress([this, handler = HandlerPtr(m_contentHandler)](const QVector &filesProgress) - { - if (handler != m_contentHandler) - return; - - Q_ASSERT(m_filesIndex.size() == filesProgress.size()); - // XXX: Why is this necessary? - if (Q_UNLIKELY(m_filesIndex.size() != filesProgress.size())) - return; + const QVector &filesProgress = m_contentHandler->filesProgress(); + Q_ASSERT(m_filesIndex.size() == filesProgress.size()); + // XXX: Why is this necessary? + if (Q_UNLIKELY(m_filesIndex.size() != filesProgress.size())) + return; - for (int i = 0; i < filesProgress.size(); ++i) - m_filesIndex[i]->setProgress(filesProgress[i]); - // Update folders progress in the tree - m_rootItem->recalculateProgress(); - m_rootItem->recalculateAvailability(); - }); + for (int i = 0; i < filesProgress.size(); ++i) + m_filesIndex[i]->setProgress(filesProgress[i]); + // Update folders progress in the tree + m_rootItem->recalculateProgress(); + m_rootItem->recalculateAvailability(); } void TorrentContentModel::updateFilesPriorities()