From 4e04cd27c90295efbc0d9b40316ee77f703b37e2 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Wed, 2 Dec 2020 09:17:13 +0300 Subject: [PATCH] Fix received metadata handling --- .../bittorrent/nativetorrentextension.cpp | 8 + src/base/bittorrent/nativetorrentextension.h | 2 + src/base/bittorrent/session.cpp | 9 +- src/base/bittorrent/torrenthandleimpl.cpp | 164 +++++++++++++----- src/base/bittorrent/torrenthandleimpl.h | 16 +- 5 files changed, 153 insertions(+), 46 deletions(-) diff --git a/src/base/bittorrent/nativetorrentextension.cpp b/src/base/bittorrent/nativetorrentextension.cpp index 9fb491766..ff79d6d73 100644 --- a/src/base/bittorrent/nativetorrentextension.cpp +++ b/src/base/bittorrent/nativetorrentextension.cpp @@ -52,3 +52,11 @@ bool NativeTorrentExtension::on_pause() // and other extensions to be also invoked. return false; } + +void NativeTorrentExtension::on_state(const lt::torrent_status::state_t state) +{ + if (m_state == lt::torrent_status::downloading_metadata) + m_torrentHandle.set_flags(lt::torrent_flags::stop_when_ready); + + m_state = state; +} diff --git a/src/base/bittorrent/nativetorrentextension.h b/src/base/bittorrent/nativetorrentextension.h index a62bee787..8e987e03c 100644 --- a/src/base/bittorrent/nativetorrentextension.h +++ b/src/base/bittorrent/nativetorrentextension.h @@ -38,6 +38,8 @@ public: private: bool on_pause() override; + void on_state(lt::torrent_status::state_t state) override; lt::torrent_handle m_torrentHandle; + lt::torrent_status::state_t m_state = lt::torrent_status::checking_resume_data; }; diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 39b536102..a6947956c 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -1774,6 +1774,13 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result) void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames) { + TorrentHandleImpl *torrent = m_torrents.value(id); + if (torrent) + { + torrent->fileSearchFinished(savePath, fileNames); + return; + } + const auto loadingTorrentsIter = m_loadingTorrents.find(id); if (loadingTorrentsIter != m_loadingTorrents.end()) { @@ -4595,7 +4602,7 @@ void Session::createTorrentHandle(const lt::torrent_handle &nativeHandle) const LoadTorrentParams params = m_loadingTorrents.take(nativeHandle.info_hash()); - auto *const torrent = new TorrentHandleImpl {this, nativeHandle, params}; + auto *const torrent = new TorrentHandleImpl {this, m_nativeSession, nativeHandle, params}; m_torrents.insert(torrent->hash(), torrent); const bool hasMetadata = torrent->hasMetadata(); diff --git a/src/base/bittorrent/torrenthandleimpl.cpp b/src/base/bittorrent/torrenthandleimpl.cpp index 792a5a72a..f8261a6c1 100644 --- a/src/base/bittorrent/torrenthandleimpl.cpp +++ b/src/base/bittorrent/torrenthandleimpl.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -101,10 +102,11 @@ namespace // TorrentHandleImpl -TorrentHandleImpl::TorrentHandleImpl(Session *session, const lt::torrent_handle &nativeHandle, - const LoadTorrentParams ¶ms) +TorrentHandleImpl::TorrentHandleImpl(Session *session, lt::session *nativeSession + , const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms) : QObject(session) , m_session(session) + , m_nativeSession(nativeSession) , m_nativeHandle(nativeHandle) , m_name(params.name) , m_savePath(Utils::Fs::toNativePath(params.savePath)) @@ -124,7 +126,12 @@ TorrentHandleImpl::TorrentHandleImpl(Session *session, const lt::torrent_handle m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category)); m_hash = InfoHash {m_nativeHandle.info_hash()}; - m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; + if (m_ltAddTorrentParams.ti) + { + // Initialize it only if torrent is added with metadata. + // Otherwise it should be initialized in "Metadata received" handler. + m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; + } updateStatus(); @@ -648,7 +655,9 @@ bool TorrentHandleImpl::isPaused() const bool TorrentHandleImpl::isQueued() const { // Torrent is Queued if it isn't in Paused state but paused internally - return ((m_nativeStatus.flags & lt::torrent_flags::paused) && !isPaused()); + return (!isPaused() + && (m_nativeStatus.flags & lt::torrent_flags::auto_managed) + && (m_nativeStatus.flags & lt::torrent_flags::paused)); } bool TorrentHandleImpl::isChecking() const @@ -755,10 +764,19 @@ void TorrentHandleImpl::updateState() { m_state = TorrentState::Error; } + else if (!hasMetadata()) + { + if (isPaused()) + m_state = TorrentState::PausedDownloading; + else if (m_session->isQueueingSystemEnabled() && isQueued()) + m_state = TorrentState::QueuedDownloading; + else + m_state = TorrentState::DownloadingMetadata; + } else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && (!isPaused() || (m_nativeStatus.flags & lt::torrent_flags::auto_managed) || !(m_nativeStatus.flags & lt::torrent_flags::paused))) - { + { // If the torrent is not just in the "checking" state, but is being actually checked m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading; } @@ -781,8 +799,6 @@ void TorrentHandleImpl::updateState() m_state = TorrentState::PausedDownloading; else if (m_session->isQueueingSystemEnabled() && isQueued()) m_state = TorrentState::QueuedDownloading; - else if (m_nativeStatus.state == lt::torrent_status::downloading_metadata) // must come after queue check - m_state = TorrentState::DownloadingMetadata; else if (isForced()) m_state = TorrentState::ForcedDownloading; else if (m_nativeStatus.download_payload_rate > 0) @@ -896,6 +912,9 @@ qlonglong TorrentHandleImpl::eta() const QVector TorrentHandleImpl::filesProgress() const { + if (!hasMetadata()) + return {}; + std::vector fp; m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity); @@ -1283,18 +1302,66 @@ void TorrentHandleImpl::applyFirstLastPiecePriority(const bool enabled, const QV m_nativeHandle.prioritize_pieces(piecePriorities); } -void TorrentHandleImpl::pause() +void TorrentHandleImpl::fileSearchFinished(const QString &savePath, const QStringList &fileNames) { - setAutoManaged(false); - m_nativeHandle.pause(); + endReceivedMetadataHandling(savePath, fileNames); +} + +void TorrentHandleImpl::endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames) +{ + const auto queuePos = m_nativeHandle.queue_position(); + + lt::add_torrent_params p = m_ltAddTorrentParams; + p.ti = std::const_pointer_cast(m_nativeHandle.torrent_file()); + + m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile); - m_speedMonitor.reset(); + for (int i = 0; i < fileNames.size(); ++i) + p.renamed_files[lt::file_index_t {i}] = fileNames[i].toStdString(); + + p.save_path = Utils::Fs::toNativePath(savePath).toStdString(); + p.flags |= lt::torrent_flags::update_subscribe + | lt::torrent_flags::override_trackers + | lt::torrent_flags::override_web_seeds; + + m_nativeHandle = m_nativeSession->add_torrent(p); + m_nativeHandle.queue_position_set(queuePos); + + // If first/last piece priority was specified when adding this torrent, + // we should apply it now that we have metadata: + if (m_hasFirstLastPiecePriority) + applyFirstLastPiecePriority(true); + if (!m_isStopped) + { + setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged); + if (m_operatingMode == TorrentOperatingMode::Forced) + m_nativeHandle.resume(); + } + + m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; + m_maintenanceJob = MaintenanceJob::None; + + updateStatus(); + + m_session->handleTorrentMetadataReceived(this); +} + +void TorrentHandleImpl::pause() +{ if (!m_isStopped) { m_isStopped = true; m_session->handleTorrentPaused(this); } + + if (m_maintenanceJob == MaintenanceJob::None) + { + setAutoManaged(false); + m_nativeHandle.pause(); + + m_speedMonitor.reset(); + } } void TorrentHandleImpl::resume(const TorrentOperatingMode mode) @@ -1308,24 +1375,24 @@ void TorrentHandleImpl::resume(const TorrentOperatingMode mode) m_nativeHandle.force_recheck(); } + m_operatingMode = mode; + if (m_isStopped) { // Torrent may have been temporarily resumed to perform checking files // so we have to ensure it will not pause after checking is done. m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready); - } - setAutoManaged(mode == TorrentOperatingMode::AutoManaged); - if (mode == TorrentOperatingMode::Forced) - m_nativeHandle.resume(); - - m_operatingMode = mode; - - if (m_isStopped) - { m_isStopped = false; m_session->handleTorrentResumed(this); } + + if (m_maintenanceJob == MaintenanceJob::None) + { + setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged); + if (m_operatingMode == TorrentOperatingMode::Forced) + m_nativeHandle.resume(); + } } void TorrentHandleImpl::moveStorage(const QString &newPath, const MoveStorageMode mode) @@ -1410,7 +1477,15 @@ void TorrentHandleImpl::handleTrackerErrorAlert(const lt::tracker_error_alert *p void TorrentHandleImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p) { Q_UNUSED(p); - qDebug("\"%s\" have just finished checking", qUtf8Printable(name())); + qDebug("\"%s\" have just finished checking.", qUtf8Printable(name())); + + + if (!hasMetadata()) + { + // The torrent is checked due to metadata received, but we should not process + // this event until the torrent is reloaded using the received metadata. + return; + } if (m_fastresumeDataRejected && !m_hasMissingFiles) { @@ -1500,6 +1575,18 @@ void TorrentHandleImpl::handleSaveResumeDataAlert(const lt::save_resume_data_ale m_ltAddTorrentParams.save_path = Profile::instance()->toPortablePath( QString::fromStdString(m_ltAddTorrentParams.save_path)).toStdString(); + if (m_maintenanceJob == MaintenanceJob::HandleMetadata) + { + m_ltAddTorrentParams.have_pieces.clear(); + m_ltAddTorrentParams.verified_pieces.clear(); + + TorrentInfo metadata = TorrentInfo {m_nativeHandle.torrent_file()}; + if (!m_hasRootFolder) + metadata.stripRootFolder(); + + m_session->findIncompleteFiles(metadata, m_savePath); + } + auto resumeDataPtr = std::make_shared(lt::write_resume_data(m_ltAddTorrentParams)); lt::entry &resumeData = *resumeDataPtr; @@ -1643,20 +1730,9 @@ void TorrentHandleImpl::handleMetadataReceivedAlert(const lt::metadata_received_ Q_UNUSED(p); qDebug("Metadata received for torrent %s.", qUtf8Printable(name())); - m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; - if (m_session->isAppendExtensionEnabled()) - manageIncompleteFiles(); - if (!m_hasRootFolder) - m_torrentInfo.stripRootFolder(); - if (filesCount() == 1) - m_hasRootFolder = false; - m_session->handleTorrentMetadataReceived(this); - - // If first/last piece priority was specified when adding this torrent, - // we should apply it now that we have metadata: - if (m_hasFirstLastPiecePriority) - applyFirstLastPiecePriority(true); + m_maintenanceJob = MaintenanceJob::HandleMetadata; + saveResumeData(); } void TorrentHandleImpl::handlePerformanceAlert(const lt::performance_alert *p) const @@ -1828,18 +1904,20 @@ void TorrentHandleImpl::updateStatus() void TorrentHandleImpl::updateStatus(const lt::torrent_status &nativeStatus) { m_nativeStatus = nativeStatus; - updateState(); - // NOTE: Don't change the order of these conditionals! - // Otherwise it will not work properly since torrent can be CheckingDownloading. - if (isChecking()) - m_unchecked = false; - else if (isDownloading()) - m_unchecked = true; - m_speedMonitor.addSample({nativeStatus.download_payload_rate - , nativeStatus.upload_payload_rate}); + , nativeStatus.upload_payload_rate}); + + if (hasMetadata()) + { + // NOTE: Don't change the order of these conditionals! + // Otherwise it will not work properly since torrent can be CheckingDownloading. + if (isChecking()) + m_unchecked = false; + else if (isDownloading()) + m_unchecked = true; + } } void TorrentHandleImpl::setRatioLimit(qreal limit) diff --git a/src/base/bittorrent/torrenthandleimpl.h b/src/base/bittorrent/torrenthandleimpl.h index 1c813a1a4..ddbf953fa 100644 --- a/src/base/bittorrent/torrenthandleimpl.h +++ b/src/base/bittorrent/torrenthandleimpl.h @@ -80,14 +80,20 @@ namespace BitTorrent Overwrite }; + enum class MaintenanceJob + { + None, + HandleMetadata + }; + class TorrentHandleImpl final : public QObject, public TorrentHandle { Q_DISABLE_COPY(TorrentHandleImpl) Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentHandleImpl) public: - TorrentHandleImpl(Session *session, const lt::torrent_handle &nativeHandle, - const LoadTorrentParams ¶ms); + TorrentHandleImpl(Session *session, lt::session *nativeSession + , const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms); ~TorrentHandleImpl() override; bool isValid() const; @@ -241,6 +247,7 @@ namespace BitTorrent void handleAppendExtensionToggled(); void saveResumeData(); void handleMoveStorageJobFinished(bool hasOutstandingJob); + void fileSearchFinished(const QString &savePath, const QStringList &fileNames); QString actualStorageLocation() const; @@ -278,7 +285,10 @@ namespace BitTorrent void manageIncompleteFiles(); void applyFirstLastPiecePriority(bool enabled, const QVector &updatedFilePrio = {}); + void endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames); + Session *const m_session; + lt::session *m_nativeSession; lt::torrent_handle m_nativeHandle; lt::torrent_status m_nativeStatus; TorrentState m_state = TorrentState::Unknown; @@ -293,6 +303,8 @@ namespace BitTorrent int m_renameCount = 0; bool m_storageIsMoving = false; + MaintenanceJob m_maintenanceJob = MaintenanceJob::None; + // Until libtorrent provide an "old_name" field in `file_renamed_alert` // we will rely on this workaround to remove empty leftover folders QHash> m_oldPath;