Files
utun2/tools/chatgui/src/stream_sources.h
T

52 lines
3.8 KiB
C++

/* Разбор снимка CHAT_EVT_STREAM_SOURCES для шапки чата и отдельных окон просмотра.
* Формат: group:8BE, valid:1 (0/1), JSON-массив UTF-8 без завершающего NUL.
* parseStreamSources заменяет out целиком только при успехе; ошибка сохраняет прежний out и логируется в stream.
* Успешный разбор с valid=false сообщает недоступность: UI сохраняет прежний каталог, а не очищает его.
* Разбор не сверяет выбранную группу, членство или подписи; получатель проверяет group перед применением. */
#pragma once
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QSet>
#include <QtEndian>
#include "../../../lib/debug_config.h"
struct LiveStreamSource {
quint64 id, epoch; // Ненулевые ID узла-источника и эпоха его публикации; ID уникален внутри снимка.
QString name; // Отображаемое имя; пустое имя JSON заменяется десятичным ID.
bool self = false; // Собственная публикация: числовое поле self из JSON, ненулевое после toInt.
};
struct StreamSourceSnapshot {
quint64 group = 0; // Ненулевой ID CHAT-группы при успешном разборе.
bool valid = false; // Каталог доступен; false не означает подтверждённое отсутствие трансляций.
QList<LiveStreamSource> sources; // Порядок JSON; применять к UI только при valid=true.
};
// data заимствован на время вызова; length — полное событие, включая 9-байтный префикс.
// true: JSON-массив с уникальными ненулевыми id (десятичная строка), epoch (hex-строка) и строковым name.
// self необязателен: отсутствующее/неприводимое к int значение даёт false. false: out не меняется.
inline bool parseStreamSources(const uint8_t* data, int length, StreamSourceSnapshot& out) {
if (!data || length < 11 || data[8] > 1) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop source event invalid bytes=%d", length); return false;
}
StreamSourceSnapshot next; next.group = qFromBigEndian<quint64>(data); next.valid = data[8];
QJsonParseError error;
auto document = QJsonDocument::fromJson(QByteArray(reinterpret_cast<const char*>(data + 9), length - 9), &error);
if (!next.group || error.error != QJsonParseError::NoError || !document.isArray()) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop source JSON invalid group=%llx error=%s",
(unsigned long long)next.group, qPrintable(error.errorString())); return false;
}
QSet<quint64> seen;
for (const auto& value : document.array()) {
auto member = value.toObject(); bool idOk = false, epochOk = false;
quint64 id = member.value("id").toString().toULongLong(&idOk);
quint64 epoch = member.value("epoch").toString().toULongLong(&epochOk, 16);
if (!idOk || !epochOk || !id || !epoch || seen.contains(id) || !member.value("name").isString()) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop source identity invalid group=%llx", (unsigned long long)next.group); return false;
}
QString name = member.value("name").toString(); if (name.isEmpty()) name = QString::number(id);
next.sources.append({id, epoch, name, member.value("self").toInt() != 0}); seen.insert(id);
}
out = std::move(next); return true;
}