diff --git a/src/webui/api/synccontroller.cpp b/src/webui/api/synccontroller.cpp index 574a833ed..5b547862b 100644 --- a/src/webui/api/synccontroller.cpp +++ b/src/webui/api/synccontroller.cpp @@ -120,6 +120,28 @@ namespace const QString KEY_FULL_UPDATE = u"full_update"_s; const QString KEY_RESPONSE_ID = u"rid"_s; + const QString KEY_TORRENT_HAS_TRACKER_WARNING = u"has_tracker_warning"_s; + const QString KEY_TORRENT_HAS_TRACKER_ERROR = u"has_tracker_error"_s; + const QString KEY_TORRENT_HAS_OTHER_ANNOUNCE_ERROR = u"has_other_announce_error"_s; + + QStringList asStrings(const QSet &torrentIDs) + { + QStringList result; + result.reserve(torrentIDs.size()); + for (const BitTorrent::TorrentID &torrentID : torrentIDs) + result.emplaceBack(torrentID.toString()); + + return result; + } + + bool hasWarningMessage(const BitTorrent::TrackerEntryStatus &status) + { + return std::ranges::any_of(status.endpoints, [](const BitTorrent::TrackerEndpointStatus &endpointEntry) + { + return (endpointEntry.state == BitTorrent::TrackerEndpointState::Working) && !endpointEntry.message.isEmpty(); + }); + } + QVariantMap processMap(const QVariantMap &prevData, const QVariantMap &data); std::pair processHash(QVariantHash prevData, const QVariantHash &data); std::pair processList(QVariantList prevData, const QVariantList &data); @@ -384,6 +406,39 @@ namespace return QJsonObject::fromVariantMap(syncData); } + + void addAnnounceStats(QVariantMap &serializedTorrent, const BitTorrent::Torrent *torrent) + { + bool hasTrackerWarning = false; + bool hasTrackerError = false; + bool hasOtherAnnounceError = false; + for (const BitTorrent::TrackerEntryStatus &status : asConst(torrent->trackers())) + { + switch (status.state) + { + case BitTorrent::TrackerEndpointState::Working: + if (!hasTrackerWarning && hasWarningMessage(status)) + hasTrackerWarning = true; + break; + case BitTorrent::TrackerEndpointState::TrackerError: + hasTrackerError = true; + break; + case BitTorrent::TrackerEndpointState::NotWorking: + case BitTorrent::TrackerEndpointState::Unreachable: + hasOtherAnnounceError = true; + break; + default: + break; + } + + if (hasTrackerWarning && hasTrackerError && hasOtherAnnounceError) + break; + } + + serializedTorrent[KEY_TORRENT_HAS_TRACKER_WARNING] = hasTrackerWarning; + serializedTorrent[KEY_TORRENT_HAS_TRACKER_ERROR] = hasTrackerError; + serializedTorrent[KEY_TORRENT_HAS_OTHER_ANNOUNCE_ERROR] = hasOtherAnnounceError; + } } SyncController::SyncController(IApplication *app, QObject *parent) @@ -488,6 +543,7 @@ void SyncController::maindataAction() connect(btSession, &BitTorrent::Session::trackersAdded, this, &SyncController::onTorrentTrackersChanged); connect(btSession, &BitTorrent::Session::trackersRemoved, this, &SyncController::onTorrentTrackersChanged); connect(btSession, &BitTorrent::Session::trackersChanged, this, &SyncController::onTorrentTrackersChanged); + connect(btSession, &BitTorrent::Session::trackerEntryStatusesUpdated, this, &SyncController::onTorrentTrackerEntryStatusesUpdated); } const int acceptedID = params()[u"rid"_s].toInt(); @@ -526,6 +582,7 @@ void SyncController::makeMaindataSnapshot() QVariantMap serializedTorrent = serialize(*torrent); serializedTorrent.remove(KEY_TORRENT_ID); + addAnnounceStats(serializedTorrent, torrent); for (const BitTorrent::TrackerEntryStatus &status : asConst(torrent->trackers())) m_knownTrackers[status.url].insert(torrentID); @@ -547,14 +604,8 @@ void SyncController::makeMaindataSnapshot() for (const Tag &tag : asConst(session->tags())) m_maindataSnapshot.tags.append(tag.toString()); - for (auto trackersIter = m_knownTrackers.cbegin(); trackersIter != m_knownTrackers.cend(); ++trackersIter) - { - QStringList torrentIDs; - for (const BitTorrent::TorrentID &torrentID : asConst(trackersIter.value())) - torrentIDs.append(torrentID.toString()); - - m_maindataSnapshot.trackers[trackersIter.key()] = torrentIDs; - } + for (const auto &[tracker, torrentIDs] : m_knownTrackers.asKeyValueRange()) + m_maindataSnapshot.trackers[tracker] = asStrings(torrentIDs); m_maindataSnapshot.serverState = getTransferInfo(); m_maindataSnapshot.serverState[KEY_TRANSFER_FREESPACEONDISK] = m_freeDiskSpace; @@ -579,8 +630,12 @@ QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fu for (const BitTorrent::TorrentID &torrentID : asConst(m_updatedTorrents)) m_maindataSyncBuf.removedTorrents.removeOne(torrentID.toString()); + for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents)) - m_maindataSyncBuf.torrents.remove(torrentID.toString()); + { + const QString torrentIDStr = torrentID.toString(); + m_maindataSyncBuf.torrents.remove(torrentIDStr); + } for (const QString &tracker : asConst(m_updatedTrackers)) m_maindataSyncBuf.removedTrackers.removeOne(tracker); @@ -635,30 +690,64 @@ QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fu QVariantMap serializedTorrent = serialize(*torrent); serializedTorrent.remove(KEY_TORRENT_ID); - auto &torrentSnapshot = m_maindataSnapshot.torrents[torrentID.toString()]; + const QString torrentIDStr = torrentID.toString(); + auto &torrentSnapshot = m_maindataSnapshot.torrents[torrentIDStr]; + + if (m_announcedTorrents.contains(torrentID)) + { + addAnnounceStats(serializedTorrent, torrent); + } + else + { + serializedTorrent[KEY_TORRENT_HAS_TRACKER_WARNING] = torrentSnapshot[KEY_TORRENT_HAS_TRACKER_WARNING]; + serializedTorrent[KEY_TORRENT_HAS_TRACKER_ERROR] = torrentSnapshot[KEY_TORRENT_HAS_TRACKER_ERROR]; + serializedTorrent[KEY_TORRENT_HAS_OTHER_ANNOUNCE_ERROR] = torrentSnapshot[KEY_TORRENT_HAS_OTHER_ANNOUNCE_ERROR]; + } if (const QVariantMap syncData = processMap(torrentSnapshot, serializedTorrent); !syncData.isEmpty()) { - m_maindataSyncBuf.torrents[torrentID.toString()] = syncData; + m_maindataSyncBuf.torrents[torrentIDStr] = syncData; torrentSnapshot = serializedTorrent; } } + + for (const BitTorrent::TorrentID &torrentID : asConst(m_announcedTorrents)) + { + if (m_updatedTorrents.contains(torrentID)) + continue; + + const BitTorrent::Torrent *torrent = session->getTorrent(torrentID); + Q_ASSERT(torrent); + + const QString torrentIDStr = torrentID.toString(); + auto &torrentSnapshot = m_maindataSnapshot.torrents[torrentIDStr]; + + // Only announce stats are changed so don't need to serialize torrent again + QVariantMap serializedTorrent = torrentSnapshot; + addAnnounceStats(serializedTorrent, torrent); + + if (const QVariantMap syncData = processMap(torrentSnapshot, serializedTorrent); !syncData.isEmpty()) + { + m_maindataSyncBuf.torrents[torrentIDStr] = syncData; + torrentSnapshot = serializedTorrent; + } + } + m_updatedTorrents.clear(); + m_announcedTorrents.clear(); for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents)) { - m_maindataSyncBuf.removedTorrents.append(torrentID.toString()); - m_maindataSnapshot.torrents.remove(torrentID.toString()); + const QString torrentIDStr = torrentID.toString(); + + m_maindataSyncBuf.removedTorrents.append(torrentIDStr); + m_maindataSnapshot.torrents.remove(torrentIDStr); } m_removedTorrents.clear(); for (const QString &tracker : asConst(m_updatedTrackers)) { - const QSet torrentIDs = m_knownTrackers[tracker]; - QStringList serializedTorrentIDs; - serializedTorrentIDs.reserve(torrentIDs.size()); - for (const BitTorrent::TorrentID &torrentID : torrentIDs) - serializedTorrentIDs.append(torrentID.toString()); + const QStringList serializedTorrentIDs = asStrings(m_knownTrackers[tracker]); m_maindataSyncBuf.trackers[tracker] = serializedTorrentIDs; m_maindataSnapshot.trackers[tracker] = serializedTorrentIDs; @@ -847,6 +936,7 @@ void SyncController::onTorrentAdded(BitTorrent::Torrent *torrent) m_removedTorrents.remove(torrentID); m_updatedTorrents.insert(torrentID); + m_announcedTorrents.insert(torrentID); for (const BitTorrent::TrackerEntryStatus &status : asConst(torrent->trackers())) { @@ -860,6 +950,7 @@ void SyncController::onTorrentAboutToBeRemoved(BitTorrent::Torrent *torrent) { const BitTorrent::TorrentID torrentID = torrent->id(); + m_announcedTorrents.remove(torrentID); m_updatedTorrents.remove(torrentID); m_removedTorrents.insert(torrentID); @@ -899,6 +990,7 @@ void SyncController::onTorrentMetadataReceived(BitTorrent::Torrent *torrent) void SyncController::onTorrentStopped(BitTorrent::Torrent *torrent) { m_updatedTorrents.insert(torrent->id()); + m_announcedTorrents.insert(torrent->id()); } void SyncController::onTorrentStarted(BitTorrent::Torrent *torrent) @@ -981,4 +1073,12 @@ void SyncController::onTorrentTrackersChanged(BitTorrent::Torrent *torrent) m_removedTrackers.remove(currentTracker); } } + + m_announcedTorrents.insert(torrentID); +} + +void SyncController::onTorrentTrackerEntryStatusesUpdated(const BitTorrent::Torrent *torrent + , [[maybe_unused]] const QHash &updatedTrackers) +{ + m_announcedTorrents.insert(torrent->id()); } diff --git a/src/webui/api/synccontroller.h b/src/webui/api/synccontroller.h index af2223827..9d7009c00 100644 --- a/src/webui/api/synccontroller.h +++ b/src/webui/api/synccontroller.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2018-2023 Vladimir Golovnev + * Copyright (C) 2018-2025 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -38,6 +38,7 @@ namespace BitTorrent { class Torrent; + struct TrackerEntryStatus; } class SyncController : public APIController @@ -79,6 +80,8 @@ private: void onTorrentTagRemoved(BitTorrent::Torrent *torrent, const Tag &tag); void onTorrentsUpdated(const QList &torrents); void onTorrentTrackersChanged(BitTorrent::Torrent *torrent); + void onTorrentTrackerEntryStatusesUpdated(const BitTorrent::Torrent *torrent + , const QHash &updatedTrackers); qint64 m_freeDiskSpace = 0; @@ -94,20 +97,24 @@ private: QSet m_updatedTrackers; QSet m_removedTrackers; QSet m_updatedTorrents; + QSet m_announcedTorrents; QSet m_removedTorrents; struct MaindataSyncBuf { QHash categories; - QVariantList tags; - QHash torrents; - QHash trackers; - QVariantMap serverState; - QStringList removedCategories; + + QVariantList tags; QStringList removedTags; + + QHash torrents; QStringList removedTorrents; + + QHash trackers; QStringList removedTrackers; + + QVariantMap serverState; }; MaindataSyncBuf m_maindataSnapshot; diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index b5f015449..a55b3a7de 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -53,7 +53,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 11, 6}; +inline const Utils::Version<3, 2> API_VERSION {2, 11, 7}; class APIController; class AuthController;