From 565c6d843a171d48a48cc74b061105f087f80429 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 28 Oct 2024 09:41:09 +0300 Subject: [PATCH 01/13] Correctly delete the moved search tab PR #21687. Closes #21675. --- src/gui/search/searchwidget.cpp | 8 +++++++- src/gui/search/searchwidget.h | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gui/search/searchwidget.cpp b/src/gui/search/searchwidget.cpp index 1128a561e..cabd8a135 100644 --- a/src/gui/search/searchwidget.cpp +++ b/src/gui/search/searchwidget.cpp @@ -1,7 +1,7 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015-2024 Vladimir Golovnev * Copyright (C) 2020, Will Da Silva - * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -120,6 +120,7 @@ SearchWidget::SearchWidget(IGUIApplication *app, MainWindow *mainWindow) #endif connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &SearchWidget::closeTab); connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &SearchWidget::tabChanged); + connect(m_ui->tabWidget->tabBar(), &QTabBar::tabMoved, this, &SearchWidget::tabMoved); const auto *searchManager = SearchPluginManager::instance(); const auto onPluginChanged = [this]() @@ -262,6 +263,11 @@ void SearchWidget::tabChanged(const int index) m_currentSearchTab = ((index < 0) ? nullptr : m_allTabs.at(m_ui->tabWidget->currentIndex())); } +void SearchWidget::tabMoved(const int from, const int to) +{ + m_allTabs.move(from, to); +} + void SearchWidget::selectMultipleBox([[maybe_unused]] const int index) { if (selectedPlugin() == u"multi") diff --git a/src/gui/search/searchwidget.h b/src/gui/search/searchwidget.h index 06544308c..a1b18e470 100644 --- a/src/gui/search/searchwidget.h +++ b/src/gui/search/searchwidget.h @@ -1,7 +1,7 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015-2024 Vladimir Golovnev * Copyright (C) 2020, Will Da Silva - * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -66,6 +66,7 @@ private slots: private: bool eventFilter(QObject *object, QEvent *event) override; void tabChanged(int index); + void tabMoved(int from, int to); void closeTab(int index); void closeAllTabs(); void tabStatusChanged(QWidget *tab); From fe24bc825b5f9f8eb66e7de9a1e6de17e23997bb Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sat, 2 Nov 2024 16:40:27 +0300 Subject: [PATCH 02/13] Remove trackers from previous category when moved to new one PR #21717. Closes #21637. --- .../trackersfilterwidget.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/gui/transferlistfilters/trackersfilterwidget.cpp b/src/gui/transferlistfilters/trackersfilterwidget.cpp index a166c7da7..20fc4ac1d 100644 --- a/src/gui/transferlistfilters/trackersfilterwidget.cpp +++ b/src/gui/transferlistfilters/trackersfilterwidget.cpp @@ -394,15 +394,11 @@ void TrackersFilterWidget::handleTrackerStatusesUpdated(const BitTorrent::Torren { if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::Working) { + // remove tracker from "error" and "tracker error" categories if (errorHashesIt != m_errors.end()) - { errorHashesIt->remove(trackerEntryStatus.url); - } - if (trackerErrorHashesIt != m_trackerErrors.end()) - { trackerErrorHashesIt->remove(trackerEntryStatus.url); - } const bool hasNoWarningMessages = std::all_of(trackerEntryStatus.endpoints.cbegin(), trackerEntryStatus.endpoints.cend() , [](const BitTorrent::TrackerEndpointStatus &endpointEntry) @@ -426,12 +422,24 @@ void TrackersFilterWidget::handleTrackerStatusesUpdated(const BitTorrent::Torren else if ((trackerEntryStatus.state == BitTorrent::TrackerEndpointState::NotWorking) || (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::Unreachable)) { + // remove tracker from "tracker error" and "warning" categories + if (warningHashesIt != m_warnings.end()) + warningHashesIt->remove(trackerEntryStatus.url); + if (trackerErrorHashesIt != m_trackerErrors.end()) + trackerErrorHashesIt->remove(trackerEntryStatus.url); + if (errorHashesIt == m_errors.end()) errorHashesIt = m_errors.insert(id, {}); errorHashesIt->insert(trackerEntryStatus.url); } else if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::TrackerError) { + // remove tracker from "error" and "warning" categories + if (warningHashesIt != m_warnings.end()) + warningHashesIt->remove(trackerEntryStatus.url); + if (errorHashesIt != m_errors.end()) + errorHashesIt->remove(trackerEntryStatus.url); + if (trackerErrorHashesIt == m_trackerErrors.end()) trackerErrorHashesIt = m_trackerErrors.insert(id, {}); trackerErrorHashesIt->insert(trackerEntryStatus.url); From 6079b2541932e6a14c688c245cb9cfcf5067614a Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sat, 2 Nov 2024 16:41:05 +0300 Subject: [PATCH 03/13] Fix .torrent file could not be deleted when torrent is canceled PR #21735. Closes #21723. --- src/base/addtorrentmanager.cpp | 6 ++---- src/base/addtorrentmanager.h | 2 +- src/gui/addnewtorrentdialog.cpp | 2 ++ src/gui/addnewtorrentdialog.h | 1 + src/gui/guiaddtorrentmanager.cpp | 19 +++++++++++++------ 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/base/addtorrentmanager.cpp b/src/base/addtorrentmanager.cpp index a4bfe8047..ce446a72b 100644 --- a/src/base/addtorrentmanager.cpp +++ b/src/base/addtorrentmanager.cpp @@ -195,11 +195,9 @@ void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_p m_guardedTorrentFiles.emplace(source, std::move(torrentFileGuard)); } -void AddTorrentManager::releaseTorrentFileGuard(const QString &source) +std::shared_ptr AddTorrentManager::releaseTorrentFileGuard(const QString &source) { - auto torrentFileGuard = m_guardedTorrentFiles.take(source); - if (torrentFileGuard) - torrentFileGuard->setAutoRemove(false); + return m_guardedTorrentFiles.take(source); } bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr diff --git a/src/base/addtorrentmanager.h b/src/base/addtorrentmanager.h index 9f1f61039..0e2fc6f49 100644 --- a/src/base/addtorrentmanager.h +++ b/src/base/addtorrentmanager.h @@ -74,7 +74,7 @@ protected: void handleAddTorrentFailed(const QString &source, const QString &reason); void handleDuplicateTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent); void setTorrentFileGuard(const QString &source, std::shared_ptr torrentFileGuard); - void releaseTorrentFileGuard(const QString &source); + std::shared_ptr releaseTorrentFileGuard(const QString &source); private: void onDownloadFinished(const Net::DownloadResult &result); diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 07eb4cc16..5417e6dbe 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -821,6 +821,8 @@ void AddNewTorrentDialog::reject() if (!m_currentContext) [[unlikely]] return; + emit torrentRejected(m_currentContext->torrentDescr); + const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr; const bool hasMetadata = torrentDescr.info().has_value(); if (!hasMetadata) diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index f81bd305b..bc530953b 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -66,6 +66,7 @@ public: signals: void torrentAccepted(const BitTorrent::TorrentDescriptor &torrentDescriptor, const BitTorrent::AddTorrentParams &addTorrentParams); + void torrentRejected(const BitTorrent::TorrentDescriptor &torrentDescriptor); private slots: void updateDiskSpaceLabel(); diff --git a/src/gui/guiaddtorrentmanager.cpp b/src/gui/guiaddtorrentmanager.cpp index c99d4afce..d600a4e17 100644 --- a/src/gui/guiaddtorrentmanager.cpp +++ b/src/gui/guiaddtorrentmanager.cpp @@ -235,15 +235,22 @@ bool GUIAddTorrentManager::processTorrent(const QString &source dlg->setAttribute(Qt::WA_DeleteOnClose); m_dialogs[infoHash] = dlg; connect(dlg, &AddNewTorrentDialog::torrentAccepted, this - , [this, source](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams) - { - addTorrentToSession(source, torrentDescr, addTorrentParams); - }); - connect(dlg, &QDialog::finished, this, [this, source, infoHash, dlg] + , [this, source, dlg](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams) { if (dlg->isDoNotDeleteTorrentChecked()) - releaseTorrentFileGuard(source); + { + if (auto torrentFileGuard = releaseTorrentFileGuard(source)) + torrentFileGuard->setAutoRemove(false); + } + addTorrentToSession(source, torrentDescr, addTorrentParams); + }); + connect(dlg, &AddNewTorrentDialog::torrentRejected, this, [this, source] + { + releaseTorrentFileGuard(source); + }); + connect(dlg, &QDialog::finished, this, [this, source, infoHash] + { m_dialogs.remove(infoHash); }); From 374951f6f245533fb757c8af227018da3392a0c6 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sun, 3 Nov 2024 09:54:57 +0300 Subject: [PATCH 04/13] Handle Qt style names in a case insensitive way PR #21720. Closes #21716. --- src/gui/optionsdialog.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index 60c1c8aef..3dc0d113f 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -1703,11 +1703,10 @@ void OptionsDialog::initializeStyleCombo() QStringList styleNames = QStyleFactory::keys(); std::sort(styleNames.begin(), styleNames.end(), Utils::Compare::NaturalLessThan()); m_ui->comboStyle->addItems(styleNames); + const QString prefStyleName = Preferences::instance()->getStyle(); const QString selectedStyleName = prefStyleName.isEmpty() ? QApplication::style()->name() : prefStyleName; - - if (selectedStyleName.compare(u"system"_s, Qt::CaseInsensitive) != 0) - m_ui->comboStyle->setCurrentText(selectedStyleName); + m_ui->comboStyle->setCurrentIndex(m_ui->comboStyle->findText(selectedStyleName, Qt::MatchFixedString)); #else m_ui->labelStyle->hide(); m_ui->comboStyle->hide(); From eea01b94a3299e8937cbe991c7f5c24abdef7908 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 4 Nov 2024 16:27:21 +0300 Subject: [PATCH 05/13] Reset tracker entries when pause the session PR #21738. --- src/base/bittorrent/sessionimpl.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 492bfac3b..e962a125e 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -4065,14 +4065,29 @@ bool SessionImpl::isPaused() const void SessionImpl::pause() { - if (!m_isPaused) - { - if (isRestored()) - m_nativeSession->pause(); + if (m_isPaused) + return; - m_isPaused = true; - emit paused(); + if (isRestored()) + { + m_nativeSession->pause(); + + for (TorrentImpl *torrent : asConst(m_torrents)) + { + torrent->resetTrackerEntryStatuses(); + + const QList trackers = torrent->trackers(); + QHash updatedTrackers; + updatedTrackers.reserve(trackers.size()); + + for (const TrackerEntryStatus &status : trackers) + updatedTrackers.emplace(status.url, status); + emit trackerEntryStatusesUpdated(torrent, updatedTrackers); + } } + + m_isPaused = true; + emit paused(); } void SessionImpl::resume() From 10499dffe913d2e4c15c703fd2e1660ab65327d8 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Tue, 5 Nov 2024 16:43:43 +0300 Subject: [PATCH 06/13] Optimize conversion of time points from libtorrent to Qt clocks Obtain current date time of Qt and libtorrent clocks only once for processing entire current libtorrent alerts bunch. PR #21764. --- src/base/bittorrent/sessionimpl.cpp | 11 +++++++++++ src/base/bittorrent/sessionimpl.h | 5 +++++ src/base/bittorrent/torrentimpl.cpp | 21 ++++++++++----------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index e962a125e..f09d9338b 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -5496,6 +5496,11 @@ void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value) // Read alerts sent by libtorrent session void SessionImpl::readAlerts() { + // cache current datetime of Qt and libtorrent clocks in order + // to optimize conversion of time points from lt to Qt clocks + m_ltNow = lt::clock_type::now(); + m_qNow = QDateTime::currentDateTime(); + const std::vector alerts = getPendingAlerts(); Q_ASSERT(m_loadedTorrents.isEmpty()); @@ -6359,3 +6364,9 @@ void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString m_removingTorrents.erase(removingTorrentDataIter); } + +QDateTime SessionImpl::fromLTTimePoint32(const libtorrent::time_point32 &timePoint) const +{ + const auto secsSinceNow = lt::duration_cast(timePoint - m_ltNow + lt::milliseconds(500)).count(); + return m_qNow.addSecs(secsSinceNow); +} diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index b387b52a1..ef0c6b401 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -475,6 +475,8 @@ namespace BitTorrent void addMappedPorts(const QSet &ports); void removeMappedPorts(const QSet &ports); + QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint) const; + template void invoke(Func &&func) { @@ -820,6 +822,9 @@ namespace BitTorrent QList m_pendingFinishedTorrents; + QDateTime m_qNow; + lt::clock_type::time_point m_ltNow; + friend void Session::initInstance(); friend void Session::freeInstance(); friend Session *Session::instance(); diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 9cd370839..3ed0d53f4 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -92,22 +92,15 @@ namespace return entry; } - QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint) - { - const auto ltNow = lt::clock_type::now(); - const auto qNow = QDateTime::currentDateTime(); - const auto secsSinceNow = lt::duration_cast(timePoint - ltNow + lt::milliseconds(500)).count(); - - return qNow.addSecs(secsSinceNow); - } - QString toString(const lt::tcp::endpoint <TCPEndpoint) { return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str()); } + template void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry - , const QSet &btProtocols, const QHash> &updateInfo) + , const QSet &btProtocols, const QHash> &updateInfo + , const FromLTTimePoint32Func &fromLTTimePoint32) { Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url)); @@ -1759,7 +1752,13 @@ TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entr #else const QSet btProtocols {1}; #endif - ::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo); + + const auto fromLTTimePoint32 = [this](const lt::time_point32 &timePoint) + { + return m_session->fromLTTimePoint32(timePoint); + }; + ::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo, fromLTTimePoint32); + return *it; } From f2b2a2b034fbc1990d8135a68969632a1585de49 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Thu, 7 Nov 2024 09:39:33 +0300 Subject: [PATCH 07/13] Optimize converting TCP endpoints to strings There may be quite a few endpoint names (one for each available network card), and they usually remain unchanged throughout the session, while previously producing such names was performed every time they were accessed. Now they are retrieved from the cache. PR #21770. --- src/base/bittorrent/torrentimpl.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 3ed0d53f4..385868126 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -94,7 +95,15 @@ namespace QString toString(const lt::tcp::endpoint <TCPEndpoint) { - return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str()); + static QCache cache; + + if (const QString *endpointName = cache.object(ltTCPEndpoint)) + return *endpointName; + + const std::string tmp = (std::ostringstream() << ltTCPEndpoint).str(); + const auto endpointName = QString::fromLatin1(tmp.c_str(), tmp.size()); + cache.insert(ltTCPEndpoint, new QString(endpointName)); + return endpointName; } template From 5e105b03482fff5e3c88188966e0722cfbb94240 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Thu, 7 Nov 2024 09:40:33 +0300 Subject: [PATCH 08/13] Optimize checking for outdated tracker endpoints PR #21768. --- src/base/bittorrent/torrentimpl.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 385868126..523f461b7 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -115,16 +115,6 @@ namespace trackerEntryStatus.tier = nativeEntry.tier; - // remove outdated endpoints - trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash, TrackerEndpointStatus>::iterator &iter) - { - return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend() - , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint) - { - return (endpointName == toString(existingEndpoint.local_endpoint)); - }); - }); - const auto numEndpoints = static_cast(nativeEntry.endpoints.size()) * btProtocols.size(); int numUpdating = 0; @@ -207,6 +197,19 @@ namespace } } + if (trackerEntryStatus.endpoints.size() > numEndpoints) + { + // remove outdated endpoints + trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash, TrackerEndpointStatus>::iterator &iter) + { + return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend() + , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint) + { + return (endpointName == toString(existingEndpoint.local_endpoint)); + }); + }); + } + if (numEndpoints > 0) { if (numUpdating > 0) From 39b965af4897b3f243a5422fbb6f5bd7ef2b8b18 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Fri, 8 Nov 2024 11:45:16 +0300 Subject: [PATCH 09/13] Check real palette darkness to detect "dark theme" `QStyleHints::colorScheme()` returns chosen color scheme even if current style doesn't support it and uses different palette. PR #21771. --- src/gui/uithememanager.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/gui/uithememanager.cpp b/src/gui/uithememanager.cpp index 4a31f2928..f20f5e0a3 100644 --- a/src/gui/uithememanager.cpp +++ b/src/gui/uithememanager.cpp @@ -47,16 +47,9 @@ namespace { bool isDarkTheme() { - switch (qApp->styleHints()->colorScheme()) - { - case Qt::ColorScheme::Dark: - return true; - case Qt::ColorScheme::Light: - return false; - default: - // fallback to custom method - return (qApp->palette().color(QPalette::Active, QPalette::Base).lightness() < 127); - } + const QPalette palette = qApp->palette(); + const QColor &color = palette.color(QPalette::Active, QPalette::Base); + return (color.lightness() < 127); } } From 330dce6aa27fc0aa419f4c1fc02834ff71bf592f Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Fri, 8 Nov 2024 11:45:57 +0300 Subject: [PATCH 10/13] Correctly handle "torrent finished" events PR #21786. Closes #21699. --- src/base/bittorrent/sessionimpl.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index f09d9338b..295b6434b 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -5230,9 +5230,6 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath) if (torrent) { torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob); - // The torrent may become "finished" at the end of the move if it was moved - // from the "incomplete" location after downloading finished. - processPendingFinishedTorrents(); } else if (!torrentHasOutstandingJob) { @@ -5527,6 +5524,9 @@ void SessionImpl::readAlerts() } } + // Some torrents may become "finished" after different alerts handling. + processPendingFinishedTorrents(); + processTrackerStatuses(); } @@ -6166,8 +6166,6 @@ void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert) if (!updatedTorrents.isEmpty()) emit torrentsUpdated(updatedTorrents); - processPendingFinishedTorrents(); - if (m_needSaveTorrentsQueue) saveTorrentsQueue(); From 993eb2532339fd42f20b6a8d4c996b3130b59155 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sun, 10 Nov 2024 12:01:12 +0300 Subject: [PATCH 11/13] Preserve initial torrent progress while checking resume data PR #21784. --- src/base/bittorrent/torrentimpl.cpp | 59 +++++++++++++++++++---------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 523f461b7..e4bdbbd4e 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -1637,9 +1637,19 @@ void TorrentImpl::forceRecheck() return; m_nativeHandle.force_recheck(); + // We have to force update the cached state, otherwise someone will be able to get // an incorrect one during the interval until the cached state is updated in a regular way. m_nativeStatus.state = lt::torrent_status::checking_resume_data; + m_nativeStatus.pieces.clear_all(); + m_nativeStatus.num_pieces = 0; + m_ltAddTorrentParams.have_pieces.clear(); + m_ltAddTorrentParams.verified_pieces.clear(); + m_ltAddTorrentParams.unfinished_pieces.clear(); + m_completedFiles.fill(false); + m_filesProgress.fill(0); + m_pieces.fill(false); + m_unchecked = false; if (m_hasMissingFiles) { @@ -1652,14 +1662,6 @@ void TorrentImpl::forceRecheck() } } - 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 (isStopped()) { // When "force recheck" is applied on Stopped torrent, we start them to perform checking @@ -2153,6 +2155,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) m_ltAddTorrentParams.have_pieces.clear(); m_ltAddTorrentParams.verified_pieces.clear(); + m_ltAddTorrentParams.unfinished_pieces.clear(); m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti; @@ -2195,23 +2198,37 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms) { - if (m_hasMissingFiles) { - const auto havePieces = m_ltAddTorrentParams.have_pieces; - const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces; - const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces; + decltype(params.have_pieces) havePieces; + decltype(params.unfinished_pieces) unfinishedPieces; + decltype(params.verified_pieces) verifiedPieces; + + // The resume data obtained from libtorrent contains an empty "progress" in the following cases: + // 1. when it was requested at a time when the initial resume data has not yet been checked, + // 2. when initial resume data was rejected + // We should preserve the initial "progress" in such cases. + const bool needPreserveProgress = m_hasMissingFiles + || (!m_ltAddTorrentParams.have_pieces.empty() && params.have_pieces.empty()); + const bool preserveSeedMode = !m_hasMissingFiles && !hasMetadata() + && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode); + + if (needPreserveProgress) + { + havePieces = std::move(m_ltAddTorrentParams.have_pieces); + unfinishedPieces = std::move(m_ltAddTorrentParams.unfinished_pieces); + verifiedPieces = std::move(m_ltAddTorrentParams.verified_pieces); + } - // Update recent resume data but preserve existing progress - m_ltAddTorrentParams = params; - m_ltAddTorrentParams.have_pieces = havePieces; - m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces; - m_ltAddTorrentParams.verified_pieces = verifiedPieces; - } - else - { - const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode)); // Update recent resume data m_ltAddTorrentParams = params; + + if (needPreserveProgress) + { + m_ltAddTorrentParams.have_pieces = std::move(havePieces); + m_ltAddTorrentParams.unfinished_pieces = std::move(unfinishedPieces); + m_ltAddTorrentParams.verified_pieces = std::move(verifiedPieces); + } + if (preserveSeedMode) m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode; } From 09899a7d0d15de7b91de9da0e664c6951fc69839 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 14 Nov 2024 15:06:33 +0800 Subject: [PATCH 12/13] Avoid reapplying Mark-of-the-Web when it already exists Also use scope guards to handle resources. Related #21788. PR #21806. --- src/base/utils/os.cpp | 62 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/base/utils/os.cpp b/src/base/utils/os.cpp index ee542e48e..2f1c77adb 100644 --- a/src/base/utils/os.cpp +++ b/src/base/utils/os.cpp @@ -31,6 +31,8 @@ #include "os.h" #ifdef Q_OS_WIN +#include + #include #include #include @@ -42,6 +44,8 @@ #include #endif // Q_OS_MACOS +#include + #ifdef QBT_USES_DBUS #include #endif // QBT_USES_DBUS @@ -283,34 +287,49 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url) // https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230 // https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955 + const CFStringRef fileString = file.toString().toCFString(); + [[maybe_unused]] const auto fileStringGuard = qScopeGuard([&fileString] { ::CFRelease(fileString); }); + const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault + , fileString, kCFURLPOSIXPathStyle, false); + [[maybe_unused]] const auto fileURLGuard = qScopeGuard([&fileURL] { ::CFRelease(fileURL); }); + + if (CFDictionaryRef currentProperties = nullptr; + ::CFURLCopyResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey, ¤tProperties, NULL) + && currentProperties) + { + [[maybe_unused]] const auto currentPropertiesGuard = qScopeGuard([¤tProperties] { ::CFRelease(currentProperties); }); + + if (CFStringRef quarantineType = nullptr; + ::CFDictionaryGetValueIfPresent(currentProperties, kLSQuarantineTypeKey, reinterpret_cast(&quarantineType)) + && quarantineType) + { + if (::CFStringCompare(quarantineType, kLSQuarantineTypeOtherDownload, 0) == kCFCompareEqualTo) + return true; + } + } + CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0 , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - if (properties == NULL) + if (!properties) return false; + [[maybe_unused]] const auto propertiesGuard = qScopeGuard([&properties] { ::CFRelease(properties); }); ::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload); if (!url.isEmpty()) ::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, url.toCFString()); - const CFStringRef fileString = file.toString().toCFString(); - const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault - , fileString, kCFURLPOSIXPathStyle, false); - const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey , properties, NULL); - - ::CFRelease(fileURL); - ::CFRelease(fileString); - ::CFRelease(properties); - return success; #elif defined(Q_OS_WIN) const QString zoneIDStream = file.toString() + u":Zone.Identifier"; - HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), GENERIC_WRITE + + HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), (GENERIC_READ | GENERIC_WRITE) , (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE) , nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == INVALID_HANDLE_VALUE) return false; + [[maybe_unused]] const auto handleGuard = qScopeGuard([&handle] { ::CloseHandle(handle); }); // 5.6.1 Zone.Identifier Stream Name // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8 @@ -318,10 +337,27 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url) const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n") + u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8(); + if (LARGE_INTEGER streamSize = {0}; + ::GetFileSizeEx(handle, &streamSize) && (streamSize.QuadPart > 0)) + { + const DWORD expectedReadSize = std::min(streamSize.QuadPart, 1024); + QByteArray buf {expectedReadSize, '\0'}; + + if (DWORD actualReadSize = 0; + ::ReadFile(handle, buf.data(), expectedReadSize, &actualReadSize, nullptr) && (actualReadSize == expectedReadSize)) + { + if (buf.startsWith("[ZoneTransfer]\r\n") && buf.contains("\r\nZoneId=3\r\n") && buf.contains("\r\nHostUrl=")) + return true; + } + } + + if (!::SetFilePointerEx(handle, {0}, nullptr, FILE_BEGIN)) + return false; + if (!::SetEndOfFile(handle)) + return false; + DWORD written = 0; const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr); - ::CloseHandle(handle); - return writeResult && (written == zoneID.size()); #endif } From ac9ca4f452e138a199ee6e16d18487ed73dbdc67 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sat, 16 Nov 2024 15:57:55 +0800 Subject: [PATCH 13/13] Don't apply Mark-of-the-Web on existing files `TorrentImpl::isDownloading()` was excessively broad which included unexpected events for the case here. So use the underlying state directly. Closes #21788. PR #21836. --- src/base/bittorrent/torrentimpl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index e4bdbbd4e..a3b82eff5 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -2378,7 +2378,8 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p) #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) // only apply Mark-of-the-Web to new download files - if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading()) + if (Preferences::instance()->isMarkOfTheWebEnabled() + && (m_nativeStatus.state == lt::torrent_status::downloading)) { const Path fullpath = actualStorageLocation() / actualPath; Utils::OS::applyMarkOfTheWeb(fullpath);