From 00083707c6b31a28c0898001b257b9b846b23c3a Mon Sep 17 00:00:00 2001 From: darken Date: Mon, 9 Mar 2026 17:29:45 +0100 Subject: [PATCH] fix(debug): Harden debug session recording and sharing --- .../recording/core/DebugSessionManager.kt | 22 ++++++++++--------- .../debug/recording/core/RecorderModule.kt | 5 +++-- .../debug/recording/ui/RecorderActivityVM.kt | 9 +++++--- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/eu/darken/capod/common/debug/recording/core/DebugSessionManager.kt b/app/src/main/java/eu/darken/capod/common/debug/recording/core/DebugSessionManager.kt index a2b15d44..7162c66b 100644 --- a/app/src/main/java/eu/darken/capod/common/debug/recording/core/DebugSessionManager.kt +++ b/app/src/main/java/eu/darken/capod/common/debug/recording/core/DebugSessionManager.kt @@ -15,6 +15,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.update import kotlinx.coroutines.CancellationException @@ -57,21 +59,20 @@ class DebugSessionManager @Inject constructor( activeDir = recorderState.currentLogDir, recordingStartedAt = recorderState.recordingStartedAt, ) - val overlaid = applyOverlays(raw, zipping, failedZips) + applyOverlays(raw, zipping, failedZips) + }.replayingShare(appScope) - val orphans = findOrphans(overlaid, zipping) - if (orphans.isNotEmpty()) { - orphans.forEach { (id, _) -> pendingAutoZips.add(id) } - appScope.launch { - orphans.forEach { (id, dir) -> + init { + sessions.onEach { allSessions -> + val orphans = findOrphans(allSessions, zippingIds.value) + orphans.forEach { (id, dir) -> + if (pendingAutoZips.add(id)) { log(TAG, INFO) { "Orphan session detected, auto-zipping: $id" } zipSessionAsync(id, dir) } } - } - - overlaid - }.replayingShare(appScope) + }.launchIn(appScope) + } private fun applyOverlays( sessions: List, @@ -226,6 +227,7 @@ class DebugSessionManager @Inject constructor( } } failedZipIds.update { emptySet() } + pendingAutoZips.clear() log(TAG) { "All stored logs deleted" } refresh() } diff --git a/app/src/main/java/eu/darken/capod/common/debug/recording/core/RecorderModule.kt b/app/src/main/java/eu/darken/capod/common/debug/recording/core/RecorderModule.kt index 72aa8f8e..5be8eb7e 100644 --- a/app/src/main/java/eu/darken/capod/common/debug/recording/core/RecorderModule.kt +++ b/app/src/main/java/eu/darken/capod/common/debug/recording/core/RecorderModule.kt @@ -76,7 +76,7 @@ class RecorderModule @Inject constructor( recordingStartedAt = System.currentTimeMillis(), ) } else if (!shouldRecord && isRecording) { - recorder!!.stop() + requireNotNull(recorder) { "Recorder is null despite isRecording" }.stop() if (triggerFile.exists() && !triggerFile.delete()) { log(TAG, ERROR) { "Failed to delete trigger file" } @@ -133,7 +133,8 @@ class RecorderModule @Inject constructor( internalState.updateBlocking { copy(shouldRecord = true) } - return internalState.flow.filter { it.isRecording }.first().currentLogDir!! + val state = internalState.flow.filter { it.isRecording }.first() + return requireNotNull(state.currentLogDir) { "Recording state has no logDir" } } suspend fun stopRecorder(): File? { diff --git a/app/src/main/java/eu/darken/capod/common/debug/recording/ui/RecorderActivityVM.kt b/app/src/main/java/eu/darken/capod/common/debug/recording/ui/RecorderActivityVM.kt index 4280151b..e46bae9c 100644 --- a/app/src/main/java/eu/darken/capod/common/debug/recording/ui/RecorderActivityVM.kt +++ b/app/src/main/java/eu/darken/capod/common/debug/recording/ui/RecorderActivityVM.kt @@ -9,6 +9,7 @@ import eu.darken.capod.R import eu.darken.capod.common.PrivacyPolicy import eu.darken.capod.common.WebpageTool import eu.darken.capod.common.coroutine.DispatcherProvider +import eu.darken.capod.common.debug.logging.Logging.Priority.WARN import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.common.debug.recording.core.DebugSession @@ -132,8 +133,6 @@ class RecorderActivityVM @Inject constructor( } fun share() = launch { - val currentState = stater.flow.first() - val dir = currentState.logDir ?: return@launch val sid = sessionId ?: return@launch stater.updateBlocking { copy(isWorking = true) } @@ -141,17 +140,21 @@ class RecorderActivityVM @Inject constructor( try { val uri = sessionManager.getZipUri(sid) + val displayName = stater.flow.first().logDir?.name ?: sid + val intent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) type = "application/zip" addCategory(Intent.CATEGORY_DEFAULT) - putExtra(Intent.EXTRA_SUBJECT, "CAPod DebugLog - ${dir.name}") + putExtra(Intent.EXTRA_SUBJECT, "CAPod DebugLog - $displayName") addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } val chooserIntent = Intent.createChooser(intent, context.getString(R.string.support_debuglog_label)) events.tryEmit(Event.ShareIntent(chooserIntent)) + } catch (e: Exception) { + log(TAG, WARN) { "Failed to share session $sid: ${e.message}" } } finally { stater.updateBlocking { copy(isWorking = false) } }