summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/NEWS25
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/README.md29
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpgbin47544 -> 0 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/pkg-details.pngbin155022 -> 0 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpgbin57766 -> 0 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-backups.jpgbin0 -> 48570 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-collections.jpgbin0 -> 69113 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-config.jpgbin0 -> 37865 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-loading.jpgbin0 -> 34424 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-packages.jpgbin0 -> 78475 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-paper.jpgbin0 -> 16959 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-pkgdetails.jpgbin0 -> 106077 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-updates.jpgbin0 -> 82512 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpgbin22768 -> 0 bytes
-rw-r--r--Master/texmf-dist/scripts/tlcockpit/tlcockpit.jarbin11704983 -> 12107255 bytes
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/.gitignore3
-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.scala1228
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/LocationDialog.scala117
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/OptionsDialog.scala129
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/PaperDialog.scala83
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/PkgInfoDialog.scala145
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/TLDisplayClasses.scala30
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/Utils.scala18
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/JsonProtocol.scala15
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsTools.scala4
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala9
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLClasses.scala56
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala15
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLUpdate.scala15
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala137
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/package.scala4
32 files changed, 1376 insertions, 692 deletions
diff --git a/Master/texmf-dist/doc/support/tlcockpit/NEWS b/Master/texmf-dist/doc/support/tlcockpit/NEWS
index d8c84e675e8..1c12e2f46db 100644
--- a/Master/texmf-dist/doc/support/tlcockpit/NEWS
+++ b/Master/texmf-dist/doc/support/tlcockpit/NEWS
@@ -1,16 +1,29 @@
NEWS for tlcockpit
-version 0.6
------------
+version 0.7 (20171124)
+----------------------
+requires tlmgr >= 2017-11-23 (version 45888 of texlive.infra)
+- fix behavior when closing using the Window Manager X on Windows (Issue: 2)
+- use tlmgr json output
+- allow ordering packages by name and by collection
+- log and error output separated (Debug area)
+- support for paper configuration and general options
+- deal with dying tlmgr
+- add search capability (searches package names and sortdesc)
+- check whether tlmgr is new enough
+- updates, installs, removes are online updated during operation
+
+version 0.6 (20171012)
+----------------------
- texlive infra updates handled properly (restarting tlmgr)
- scrollable list of files in pkg info window
-version 0.5
------------
+version 0.5 (20171009)
+----------------------
- better pkg info display
- try to open doc files from pkg info window
- dynamic updates of the update screen during updates
-version 0.4
------------
+version 0.4 (20171004)
+----------------------
First half-way usable version
diff --git a/Master/texmf-dist/doc/support/tlcockpit/README.md b/Master/texmf-dist/doc/support/tlcockpit/README.md
index 4ee2115d88c..e151947c742 100644
--- a/Master/texmf-dist/doc/support/tlcockpit/README.md
+++ b/Master/texmf-dist/doc/support/tlcockpit/README.md
@@ -25,8 +25,8 @@ call as in `java -jar PATH/TO/tlcockpit.jar`.
Requirements
------------
-You need at least `tlmgr` from after 2017-09-01, in TeX Live language at least
-version 45190 or `texlive.infra`.
+You need at least `tlmgr` from after 2017-11-23, in TeX Live language at least
+version 45888 or `texlive.infra`.
A recent Java installation that includes JavaFX is required. If you see
errors like
@@ -49,7 +49,7 @@ Development is done in Scala with the ScalaFX widget set. The project is
mainly a challenge for myself to learn Scala. Building is done with *sbt*,
opening the project in IntelliJ works fine.
-Deplyment is done via [sbt assembly](https://github.com/sbt/sbt-assembly)
+Deployment is done via [sbt assembly](https://github.com/sbt/sbt-assembly)
which packs all the necessary libraries into one jar file.
Contributions are welcome, improvements are easily done as I just started
@@ -58,24 +58,37 @@ writing Scala and my style is horrible ;-)
Screenshots
-----------
-(permanently outdated)
+Starting up TLCockpit
+
+![Loading screen](screenshots/tlcockpit-loading.jpg)
Package listing
-![Package listing view](screenshots/pkgs-view.jpg)
+![Package listing view](screenshots/tlcockpit-packages.jpg)
+
+View packages by collections
+
+![Packages by collections](screenshots/tlcockpit-collections.jpg)
Updates listing
-![Updates listing view](screenshots/update-view.jpg)
+![Updates listing view](screenshots/tlcockpit-updates.jpg)
Backup/Restore listing
-![Backup listing view](screenshots/backup-view.jpg)
+![Backup listing view](screenshots/tlcockpit-backups.jpg)
Package details
-![Package details](screenshots/pkg-details.png)
+![Package details](screenshots/tlcockpit-pkgdetails.jpg)
+
+Configuration dialog
+
+![Config screen](screenshots/tlcockpit-config.jpg)
+
+Paper setting dialog
+![Config screen](screenshots/tlcockpit-paper.jpg)
License
-------
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg
deleted file mode 100644
index 552ec831b14..00000000000
--- a/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg
+++ /dev/null
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkg-details.png b/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkg-details.png
deleted file mode 100644
index 0b385b81169..00000000000
--- a/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkg-details.png
+++ /dev/null
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg
deleted file mode 100644
index ef0ca60ef4f..00000000000
--- a/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg
+++ /dev/null
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-backups.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-backups.jpg
new file mode 100644
index 00000000000..20dbdf413a9
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-backups.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-collections.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-collections.jpg
new file mode 100644
index 00000000000..af5a3240ee4
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-collections.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-config.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-config.jpg
new file mode 100644
index 00000000000..efd6b1bf909
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-config.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-loading.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-loading.jpg
new file mode 100644
index 00000000000..40711b3d672
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-loading.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-packages.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-packages.jpg
new file mode 100644
index 00000000000..d31476c4887
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-packages.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-paper.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-paper.jpg
new file mode 100644
index 00000000000..354c3d3560d
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-paper.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-pkgdetails.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-pkgdetails.jpg
new file mode 100644
index 00000000000..01aa4b93dd4
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-pkgdetails.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-updates.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-updates.jpg
new file mode 100644
index 00000000000..85052b11c63
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/tlcockpit-updates.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg
deleted file mode 100644
index 1d10628df52..00000000000
--- a/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg
+++ /dev/null
Binary files differ
diff --git a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar
index 0a57df3631f..ecfba0e9c1f 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/.gitignore b/Master/texmf-dist/source/support/tlcockpit/.gitignore
deleted file mode 100644
index d2d742875be..00000000000
--- a/Master/texmf-dist/source/support/tlcockpit/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-.idea/
-target/
-project/target/
diff --git a/Master/texmf-dist/source/support/tlcockpit/build.sbt b/Master/texmf-dist/source/support/tlcockpit/build.sbt
index fb5cf2f98d3..032d4016c04 100644
--- a/Master/texmf-dist/source/support/tlcockpit/build.sbt
+++ b/Master/texmf-dist/source/support/tlcockpit/build.sbt
@@ -1,13 +1,11 @@
name := "tlcockpit"
-version := "0.6"
+version := "0.7"
scalaVersion := "2.12.3"
libraryDependencies += "org.scalafx" % "scalafx_2.12" % "8.0.102-R11"
-
-// unmanagedJars in Compile += Attributed.blank(file(System.getenv("JAVA_HOME") + "/jre/lib/ext/jfxrt.jar"))
-// unmanagedJars in Compile += Attributed.blank(file("/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/jfxrt.jar"))
+libraryDependencies += "io.spray" %% "spray-json" % "1.3.3"
mainClass in assembly := Some("TLCockpit.ApplicationMain")
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 696744bbeb1..f37dc10806e 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,22 +6,21 @@
package TLCockpit
-import javafx.collections.ObservableList
-import javafx.scene.control
-
-import TLCockpit.ApplicationMain.getClass
+import TLCockpit.Utils._
import TeXLive._
-// import java.io.File
-import scalafx.beans.property.StringProperty
+import scala.collection.{immutable, mutable}
import scala.collection.mutable.ArrayBuffer
-import scala.concurrent.{Future, SyncVar}
+import scala.concurrent.{Future, Promise, SyncVar}
import scala.concurrent.ExecutionContext.Implicits.global
-// import scala.reflect.io
-// import scala.reflect.io.File
import scala.util.{Failure, Success}
-import scalafx.beans.property.ObjectProperty
-import scalafx.geometry.{HPos, Pos, VPos, Orientation}
+import scala.sys.process._
+import scalafx.beans.property.BooleanProperty
+import scalafx.scene.text.Font
+// ScalaFX imports
+import scalafx.event.Event
+import scalafx.beans.property.{ObjectProperty, StringProperty}
+import scalafx.geometry.{Pos, Orientation}
import scalafx.scene.Cursor
import scalafx.scene.control.Alert.AlertType
import scalafx.scene.image.{Image, ImageView}
@@ -33,8 +32,6 @@ import scalafx.scene.control.TreeTableColumn._
import scalafx.scene.control.TreeItem
import scalafx.scene.control.Menu._
import scalafx.scene.control.ListCell
-import scalafx.scene.control.cell._
-import scalafx.scene.text.Text
import scalafx.Includes._
import scalafx.application.{JFXApp, Platform}
import scalafx.application.JFXApp.PrimaryStage
@@ -43,21 +40,20 @@ import scalafx.scene.Scene
import scalafx.scene.layout._
import scalafx.scene.control._
import scalafx.event.ActionEvent
-import scala.sys.process._
import scalafx.collections.ObservableBuffer
import scalafx.collections.ObservableMap
-// import java.awt.Desktop
+// JSON support - important load TLPackageJsonProtocol later!
+import spray.json._
+import TeXLive.JsonProtocol._
-// TODO when installing a collection list the additionally installed packages, too
-// TODO idea pkg tree with collection -> packages
-// TODO pkg info listview adjust height according to font size and # of items
-// TODO TreeTableView indentation is lazy
object ApplicationMain extends JFXApp {
val version: String = getClass.getPackage.getImplementationVersion
+ var tlmgrBusy = BooleanProperty(false)
+
// necessary action when Window is closed with X or some other operation
override def stopApp(): Unit = {
tlmgr.cleanup()
@@ -66,26 +62,41 @@ object ApplicationMain extends JFXApp {
val iconImage = new Image(getClass.getResourceAsStream("tlcockpit-48.jpg"))
val logoImage = new Image(getClass.getResourceAsStream("tlcockpit-128.jpg"))
- val pkgs: ObservableMap[String, TLPackage] = ObservableMap[String, TLPackage]()
- val upds: ObservableMap[String, TLUpdate] = ObservableMap[String, TLUpdate]()
- val bkps: ObservableMap[String, Map[String, TLBackup]] = ObservableMap[String, Map[String,TLBackup]]() // pkgname -> (version -> TLBackup)*
+ val tlpkgs: ObservableMap[String, TLPackage] = ObservableMap[String,TLPackage]()
+ val pkgs: ObservableMap[String, TLPackageDisplay] = ObservableMap[String, TLPackageDisplay]()
+ val upds: ObservableMap[String, TLUpdateDisplay] = ObservableMap[String, TLUpdateDisplay]()
+ val bkps: ObservableMap[String, Map[String, TLBackupDisplay]] = ObservableMap[String, Map[String,TLBackupDisplay]]() // pkgname -> (version -> TLBackup)*
- val errorText: ObservableBuffer[String] = ObservableBuffer[String]()
+ val logText: ObservableBuffer[String] = ObservableBuffer[String]()
val outputText: ObservableBuffer[String] = ObservableBuffer[String]()
+ val errorText: ObservableBuffer[String] = ObservableBuffer[String]()
val outputfield: TextArea = new TextArea {
editable = false
wrapText = true
text = ""
}
+ val logfield: TextArea = new TextArea {
+ editable = false
+ wrapText = true
+ text = ""
+ }
val errorfield: TextArea = new TextArea {
editable = false
wrapText = true
text = ""
}
+ logText.onChange({
+ logfield.text = logText.mkString("\n")
+ logfield.scrollTop = Double.MaxValue
+ })
errorText.onChange({
errorfield.text = errorText.mkString("\n")
errorfield.scrollTop = Double.MaxValue
+ if (errorfield.text.value.nonEmpty) {
+ outerrpane.expanded = true
+ outerrtabs.selectionModel().select(2)
+ }
})
outputText.onChange({
outputfield.text = outputText.mkString("\n")
@@ -93,7 +104,11 @@ object ApplicationMain extends JFXApp {
})
val update_all_menu: MenuItem = new MenuItem("Update all") {
- onAction = (ae) => callback_update("--all")
+ val cmd = "--all" + {
+ if (disable_auto_install) " --no-auto-install" else "" } + {
+ if (disable_auto_removal) " --no-auto-remove" else "" } + {
+ if (enable_reinstall_forcible) " --reinstall-forcibly-removed" else "" }
+ onAction = (ae) => callback_update(cmd)
disable = true
}
val update_self_menu: MenuItem = new MenuItem("Update self") {
@@ -110,7 +125,12 @@ object ApplicationMain extends JFXApp {
content = outputfield
},
new Tab {
- text = "Error"
+ text = "Logging"
+ closable = false
+ content = logfield
+ },
+ new Tab {
+ text = "Errors"
closable = false
content = errorfield
}
@@ -128,26 +148,8 @@ object ApplicationMain extends JFXApp {
(ae: KeyEvent) => if (ae.code == KeyCode.Enter) callback_run_cmdline()
}
val outputLine = new SyncVar[String]
+ val errorLine = new SyncVar[String]
- def tlmgr_async_command(s: String, f: Array[String] => Unit): Unit = {
- 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)
- 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 callback_quit(): Unit = {
tlmgr.cleanup()
@@ -156,12 +158,12 @@ object ApplicationMain extends JFXApp {
}
def callback_run_text(s: String): Unit = {
- tlmgr_async_command(s, (s: Array[String]) => {})
+ tlmgr_send(s, (a: String, b: Array[String]) => {})
}
def callback_run_cmdline(): Unit = {
- tlmgr_async_command(cmdline.text.value, s => {
- outputText.appendAll(s)
+ tlmgr_send(cmdline.text.value, (status,output) => {
+ outputText.append(output.mkString("\n"))
outerrpane.expanded = true
outerrtabs.selectionModel().select(0)
})
@@ -199,7 +201,7 @@ object ApplicationMain extends JFXApp {
def callback_run_external(s: String, unbuffered: Boolean = true): Unit = {
outputText.clear()
- errorText.clear()
+ // logText.clear()
outerrpane.expanded = true
outerrtabs.selectionModel().select(0)
outputText.append(s"Running $s" + (if (unbuffered) " (unbuffered)" else " (buffered)"))
@@ -207,7 +209,7 @@ object ApplicationMain extends JFXApp {
s ! ProcessLogger(
line => if (unbuffered) Platform.runLater( outputText.append(line) )
else OutputBuffer.synchronized( OutputBuffer.append(line + "\n") ),
- line => Platform.runLater( errorText.append(line) )
+ line => Platform.runLater( logText.append(line) )
)
}
foo.onComplete {
@@ -227,7 +229,7 @@ object ApplicationMain extends JFXApp {
errorText.append("An ERROR has occurred running $s: " + t.getMessage)
errorfield.scrollTop = Double.MaxValue
outerrpane.expanded = true
- outerrtabs.selectionModel().select(1)
+ outerrtabs.selectionModel().select(2)
}
}
}
@@ -242,11 +244,10 @@ object ApplicationMain extends JFXApp {
}.showAndWait()
}
- def callback_update(s: String): Unit = {
- var prevUpdName = ""
- var prevUpdPkg = new TLUpdate("","","","","","")
- lineUpdateFunc = (l:String) => {
- // println("line update: " + l + "=")
+ def set_line_update_function(mode: String) = {
+ var prevName = ""
+ stdoutLineUpdateFunc = (l:String) => {
+ // println("DEBUG line update: " + l + "=")
l match {
case u if u.startsWith("location-url") => None
case u if u.startsWith("total-bytes") => None
@@ -255,84 +256,115 @@ object ApplicationMain extends JFXApp {
case u if u == "OK" => None
case u if u.startsWith("tlmgr>") => None
case u =>
- if (prevUpdName != "") {
- // println("Removing " + prevUpdName + " from list!")
- upds.remove(prevUpdName)
- val op = pkgs(prevUpdName)
- pkgs(prevUpdName) = new TLPackage(prevUpdPkg.name.value, op.rrev.value.toString, op.rrev.value.toString,
- prevUpdPkg.shortdesc.value, op.size.value.toString, "Installed")
- Platform.runLater {
+ if (prevName != "") {
+ if (mode == "update") {
+ // parallelism is a pain, I get concurrent access here, but don't know with whom?
+ // ConcurrentModificationExceptions often occur when you're modifying
+ // a collection while you are iterating over its elements.
+ // val newkids: ObservableBuffer[TreeItem[TLUpdateDisplay]] =
+ // updateTable.root.value.children.filter(_.value.value.name.value != prevName)
+ // .map(_.asInstanceOf[TreeItem[TLUpdateDisplay]])
+ // the last map is only necessary becasue ScalaFX is buggy and does not produce
+ // proper types here!!! Probably similar to https://github.com/scalafx/scalafx/issues/137
+ // updateTable.root.value.children = newkids
+ upds.remove(prevName)
trigger_update("upds")
- trigger_update("pkgs")
+ } else if (mode == "remove") {
+ tlpkgs(prevName).installed = false
+ } else { // install
+ tlpkgs(prevName).installed = true
}
+ if (mode == "remove") {
+ pkgs(prevName).lrev = ObjectProperty[Int](0)
+ pkgs(prevName).installed = StringProperty("Not installed")
+ tlpkgs(prevName).lrev = 0
+ } else { // install and update
+ pkgs(prevName).lrev = pkgs(prevName).rrev
+ pkgs(prevName).installed = StringProperty("Installed") // TODO support Mixed!!!
+ tlpkgs(prevName).lrev = tlpkgs(prevName).rrev
+ }
+ packageTable.refresh()
}
if (u.startsWith("end-of-updates")) {
// nothing to be done, all has been done above
- // println("got end of updates")
+ // println("DEBUG got end of updates")
} else {
- // println("getting update line")
- val foo = parse_one_update_line(l)
- val pkgname = foo.name.value
- val PkgTreeItemOption = updateTable.root.value.children.find(_.value.value.name.value == pkgname)
- PkgTreeItemOption match {
- case Some(p) =>
- prevUpdName = pkgname
- prevUpdPkg = p.getValue
- // println("Setting status to Updating ... for " + pkgname)
- prevUpdPkg.status = StringProperty("Updating ...")
- Platform.runLater {
- // println("calling updateTable refresh")
- updateTable.refresh()
- }
- case None => println("Very strange: Cannot find TreeItem for upd package" + pkgname)
+ // println("DEBUG getting update line")
+ prevName = if (mode == "update") {
+ val foo = parse_one_update_line(l)
+ val pkgname = foo.name.value
+ upds(pkgname).status = StringProperty("Updating ...")
+ updateTable.refresh()
+ pkgname
+ } else if (mode == "install") {
+ val fields = l.split("\t")
+ val pkgname = fields(0)
+ pkgs(pkgname).installed = StringProperty("Installing ...")
+ packageTable.refresh()
+ pkgname
+ } else { // remove
+ val fields = l.split("\t")
+ val pkgname = fields(0)
+ pkgs(pkgname).installed = StringProperty("Removing ...")
+ packageTable.refresh()
+ pkgname
}
}
}
}
- // val cmd = if (s == "--self") "update --self --no-restart" else s"update $s"
+ }
+
+ def callback_update(s: String): Unit = {
+ set_line_update_function("update")
val cmd = if (s == "--self") "update --self" else s"update $s"
- tlmgr_async_command(cmd, _ => {
- Platform.runLater {
- lineUpdateFunc = { (s: String) => }
- if (s == "--self") {
- reinitialize_tlmgr()
- // this doesn't work seemingly
- // update_upds_list()
- }
+ tlmgr_send(cmd, (a,b) => {
+ stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc
+ if (s == "--self") {
+ reinitialize_tlmgr()
+ // this doesn't work seemingly
+ // update_upds_list()
}
})
}
-
-
- def do_one_pkg(what: String, pkg: String): Unit = {
- tlmgr_async_command(s"$what $pkg", _ => { Platform.runLater { update_pkgs_lists() } })
+ def callback_remove(pkg: String): Unit = {
+ set_line_update_function("remove")
+ tlmgr_send(s"remove $pkg", (_, _) => { stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc })
}
+ def callback_install(pkg: String): Unit = {
+ set_line_update_function("install")
+ tlmgr_send(s"install $pkg", (_,_) => { stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc })
+ }
+
- def callback_restore_pkg(str: String, rev: String): Unit = {
- not_implemented_info()
+ def callback_restore(str: String, rev: String): Unit = {
+ tlmgr_send(s"restore --force $str $rev", (_,_) => {
+ tlpkgs(str).lrev = rev.toLong
+ pkgs(str).lrev = ObjectProperty[Int](rev.toInt)
+ packageTable.refresh()
+ })
}
bkps.onChange( (obs,chs) => {
- var doit = chs match {
+ val doit = chs match {
case ObservableMap.Add(k, v) => k.toString == "root"
case ObservableMap.Replace(k, va, vr) => k.toString == "root"
case ObservableMap.Remove(k, v) => k.toString == "root"
}
if (doit) {
// println("DEBUG bkps.onChange called new length = " + bkps.keys.toArray.length)
- val newroot = new TreeItem[TLBackup](new TLBackup("root", "", "")) {
+ val newroot = new TreeItem[TLBackupDisplay](new TLBackupDisplay("root", "", "")) {
children = bkps
.filter(_._1 != "root")
.map(p => {
val pkgname: String = p._1
// we sort by negative of revision number, which give inverse sort
- val versmap: Array[(String, TLBackup)] = p._2.toArray.sortBy(-_._2.rev.value.toInt)
+ val versmap: Array[(String, TLBackupDisplay)] = p._2.toArray.sortBy(-_._2.rev.value.toInt)
- val foo: Seq[TreeItem[TLBackup]] = versmap.tail.sortBy(-_._2.rev.value.toInt).map { q =>
- new TreeItem[TLBackup](q._2)
+ val foo: Seq[TreeItem[TLBackupDisplay]] = versmap.tail.sortBy(-_._2.rev.value.toInt).map { q =>
+ new TreeItem[TLBackupDisplay](q._2)
}.toSeq
- new TreeItem[TLBackup](versmap.head._2) {
+ new TreeItem[TLBackupDisplay](versmap.head._2) {
children = foo
}
}).toArray.sortBy(_.value.value.name.value)
@@ -342,63 +374,166 @@ object ApplicationMain extends JFXApp {
}
}
})
+
+ def view_pkgs_by_collections(pkgbuf: scala.collection.mutable.Map[String, TLPackageDisplay],
+ binbuf: scala.collection.mutable.Map[String, ArrayBuffer[TLPackageDisplay]],
+ colbuf: scala.collection.mutable.Map[String, ArrayBuffer[TLPackageDisplay]]): Seq[TreeItem[TLPackageDisplay]] = {
+ val bin_pkg_map = compute_bin_pkg_mapping(pkgbuf, binbuf)
+ colbuf.map(
+ p => {
+ val colname: String = p._1
+ val coldeps: Seq[TLPackageDisplay] = p._2
+ val coltlpd: TLPackageDisplay = pkgbuf(colname)
+
+ new TreeItem[TLPackageDisplay](coltlpd) {
+ children = coldeps.filter(q => tlpkgs(q.name.value).category != "Collection").sortBy(_.name.value).map(sub => {
+ val binmap: (Boolean, Seq[TLPackageDisplay]) = bin_pkg_map(sub.name.value)
+ val ismixed: Boolean = binmap._1
+ val kids: Seq[TLPackageDisplay] = binmap._2.sortBy(_.name.value)
+ val ti = if (ismixed) {
+ // replace installed status with "Mixed"
+ new TreeItem[TLPackageDisplay](
+ new TLPackageDisplay(sub.name.value, sub.lrev.value.toString, sub.rrev.value.toString, sub.shortdesc.value, sub.size.value.toString, "Mixed")
+ ) {
+ children = kids.map(new TreeItem[TLPackageDisplay](_))
+ }
+ } else {
+ new TreeItem[TLPackageDisplay](sub) {
+ children = kids.map(new TreeItem[TLPackageDisplay](_))
+ }
+ }
+ ti
+ }
+ )
+ }
+ }
+ ).toSeq
+ // ArrayBuffer.empty[TreeItem[TLPackageDisplay]]
+ }
+
+ def view_pkgs_by_names(pkgbuf: scala.collection.mutable.Map[String, TLPackageDisplay],
+ binbuf: scala.collection.mutable.Map[String, ArrayBuffer[TLPackageDisplay]]): Seq[TreeItem[TLPackageDisplay]] = {
+ val bin_pkg_map: Map[String, (Boolean, Seq[TLPackageDisplay])] = compute_bin_pkg_mapping(pkgbuf, binbuf)
+ pkgbuf.map{
+ p => {
+ val binmap: (Boolean, Seq[TLPackageDisplay]) = bin_pkg_map(p._1)
+ val pkgtlp: TLPackageDisplay = p._2
+ val ismixed: Boolean = binmap._1
+ val kids: Seq[TLPackageDisplay] = binmap._2.sortBy(_.name.value)
+ if (ismixed) {
+ new TreeItem[TLPackageDisplay](
+ new TLPackageDisplay(pkgtlp.name.value, pkgtlp.lrev.value.toString, pkgtlp.rrev.value.toString, pkgtlp.shortdesc.value, pkgtlp.size.value.toString, "Mixed")
+ ) {
+ children = kids.map(new TreeItem[TLPackageDisplay](_))
+ }
+ } else {
+ new TreeItem[TLPackageDisplay](pkgtlp) {
+ children = kids.map(new TreeItem[TLPackageDisplay](_))
+ }
+ }
+ }
+ }.toSeq
+ }
+
+ def compute_bin_pkg_mapping(pkgbuf: scala.collection.mutable.Map[String, TLPackageDisplay],
+ binbuf: scala.collection.mutable.Map[String, ArrayBuffer[TLPackageDisplay]]): Map[String, (Boolean, Seq[TLPackageDisplay])] = {
+ pkgbuf.map {
+ p => {
+ val kids: Seq[TLPackageDisplay] = if (binbuf.keySet.contains(p._2.name.value)) {
+ binbuf(p._2.name.value)
+ } else {
+ Seq()
+ }
+ // for ismixed we && all the installed status. If all are installed, we get true
+ val allinstalled = (kids :+ p._2).foldRight[Boolean](true)((k, b) => k.installed.value == "Installed" && b)
+ val someinstalled = (kids :+ p._2).exists(_.installed.value == "Installed")
+ val mixedinstalled = !allinstalled && someinstalled
+ (p._1, (mixedinstalled, kids))
+ }
+ }.toMap
+ }
pkgs.onChange( (obs,chs) => {
- var doit = chs match {
+ val doit = chs match {
case ObservableMap.Add(k, v) => k.toString == "root"
case ObservableMap.Replace(k, va, vr) => k.toString == "root"
- case ObservableMap.Remove(k, v) => k.toString == "root"
+ // don't call the trigger on root removal!
+ // case ObservableMap.Remove(k, v) => k.toString == "root"
+ case ObservableMap.Remove(k,v) => false
}
if (doit) {
- val pkgbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage]
- val binbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackage]]
+ // println("DEBUG: entering pkgs.onChange")
+ // val pkgbuf: ArrayBuffer[TLPackageDisplay] = ArrayBuffer.empty[TLPackageDisplay]
+ val pkgbuf = scala.collection.mutable.Map.empty[String, TLPackageDisplay]
+ val binbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackageDisplay]]
+ val colbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackageDisplay]]
pkgs.foreach(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._1.stripPrefix("texlive.").contains(".")) {
- val foo: Array[String] = pkg._1.stripPrefix("texlive.infra").split('.')
+ if ((pkg._1.startsWith("texlive.infra") && pkg._1.stripPrefix("texlive.infra").contains(".")) ||
+ pkg._1.stripPrefix("texlive.infra").contains(".")) {
+ val foo: Array[String] = if (pkg._1.startsWith("texlive.infra"))
+ Array("texlive.infra", pkg._1.stripPrefix("texlive.infra"))
+ else
+ pkg._1.split('.')
val pkgname = foo(0)
- val binname = foo(1)
- if (binbuf.keySet.contains(pkgname)) {
- binbuf(pkgname) += pkg._2
- } else {
- binbuf(pkgname) = ArrayBuffer[TLPackage](pkg._2)
+ if (pkgname != "") {
+ val binname = foo(1)
+ if (binbuf.keySet.contains(pkgname)) {
+ binbuf(pkgname) += pkg._2
+ } else {
+ binbuf(pkgname) = ArrayBuffer[TLPackageDisplay](pkg._2)
+ }
}
} else if (pkg._1 == "root") {
// ignore the dummy root element,
// only used for speeding up event handling
} else {
- pkgbuf += pkg._2
+ pkgbuf(pkg._1) = pkg._2
}
})
- // now we have all normal packages in pkgbuf, and its sub-packages in binbuf
- // we need to create TreeItems
- val viewkids: ArrayBuffer[TreeItem[TLPackage]] = pkgbuf.map { p => {
- val kids: Seq[TLPackage] = if (binbuf.keySet.contains(p.name.value)) {
- binbuf(p.name.value)
- } else {
- Seq()
+ // Another round to propagate purely .win32 packages like wintools.win32 or
+ // dviout.win32 from binpkg status to full pkg, since they don't have
+ // accompanying main packages
+ binbuf.foreach(p => {
+ if (!pkgbuf.contains(p._1)) {
+ if (p._2.length > 1) {
+ errorText += "THAT SHOULD NOT HAPPEN: >>" + p._1 + "<< >>" + p._2.length + "<<"
+ // p._2.foreach(f => println("-> " + f.name.value))
+ } else {
+ // println("DEBUG Moving " + p._2.head.name.value + " up to pkgbuf " + p._1)
+ pkgbuf(p._2.head.name.value) = p._2.head
+ // TODO will this work out with the foreach loop above???
+ binbuf -= p._1
+ }
}
- // 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)
- val someinstalled = (kids :+ p).exists(_.installed.value == "Installed")
- val mixedinstalled = !allinstalled && someinstalled
- if (mixedinstalled) {
- // 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](_))
+ })
+ // another loop to collection and fill the collections buffer
+ pkgs.foreach(pkg => {
+ if (tlpkgs.contains(pkg._1)) {
+ if (tlpkgs(pkg._1).category == "Collection") {
+ val foo: immutable.Seq[String] = tlpkgs(pkg._1).depends
+ colbuf(pkg._1) = ArrayBuffer[TLPackageDisplay]()
+ // TODO we need to deal with packages that get removed!!!
+ // for now just make sure we don't add them here!
+ colbuf(pkg._1) ++= foo.filter(pkgbuf.contains(_)).map(pkgbuf(_))
}
+ } else if (pkg._1 == "root") {
+ // do nothing
} else {
- new TreeItem[TLPackage](p) {
- children = kids.map(new TreeItem[TLPackage](_))
- }
+ errorText += "Cannot find information for " + pkg._1
}
- }
- }
+ })
+ // now we have all normal packages in pkgbuf, and its sub-packages in binbuf
+ // we need to create TreeItems
+ val viewkids: Seq[TreeItem[TLPackageDisplay]] =
+ if (ViewByPkg.selected.value)
+ view_pkgs_by_names(pkgbuf, binbuf)
+ else
+ view_pkgs_by_collections(pkgbuf, binbuf, colbuf)
+ // println("DEBUG: leaving pkgs.onChange before runLater")
Platform.runLater {
- packageTable.root = new TreeItem[TLPackage](new TLPackage("root", "0", "0", "", "0", "")) {
+ packageTable.root = new TreeItem[TLPackageDisplay](new TLPackageDisplay("root", "0", "0", "", "0", "")) {
expanded = true
children = viewkids.sortBy(_.value.value.name.value)
}
@@ -415,10 +550,10 @@ object ApplicationMain extends JFXApp {
val infraAvailable = upds.keys.exists(_.startsWith("texlive.infra"))
// only allow for updates of other packages when no infra update available
val updatesAvailable = !infraAvailable && upds.keys.exists(p => !p.startsWith("texlive.infra") && !(p == "root"))
- val newroot = new TreeItem[TLUpdate](new TLUpdate("root", "", "", "", "", "")) {
+ val newroot = new TreeItem[TLUpdateDisplay](new TLUpdateDisplay("root", "", "", "", "", "")) {
children = upds
.filter(_._1 != "root")
- .map(p => new TreeItem[TLUpdate](p._2))
+ .map(p => new TreeItem[TLUpdateDisplay](p._2))
.toArray
.sortBy(_.value.value.name.value)
}
@@ -442,37 +577,65 @@ object ApplicationMain extends JFXApp {
}.showAndWait()
}
- def update_bkps_list(): Unit = {
- tlmgr_async_command("restore", lines => {
- // lines.drop(1).foreach(println(_))
- val newbkps: Map[String, Map[String, TLBackup]] = lines.drop(1).map { (l: String) =>
- val fields = l.split("[ ():]", -1).filter(_.nonEmpty)
- val pkgname = fields(0)
- val rests: Array[Array[String]] = fields.drop(1).sliding(4, 4).toArray
- (pkgname, rests.map({ p => (p(1), new TLBackup(pkgname, p(0), p(1) + " " + p(2) + ":" + p(3)))}).toMap)
- }.toMap
+ def load_backups_update_bkps_view(): Unit = {
+ val tmp = new Label("Loading backups, please wait ...")
+ tmp.wrapText = true
+ tmp.opacity = 0.4f
+ tmp.font = new Font(30f)
+ val prevph = backupTable.placeholder.value
+ backupTable.placeholder = tmp
+ tlmgr_send("restore --json", (status, lines) => {
+ val jsonAst = lines.mkString("").parseJson
+ val backups: Map[String, Map[String, TLBackupDisplay]] =
+ jsonAst
+ .convertTo[List[TLBackup]]
+ .groupBy[String](_.name)
+ .map(p => (p._1, p._2.map(q => (q.rev, new TLBackupDisplay(q.name, q.rev, q.date))).toMap))
bkps.clear()
- bkps ++= newbkps
+ bkps ++= backups
trigger_update("bkps")
+ backupTable.placeholder = prevph
})
}
- def update_pkgs_lists():Unit = {
- tlmgr_async_command("info --data name,localrev,remoterev,shortdesc,size,installed", (s: Array[String]) => {
- val newpkgs = 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"
- (fields(0), new TLPackage(fields(0), fields(1), fields(2), shortdesc, fields(4), inst))
- }.toMap
- pkgs.clear()
- pkgs ++= newpkgs
- trigger_update("pkgs")
- })
+
+ def update_pkgs_view(): Unit = {
+ val newpkgs: Map[String, TLPackageDisplay] =
+ tlpkgs
+ .filter { p =>
+ val searchTerm = searchEntry.text.value.toLowerCase
+ p._1.toLowerCase.contains(searchTerm) ||
+ p._2.shortdesc.getOrElse("").toLowerCase.contains(searchTerm)
+ }
+ .map { p =>
+ (p._2.name,
+ new TLPackageDisplay(
+ p._2.name, p._2.lrev.toString, p._2.rrev.toString,
+ p._2.shortdesc.getOrElse(""), "0", if (p._2.installed) "Installed" else "Not installed"
+ )
+ )
+ }.toMap
+ pkgs.clear()
+ pkgs ++= newpkgs
+ trigger_update("pkgs")
}
+ def load_tlpdb_update_pkgs_view():Unit = {
+ val tmp = new Label("Loading database, please wait ...")
+ tmp.wrapText = true
+ tmp.opacity = 0.4f
+ tmp.font = new Font(30f)
+ val prevph = packageTable.placeholder.value
+ packageTable.placeholder = tmp
+ tlmgr_send("info --json", (status, lines) => {
+ val jsonAst = lines.mkString("").parseJson
+ tlpkgs.clear()
+ tlpkgs ++= jsonAst.convertTo[List[TLPackage]].map { p => (p.name, p)}
+ update_pkgs_view()
+ packageTable.placeholder = prevph
+ })
+ }
- def parse_one_update_line(l: String): TLUpdate = {
+ def parse_one_update_line(l: String): TLUpdateDisplay = {
val fields = l.split("\t")
val pkgname = fields(0)
val status = fields(1) match {
@@ -486,15 +649,15 @@ object ApplicationMain extends JFXApp {
}
val localrev = fields(2)
val serverrev = fields(3)
- val size = humanReadableByteSize(fields(4).toLong)
+ val size = if (fields(1) == "d") "0" else humanReadableByteSize(fields(4).toLong)
val runtime = fields(5)
val esttot = fields(6)
val tag = fields(7)
val lctanv = fields(8)
val rctanv = fields(9)
- val tlpkg: TLPackage = pkgs(pkgname)
+ val tlpkg: TLPackageDisplay = pkgs(pkgname)
val shortdesc = tlpkg.shortdesc.value
- new TLUpdate(pkgname, status,
+ new TLUpdateDisplay(pkgname, status,
localrev + {
if (lctanv != "-") s" ($lctanv)" else ""
},
@@ -504,10 +667,17 @@ object ApplicationMain extends JFXApp {
shortdesc, size)
}
- def update_upds_list(): Unit = {
- tlmgr_async_command("update --list", lines => {
- // val newupds = scala.collection.mutable.Map.empty[String,TLUpdate]
- val newupds: Map[String, TLUpdate] = lines.filter { l =>
+ def load_updates_update_upds_view(): Unit = {
+ val tmp = new Label("Loading updates, please wait ...")
+ tmp.wrapText = true
+ tmp.opacity = 0.4f
+ tmp.font = new Font(30f)
+ val prevph = updateTable.placeholder.value
+ updateTable.placeholder = tmp
+ tlmgr_send("update --list", (status, lines) => {
+ // println(s"DEBUG got updates length ${lines.length}")
+ // println(s"DEBUG tlmgr last output = ${lines}")
+ val newupds: Map[String, TLUpdateDisplay] = lines.filter { l =>
l match {
case u if u.startsWith("location-url") => false
case u if u.startsWith("total-bytes") => false
@@ -527,17 +697,19 @@ object ApplicationMain extends JFXApp {
upds ++= newupds
}
trigger_update("upds")
+ updateTable.placeholder = prevph
})
}
def trigger_update(s:String): Unit = {
- // println("Triggering update of " + s)
- if (s == "pkgs")
- pkgs("root") = new TLPackage("root","0","0","","0","")
- else if (s == "upds")
- upds("root") = new TLUpdate("root", "", "", "", "", "")
- else if (s == "bkps")
- bkps("root") = Map[String,TLBackup](("0", new TLBackup("root","0","0")))
+ // println("DEBUG: Triggering update of " + s)
+ if (s == "pkgs") {
+ pkgs("root") = new TLPackageDisplay("root", "0", "0", "", "0", "")
+ } else if (s == "upds") {
+ upds("root") = new TLUpdateDisplay("root", "", "", "", "", "")
+ } else if (s == "bkps") {
+ bkps("root") = Map[String, TLBackupDisplay](("0", new TLBackupDisplay("root", "0", "0")))
+ }
}
def doListView(files: Seq[String], clickable: Boolean): scalafx.scene.Node = {
@@ -560,7 +732,6 @@ object ApplicationMain extends JFXApp {
vb.prefHeight = 150
vb.maxHeight = 200
vb.vgrow = Priority.Always
- // TODO tighter spacing for ListView
vb.orientation = Orientation.Vertical
vb.cellFactory = { p => {
val foo = new ListCell[String]
@@ -582,210 +753,18 @@ object ApplicationMain extends JFXApp {
}
}
- def callback_show_pkg_info(pkg: String): Unit = {
- tlmgr_async_command(s"info --list $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 isInstalled = pkgs(pkg).installed.value == "Installed"
- val grid = new GridPane() {
- hgap = 10
- vgap = 10
- padding = Insets(20)
- }
- var crow = 0
- val runFiles = ArrayBuffer[String]()
- val docFiles = ArrayBuffer[String]()
- val binFiles = ArrayBuffer[String]()
- val srcFiles = ArrayBuffer[String]()
- var whichFiles = "run" // we assume run files if not found!
- pkginfo.foreach((line: String) => {
- if (line.startsWith(" ")) {
- // files are listed with two spaces in the beginning
- whichFiles match {
- case "run" => runFiles += line.trim
- case "doc" => docFiles += line.trim
- case "bin" => binFiles += line.trim
- case "src" => srcFiles += line.trim
- case _ => println("Don't know what to do?!?!")
- }
- } else {
- val keyval = line.split(":", 2).map(_.trim)
- whichFiles = if (keyval(0).startsWith("run files")) "run" else
- if (keyval(0).startsWith("doc files")) "doc" else
- if (keyval(0).startsWith("source files")) "src" else
- if (keyval(0).startsWith("bin files (all platforms)")) "bin" else
- "unknown"
- if (keyval.length == 2) {
- if (keyval(0) == "doc files" || keyval(0) == "source files" || keyval(0) == "run files" ||
- keyval(0).startsWith("depending package ") ||
- keyval(0).startsWith("bin files (all platforms)") || keyval(0).startsWith("Included files, by type")) {
- // do nothing
- } else {
- 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
- }
- }
- }
- })
- // add files section
- if (docFiles.nonEmpty) {
- grid.add(new Label("doc files"), 0, crow)
- //grid.add(new Label(docFiles.mkString("\n")) { wrapText = true },1, crow)
- grid.add(doListView(docFiles.map(s => s.replaceFirst("RELOC","texmf-dist")),isInstalled),1,crow)
- crow += 1
- }
- if (runFiles.nonEmpty) {
- grid.add(new Label("run files"), 0, crow)
- // grid.add(new Label(runFiles.mkString("\n")) { wrapText = true },1, crow)
- grid.add(doListView(runFiles.map(s => s.replaceFirst("RELOC","texmf-dist")),false),1,crow)
- crow += 1
- }
- if (srcFiles.nonEmpty) {
- grid.add(new Label("src files"), 0, crow)
- // grid.add(new Label(srcFiles.mkString("\n")) { wrapText = true },1, crow)
- grid.add(doListView(srcFiles.map(s => s.replaceFirst("RELOC","texmf-dist")),false),1,crow)
- crow += 1
- }
- if (binFiles.nonEmpty) {
- grid.add(new Label("bin files"), 0, crow)
- // grid.add(new Label(binFiles.mkString("\n")) { wrapText = true },1, crow)
- grid.add(doListView(binFiles.map(s => s.replaceFirst("RELOC","texmf-dist")),false),1,crow)
- crow += 1
- }
- grid.columnConstraints = Seq(new ColumnConstraints(100, 200, 200), new ColumnConstraints(100, 400, 5000, Priority.Always, new HPos(HPos.Left), true))
- dialog.dialogPane().content = grid
- dialog.width = 600
- dialog.height = 1500
- dialog.showAndWait()
- }
- })
- }
- def callback_show_pkg_info_dev(pkg: String): Unit = {
- tlmgr_async_command(s"info --list $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 keyCol = new TreeTableColumn[(String,String), String] {
- text = "Package"
- cellValueFactory = { p => StringProperty(p.value.value.value._1) }
- }
- val valCol = new TreeTableColumn[(String,String), String] {
- text = pkg
- cellValueFactory = { p => StringProperty(p.value.value.value._2) }
- }
- val infoTable = new TreeTableView[(String, String)](
- new TreeItem[(String, String)](("","")) {
- expanded = false
- }) {
- columns ++= List(keyCol, valCol)
- }
- valCol.prefWidth.bind(infoTable.width - keyCol.width - 15)
- infoTable.prefHeight = 300
- infoTable.vgrow = Priority.Always
- infoTable.showRoot = false
- infoTable.fixedCellSize = Region.USE_COMPUTED_SIZE
- val runFiles = ArrayBuffer[String]()
- val docFiles = ArrayBuffer[String]()
- val binFiles = ArrayBuffer[String]()
- val srcFiles = ArrayBuffer[String]()
- val kids = ArrayBuffer[TreeItem[(String,String)]]()
- var whichFiles = "run" // we assume run files if not found!
- pkginfo.foreach((line: String) => {
- if (line.startsWith(" ")) {
- // files are listed with two spaces in the beginning
- whichFiles match {
- case "run" => runFiles += line.trim
- case "doc" => docFiles += line.trim
- case "bin" => binFiles += line.trim
- case "src" => srcFiles += line.trim
- case _ => println("Don't know what to do?!?!")
- }
- } else {
- val keyval = line.split(":", 2).map(_.trim)
- whichFiles = if (keyval(0).startsWith("run files")) "run" else
- if (keyval(0).startsWith("doc files")) "doc" else
- if (keyval(0).startsWith("source files")) "src" else
- if (keyval(0).startsWith("bin files (all platforms)")) "bin" else
- "unknown"
- if (keyval.length == 2) {
- if (keyval(0) == "doc files" || keyval(0) == "source files" || keyval(0) == "run files" ||
- keyval(0).startsWith("bin files (all platforms)")) {
- // do nothing
- } else {
- kids += new TreeItem[(String,String)]((keyval(0), keyval(1)))
- }
- }
- }
- })
- // add files section
- if (docFiles.nonEmpty) {
- kids += new TreeItem[(String,String)](("Doc files","")) {
- children = docFiles.map { p => new TreeItem[(String,String)](("",p)) }
- }
- }
- if (runFiles.nonEmpty) {
- kids += new TreeItem[(String,String)](("Run files","")) {
- children = runFiles.map { p => new TreeItem[(String,String)](("",p)) }
- }
- }
- if (srcFiles.nonEmpty) {
- kids += new TreeItem[(String,String)](("Source files","")) {
- children = srcFiles.map { p => new TreeItem[(String,String)](("",p)) }
- }
- }
- if (binFiles.nonEmpty) {
- kids += new TreeItem[(String,String)](("Bin files","")) {
- children = binFiles.map { p => new TreeItem[(String,String)](("",p)) }
- }
- }
- infoTable.root = new TreeItem[(String, String)](("","")) {
- expanded = true
- children = kids
- }
- dialog.dialogPane().content = infoTable
- // dialog.width = 500
- // dialog.height = 800
- dialog.showAndWait()
- }
- })
- }
-
- /**
- * @see https://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc
- * @see https://en.wikipedia.org/wiki/Zettabyte
- * @param fileSize Up to Exabytes
- * @return
- */
- def humanReadableByteSize(fileSize: Long): String = {
- if(fileSize <= 0) return "0 B"
- // kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta
- val units: Array[String] = Array("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
- val digitGroup: Int = (Math.log10(fileSize)/Math.log10(1024)).toInt
- f"${fileSize/Math.pow(1024, digitGroup)}%3.1f ${units(digitGroup)}"
- }
-
val mainMenu: Menu = new Menu("TLCockpit") {
items = List(
- update_all_menu,
- update_self_menu,
- new SeparatorMenuItem,
+ // temporarily move here as we disable the Help menu
+ new MenuItem("About") {
+ onAction = (ae) => callback_about()
+ },
+ new MenuItem("Exit") {
+ onAction = (ae: ActionEvent) => callback_quit()
+ })
+ }
+ val toolsMenu: Menu = new Menu("Tools") {
+ items = List(
new MenuItem("Update filename databases ...") {
onAction = (ae) => {
callback_run_external("mktexlsr")
@@ -797,49 +776,96 @@ object ApplicationMain extends JFXApp {
new MenuItem("Rebuild all formats ...") { onAction = (ae) => callback_run_external("fmtutil --sys --all", false) },
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()
- },
- */
+ }
+ )
+ }
+ val ViewByPkg = new RadioMenuItem("by package name") {
+ onAction = (ae) => {
+ searchEntry.text = ""
+ update_pkgs_view()
+ }
+ }
+ val ViewByCol = new RadioMenuItem("by collections") {
+ onAction = (ae) => {
+ searchEntry.text = ""
+ update_pkgs_view()
+ }
+ }
+ ViewByPkg.selected = true
+ ViewByCol.selected = false
+ val pkgsMenu: Menu = new Menu("Packages") {
+ val foo = new ToggleGroup
+ foo.toggles = Seq(ViewByPkg, ViewByCol)
+ items = List(ViewByPkg, ViewByCol)
+ }
+ var disable_auto_removal = false
+ var disable_auto_install = false
+ var enable_reinstall_forcible = false
+ val updMenu: Menu = new Menu("Updates") {
+ items = List(
+ update_all_menu,
+ update_self_menu,
new SeparatorMenuItem,
- // temporarily move here as we disable the Help menu
- new MenuItem("About") {
- onAction = (ae) => callback_about()
- },
- new MenuItem("Exit") {
- onAction = (ae: ActionEvent) => callback_quit()
- })
+ new CheckMenuItem("Disable auto removal") { onAction = (ae) => disable_auto_removal = selected.value },
+ new CheckMenuItem("Disable auto install") { onAction = (ae) => disable_auto_install = selected.value },
+ new CheckMenuItem("Reinstall forcibly removed") { onAction = (ae) => enable_reinstall_forcible = selected.value }
+ )
+ }
+
+
+ def callback_general_options(): Unit = {
+ tlmgr_send("option showall --json", (status, lines) => {
+ val jsonAst = lines.mkString("").parseJson
+ val tlpdopts: List[TLOption] = jsonAst.convertTo[List[TLOption]]
+ Platform.runLater {
+ val dg = new OptionsDialog(tlpdopts)
+ dg.showAndWait() match {
+ case Some(changedOpts) =>
+ changedOpts.foreach(p => {
+ // don't believe it or not, but \" does *NOT* work in Scala in
+ // interpolated strings, and it seems there is no better way
+ // than that one ...
+ tlmgr_send(s"option ${p._1} ${'"'}${p._2}${'"'}", (_,_) => None)
+ })
+ case None =>
+ }
+ }
+ })
}
+
+
+ def callback_paper(): Unit = {
+ tlmgr_send("paper --json", (status, lines) => {
+ val jsonAst = lines.mkString("").parseJson
+ val paperconfs: Map[String, TLPaperConf] = jsonAst.convertTo[List[TLPaperConf]].map { p => (p.program, p) }.toMap
+ val currentPapers: Map[String, String] = paperconfs.mapValues(p => p.options.head)
+ Platform.runLater {
+ val dg = new PaperDialog(paperconfs)
+ dg.showAndWait() match {
+ case Some(newPapers) =>
+ // println(s"Got result ${newPapers}")
+ // collect changed settings
+ val changedPapers = newPapers.filter(p => currentPapers(p._1) != p._2)
+ // println(s"Got changed papers ${changedPapers}")
+ changedPapers.foreach(p => {
+ tlmgr_send(s"paper ${p._1} paper ${p._2}", (_,_) => None)
+ })
+ case None =>
+ }
+ }
+ })
+ }
+
val optionsMenu: Menu = 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() },
+ items = List(
+ new MenuItem("General ...") { onAction = (ae) => callback_general_options() },
+ new MenuItem("Paper ...") { onAction = (ae) => callback_paper() },
+ /* 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 }
- )
- }
- val helpMenu: Menu = new Menu("Help") {
- items = List(
- /*
- new MenuItem("Manual") {
- disable = true; onAction = (ae) => not_implemented_info()
- },
- */
- new MenuItem("About") {
- onAction = (ae) => callback_about()
- },
+ new CheckMenuItem("Disable auto-removal of server-deleted packages") { disable = true } */
)
}
val statusMenu: Menu = new Menu("Status: Idle") {
@@ -867,39 +893,39 @@ object ApplicationMain extends JFXApp {
)
}
}
- val updateTable: TreeTableView[TLUpdate] = {
- val colName = new TreeTableColumn[TLUpdate, String] {
+ val updateTable: TreeTableView[TLUpdateDisplay] = {
+ val colName = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Package"
cellValueFactory = { _.value.value.value.name }
prefWidth = 150
}
- val colStatus = new TreeTableColumn[TLUpdate, String] {
+ val colStatus = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Status"
cellValueFactory = { _.value.value.value.status }
prefWidth = 120
}
- val colDesc = new TreeTableColumn[TLUpdate, String] {
+ val colDesc = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Description"
cellValueFactory = { _.value.value.value.shortdesc }
prefWidth = 300
}
- val colLRev = new TreeTableColumn[TLUpdate, String] {
+ val colLRev = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Local rev"
cellValueFactory = { _.value.value.value.lrev }
prefWidth = 100
}
- val colRRev = new TreeTableColumn[TLUpdate, String] {
+ val colRRev = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Remote rev"
cellValueFactory = { _.value.value.value.rrev }
prefWidth = 100
}
- val colSize = new TreeTableColumn[TLUpdate, String] {
+ val colSize = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Size"
cellValueFactory = { _.value.value.value.size }
prefWidth = 70
}
- val table = new TreeTableView[TLUpdate](
- new TreeItem[TLUpdate](new TLUpdate("root","","","","","")) {
+ val table = new TreeTableView[TLUpdateDisplay](
+ new TreeItem[TLUpdateDisplay](new TLUpdateDisplay("root","","","","","")) {
expanded = false
}) {
columns ++= List(colName, colStatus, colDesc, colLRev, colRRev, colSize)
@@ -910,47 +936,60 @@ object ApplicationMain extends JFXApp {
table.placeholder = new Label("No updates available")
table.showRoot = false
table.rowFactory = { _ =>
- val row = new TreeTableRow[TLUpdate] {}
- 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) => callback_update(row.item.value.name.value)
+ val row = new TreeTableRow[TLUpdateDisplay] {}
+ val infoMI = new MenuItem("Info") {
+ onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait()
+ }
+ val updateMI = new MenuItem("Update") {
+ onAction = (ae) => callback_update(row.item.value.name.value)
+ }
+ val installMI = new MenuItem("Install") {
+ onAction = (ae) => callback_install(row.item.value.name.value)
+ }
+ val removeMI = new MenuItem("Remove") {
+ onAction = (ae) => callback_remove(row.item.value.name.value)
+ }
+ val ctm = new ContextMenu(infoMI, updateMI, installMI, removeMI)
+ row.item.onChange { (_,_,newTL) =>
+ if (newTL != null) {
+ if (newTL.status.value == "New on server") {
+ installMI.disable = false
+ removeMI.disable = true
+ updateMI.disable = true
+ } else if (newTL.status.value == "Removed on server") {
+ installMI.disable = true
+ removeMI.disable = false
+ updateMI.disable = true
+ } else {
+ installMI.disable = true
+ removeMI.disable = false
+ updateMI.disable = false
+ }
}
- )
+ }
row.contextMenu = ctm
row
}
table
}
- val packageTable: TreeTableView[TLPackage] = {
- val colName = new TreeTableColumn[TLPackage, String] {
+ val packageTable: TreeTableView[TLPackageDisplay] = {
+ val colName = new TreeTableColumn[TLPackageDisplay, String] {
text = "Package"
cellValueFactory = { _.value.value.value.name }
prefWidth = 150
}
- val colDesc = new TreeTableColumn[TLPackage, String] {
+ val colDesc = new TreeTableColumn[TLPackageDisplay, String] {
text = "Description"
cellValueFactory = { _.value.value.value.shortdesc }
prefWidth = 300
}
- val colInst = new TreeTableColumn[TLPackage, String] {
+ val colInst = new TreeTableColumn[TLPackageDisplay, String] {
text = "Installed"
cellValueFactory = { _.value.value.value.installed }
prefWidth = 100
}
- val table = new TreeTableView[TLPackage](
- new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) {
+ val table = new TreeTableView[TLPackageDisplay](
+ new TreeItem[TLPackageDisplay](new TLPackageDisplay("root","0","0","","0","")) {
expanded = false
}) {
columns ++= List(colName, colDesc, colInst)
@@ -959,42 +998,48 @@ object ApplicationMain extends JFXApp {
table.prefHeight = 300
table.showRoot = false
table.vgrow = Priority.Always
- table.rowFactory = { _ =>
- 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) => do_one_pkg("install", row.item.value.name.value)
- },
- new MenuItem("Remove") {
- onAction = (ae) => do_one_pkg("remove", row.item.value.name.value)
+ table.rowFactory = { p =>
+ val row = new TreeTableRow[TLPackageDisplay] {}
+ val infoMI = new MenuItem("Info") {
+ onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait()
+ }
+ val installMI = new MenuItem("Install") {
+ onAction = (ae) => callback_install(row.item.value.name.value)
+ }
+ val removeMI = new MenuItem("Remove") {
+ onAction = (ae) => callback_remove(row.item.value.name.value)
+ }
+ val ctm = new ContextMenu(infoMI, installMI, removeMI)
+ row.item.onChange { (_,_,newTL) =>
+ if (newTL != null) {
+ val is_installed: Boolean = !(newTL.installed.value == "Not installed")
+ installMI.disable = is_installed
+ removeMI.disable = !is_installed
}
- )
+ }
row.contextMenu = ctm
row
}
table
}
- val backupTable: TreeTableView[TLBackup] = {
- val colName = new TreeTableColumn[TLBackup, String] {
+ val backupTable: TreeTableView[TLBackupDisplay] = {
+ val colName = new TreeTableColumn[TLBackupDisplay, String] {
text = "Package"
cellValueFactory = { _.value.value.value.name }
prefWidth = 150
}
- val colRev = new TreeTableColumn[TLBackup, String] {
+ val colRev = new TreeTableColumn[TLBackupDisplay, String] {
text = "Revision"
cellValueFactory = { _.value.value.value.rev }
prefWidth = 100
}
- val colDate = new TreeTableColumn[TLBackup, String] {
+ val colDate = new TreeTableColumn[TLBackupDisplay, String] {
text = "Date"
cellValueFactory = { _.value.value.value.date }
prefWidth = 300
}
- val table = new TreeTableView[TLBackup](
- new TreeItem[TLBackup](new TLBackup("root","","")) {
+ val table = new TreeTableView[TLBackupDisplay](
+ new TreeItem[TLBackupDisplay](new TLBackupDisplay("root","","")) {
expanded = false
}) {
columns ++= List(colName, colRev, colDate)
@@ -1004,13 +1049,13 @@ object ApplicationMain extends JFXApp {
table.showRoot = false
table.vgrow = Priority.Always
table.rowFactory = { _ =>
- val row = new TreeTableRow[TLBackup] {}
+ val row = new TreeTableRow[TLBackupDisplay] {}
val ctm = new ContextMenu(
new MenuItem("Info") {
- onAction = (ae) => callback_show_pkg_info(row.item.value.name.value)
+ onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait()
},
new MenuItem("Restore") {
- onAction = (ae) => callback_restore_pkg(row.item.value.name.value, row.item.value.rev.value)
+ onAction = (ae) => callback_restore(row.item.value.name.value, row.item.value.rev.value)
}
)
row.contextMenu = ctm
@@ -1018,6 +1063,36 @@ object ApplicationMain extends JFXApp {
}
table
}
+ val searchEntry = new TextField()
+ searchEntry.hgrow = Priority.Sometimes
+ searchEntry.onKeyPressed = {
+ (ae: KeyEvent) => if (ae.code == KeyCode.Enter) update_pkgs_view()
+ }
+ val searchBox = new HBox {
+ children = Seq(
+ new Label("Search:") {
+ vgrow = Priority.Always
+ alignmentInParent = Pos.CenterLeft
+ },
+ searchEntry,
+ new Button("Go") {
+ onAction = _ => update_pkgs_view()
+ },
+ new Button("Reset") {
+ onAction = _ => {
+ searchEntry.text = ""
+ update_pkgs_view()
+ }
+ }
+ )
+ alignment = Pos.Center
+ padding = Insets(10)
+ spacing = 10
+ }
+ val pkgsContainer = new VBox {
+ children = Seq(packageTable,searchBox)
+ }
+
val pkgstabs: TabPane = new TabPane {
minWidth = 400
vgrow = Priority.Always
@@ -1025,7 +1100,7 @@ object ApplicationMain extends JFXApp {
new Tab {
text = "Packages"
closable = false
- content = packageTable
+ content = pkgsContainer
},
new Tab {
text = "Updates"
@@ -1039,23 +1114,31 @@ object ApplicationMain extends JFXApp {
}
)
}
+ // val spacerMenu: Menu = new Menu(" ")
+ // spacerMenu.disable = true
+ // spacerMenu.hgrow = Priority.Always
+ val menuBar: MenuBar = new MenuBar {
+ useSystemMenuBar = true
+ // menus.addAll(mainMenu, optionsMenu, helpMenu)
+ menus.addAll(mainMenu, pkgsMenu, toolsMenu, optionsMenu, statusMenu)
+ }
pkgstabs.selectionModel().selectedItem.onChange(
(a,b,c) => {
if (a.value.text() == "Backups") {
if (backupTable.root.value.children.length == 0)
- update_bkps_list()
+ load_backups_update_bkps_view()
+ menuBar.menus = Seq(mainMenu, toolsMenu, optionsMenu, statusMenu)
} else if (a.value.text() == "Updates") {
// only update if not done already
if (updateTable.root.value.children.length == 0)
- update_upds_list()
+ load_updates_update_upds_view()
+ menuBar.menus = Seq(mainMenu, updMenu, toolsMenu, optionsMenu, statusMenu)
+ } else if (a.value.text() == "Packages") {
+ menuBar.menus = Seq(mainMenu, pkgsMenu, toolsMenu, optionsMenu, statusMenu)
}
}
)
- val menuBar: MenuBar = new MenuBar {
- useSystemMenuBar = true
- // menus.addAll(mainMenu, optionsMenu, helpMenu)
- menus.addAll(mainMenu, statusMenu)
- }
+
stage = new PrimaryStage {
title = "TLCockpit"
@@ -1083,42 +1166,131 @@ object ApplicationMain extends JFXApp {
icons.add(iconImage)
}
+ stage.onCloseRequest = (e: Event) => callback_quit()
+
+ stage.width = 800
+
+ var currentPromise = Promise[(String,Array[String])]()
+ val pendingJobs = scala.collection.mutable.Queue[(String,(String, Array[String]) => Unit)]()
def initialize_tlmgr(): TlmgrProcess = {
+ tlmgrBusy.value = true
val tt = new TlmgrProcess(
- // (s:String) => outputfield.text = s,
- (s: Array[String]) => {
- // we don't wont normal output to be displayed
- // as it is anyway returned
- // outputText.clear()
- // outputText.appendAll(s)
- },
- (s: String) => {
- // errorText.clear()
- errorText.append(s)
- if (s.trim != "") {
- outerrpane.expanded = true
- outerrtabs.selectionModel().select(1)
- }
- },
- (s: String) => outputLine.put(s)
+ (s: String) => outputLine.put(s),
+ (s: String) => errorLine.put(s)
)
- val bar = Future {
+ /* val tlmgrMonitor = Future {
while (true) {
+ if (!tlmgr.isAlive) {
+ println("TLMGR HAS DIED!!!!")
+ // Platform.exit()
+ // sys.exit(1)
+ }
+ Thread.sleep(5000)
+ }
+ }*/
+ val stdoutFuture = Future {
+ val tlmgrOutput = ArrayBuffer[String]()
+ var tlmgrStatus = ""
+ var alive = true
+ while (alive) {
val s = outputLine.take
- lineUpdateFunc(s)
+ if (s == null) {
+ alive = false
+ // println("GOT NULL from outputline tlmgr dead???")
+ } else {
+ //println(s"DEBUG: got " + s)
+ if (s == "OK") {
+ tlmgrStatus = s
+ } else if (s == "ERROR") {
+ tlmgrStatus = s
+ } else if (s == "tlmgr> ") {
+ // println("DEBUG: fulfilling current promise!")
+ currentPromise.success((tlmgrStatus, tlmgrOutput.toArray))
+ tlmgrStatus = ""
+ tlmgrOutput.clear()
+ tlmgrBusy.value = false
+ if (pendingJobs.nonEmpty) {
+ val nextCmd = pendingJobs.dequeue()
+ tlmgr_run_one_cmd(nextCmd._1, nextCmd._2)
+ }
+ } else {
+ tlmgrOutput += s
+ stdoutLineUpdateFunc(s)
+ }
+ }
+ }
+ }
+ tlmgrBusy.onChange({ Platform.runLater{ statusMenu.text = "Status: " + (if (tlmgrBusy.value) "Busy" else "Idle") }})
+ stdoutFuture.onComplete {
+ case Success(value) =>
+ // println(s"tlmgr stdout reader terminated: ${value}")
+ Platform.runLater {
+ outerrpane.expanded = true
+ outerrtabs.selectionModel().select(1)
+ errorfield.scrollTop = Double.MaxValue
+ logfield.scrollTop = Double.MaxValue
+ new Alert(AlertType.Error) {
+ initOwner(stage)
+ title = "TeX Live Manager tlmgr has terminated"
+ headerText = "TeX Live Manager tlmgr has terminated - we cannot continue"
+ contentText = "Please inspect the debug output in the main window lower pane!"
+ }.showAndWait()
+ // Platform.exit()
+ // sys.exit(1)
+ }
+ case Failure(e) =>
+ errorText += "lineUpdateFunc(stdout) thread got interrupted -- probably old tlmgr, ignoring it!"
+ e.printStackTrace
+ }
+ val stderrFuture = Future {
+ var alive = true
+ while (alive) {
+ val s = errorLine.take
+ if (s == null)
+ alive = false
+ else
+ stderrLineUpdateFunc(s)
}
}
- bar.onComplete {
- case Success(value) => println(s"Got the callback, meaning = $value")
+ stderrFuture.onComplete {
+ case Success(value) => // println(s"tlmgr stderr reader terminated: ${value}")
case Failure(e) =>
- println("lineUpdateFunc thread got interrupted -- probably old tlmgr, ignoring it!")
- //e.printStackTrace
+ errorText += "lineUpdateFunc(stderr) thread got interrupted -- probably old tlmgr, ignoring it!"
+ e.printStackTrace
}
tt
}
+ def tlmgr_run_one_cmd(s: String, onCompleteFunc: (String, Array[String]) => Unit): Unit = {
+ currentPromise = Promise[(String, Array[String])]()
+ tlmgrBusy.value = true
+ currentPromise.future.onComplete {
+ case Success((a, b)) =>
+ // println("DEBUG current future completed!")
+ Platform.runLater {
+ onCompleteFunc(a, b)
+ }
+ case Failure(ex) =>
+ errorText += "Running a tlmgr command did not succeed: " + ex.getMessage
+ }
+ // println(s"DEBUG sending ${s}")
+ tlmgr.send_command(s)
+ }
+
+ def tlmgr_send(s: String, onCompleteFunc: (String, Array[String]) => Unit): Unit = {
+ // logText.clear()
+ outputText.clear()
+ outerrpane.expanded = false
+ if (!currentPromise.isCompleted) {
+ // println(s"DEBUG tlmgr busy, put onto pending jobs: $s")
+ pendingJobs += ((s, onCompleteFunc))
+ } else {
+ tlmgr_run_one_cmd(s, onCompleteFunc)
+ }
+ }
+
def reinitialize_tlmgr(): Unit = {
tlmgr.cleanup()
// Thread.sleep(1000)
@@ -1128,37 +1300,47 @@ object ApplicationMain extends JFXApp {
}
def tlmgr_post_init():Unit = {
- tlmgr.start_process()
- tlmgr.get_output_till_prompt()
- // update_pkg_lists_to_be_renamed() // this is async done
- pkgs.clear()
- upds.clear()
- bkps.clear()
- update_pkgs_lists()
- }
-
-
- /* TODO implement splash screen - see example in ProScalaFX
- 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()
- */
-
- /*
- 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 (!tlmgr.start_process()) {
+ println("Cannot start tlmgr process, terminating!")
+ Platform.exit()
+ sys.exit(1)
}
+
+ // check for tlmgr revision
+ tlmgr_send("version", (status,output) => {
+ output.foreach ( l => {
+ if (l.startsWith("revision ")) {
+ val tlmgrRev = l.stripPrefix("revision ")
+ if (tlmgrRev == "unknown") {
+ println("Unknown tlmgr revision, assuming git/svn version")
+ logText += "Unknown tlmgr revision, assuming git/svn version"
+ } else {
+ if (tlmgrRev.toInt < 45838) {
+ new Alert(AlertType.Error) {
+ initOwner(stage)
+ title = "TeX Live Manager tlmgr is too old"
+ headerText = "TeX Live Manager tlmgr is too old"
+ contentText = "Please update from the command line\nusing 'tlmgr update --self'\nTerminating!"
+ }.showAndWait()
+ callback_quit()
+ }
+ }
+ }
+ })
+ pkgs.clear()
+ upds.clear()
+ bkps.clear()
+ load_tlpdb_update_pkgs_view()
+ })
}
- */
- var lineUpdateFunc: String => Unit = { (l: String) => } // println(s"DEBUG: got ==$l== from tlmgr") }
+ def defaultStdoutLineUpdateFunc(l: String) : Unit = { } // println(s"DEBUG: got ==$l== from tlmgr") }
+ def defaultStderrLineUpdateFunc(l: String) : Unit = { Platform.runLater { logText.append(l) } }
+
+ var stdoutLineUpdateFunc: String => Unit = defaultStdoutLineUpdateFunc
+ var stderrLineUpdateFunc: String => Unit = defaultStderrLineUpdateFunc
var tlmgr = initialize_tlmgr()
tlmgr_post_init()
-
} // object ApplicationMain
// vim:set tabstop=2 expandtab : //
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/LocationDialog.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/LocationDialog.scala
new file mode 100644
index 00000000000..30088ff69a6
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/LocationDialog.scala
@@ -0,0 +1,117 @@
+package TLCockpit
+
+import TLCockpit.ApplicationMain.{not_implemented_info, stage}
+import TeXLive.{OsTools, TLOption}
+
+import scalafx.Includes._
+import scalafx.event.ActionEvent
+import scalafx.geometry.{HPos, Insets}
+import scalafx.scene.Node
+import scalafx.scene.control._
+import scalafx.scene.layout._
+
+class LocationDialog(locs: Map[String,String]) {
+
+ case class Result(selected: Map[String, String])
+
+ val dialog = new Dialog[Result]() {
+ initOwner(stage)
+ title = s"Default package repositories"
+ headerText = s"Default package repositories"
+ resizable = true
+ }
+ dialog.dialogPane().buttonTypes = Seq(ButtonType.OK, ButtonType.Cancel)
+ val grid = new GridPane() {
+ hgap = 10
+ vgap = 10
+ padding = Insets(20)
+ }
+
+ val newlocs = scala.collection.mutable.ArrayBuffer[(TextField, TextField)]()
+
+ var crow = 0
+
+ def do_one(tag: String, url: String): Unit = {
+ val tagNode = new TextField() {
+ text = tag
+ }
+ tagNode.disable = (tag == "main")
+ val urlNode = new TextField() {
+ text = url
+ }
+ grid.add(tagNode, 0, crow)
+ grid.add(urlNode, 1, crow)
+ newlocs += ((tagNode, urlNode))
+ if (tag != "main") {
+ grid.add(new Button("Delete") {
+ onAction = _ => {
+ tagNode.disable = true
+ urlNode.disable = true
+ this.disable = true
+ }
+ }, 2, crow)
+ }
+ crow += 1
+ }
+
+ do_one("main", locs("main"))
+ locs.filter(_._1 != "main").foreach(p => do_one(p._1, p._2))
+
+ val addButton = new Button("Add")
+ addButton.onAction = _ => {
+ val tagNode = new TextField()
+ val urlNode = new TextField()
+ newlocs += ((tagNode, urlNode))
+ // println(s"current row = ${crow}")
+ grid.children.remove(addButton)
+ grid.add(tagNode, 0, crow)
+ grid.add(urlNode, 1, crow)
+ grid.add(new Button("Delete") {
+ onAction = _ => {
+ tagNode.disable = true
+ urlNode.disable = true
+ this.disable = true
+ }
+ }, 2, crow)
+ crow += 1
+ // println(s"adding addButton at row ${crow}")
+ grid.add(addButton, 2, crow)
+ dialog.dialogPane.value.scene.value.window.value.sizeToScene()
+ }
+ grid.add(addButton, 2, crow)
+
+ // val changedValues = scala.collection.mutable.Map[String,String]()
+
+
+ grid.columnConstraints = Seq(new ColumnConstraints(100, 100, 200), new ColumnConstraints(250, 250, 5000, Priority.Always, new HPos(HPos.Left), true))
+ dialog.dialogPane().content = grid
+ dialog.width = 500
+ dialog.height = 1500
+
+ dialog.resultConverter = dialogButton =>
+ if (dialogButton == ButtonType.OK) {
+ val newlocsfiltered =
+ newlocs.filter(p => (p._1.text.value + p._2.text.value).trim != "")
+ .filter(p => !p._2.disabled.value)
+ .map(p => (if (p._1.text.value == "") p._2.text.value else p._1.text.value, p._2.text.value))
+ .toMap
+ Result(newlocsfiltered)
+ } else
+ null
+
+ def showAndWait(): Option[Map[String,String]] = {
+ val result = dialog.showAndWait()
+
+ result match {
+ case Some(Result(foo)) =>
+ // println("Got resutl " + foo)
+ Some(foo)
+ case Some(foo) =>
+ // println("Got strange result " + foo)
+ None
+ case None =>
+ None
+ }
+ }
+
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/OptionsDialog.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/OptionsDialog.scala
new file mode 100644
index 00000000000..ca7228da7ee
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/OptionsDialog.scala
@@ -0,0 +1,129 @@
+package TLCockpit
+
+import TLCockpit.ApplicationMain.{not_implemented_info, stage}
+import TeXLive.{OsTools, TLOption, TLPaperConf}
+
+import scalafx.Includes._
+import scalafx.collections.ObservableBuffer
+import scalafx.event.ActionEvent
+import scalafx.geometry.{HPos, Insets}
+import scalafx.scene.Node
+import scalafx.scene.control._
+import scalafx.scene.layout._
+
+class OptionsDialog(opts: List[TLOption]) {
+
+ case class Result(selected: Map[String,String])
+
+ val dialog = new Dialog[Result]() {
+ initOwner(stage)
+ title = s"General options"
+ headerText = s"General options"
+ resizable = true
+ }
+ dialog.dialogPane().buttonTypes = Seq(ButtonType.OK, ButtonType.Cancel)
+ val grid = new GridPane() {
+ hgap = 10
+ vgap = 10
+ padding = Insets(20)
+ }
+
+ var crow = 0
+ val optsMap = opts.map(p => (p.name, p)).toMap
+ val orderOpts = Seq("location", "create_formats", "install_srcfiles", "install_docfiles",
+ "backupdir", "autobackup") ++ {
+ if (OsTools.isWindows) Seq("desktop_integration", "file_assocs", "w32_multi_user")
+ else Seq("sys_bin", "sys_info", "sys_man")
+ }
+ val changedValues = scala.collection.mutable.Map[String,String]()
+ val nodes = orderOpts.map( nm => {
+ val tlopt = optsMap(nm) // this is dangerous in case we change the names in TLPDB!
+ grid.add(new Label(tlopt.description), 0, crow)
+ val value = tlopt.value.getOrElse(tlopt.default)
+ val nd: Node = if (tlopt.format.startsWith("b"))
+ new CheckBox() {
+ selected = if (value == "1") true else false
+ selected.onChange( (_,_,newVal) => { changedValues(nm) = if (newVal) "1" else "0" })
+ }
+ else if (tlopt.format.startsWith("n")) {
+ // parse additional specs
+ if (tlopt.format.startsWith("n:")) {
+ var nrSpec = tlopt.format.substring(2)
+ var splits: Array[String] = nrSpec.split("""\.\.""")
+ val start: Int = splits(0).toInt
+ val end: Option[Int] = if (splits.length > 1) Some(splits(1).toInt) else None
+ new Spinner[Int](start, end.getOrElse(50), value.toInt) {
+ value.onChange( (_,_,newVal) => { changedValues(nm) = newVal.toString })
+ if (nm == "autobackup")
+ tooltip = new Tooltip("-1 ... arbitrary many backups\n0 ... no backups\notherwise number of backups")
+ }
+ } else {
+ new Spinner[Int](-50, 50, value.toInt) {
+ value.onChange( (_,_,newVal) => { changedValues(nm) = newVal.toString })
+ }
+ }
+ } else if (tlopt.format == "p")
+ new TextField() {
+ text = value
+ text.onChange( (_,_,newVal) => { changedValues(nm) = newVal })
+ }
+ else if (tlopt.format == "u") {
+ val locs = value.split(' ')
+ val locsmap = if (locs.length == 1) Map[String,String](("main", value))
+ else locs.map(p => {
+ val uu = p.split('#')
+ (uu(1), uu(0))
+ }).toMap
+ val buttonStr = if (locs.length == 1) value else "Multiple repository"
+ new Button(buttonStr) {
+ onAction = (event: ActionEvent) => {
+ val dg = new LocationDialog(locsmap)
+ dg.showAndWait() match {
+ case Some(newlocs) =>
+ if (newlocs.toList.length == 1) {
+ changedValues(nm) = newlocs("main")
+ this.text = newlocs("main")
+ } else {
+ changedValues(nm) = newlocs.map(p => p._2 + "#" + p._1).mkString(" ")
+ this.text = "Multiple repository"
+ }
+ case None =>
+ }
+ }
+ }
+ } else
+ new Label("Unknown setting")
+
+ grid.add(nd, 1, crow)
+ crow += 1
+ (tlopt.name, nd)
+ }).toMap
+
+ grid.columnConstraints = Seq(new ColumnConstraints(300, 300, 500), new ColumnConstraints(200, 200, 5000, Priority.Always, new HPos(HPos.Left), true))
+ dialog.dialogPane().content = grid
+ dialog.width = 500
+ dialog.height = 1500
+
+ dialog.resultConverter = dialogButton =>
+ if (dialogButton == ButtonType.OK) {
+ // TODO we could check that the values actually have changed
+ Result(changedValues.toMap)
+ } else
+ null
+
+ def showAndWait(): Option[Map[String,String]] = {
+ val result = dialog.showAndWait()
+
+ result match {
+ case Some(Result(foo)) =>
+ // println("Got resutl " + foo)
+ Some(foo)
+ case Some(foo) =>
+ // println("Got strange result " + foo)
+ None
+ case None =>
+ None
+ }
+ }
+
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/PaperDialog.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/PaperDialog.scala
new file mode 100644
index 00000000000..a195c23f654
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/PaperDialog.scala
@@ -0,0 +1,83 @@
+package TLCockpit
+
+import TLCockpit.ApplicationMain.stage
+import TeXLive.TLPaperConf
+
+import scalafx.Includes._
+import scalafx.collections.ObservableBuffer
+import scalafx.geometry.{HPos, Insets}
+import scalafx.scene.control._
+import scalafx.scene.layout._
+
+class PaperDialog(paperconf: Map[String, TLPaperConf]) {
+
+ case class Result(selected: Map[String,String])
+
+ val dialog = new Dialog[Result]() {
+ initOwner(stage)
+ title = s"Paper configuration"
+ headerText = s"Paper configuration"
+ resizable = true
+ }
+ dialog.dialogPane().buttonTypes = Seq(ButtonType.OK, ButtonType.Cancel)
+ val grid = new GridPane() {
+ hgap = 10
+ vgap = 10
+ padding = Insets(20)
+ }
+
+ var crow = 0
+
+ def update_all_to(str: String): Unit = {
+ cbseq.foreach(f => f._2.value = str)
+ }
+ grid.add(new Label("Set all to:"), 0, crow)
+ grid.add(new HBox(20) {
+ children = List(
+ new Button("a4") { onAction = _ => update_all_to("a4") },
+ new Button("letter") { onAction = _ => update_all_to("letter")}
+ )}, 1, crow)
+ crow += 1
+ def do_one(k: String, v: ChoiceBox[String], row: Int): Int = {
+ grid.add(new Label(k), 0, row)
+ grid.add(v, 1, row)
+ row + 1
+ }
+
+ val cbseq: Map[String, ChoiceBox[String]] = paperconf.map { p =>
+ val prog: String = p._1
+ val opts: TLPaperConf = p._2
+ val paperopts: Seq[String] = opts.options
+ val cb = new ChoiceBox[String](ObservableBuffer(paperopts.sorted))
+ cb.value = paperopts.head
+ crow = do_one(prog,cb, crow)
+ (prog, cb)
+ }
+
+ grid.columnConstraints = Seq(new ColumnConstraints(100, 100, 150), new ColumnConstraints(150, 150, 5000, Priority.Always, new HPos(HPos.Left), true))
+ dialog.dialogPane().content = grid
+ dialog.width = 300
+ dialog.height = 1500
+
+ dialog.resultConverter = dialogButton =>
+ if (dialogButton == ButtonType.OK)
+ Result(cbseq.mapValues(_.value.value))
+ else
+ null
+
+ def showAndWait(): Option[Map[String,String]] = {
+ val result = dialog.showAndWait()
+
+ result match {
+ case Some(Result(foo)) =>
+ // println("Got resutl " + foo)
+ Some(foo)
+ case Some(foo) =>
+ // println("Got strange result " + foo)
+ None
+ case None =>
+ None
+ }
+ }
+
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/PkgInfoDialog.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/PkgInfoDialog.scala
new file mode 100644
index 00000000000..3b5ea6fc848
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/PkgInfoDialog.scala
@@ -0,0 +1,145 @@
+package TLCockpit
+
+import TLCockpit.ApplicationMain.{tlpkgs,tlmgr,stage}
+import TLCockpit.Utils._
+import TeXLive.OsTools
+
+import scalafx.Includes._
+import scalafx.collections.ObservableBuffer
+import scalafx.geometry.{HPos, Insets, Orientation}
+import scalafx.scene.Cursor
+import scalafx.scene.layout.{ColumnConstraints, GridPane, Priority, VBox}
+import scalafx.scene.control._
+import scalafx.scene.input.MouseEvent
+import scalafx.scene.paint.Color
+
+class PkgInfoDialog(pkg: String) extends Dialog {
+ 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 isInstalled = tlpkgs(pkg).installed
+ val grid = new GridPane() {
+ hgap = 10
+ vgap = 10
+ padding = Insets(20)
+ }
+
+ def do_one(k: String, v: String, row: Int): Int = {
+ grid.add(new Label(k), 0, row)
+ grid.add(new Label(v) {
+ wrapText = true
+ }, 1, row)
+ row + 1
+ }
+
+ var crow = 0
+ val tlp = tlpkgs(pkg)
+ crow = do_one("package", pkg, crow)
+ crow = do_one("category", tlp.category, crow)
+ crow = do_one("shortdesc", tlp.shortdesc.getOrElse(""), crow)
+ crow = do_one("longdesc", tlp.longdesc.getOrElse(""), crow)
+ crow = do_one("installed", if (tlp.installed) "Yes" else "No", crow)
+ crow = do_one("available", if (tlp.available) "Yes" else "No", crow)
+ if (tlp.installed)
+ crow = do_one("local revision", tlp.lrev.toString, crow)
+ if (tlp.available)
+ crow = do_one("remote revision", tlp.rrev.toString, crow)
+ val binsizesum = tlp.binsize.map { _._2}.toList.foldLeft[Long](0)(_ + _) * TeXLive.tlBlockSize
+ val binsizestr = if (binsizesum > 0) "bin " + humanReadableByteSize(binsizesum) + " " else "";
+ val runsizestr = if (tlp.runsize > 0) "run " + humanReadableByteSize(tlp.runsize) + " " else "";
+ val srcsizestr = if (tlp.srcsize > 0) "src " + humanReadableByteSize(tlp.srcsize) + " " else "";
+ val docsizestr = if (tlp.docsize > 0) "doc " + humanReadableByteSize(tlp.docsize) + " " else "";
+ crow = do_one("sizes", runsizestr + docsizestr + binsizestr + srcsizestr, crow)
+ val catdata = tlp.cataloguedata
+ if (catdata.version != None)
+ crow = do_one("cat-version", catdata.version.get, crow)
+ if (catdata.date != None)
+ crow = do_one("cat-date", catdata.date.get, crow)
+ if (catdata.license != None)
+ crow = do_one("cat-license", catdata.license.get, crow)
+ if (catdata.topics != None)
+ crow = do_one("cat-topics", catdata.topics.get, crow)
+ if (catdata.related != None)
+ crow = do_one("cat-related", catdata.related.get, crow)
+ // add files section
+ //println(tlpkgs(pkg))
+ val docFiles = tlpkgs(pkg).docfiles
+ if (docFiles.nonEmpty) {
+ grid.add(new Label("doc files"), 0, crow)
+ grid.add(doListView(docFiles.map(s => s.file.replaceFirst("RELOC", "texmf-dist")), isInstalled), 1, crow)
+ crow += 1
+ }
+ val runFiles = tlpkgs(pkg).runfiles
+ if (runFiles.nonEmpty) {
+ grid.add(new Label("run files"), 0, crow)
+ grid.add(doListView(runFiles.map(s => s.replaceFirst("RELOC", "texmf-dist")), false), 1, crow)
+ crow += 1
+ }
+ val srcFiles = tlpkgs(pkg).srcfiles
+ if (srcFiles.nonEmpty) {
+ grid.add(new Label("src files"), 0, crow)
+ grid.add(doListView(srcFiles.map(s => s.replaceFirst("RELOC", "texmf-dist")), false), 1, crow)
+ crow += 1
+ }
+ val binFiles = tlpkgs(pkg).binfiles
+ if (binFiles.nonEmpty) {
+ grid.add(new Label("bin files"), 0, crow)
+ grid.add(doListView(binFiles.flatMap(_._2).toSeq.map(s => s.replaceFirst("RELOC", "texmf-dist")), false), 1, crow)
+ crow += 1
+ }
+ grid.columnConstraints = Seq(new ColumnConstraints(100, 200, 200), new ColumnConstraints(100, 400, 5000, Priority.Always, new HPos(HPos.Left), true))
+ dialog.dialogPane().content = grid
+ dialog.width = 600
+ dialog.height = 1500
+ // dialog
+
+ def showAndWait(): Unit = this.dialog.showAndWait()
+
+ def doListView(files: Seq[String], clickable: Boolean): scalafx.scene.Node = {
+ if (files.length <= 5) {
+ val vb = new VBox()
+ vb.children = files.map { f =>
+ val fields = f.split(" ")
+ new Label(fields(0)) {
+ if (clickable) {
+ textFill = Color.Blue
+ onMouseClicked = { me: MouseEvent => OsTools.openFile(tlmgr.tlroot + "/" + fields(0)) }
+ cursor = Cursor.Hand
+ }
+ }
+ }
+ vb
+ } else {
+ val vb = new ListView[String] {}
+ vb.minHeight = 100
+ vb.prefHeight = 100
+ vb.maxHeight = 200
+ vb.vgrow = Priority.Always
+ vb.orientation = Orientation.Vertical
+ vb.cellFactory = { p => {
+ val foo = new ListCell[String]
+ // ensure tighter spacing similar to the other entires
+ foo.padding = Insets(0)
+ foo.item.onChange { (_, _, str) => foo.text = str }
+ if (clickable) {
+ foo.textFill = Color.Blue
+ foo.onMouseClicked = { me: MouseEvent => OsTools.openFile(tlmgr.tlroot + "/" + foo.text.value) }
+ foo.cursor = Cursor.Hand
+ }
+ foo
+ }
+ }
+ // vb.children = docFiles.map { f =>
+ vb.items = ObservableBuffer(files.map { f =>
+ val fields = f.split(" ")
+ fields(0)
+ })
+ vb
+ }
+ }
+
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/TLDisplayClasses.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/TLDisplayClasses.scala
new file mode 100644
index 00000000000..630cbfce061
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/TLDisplayClasses.scala
@@ -0,0 +1,30 @@
+package TLCockpit
+
+import scalafx.beans.property.{ObjectProperty, StringProperty}
+
+
+// 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 TLPackageDisplay(name: StringProperty, var lrev: ObjectProperty[Int], rrev: ObjectProperty[Int],
+ shortdesc: StringProperty, size: ObjectProperty[Int], var 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)
+ )
+}
+
+case class TLBackupDisplay(name: StringProperty, rev: StringProperty, date: StringProperty) {
+ def this(_name: String, _rev: String, _date: String) =
+ this(StringProperty(_name), StringProperty(_rev), StringProperty(_date))
+}
+
+case class TLUpdateDisplay(name: StringProperty, var status: StringProperty, var lrev: StringProperty, rrev: StringProperty,
+ shortdesc: StringProperty, size: StringProperty) {
+ def this(_name: String, _status: String, _lrev: String, _rrev: String, _shortdesc: String, _size: String) =
+ this(
+ StringProperty(_name), StringProperty(_status), StringProperty(_lrev), StringProperty(_rrev),
+ StringProperty(_shortdesc), StringProperty(_size)
+ )
+} \ No newline at end of file
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/Utils.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/Utils.scala
new file mode 100644
index 00000000000..fb364f12f80
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/Utils.scala
@@ -0,0 +1,18 @@
+package TLCockpit
+
+object Utils {
+ /**
+ * @see https://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc
+ * @see https://en.wikipedia.org/wiki/Zettabyte
+ * @param fileSize Up to Exabytes
+ * @return
+ */
+ def humanReadableByteSize(fileSize: Long): String = {
+ if(fileSize <= 0) return "0 B"
+ // kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta
+ val units: Array[String] = Array("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
+ val digitGroup: Int = (Math.log10(fileSize)/Math.log10(1024)).toInt
+ f"${fileSize/Math.pow(1024, digitGroup)}%3.1f ${units(digitGroup)}"
+ }
+
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/JsonProtocol.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/JsonProtocol.scala
new file mode 100644
index 00000000000..3e28c582f2a
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/JsonProtocol.scala
@@ -0,0 +1,15 @@
+package TeXLive
+
+import spray.json._
+
+
+object JsonProtocol extends DefaultJsonProtocol {
+ implicit val catalogueDataFormat = jsonFormat6(CatalogueData)
+ implicit val docfileFormat = jsonFormat3(DocFile)
+ implicit val tlpackageFormat = jsonFormat19(TLPackage)
+ implicit val tlbackupFormat = jsonFormat3(TLBackup)
+ implicit val tlpaperconfFormat = jsonFormat3(TLPaperConf)
+ implicit val tloptionsFormat = jsonFormat14(TLOptions)
+ implicit val tloptionFormat = jsonFormat6(TLOption)
+}
+
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsTools.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsTools.scala
index 38d0c334d99..4590eb6b5c5 100644
--- a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsTools.scala
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/OsTools.scala
@@ -5,7 +5,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
import sys.process._
object OsTools {
- val OS = System.getProperty("os.name").map(_.toLower)
+ val OS: String = System.getProperty("os.name").map(_.toLower)
def isWindows: Boolean = {
OS.startsWith("windows")
}
@@ -19,7 +19,7 @@ object OsTools {
def openFileCmd(f: String): Seq[String] = {
val absf = new java.io.File(f).getCanonicalPath
if (isWindows) {
- Seq("cmd", "start", absf)
+ Seq("cmd", "/c", "start", absf)
} else if (isApple) {
Seq("open", absf)
} else {
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
deleted file mode 100644
index a00c1464f30..00000000000
--- a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-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/TLClasses.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLClasses.scala
new file mode 100644
index 00000000000..c526ae50739
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLClasses.scala
@@ -0,0 +1,56 @@
+package TeXLive
+
+// classes that wrap up structures in TeX Live
+// all of them have associated JSON parsers
+
+case class CatalogueData(version: Option[String], topics: Option[String], license: Option[String],
+ date: Option[String], related: Option[String], ctan: Option[String])
+
+case class DocFile(file: String, details: Option[String], language: Option[String])
+case class TLPaperConf(program: String, file: String, options: List[String])
+case class TLBackup(name: String, rev: String, date: String)
+
+case class TLPackage
+(
+ name: String,
+ shortdesc: Option[String],
+ longdesc: Option[String],
+ var lrev: Long,
+ rrev: Long,
+ category: String,
+ docfiles: List[DocFile],
+ runfiles: List[String],
+ srcfiles: List[String],
+ binfiles: Map[String,List[String]],
+ docsize: Long,
+ runsize: Long,
+ srcsize: Long,
+ binsize: Map[String,Long],
+ cataloguedata: CatalogueData,
+ depends: List[String],
+ catalogue: Option[String],
+ var installed: Boolean,
+ available: Boolean
+)
+
+case class TLOption (name: String, description: String, format: String,
+ tlmgrname: String, value: Option[String], default: String)
+
+case class TLOptions
+(
+ sys_bin: String,
+ sys_man: String,
+ sys_info: String,
+ backupdir: String,
+ autobackup: Long,
+ install_srcfiles: Boolean,
+ install_docfiles: Boolean,
+ create_formats: Boolean,
+ post_code: Boolean,
+ generate_updmap: Boolean,
+ location: Map[String,String],
+ desktop_integration: Option[Boolean],
+ w32_multi_user: Option[Boolean],
+ file_assocs: Option[Long]
+)
+
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
deleted file mode 100644
index 9a797d9c8ae..00000000000
--- a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-package TeXLive
-
-import scalafx.beans.property.{ObjectProperty, StringProperty}
-
-// 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/TLUpdate.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLUpdate.scala
deleted file mode 100644
index 43623a523dd..00000000000
--- a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLUpdate.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-package TeXLive
-
-import scalafx.beans.property.{ObjectProperty, StringProperty}
-
-// 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 TLUpdate(name: StringProperty, var status: StringProperty, lrev: StringProperty, rrev: StringProperty,
- shortdesc: StringProperty, size: StringProperty) {
- def this(_name: String, _status: String, _lrev: String, _rrev: String, _shortdesc: String, _size: String) =
- this(
- StringProperty(_name), StringProperty(_status), StringProperty(_lrev), StringProperty(_rrev),
- StringProperty(_shortdesc), StringProperty(_size)
- )
-} \ 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 169f44154fb..7d84e6dd008 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
@@ -2,103 +2,50 @@ package TeXLive
import java.io._
-import OsTools._
+import TeXLive.OsTools._
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.SyncVar
-// import scala.sys.process.{Process, ProcessBuilder, ProcessIO}
import scala.sys.process._
-class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit, updline: String => Unit) {
+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
+ // 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 = "kpsewhich -var-value SELFAUTOPARENT".!!.trim
// println("tlroot ==" + tlroot + "==")
// set in only one place, in the main thread
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
- var sendNL = 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 && sendNL < 3) {
- outputString.put("protocol")
- sendNL += 1
- }
- if (waited > maxWaitTime && sendNL >= 3) {
- throw new Exception("tlmgr busy, waited too long, aborting calling: " + input)
- }
- }
+ def send_command(input: String): Unit = {
try {
- // pass the input and wait for the output
assert(!inputString.isSet)
- assert(!outputString.isSet)
- // errorBuffer.setLength(0)
- synchronized(isBusy = true)
inputString.put(input)
- val ret = if (input != "quit") {
- get_output_till_prompt()
- } else {
- new Array[String](0)
- }
- // updout(ret.mkString("\n"))
- updout(ret)
- upderr(errorBuffer.toString)
- synchronized( isBusy = false )
- ret
} catch {
case exc: Throwable =>
- errorBuffer.append(" Main thread: " +
+ upderr("Main thread: " +
(if (exc.isInstanceOf[NoSuchElementException]) "Timeout" else "Exception: " + exc))
- null
}
}
- def start_process(): Unit = {
+ def isAlive(): Boolean = {
+ if (process != null)
+ process.isAlive()
+ else
+ // return true on not-started process
+ true
+ }
+
+ def start_process(): Boolean = {
// 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")
+ val procIO = new ProcessIO(inputFn(_), outputFn(_, updout), outputFn(_, upderr))
+ val processBuilder: ProcessBuilder = Seq({if (isWindows) "tlmgr.bat" else "tlmgr"}, "-v", "--machine-readable", "shell")
process = processBuilder.run(procIO)
}
- }
-
- def get_output_till_prompt(): Array[String] = {
- var ret = ArrayBuffer[String]()
- var result = ""
- var found = false
- synchronized(isBusy = true)
- 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
- }
- }
- synchronized(isBusy = false)
- ret.toArray
+ process.isAlive()
}
def cleanup(): Unit = {
@@ -129,53 +76,29 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit, updlin
case exc: Throwable =>
stdin.close()
// println("Exception in inputFn thread: " + exc + "\n")
- errorBuffer.append(" Input thread: Exception: " + exc + "\n")
+ upderr("Input thread: Exception: " + exc + "\n")
}
}
- private[this] def outputFn(stdOut: InputStream): Unit = {
- val reader = new BufferedReader(new InputStreamReader(stdOut))
+ 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
- if (line == null) {
- // println("Did read NULL from stdin giving up")
- // error = true
- } else {
- // println("DDD did read " + line + " from process")
- outputString.put(line)
- try {
- updline(line)
- } catch {
- case exc: Throwable =>
- println("Update line function failed, continuing anyway (probably old tlmgr)!")
- }
+ // println("DDD did read " + line + " from process")
+ try {
+ updfun(line)
+ } catch {
+ case exc: Throwable =>
+ upderr("Update output line function failed, continuing anyway. Exception: " + exc)
}
}
- stdOut.close()
- } catch {
- case exc: Throwable =>
- stdOut.close()
- // println("Exception in outputFn thread: " + exc + "\n")
- 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()
+ outStr.close()
} catch {
case exc: Throwable =>
- stdErr.close()
- // println("Exception in errorFn thread: " + exc + "\n")
- errorBuffer.append(" Error thread: Exception: " + exc + "\n")
+ outStr.close()
+ upderr("Output thread: Exception: " + exc + "\n")
}
}
}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/package.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/package.scala
new file mode 100644
index 00000000000..88b3bb3e41a
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/package.scala
@@ -0,0 +1,4 @@
+
+package object TeXLive {
+ val tlBlockSize = 4096 // TL gives sizes in multiples of BlockSize!
+}