From 7cd2445a49e879255dca5015592ea94ccd9d0d12 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Thu, 7 Sep 2023 08:58:13 +0300 Subject: [PATCH] Improve tracker entries handling PR #19496. * Add torrent entry status to represent tracker error * Add torrent entry status to represent unreachable endpoint * Display tracker entry next/min announce time * Reset tracker entries when torrent is stopped --- src/base/bittorrent/sessionimpl.cpp | 80 +++++++++++-------- src/base/bittorrent/sessionimpl.h | 2 + src/base/bittorrent/torrentimpl.cpp | 77 ++++++++++++++---- src/base/bittorrent/torrentimpl.h | 3 +- src/base/bittorrent/trackerentry.h | 17 ++-- src/base/utils/misc.cpp | 17 ++-- src/base/utils/misc.h | 8 +- src/gui/properties/trackerlistwidget.cpp | 58 +++++++++++++- src/gui/properties/trackerlistwidget.h | 2 + .../trackersfilterwidget.cpp | 14 +++- src/webui/api/torrentscontroller.cpp | 30 ++++++- 11 files changed, 239 insertions(+), 69 deletions(-) diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 7ef1d22af..652fb9cc9 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -4841,6 +4841,15 @@ void SessionImpl::handleTorrentMetadataReceived(TorrentImpl *const torrent) void SessionImpl::handleTorrentPaused(TorrentImpl *const torrent) { + torrent->resetTrackerEntries(); + + const auto &trackerEntries = torrent->trackers(); + QHash updatedTrackerEntries; + updatedTrackerEntries.reserve(trackerEntries.size()); + for (const auto &trackerEntry : trackerEntries) + updatedTrackerEntries.emplace(trackerEntry.url, trackerEntry); + emit trackerEntriesUpdated(torrent, updatedTrackerEntries); + LogMsg(tr("Torrent paused. Torrent: \"%1\"").arg(torrent->name())); emit torrentPaused(torrent); } @@ -5985,39 +5994,7 @@ void SessionImpl::processTrackerStatuses() for (auto it = m_updatedTrackerEntries.cbegin(); it != m_updatedTrackerEntries.cend(); ++it) { - invokeAsync([this, torrentHandle = it.key(), updatedTrackers = it.value()]() mutable - { - try - { - std::vector nativeTrackers = torrentHandle.trackers(); - invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers) - , updatedTrackers = std::move(updatedTrackers)] - { - TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash()); - if (!torrent) - return; - - QHash updatedTrackerEntries; - updatedTrackerEntries.reserve(updatedTrackers.size()); - for (const lt::announce_entry &announceEntry : nativeTrackers) - { - const auto updatedTrackersIter = updatedTrackers.find(announceEntry.url); - if (updatedTrackersIter == updatedTrackers.end()) - continue; - - const auto &updateInfo = updatedTrackersIter.value(); - TrackerEntry trackerEntry = torrent->updateTrackerEntry(announceEntry, updateInfo); - const QString url = trackerEntry.url; - updatedTrackerEntries.emplace(url, std::move(trackerEntry)); - } - - emit trackerEntriesUpdated(torrent, updatedTrackerEntries); - }); - } - catch (const std::exception &) - { - } - }); + updateTrackerEntries(it.key(), it.value()); } m_updatedTrackerEntries.clear(); @@ -6046,3 +6023,40 @@ void SessionImpl::loadStatistics() m_previouslyDownloaded = value[u"AlltimeDL"_s].toLongLong(); m_previouslyUploaded = value[u"AlltimeUL"_s].toLongLong(); } + +void SessionImpl::updateTrackerEntries(lt::torrent_handle torrentHandle, QHash>> updatedTrackers) +{ + invokeAsync([this, torrentHandle = std::move(torrentHandle), updatedTrackers = std::move(updatedTrackers)]() mutable + { + try + { + std::vector nativeTrackers = torrentHandle.trackers(); + invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers) + , updatedTrackers = std::move(updatedTrackers)] + { + TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash()); + if (!torrent || torrent->isPaused()) + return; + + QHash updatedTrackerEntries; + updatedTrackerEntries.reserve(updatedTrackers.size()); + for (const lt::announce_entry &announceEntry : nativeTrackers) + { + const auto updatedTrackersIter = updatedTrackers.find(announceEntry.url); + if (updatedTrackersIter == updatedTrackers.end()) + continue; + + const auto &updateInfo = updatedTrackersIter.value(); + TrackerEntry trackerEntry = torrent->updateTrackerEntry(announceEntry, updateInfo); + const QString url = trackerEntry.url; + updatedTrackerEntries.emplace(url, std::move(trackerEntry)); + } + + emit trackerEntriesUpdated(torrent, updatedTrackerEntries); + }); + } + catch (const std::exception &) + { + } + }); +} diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index bd6f730ca..64daa9ab6 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -572,6 +572,8 @@ namespace BitTorrent void saveStatistics() const; void loadStatistics(); + void updateTrackerEntries(lt::torrent_handle torrentHandle, QHash>> updatedTrackers); + // BitTorrent lt::session *m_nativeSession = nullptr; NativeSessionExtension *m_nativeSessionExtension = nullptr; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 62a5811be..c93335054 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -78,6 +78,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); + } + #ifdef QBT_USES_LIBTORRENT2 void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry , const lt::info_hash_t &hashes, const QHash> &updateInfo) @@ -103,8 +112,8 @@ namespace int numUpdating = 0; int numWorking = 0; int numNotWorking = 0; - QString firstTrackerMessage; - QString firstErrorMessage; + int numTrackerError = 0; + int numUnreachable = 0; #ifdef QBT_USES_LIBTORRENT2 const auto numEndpoints = static_cast(nativeEntry.endpoints.size()) * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1); for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints) @@ -127,6 +136,8 @@ namespace trackerEndpoint.numSeeds = infoHash.scrape_complete; trackerEndpoint.numLeeches = infoHash.scrape_incomplete; trackerEndpoint.numDownloaded = infoHash.scrape_downloaded; + trackerEndpoint.nextAnnounceTime = fromLTTimePoint32(infoHash.next_announce); + trackerEndpoint.minAnnounceTime = fromLTTimePoint32(infoHash.min_announce); if (infoHash.updating) { @@ -135,8 +146,21 @@ namespace } else if (infoHash.fails > 0) { - trackerEndpoint.status = TrackerEntry::NotWorking; - ++numNotWorking; + if (infoHash.last_error == lt::errors::tracker_failure) + { + trackerEndpoint.status = TrackerEntry::TrackerError; + ++numTrackerError; + } + else if (infoHash.last_error == lt::errors::announce_skipped) + { + trackerEndpoint.status = TrackerEntry::Unreachable; + ++numUnreachable; + } + else + { + trackerEndpoint.status = TrackerEntry::NotWorking; + ++numNotWorking; + } } else if (nativeEntry.verified) { @@ -151,14 +175,10 @@ namespace if (!infoHash.message.empty()) { trackerEndpoint.message = QString::fromStdString(infoHash.message); - if (firstTrackerMessage.isEmpty()) - firstTrackerMessage = trackerEndpoint.message; } else if (infoHash.last_error) { trackerEndpoint.message = QString::fromLocal8Bit(infoHash.last_error.message()); - if (firstErrorMessage.isEmpty()) - firstErrorMessage = trackerEndpoint.message; } } } @@ -175,6 +195,8 @@ namespace trackerEndpoint.numSeeds = endpoint.scrape_complete; trackerEndpoint.numLeeches = endpoint.scrape_incomplete; trackerEndpoint.numDownloaded = endpoint.scrape_downloaded; + trackerEndpoint.nextAnnounceTime = fromLTTimePoint32(endpoint.next_announce); + trackerEndpoint.minAnnounceTime = fromLTTimePoint32(endpoint.min_announce); if (endpoint.updating) { @@ -183,8 +205,21 @@ namespace } else if (endpoint.fails > 0) { - trackerEndpoint.status = TrackerEntry::NotWorking; - ++numNotWorking; + if (endpoint.last_error == lt::errors::tracker_failure) + { + trackerEndpoint.status = TrackerEntry::TrackerError; + ++numTrackerError; + } + else if (endpoint.last_error == lt::errors::announce_skipped) + { + trackerEndpoint.status = TrackerEntry::Unreachable; + ++numUnreachable; + } + else + { + trackerEndpoint.status = TrackerEntry::NotWorking; + ++numNotWorking; + } } else if (nativeEntry.verified) { @@ -199,14 +234,10 @@ namespace if (!endpoint.message.empty()) { trackerEndpoint.message = QString::fromStdString(endpoint.message); - if (firstTrackerMessage.isEmpty()) - firstTrackerMessage = trackerEndpoint.message; } else if (endpoint.last_error) { trackerEndpoint.message = QString::fromLocal8Bit(endpoint.last_error.message()); - if (firstErrorMessage.isEmpty()) - firstErrorMessage = trackerEndpoint.message; } } #endif @@ -220,12 +251,18 @@ namespace else if (numWorking > 0) { trackerEntry.status = TrackerEntry::Working; - trackerEntry.message = firstTrackerMessage; } - else if (numNotWorking == numEndpoints) + else if (numTrackerError > 0) + { + trackerEntry.status = TrackerEntry::TrackerError; + } + else if (numUnreachable == numEndpoints) + { + trackerEntry.status = TrackerEntry::Unreachable; + } + else if ((numUnreachable + numNotWorking) == numEndpoints) { trackerEntry.status = TrackerEntry::NotWorking; - trackerEntry.message = (!firstTrackerMessage.isEmpty() ? firstTrackerMessage : firstErrorMessage); } } } @@ -1639,6 +1676,12 @@ TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceE return *it; } +void TorrentImpl::resetTrackerEntries() +{ + for (auto &trackerEntry : m_trackerEntries) + trackerEntry = {trackerEntry.url, trackerEntry.tier}; +} + std::shared_ptr TorrentImpl::nativeTorrentInfo() const { if (m_nativeStatus.torrent_file.expired()) diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index fae5b94a6..b8a3b4bf1 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -264,7 +264,8 @@ namespace BitTorrent void saveResumeData(lt::resume_data_flags_t flags = {}); void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob); void fileSearchFinished(const Path &savePath, const PathList &fileNames); - TrackerEntry updateTrackerEntry(const lt::announce_entry &announceEntry, const QHash > &updateInfo); + TrackerEntry updateTrackerEntry(const lt::announce_entry &announceEntry, const QHash> &updateInfo); + void resetTrackerEntries(); private: using EventTrigger = std::function; diff --git a/src/base/bittorrent/trackerentry.h b/src/base/bittorrent/trackerentry.h index d6346a18d..e996c9c6e 100644 --- a/src/base/bittorrent/trackerentry.h +++ b/src/base/bittorrent/trackerentry.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2022 Vladimir Golovnev + * Copyright (C) 2015-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 @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -46,24 +47,30 @@ namespace BitTorrent NotContacted = 1, Working = 2, Updating = 3, - NotWorking = 4 + NotWorking = 4, + TrackerError = 5, + Unreachable = 6 }; struct EndpointStats { + QString name {}; + Status status = NotContacted; + QString message {}; + int numPeers = -1; int numSeeds = -1; int numLeeches = -1; int numDownloaded = -1; - QString message {}; - QString name {}; + + QDateTime nextAnnounceTime; + QDateTime minAnnounceTime; }; QString url {}; int tier = 0; Status status = NotContacted; - QString message {}; QHash> stats {}; }; diff --git a/src/base/utils/misc.cpp b/src/base/utils/misc.cpp index 057e11c6d..e0836efbf 100644 --- a/src/base/utils/misc.cpp +++ b/src/base/utils/misc.cpp @@ -352,7 +352,7 @@ bool Utils::Misc::isPreviewable(const Path &filePath) return multimediaExtensions.contains(filePath.extension().toUpper()); } -QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap) +QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap, const TimeResolution resolution) { if (seconds < 0) return C_INFINITY; @@ -363,29 +363,34 @@ QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglo return u"0"_s; if (seconds < 60) - return QCoreApplication::translate("misc", "< 1m", "< 1 minute"); + { + if (resolution == TimeResolution::Minutes) + return QCoreApplication::translate("misc", "< 1m", "< 1 minute"); + + return QCoreApplication::translate("misc", "%1s", "e.g: 10 seconds").arg(QString::number(seconds)); + } qlonglong minutes = (seconds / 60); if (minutes < 60) - return QCoreApplication::translate("misc", "%1m", "e.g: 10minutes").arg(QString::number(minutes)); + return QCoreApplication::translate("misc", "%1m", "e.g: 10 minutes").arg(QString::number(minutes)); qlonglong hours = (minutes / 60); if (hours < 24) { minutes -= (hours * 60); - return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours), QString::number(minutes)); + return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3 hours 5 minutes").arg(QString::number(hours), QString::number(minutes)); } qlonglong days = (hours / 24); if (days < 365) { hours -= (days * 24); - return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2days 10hours").arg(QString::number(days), QString::number(hours)); + return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2 days 10 hours").arg(QString::number(days), QString::number(hours)); } qlonglong years = (days / 365); days -= (years * 365); - return QCoreApplication::translate("misc", "%1y %2d", "e.g: 2years 10days").arg(QString::number(years), QString::number(days)); + return QCoreApplication::translate("misc", "%1y %2d", "e.g: 2 years 10 days").arg(QString::number(years), QString::number(days)); } QString Utils::Misc::getUserIDString() diff --git a/src/base/utils/misc.h b/src/base/utils/misc.h index d58ae50f3..7f4f4fa2c 100644 --- a/src/base/utils/misc.h +++ b/src/base/utils/misc.h @@ -60,6 +60,12 @@ namespace Utils::Misc // YobiByte, // 1024^8 }; +enum class TimeResolution + { + Seconds, + Minutes + }; + QString parseHtmlLinks(const QString &rawText); void shutdownComputer(const ShutdownDialogAction &action); @@ -82,7 +88,7 @@ namespace Utils::Misc // Take a number of seconds and return a user-friendly // time duration like "1d 2h 10m". - QString userFriendlyDuration(qlonglong seconds, qlonglong maxCap = -1); + QString userFriendlyDuration(qlonglong seconds, qlonglong maxCap = -1, TimeResolution resolution = TimeResolution::Minutes); QString getUserIDString(); QString languageToLocalizedString(const QString &localeStr); diff --git a/src/gui/properties/trackerlistwidget.cpp b/src/gui/properties/trackerlistwidget.cpp index 52a0c9c0c..0624a792e 100644 --- a/src/gui/properties/trackerlistwidget.cpp +++ b/src/gui/properties/trackerlistwidget.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ #include "base/bittorrent/trackerentry.h" #include "base/global.h" #include "base/preferences.h" +#include "base/utils/misc.h" #include "gui/autoexpandabledialog.h" #include "gui/uithememanager.h" #include "propertieswidget.h" @@ -128,6 +130,8 @@ TrackerListWidget::TrackerListWidget(PropertiesWidget *properties) headerItem()->setTextAlignment(COL_SEEDS, alignment); headerItem()->setTextAlignment(COL_LEECHES, alignment); headerItem()->setTextAlignment(COL_TIMES_DOWNLOADED, alignment); + headerItem()->setTextAlignment(COL_NEXT_ANNOUNCE, alignment); + headerItem()->setTextAlignment(COL_MIN_ANNOUNCE, alignment); // Set hotkeys const auto *editHotkey = new QShortcut(Qt::Key_F2, this, nullptr, nullptr, Qt::WidgetShortcut); @@ -145,6 +149,8 @@ TrackerListWidget::TrackerListWidget(PropertiesWidget *properties) item->setText(COL_LEECHES, QString()); item->setText(COL_TIMES_DOWNLOADED, QString()); item->setText(COL_MSG, QString()); + item->setText(COL_NEXT_ANNOUNCE, QString()); + item->setText(COL_MIN_ANNOUNCE, QString()); }); connect(this, &QTreeWidget::itemCollapsed, this, [](QTreeWidgetItem *item) { @@ -153,6 +159,12 @@ TrackerListWidget::TrackerListWidget(PropertiesWidget *properties) item->setText(COL_LEECHES, item->data(COL_LEECHES, Qt::UserRole).toString()); item->setText(COL_TIMES_DOWNLOADED, item->data(COL_TIMES_DOWNLOADED, Qt::UserRole).toString()); item->setText(COL_MSG, item->data(COL_MSG, Qt::UserRole).toString()); + + const auto now = QDateTime::currentDateTime(); + const auto secsToNextAnnounce = now.secsTo(item->data(COL_NEXT_ANNOUNCE, Qt::UserRole).toDateTime()); + item->setText(COL_NEXT_ANNOUNCE, Utils::Misc::userFriendlyDuration(secsToNextAnnounce, -1, Utils::Misc::TimeResolution::Seconds)); + const auto secsToMinAnnounce = now.secsTo(item->data(COL_MIN_ANNOUNCE, Qt::UserRole).toDateTime()); + item->setText(COL_MIN_ANNOUNCE, Utils::Misc::userFriendlyDuration(secsToMinAnnounce, -1, Utils::Misc::TimeResolution::Seconds)); }); } @@ -386,8 +398,11 @@ void TrackerListWidget::loadTrackers() const auto setAlignment = [](QTreeWidgetItem *item) { - for (const TrackerListColumn col : {COL_TIER, COL_PROTOCOL, COL_PEERS, COL_SEEDS, COL_LEECHES, COL_TIMES_DOWNLOADED}) + for (const TrackerListColumn col : {COL_TIER, COL_PROTOCOL, COL_PEERS, COL_SEEDS + , COL_LEECHES, COL_TIMES_DOWNLOADED, COL_NEXT_ANNOUNCE, COL_MIN_ANNOUNCE}) + { item->setTextAlignment(col, (Qt::AlignRight | Qt::AlignVCenter)); + } }; const auto prettyCount = [](const int val) @@ -405,6 +420,10 @@ void TrackerListWidget::loadTrackers() return tr("Updating..."); case BitTorrent::TrackerEntry::Status::NotWorking: return tr("Not working"); + case BitTorrent::TrackerEntry::Status::TrackerError: + return tr("Tracker error"); + case BitTorrent::TrackerEntry::Status::Unreachable: + return tr("Unreachable"); case BitTorrent::TrackerEntry::Status::NotContacted: return tr("Not contacted yet"); } @@ -432,10 +451,15 @@ void TrackerListWidget::loadTrackers() oldTrackerURLs.removeOne(trackerURL); } + const auto now = QDateTime::currentDateTime(); + int peersMax = -1; int seedsMax = -1; int leechesMax = -1; int downloadedMax = -1; + QDateTime nextAnnounceTime; + QDateTime minAnnounceTime; + QString message; int index = 0; for (const auto &endpoint : entry.stats) @@ -450,6 +474,26 @@ void TrackerListWidget::loadTrackers() leechesMax = std::max(leechesMax, protocolStats.numLeeches); downloadedMax = std::max(downloadedMax, protocolStats.numDownloaded); + if (protocolStats.status == entry.status) + { + if (!nextAnnounceTime.isValid() || (nextAnnounceTime > protocolStats.nextAnnounceTime)) + { + nextAnnounceTime = protocolStats.nextAnnounceTime; + minAnnounceTime = protocolStats.minAnnounceTime; + if ((protocolStats.status != BitTorrent::TrackerEntry::Status::Working) + || !protocolStats.message.isEmpty()) + { + message = protocolStats.message; + } + } + + if (protocolStats.status == BitTorrent::TrackerEntry::Status::Working) + { + if (message.isEmpty()) + message = protocolStats.message; + } + } + QTreeWidgetItem *child = (index < item->childCount()) ? item->child(index) : new QTreeWidgetItem(item); child->setText(COL_URL, protocolStats.name); child->setText(COL_PROTOCOL, tr("v%1").arg(protocolVersion)); @@ -460,6 +504,8 @@ void TrackerListWidget::loadTrackers() child->setText(COL_TIMES_DOWNLOADED, prettyCount(protocolStats.numDownloaded)); child->setText(COL_MSG, protocolStats.message); child->setToolTip(COL_MSG, protocolStats.message); + child->setText(COL_NEXT_ANNOUNCE, Utils::Misc::userFriendlyDuration(now.secsTo(protocolStats.nextAnnounceTime), -1, Utils::Misc::TimeResolution::Seconds)); + child->setText(COL_MIN_ANNOUNCE, Utils::Misc::userFriendlyDuration(now.secsTo(protocolStats.minAnnounceTime), -1, Utils::Misc::TimeResolution::Seconds)); setAlignment(child); ++index; } @@ -475,7 +521,9 @@ void TrackerListWidget::loadTrackers() item->setData(COL_SEEDS, Qt::UserRole, prettyCount(seedsMax)); item->setData(COL_LEECHES, Qt::UserRole, prettyCount(leechesMax)); item->setData(COL_TIMES_DOWNLOADED, Qt::UserRole, prettyCount(downloadedMax)); - item->setData(COL_MSG, Qt::UserRole, entry.message); + item->setData(COL_MSG, Qt::UserRole, message); + item->setData(COL_NEXT_ANNOUNCE, Qt::UserRole, nextAnnounceTime); + item->setData(COL_MIN_ANNOUNCE, Qt::UserRole, minAnnounceTime); if (!item->isExpanded()) { item->setText(COL_PEERS, item->data(COL_PEERS, Qt::UserRole).toString()); @@ -483,6 +531,10 @@ void TrackerListWidget::loadTrackers() item->setText(COL_LEECHES, item->data(COL_LEECHES, Qt::UserRole).toString()); item->setText(COL_TIMES_DOWNLOADED, item->data(COL_TIMES_DOWNLOADED, Qt::UserRole).toString()); item->setText(COL_MSG, item->data(COL_MSG, Qt::UserRole).toString()); + const auto secsToNextAnnounce = now.secsTo(item->data(COL_NEXT_ANNOUNCE, Qt::UserRole).toDateTime()); + item->setText(COL_NEXT_ANNOUNCE, Utils::Misc::userFriendlyDuration(secsToNextAnnounce, -1, Utils::Misc::TimeResolution::Seconds)); + const auto secsToMinAnnounce = now.secsTo(item->data(COL_MIN_ANNOUNCE, Qt::UserRole).toDateTime()); + item->setText(COL_MIN_ANNOUNCE, Utils::Misc::userFriendlyDuration(secsToMinAnnounce, -1, Utils::Misc::TimeResolution::Seconds)); } setAlignment(item); } @@ -687,6 +739,8 @@ QStringList TrackerListWidget::headerLabels() , tr("Leeches") , tr("Times Downloaded") , tr("Message") + , tr("Next announce") + , tr("Min announce") }; } diff --git a/src/gui/properties/trackerlistwidget.h b/src/gui/properties/trackerlistwidget.h index 777f52e2e..71287a4f7 100644 --- a/src/gui/properties/trackerlistwidget.h +++ b/src/gui/properties/trackerlistwidget.h @@ -55,6 +55,8 @@ public: COL_LEECHES, COL_TIMES_DOWNLOADED, COL_MSG, + COL_NEXT_ANNOUNCE, + COL_MIN_ANNOUNCE, COL_COUNT }; diff --git a/src/gui/transferlistfilters/trackersfilterwidget.cpp b/src/gui/transferlistfilters/trackersfilterwidget.cpp index 20c2d914a..87c87437e 100644 --- a/src/gui/transferlistfilters/trackersfilterwidget.cpp +++ b/src/gui/transferlistfilters/trackersfilterwidget.cpp @@ -335,7 +335,15 @@ void TrackersFilterWidget::handleTrackerEntriesUpdated(const BitTorrent::Torrent errored.remove(trackerEntry.url); } - if (trackerEntry.message.isEmpty()) + const bool hasNoWarningMessages = std::all_of(trackerEntry.stats.cbegin(), trackerEntry.stats.cend(), [](const auto &endpoint) + { + return std::all_of(endpoint.cbegin(), endpoint.cend() + , [](const BitTorrent::TrackerEntry::EndpointStats &protocolStats) + { + return protocolStats.message.isEmpty() || (protocolStats.status != BitTorrent::TrackerEntry::Working); + }); + }); + if (hasNoWarningMessages) { if (warningHashesIt != m_warnings.end()) { @@ -350,7 +358,9 @@ void TrackersFilterWidget::handleTrackerEntriesUpdated(const BitTorrent::Torrent warningHashesIt.value().insert(trackerEntry.url); } } - else if (trackerEntry.status == BitTorrent::TrackerEntry::NotWorking) + else if ((trackerEntry.status == BitTorrent::TrackerEntry::NotWorking) + || (trackerEntry.status == BitTorrent::TrackerEntry::TrackerError) + || (trackerEntry.status == BitTorrent::TrackerEntry::Unreachable)) { if (errorHashesIt == m_errors.end()) errorHashesIt = m_errors.insert(id, {}); diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index b65124529..5fa36a8b4 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -487,6 +487,9 @@ void TorrentsController::trackersAction() int numSeeds = -1; int numLeeches = -1; int numDownloaded = -1; + QDateTime nextAnnounceTime; + QDateTime minAnnounceTime; + QString message; for (const auto &endpoint : tracker.stats) { for (const auto &protocolStat : endpoint) @@ -495,15 +498,38 @@ void TorrentsController::trackersAction() numSeeds = std::max(numSeeds, protocolStat.numSeeds); numLeeches = std::max(numLeeches, protocolStat.numLeeches); numDownloaded = std::max(numDownloaded, protocolStat.numDownloaded); + + if (protocolStat.status == tracker.status) + { + if (!nextAnnounceTime.isValid() || (nextAnnounceTime > protocolStat.nextAnnounceTime)) + { + nextAnnounceTime = protocolStat.nextAnnounceTime; + minAnnounceTime = protocolStat.minAnnounceTime; + if ((protocolStat.status != BitTorrent::TrackerEntry::Status::Working) + || !protocolStat.message.isEmpty()) + { + message = protocolStat.message; + } + } + + if (protocolStat.status == BitTorrent::TrackerEntry::Status::Working) + { + if (message.isEmpty()) + message = protocolStat.message; + } + } } } + const bool isNotWorking = (tracker.status == BitTorrent::TrackerEntry::Status::NotWorking) + || (tracker.status == BitTorrent::TrackerEntry::Status::TrackerError) + || (tracker.status == BitTorrent::TrackerEntry::Status::Unreachable); trackerList << QJsonObject { {KEY_TRACKER_URL, tracker.url}, {KEY_TRACKER_TIER, tracker.tier}, - {KEY_TRACKER_STATUS, static_cast(tracker.status)}, - {KEY_TRACKER_MSG, tracker.message}, + {KEY_TRACKER_STATUS, static_cast((isNotWorking ? BitTorrent::TrackerEntry::Status::NotWorking : tracker.status))}, + {KEY_TRACKER_MSG, message}, {KEY_TRACKER_PEERS_COUNT, numPeers}, {KEY_TRACKER_SEEDS_COUNT, numSeeds}, {KEY_TRACKER_LEECHES_COUNT, numLeeches},