WebAPI: Prevent producing empty sync data

PR #21688.
This commit is contained in:
Vladimir Golovnev 2024-10-28 09:40:13 +03:00 committed by GitHub
commit 91b2687032
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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-2024 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
@ -28,8 +28,6 @@
#include "synccontroller.h" #include "synccontroller.h"
#include <algorithm>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QMetaObject> #include <QMetaObject>
@ -119,9 +117,9 @@ 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;
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData); QVariantMap processMap(const QVariantMap &prevData, const QVariantMap &data);
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems); std::pair<QVariantMap, QVariantList> processHash(QVariantHash prevData, const QVariantHash &data);
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems); std::pair<QVariantList, QVariantList> processList(QVariantList prevData, const QVariantList &data);
QJsonObject generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData); QJsonObject generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData);
QVariantMap getTransferInfo() QVariantMap getTransferInfo()
@ -171,31 +169,28 @@ namespace
// Compare two structures (prevData, data) and calculate difference (syncData). // Compare two structures (prevData, data) and calculate difference (syncData).
// Structures encoded as map. // Structures encoded as map.
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData) QVariantMap processMap(const QVariantMap &prevData, const QVariantMap &data)
{ {
// initialize output variable // initialize output variable
syncData.clear(); QVariantMap syncData;
for (auto i = data.cbegin(); i != data.cend(); ++i) for (auto i = data.cbegin(); i != data.cend(); ++i)
{ {
const QString &key = i.key(); const QString &key = i.key();
const QVariant &value = i.value(); const QVariant &value = i.value();
QVariantList removedItems;
switch (value.userType()) switch (value.userType())
{ {
case QMetaType::QVariantMap: case QMetaType::QVariantMap:
{ {
QVariantMap map; const QVariantMap map = processMap(prevData[key].toMap(), value.toMap());
processMap(prevData[key].toMap(), value.toMap(), map);
if (!map.isEmpty()) if (!map.isEmpty())
syncData[key] = map; syncData[key] = map;
} }
break; break;
case QMetaType::QVariantHash: case QMetaType::QVariantHash:
{ {
QVariantMap map; const auto [map, removedItems] = processHash(prevData[key].toHash(), value.toHash());
processHash(prevData[key].toHash(), value.toHash(), map, removedItems);
if (!map.isEmpty()) if (!map.isEmpty())
syncData[key] = map; syncData[key] = map;
if (!removedItems.isEmpty()) if (!removedItems.isEmpty())
@ -204,8 +199,7 @@ namespace
break; break;
case QMetaType::QVariantList: case QMetaType::QVariantList:
{ {
QVariantList list; const auto [list, removedItems] = processList(prevData[key].toList(), value.toList());
processList(prevData[key].toList(), value.toList(), list, removedItems);
if (!list.isEmpty()) if (!list.isEmpty())
syncData[key] = list; syncData[key] = list;
if (!removedItems.isEmpty()) if (!removedItems.isEmpty())
@ -228,21 +222,22 @@ namespace
break; break;
default: default:
Q_ASSERT_X(false, "processMap" Q_ASSERT_X(false, "processMap"
, u"Unexpected type: %1"_s , u"Unexpected type: %1"_s.arg(QString::fromLatin1(value.metaType().name()))
.arg(QString::fromLatin1(value.metaType().name()))
.toUtf8().constData()); .toUtf8().constData());
} }
} }
return syncData;
} }
// Compare two lists of structures (prevData, data) and calculate difference (syncData, removedItems). // Compare two lists of structures (prevData, data) and calculate difference (syncData, removedItems).
// Structures encoded as map. // Structures encoded as map.
// Lists are encoded as hash table (indexed by structure key value) to improve ease of searching for removed items. // Lists are encoded as hash table (indexed by structure key value) to improve ease of searching for removed items.
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems) std::pair<QVariantMap, QVariantList> processHash(QVariantHash prevData, const QVariantHash &data)
{ {
// initialize output variables // initialize output variables
syncData.clear(); std::pair<QVariantMap, QVariantList> result;
removedItems.clear(); auto &[syncData, removedItems] = result;
if (prevData.isEmpty()) if (prevData.isEmpty())
{ {
@ -264,8 +259,7 @@ namespace
} }
else else
{ {
QVariantMap map; const QVariantMap map = processMap(prevData[i.key()].toMap(), i.value().toMap());
processMap(prevData[i.key()].toMap(), i.value().toMap(), map);
// existing list item found - remove it from prevData // existing list item found - remove it from prevData
prevData.remove(i.key()); prevData.remove(i.key());
if (!map.isEmpty()) if (!map.isEmpty())
@ -283,9 +277,7 @@ namespace
} }
else else
{ {
QVariantList list; const auto [list, removedList] = processList(prevData[i.key()].toList(), i.value().toList());
QVariantList removedList;
processList(prevData[i.key()].toList(), i.value().toList(), list, removedList);
// existing list item found - remove it from prevData // existing list item found - remove it from prevData
prevData.remove(i.key()); prevData.remove(i.key());
if (!list.isEmpty() || !removedList.isEmpty()) if (!list.isEmpty() || !removedList.isEmpty())
@ -309,14 +301,16 @@ namespace
removedItems << i.key(); removedItems << i.key();
} }
} }
return result;
} }
// Compare two lists of simple value (prevData, data) and calculate difference (syncData, removedItems). // Compare two lists of simple value (prevData, data) and calculate difference (syncData, removedItems).
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems) std::pair<QVariantList, QVariantList> processList(QVariantList prevData, const QVariantList &data)
{ {
// initialize output variables // initialize output variables
syncData.clear(); std::pair<QVariantList, QVariantList> result;
removedItems.clear(); auto &[syncData, removedItems] = result;
if (prevData.isEmpty()) if (prevData.isEmpty())
{ {
@ -346,6 +340,8 @@ namespace
removedItems = prevData; removedItems = prevData;
} }
} }
return result;
} }
QJsonObject generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData) QJsonObject generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData)
@ -373,7 +369,7 @@ namespace
} }
else else
{ {
processMap(lastAcceptedData, data, syncData); syncData = processMap(lastAcceptedData, data);
} }
const int responseId = (lastResponseId % 1000000) + 1; // cycle between 1 and 1000000 const int responseId = (lastResponseId % 1000000) + 1; // cycle between 1 and 1000000
@ -595,9 +591,12 @@ QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fu
category.insert(u"name"_s, categoryName); category.insert(u"name"_s, categoryName);
auto &categorySnapshot = m_maindataSnapshot.categories[categoryName]; auto &categorySnapshot = m_maindataSnapshot.categories[categoryName];
processMap(categorySnapshot, category, m_maindataSyncBuf.categories[categoryName]); if (const QVariantMap syncData = processMap(categorySnapshot, category); !syncData.isEmpty())
{
m_maindataSyncBuf.categories[categoryName] = syncData;
categorySnapshot = category; categorySnapshot = category;
} }
}
m_updatedCategories.clear(); m_updatedCategories.clear();
for (const QString &category : asConst(m_removedCategories)) for (const QString &category : asConst(m_removedCategories))
@ -630,9 +629,13 @@ QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fu
serializedTorrent.remove(KEY_TORRENT_ID); serializedTorrent.remove(KEY_TORRENT_ID);
auto &torrentSnapshot = m_maindataSnapshot.torrents[torrentID.toString()]; auto &torrentSnapshot = m_maindataSnapshot.torrents[torrentID.toString()];
processMap(torrentSnapshot, serializedTorrent, m_maindataSyncBuf.torrents[torrentID.toString()]);
if (const QVariantMap syncData = processMap(torrentSnapshot, serializedTorrent); !syncData.isEmpty())
{
m_maindataSyncBuf.torrents[torrentID.toString()] = syncData;
torrentSnapshot = serializedTorrent; torrentSnapshot = serializedTorrent;
} }
}
m_updatedTorrents.clear(); m_updatedTorrents.clear();
for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents)) for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents))
@ -668,8 +671,11 @@ QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fu
serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled(); serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval(); serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
serverState[KEY_SYNC_MAINDATA_USE_SUBCATEGORIES] = session->isSubcategoriesEnabled(); serverState[KEY_SYNC_MAINDATA_USE_SUBCATEGORIES] = session->isSubcategoriesEnabled();
processMap(m_maindataSnapshot.serverState, serverState, m_maindataSyncBuf.serverState); if (const QVariantMap syncData = processMap(m_maindataSnapshot.serverState, serverState); !syncData.isEmpty())
{
m_maindataSyncBuf.serverState = syncData;
m_maindataSnapshot.serverState = serverState; m_maindataSnapshot.serverState = serverState;
}
QJsonObject syncData; QJsonObject syncData;
syncData[KEY_RESPONSE_ID] = id; syncData[KEY_RESPONSE_ID] = id;