diff options
-rw-r--r-- | Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar | bin | 11651966 -> 11667498 bytes | |||
-rw-r--r-- | Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala | 480 | ||||
-rw-r--r-- | Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLUpdate.scala | 15 | ||||
-rw-r--r-- | Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala | 12 |
4 files changed, 342 insertions, 165 deletions
diff --git a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar Binary files differindex ee194fe5d2e..a3d9ba726ae 100644 --- a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar +++ b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala index 5831e4828e4..65b4b0187df 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,19 +6,13 @@ package TLCockpit -import javafx.collections.ObservableList -import javafx.scene.control - import TLCockpit.ApplicationMain.getClass -import TeXLive.{TLBackup, TLPackage, TlmgrProcess} +import TeXLive.{TLBackup, TLPackage, TLUpdate, TlmgrProcess} -import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.concurrent.{Future, SyncVar} import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.{Failure, Sorting, Success} -import scalafx.beans.Observable -import scalafx.beans.property.{ObjectProperty, ReadOnlyStringWrapper} +import scala.util.{Failure, Success} import scalafx.geometry.{HPos, Pos, VPos} import scalafx.scene.control.Alert.AlertType import scalafx.scene.image.{Image, ImageView} @@ -34,11 +28,15 @@ import scalafx.scene.Scene import scalafx.scene.layout._ import scalafx.scene.control._ import scalafx.event.ActionEvent -import scalafx.collections.ObservableBuffer import scala.sys.process._ -import scalafx.scene.text._ import scalafx.collections.ObservableBuffer +import scalafx.collections.ObservableMap + +// TODO update listings when package is updated/installed/removed ... +// TODO line by line reading/action of shell output +// TODO TreeTableView indentation is lazy +// TODO pkg info - access to doc files object ApplicationMain extends JFXApp { @@ -52,7 +50,9 @@ object ApplicationMain extends JFXApp { val iconImage = new Image(getClass.getResourceAsStream("tlcockpit-48.jpg")) val logoImage = new Image(getClass.getResourceAsStream("tlcockpit-128.jpg")) - val pkgs: ArrayBuffer[TLPackage] = ArrayBuffer() + val pkgs = ObservableMap[String, TLPackage]() + val upds = ObservableMap[String, TLUpdate]() + val bkps = ObservableMap[String, Map[String,TLBackup]]() // pkgname -> (version -> TLBackup)* val errorText: ObservableBuffer[String] = ObservableBuffer[String]() val outputText: ObservableBuffer[String] = ObservableBuffer[String]() @@ -77,11 +77,11 @@ object ApplicationMain extends JFXApp { }) val update_all_menu: MenuItem = new MenuItem("Update all") { - onAction = (ae) => callback_update_all() + onAction = (ae) => callback_update("--all") disable = true } val update_self_menu: MenuItem = new MenuItem("Update self") { - onAction = (ae) => callback_update_self() + onAction = (ae) => callback_update("--self") disable = true } @@ -111,6 +111,7 @@ object ApplicationMain extends JFXApp { cmdline.onKeyPressed = { (ae: KeyEvent) => if (ae.code == KeyCode.Enter) callback_run_cmdline() } + val outputLine = new SyncVar[String] val tlmgr = new TlmgrProcess( // (s:String) => outputfield.text = s, (s: Array[String]) => { @@ -126,7 +127,9 @@ object ApplicationMain extends JFXApp { outerrpane.expanded = true outerrtabs.selectionModel().select(1) } - }) + }, + (s: String) => outputLine.put(s) + ) def tlmgr_async_command(s: String, f: Array[String] => Unit): Unit = { errorText.clear() @@ -148,92 +151,6 @@ object ApplicationMain extends JFXApp { } } - def update_update_menu_state(): Unit = { - update_all_menu.disable = !pkgs.foldLeft(false)( - (a, b) => a || (if (b.name.value == "texlive.infra") false else b.lrev.value.toInt > 0 && b.lrev.value.toInt < b.rrev.value.toInt) - ) - // TODO we should change pkgs to a Map with keys are the names + repository (for multi repo support) - // and the values are llrev/rrev or some combination - update_self_menu.disable = !pkgs.foldLeft(false)( - (a, b) => a || (if (b.name.value == "texlive.infra") b.lrev.value.toInt < b.rrev.value.toInt else false) - ) - updateTable.refresh() - packageTable.refresh() - } - - def update_pkg_lists(): Unit = { - tlmgr_async_command("info --data name,localrev,remoterev,shortdesc,size,installed", (s: Array[String]) => { - pkgs.clear() - s.map((line: String) => { - val fields: Array[String] = line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1) - val sd = fields(3) - val shortdesc = if (sd.isEmpty) "" else sd.substring(1).dropRight(1).replace("""\"""",""""""") - val inst = if (fields(5) == "1") "Installed" else "Not installed" - pkgs += new TLPackage(fields(0), fields(1), fields(2), shortdesc, fields(4), inst) - }) - val pkgbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage] - val binbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackage]] - val updbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage] - pkgs.foreach(pkg => { - if (pkg.lrev.value > 0 && pkg.lrev.value < pkg.rrev.value) { - // easy part: in the update tab show all packages in raw format - // TODO if the main package is updated, too, use a child item? - updbuf += pkg - } - // complicated part, determine whether it is a sub package or not! - // we strip of initial texlive. prefixes to make sure we deal - // with real packages - if (pkg.name.value.stripPrefix("texlive.").contains(".")) { - val foo: Array[String] = pkg.name.value.stripPrefix("texlive.infra").split('.') - val pkgname = foo(0) - val binname = foo(1) - if (binbuf.keySet.contains(pkgname)) { - binbuf(pkgname) += pkg - } else { - binbuf(pkgname) = ArrayBuffer[TLPackage](pkg) - } - } else { - pkgbuf += pkg - } - }) - // now we have all normal packages in pkgbuf, and its sub-packages in binbuf - // we need to create TreeItems - val viewkids = pkgbuf.map { p => { - val kids: Seq[TLPackage] = if (binbuf.keySet.contains(p.name.value)) { - binbuf(p.name.value) - } else { - Seq() - } - // for ismixed we && all the installed status. If all are installed, we get true - val allinstalled = (kids :+ p).foldRight[Boolean](true)((k,b) => k.installed.value == "Installed" && b) - if (!allinstalled) { - // replace installed status with "Mixed" - new TreeItem[TLPackage](new TreeItem[TLPackage]( - new TLPackage(p.name.value, p.lrev.value.toString, p.rrev.value.toString, p.shortdesc.value, p.size.value.toString, "Mixed") - )) { - children = kids.map(new TreeItem[TLPackage](_)) - } - } else { - new TreeItem[TLPackage](p) { - children = kids.map(new TreeItem[TLPackage](_)) - } - } - }} - val updkids = updbuf.map { p => new TreeItem[TLPackage](p) } - Platform.runLater { - packageTable.root = new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) { - expanded = true - children = viewkids - } - updateTable.root = new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) { - expanded = true - children = updkids - } - update_update_menu_state() - } - }) - } - def callback_quit(): Unit = { tlmgr.cleanup() Platform.exit() @@ -268,20 +185,29 @@ object ApplicationMain extends JFXApp { outerrtabs.selectionModel().select(0) outputText.append(s"Running $s") val foo = Future { + // this is really a pain because with many lines being shot out + // from say fmtutil-sys --all, there are thousands of + // calls to Platform.runLater, which makes Java go to 100% + // but finally it succeeds. + // TODO use buffered update of output s ! ProcessLogger( - line => outputText.append(line), - line => errorText.append(line) + line => Platform.runLater( outputText.append(line) ), + line => Platform.runLater( errorText.append(line) ) ) } foo.onComplete { case Success(ret) => - outputText.append("Completed") - outputfield.scrollTop = Double.MaxValue + Platform.runLater { + outputText.append("Completed") + outputfield.scrollTop = Double.MaxValue + } case Failure(t) => - errorText.append("An ERROR has occurred running $s: " + t.getMessage) - errorfield.scrollTop = Double.MaxValue - outerrpane.expanded = true - outerrtabs.selectionModel().select(1) + Platform.runLater { + errorText.append("An ERROR has occurred running $s: " + t.getMessage) + errorfield.scrollTop = Double.MaxValue + outerrpane.expanded = true + outerrtabs.selectionModel().select(1) + } } } @@ -295,55 +221,248 @@ object ApplicationMain extends JFXApp { }.showAndWait() } - def callback_update_all(): Unit = { - // TODO parse table and update line by line - tlmgr_async_command("update --all", _ => { Platform.runLater { update_pkg_lists() } }) - } - - def callback_update_self(): Unit = { - // TODO should we restart tlmgr here - it might be necessary!!! - tlmgr_async_command("update --self", _ => { Platform.runLater { update_pkg_lists() } }) + def callback_update(s: String): Unit = { + lineUpdateFunc = (l:String) => { + // println("line update: " + l + "=") + l match { + case u if u.startsWith("location-url") => None + case u if u.startsWith("total-bytes") => None + case u if u.startsWith("end-of-header") => None + case u if u.startsWith("end-of-updates") => None + case u if u == "OK" => None + case u if u.startsWith("tlmgr>") => None + case u => + val foo = parse_one_update_line(l) + upds.remove(foo.name.value) + val op = pkgs(foo.name.value) + pkgs(foo.name.value) = new TLPackage(foo.name.value, op.rrev.value.toString, op.rrev.value.toString, foo.shortdesc.value, op.size.value.toString, "Installed") + Platform.runLater { + trigger_update("upds") + trigger_update("pkgs") + } + } + } + tlmgr_async_command(s"update $s", _ => { + Platform.runLater { lineUpdateFunc = { (s: String) => } } + }) } def do_one_pkg(what: String, pkg: String): Unit = { - tlmgr_async_command(s"$what $pkg", _ => { Platform.runLater { update_pkg_lists() } }) + tlmgr_async_command(s"$what $pkg", _ => { Platform.runLater { update_pkgs_lists() } }) } def callback_restore_pkg(str: String, rev: String): Unit = { not_implemented_info() } - def callback_populate_backup_tab(): Unit = { - // only run the restore command once - if (backupTable.root.value.children.length == 0) { - - tlmgr_async_command("restore", lines => { - // lines.drop(1).foreach(println(_)) - val foo = lines.drop(1).map { l => - val fields = l.split("[ ():]", -1).filter(_.nonEmpty) - val pkgname = fields(0) - val rests: Array[Array[String]] = fields.drop(1).sliding(4, 4).toArray - if (rests.length == 1) { - val p = rests(0) - new TreeItem[TLBackup](new TLBackup(pkgname, p(0), p(1) + " " + p(2) + ":" + p(3))) - } else { - new TreeItem[TLBackup](new TLBackup(pkgname, "", "")) { - children = rests.map(p => new TreeItem[TLBackup]( - new TLBackup(pkgname, p(0), p(1) + " " + p(2) + ":" + p(3)) - )).toSeq + bkps.onChange( (obs,chs) => { + var 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", "", "")) { + 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 foo: Seq[TreeItem[TLBackup]] = versmap.tail.sortBy(-_._2.rev.value.toInt).map { q => + new TreeItem[TLBackup](q._2) + }.toSeq + new TreeItem[TLBackup](versmap.head._2) { + children = foo } + }).toArray.sortBy(_.value.value.name.value) + } + Platform.runLater { + backupTable.root = newroot + } + } + }) + pkgs.onChange( (obs,chs) => { + var 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) { + val pkgbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage] + val binbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackage]] + 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('.') + val pkgname = foo(0) + val binname = foo(1) + if (binbuf.keySet.contains(pkgname)) { + binbuf(pkgname) += pkg._2 + } else { + binbuf(pkgname) = ArrayBuffer[TLPackage](pkg._2) } + } else if (pkg._1 == "root") { + // ignore the dummy root element, + // only used for speeding up event handling + } else { + pkgbuf += pkg._2 } - val newroot = new TreeItem[TLBackup](new TLBackup("root", "", "")) { - children = foo + }) + // 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() } - Platform.runLater { - backupTable.root = newroot + // for ismixed we && all the installed status. If all are installed, we get true + val allinstalled = (kids :+ p).foldRight[Boolean](true)((k, b) => k.installed.value == "Installed" && b) + if (!allinstalled) { + // replace installed status with "Mixed" + new TreeItem[TLPackage](new TreeItem[TLPackage]( + new TLPackage(p.name.value, p.lrev.value.toString, p.rrev.value.toString, p.shortdesc.value, p.size.value.toString, "Mixed") + )) { + children = kids.map(new TreeItem[TLPackage](_)) + } + } else { + new TreeItem[TLPackage](p) { + children = kids.map(new TreeItem[TLPackage](_)) + } } - }) + } + } + Platform.runLater { + packageTable.root = new TreeItem[TLPackage](new TLPackage("root", "0", "0", "", "0", "")) { + expanded = true + children = viewkids.sortBy(_.value.value.name.value) + } + } + } + }) + upds.onChange( (obs, chs) => { + var 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) { + val infraAvailable = upds.keys.exists(_.startsWith("texlive.infra")) + val updatesAvailable = upds.keys.exists(p => !p.startsWith("texlive.infra")) + val newroot = new TreeItem[TLUpdate](new TLUpdate("root", "", "", "", "", "")) { + children = upds + .filter(_._1 != "root") + .map(p => new TreeItem[TLUpdate](p._2)) + .toArray + .sortBy(_.value.value.name.value) + } + Platform.runLater { + update_self_menu.disable = !infraAvailable + update_all_menu.disable = !updatesAvailable + updateTable.root = newroot + } + } + }) + + 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 + bkps.clear() + bkps ++= newbkps + trigger_update("bkps") + }) + } + 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 parse_one_update_line(l: String): TLUpdate = { + val fields = l.split("\t") + val pkgname = fields(0) + val status = fields(1) match { + case "d" => "Removed on server" + case "f" => "Forcibly removed" + case "u" => "Update available" + case "r" => "Local is newer" + case "a" => "New on server" + case "i" => "Not installed" + case "I" => "Reinstall" } + val localrev = fields(2) + val serverrev = fields(3) + val size = 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 shortdesc = tlpkg.shortdesc.value + new TLUpdate(pkgname, status, + localrev + { + if (lctanv != "-") s" ($lctanv)" else "" + }, + serverrev + { + if (rctanv != "-") s" ($rctanv)" else "" + }, + 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 => + l match { + case u if u.startsWith("location-url") => false + case u if u.startsWith("total-bytes") => false + case u if u.startsWith("end-of-header") => false + case u if u.startsWith("end-of-updates") => false + case u => true + } + }.map { l => + val foo = parse_one_update_line(l) + (foo.name.value, foo) + }.toMap + upds.clear() + upds ++= newupds + trigger_update("upds") + }) } + 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"))) + } + + def callback_show_pkg_info(pkg: String): Unit = { tlmgr_async_command(s"info $pkg", pkginfo => { // need to call runLater to go back to main thread! @@ -380,6 +499,19 @@ object ApplicationMain extends JFXApp { }) } + /** + * @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 = new Menu("TLCockpit") { items = List( @@ -389,9 +521,12 @@ object ApplicationMain extends JFXApp { new MenuItem("Update filename database ...") { onAction = (ae) => callback_run_external("mktexlsr") }, + new MenuItem("Update context filename database ...") { + onAction = (ae) => callback_run_external("mtxrun --generate") + }, // calling fmtutil kills JavaFX when the first sub-process (format) is called // I get loads of Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException - // new MenuItem("Rebuild all formats ...") { onAction = (ae) => callback_run_external("fmtutil --sys --all") }, + new MenuItem("Rebuild all formats ...") { onAction = (ae) => callback_run_external("fmtutil --sys --all") }, new MenuItem("Update font map database ...") { onAction = (ae) => callback_run_external("updmap --sys") }, @@ -500,44 +635,49 @@ object ApplicationMain extends JFXApp { } } val updateTable = { - val colName = new TreeTableColumn[TLPackage, String] { + val colName = new TreeTableColumn[TLUpdate, String] { text = "Package" cellValueFactory = { _.value.value.value.name } prefWidth = 150 } - val colDesc = new TreeTableColumn[TLPackage, String] { + val colStatus = new TreeTableColumn[TLUpdate, String] { + text = "Status" + cellValueFactory = { _.value.value.value.status } + prefWidth = 120 + } + val colDesc = new TreeTableColumn[TLUpdate, String] { text = "Description" cellValueFactory = { _.value.value.value.shortdesc } prefWidth = 300 } - val colLRev = new TreeTableColumn[TLPackage, Int] { + val colLRev = new TreeTableColumn[TLUpdate, String] { text = "Local rev" cellValueFactory = { _.value.value.value.lrev } prefWidth = 100 } - val colRRev = new TreeTableColumn[TLPackage, Int] { + val colRRev = new TreeTableColumn[TLUpdate, String] { text = "Remote rev" cellValueFactory = { _.value.value.value.rrev } prefWidth = 100 } - val colSize = new TreeTableColumn[TLPackage, Int] { + val colSize = new TreeTableColumn[TLUpdate, String] { text = "Size" cellValueFactory = { _.value.value.value.size } - prefWidth = 100 + prefWidth = 70 } - val table = new TreeTableView[TLPackage]( - new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) { + val table = new TreeTableView[TLUpdate]( + new TreeItem[TLUpdate](new TLUpdate("root","","","","","")) { expanded = false }) { - columns ++= List(colName, colDesc, colLRev, colRRev, colSize) + columns ++= List(colName, colStatus, colDesc, colLRev, colRRev, colSize) } - colDesc.prefWidth.bind(table.width - colName.width - colLRev.width - colRRev.width - colSize.width - 15) + colDesc.prefWidth.bind(table.width - colName.width - colLRev.width - colRRev.width - colSize.width - colStatus. width - 15) table.prefHeight = 300 table.vgrow = Priority.Always table.placeholder = new Label("No updates available") table.showRoot = false table.rowFactory = { _ => - val row = new TreeTableRow[TLPackage] {} + val row = new TreeTableRow[TLUpdate] {} val ctm = new ContextMenu( new MenuItem("Info") { onAction = (ae) => callback_show_pkg_info(row.item.value.name.value) @@ -552,7 +692,7 @@ object ApplicationMain extends JFXApp { }, new MenuItem("Update") { // onAction = (ae) => callback_run_text("update " + row.item.value.name.value) - onAction = (ae) => do_one_pkg("update", row.item.value.name.value) + onAction = (ae) => callback_update(row.item.value.name.value) } ) row.contextMenu = ctm @@ -609,9 +749,6 @@ object ApplicationMain extends JFXApp { }, new MenuItem("Remove") { onAction = (ae) => do_one_pkg("remove", row.item.value.name.value) - }, - new MenuItem("Update") { - onAction = (ae) => do_one_pkg("update", row.item.value.name.value) } ) row.contextMenu = ctm @@ -665,14 +802,14 @@ object ApplicationMain extends JFXApp { vgrow = Priority.Always tabs = Seq( new Tab { - text = "Updates" + text = "Packages" closable = false - content = updateTable + content = packageTable }, new Tab { - text = "Packages" + text = "Updates" closable = false - content = packageTable + content = updateTable }, new Tab { text = "Backups" @@ -684,8 +821,12 @@ object ApplicationMain extends JFXApp { pkgstabs.selectionModel().selectedItem.onChange( (a,b,c) => { if (a.value.text() == "Backups") { - callback_populate_backup_tab() - // println("Entering Backup Tab") + if (backupTable.root.value.children.length == 0) + update_bkps_list() + } else if (a.value.text() == "Updates") { + // only update if not done already + if (updateTable.root.value.children.length == 0) + update_upds_list() } } ) @@ -738,9 +879,22 @@ object ApplicationMain extends JFXApp { } */ + var lineUpdateFunc = { (l: String) => } // println(s"DEBUG: got ==$l== from tlmgr") } + + val bar = Future { + while (true) { + val s = outputLine.take + try { + lineUpdateFunc(s) + } catch { + case e: Exception => println("SOMETHING BAD IN lineUpdateFUnc happened: " + e + "\n") + } + } + } tlmgr.start_process() tlmgr.get_output_till_prompt() - update_pkg_lists() // this is async done + // update_pkg_lists_to_be_renamed() // this is async done + update_pkgs_lists() } // object ApplicationMain 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 new file mode 100644 index 00000000000..427ad722ceb --- /dev/null +++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLUpdate.scala @@ -0,0 +1,15 @@ +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, 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 360ad9bc433..c65058e169a 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 @@ -8,7 +8,7 @@ import scala.collection.mutable.ArrayBuffer import scala.concurrent.SyncVar import scala.sys.process.{Process, ProcessBuilder, ProcessIO} -class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) { +class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit, updline: 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 @@ -26,12 +26,18 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) { 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) { + + 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) } } @@ -129,11 +135,13 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) { while (true) { line = reader.readLine if (line == null) { + // println("DDD: got null line") // println("Did read NULL from stdin giving up") // error = true } else { // println("did read " + line + " from process") outputString.put(line) + updline(line) } } stdOut.close() |