// TLCockpit // Copyright 2017-2018 Norbert Preining // Licensed according to GPLv3+ // // Front end for tlmgr package TeXLive import java.io._ import TeXLive.OsTools._ import com.typesafe.scalalogging.LazyLogging import scala.collection.mutable.ArrayBuffer import scala.concurrent.SyncVar import scala.sys.process._ class TlmgrProcess(updout: String => Unit, upderr: String => Unit) extends LazyLogging { val inputString = new SyncVar[String] // used for the tlmgr process input // val outputString = new SyncVar[String] // used for the tlmgr process output // val errorBuffer: StringBuffer = new StringBuffer() // buffer used for both tmgr process error console AND logging val tlroot: String = "kpsewhich -var-value SELFAUTOPARENT".!!.trim logger.debug("tlroot ==" + tlroot + "==") // set in only one place, in the main thread var process: Process = _ def send_command(input: String): Unit = { logger.debug(s"send_command: ${input}") try { assert(!inputString.isSet) inputString.put(input) } catch { case exc: Throwable => logger.warn("Main thread: " + (if (exc.isInstanceOf[NoSuchElementException]) "Timeout" else "Exception: " + exc)) } } def isAlive(): Boolean = { if (process != null) process.isAlive() else // return true on not-started process true } def start_process(): Boolean = { logger.debug("tlmgr process: entering starting process") // process creation if (process == null) { logger.debug("tlmgr process: start_process: creating procIO") val procIO = new ProcessIO(inputFn, outputFn(_, updout), outputFn(_, upderr)) val startCmd = if (isWindows) { logger.debug("detected Windows, using tlmgr.bat") Seq("tlmgr.bat", "-v", "--machine-readable", "shell") } else if (isCygwin) { logger.debug("detected Cygwin, using bash -l -c tlmgr") Seq("bash", "-l", "-c", "tlmgr -v --machine-readable shell") } else { logger.debug("detected Unixish, using tlmgr") Seq("tlmgr", "-v", "--machine-readable", "shell") } val processBuilder: ProcessBuilder = startCmd logger.debug("tlmgr process: start_process: running new tlmgr process") process = processBuilder.run(procIO) } logger.debug("tlmgr process: start_process: checking for being alive") process.isAlive() } def cleanup(): Unit = { if (process != null) { logger.debug("tlmgr process: cleanup - sending quit") send_command("quit") logger.debug("tlmgr process: cleanup - sleeping 2s") Thread.sleep(1000) logger.debug("tlmgr process: cleanup - destroying process") process.destroy() } } /* The standard input passing function */ private[this] def inputFn(stdin: OutputStream): Unit = { val writer = new BufferedWriter(new OutputStreamWriter(stdin)) try { var input = "" while (true) { input = inputString.take() if (input == "quit") { stdin.close() return } else { writer.write(input + "\n") logger.trace("writing " + input + " to process") writer.flush() } } stdin.close() } catch { case exc: Throwable => logger.warn("Exception in inputFn thread: " + exc + "\n") stdin.close() } } private[this] def outputFn(outStr: InputStream, updfun: String => Unit): Unit = { val reader = new BufferedReader(new InputStreamReader(outStr)) try { var line: String = "" while (true) { line = reader.readLine logger.trace("DDD did read " + line + " from process") try { updfun(line) } catch { case exc: Throwable => logger.debug("Update output line function failed, continuing anyway. Exception: " + exc) } } outStr.close() } catch { case exc: Throwable => logger.warn("Exception in outputFn thread: " + exc + "\n") outStr.close() } } }