Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cc.unitmesh.agent.runconfig

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit

actual suspend fun readProcessOutputPlatform(
processHandle: Any?,
onOutput: (String) -> Unit,
timeoutMs: Long,
onProcessStarted: (processId: Int?) -> Unit
): RunConfigResult = withContext(Dispatchers.IO) {
val process = processHandle as? Process
?: return@withContext RunConfigResult(
success = false,
error = "Invalid process handle"
)

val startTime = System.currentTimeMillis()
val outputBuilder = StringBuilder()
var cancelled = false

// Read stdout in a separate thread
val stdoutReader = Thread {
try {
process.inputStream.bufferedReader().use { reader ->
val buffer = CharArray(1024)
while (!cancelled) {
val charsRead = reader.read(buffer)
if (charsRead == -1) break
val chunk = buffer.concatToString(0, charsRead)
outputBuilder.append(chunk)
onOutput(chunk)
}
}
} catch (_: Exception) {
// Stream closed, ignore
}
}.apply { start() }

// Read stderr in a separate thread
val stderrReader = Thread {
try {
process.errorStream.bufferedReader().use { reader ->
val buffer = CharArray(1024)
while (!cancelled) {
val charsRead = reader.read(buffer)
if (charsRead == -1) break
val chunk = buffer.concatToString(0, charsRead)
outputBuilder.append(chunk)
onOutput(chunk)
}
}
} catch (_: Exception) {
// Stream closed, ignore
}
}.apply { start() }

try {
// Wait for process with timeout
val completed = process.waitFor(timeoutMs, TimeUnit.MILLISECONDS)

// Wait for readers to finish
stdoutReader.join(2000)
stderrReader.join(2000)

if (!completed) {
onOutput("\n[INFO] Process still running after ${timeoutMs / 1000}s timeout.\n")
// Android API level check for pid() - use reflection or fallback
val pid = try {
process.javaClass.getMethod("pid").invoke(process) as? Long
} catch (_: Exception) {
null
}
onProcessStarted(pid?.toInt())
return@withContext RunConfigResult(
success = true,
message = "Process started (may still be running)",
pid = pid?.toInt()
)
}

val exitCode = process.exitValue()
val executionTime = System.currentTimeMillis() - startTime

RunConfigResult(
success = exitCode == 0,
exitCode = exitCode,
message = if (exitCode == 0) {
"Command completed successfully (${executionTime}ms)"
} else {
"Command exited with code $exitCode"
}
)
} catch (e: kotlinx.coroutines.CancellationException) {
cancelled = true
stdoutReader.interrupt()
stderrReader.interrupt()
throw e
}
}

actual fun killProcessPlatform(processHandle: Any?) {
val process = processHandle as? Process ?: return
try {
// Use exitValue to check if alive (throws if still running)
try {
process.exitValue()
} catch (_: IllegalThreadStateException) {
// Process is still running, destroy it
process.destroyForcibly()
}
} catch (_: Exception) {
// Ignore
}
}

actual fun isNativeProcess(handle: Any?): Boolean = handle is Process

actual fun currentTimeMillis(): Long = System.currentTimeMillis()
Loading
Loading