Skip to content

Commit

Permalink
Throw and handle exception from sample 'appIdFor'
Browse files Browse the repository at this point in the history
  • Loading branch information
NorseDreki committed Jan 30, 2024
1 parent bc27b56 commit 619e4c9
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 108 deletions.
3 changes: 3 additions & 0 deletions src/commonMain/kotlin/dogcat/DogcatException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package dogcat

class DogcatException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
3 changes: 2 additions & 1 deletion src/commonMain/kotlin/dogcat/Shell.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface Shell {

fun lines(minLogLevel: String, userId: String) : Flow<String>

suspend fun userIdFor(packageName: String): String
suspend fun appIdFor(packageName: String): String

suspend fun currentEmulatorName(): String?

Expand All @@ -20,3 +20,4 @@ interface Shell {

suspend fun isShellAvailable(): Boolean
}

88 changes: 59 additions & 29 deletions src/nativeMain/kotlin/AdbShell.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import com.kgit2.kommand.exception.KommandException
import com.kgit2.kommand.process.Child
import com.kgit2.kommand.process.Command
import com.kgit2.kommand.process.Stdio
import dogcat.DogcatConfig
import com.kgit2.kommand.process.Stdio.Pipe
import dogcat.DogcatException
import dogcat.Shell
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.ClosedReceiveChannelException
Expand Down Expand Up @@ -41,7 +42,7 @@ class AdbShell(
.args(
listOf("logcat", "-v", "brief", userId, minLogLevel)
)
.stdout(Stdio.Pipe)
.stdout(Pipe)
.spawn()

} catch (e: KommandException) {
Expand Down Expand Up @@ -126,36 +127,52 @@ class AdbShell(
}
}

override suspend fun userIdFor(packageName: String) = withContext(dispatcherIo) {
val UID_CONTEXT = """Packages:\R\s+Package\s+\[$packageName]\s+\(.*\):\R\s+(?:appId|userId)=(\d*)""".toRegex()

val output = withTimeout(COMMAND_TIMEOUT_MILLIS) {
Command("adb")
.args(
listOf("shell", "dumpsys", "package")
)
.arg(packageName)
.stdout(Stdio.Pipe)
.output()
}
override suspend fun appIdFor(packageName: String): String {
val appIdContext =
"""Packages:\R\s+Package\s+\[$packageName]\s+\(.*\):\R\s+(?:appId|userId)=(\d*)""".toRegex()

val appId = try {
withContext(dispatcherIo) {
val commandOutput = withTimeout(COMMAND_TIMEOUT_MILLIS) {
// Looks like despite its claims, Kommand still doesn't support timeouts
Command("adb")
.args(
listOf("shell", "dumpsys", "package")
)
.arg(packageName)
.stdout(Pipe)
.output()
}

val userId = output.stdout?.let {
val match = UID_CONTEXT.find(it)
match?.let {
val (userId) = it.destructured
userId
val appId = commandOutput.stdout?.let {
val match = appIdContext.find(it)

match?.let {
val (id) = it.destructured
id
}
}

appId
}
} catch (e: KommandException) {
throw DogcatException("Couldn't launch ADB command", e)

} catch (e: TimeoutCancellationException) {
throw DogcatException("Running ADB timed out", e)
}

userId ?: throw RuntimeException("UserId not found!")
return appId
?: throw DogcatException("App ID is not found for the package '$packageName'. Package is not installed on device.")
}

override suspend fun currentEmulatorName() = withContext(Dispatchers.IO) {
val name = Command("adb")
.args(
listOf("emu", "avd", "name")
)
.stdout(Stdio.Pipe)
.stdout(Pipe)
.output()
.stdout
?.lines()
Expand All @@ -172,7 +189,7 @@ class AdbShell(
.args(
listOf("shell", "dumpsys", "activity", "activities")
)
.stdout(Stdio.Pipe)
.stdout(Pipe)
.spawn()

val stdoutReader = out.bufferedStdout()!!// getChildStdout()!!
Expand All @@ -197,14 +214,27 @@ class AdbShell(
}

override suspend fun clearSource(): Boolean {
val childStatus = withContext(dispatcherIo) {
Command("adb")
.args(
listOf("logcat", "-c")
)
.status()
val childStatus = try {
withContext(dispatcherIo) {
withTimeout(3000) {
Command("adb")
.args(
listOf("logcat", "-c")
)
.status()
}
}
} catch (e: KommandException) {
//log

return false
} catch (e: TimeoutCancellationException) {

//or throw?
return false
}


Logger.d("${context()} Exit code for 'adb logcat -c': ${childStatus}")

return childStatus == 0
Expand Down Expand Up @@ -240,7 +270,7 @@ class AdbShell(
.args(
listOf("emu", "avd", "status")
)
.stdout(Stdio.Pipe)
.stdout(Pipe)
.output()
.stdout
?.lines()
Expand All @@ -264,7 +294,7 @@ class AdbShell(
.args(
listOf("version")
)
.stdout(Stdio.Pipe)
.stdout(Pipe)
.status()
} catch (e: KommandException) {
//whoa exception and not code if command not found
Expand Down
54 changes: 29 additions & 25 deletions src/nativeMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import di.AppModule
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import logger.Logger
import logger.context
import platform.posix.signal
import userInput.Arguments
import userInput.Keymap
import userInput.Keymap.Actions
Expand All @@ -16,34 +16,38 @@ fun main(args: Array<String>) {

val ui = newSingleThreadContext("UI")

val handler = CoroutineExceptionHandler { _, t ->
Logger.d("!!!!!!11111111 CATCH! ${t.message}")
//do we need to just terminate the app and write message to stdout on exit rather than logging?
val handler = CoroutineExceptionHandler { c, t ->
Logger.d("CATCH! ${t.message}")
println("${t.message}")
}

runBlocking(ui) {
val appModule = AppModule(ui)

with(appModule) {
Logger.set(fileLogger)

input.start()
appPresenter.start()

input
.keypresses
.filter {
Keymap.bindings[it] == Actions.Quit
}
.onEach {
Logger.d("${context()} Cancel scope")

//make sure to cancel last leaking ADB
appPresenter.stop()
coroutineContext.cancelChildren()
}
.launchIn(this@runBlocking)
//he key takeaway is that if you call launch on a custom CoroutineScope, any CoroutineExceptionHandler provided
// directly to the CoroutineScope constructor or to launch will be executed when an exception is thrown within the launched coroutine.
val appJob = CoroutineScope(ui).launch(handler) {
val appModule = AppModule(ui)

with(appModule) {
Logger.set(fileLogger)

input.start()
appPresenter.start()

input
.keypresses
.filter {
Keymap.bindings[it] == Actions.Quit
}
.onEach {
Logger.d("${context()} Cancel scope")

appPresenter.stop()
coroutineContext.cancelChildren()
}
.launchIn(this@launch)
}
}
appJob.join()
}
ui.close()

Expand Down
55 changes: 21 additions & 34 deletions src/nativeMain/kotlin/ui/AppPresenter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,68 +9,68 @@ import dogcat.state.PublicState
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import logger.Logger
import logger.context
import ui.logLines.LogLinesPresenter
import ui.status.StatusPresenter
import userInput.Arguments
import userInput.HasHifecycle
import userInput.Input
import userInput.Keymap
import userInput.Keymap.Actions.*
import kotlin.coroutines.coroutineContext

@OptIn(ExperimentalForeignApi::class)
class AppPresenter(
private val dogcat: Dogcat,
private val appStateFlow: AppStateFlow,
private val input: Input,
private val logLinesPresenter: LogLinesPresenter,
private val statusPresenter: StatusPresenter,
) : HasHifecycle {
) : HasLifecycle {
private val view = AppView()

@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun start() {
view.start()
when {
Arguments.packageName != null -> dogcat(Start.PickAppPackage(Arguments.packageName!!))
Arguments.current == true -> dogcat(Start.PickForegroundApp)
else -> dogcat(Start.PickAllApps)
}

val s = CoroutineScope(coroutineContext)
view.start()

s.launch {
val scope = CoroutineScope(coroutineContext)
scope.launch {
collectDogcatEvents()
}
s.launch {
scope.launch {
collectKeypresses()
}

when {
Arguments.packageName != null -> dogcat(Start.PickApp(Arguments.packageName!!))
Arguments.current == true -> dogcat(Start.PickForegroundApp)
else -> dogcat(Start.All)
}

logLinesPresenter.start()
//statusPresenter.start()
statusPresenter.start()
}

override suspend fun stop() {
dogcat(Stop)

logLinesPresenter.stop()
statusPresenter.stop()

view.stop()
}

@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun collectDogcatEvents() {
dogcat
.state
.filterIsInstance<PublicState.Stopped>()
.filterIsInstance<PublicState.Terminated>()
.collect {
println(
"Either ADB is not found in your PATH or it's found but no emulator is running ")

"Either ADB is not found in your PATH or it's found but no emulator is running "
)
}
}

Expand All @@ -82,19 +82,6 @@ class AppPresenter(
Autoscroll -> {
appStateFlow.autoscroll(!appStateFlow.state.value.autoscroll)
}
Quit -> { // catch control-c
//dogcat(Command.Stop)
//coroutineContext.cancelChildren()
//currentCoroutineContext().cancelChildren()
//scope.coroutineContext.cancelChildren() -- only this works
Logger.d("{${context()} ++++++ cancelled ${coroutineContext}'s job")
//endwin()

//pad.terminate()
//pad2.terminate()
//resetty()
//exit(0)
}

ClearLogs -> {
dogcat(ClearLogSource)
Expand All @@ -109,7 +96,7 @@ class AppPresenter(
dogcat(ResetFilter(ByPackage::class))
} else {
Logger.d("${context()} !SelectAppByPackage")
dogcat(Start.PickApp(f.first!!.packageName))
dogcat(Start.PickAppPackage(f.first!!.packageName))
appStateFlow.filterByPackage(f.first, true)
}
}
Expand Down
Loading

0 comments on commit 619e4c9

Please sign in to comment.