mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-17 14:08:03 -06:00
WebAPI: Provide announce stats within "sync" data
This commit is contained in:
@@ -120,6 +120,28 @@ namespace
|
|||||||
const QString KEY_FULL_UPDATE = u"full_update"_s;
|
const QString KEY_FULL_UPDATE = u"full_update"_s;
|
||||||
const QString KEY_RESPONSE_ID = u"rid"_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<BitTorrent::TorrentID> &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);
|
QVariantMap processMap(const QVariantMap &prevData, const QVariantMap &data);
|
||||||
std::pair<QVariantMap, QVariantList> processHash(QVariantHash prevData, const QVariantHash &data);
|
std::pair<QVariantMap, QVariantList> processHash(QVariantHash prevData, const QVariantHash &data);
|
||||||
std::pair<QVariantList, QVariantList> processList(QVariantList prevData, const QVariantList &data);
|
std::pair<QVariantList, QVariantList> processList(QVariantList prevData, const QVariantList &data);
|
||||||
@@ -384,6 +406,39 @@ namespace
|
|||||||
|
|
||||||
return QJsonObject::fromVariantMap(syncData);
|
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)
|
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::trackersAdded, this, &SyncController::onTorrentTrackersChanged);
|
||||||
connect(btSession, &BitTorrent::Session::trackersRemoved, this, &SyncController::onTorrentTrackersChanged);
|
connect(btSession, &BitTorrent::Session::trackersRemoved, this, &SyncController::onTorrentTrackersChanged);
|
||||||
connect(btSession, &BitTorrent::Session::trackersChanged, 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();
|
const int acceptedID = params()[u"rid"_s].toInt();
|
||||||
@@ -526,6 +582,7 @@ void SyncController::makeMaindataSnapshot()
|
|||||||
|
|
||||||
QVariantMap serializedTorrent = serialize(*torrent);
|
QVariantMap serializedTorrent = serialize(*torrent);
|
||||||
serializedTorrent.remove(KEY_TORRENT_ID);
|
serializedTorrent.remove(KEY_TORRENT_ID);
|
||||||
|
addAnnounceStats(serializedTorrent, torrent);
|
||||||
|
|
||||||
for (const BitTorrent::TrackerEntryStatus &status : asConst(torrent->trackers()))
|
for (const BitTorrent::TrackerEntryStatus &status : asConst(torrent->trackers()))
|
||||||
m_knownTrackers[status.url].insert(torrentID);
|
m_knownTrackers[status.url].insert(torrentID);
|
||||||
@@ -547,14 +604,8 @@ void SyncController::makeMaindataSnapshot()
|
|||||||
for (const Tag &tag : asConst(session->tags()))
|
for (const Tag &tag : asConst(session->tags()))
|
||||||
m_maindataSnapshot.tags.append(tag.toString());
|
m_maindataSnapshot.tags.append(tag.toString());
|
||||||
|
|
||||||
for (auto trackersIter = m_knownTrackers.cbegin(); trackersIter != m_knownTrackers.cend(); ++trackersIter)
|
for (const auto &[tracker, torrentIDs] : m_knownTrackers.asKeyValueRange())
|
||||||
{
|
m_maindataSnapshot.trackers[tracker] = asStrings(torrentIDs);
|
||||||
QStringList torrentIDs;
|
|
||||||
for (const BitTorrent::TorrentID &torrentID : asConst(trackersIter.value()))
|
|
||||||
torrentIDs.append(torrentID.toString());
|
|
||||||
|
|
||||||
m_maindataSnapshot.trackers[trackersIter.key()] = torrentIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_maindataSnapshot.serverState = getTransferInfo();
|
m_maindataSnapshot.serverState = getTransferInfo();
|
||||||
m_maindataSnapshot.serverState[KEY_TRANSFER_FREESPACEONDISK] = m_freeDiskSpace;
|
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))
|
for (const BitTorrent::TorrentID &torrentID : asConst(m_updatedTorrents))
|
||||||
m_maindataSyncBuf.removedTorrents.removeOne(torrentID.toString());
|
m_maindataSyncBuf.removedTorrents.removeOne(torrentID.toString());
|
||||||
|
|
||||||
for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents))
|
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))
|
for (const QString &tracker : asConst(m_updatedTrackers))
|
||||||
m_maindataSyncBuf.removedTrackers.removeOne(tracker);
|
m_maindataSyncBuf.removedTrackers.removeOne(tracker);
|
||||||
@@ -635,30 +690,64 @@ QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fu
|
|||||||
QVariantMap serializedTorrent = serialize(*torrent);
|
QVariantMap serializedTorrent = serialize(*torrent);
|
||||||
serializedTorrent.remove(KEY_TORRENT_ID);
|
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())
|
if (const QVariantMap syncData = processMap(torrentSnapshot, serializedTorrent); !syncData.isEmpty())
|
||||||
{
|
{
|
||||||
m_maindataSyncBuf.torrents[torrentID.toString()] = syncData;
|
m_maindataSyncBuf.torrents[torrentIDStr] = syncData;
|
||||||
torrentSnapshot = serializedTorrent;
|
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_updatedTorrents.clear();
|
||||||
|
m_announcedTorrents.clear();
|
||||||
|
|
||||||
for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents))
|
for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents))
|
||||||
{
|
{
|
||||||
m_maindataSyncBuf.removedTorrents.append(torrentID.toString());
|
const QString torrentIDStr = torrentID.toString();
|
||||||
m_maindataSnapshot.torrents.remove(torrentID.toString());
|
|
||||||
|
m_maindataSyncBuf.removedTorrents.append(torrentIDStr);
|
||||||
|
m_maindataSnapshot.torrents.remove(torrentIDStr);
|
||||||
}
|
}
|
||||||
m_removedTorrents.clear();
|
m_removedTorrents.clear();
|
||||||
|
|
||||||
for (const QString &tracker : asConst(m_updatedTrackers))
|
for (const QString &tracker : asConst(m_updatedTrackers))
|
||||||
{
|
{
|
||||||
const QSet<BitTorrent::TorrentID> torrentIDs = m_knownTrackers[tracker];
|
const QStringList serializedTorrentIDs = asStrings(m_knownTrackers[tracker]);
|
||||||
QStringList serializedTorrentIDs;
|
|
||||||
serializedTorrentIDs.reserve(torrentIDs.size());
|
|
||||||
for (const BitTorrent::TorrentID &torrentID : torrentIDs)
|
|
||||||
serializedTorrentIDs.append(torrentID.toString());
|
|
||||||
|
|
||||||
m_maindataSyncBuf.trackers[tracker] = serializedTorrentIDs;
|
m_maindataSyncBuf.trackers[tracker] = serializedTorrentIDs;
|
||||||
m_maindataSnapshot.trackers[tracker] = serializedTorrentIDs;
|
m_maindataSnapshot.trackers[tracker] = serializedTorrentIDs;
|
||||||
@@ -847,6 +936,7 @@ void SyncController::onTorrentAdded(BitTorrent::Torrent *torrent)
|
|||||||
|
|
||||||
m_removedTorrents.remove(torrentID);
|
m_removedTorrents.remove(torrentID);
|
||||||
m_updatedTorrents.insert(torrentID);
|
m_updatedTorrents.insert(torrentID);
|
||||||
|
m_announcedTorrents.insert(torrentID);
|
||||||
|
|
||||||
for (const BitTorrent::TrackerEntryStatus &status : asConst(torrent->trackers()))
|
for (const BitTorrent::TrackerEntryStatus &status : asConst(torrent->trackers()))
|
||||||
{
|
{
|
||||||
@@ -860,6 +950,7 @@ void SyncController::onTorrentAboutToBeRemoved(BitTorrent::Torrent *torrent)
|
|||||||
{
|
{
|
||||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||||
|
|
||||||
|
m_announcedTorrents.remove(torrentID);
|
||||||
m_updatedTorrents.remove(torrentID);
|
m_updatedTorrents.remove(torrentID);
|
||||||
m_removedTorrents.insert(torrentID);
|
m_removedTorrents.insert(torrentID);
|
||||||
|
|
||||||
@@ -899,6 +990,7 @@ void SyncController::onTorrentMetadataReceived(BitTorrent::Torrent *torrent)
|
|||||||
void SyncController::onTorrentStopped(BitTorrent::Torrent *torrent)
|
void SyncController::onTorrentStopped(BitTorrent::Torrent *torrent)
|
||||||
{
|
{
|
||||||
m_updatedTorrents.insert(torrent->id());
|
m_updatedTorrents.insert(torrent->id());
|
||||||
|
m_announcedTorrents.insert(torrent->id());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncController::onTorrentStarted(BitTorrent::Torrent *torrent)
|
void SyncController::onTorrentStarted(BitTorrent::Torrent *torrent)
|
||||||
@@ -981,4 +1073,12 @@ void SyncController::onTorrentTrackersChanged(BitTorrent::Torrent *torrent)
|
|||||||
m_removedTrackers.remove(currentTracker);
|
m_removedTrackers.remove(currentTracker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_announcedTorrents.insert(torrentID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncController::onTorrentTrackerEntryStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||||
|
, [[maybe_unused]] const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers)
|
||||||
|
{
|
||||||
|
m_announcedTorrents.insert(torrent->id());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2018-2023 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2018-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class Torrent;
|
class Torrent;
|
||||||
|
struct TrackerEntryStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncController : public APIController
|
class SyncController : public APIController
|
||||||
@@ -79,6 +80,8 @@ private:
|
|||||||
void onTorrentTagRemoved(BitTorrent::Torrent *torrent, const Tag &tag);
|
void onTorrentTagRemoved(BitTorrent::Torrent *torrent, const Tag &tag);
|
||||||
void onTorrentsUpdated(const QList<BitTorrent::Torrent *> &torrents);
|
void onTorrentsUpdated(const QList<BitTorrent::Torrent *> &torrents);
|
||||||
void onTorrentTrackersChanged(BitTorrent::Torrent *torrent);
|
void onTorrentTrackersChanged(BitTorrent::Torrent *torrent);
|
||||||
|
void onTorrentTrackerEntryStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||||
|
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers);
|
||||||
|
|
||||||
qint64 m_freeDiskSpace = 0;
|
qint64 m_freeDiskSpace = 0;
|
||||||
|
|
||||||
@@ -94,20 +97,24 @@ private:
|
|||||||
QSet<QString> m_updatedTrackers;
|
QSet<QString> m_updatedTrackers;
|
||||||
QSet<QString> m_removedTrackers;
|
QSet<QString> m_removedTrackers;
|
||||||
QSet<BitTorrent::TorrentID> m_updatedTorrents;
|
QSet<BitTorrent::TorrentID> m_updatedTorrents;
|
||||||
|
QSet<BitTorrent::TorrentID> m_announcedTorrents;
|
||||||
QSet<BitTorrent::TorrentID> m_removedTorrents;
|
QSet<BitTorrent::TorrentID> m_removedTorrents;
|
||||||
|
|
||||||
struct MaindataSyncBuf
|
struct MaindataSyncBuf
|
||||||
{
|
{
|
||||||
QHash<QString, QVariantMap> categories;
|
QHash<QString, QVariantMap> categories;
|
||||||
QVariantList tags;
|
|
||||||
QHash<QString, QVariantMap> torrents;
|
|
||||||
QHash<QString, QStringList> trackers;
|
|
||||||
QVariantMap serverState;
|
|
||||||
|
|
||||||
QStringList removedCategories;
|
QStringList removedCategories;
|
||||||
|
|
||||||
|
QVariantList tags;
|
||||||
QStringList removedTags;
|
QStringList removedTags;
|
||||||
|
|
||||||
|
QHash<QString, QVariantMap> torrents;
|
||||||
QStringList removedTorrents;
|
QStringList removedTorrents;
|
||||||
|
|
||||||
|
QHash<QString, QStringList> trackers;
|
||||||
QStringList removedTrackers;
|
QStringList removedTrackers;
|
||||||
|
|
||||||
|
QVariantMap serverState;
|
||||||
};
|
};
|
||||||
|
|
||||||
MaindataSyncBuf m_maindataSnapshot;
|
MaindataSyncBuf m_maindataSnapshot;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
#include "base/utils/version.h"
|
#include "base/utils/version.h"
|
||||||
#include "api/isessionmanager.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 APIController;
|
||||||
class AuthController;
|
class AuthController;
|
||||||
|
|||||||
Reference in New Issue
Block a user