mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-21 08:19:33 +00:00
156 lines
4.1 KiB
Kotlin
156 lines
4.1 KiB
Kotlin
package com.geode.launcher.log
|
|
|
|
import androidx.compose.runtime.mutableStateListOf
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.coroutineScope
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.withContext
|
|
import kotlinx.coroutines.yield
|
|
import okio.Buffer
|
|
import okio.buffer
|
|
import okio.source
|
|
import java.io.EOFException
|
|
import java.io.IOException
|
|
|
|
/**
|
|
* ViewModel to manage log output from logcat.
|
|
* Currently, it loads the list of logs at init with no ability to read additional logs.
|
|
*/
|
|
class LogViewModel: ViewModel() {
|
|
// the original version of this code did support streaming btw
|
|
// the states were way too annoying to manage across multiple threads
|
|
// yay!
|
|
|
|
private val _lineState = mutableStateListOf<LogLine>()
|
|
val lineState = _lineState
|
|
|
|
private val _isLoading = MutableStateFlow(false)
|
|
val isLoading = _isLoading.asStateFlow()
|
|
|
|
private var failedToLoad = false
|
|
|
|
private var logJob: Job? = null
|
|
|
|
var filterCrashes = false
|
|
private set
|
|
|
|
// skip debug/verbose lines by default
|
|
var logLevel = LogPriority.INFO
|
|
set(value) {
|
|
field = value
|
|
|
|
_lineState.clear()
|
|
loadLogs()
|
|
}
|
|
|
|
fun toggleCrashBuffer() {
|
|
filterCrashes = !filterCrashes
|
|
|
|
_lineState.clear()
|
|
loadLogs()
|
|
}
|
|
|
|
private suspend fun logOutput(): List<LogLine> {
|
|
val logLines = ArrayList<LogLine>()
|
|
|
|
// -B = binary format, -d = dump logs
|
|
val logCommand = if (filterCrashes) "logcat -b crash -B -d"
|
|
else "logcat -B -d"
|
|
|
|
val logProcess = try {
|
|
withContext(Dispatchers.IO) {
|
|
Runtime.getRuntime().exec(logCommand)
|
|
}
|
|
} catch (ioe: IOException) {
|
|
ioe.printStackTrace()
|
|
logLines += LogLine.showException(ioe)
|
|
|
|
failedToLoad = true
|
|
|
|
return logLines
|
|
}
|
|
|
|
// read entire log into a buffer so no logs are added to the buffer during processing
|
|
val logBuffer = Buffer()
|
|
logProcess.inputStream.source().buffer().readAll(logBuffer)
|
|
|
|
try {
|
|
coroutineScope {
|
|
// this runs until the stream is exhausted
|
|
while (true) {
|
|
val line = LogLine.fromBufferedSource(logBuffer)
|
|
|
|
if (line.priority >= logLevel) {
|
|
logLines += line
|
|
}
|
|
|
|
yield()
|
|
}
|
|
}
|
|
} catch (_: EOFException) {
|
|
// ignore, end of file reached
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
logLines += LogLine.showException(e)
|
|
|
|
// technically it maybe didn't completely fail...
|
|
failedToLoad = true
|
|
}
|
|
|
|
return logLines
|
|
}
|
|
|
|
fun clearLogs() {
|
|
logJob?.cancel()
|
|
|
|
viewModelScope.launch(Dispatchers.IO) {
|
|
// -c = clear
|
|
Runtime.getRuntime().exec("logcat -c")
|
|
.waitFor()
|
|
}
|
|
|
|
_lineState.clear()
|
|
}
|
|
|
|
private fun dumpLogcatText(): String {
|
|
val logCommand = if (filterCrashes) "logcat -b crash -d"
|
|
else "logcat -d"
|
|
|
|
val logProcess = try {
|
|
Runtime.getRuntime().exec(logCommand)
|
|
} catch (ioe: IOException) {
|
|
ioe.printStackTrace()
|
|
return ioe.stackTraceToString()
|
|
}
|
|
|
|
return logProcess.inputStream.bufferedReader().readText()
|
|
}
|
|
|
|
fun getLogData(): String = if (failedToLoad) {
|
|
dumpLogcatText()
|
|
} else {
|
|
_lineState.joinToString("\n") { it.asSimpleString }
|
|
}
|
|
|
|
private fun loadLogs() {
|
|
_isLoading.value = true
|
|
|
|
logJob?.cancel()
|
|
logJob = viewModelScope.launch(Dispatchers.IO) {
|
|
val lines = logOutput()
|
|
_lineState.addAll(lines)
|
|
_isLoading.value = false
|
|
|
|
logJob = null
|
|
}
|
|
}
|
|
|
|
init {
|
|
loadLogs()
|
|
}
|
|
} |