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

com.jtransc.io.processutils.kt Maven / Gradle / Ivy

/*
 * Copyright 2016 Carlos Ballesteros Velasco
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jtransc.io

import com.jtransc.JTranscSystem
import com.jtransc.ds.toHashMap
import com.jtransc.env.OS
import com.jtransc.error.invalidOp
import com.jtransc.vfs.ExecOptions
import com.jtransc.vfs.ProcessResult
import com.jtransc.vfs.RootLocalVfs
import com.jtransc.vfs.SyncVfsFile
import java.io.File
import java.io.InputStream
import java.io.InputStreamReader
import java.nio.charset.Charset


data class ProcessResult2(val exitValue: Int, val out: String = "", val err: String = "", val outerr: String = out + err) {
	val success = exitValue == 0

	constructor(pr: ProcessResult) : this(pr.exitCode, pr.outputString, pr.errorString, pr.outputString + pr.errorString)
}

object ProcessUtils : ProcessUtilsBase(RootLocalVfs())

open class ProcessHandler(val parent: ProcessHandler? = null) {
	open fun onStarted(): Unit = parent?.onStarted() ?: Unit
	open fun onOutputData(data: String): Unit = parent?.onOutputData(data) ?: Unit
	open fun onErrorData(data: String): Unit = parent?.onErrorData(data) ?: Unit
	open fun onCompleted(exitValue: Int): Unit = parent?.onCompleted(exitValue) ?: Unit
}

object RedirectOutputHandler : ProcessHandler() {
	override fun onOutputData(data: String) = System.out.print(data)
	override fun onErrorData(data: String) = System.err.print(data)
	override fun onCompleted(exitValue: Int) = Unit
}

open class ProcessUtilsBase(val rootVfs: SyncVfsFile) {
	//val defaultCharset = Charset.forName("UTF-8")
	val defaultCharset = if (OS.isWindows) Charset.forName("UTF-8") else Charset.defaultCharset()
	//val defaultCharset = Charset.forName("ISO-8859-1")
	//val defaultCharset = Charset.forName("ASCII")
	//val defaultCharset = Charset.forName("Cp850")
	//val defaultCharset = Charset.forName("Cp1252")

	//init {
	//	println(getConsoleCharset())
	//}
	//
	//private fun getConsoleCharset(): String {
	//	val osw = OutputStreamWriter(System.out)
	//	println(osw.encoding)
	//	//val stdOutClass = System.out.javaClass
	//	////val charOutField = stdOutClass.getDeclaredField("charOut")
	//	//val charOutField = stdOutClass.getField("charOut")
	//	//charOutField.isAccessible = true
	//	//val o = charOutField.get(System.out) as OutputStreamWriter
	//	//System.out.println(o.getEncoding())
	//	return osw.encoding
	//}

	fun run(currentDir: File, command: String, args: List, options: ExecOptions): ProcessResult2 {
		//val pb = ProcessBuilder("cmd.exe", "/c", "chcp", "65001").inheritIO()
		//val p = pb.start()
		//p.waitFor()


		var out = ""
		var err = ""
		var outerr = ""
		//redirect: Boolean, env: Map = mapOf()

		fun String.fix(): String {
			return if (options.fixLineEndings) {
				//this.replace("\r\n", "\n").replace('\r', '\n')
				this.replace("\r", "")
			} else {
				this
			}
		}

		val exitValue = run2(currentDir, command, args, object : ProcessHandler() {
			override fun onStarted() {
			}

			override fun onOutputData(data: String) {
				if (options.passthru) System.out.print(data.fix())
				out += data
				outerr += data
			}

			override fun onErrorData(data: String) {
				if (options.passthru) System.err.print(data.fix())
				err += data
				outerr += data
			}

			override fun onCompleted(exitValue: Int) {
			}
		}, options = options)

		return ProcessResult2(exitValue, out, err, outerr)
	}

	fun runAndReadStderr(currentDir: File, command: String, args: List, env: Map = mapOf()): ProcessResult2 {
		return run(currentDir, command, args, options = ExecOptions(sysexec = true).copy(passthru = false, env = env))
	}

	fun runAndRedirect(currentDir: File, command: String, args: List, env: Map = mapOf()): ProcessResult2 {
		return run(currentDir, command, args, options = ExecOptions(sysexec = true).copy(passthru = true, env = env))
	}

	fun runAndRedirect(currentDir: File, commandAndArgs: List, env: Map = mapOf()): ProcessResult2 {
		return run(currentDir, commandAndArgs.first(), commandAndArgs.drop(1), options = ExecOptions(sysexec = true).copy(passthru = true, env = env))
	}

	val pathSeparator by lazy { System.getProperty("path.separator") ?: ":" }
	val fileSeparator by lazy { System.getProperty("file.separator") ?: "/" }

	fun getPaths(): List {
		return rootVfs.getPaths()
	}

	fun locateCommandSure(name: String): String = locateCommand(name) ?: invalidOp("Can't find command $name in path")

	fun which(name: String): String? = locateCommand(name)

	fun locateCommand(name: String): String? {
		for (ext in if (JTranscSystem.isWindows()) listOf(".exe", ".cmd", ".bat", "") else listOf("")) {
			for (path in getPaths()) {
				val fullPath = "$path$fileSeparator$name$ext"
				if (rootVfs[fullPath].exists) return fullPath
			}
		}
		return null
	}

	fun run2(currentDir: File, command: String, args: List, handler: ProcessHandler = RedirectOutputHandler, charset: Charset = defaultCharset, options: ExecOptions = ExecOptions()): Int {
		val fullCommand = if (File(command).isAbsolute) command else locateCommand(command) ?: command
		var printCmd = fullCommand
		for (arg in args) {
			printCmd += " " + arg
		}
		println("Execute command " + printCmd)

		val absoluteCurrentDir = currentDir.absoluteFile
		val env = options.env

		val penv = System.getenv().toHashMap()
		for ((key, value) in env.entries) {
			if (key.startsWith("*")) {
				val akey = key.substring(1)
				penv[akey] = value + penv[akey]
			} else if (key.endsWith("*")) {
				val akey = key.substring(0, key.length - 1)
				penv[akey] = penv[akey] + value
			} else {
				penv[key] = value
			}
		}

		val p = if (options.sysexec) {
			val envList = penv.map { it.key + "=" + it.value }
			//val envList = listOf()

			val prefixCmds = if (OS.isWindows) {
				if (options.fixencoding) listOf("cmd", "/c", "chcp", "65001", ">", "NUL", "&") else listOf("cmd", "/c")
			} else {
				listOf()
			}

			Runtime.getRuntime().exec((prefixCmds + listOf(fullCommand) + args).toTypedArray(), envList.toTypedArray(), absoluteCurrentDir)
		} else {
			val pb = ProcessBuilder(fullCommand, *args.toTypedArray())
			pb.directory(absoluteCurrentDir)
			val penv2 = pb.environment()
			for ((key, value) in penv.entries) penv2[key] = value
			pb.start()
		}
		val input = InputStreamReader(p.inputStream, charset)
		val error = InputStreamReader(p.errorStream, charset)
		var closing = false
		while (true) {
			val i = input.readAvailableChunk(p.inputStream, readRest = closing)
			val e = error.readAvailableChunk(p.errorStream, readRest = closing)
			if (i.isNotEmpty()) handler.onOutputData(i)
			if (e.isNotEmpty()) handler.onErrorData(e)
			if (closing) break
			if (i.isEmpty() && e.isEmpty() && !p.isAliveJre7) {
				closing = true
				continue
			}
			Thread.sleep(1L)
		}
		p.waitFor()
		handler.onCompleted(p.exitValue())
		return p.exitValue()
	}

	//fun runAsync(currentDir: File, command: String, args: List, handler: ProcessHandler = RedirectOutputHandler, charset: Charset = Charsets.UTF_8) {
	fun runAsync(currentDir: File, command: String, args: List, handler: ProcessHandler = RedirectOutputHandler, charset: Charset = defaultCharset) {
		Thread {
			run2(currentDir, command, args, handler, charset)
		}.start()
	}

	fun findCommandInPathsOrNull(folders: List, cmd: String): String? {
		for (folder in folders) {
			val files = listOf(rootVfs[folder]["$cmd.exe"], rootVfs[folder]["$cmd.cmd"], rootVfs[folder]["$cmd.bat"])
			for (file in files) {
				if (file.exists) return folder
			}
		}
		return null
	}
}

private fun InputStreamReader.readAvailableChunk(i: InputStream, readRest: Boolean): String {
	val out = StringBuilder()
	while (if (readRest) true else i.available() > 0) {
		val c = this.read()
		if (c < 0) break
		out.append(c.toChar())
	}
	return out.toString()
}

val Process.isAliveJre7: Boolean get() = try {
	exitValue()
	false
} catch (e: IllegalThreadStateException) {
	true
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy