From ae461e746fb3ff77f7b42be680cd13ac5298cb92 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 13:02:01 +0200 Subject: [PATCH] Update Android consumers for extended member snapshots --- doc/member_files.txt | 8 ++++---- .../java/com/utun/chat/data/ChatRepository.kt | 16 +++++++++++----- .../com/utun/chat/viewmodel/ChatViewModel.kt | 10 +++++----- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/doc/member_files.txt b/doc/member_files.txt index 0fc12609..91817d91 100644 --- a/doc/member_files.txt +++ b/doc/member_files.txt @@ -316,10 +316,10 @@ AvatarManager общий для списка участников и сообщ test_avatars проверяет нормализацию, хеш/размеры, очистку temp, настоящее storage в двух каналах, публикацию, сохранение при смене имени, DM-кеш, restart без сети, -автоматический download после потери кеша и удаление ссылок. test_member_adapter -Проверяет также max_bytes до открытия local temp и смену аватарки при задержанном -download старой версии: поздний результат не заменяет новый рисунок. -проверяет подписанные профили 191/255/256/512/1023/1024 байт вместе с adm_tags до 8192. +автоматический download после потери кеша и удаление ссылок. Также проверяются +max_bytes до открытия local temp и смена аватарки при задержанном download старой +версии: поздний результат не заменяет новый рисунок. test_member_adapter проверяет +подписанные профили 191/255/256/512/1023/1024 байт вместе с adm_tags до 8192. Категория media=debug показывает discovery, cache hit/eviction, направление, group/owner/server/hash, ошибки проверки и retry; media=info — передачи/публикации. diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt index 0df23a67..643a5d6d 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt @@ -161,16 +161,18 @@ fun parseConnList(data: ByteArray): List { return list } -/* ── Member wire format (79 bytes, from chat_member.h) ── */ +/* Размер общего снимка из chat_member.h; одинаковый для desktop и Android. */ +const val MEMBER_WIRE_SIZE = 157 data class MemberWire( val nodeId: Long, val online: Int, val isSelf: Boolean, val flags: Int, val rtt: Int, val natType: Int, - val storage: Boolean, val name: String + val storage: Boolean, val name: String, + val avatarServer: Long = 0, val avatarFile: String = "", val storageRole: Boolean = false ) fun parseMemberWire(data: ByteArray, offset: Int = 0): MemberWire? { - if (offset < 0 || offset > data.size - 79) return null - val buf = ByteBuffer.wrap(data, offset, 79).order(ByteOrder.LITTLE_ENDIAN) + if (offset < 0 || offset > data.size - MEMBER_WIRE_SIZE) return null + val buf = ByteBuffer.wrap(data, offset, MEMBER_WIRE_SIZE).order(ByteOrder.LITTLE_ENDIAN) val nodeId = buf.getLong() val online = buf.get().toInt() and 0xFF val isSelf = buf.get().toInt() != 0 @@ -180,7 +182,11 @@ fun parseMemberWire(data: ByteArray, offset: Int = 0): MemberWire? { val storage = buf.get().toInt() != 0 val nameBytes = ByteArray(64); buf.get(nameBytes) val name = String(nameBytes).trimEnd('\u0000') - return MemberWire(nodeId, online, isSelf, flags, rtt, natType, storage, name) + val avatarServer = buf.getLong() + val avatarBytes = ByteArray(69); buf.get(avatarBytes) + val avatarFile = String(avatarBytes).trimEnd('\u0000') + val storageRole = buf.get().toInt() != 0 + return MemberWire(nodeId, online, isSelf, flags, rtt, natType, storage, name, avatarServer, avatarFile, storageRole) } /* Wire не содержит verified/nodeType и состояния транспортов: сохраняем их из текущей строки. */ diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt index 4e4647ee..488602b8 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt @@ -467,11 +467,11 @@ class ChatViewModel : ViewModel() { val chId = String(data, 1, cl) val count = (data[1 + cl].toInt() and 0xFF) or ((data[2 + cl].toInt() and 0xFF) shl 8) val offset = cl + 3 - if (data.size != offset + count * 79) { + if (data.size != offset + count * MEMBER_WIRE_SIZE) { LogManager.addLog("ERROR", "VM", "MEMBER_LIST: invalid size ch=$chId count=$count bytes=${data.size}"); return } val radios = (0 until count).associate { i -> - val w = requireNotNull(parseMemberWire(data, offset + i * 79)) + val w = requireNotNull(parseMemberWire(data, offset + i * MEMBER_WIRE_SIZE)) w.nodeId to ((w.flags and 0x10) != 0) } memberRadios[chId] = radios @@ -479,12 +479,12 @@ class ChatViewModel : ViewModel() { _members.value = _members.value.map { it.copy(radioActive = radios[it.nodeId] == true) } LogManager.addLog("DEBUG", "VM", "MEMBER_LIST: ch=$chId members=$count radios=${radios.values.count { it }}") } - 24 -> { /* MEMBER_UPDATED: [ch_id_len:1][ch_id:var][count:1][member:79B] */ - if (data == null || data.size < 3 + 79) { + 24 -> { /* MEMBER_UPDATED: [ch_id_len:1][ch_id:var][count:1][member:MEMBER_WIRE_SIZE] */ + if (data == null || data.size < 3 + MEMBER_WIRE_SIZE) { LogManager.addLog("ERROR", "VM", "MEMBER_UPDATED: missing header/member"); return } val cl = data[0].toInt() and 0xFF - if (cl == 0 || data.size != cl + 2 + 79 || data[cl + 1].toInt() != 1) { + if (cl == 0 || data.size != cl + 2 + MEMBER_WIRE_SIZE || data[cl + 1].toInt() != 1) { LogManager.addLog("ERROR", "VM", "MEMBER_UPDATED: invalid payload channelLength=$cl bytes=${data.size}"); return } val chId = String(data, 1, cl)