From 8e108425ae1b0a2e4fa249cafa4a00b5839e5a27 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 1 Oct 2026 18:50:32 +0300 Subject: [PATCH] Track Android video probing and clean rejected cache files --- .../ui/components/ConversationComposer.kt | 3 +- .../com/utun/chat/viewmodel/ChatViewModel.kt | 47 ++++++++++++------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/ConversationComposer.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/ConversationComposer.kt index 516181b4..38fe921b 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/ConversationComposer.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/ConversationComposer.kt @@ -62,7 +62,8 @@ fun ConversationComposer(target: ConversationTarget, viewModel: ChatViewModel, o } if (pinned == activeTarget) { selected = attachment; sendAsVideo = false; chooseVideo = attachment.video } else LogManager.addLog("INFO", "Attachment", "picker discarded after target change") - } catch (e: Exception) { LogManager.addLog("ERROR", "Attachment", "picker failed: ${e.message}"); error = "Не удалось открыть вложение" } + } catch (e: CancellationException) { throw e } + catch (e: Exception) { LogManager.addLog("ERROR", "Attachment", "picker failed: ${e.message}"); error = "Не удалось открыть вложение" } } } Column { 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 f65215cb..1f1206a3 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 @@ -320,7 +320,7 @@ class ChatViewModel : ViewModel() { } 4 -> { if (repo != null) { LogManager.addLog("INFO", "VM", "CHANNEL_UPDATED — refreshChannels"); refreshChannels() } else { LogManager.addLog("WARN", "VM", "CHANNEL_UPDATED skipped — repo is NULL") } } 12 -> LogManager.addLog("INFO", "VM", "service started") - 13 -> LogManager.addLog("INFO", "VM", "service stopped") + 13 -> { _attachmentPreparations.value = emptyMap(); LogManager.addLog("INFO", "VM", "service stopped") } 16 -> { /* NODEINFO_UPDATED: [node_id:8][presence:1][up:1][best_rtt:2][bgp_rtt:2][bgp_nodes:2] */ if (data == null || data.size < 12) return val ns = parseNodeStatus(data) ?: return @@ -1024,7 +1024,7 @@ class ChatViewModel : ViewModel() { } fun sendAttachment(filePath: String, target: ConversationTarget, name: String = File(filePath).name) { - val r = repo ?: return + val r = repo ?: throw IllegalStateException("Сервис чата остановлен") if (target is ConversationTarget.Pm) { NativeLib.dmSendFile(target.id, filePath, name); return } val chId = target.id val ext = filePath.substringAfterLast('.', "").lowercase() @@ -1057,26 +1057,37 @@ class ChatViewModel : ViewModel() { } } + /** Cache-файл передаётся native только после успешного чтения метаданных. */ fun sendVideoMessage(filePath: String, target: ConversationTarget) { - val r = repo ?: return + val job = java.util.UUID.randomUUID().toString() + preparationState(target.nativeId, job, 0) viewModelScope.launch { - val meta = withContext(Dispatchers.IO) { - var dur = 0; var w = 0; var h = 0 - val mmr = MediaMetadataRetriever() - try { - mmr.setDataSource(filePath) - dur = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toIntOrNull() ?: 0 - w = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0 - h = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: 0 - } catch (e: Exception) { - LogManager.addLog("WARN", "VM", "video metadata failed: ${e.message}") - } finally { - try { mmr.release() } catch (_: Exception) {} + var accepted = false + try { + val r = repo ?: throw IllegalStateException("Сервис чата остановлен") + val meta = withContext(Dispatchers.IO) { + val mmr = MediaMetadataRetriever() + try { + mmr.setDataSource(filePath) + Triple( + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toIntOrNull() ?: 0, + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0, + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: 0 + ) + } finally { mmr.release() } + } + accepted = r.sendVideo(target.nativeId, filePath, meta.first, meta.second, meta.third) + if (!accepted) throw IllegalStateException("Некорректное видео или сервис остановлен") + LogManager.addLog("INFO", "VM", "video queued target=${target.nativeId} dur=${meta.first} ${meta.second}x${meta.third}") + } catch (e: kotlinx.coroutines.CancellationException) { throw e } + catch (e: Exception) { LogManager.addLog("ERROR", "VM", "video failed target=${target.nativeId}: ${e.message}") } + finally { + preparationState(target.nativeId, job, if (accepted) 1 else 2) + if (!accepted) withContext(kotlinx.coroutines.NonCancellable + Dispatchers.IO) { + if (!File(filePath).delete() && File(filePath).exists()) + LogManager.addLog("ERROR", "VM", "video cache cleanup failed: $filePath") } - Triple(dur, w, h) } - LogManager.addLog("INFO", "VM", "sendVideoMessage target=${target.nativeId} dur=${meta.first} ${meta.second}x${meta.third}") - r.sendVideo(target.nativeId, filePath, meta.first, meta.second, meta.third) } }