summaryrefslogtreecommitdiff
path: root/Master
diff options
context:
space:
mode:
authorNorbert Preining <preining@logic.at>2017-09-26 02:11:36 +0000
committerNorbert Preining <preining@logic.at>2017-09-26 02:11:36 +0000
commit779ef74ce4a9899908fab4c48e66b5279dfc47b2 (patch)
treed487dbd53d0398bc0cf20c61fe64f66c3120cbb1 /Master
parent67d524900a5b7016dba0098cc49dbe5ceb6ee840 (diff)
update tlcockpit
git-svn-id: svn://tug.org/texlive/trunk@45401 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master')
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/README.md34
-rw-r--r--Master/texmf-dist/scripts/tlcockpit/tlcockpit.jarbin11620851 -> 11623474 bytes
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/build.sbt6
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala482
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsInfo.scala8
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala10
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala152
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/tlcockpit/ApplicationMain.scala536
8 files changed, 678 insertions, 550 deletions
diff --git a/Master/texmf-dist/doc/support/tlcockpit/README.md b/Master/texmf-dist/doc/support/tlcockpit/README.md
index bd806ba995e..7d6d09b3e4c 100644
--- a/Master/texmf-dist/doc/support/tlcockpit/README.md
+++ b/Master/texmf-dist/doc/support/tlcockpit/README.md
@@ -1,35 +1,41 @@
TLCockpit - GUI frontend to tlmgr
=================================
-TLCockpit aims at being a GUI for [tlmgr](https://www.tug.org/texlive/tlmgr.html), the [TeX Live](https://www.tug.org/texlive/) Manager, with a modern look and feel.
+TLCockpit aims at being a GUI for [tlmgr](https://www.tug.org/texlive/tlmgr.html),
+the [TeX Live](https://www.tug.org/texlive/) Manager, with a modern look and feel.
-TLCockpit is included in TeX Live, but you need the latest version of tlmgr, see the secion on <em>Test versions</em> in the above document.
Supported functionality
-----------------------
-By now only loading of remote repository, listing installed, updated,
-or all packages. Clicking on a package in the list opens a details
-window.
+While functionality is by now restricted to listing of packages, getting information
+on packages, and updating/installing/removing packages, further features will be added
+over time.
-Furthermore, an input field allows entering arbitrary commands of the
+For advanced users there is an input field allowing to enter arbitrary commands of the
*tlmgr shell*, see [tlmgr manual](https://www.tug.org/texlive/doc/tlmgr.html#shell) for details.
Installing/Running
------------------
-If you don't want to build your own deployment jar (which I don't recommend),
-download [tlcockpit.jar](jar/tlcockpit.jar) and run it with
+Since TLCockpit is included in TeX Live, it will be probably already installed
+on your system. In case it isn't, a simple call to `tlmgr install tlcockpit`
+should suffice.
-```
-java -jar tlcockpit.jar
-```
+We are providing new jar files on a daily bases in the github page, but update
+the copy in TeX Live only at certain intervals. If you want to try out the latest
+and greatest, get [tlcockpit.jar](jar/tlcockpit.jar) and put it into
+`TEXLIVEROOT/temxf-dist/scripts/tlcockpit` replacing the current `tlcockpit.jar`.
-The `tlmgr` program has to be in the PATH, and be the most current version.
+After this you can run it by simply typing `tlcockpit`, or via a direct `java`
+call as in `java -jar PATH/TO/tlcockpit.jar`.
Requirements
------------
-A recent Java installation that included JavaFX is required. If you see
+You need at least `tlmgr` from after 2017-09-01, in TeX Live language at least
+version 45190 or `texlive.infra`.
+
+A recent Java installation that includes JavaFX is required. If you see
errors like
```
Error: A JNI error has occurred, please check your installation and try again
@@ -59,6 +65,8 @@ writing Scala and my style is horrible ;-)
Screenshots
-----------
+(permanently outdated)
+
Main window
![Main window](screenshots/mainwindow.png)
diff --git a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar
index 1e1eb2f2996..8f7a587af2a 100644
--- a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar
+++ b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar
Binary files differ
diff --git a/Master/texmf-dist/source/support/tlcockpit/build.sbt b/Master/texmf-dist/source/support/tlcockpit/build.sbt
index c5f7bbd251e..75d9d200771 100644
--- a/Master/texmf-dist/source/support/tlcockpit/build.sbt
+++ b/Master/texmf-dist/source/support/tlcockpit/build.sbt
@@ -1,6 +1,6 @@
name := "tlcockpit"
-version := "0.1"
+version := "0.2"
scalaVersion := "2.12.3"
@@ -11,5 +11,9 @@ libraryDependencies += "org.scalafx" % "scalafx_2.12" % "8.0.102-R11"
mainClass in assembly := Some("TLCockpit.ApplicationMain")
+assemblyJarName in assembly := "tlcockpit.jar"
+assemblyOutputPath in assembly := file("jar/tlcockpit.jar")
+
+
// for scalafx
fork := true
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala
new file mode 100644
index 00000000000..d7cad7021af
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala
@@ -0,0 +1,482 @@
+// TLCockpit
+// Copyright 2017 Norbert Preining
+// Licensed according to GPLv3+
+//
+// Front end for tlmgr
+
+package TLCockpit
+
+import TeXLive.{TLPackage, TlmgrProcess}
+
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.util.{Failure, Success}
+import scalafx.geometry.HPos
+import scalafx.scene.control.Alert.AlertType
+
+// needed see https://github.com/scalafx/scalafx/issues/137
+import scalafx.scene.control.TableColumn._
+import scalafx.scene.control.Menu._
+import scalafx.Includes._
+import scalafx.application.{JFXApp, Platform}
+import scalafx.application.JFXApp.PrimaryStage
+import scalafx.geometry.Insets
+import scalafx.scene.Scene
+import scalafx.scene.layout._
+import scalafx.scene.control._
+import scalafx.event.ActionEvent
+import scalafx.collections.ObservableBuffer
+import scala.sys.process._
+import scala.collection.mutable.ArrayBuffer
+
+
+object ApplicationMain extends JFXApp {
+
+ // necessary action when Window is closed with X or some other operation
+ override def stopApp(): Unit = {
+ tlmgr.cleanup()
+ }
+
+
+ val pkgs = ArrayBuffer[TLPackage]()
+ val viewpkgs = ObservableBuffer[TLPackage]()
+
+ val outputfield = new TextArea {
+ editable = false
+ wrapText = true
+ }
+ val errorfield = new TextArea {
+ editable = false
+ wrapText = true
+ }
+
+ val update_self_button = new Button {
+ text = "Update self"
+ disable = true
+ onAction = (e: ActionEvent) => callback_update_self()
+ }
+ val update_all_button = new Button {
+ text = "Update all"
+ disable = true
+ onAction = (e: ActionEvent) => callback_update_all()
+ }
+
+ val outerrtabs = new TabPane {
+ minWidth = 400
+ tabs = Seq(
+ new Tab {
+ text = "Output"
+ closable = false
+ content = outputfield
+ },
+ new Tab {
+ text = "Error"
+ closable = false
+ content = errorfield
+ }
+ )
+ }
+ val outerrpane = new TitledPane {
+ text = "Debug"
+ collapsible = true
+ expanded = false
+ content = outerrtabs
+ }
+
+ val cmdline = new TextField()
+ val tlmgr = new TlmgrProcess((s:String) => outputfield.text = s,
+ (s:String) => {
+ errorfield.text = s
+ if (s != "") {
+ outerrpane.expanded = true
+ val selmod = outerrtabs.selectionModel()
+ selmod.select(1)
+ }
+ })
+
+ def tlmgr_async_command(s: String, f: Array[String] => Unit): Unit = {
+ errorfield.text = ""
+ outputfield.text = ""
+ outerrpane.expanded = false
+ val foo = Future { tlmgr.send_command(s) }
+ foo onComplete {
+ case Success(ret) => f(ret)
+ case Failure(t) => println("An ERROR has occurred: " + t.getMessage)
+ }
+ }
+
+ def update_update_button_state(): Unit = {
+ update_all_button.disable = ! pkgs.foldLeft(false)(
+ (a,b) => a || (if (b.name.value == "texlive.infra") false else b.lrev.value.toInt > 0 && b.lrev.value.toInt < b.rrev.value.toInt)
+ )
+ // TODO we should change pkgs to a Map with keys are the names + repository (for multi repo support)
+ // and the values are llrev/rrev or some combination
+ update_self_button.disable = ! pkgs.foldLeft(false)(
+ (a,b) => a || (if (b.name.value == "texlive.infra") b.lrev.value.toInt < b.rrev.value.toInt else false)
+ )
+ }
+
+ def callback_quit(): Unit = {
+ tlmgr.cleanup()
+ Platform.exit()
+ sys.exit(0)
+ }
+
+ def callback_load_update_info(s:String, strs: Array[String]): Unit = {
+ if (s == "remote") {
+ tlmgr_async_command("info --data name,localrev,remoterev,shortdesc", (s: Array[String]) => {
+ val newpkgs = ArrayBuffer[TLPackage]()
+ s.map((line:String) => {
+ val fields: Array[String] = line.split(",", -1)
+ val sd = fields(3)
+ val shortdesc = if (sd.isEmpty) "" else sd.substring(1).dropRight(1).replace("""\"""",""""""")
+ newpkgs += new TLPackage(fields(0), fields(1), fields(2), shortdesc)
+ })
+ pkgs.clear()
+ newpkgs.map(pkgs += _)
+ viewpkgs.clear()
+ pkgs.map(viewpkgs += _)
+ // check for updates available
+ update_update_button_state()
+ })
+ }
+ }
+
+ def callback_load(s: String): Unit = {
+ tlmgr_async_command("load " + s, callback_load_update_info(s, _) )
+ }
+
+ def callback_run_text(s: String): Unit = {
+ tlmgr_async_command(s, (s: Array[String]) => {})
+ // tlmgr_command(s)
+ }
+ def callback_run_cmdline(): Unit = {
+ callback_run_text(cmdline.text.value)
+ }
+
+ def callback_list_collections(): Unit = {
+ tlmgr_async_command("info collections", foo => {
+ val foo: Array[String] = Array()
+ // println("got result from info collections: ")
+ // foo.map(println(_))
+ outputfield.text = foo.mkString("\n")
+ errorfield.text = tlmgr.errorBuffer.toString
+ })
+ }
+
+ def not_implemented_info(): Unit = {
+ new Alert(AlertType.Warning) {
+ initOwner(stage)
+ title = "Warning"
+ headerText = "This functionality is not implemented by now!"
+ contentText = "Sorry for the inconveniences."
+ }.showAndWait()
+ }
+
+ def callback_run_external(s: String): Unit = {
+ val foo = Future { s.!! }
+ foo.onComplete {
+ case Success(ret) => outputfield.text = ret.mkString("\n")
+ case Failure(t) => println("An ERROR has occurred: " + t.getMessage)
+ }
+ }
+
+ def callback_about(): Unit = {
+ new Alert(AlertType.Information) {
+ initOwner(stage)
+ title = "About TLCockpit"
+ headerText = "TLCockpit version 0.2\nLicense: GPL3+"
+ contentText = "Brought to you by Norbert\nSources: https://github.com/TeX-Live/tlcockpit"
+ }.showAndWait()
+ }
+ def callback_show_all() : Unit = {
+ viewpkgs.clear()
+ pkgs.map(viewpkgs += _)
+ }
+ def callback_show_installed() : Unit = {
+ viewpkgs.clear()
+ pkgs.map(p => if (p.lrev.value.toInt > 0) viewpkgs += p)
+ }
+ def callback_show_updates(): Unit = {
+ val newpkgs = ArrayBuffer[TLPackage]()
+ pkgs.map(p =>
+ // TODO we are not dealing with not integer values by now!
+ if (p.lrev.value.toInt > 0 && p.lrev.value.toInt < p.rrev.value.toInt) {
+ newpkgs += p
+ }
+ )
+ viewpkgs.clear()
+ newpkgs.map(viewpkgs += _)
+ }
+ def callback_update_all() : Unit = {
+ tlmgr_async_command("update --all", s => {
+ update_update_button_state()
+ callback_show_all()
+ })
+ }
+ def callback_update_self() : Unit = {
+ // TODO
+ // should we restart tlmgr here - it might be necessary!!!
+ tlmgr_async_command("update --self", s => {
+ update_update_button_state()
+ callback_show_all()
+ })
+ }
+
+
+ def callback_show_pkg_info(pkg: String): Unit = {
+ // val pkginfo = tlmgr_command(s"info $pkg")
+ tlmgr_async_command(s"info $pkg", pkginfo => {
+ // need to call runLater to go back to main thread!
+ Platform.runLater {
+ val dialog = new Dialog() {
+ initOwner(stage)
+ title = s"Package Information for $pkg"
+ headerText = s"Package Information for $pkg"
+ resizable = true
+ }
+ dialog.dialogPane().buttonTypes = Seq(ButtonType.OK)
+ val grid = new GridPane() {
+ hgap = 10
+ vgap = 10
+ padding = Insets(20)
+ }
+ var crow = 0
+ pkginfo.foreach((line: String) => {
+ val keyval = line.split(":", 2).map(_.trim)
+ if (keyval.length == 2) {
+ val keylabel = new Label(keyval(0))
+ val vallabel = new Label(keyval(1))
+ vallabel.wrapText = true
+ grid.add(keylabel, 0, crow)
+ grid.add(vallabel, 1, crow)
+ crow += 1
+ }
+ })
+ grid.columnConstraints = Seq(new ColumnConstraints(100, 150, 200), new ColumnConstraints(100, 300, 5000, Priority.Always, new HPos(HPos.Left), true))
+ dialog.dialogPane().content = grid
+ dialog.width = 500
+ dialog.showAndWait()
+ }
+ })
+ }
+
+ var testmode = false
+ if (parameters.unnamed.nonEmpty) {
+ if (parameters.unnamed.head == "-test" || parameters.unnamed.head == "--test") {
+ println("Testing mode enabled, not actually calling tlmgr!")
+ testmode = true
+ }
+ }
+
+ if (!testmode) {
+ tlmgr.start_process()
+
+ // wait until we got a prompt
+ val foo: Array[String] = tlmgr.get_output_till_prompt()
+ // println("current output: ")
+ // foo.map(println(_))
+ }
+
+ stage = new PrimaryStage {
+ title = "TLCockpit"
+ scene = new Scene {
+ root = {
+ val topBox = new MenuBar {
+ useSystemMenuBar = true
+ menus.add(new Menu("TLCockpit") {
+ items = List(
+ new MenuItem("Load default (from tlpdb) repository") {
+ onAction = (ae: ActionEvent) => callback_load("remote")
+ },
+ new MenuItem("Load standard net repository") {
+ onAction = (ae: ActionEvent) => not_implemented_info()
+ disable = true
+ },
+ new MenuItem("Load other repository ...") {
+ onAction = (ae: ActionEvent) => not_implemented_info()
+ disable = true
+ },
+ new SeparatorMenuItem,
+ new MenuItem("Exit") {
+ onAction = (ae: ActionEvent) => callback_quit()
+ }
+ )
+ })
+ menus.add(new Menu("Options") {
+ items = List(
+ new MenuItem("General ...") { disable = true; onAction = (ae) => not_implemented_info() },
+ new MenuItem("Paper ...") { disable = true; onAction = (ae) => not_implemented_info() },
+ new MenuItem("Platforms ...") { disable = true; onAction = (ae) => not_implemented_info() },
+ new SeparatorMenuItem,
+ new CheckMenuItem("Expert options") { disable = true },
+ new CheckMenuItem("Enable debugging options") { disable = true },
+ new CheckMenuItem("Disable auto-install of new packages") { disable = true },
+ new CheckMenuItem("Disable auto-removal of server-deleted packages") { disable = true }
+ )
+ })
+ menus.add(new Menu("Actions") {
+ items = List(
+ new MenuItem("Update filename database ...") { onAction = (ae) => callback_run_external("mktexlsr") },
+ new MenuItem("Rebuild all formats ...") { onAction = (ae) => callback_run_external("fmtutil --sys --all") },
+ new MenuItem("Update font map database ...") { onAction = (ae) => callback_run_external("updmap --sys") },
+ new MenuItem("Restore packages from backup ...") { disable = true; onAction = (ae) => not_implemented_info() },
+ new MenuItem("Handle symlinks in system dirs ...") { disable = true; onAction = (ae) => not_implemented_info() },
+ new SeparatorMenuItem,
+ new MenuItem("Remove TeX Live ...") { disable = true; onAction = (ae) => not_implemented_info() }
+ )
+ })
+ menus.add(new Menu("Help") {
+ items = List(
+ new MenuItem("Manual") { disable = true; onAction = (ae) => not_implemented_info() },
+ new MenuItem("About") { onAction = (ae) => callback_about() },
+ )
+ })
+ }
+ val centerBox = new VBox {
+ padding = Insets(10)
+ children = List(
+ new TitledPane {
+ text = "Actions"
+ collapsible = false
+ content = new VBox {
+ spacing = 10
+ children = List(
+ new HBox {
+ spacing = 10
+ children = List(
+ cmdline,
+ new Button {
+ text = "Go"
+ onAction = (event: ActionEvent) => callback_run_cmdline()
+ },
+ new Button {
+ text = "Load repository";
+ onAction = (event: ActionEvent) => callback_load("remote")
+ }
+ )
+ },
+ new HBox {
+ spacing = 10
+ children = List(
+ new Button {
+ text = "Show updates"
+ onAction = (e: ActionEvent) => callback_show_updates()
+ },
+ new Button {
+ text = "Show installed"
+ onAction = (e: ActionEvent) => callback_show_installed()
+ },
+ new Button {
+ text = "Show all"
+ onAction = (e: ActionEvent) => callback_show_all()
+ },
+ update_self_button,
+ update_all_button
+ )
+ }
+ )
+ }
+ },
+ outerrpane,
+ {
+ val col1 = new TableColumn[TLPackage, String] {
+ text = "Package"
+ cellValueFactory = {
+ _.value.name
+ }
+ prefWidth = 150
+ }
+ val col2 = new TableColumn[TLPackage, String] {
+ text = "Local rev"
+ cellValueFactory = {
+ _.value.lrev
+ }
+ prefWidth = 100
+ }
+ val col3 = new TableColumn[TLPackage, String] {
+ text = "Remote rev"
+ cellValueFactory = {
+ _.value.rrev
+ }
+ prefWidth = 100
+ }
+ val col4 = new TableColumn[TLPackage, String] {
+ text = "Description"
+ cellValueFactory = {
+ _.value.shortdesc
+ }
+ prefWidth = 300
+ }
+ val table = new TableView[TLPackage](viewpkgs) {
+ columns ++= List(col1,col2,col3,col4)
+ }
+ col4.prefWidth.bind(table.width - col1.width - col2.width - col3.width)
+ table.prefHeight = 300
+ table.vgrow = Priority.Always
+ table.rowFactory = { _ =>
+ val row = new TableRow[TLPackage] {}
+ val ctm = new ContextMenu(
+ new MenuItem("Info") { onAction = (ae) => callback_show_pkg_info(row.item.value.name.value) },
+ new MenuItem("Install") {
+ onAction = (ae) => callback_run_text("install " + row.item.value.name.value)
+ },
+ new MenuItem("Remove") {
+ onAction = (ae) => callback_run_text("remove " + row.item.value.name.value)
+ },
+ new MenuItem("Update") {
+ onAction = (ae) => callback_run_text("update " + row.item.value.name.value)
+ }
+ )
+ row.contextMenu = ctm
+ row
+ }
+ table
+ },
+ )
+ }
+ /* val bottomBox = new HBox {
+ padding = Insets(10)
+ spacing = 10
+ children = List(
+ new Button {
+ text = "Quit"
+ onAction = (event: ActionEvent) => callback_quit()
+ }
+ )
+ }
+ */
+ new BorderPane {
+ // padding = Insets(20)
+ top = topBox
+ // left = leftBox
+ center = centerBox
+ // bottom = bottomBox
+ }
+ }
+ }
+ }
+
+ val startalert = new Alert(AlertType.Information)
+ startalert.setTitle("Loading package database ...")
+ startalert.setContentText("Loading package database, this might take a while. Please wait!")
+ startalert.show()
+
+ // load the initial set of packages
+ var pkglines: Array[String] = Array()
+
+ tlmgr_async_command("info --only-installed --data name,localrev,shortdesc", (s: Array[String]) => {
+ s.map(line => {
+ val fields: Array[String] = line.split(",", -1)
+ val sd = if (fields(2).isEmpty) "" else fields(2).substring(1).dropRight(1).replace("""\"""",""""""")
+ pkgs += new TLPackage(fields(0), fields(1), "0", sd)
+ })
+ viewpkgs.clear()
+ pkgs.map(viewpkgs += _)
+ Platform.runLater { startalert.close() }
+ })
+
+
+} // object ApplicationMain
+
+// vim:set tabstop=2 expandtab : //
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsInfo.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsInfo.scala
new file mode 100644
index 00000000000..3b5ad20864f
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsInfo.scala
@@ -0,0 +1,8 @@
+package TeXLive
+
+object OsInfo {
+ val OS = System.getProperty("os.name")
+ def isWindows: Boolean = {
+ OS.startsWith("Windows")
+ }
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala
new file mode 100644
index 00000000000..1f79744fc7f
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala
@@ -0,0 +1,10 @@
+package TeXLive
+
+import scalafx.beans.property.StringProperty
+
+class TLPackage(name_ : String, lrev_ : String, rrev_ : String, shortdesc_ : String) {
+ val name = new StringProperty(this, "name", name_)
+ val shortdesc = new StringProperty(this, "shortdesc", shortdesc_)
+ val lrev = new StringProperty(this, "revision", lrev_)
+ val rrev = new StringProperty(this, "revision", rrev_)
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala
new file mode 100644
index 00000000000..be1a8cd8290
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala
@@ -0,0 +1,152 @@
+package TeXLive
+
+import java.io._
+
+import OsInfo._
+
+import scala.collection.mutable.ArrayBuffer
+import scala.concurrent.SyncVar
+import scala.sys.process.{Process, ProcessBuilder, ProcessIO}
+
+// TODO line by line update of stdout/stderr via an update function passed in
+// otherwise we don't see any update while a long running command like update --all
+// is executed!
+class TlmgrProcess(updout: String => Unit, upderr: String => Unit) {
+ 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
+
+ // set in only one place, in the main thread
+ var process: Process = _
+ var lastInput: String = ""
+ var lastOk: Boolean = false
+
+ def send_command(input: String): Array[String] = {
+
+ lastInput = input
+
+ try {
+ // pass the input and wait for the output
+ assert(!inputString.isSet)
+ assert(!outputString.isSet)
+ errorBuffer.setLength(0)
+ inputString.put(input)
+ var ret = if (input != "quit") {
+ get_output_till_prompt()
+ } else {
+ new Array[String](0)
+ }
+ updout(ret.mkString("\n"))
+ upderr(errorBuffer.toString)
+ ret
+ } catch {
+ case exc: Throwable =>
+ errorBuffer.append(" Main thread: " +
+ (if (exc.isInstanceOf[NoSuchElementException]) "Timeout" else "Exception: " + exc))
+ null
+ }
+ }
+
+ def start_process(): Unit = {
+ // process creation
+ if (process == null) {
+ val procIO = new ProcessIO(inputFn(_), outputFn(_), errorFn(_))
+ val processBuilder: ProcessBuilder = Seq({if (isWindows) "tlmgr.bat" else "tlmgr"}, "--machine-readable", "shell")
+ process = processBuilder.run(procIO)
+ }
+ }
+
+ def get_output_till_prompt(): Array[String] = {
+ var ret = ArrayBuffer[String]()
+ var result = ""
+ var found = false
+ while (!found) {
+ result = outputString.take()
+ if (result == "tlmgr> ") {
+ found = true
+ } else if (result == "OK") {
+ lastOk = true
+ // do nothing, we found a good ok
+ } else if (result == "ERROR") {
+ lastOk = false
+ } else {
+ ret += result
+ }
+ }
+ ret.toArray
+ }
+
+ def cleanup(): Unit = {
+ if (process != null) {
+ send_command("quit")
+ process.destroy()
+ // we'll need to unblock the input again
+ // TODO what is that needed for???
+ // if (!inputString.isSet) inputString.put("")
+ // if (outputString.isSet) outputString.take()
+ }
+ }
+
+ /* 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")
+ // println("writing " + input + " to process")
+ writer.flush()
+ }
+ }
+ stdin.close()
+ } catch {
+ case exc: Throwable =>
+ stdin.close()
+ errorBuffer.append(" Input thread: Exception: " + exc + "\n")
+ }
+ }
+
+ private[this] def outputFn(stdOut: InputStream): Unit = {
+ val reader = new BufferedReader(new InputStreamReader(stdOut))
+ val buffer: StringBuilder = new StringBuilder()
+ try {
+ var line: String = ""
+ while (true) {
+ line = reader.readLine
+ if (line == null) {
+ // println("Did read NULL from stdin giving up")
+ // error = true
+ } else {
+ // println("did read " + line + " from process")
+ outputString.put(line)
+ }
+ }
+ stdOut.close()
+ } catch {
+ case exc: Throwable =>
+ stdOut.close()
+ errorBuffer.append(" Output thread: Exception: " + exc + "\n")
+ }
+ }
+
+ private[this] def errorFn(stdErr: InputStream): Unit = {
+ val reader = new BufferedReader(new InputStreamReader(stdErr))
+ try {
+ var line = reader.readLine
+ while (line != null) {
+ errorBuffer.append(line + "\n")
+ line = reader.readLine
+ }
+ stdErr.close()
+ } catch {
+ case exc: Throwable =>
+ stdErr.close()
+ errorBuffer.append(" Error thread: Exception: " + exc + "\n")
+ }
+ }
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/tlcockpit/ApplicationMain.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/tlcockpit/ApplicationMain.scala
deleted file mode 100644
index a12d5bad2ed..00000000000
--- a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/tlcockpit/ApplicationMain.scala
+++ /dev/null
@@ -1,536 +0,0 @@
-// TLCockpit
-// Copyright 2017 Norbert Preining
-// Licensed according to GPLv3+
-//
-// Front end for tlmgr
-
-package TLCockpit
-
-// import java.util.Date
-
-import java.io.InputStream
-import java.io.OutputStream
-import java.io.InputStreamReader
-import java.io.OutputStreamWriter
-import java.io.BufferedWriter
-import java.io.BufferedReader
-
-import scalafx.beans.property.ObjectProperty
-import scalafx.geometry.HPos
-import scalafx.scene.control.Alert.AlertType
-
-// needed see https://github.com/scalafx/scalafx/issues/137
-import scalafx.scene.control.TableColumn._
-import scalafx.scene.control.Menu._
-import scalafx.Includes._
-import scalafx.application.{JFXApp, Platform}
-import scalafx.application.JFXApp.PrimaryStage
-import scalafx.geometry.Insets
-import scalafx.scene.Scene
-import scalafx.scene.layout._
-import scalafx.scene.control._
-import scalafx.event.ActionEvent
-import scalafx.collections.ObservableBuffer
-import scalafx.beans.property.StringProperty
-import scalafx.scene.input.{KeyCode, KeyEvent, MouseEvent}
-
-//import com.sun.xml.internal.bind.WhiteSpaceProcessor
-
-import scala.sys.process._
-import scala.concurrent.SyncVar
-import scala.collection.mutable.ArrayBuffer
-import scala.collection.mutable.ArrayBuilder
-
-import scala.io.Source
-//import scala.swing._
-
-
-class TLPackage(name_ : String, lrev_ : String, rrev_ : String, shortdesc_ : String) {
- val name = new StringProperty(this, "name", name_)
- val shortdesc = new StringProperty(this, "shortdesc", shortdesc_)
- val lrev = new StringProperty(this, "revision", lrev_)
- val rrev = new StringProperty(this, "revision", rrev_)
-}
-
-
-object OsInfo {
- val OS = System.getProperty("os.name")
- def isWindows: Boolean = {
- OS.startsWith("Windows")
- }
-}
-
-class TlmgrProcess(updout: String => Unit, upderr: String => Unit) {
- 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
-
- // set in only one place, in the main thread
- var process: Process = _
- var lastInput: String = ""
- var lastOk: Boolean = false
-
- def send_command(input: String): Array[String] = {
-
- lastInput = input
-
- try {
- // pass the input and wait for the output
- assert(!inputString.isSet)
- assert(!outputString.isSet)
- inputString.put(input)
- var ret = if (input != "quit") {
- get_output_till_prompt()
- } else {
- new Array[String](0)
- }
- updout(ret.mkString("\n"))
- upderr(errorBuffer.toString)
- ret
- } catch {
- case exc: Throwable =>
- errorBuffer.append(" Main thread: " +
- (if (exc.isInstanceOf[NoSuchElementException]) "Timeout" else "Exception: " + exc))
- null
- }
- }
-
- def start_process(): Unit = {
- // process creation
- if (process == null) {
- val procIO = new ProcessIO(inputFn(_), outputFn(_), errorFn(_))
- val processBuilder: ProcessBuilder = Seq({if (OsInfo.isWindows) "tlmgr.bat" else "tlmgr"}, "--machine-readable", "shell")
- process = processBuilder.run(procIO)
- }
- }
-
- def get_output_till_prompt(): Array[String] = {
- var ret = ArrayBuffer[String]()
- var result = ""
- var found = false
- while (!found) {
- result = outputString.take()
- if (result == "tlmgr> ") {
- found = true
- } else if (result == "OK") {
- lastOk = true
- // do nothing, we found a good ok
- } else if (result == "ERROR") {
- lastOk = false
- } else {
- ret += result
- }
- }
- ret.toArray
- }
-
- def cleanup(): Unit = {
- if (process != null) {
- send_command("quit")
- process.destroy()
- // we'll need to unblock the input again
- // TODO what is that needed for???
- // if (!inputString.isSet) inputString.put("")
- // if (outputString.isSet) outputString.take()
- }
- }
-
- /* 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")
- // println("writing " + input + " to process")
- writer.flush()
- }
- }
- stdin.close()
- } catch {
- case exc: Throwable =>
- stdin.close()
- errorBuffer.append(" Input thread: Exception: " + exc + "\n")
- }
- }
-
- private[this] def outputFn(stdOut: InputStream): Unit = {
- val reader = new BufferedReader(new InputStreamReader(stdOut))
- val buffer: StringBuilder = new StringBuilder()
- try {
- var line: String = ""
- while (true) {
- line = reader.readLine
- if (line == null) {
- // println("Did read NULL from stdin giving up")
- // error = true
- } else {
- // println("did read " + line + " from process")
- outputString.put(line)
- }
- }
- stdOut.close()
- } catch {
- case exc: Throwable =>
- stdOut.close()
- errorBuffer.append(" Output thread: Exception: " + exc + "\n")
- }
- }
-
- private[this] def errorFn(stdErr: InputStream): Unit = {
- val reader = new BufferedReader(new InputStreamReader(stdErr))
- try {
- var line = reader.readLine
- while (line != null) {
- errorBuffer.append(line + "\n")
- line = reader.readLine
- }
- stdErr.close()
- } catch {
- case exc: Throwable =>
- stdErr.close()
- errorBuffer.append(" Error thread: Exception: " + exc + "\n")
- }
- }
-}
-
-object ApplicationMain extends JFXApp {
-
- var testmode = false
- if (parameters.unnamed.length > 0) {
- if (parameters.unnamed(0) == "-test" || parameters.unnamed(0) == "--test") {
- println("Testing mode enabled, not actually calling tlmgr!")
- testmode = true
- }
- }
-
- val pkgs = ArrayBuffer[TLPackage]()
- val viewpkgs = ObservableBuffer[TLPackage]()
-
-
- def onQuit(event: ActionEvent): Unit = Platform.exit()
-
- val outputfield = new TextArea {
- editable = false
- wrapText = true
- }
- val errorfield = new TextArea {
- editable = false
- wrapText = true
- }
- val cmdline = new TextField()
- val tlmgr = new TlmgrProcess((s:String) => outputfield.text = s,
- (s:String) => errorfield.text = s)
-
- if (!testmode) {
- tlmgr.start_process()
-
- // wait until we got a prompt
- val foo: Array[String] = tlmgr.get_output_till_prompt()
- // println("current output: ")
- // foo.map(println(_))
- }
-
- // load the initial set of packages
- val pkglines = if (testmode)
- Array("aa,0,1,ffobar", "bb,4,5,fafaf")
- else
- tlmgr.send_command("info --only-installed --data name,localrev,shortdesc")
-
- pkglines.map(line => {
- val fields: Array[String] = line.split(",",-1)
- val sd = if (fields(2).isEmpty) "" else fields(2).substring(1).dropRight(1).replace("""\"""",""""""")
- pkgs += new TLPackage(fields(0),fields(1),"0",sd)
- })
- viewpkgs.clear()
- pkgs.map(viewpkgs += _)
-
- def callback_quit(): Unit = {
- tlmgr.cleanup()
- Platform.exit()
- sys.exit(0)
- }
-
- def callback_load(s: String): Unit = {
- val foo = tlmgr.send_command("load " + s)
- if (s == "remote") {
- val pkglines: Array[String] = tlmgr.send_command("info --data name,localrev,remoterev,shortdesc")
- val newpkgs = ArrayBuffer[TLPackage]()
- pkglines.map(line => {
- val fields: Array[String] = line.split(",", -1)
- val sd = fields(3)
- val shortdesc = if (sd.isEmpty) "" else sd.substring(1).dropRight(1).replace("""\"""",""""""")
- newpkgs += new TLPackage(fields(0), fields(1), fields(2), shortdesc)
- })
- pkgs.clear()
- newpkgs.map(pkgs += _)
- viewpkgs.clear()
- pkgs.map(viewpkgs += _)
- }
- // println("got result from load " + s + ": ")
- // foo.map(println(_))
- }
-
- def callback_run_cmdline(): Unit = {
- tlmgr.send_command(cmdline.text.value)
- }
-
- def callback_list_collections(): Unit = {
- val foo = tlmgr.send_command("info collections")
- // println("got result from info collections: ")
- // foo.map(println(_))
- outputfield.text = foo.mkString("\n")
- errorfield.text = tlmgr.errorBuffer.toString
- }
-
- def not_implemented_info(): Unit = {
- new Alert(AlertType.Warning) {
- initOwner(stage)
- title = "Warning"
- headerText = "This functionality is not implemented by now!"
- contentText = "Sorry for the inconveniences."
- }.showAndWait()
- }
-
- def callback_about(): Unit = {
- new Alert(AlertType.Information) {
- initOwner(stage)
- title = "About TLCockpit"
- headerText = "TLCockpit version 0.1\nLicense: GPL3+"
- contentText = "Brought to you by Norbert\nSources: https://github.com/TeX-Live/tlcockpit"
- }.showAndWait()
- }
- def callback_show_all() : Unit = {
- viewpkgs.clear()
- pkgs.map(viewpkgs += _)
- }
- def callback_show_installed() : Unit = {
- viewpkgs.clear()
- pkgs.map(p => if (p.lrev.value.toInt > 0) viewpkgs += p)
- }
- def callback_show_updates(): Unit = {
- val newpkgs = ArrayBuffer[TLPackage]()
- pkgs.map(p =>
- // TODO we are not dealing with not integer values by now!
- if (p.lrev.value.toInt > 0 && p.lrev.value.toInt < p.rrev.value.toInt) {
- newpkgs += p
- }
- )
- viewpkgs.clear()
- newpkgs.map(viewpkgs += _)
- }
- def callback_show_pkg_info(pkg: String): Unit = {
- val pkginfo = tlmgr.send_command(s"info $pkg")
- val dialog = new Dialog() {
- initOwner(stage)
- title = s"Package Information for $pkg"
- headerText = s"Package Information for $pkg"
- resizable = true
- }
- dialog.dialogPane().buttonTypes = Seq(ButtonType.OK)
- val grid = new GridPane() {
- hgap = 10
- vgap = 10
- padding = Insets(20)
- }
- var crow = 0
- pkginfo.map((line:String) => {
- val keyval = line.split(":",2).map(_.trim)
- if (keyval.length == 2) {
- val keylabel = new Label(keyval(0))
- val vallabel = new Label(keyval(1))
- vallabel.wrapText = true
- grid.add(keylabel, 0, crow)
- grid.add(vallabel, 1, crow)
- crow += 1
- }
- })
- grid.columnConstraints = Seq(new ColumnConstraints(100,150,200),new ColumnConstraints(100,300,5000,Priority.Always,new HPos(HPos.Left),true))
- dialog.dialogPane().content = grid
- dialog.width = 500
- dialog.showAndWait()
- }
- stage = new PrimaryStage {
- title = "TLCockpit"
- scene = new Scene {
- root = {
- val topBox = new MenuBar {
- useSystemMenuBar = true
- menus.add(new Menu("TLCockpit") {
- items = List(
- new MenuItem("Load default (from tlpdb) repository") {
- onAction = (ae: ActionEvent) => callback_load("remote")
- },
- new MenuItem("Load standard net repository") {
- onAction = (ae: ActionEvent) => not_implemented_info()
- },
- new MenuItem("Load other repository ...") {
- onAction = (ae: ActionEvent) => not_implemented_info()
- },
- new SeparatorMenuItem,
- new MenuItem("Exit") {
- onAction = (ae: ActionEvent) => callback_quit()
- }
- )
- })
- menus.add(new Menu("Options") {
- items = List(
- new MenuItem("General ...") { onAction = (ae) => not_implemented_info() },
- new MenuItem("Paper ...") { onAction = (ae) => not_implemented_info() },
- new MenuItem("Platforms ...") { onAction = (ae) => not_implemented_info() },
- new SeparatorMenuItem,
- new CheckMenuItem("Expert options"),
- new CheckMenuItem("Enable debugging options"),
- new CheckMenuItem("Disable auto-install of new packages"),
- new CheckMenuItem("Disable auto-removal of server-deleted packages")
- )
- })
- menus.add(new Menu("Actions") {
- items = List(
- new MenuItem("Update filename database ...") { onAction = (ae) => not_implemented_info() },
- new MenuItem("Rebuild all formats ...") { onAction = (ae) => not_implemented_info() },
- new MenuItem("Update font map database ...") { onAction = (ae) => not_implemented_info() },
- new MenuItem("Restore packages from backup ...") { onAction = (ae) => not_implemented_info() },
- new MenuItem("Handle symlinks in system dirs ...") { onAction = (ae) => not_implemented_info() },
- new SeparatorMenuItem,
- new MenuItem("Remove TeX Live ...") { onAction = (ae) => not_implemented_info() }
- )
- })
- menus.add(new Menu("Help") {
- items = List(
- new MenuItem("Manual") { onAction = (ae) => not_implemented_info() },
- new MenuItem("About") { onAction = (ae) => callback_about() },
- )
- })
- }
- val centerBox = new VBox {
- children = List(
- new TitledPane {
- text = "Actions"
- collapsible = false
- content = new VBox {
- spacing = 10
- children = List(
- new HBox {
- spacing = 10
- children = List(
- cmdline,
- new Button {
- text = "Go"
- onAction = (event: ActionEvent) => callback_run_cmdline()
- },
- new Button {
- text = "Load repository";
- onAction = (event: ActionEvent) => callback_load("remote")
- }
- )
- },
- new HBox {
- spacing = 10
- children = List(
- new Button {
- text = "Show updates"
- onAction = (e: ActionEvent) => callback_show_updates()
- },
- new Button {
- text = "Show installed"
- onAction = (e: ActionEvent) => callback_show_installed()
- },
- new Button {
- text = "Show all"
- onAction = (e: ActionEvent) => callback_show_all()
- }
- )
- }
- )
- }
- },
- new TitledPane {
- text = "Debug"
- collapsible = true
- expanded = false
- content = new TabPane {
- minWidth = 400
- tabs = Seq(
- new Tab {
- text = "Output"
- closable = false
- content = outputfield
- },
- new Tab {
- text = "Error"
- closable = false
- content = errorfield
- }
- )
- }
- }, {
- val col1 = new TableColumn[TLPackage, String] {
- text = "Package"
- cellValueFactory = {
- _.value.name
- }
- prefWidth = 150
- }
- val col2 = new TableColumn[TLPackage, String] {
- text = "Local rev"
- cellValueFactory = {
- _.value.lrev
- }
- prefWidth = 100
- }
- val col3 = new TableColumn[TLPackage, String] {
- text = "Remote rev"
- cellValueFactory = {
- _.value.rrev
- }
- prefWidth = 100
- }
- val col4 = new TableColumn[TLPackage, String] {
- text = "Description"
- cellValueFactory = {
- _.value.shortdesc
- }
- prefWidth = 300
- }
- val table = new TableView[TLPackage](viewpkgs) {
- columns ++= List(col1,col2,col3,col4)
- }
- col4.prefWidth.bind(table.width - col1.width - col2.width - col3.width)
- table.rowFactory = { _ =>
- val row = new TableRow[TLPackage] {}
- row.onMouseClicked = { (me: MouseEvent) => callback_show_pkg_info(row.item.value.name.value) }
- row
- }
- table
- },
- )
- }
- val bottomBox = new HBox {
- spacing = 10
- children = List(
- new Button {
- text = "Quit"
- onAction = (event: ActionEvent) => callback_quit()
- }
- )
- }
-
- new BorderPane {
- padding = Insets(20)
- top = topBox
- // left = leftBox
- center = centerBox
- bottom = bottomBox
- }
- }
- }
- }
-} // object ApplicationMain
-
-// vim:set tabstop=2 expandtab : //