All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.imkarl.core.common.command.CommandBuilder.kt Maven / Gradle / Ivy

package cn.imkarl.core.common.command

import cn.imkarl.core.common.log.LogUtils
import cn.imkarl.core.common.platform.Platform
import kotlinx.coroutines.*
import java.io.BufferedWriter
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
 * 命令构建器
 * @author imkarl
 */
class CommandBuilder(
    val commands: List,
    val showLog: Boolean = false,
    var directory: File? = null,
    val environment: MutableMap = mutableMapOf(),
    var writer: (suspend (BufferedWriter) -> Unit)? = null
) {

    private val defaultCommands: List by lazy {
        if (Platform.osType == Platform.OSType.WINDOWS) {
            mutableListOf("CMD", "/C")
        } else {
            mutableListOf("/bin/sh", "-c")
        }
    }

    private val defaultEnvironments: MutableMap by lazy {
        mutableMapOf(
            "PATH" to "${System.getProperty("java.home").removeSuffix("/")}/bin"
        )
    }


    private fun ProcessBuilder.putEnvironments(environments: Map): ProcessBuilder {
        if (environments.isNotEmpty()) {
            val oldEnvironment = this.environment()
            environments.forEach { (key, value) ->
                oldEnvironment[key] = if (key == "PATH") {
                    oldEnvironment["PATH"] + ":" + value.removePrefix(":").removePrefix(".:")
                } else {
                    value
                }
            }
        }
        return this
    }

    /**
     * 执行命令
     */
    suspend fun start(
        timeout: Long = -1,
        output: (suspend (line: String, isError: Boolean) -> Unit)? = null
    ): ExecResult {
        return if (timeout <= 0) {
            start(output)
        } else {
            withTimeout(timeout) {
                start(output)
            }
        }
    }
    /**
     * 执行命令
     */
    suspend fun start(
        output: (suspend (line: String, isError: Boolean) -> Unit)? = null
    ): ExecResult {
        if (commands.isEmpty()) {
            return ExecResult(exitCode = 126, output = emptyList(), error = emptyList())
        }

        return supervisorScope {
            suspendCancellableCoroutine { continuation ->
                val outputCache = mutableListOf()
                val errorCache = mutableListOf()

                val startTime = System.currentTimeMillis()
                val process = ProcessBuilder(defaultCommands + commands)
                    .directory(directory)
                    .putEnvironments(defaultEnvironments)
                    .putEnvironments(environment)
                    .start()
                if (showLog) {
                    LogUtils.e("start success (${System.currentTimeMillis() - startTime}ms)")
                }

                // 打印日志
                if (showLog) {
                    LogUtils.i("exec commands: ${commands.joinToString("\n")}")
                }

                // 取消协程时,释放资源
                continuation.invokeOnCancellation {
                    if (showLog) {
                        LogUtils.i("invokeOnCancellation (${System.currentTimeMillis() - startTime}ms)")
                    }
                    process.destroy()
                }

                try {
                    // 写入流
                    if (writer != null) {
                        launch(Dispatchers.IO) {
                            process.outputStream.bufferedWriter().use {
                                writer?.invoke(it)
                            }
                        }
                    }
                    // 输出流
                    launch(Dispatchers.IO) {
                        process.inputStream.bufferedReader().use {
                            while (continuation.context.isActive) {
                                try {
                                    val line = it.readLine() ?: break
                                    outputCache.add(line)
                                    output?.invoke(line, false)
                                } catch (throwable: Throwable) {
                                    continuation.cancel()
                                }
                            }
                        }
                    }
                    // 异常流
                    launch(Dispatchers.IO) {
                        process.errorStream.bufferedReader().use {
                            while (continuation.context.isActive) {
                                try {
                                    val line = it.readLine() ?: break
                                    errorCache.add(line)
                                    output?.invoke(line, true)
                                } catch (throwable: Throwable) {
                                    continuation.cancel()
                                }
                            }
                        }
                    }
                } catch (throwable: Throwable) {
                    if (showLog) {
                        LogUtils.e(throwable)
                    }
                    continuation.resumeWithException(throwable)
                }

                if (showLog) {
                    LogUtils.d("start wait (${System.currentTimeMillis() - startTime}ms)")
                }
                // 等待执行结果
                val exitCode = process.waitFor()
                if (showLog) {
                    LogUtils.d("end (${System.currentTimeMillis() - startTime}ms) isActive:${isActive}")
                }
                if (isActive) {
                    continuation.resume(ExecResult(exitCode = exitCode, output = outputCache, error = errorCache))
                } else {
                    continuation.cancel()
                }
            }
        }
    }

}

data class ExecResult(
    val exitCode: Int,
    val output: List,
    val error: List
)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy