diff options
Diffstat (limited to 'Master/texmf-dist')
-rw-r--r-- | Master/texmf-dist/doc/support/tlcockpit/README.md | 25 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg | bin | 0 -> 47544 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/support/tlcockpit/screenshots/mainwindow.png | bin | 100565 -> 0 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg | bin | 0 -> 57766 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg | bin | 0 -> 22768 bytes | |||
-rw-r--r-- | Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar | bin | 11620031 -> 11646457 bytes | |||
-rw-r--r-- | Master/texmf-dist/source/support/tlcockpit/build.sbt | 2 | ||||
-rw-r--r-- | Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala | 418 | ||||
-rw-r--r-- | Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala | 9 | ||||
-rw-r--r-- | Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala | 23 | ||||
-rw-r--r-- | Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala | 26 |
11 files changed, 339 insertions, 164 deletions
diff --git a/Master/texmf-dist/doc/support/tlcockpit/README.md b/Master/texmf-dist/doc/support/tlcockpit/README.md index 7d6d09b3e4c..40e85c26f4d 100644 --- a/Master/texmf-dist/doc/support/tlcockpit/README.md +++ b/Master/texmf-dist/doc/support/tlcockpit/README.md @@ -3,18 +3,9 @@ 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. +We take inspiration from the [TeX Live Utility](http://amaxwell.github.io/tlutility/). -Supported functionality ------------------------ - -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. - -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 ------------------ @@ -25,7 +16,7 @@ should suffice. 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`. +`TEXLIVEROOT/texmf-dist/scripts/tlcockpit` replacing the current `tlcockpit.jar`. After this you can run it by simply typing `tlcockpit`, or via a direct `java` call as in `java -jar PATH/TO/tlcockpit.jar`. @@ -67,9 +58,17 @@ Screenshots (permanently outdated) -Main window +Package listing + +![Package listing view](screenshots/pkgs-view.jpg) + +Updates listing + +![Updates listing view](screenshots/update-view.jpg) + +Backup/Restore listing -![Main window](screenshots/mainwindow.png) +![Backup listing view](screenshots/backup-view.jpg) Package details diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg Binary files differnew file mode 100644 index 00000000000..552ec831b14 --- /dev/null +++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/mainwindow.png b/Master/texmf-dist/doc/support/tlcockpit/screenshots/mainwindow.png Binary files differdeleted file mode 100644 index b74fd22499d..00000000000 --- a/Master/texmf-dist/doc/support/tlcockpit/screenshots/mainwindow.png +++ /dev/null diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg Binary files differnew file mode 100644 index 00000000000..ef0ca60ef4f --- /dev/null +++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg Binary files differnew file mode 100644 index 00000000000..1d10628df52 --- /dev/null +++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg diff --git a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar Binary files differindex c581adb0880..4fe251977c0 100644 --- a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar +++ b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar diff --git a/Master/texmf-dist/source/support/tlcockpit/build.sbt b/Master/texmf-dist/source/support/tlcockpit/build.sbt index 75d9d200771..10d46ad0d15 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.2" +version := "0.3" scalaVersion := "2.12.3" 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 index f76a34d2f75..c281308d6b4 100644 --- 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 @@ -6,14 +6,21 @@ package TLCockpit -import TeXLive.{TLPackage, TlmgrProcess} +import javafx.collections.ObservableList +import javafx.scene.control +import TeXLive.{TLBackup, TLPackage, TlmgrProcess} + +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer import scala.concurrent.{Future, SyncVar} import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.{Failure, Success} +import scala.util.{Failure, Sorting, Success} +import scalafx.beans.Observable +import scalafx.beans.property.{ObjectProperty, ReadOnlyStringWrapper} import scalafx.geometry.{HPos, Pos, VPos} import scalafx.scene.control.Alert.AlertType - +import scalafx.scene.input.{KeyCode, KeyEvent} // needed see https://github.com/scalafx/scalafx/issues/137 import scalafx.scene.control.TableColumn._ import scalafx.scene.control.Menu._ @@ -27,31 +34,30 @@ import scalafx.scene.control._ import scalafx.event.ActionEvent import scalafx.collections.ObservableBuffer import scala.sys.process._ -import scala.collection.mutable.ArrayBuffer import scalafx.scene.text._ +import scalafx.collections.ObservableBuffer object ApplicationMain extends JFXApp { + val version: String = getClass.getPackage.getImplementationVersion + // necessary action when Window is closed with X or some other operation override def stopApp(): Unit = { tlmgr.cleanup() } + val pkgs: ArrayBuffer[TLPackage] = ArrayBuffer() - val pkgs = ArrayBuffer[TLPackage]() - val viewpkgs = ObservableBuffer[TLPackage]() - val updpkgs = ObservableBuffer[TLPackage]() + val errorText: ObservableBuffer[String] = ObservableBuffer[String]() + val outputText: ObservableBuffer[String] = ObservableBuffer[String]() - val errorText = ObservableBuffer[String]() - val outputText = ObservableBuffer[String]() - - val outputfield = new TextArea { + val outputfield: TextArea = new TextArea { editable = false wrapText = true text = "" } - val errorfield = new TextArea { + val errorfield: TextArea = new TextArea { editable = false wrapText = true text = "" @@ -65,18 +71,16 @@ object ApplicationMain extends JFXApp { outputfield.scrollTop = Double.MaxValue }) - val update_self_button = new Button { - text = "Update self" + val update_all_menu: MenuItem = new MenuItem("Update all") { + onAction = (ae) => callback_update_all() disable = true - onAction = (e: ActionEvent) => callback_update_self() } - val update_all_button = new Button { - text = "Update all" + val update_self_menu: MenuItem = new MenuItem("Update self") { + onAction = (ae) => callback_update_self() disable = true - onAction = (e: ActionEvent) => callback_update_all() } - val outerrtabs = new TabPane { + val outerrtabs: TabPane = new TabPane { minWidth = 400 tabs = Seq( new Tab { @@ -91,7 +95,7 @@ object ApplicationMain extends JFXApp { } ) } - val outerrpane = new TitledPane { + val outerrpane: TitledPane = new TitledPane { text = "Debug" collapsible = true expanded = false @@ -99,14 +103,19 @@ object ApplicationMain extends JFXApp { } val cmdline = new TextField() + cmdline.onKeyPressed = { + (ae: KeyEvent) => if (ae.code == KeyCode.Enter) callback_run_cmdline() + } val tlmgr = new TlmgrProcess( // (s:String) => outputfield.text = s, (s: Array[String]) => { - outputText.clear() - outputText.appendAll(s) + // we don't wont normal output to be displayed + // as it is anyway returned + // outputText.clear() + // outputText.appendAll(s) }, (s: String) => { - errorText.clear() + // errorText.clear() errorText.append(s) if (s != "") { outerrpane.expanded = true @@ -118,27 +127,108 @@ object ApplicationMain extends JFXApp { errorText.clear() outputText.clear() outerrpane.expanded = false + statusMenu.text = "Status: Busy" val foo = Future { tlmgr.send_command(s) } foo onComplete { - case Success(ret) => f(ret) + case Success(ret) => + f(ret) + Platform.runLater { statusMenu.text = "Status: Idle" } case Failure(t) => errorText.append("An ERROR has occurred calling tlmgr: " + t.getMessage) outerrpane.expanded = true outerrtabs.selectionModel().select(1) + Platform.runLater { statusMenu.text = "Status: Idle" } } } - def update_update_button_state(): Unit = { - update_all_button.disable = !pkgs.foldLeft(false)( + def update_update_menu_state(): Unit = { + update_all_menu.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)( + update_self_menu.disable = !pkgs.foldLeft(false)( (a, b) => a || (if (b.name.value == "texlive.infra") b.lrev.value.toInt < b.rrev.value.toInt else false) ) + updateTable.refresh() + packageTable.refresh() + } + + def update_pkg_lists(): Unit = { + tlmgr_async_command("info --data name,localrev,remoterev,shortdesc,size,installed", (s: Array[String]) => { + pkgs.clear() + 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("""\"""",""""""") + val inst = if (fields(5) == "1") "Installed" else "Not installed" + pkgs += new TLPackage(fields(0), fields(1), fields(2), shortdesc, fields(4), inst) + }) + val pkgbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage] + val binbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackage]] + val updbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage] + pkgs.foreach(pkg => { + if (pkg.lrev.value > 0 && pkg.lrev.value < pkg.rrev.value) { + // easy part: in the update tab show all packages in raw format + // TODO if the main package is updated, too, use a child item? + updbuf += pkg + } + // complicated part, determine whether it is a sub package or not! + // we strip of initial texlive. prefixes to make sure we deal + // with real packages + if (pkg.name.value.stripPrefix("texlive.").contains(".")) { + val foo: Array[String] = pkg.name.value.stripPrefix("texlive.infra").split('.') + val pkgname = foo(0) + val binname = foo(1) + if (binbuf.keySet.contains(pkgname)) { + binbuf(pkgname) += pkg + } else { + binbuf(pkgname) = ArrayBuffer[TLPackage](pkg) + } + } else { + pkgbuf += pkg + } + }) + // now we have all normal packages in pkgbuf, and its sub-packages in binbuf + // we need to create TreeItems + val viewkids = pkgbuf.map { p => { + val kids: Seq[TLPackage] = if (binbuf.keySet.contains(p.name.value)) { + binbuf(p.name.value) + } else { + Seq() + } + // for ismixed we && all the installed status. If all are installed, we get true + val allinstalled = (kids :+ p).foldRight[Boolean](true)((k,b) => k.installed.value == "Installed" && b) + if (!allinstalled) { + // replace installed status with "Mixed" + new TreeItem[TLPackage](new TreeItem[TLPackage]( + new TLPackage(p.name.value, p.lrev.value.toString, p.rrev.value.toString, p.shortdesc.value, p.size.value.toString, "Mixed") + )) { + children = kids.map(new TreeItem[TLPackage](_)) + } + } else { + new TreeItem[TLPackage](p) { + children = kids.map(new TreeItem[TLPackage](_)) + } + } + }} + val updkids = updbuf.map { p => new TreeItem[TLPackage](p) } + Platform.runLater { + packageTable.root = new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) { + expanded = true + children = viewkids + } + updateTable.root = new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) { + expanded = true + children = updkids + } + } + updateTable.refresh() + packageTable.refresh() + update_update_menu_state() + }) } def callback_quit(): Unit = { @@ -152,9 +242,11 @@ object ApplicationMain extends JFXApp { } def callback_run_cmdline(): Unit = { - callback_run_text(cmdline.text.value) - outerrpane.expanded = true - outerrtabs.selectionModel().select(0) + tlmgr_async_command(cmdline.text.value, s => { + outputText.appendAll(s) + outerrpane.expanded = true + outerrtabs.selectionModel().select(0) + }) } def not_implemented_info(): Unit = { @@ -194,53 +286,61 @@ object ApplicationMain extends JFXApp { new Alert(AlertType.Information) { initOwner(stage) title = "About TLCockpit" - headerText = "TLCockpit version 0.2\nLicense: GPL3+" + headerText = "TLCockpit version " + version + "\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_update_all(): Unit = { + // TODO parse table and update line by line + tlmgr_async_command("update --all", _ => { Platform.runLater { update_pkg_lists() } }) } - def callback_show_installed(): Unit = { - viewpkgs.clear() - pkgs.map(p => if (p.lrev.value.toInt > 0) viewpkgs += p) + def callback_update_self(): Unit = { + // TODO should we restart tlmgr here - it might be necessary!!! + tlmgr_async_command("update --self", _ => { Platform.runLater { update_pkg_lists() } }) } - 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 do_one_pkg(what: String, pkg: String): Unit = { + tlmgr_async_command(s"$what $pkg", _ => { Platform.runLater { update_pkg_lists() } }) } - */ - def callback_update_all(): Unit = { - tlmgr_async_command("update --all", s => { - update_update_button_state() - // callback_show_all() - }) + def callback_restore_pkg(str: String, rev: String): Unit = { + not_implemented_info() } - 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_populate_backup_tab(): Unit = { + // only run the restore command once + if (backupTable.root.value.children.length == 0) { + + tlmgr_async_command("restore", lines => { + // lines.drop(1).foreach(println(_)) + val foo = lines.drop(1).map { l => + val fields = l.split("[ ():]", -1).filter(_.nonEmpty) + val pkgname = fields(0) + val rests: Array[Array[String]] = fields.drop(1).sliding(4, 4).toArray + if (rests.length == 1) { + val p = rests(0) + new TreeItem[TLBackup](new TLBackup(pkgname, p(0), p(1) + " " + p(2) + ":" + p(3))) + } else { + new TreeItem[TLBackup](new TLBackup(pkgname, "", "")) { + children = rests.map(p => new TreeItem[TLBackup]( + new TLBackup(pkgname, p(0), p(1) + " " + p(2) + ":" + p(3)) + )).toSeq + } + } + } + val newroot = new TreeItem[TLBackup](new TLBackup("root", "", "")) { + children = foo + } + Platform.runLater { + backupTable.root = newroot + } + }) + } } - 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 { @@ -276,8 +376,12 @@ object ApplicationMain extends JFXApp { }) } + val mainMenu = new Menu("TLCockpit") { items = List( + update_all_menu, + update_self_menu, + new SeparatorMenuItem, new MenuItem("Update filename database ...") { onAction = (ae) => callback_run_external("mktexlsr") }, @@ -346,6 +450,9 @@ object ApplicationMain extends JFXApp { }, ) } + val statusMenu = new Menu("Status: Idle") { + disable = true + } val expertPane = new TitledPane { text = "Experts only" collapsible = true @@ -355,11 +462,9 @@ object ApplicationMain extends JFXApp { children = List( new HBox { spacing = 10 + alignment = Pos.CenterLeft children = List( - new Label("tlmgr shell command:") { - // TODO vertical text alignment does not work - textAlignment = TextAlignment.Center - }, + new Label("tlmgr shell command:"), cmdline, new Button { text = "Go" @@ -391,51 +496,59 @@ object ApplicationMain extends JFXApp { } } val updateTable = { - val colName = new TableColumn[TLPackage, String] { + val colName = new TreeTableColumn[TLPackage, String] { text = "Package" - cellValueFactory = { _.value.name } + cellValueFactory = { _.value.value.value.name } prefWidth = 150 } - val colDesc = new TableColumn[TLPackage, String] { + val colDesc = new TreeTableColumn[TLPackage, String] { text = "Description" - cellValueFactory = { _.value.shortdesc } + cellValueFactory = { _.value.value.value.shortdesc } prefWidth = 300 } - val colLRev = new TableColumn[TLPackage, String] { + val colLRev = new TreeTableColumn[TLPackage, Int] { text = "Local rev" - cellValueFactory = { _.value.lrev } + cellValueFactory = { _.value.value.value.lrev } prefWidth = 100 } - val colRRev = new TableColumn[TLPackage, String] { + val colRRev = new TreeTableColumn[TLPackage, Int] { text = "Remote rev" - cellValueFactory = { _.value.rrev } + cellValueFactory = { _.value.value.value.rrev } prefWidth = 100 } - val colSize = new TableColumn[TLPackage, String] { + val colSize = new TreeTableColumn[TLPackage, Int] { text = "Size" - cellValueFactory = { _.value.size } + cellValueFactory = { _.value.value.value.size } prefWidth = 100 } - val table = new TableView[TLPackage](updpkgs) { + val table = new TreeTableView[TLPackage]( + new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) { + expanded = false + }) { columns ++= List(colName, colDesc, colLRev, colRRev, colSize) } - colDesc.prefWidth.bind(table.width - colName.width - colLRev.width - colRRev.width - colSize.width) + colDesc.prefWidth.bind(table.width - colName.width - colLRev.width - colRRev.width - colSize.width - 15) table.prefHeight = 300 table.vgrow = Priority.Always + table.placeholder = new Label("No updates available") + table.showRoot = false table.rowFactory = { _ => - val row = new TableRow[TLPackage] {} + val row = new TreeTableRow[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) + // onAction = (ae) => callback_run_text("install " + row.item.value.name.value) + onAction = (ae) => do_one_pkg("install", row.item.value.name.value) }, new MenuItem("Remove") { - onAction = (ae) => callback_run_text("remove " + row.item.value.name.value) + // onAction = (ae) => callback_run_text("remove " + row.item.value.name.value) + onAction = (ae) => do_one_pkg("remove", row.item.value.name.value) }, new MenuItem("Update") { - onAction = (ae) => callback_run_text("update " + row.item.value.name.value) + // onAction = (ae) => callback_run_text("update " + row.item.value.name.value) + onAction = (ae) => do_one_pkg("update", row.item.value.name.value) } ) row.contextMenu = ctm @@ -444,11 +557,12 @@ object ApplicationMain extends JFXApp { table } val packageTable = { - val colName = new TableColumn[TLPackage, String] { + val colName = new TreeTableColumn[TLPackage, String] { text = "Package" - cellValueFactory = { _.value.name } + cellValueFactory = { _.value.value.value.name } prefWidth = 150 } + /* val colLRev = new TableColumn[TLPackage, String] { text = "Local rev" cellValueFactory = { _.value.lrev } @@ -459,36 +573,82 @@ object ApplicationMain extends JFXApp { cellValueFactory = { _.value.rrev } prefWidth = 100 } - val colDesc = new TableColumn[TLPackage, String] { + */ + val colDesc = new TreeTableColumn[TLPackage, String] { text = "Description" - cellValueFactory = { _.value.shortdesc } + cellValueFactory = { _.value.value.value.shortdesc } prefWidth = 300 } - val colInst = new TableColumn[TLPackage, String] { + val colInst = new TreeTableColumn[TLPackage, String] { text = "Installed" - cellValueFactory = { _.value.installed } - prefWidth = 30 + cellValueFactory = { _.value.value.value.installed } + prefWidth = 100 } - val table = new TableView[TLPackage](viewpkgs) { - columns ++= List(colName, colDesc, colInst, colLRev, colRRev) + val table = new TreeTableView[TLPackage]( + new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) { + expanded = false + }) { + columns ++= List(colName, colDesc, colInst) } - colDesc.prefWidth.bind(table.width - colLRev.width - colRRev.width - colInst.width - colName.width) + colDesc.prefWidth.bind(table.width - colInst.width - colName.width - 15) table.prefHeight = 300 + table.showRoot = false table.vgrow = Priority.Always table.rowFactory = { _ => - val row = new TableRow[TLPackage] {} + val row = new TreeTableRow[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) + onAction = (ae) => do_one_pkg("install", row.item.value.name.value) }, new MenuItem("Remove") { - onAction = (ae) => callback_run_text("remove " + row.item.value.name.value) + onAction = (ae) => do_one_pkg("remove", row.item.value.name.value) }, new MenuItem("Update") { - onAction = (ae) => callback_run_text("update " + row.item.value.name.value) + onAction = (ae) => do_one_pkg("update", row.item.value.name.value) + } + ) + row.contextMenu = ctm + row + } + table + } + val backupTable = { + val colName = new TreeTableColumn[TLBackup, String] { + text = "Package" + cellValueFactory = { _.value.value.value.name } + prefWidth = 150 + } + val colRev = new TreeTableColumn[TLBackup, String] { + text = "Revision" + cellValueFactory = { _.value.value.value.rev } + prefWidth = 100 + } + val colDate = new TreeTableColumn[TLBackup, String] { + text = "Date" + cellValueFactory = { _.value.value.value.date } + prefWidth = 300 + } + val table = new TreeTableView[TLBackup]( + new TreeItem[TLBackup](new TLBackup("root","","")) { + expanded = false + }) { + columns ++= List(colName, colRev, colDate) + } + colDate.prefWidth.bind(table.width - colRev.width - colName.width - 15) + table.prefHeight = 300 + table.showRoot = false + table.vgrow = Priority.Always + table.rowFactory = { _ => + val row = new TreeTableRow[TLBackup] {} + val ctm = new ContextMenu( + new MenuItem("Info") { + onAction = (ae) => callback_show_pkg_info(row.item.value.name.value) + }, + new MenuItem("Restore") { + onAction = (ae) => callback_restore_pkg(row.item.value.name.value, row.item.value.rev.value) } ) row.contextMenu = ctm @@ -509,18 +669,38 @@ object ApplicationMain extends JFXApp { text = "Packages" closable = false content = packageTable + }, + new Tab { + text = "Backups" + closable = false + content = backupTable } ) } + pkgstabs.selectionModel().selectedItem.onChange( + (a,b,c) => { + if (a.value.text() == "Backups") { + callback_populate_backup_tab() + // println("Entering Backup Tab") + } + } + ) + val menuBar = new MenuBar { + useSystemMenuBar = true + // menus.addAll(mainMenu, optionsMenu, helpMenu) + menus.addAll(mainMenu, statusMenu) + } + stage = new PrimaryStage { title = "TLCockpit" scene = new Scene { root = { - val topBox = new MenuBar { - useSystemMenuBar = true - // menus.addAll(mainMenu, optionsMenu, helpMenu) - menus.addAll(mainMenu) - } + // val topBox = new HBox { + // children = List(menuBar, statusLabel) + // } + // topBox.hgrow = Priority.Always + // topBox.maxWidth = Double.MaxValue + val topBox = menuBar val centerBox = new VBox { padding = Insets(10) children = List(pkgstabs, expertPane, outerrpane) @@ -543,6 +723,7 @@ object ApplicationMain extends JFXApp { startalert.show() */ + /* var testmode = false if (parameters.unnamed.nonEmpty) { if (parameters.unnamed.head == "-test" || parameters.unnamed.head == "--test") { @@ -550,38 +731,11 @@ object ApplicationMain extends JFXApp { testmode = true } } + */ - // Thread.sleep(3000) - - 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 - var pkglines: Array[String] = Array() - - // tlmgr loads local and remote if we request localrev and remoterev! - tlmgr_async_command("info --data name,localrev,remoterev,shortdesc,size,installed", (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("""\"""",""""""") - pkgs += new TLPackage(fields(0), fields(1), fields(2), shortdesc, fields(4), fields(5)) - }) - pkgs.map(pkg => { - viewpkgs += pkg - if (pkg.installed.value == "1" && pkg.lrev.value.toInt < pkg.rrev.value.toInt) updpkgs += pkg - }) - // check for updates available - update_update_button_state() - // Platform.runLater { startalert.close() } - }) + tlmgr.start_process() + tlmgr.get_output_till_prompt() + update_pkg_lists() // this is async done } // object ApplicationMain diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala new file mode 100644 index 00000000000..a00c1464f30 --- /dev/null +++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala @@ -0,0 +1,9 @@ +package TeXLive + +import scalafx.beans.property.StringProperty + + +case class TLBackup(name: StringProperty, rev: StringProperty, date: StringProperty) { + def this(_name: String, _rev: String, _date: String) = + this(StringProperty(_name), StringProperty(_rev), StringProperty(_date)) +} 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 index cb9d8c7e88e..9a797d9c8ae 100644 --- 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 @@ -1,14 +1,15 @@ package TeXLive -import scalafx.beans.property.StringProperty -import scalafx.beans.property.IntegerProperty -import scalafx.beans.property.BooleanProperty +import scalafx.beans.property.{ObjectProperty, StringProperty} -class TLPackage(name_ : String, lrev_ : String, rrev_ : String, shortdesc_ : String, size_ : String, installed_ : 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_) - val size = new StringProperty(this, "size", size_) - val installed = new StringProperty(this, "installed", installed_) -} +// Note!!! we have to use ObjectProperty[Int] here instead of IntegerProperty +// since IntegerProperty does NOT implement Observable[Int,Int] +// see https://github.com/scalafx/scalafx/issues/243 +case class TLPackage(name: StringProperty, lrev: ObjectProperty[Int], rrev: ObjectProperty[Int], + shortdesc: StringProperty, size: ObjectProperty[Int], installed: StringProperty) { + def this(_name: String, _lrev: String, _rrev: String, _shortdesc: String, _size: String, _installed: String) = + this( + StringProperty(_name), ObjectProperty[Int](_lrev.toInt), ObjectProperty[Int](_rrev.toInt), + StringProperty(_shortdesc), ObjectProperty[Int](_size.toInt), StringProperty(_installed) + ) +}
\ No newline at end of file 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 index 8984ab7a1af..360ad9bc433 100644 --- 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 @@ -17,18 +17,32 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) { var process: Process = _ var lastInput: String = "" var lastOk: Boolean = false + var isBusy = false + def send_command(input: String): Array[String] = { lastInput = input + val maxWaitTime = 5000 + var waited = 0 + while (isBusy) { + // do busy waiting here in case some other tlmgr command is running + // println("Debug: waiting for tlmgr being ready, want to send " + input) + Thread.sleep(300) + waited += 300 + if (waited > maxWaitTime) { + throw new Exception("tlmgr busy, waited too long, aborting calling: " + input) + } + } try { // pass the input and wait for the output assert(!inputString.isSet) assert(!outputString.isSet) - errorBuffer.setLength(0) + // errorBuffer.setLength(0) + synchronized(isBusy = true) inputString.put(input) - var ret = if (input != "quit") { + val ret = if (input != "quit") { get_output_till_prompt() } else { new Array[String](0) @@ -36,6 +50,7 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) { // updout(ret.mkString("\n")) updout(ret) upderr(errorBuffer.toString) + synchronized( isBusy = false ) ret } catch { case exc: Throwable => @@ -58,6 +73,7 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) { var ret = ArrayBuffer[String]() var result = "" var found = false + synchronized(isBusy = true) while (!found) { result = outputString.take() if (result == "tlmgr> ") { @@ -71,6 +87,7 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) { ret += result } } + synchronized(isBusy = false) ret.toArray } @@ -78,10 +95,6 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => 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() } } @@ -111,7 +124,6 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) { 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) { |