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

net.dankito.utils.process.CommandExecutor.kt Maven / Gradle / Ivy

There is a newer version: 1.0.20
Show newest version
package net.dankito.utils.process

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import org.slf4j.LoggerFactory
import java.io.InputStream


open class CommandExecutor : ICommandExecutor {

    companion object {
        private val log = LoggerFactory.getLogger(CommandExecutor::class.java)
    }


    /**
     * Do not call this for commands that have a large standard or error output!
     *
     * This method does not read process' standard and error output on extra threads like [executeCommand] does.
     *
     * Standard and error output are being read on same thread after process ended. Therefore if standard and error
     * InputStream reader's buffer is not large enough, the call to Process.waitFor() will hang forever.
     *
     * So it has slightly better performance than [executeCommand] has as it doesn't create two new threads but at the
     * cost that app may hangs forever.
     */
    override fun executeCommandWithLittleOutput(vararg arguments: String): ExecuteCommandResult {
        return executeCommandWithLittleOutput(CommandConfig(arguments.asList()))
    }

    /**
     * Do not call this for commands that have a large standard or error output!
     *
     * This method does not read process' standard and error output on extra threads like [executeCommand] does.
     *
     * Standard and error output are being read on same thread after process ended. Therefore if standard and error
     * InputStream reader's buffer is not large enough, the call to Process.waitFor() will hang forever.
     *
     * So it has slightly better performance than [executeCommand] has as it doesn't create two new threads but at the
     * cost that app may hangs forever.
     */
    override fun executeCommandWithLittleOutput(config: CommandConfig): ExecuteCommandResult {
        try {
            val process = createProcessFromCommandConfig(config)

            val exitCode = process.waitFor()
            val outputLines = process.inputStream.bufferedReader().lineSequence().toList()
            val errorLines = process.errorStream.bufferedReader().lineSequence().toList()

            return ExecuteCommandResult(exitCode, outputLines, errorLines)
        } catch (e: Exception) {
            return parseExceptionToExecuteCommandResult(config, e)
        }
    }


    override fun executeCommand(config: CommandConfig): ExecuteCommandResult {
        try {
            val process = createProcessFromCommandConfig(config)

            val outputStreamReader = AsyncStreamReader(process.inputStream)
            val errorStreamReader = AsyncStreamReader(process.errorStream)

            outputStreamReader.start()
            errorStreamReader.start()

            val exitCode = process.waitFor()
            val outputLines = outputStreamReader.lines
            val errorsLines = errorStreamReader.lines

            return ExecuteCommandResult(exitCode, outputLines, errorsLines)
        } catch (e: Exception) {
            return parseExceptionToExecuteCommandResult(config, e)
        }
    }


    override suspend fun executeCommandSuspendable(config: CommandConfig, scope: CoroutineScope): ExecuteCommandResult {
        try {
            val process = createProcessFromCommandConfig(config)

            val outputStream = scope.async(Dispatchers.IO) { readStream(process.inputStream) }
            val errorStream = scope.async(Dispatchers.IO) { readStream(process.errorStream) }

            while (process.isAlive) {
                delay(25)
            }
            val exitCode = process.waitFor()

            return ExecuteCommandResult(exitCode, outputStream.await(), errorStream.await())
        } catch (e: Exception) {
            return parseExceptionToExecuteCommandResult(config, e)
        }
    }

    protected open fun readStream(inputStream: InputStream): List {
        val lines = mutableListOf()

        try {
            inputStream.bufferedReader().use { reader ->
                reader.forEachLine {
                    lines.add(it)
                }
            }
        } catch (e: Exception) {
            log.error("Error occurred while reading stream", e)
        }

        return lines
    }


    protected open fun createProcessFromCommandConfig(config: CommandConfig): Process {
        log.debug("Executing command '$config' ...")

        val processBuilder = ProcessBuilder(config.commandArgs)

        config.workingDir?.let { workingDir ->
            processBuilder.directory(workingDir)
        }

        config.environmentVariables.forEach { environmentVariable ->
            processBuilder.environment().put(environmentVariable.key, environmentVariable.value)
        }

        return processBuilder.start()
    }

    protected open fun parseExceptionToExecuteCommandResult(config: CommandConfig, e: Exception): ExecuteCommandResult {
        if (config.logErrors) {
            log.error("Could not execute command '$config'", e)
        }

        var exitCode = -1

        e.message?.let { exceptionMessage ->
            if (exceptionMessage.contains("error=")) {
                val startIndex = exceptionMessage.indexOf("error=") + "error=".length
                var endIndex = startIndex + 1

                for (i in startIndex + 1..exceptionMessage.length - 1) {
                    if (exceptionMessage[i].isDigit() == false) {
                        endIndex = i
                        break
                    }
                }

                exitCode = exceptionMessage.substring(startIndex, endIndex).toInt()
            }
        }

        return ExecuteCommandResult(exitCode, listOf(), listOf(e.localizedMessage))
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy