From 3b69cd9c67341466bf757570a0e3b0f39c207c71 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Sat, 20 Jan 2018 14:02:47 +0000 Subject: update tlcockpit to version 0.8 git-svn-id: svn://tug.org/texlive/trunk@46391 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/source/support/tlcockpit/TODO | 4 + .../texmf-dist/source/support/tlcockpit/build.sbt | 4 +- .../src/main/resources/TLCockpit/spinner-small.gif | Bin 0 -> 16225 bytes .../src/main/resources/TLCockpit/spinner.gif | Bin 0 -> 60549 bytes .../tlcockpit/src/main/resources/logback.xml | 11 + .../src/main/scala/TLCockpit/ApplicationMain.scala | 530 +++++++++++++++------ .../src/main/scala/TLCockpit/LocationDialog.scala | 37 +- .../src/main/scala/TLCockpit/OptionsDialog.scala | 13 +- .../src/main/scala/TLCockpit/PaperDialog.scala | 13 +- .../src/main/scala/TLCockpit/PkgInfoDialog.scala | 44 +- .../main/scala/TLCockpit/TLDisplayClasses.scala | 6 + .../tlcockpit/src/main/scala/TLCockpit/Utils.scala | 6 + .../src/main/scala/TeXLive/JsonProtocol.scala | 7 + .../tlcockpit/src/main/scala/TeXLive/OsTools.scala | 5 + .../src/main/scala/TeXLive/TLClasses.scala | 19 + .../src/main/scala/TeXLive/TlmgrProcess.scala | 37 +- .../tlcockpit/src/main/scala/TeXLive/package.scala | 5 + 17 files changed, 553 insertions(+), 188 deletions(-) create mode 100644 Master/texmf-dist/source/support/tlcockpit/TODO create mode 100644 Master/texmf-dist/source/support/tlcockpit/src/main/resources/TLCockpit/spinner-small.gif create mode 100644 Master/texmf-dist/source/support/tlcockpit/src/main/resources/TLCockpit/spinner.gif create mode 100644 Master/texmf-dist/source/support/tlcockpit/src/main/resources/logback.xml (limited to 'Master/texmf-dist/source') diff --git a/Master/texmf-dist/source/support/tlcockpit/TODO b/Master/texmf-dist/source/support/tlcockpit/TODO new file mode 100644 index 00000000000..8597b267987 --- /dev/null +++ b/Master/texmf-dist/source/support/tlcockpit/TODO @@ -0,0 +1,4 @@ +TODO +==== +(see TODOs in the code) + diff --git a/Master/texmf-dist/source/support/tlcockpit/build.sbt b/Master/texmf-dist/source/support/tlcockpit/build.sbt index 032d4016c04..5f3770f7930 100644 --- a/Master/texmf-dist/source/support/tlcockpit/build.sbt +++ b/Master/texmf-dist/source/support/tlcockpit/build.sbt @@ -1,11 +1,13 @@ name := "tlcockpit" -version := "0.7" +version := "0.8" scalaVersion := "2.12.3" libraryDependencies += "org.scalafx" % "scalafx_2.12" % "8.0.102-R11" libraryDependencies += "io.spray" %% "spray-json" % "1.3.3" +libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2" +libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" mainClass in assembly := Some("TLCockpit.ApplicationMain") diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/resources/TLCockpit/spinner-small.gif b/Master/texmf-dist/source/support/tlcockpit/src/main/resources/TLCockpit/spinner-small.gif new file mode 100644 index 00000000000..10dfdccdbd4 Binary files /dev/null and b/Master/texmf-dist/source/support/tlcockpit/src/main/resources/TLCockpit/spinner-small.gif differ diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/resources/TLCockpit/spinner.gif b/Master/texmf-dist/source/support/tlcockpit/src/main/resources/TLCockpit/spinner.gif new file mode 100644 index 00000000000..bff38ea7a93 Binary files /dev/null and b/Master/texmf-dist/source/support/tlcockpit/src/main/resources/TLCockpit/spinner.gif differ diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/resources/logback.xml b/Master/texmf-dist/source/support/tlcockpit/src/main/resources/logback.xml new file mode 100644 index 00000000000..ee899bf75de --- /dev/null +++ b/Master/texmf-dist/source/support/tlcockpit/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file 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 f37dc10806e..67b60576944 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 @@ -1,11 +1,13 @@ // TLCockpit -// Copyright 2017 Norbert Preining +// Copyright 2017-2018 Norbert Preining // Licensed according to GPLv3+ // // Front end for tlmgr package TLCockpit +import javafx.scene.Node + import TLCockpit.Utils._ import TeXLive._ @@ -13,6 +15,7 @@ import scala.collection.{immutable, mutable} import scala.collection.mutable.ArrayBuffer import scala.concurrent.{Future, Promise, SyncVar} import scala.concurrent.ExecutionContext.Implicits.global +import scala.io.Source import scala.util.{Failure, Success} import scala.sys.process._ import scalafx.beans.property.BooleanProperty @@ -47,11 +50,40 @@ import scalafx.collections.ObservableMap import spray.json._ import TeXLive.JsonProtocol._ +// logging +import com.typesafe.scalalogging.LazyLogging +import ch.qos.logback.classic.{Level,Logger} +import org.slf4j.LoggerFactory -object ApplicationMain extends JFXApp { +object ApplicationMain extends JFXApp with LazyLogging { val version: String = getClass.getPackage.getImplementationVersion + // parse command line arguments + // nothing => INFO + // -q WARN -qq ERROR + // -d => DEBUG -dd => TRACE + val cmdlnlog: Int = parameters.unnamed.map( { + case "-d" => Level.DEBUG_INT + case "-dd" => Level.TRACE_INT + case "-q" => Level.WARN_INT + case "-qq" => Level.ERROR_INT + case _ => -1 + } ).foldLeft(Level.OFF_INT)(scala.math.min) + if (cmdlnlog == -1) { + // Unknown log level has been passed in, error out + Console.err.println("Unsupported command line argument passed in, terminating.") + Platform.exit() + sys.exit(0) + } + // if nothing has been passed on the command line, use INFO + val newloglevel: Int = if (cmdlnlog == Level.OFF_INT) Level.INFO_INT else cmdlnlog + + LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME). + asInstanceOf[Logger].setLevel(Level.toLevel(newloglevel)) + + logger.trace("starting program tlcockpit") + var tlmgrBusy = BooleanProperty(false) // necessary action when Window is closed with X or some other operation @@ -61,8 +93,24 @@ object ApplicationMain extends JFXApp { val iconImage = new Image(getClass.getResourceAsStream("tlcockpit-48.jpg")) val logoImage = new Image(getClass.getResourceAsStream("tlcockpit-128.jpg")) + val busyImage = new Image(getClass.getResourceAsStream("spinner-small.gif")) + val msgFont = new Font(30f) + + val busySpinner: ImageView = new ImageView(busyImage) { + // scaleX = 0.3 + // scaleY = 0.3 + } + + def SpinnerPlaceHolder(txt: String): Node = { + val tmp = new Label(txt) + tmp.wrapText = true + tmp.opacity = 0.4f + tmp.font = msgFont + tmp.graphic = busySpinner + tmp + } - val tlpkgs: ObservableMap[String, TLPackage] = ObservableMap[String,TLPackage]() + val tlpkgs: ObservableMap[String, TLPackageShort] = ObservableMap[String,TLPackageShort]() 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)* @@ -104,7 +152,7 @@ object ApplicationMain extends JFXApp { }) val update_all_menu: MenuItem = new MenuItem("Update all") { - val cmd = "--all" + { + val cmd: String = "--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 "" } @@ -147,10 +195,29 @@ object ApplicationMain extends JFXApp { cmdline.onKeyPressed = { (ae: KeyEvent) => if (ae.code == KeyCode.Enter) callback_run_cmdline() } - val outputLine = new SyncVar[String] - val errorLine = new SyncVar[String] + + // read the perl dump of ctan mirrors by converting it to JSON code and parsing it + def parse_ctan_mirrors(tlroot: String): Map[String,Map[String,Seq[String]]] = { + try { + val fileName = tlroot + "/tlpkg/installer/ctan-mirrors.pl" + val foo: String = Source.fromFile(fileName).getLines.mkString("") + val jsonMirrorString = foo.substring(10).replace("=>", ":").replace("""'""", "\"").replace(";", "") + val ast = jsonMirrorString.parseJson + ast.convertTo[Map[String, Map[String, Map[String, Int]]]].map { + contpair => + (contpair._1, contpair._2.map { + countrypair => (countrypair._1, countrypair._2.keys.toSeq) + }) + } + } catch { case e: Exception => + logText.append("Cannot find or parse ctan-mirrors.pl") + logger.debug("Cannot find or parse ctan-mirrors.pl") + Map[String,Map[String,Seq[String]]]() + } + } + def callback_quit(): Unit = { tlmgr.cleanup() Platform.exit() @@ -199,22 +266,29 @@ object ApplicationMain extends JFXApp { OutputBufferIndex = 0 } - def callback_run_external(s: String, unbuffered: Boolean = true): Unit = { + def callback_run_external(ss: Array[String], unbuffered: Boolean = true): Unit = { outputText.clear() // logText.clear() outerrpane.expanded = true outerrtabs.selectionModel().select(0) - outputText.append(s"Running $s" + (if (unbuffered) " (unbuffered)" else " (buffered)")) + // outputText.append(s"Running ${ss.mkString(" ")}" + (if (unbuffered) " (unbuffered)" else " (buffered)")) val foo = Future { - s ! ProcessLogger( - line => if (unbuffered) Platform.runLater( outputText.append(line) ) - else OutputBuffer.synchronized( OutputBuffer.append(line + "\n") ), - line => Platform.runLater( logText.append(line) ) - ) + ss.foreach { s => + Platform.runLater { + outputText.append(s"Running ${s}" + (if (unbuffered) " (unbuffered)" else " (buffered)")) + actionLabel.text = s"[${s}]" + } + s ! ProcessLogger( + line => if (unbuffered) Platform.runLater(outputText.append(line)) + else OutputBuffer.synchronized(OutputBuffer.append(line + "\n")), + line => Platform.runLater(logText.append(line)) + ) + } } foo.onComplete { case Success(ret) => Platform.runLater { + actionLabel.text = "" outputText.append(OutputBuffer.slice(OutputBufferIndex,OutputBuffer.length).mkString("")) outputText.append("Completed") reset_output_buffer() @@ -222,11 +296,12 @@ object ApplicationMain extends JFXApp { } case Failure(t) => Platform.runLater { + actionLabel.text = "" outputText.append(OutputBuffer.slice(OutputBufferIndex,OutputBuffer.length).mkString("")) outputText.append("Completed") reset_output_buffer() outputfield.scrollTop = Double.MaxValue - errorText.append("An ERROR has occurred running $s: " + t.getMessage) + errorText.append(s"An ERROR has occurred running one of ${ss.mkString(" ")}: " + t.getMessage) errorfield.scrollTop = Double.MaxValue outerrpane.expanded = true outerrtabs.selectionModel().select(2) @@ -240,14 +315,36 @@ object ApplicationMain extends JFXApp { title = "About TLCockpit" graphic = new ImageView(logoImage) headerText = "TLCockpit version " + version + "\n\nManage your TeX Live with speed!" - contentText = "Copyright 2017 Norbert Preining\nLicense: GPL3+\nSources: https://github.com/TeX-Live/tlcockpit" + contentText = "Copyright 2017-2018 Norbert Preining\nLicense: GPL3+\nSources: https://github.com/TeX-Live/tlcockpit" }.showAndWait() } - def set_line_update_function(mode: String) = { + // Output of update --self + /* +tlmgr> +update --self +location-url /home/norbert/public_html/tlnet /home/norbert/public_html/tlcritical /home/norbert/Domains/server/texlive.info/contrib/2017 /home/norbert/public_html/tltexjp +total-bytes 381087 +end-of-header +texlive.infra u 4629 46295 381087 ??:?? ??:?? tlcritical - - +end-of-updates +STDERR running mktexlsr ... +STDERR done running mktexlsr. +STDERR running mtxrun --generate ... +STDERR done running mtxrun --generate. +OK +STDOUT (with patch STDERR) tlmgr has been updated, restarting! +protocol 1 +tlmgr> + + The problem with the update function lies in the + protocol 1 + which is not accepted/expected by the update function! + */ + def set_line_update_function(mode: String): Unit = { var prevName = "" stdoutLineUpdateFunc = (l:String) => { - // println("DEBUG line update: " + l + "=") + logger.trace("DEBUG line update: " + l + "=") l match { case u if u.startsWith("location-url") => None case u if u.startsWith("total-bytes") => None @@ -286,10 +383,21 @@ object ApplicationMain extends JFXApp { packageTable.refresh() } if (u.startsWith("end-of-updates")) { + if (mode == "update") { + Platform.runLater { + updateTable.placeholder = SpinnerPlaceHolder("Post actions running") + actionLabel.text = "[post actions running]" + } + } // nothing to be done, all has been done above - // println("DEBUG got end of updates") + logger.debug("DEBUG got end of updates") + // } else if (u.startsWith("protocol ")) { + // logger.debug("Got protocol line, seems tlmgr got updated and restarted!") + // // nothing else to be done + // } else if (u.startsWith("tlmgr has been updated, restarting")) { + // logger.debug("tlmgr got updated and restarted, ignoring output") } else { - // println("DEBUG getting update line") + logger.debug("DEBUG getting update line") prevName = if (mode == "update") { val foo = parse_one_update_line(l) val pkgname = foo.name.value @@ -315,10 +423,14 @@ object ApplicationMain extends JFXApp { } def callback_update(s: String): Unit = { + val prevph = updateTable.placeholder.value set_line_update_function("update") - val cmd = if (s == "--self") "update --self" else s"update $s" + val cmd = if (s == "--self") "update --self --no-restart" else s"update $s" tlmgr_send(cmd, (a,b) => { stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc + Platform.runLater { + updateTable.placeholder = prevph + } if (s == "--self") { reinitialize_tlmgr() // this doesn't work seemingly @@ -329,11 +441,15 @@ object ApplicationMain extends JFXApp { def callback_remove(pkg: String): Unit = { set_line_update_function("remove") - tlmgr_send(s"remove $pkg", (_, _) => { stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc }) + 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 }) + tlmgr_send(s"install $pkg", (_,_) => { + stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc + }) } @@ -342,6 +458,7 @@ object ApplicationMain extends JFXApp { tlpkgs(str).lrev = rev.toLong pkgs(str).lrev = ObjectProperty[Int](rev.toInt) packageTable.refresh() + Platform.runLater { actionLabel.text = "[running post actions]" } }) } @@ -352,7 +469,7 @@ object ApplicationMain extends JFXApp { case ObservableMap.Remove(k, v) => k.toString == "root" } if (doit) { - // println("DEBUG bkps.onChange called new length = " + bkps.keys.toArray.length) + logger.debug("DEBUG bkps.onChange called new length = " + bkps.keys.toArray.length) val newroot = new TreeItem[TLBackupDisplay](new TLBackupDisplay("root", "", "")) { children = bkps .filter(_._1 != "root") @@ -461,7 +578,7 @@ object ApplicationMain extends JFXApp { case ObservableMap.Remove(k,v) => false } if (doit) { - // println("DEBUG: entering pkgs.onChange") + logger.debug("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]] @@ -499,9 +616,9 @@ object ApplicationMain extends JFXApp { 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)) + p._2.foreach(f => logger.trace("-> " + f.name.value)) } else { - // println("DEBUG Moving " + p._2.head.name.value + " up to pkgbuf " + p._1) + logger.trace("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 @@ -516,7 +633,7 @@ object ApplicationMain extends JFXApp { 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(_)) + colbuf(pkg._1) ++= foo.filter(pkgbuf.contains).map(pkgbuf(_)) } } else if (pkg._1 == "root") { // do nothing @@ -531,7 +648,7 @@ object ApplicationMain extends JFXApp { view_pkgs_by_names(pkgbuf, binbuf) else view_pkgs_by_collections(pkgbuf, binbuf, colbuf) - // println("DEBUG: leaving pkgs.onChange before runLater") + logger.debug("DEBUG: leaving pkgs.onChange before runLater") Platform.runLater { packageTable.root = new TreeItem[TLPackageDisplay](new TLPackageDisplay("root", "0", "0", "", "0", "")) { expanded = true @@ -578,12 +695,8 @@ object ApplicationMain extends JFXApp { } 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 + backupTable.placeholder = SpinnerPlaceHolder("Loading backups") tlmgr_send("restore --json", (status, lines) => { val jsonAst = lines.mkString("").parseJson val backups: Map[String, Map[String, TLBackupDisplay]] = @@ -619,14 +732,13 @@ object ApplicationMain extends JFXApp { 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 + packageTable.placeholder = SpinnerPlaceHolder("Loading database") tlmgr_send("info --json", (status, lines) => { + logger.debug(s"load tlpdb update pkgs: got status ${status}") + logger.trace(s"load tlpdb update pkgs: got lines = " + lines.head) val jsonAst = lines.mkString("").parseJson tlpkgs.clear() tlpkgs ++= jsonAst.convertTo[List[TLPackage]].map { p => (p.name, p)} @@ -634,9 +746,40 @@ object ApplicationMain extends JFXApp { packageTable.placeholder = prevph }) } + */ + + def load_tlpdb_update_pkgs_view_no_json():Unit = { + val prevph = packageTable.placeholder.value + packageTable.placeholder = SpinnerPlaceHolder("Loading database") + tlmgr_send("info --data name,localrev,remoterev,category,size,installed,depends,shortdesc", (status, lines) => { + logger.debug(s"load tlpdb update (no json) pkgs: got status ${status}") + logger.trace(s"load tlpdb update (no json) pkgs: got lines = " + lines.head) + val newtlpkgs: Map[String, TLPackageShort] = lines.map(l => { + val fields = l.split(",",8) + val pkgname = fields(0) + val shortdesc = fields(7).stripPrefix(""""""").stripSuffix(""""""").replaceAll("""\\"""",""""""") + val lrev = fields(1).toLong + val rrev = fields(2).toLong + val cat = fields(3) + val size = fields(4).toLong + val installed = fields(5) == "1" + val depends = fields(6).split(":").toList + TLPackageShort(pkgname, if (shortdesc == "") None else Some(shortdesc), lrev, rrev, cat, depends, installed, lrev > 0) + }).map{ p => + logger.trace("Constructed TLPackage: " + p) + (p.name, p) + }.toMap + tlpkgs.clear() + tlpkgs ++= newtlpkgs + update_pkgs_view() + packageTable.placeholder = prevph + }) + } def parse_one_update_line(l: String): TLUpdateDisplay = { + logger.debug(s"Got update line >>${l}") val fields = l.split("\t") + logger.debug(s"Splitting into ${fields}") val pkgname = fields(0) val status = fields(1) match { case "d" => "Removed on server" @@ -668,23 +811,17 @@ object ApplicationMain extends JFXApp { } 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 + updateTable.placeholder = SpinnerPlaceHolder("Loading updates") 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 - case u if u.startsWith("end-of-header") => false - case u if u.startsWith("end-of-updates") => false - case u => true - } + logger.debug(s"got updates length ${lines.length}") + logger.trace(s"tlmgr last output = ${lines}") + val newupds: Map[String, TLUpdateDisplay] = lines.filter { + 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) @@ -702,7 +839,7 @@ object ApplicationMain extends JFXApp { } def trigger_update(s:String): Unit = { - // println("DEBUG: Triggering update of " + s) + logger.debug("DEBUG: Triggering update of " + s) if (s == "pkgs") { pkgs("root") = new TLPackageDisplay("root", "0", "0", "", "0", "") } else if (s == "upds") { @@ -767,15 +904,15 @@ object ApplicationMain extends JFXApp { items = List( new MenuItem("Update filename databases ...") { onAction = (ae) => { - callback_run_external("mktexlsr") - callback_run_external("mtxrun --generate") + callback_run_external(Array("mktexlsr", "mtxrun --generate")) + // callback_run_external("mtxrun --generate") } }, // too many lines are quickly output -> GUI becomes hanging until // all the callbacks are done - call fmtutil with unbuffered = false - new MenuItem("Rebuild all formats ...") { onAction = (ae) => callback_run_external("fmtutil --sys --all", false) }, + new MenuItem("Rebuild all formats ...") { onAction = (ae) => callback_run_external(Array("fmtutil --sys --all"), false) }, new MenuItem("Update font map database ...") { - onAction = (ae) => callback_run_external("updmap --sys") + onAction = (ae) => callback_run_external(Array("updmap --sys")) } ) } @@ -843,10 +980,10 @@ object ApplicationMain extends JFXApp { val dg = new PaperDialog(paperconfs) dg.showAndWait() match { case Some(newPapers) => - // println(s"Got result ${newPapers}") + logger.debug(s"Got result ${newPapers}") // collect changed settings val changedPapers = newPapers.filter(p => currentPapers(p._1) != p._2) - // println(s"Got changed papers ${changedPapers}") + logger.debug(s"Got changed papers ${changedPapers}") changedPapers.foreach(p => { tlmgr_send(s"paper ${p._1} paper ${p._2}", (_,_) => None) }) @@ -856,6 +993,33 @@ object ApplicationMain extends JFXApp { }) } + def callback_pkg_info(pkg: String) = { + tlmgr_send(s"info --json ${pkg}", (status, lines) => { + try { + val jsonAst = lines.mkString("").parseJson + val tlpkgs = jsonAst.convertTo[List[TLPackage]] + Platform.runLater { + new PkgInfoDialog(tlpkgs.head).showAndWait() + } + } catch { + case foo : spray.json.DeserializationException => + new Alert(AlertType.Warning) { + initOwner(stage) + title = "Warning" + headerText = s"Cannot display information for ${pkg}" + contentText = s"We couldn't parse the output of\ntlmgr info --json ${pkg}\n" + }.showAndWait() + case bar : ArrayIndexOutOfBoundsException => + new Alert(AlertType.Warning) { + initOwner(stage) + title = "Warning" + headerText = s"Cannot find information for ${pkg}" + contentText = s"We couldn't find information for ${pkg}\n" + }.showAndWait() + } + }) + } + val optionsMenu: Menu = new Menu("Options") { items = List( new MenuItem("General ...") { onAction = (ae) => callback_general_options() }, @@ -868,9 +1032,6 @@ object ApplicationMain extends JFXApp { new CheckMenuItem("Disable auto-removal of server-deleted packages") { disable = true } */ ) } - val statusMenu: Menu = new Menu("Status: Idle") { - disable = true - } val expertPane: TitledPane = new TitledPane { text = "Experts only" collapsible = true @@ -933,12 +1094,15 @@ object ApplicationMain extends JFXApp { 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.placeholder = new Label("No updates available") { + opacity = 0.4f + font = msgFont + } table.showRoot = false table.rowFactory = { _ => val row = new TreeTableRow[TLUpdateDisplay] {} val infoMI = new MenuItem("Info") { - onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait() + onAction = (ae) => callback_pkg_info(row.item.value.name.value) } val updateMI = new MenuItem("Update") { onAction = (ae) => callback_update(row.item.value.name.value) @@ -1001,7 +1165,7 @@ object ApplicationMain extends JFXApp { table.rowFactory = { p => val row = new TreeTableRow[TLPackageDisplay] {} val infoMI = new MenuItem("Info") { - onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait() + onAction = (ae) => callback_pkg_info(row.item.value.name.value) } val installMI = new MenuItem("Install") { onAction = (ae) => callback_install(row.item.value.name.value) @@ -1052,7 +1216,7 @@ object ApplicationMain extends JFXApp { val row = new TreeTableRow[TLBackupDisplay] {} val ctm = new ContextMenu( new MenuItem("Info") { - onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait() + onAction = (ae) => callback_pkg_info(row.item.value.name.value) }, new MenuItem("Restore") { onAction = (ae) => callback_restore(row.item.value.name.value, row.item.value.rev.value) @@ -1068,6 +1232,28 @@ object ApplicationMain extends JFXApp { searchEntry.onKeyPressed = { (ae: KeyEvent) => if (ae.code == KeyCode.Enter) update_pkgs_view() } + val statusLabel = new Label("Idle") + val actionLabel = new Label("") { + hgrow = Priority.Always + maxWidth = Double.MaxValue + } + val statusBox = new HBox { + children = Seq( + new Label("Tlmgr status:") { + vgrow = Priority.Always + alignmentInParent = Pos.CenterLeft + }, + statusLabel, + actionLabel + ) + maxWidth = Double.MaxValue + hgrow = Priority.Always + alignment = Pos.Center + alignmentInParent = Pos.CenterLeft + padding = Insets(10) + spacing = 10 + } + val searchBox = new HBox { children = Seq( new Label("Search:") { @@ -1086,7 +1272,8 @@ object ApplicationMain extends JFXApp { } ) alignment = Pos.Center - padding = Insets(10) + // do not add padding at the bottom as we have one from the status field + padding = Insets(10,10,0,10) spacing = 10 } val pkgsContainer = new VBox { @@ -1120,21 +1307,27 @@ object ApplicationMain extends JFXApp { val menuBar: MenuBar = new MenuBar { useSystemMenuBar = true // menus.addAll(mainMenu, optionsMenu, helpMenu) - menus.addAll(mainMenu, pkgsMenu, toolsMenu, optionsMenu, statusMenu) + menus.addAll(mainMenu, pkgsMenu, toolsMenu, optionsMenu) } + var updLoaded = false + var bckLoaded = false pkgstabs.selectionModel().selectedItem.onChange( (a,b,c) => { if (a.value.text() == "Backups") { - if (backupTable.root.value.children.length == 0) + if (!bckLoaded) { load_backups_update_bkps_view() - menuBar.menus = Seq(mainMenu, toolsMenu, optionsMenu, statusMenu) + bckLoaded = true + } + menuBar.menus = Seq(mainMenu, toolsMenu, optionsMenu) } else if (a.value.text() == "Updates") { // only update if not done already - if (updateTable.root.value.children.length == 0) + if (!updLoaded) { load_updates_update_upds_view() - menuBar.menus = Seq(mainMenu, updMenu, toolsMenu, optionsMenu, statusMenu) + updLoaded = true + } + menuBar.menus = Seq(mainMenu, updMenu, toolsMenu, optionsMenu) } else if (a.value.text() == "Packages") { - menuBar.menus = Seq(mainMenu, pkgsMenu, toolsMenu, optionsMenu, statusMenu) + menuBar.menus = Seq(mainMenu, pkgsMenu, toolsMenu, optionsMenu) } } ) @@ -1152,7 +1345,7 @@ object ApplicationMain extends JFXApp { val topBox = menuBar val centerBox = new VBox { padding = Insets(10) - children = List(pkgstabs, expertPane, outerrpane) + children = List(pkgstabs, statusBox, expertPane, outerrpane) } new BorderPane { // padding = Insets(20) @@ -1176,14 +1369,32 @@ object ApplicationMain extends JFXApp { def initialize_tlmgr(): TlmgrProcess = { tlmgrBusy.value = true + actionLabel.text = "[initializing tlmgr]" + // create new sync vars for each process + val outputLine = new SyncVar[String] + val errorLine = new SyncVar[String] + val tt = new TlmgrProcess( - (s: String) => outputLine.put(s), - (s: String) => errorLine.put(s) + (s: String) => { + logger.trace(s"outputline put ${s}") + outputLine.put(s) + }, + (s: String) => { + logger.trace(s"errorline put ${s}") + errorLine.put(s) + } ) + if (!tt.start_process()) { + logger.debug("Cannot start tlmgr process, terminating!") + Platform.exit() + sys.exit(1) + } + logger.debug("initialize_tlmgr: sleeping after starting process") + Thread.sleep(1000) /* val tlmgrMonitor = Future { while (true) { if (!tlmgr.isAlive) { - println("TLMGR HAS DIED!!!!") + logger.debug("TLMGR HAS DIED!!!!") // Platform.exit() // sys.exit(1) } @@ -1194,25 +1405,39 @@ object ApplicationMain extends JFXApp { val tlmgrOutput = ArrayBuffer[String]() var tlmgrStatus = "" var alive = true + logger.debug("initialize tlmgr: starting stdout reader thread") while (alive) { + logger.trace("stdout reader before outputLine.take") val s = outputLine.take + logger.trace("stdout reader after outputLine.take") if (s == null) { alive = false - // println("GOT NULL from outputline tlmgr dead???") + logger.debug("got null from stdout, tlmgr seems to be terminated for restart") + if (!currentPromise.isCompleted) { + logger.debug(s"Fulfilling remaining open promise with ${tlmgrStatus} and ${tlmgrOutput.toArray.mkString}") + currentPromise.success((tlmgrStatus, tlmgrOutput.toArray)) + } + tlmgrStatus = "" + tlmgrOutput.clear() + tlmgrBusy.value = false } else { - //println(s"DEBUG: got " + s) + logger.trace(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)) + logger.trace(s"Fulfilling current promise with ${tlmgrStatus} and ${tlmgrOutput.toArray.mkString}!") + if (!currentPromise.isCompleted) { + currentPromise.success((tlmgrStatus, tlmgrOutput.toArray)) + } tlmgrStatus = "" tlmgrOutput.clear() tlmgrBusy.value = false if (pendingJobs.nonEmpty) { + logger.debug("pending Job found!") val nextCmd = pendingJobs.dequeue() + logger.debug(s"running ${nextCmd._1}") tlmgr_run_one_cmd(nextCmd._1, nextCmd._2) } } else { @@ -1221,31 +1446,26 @@ object ApplicationMain extends JFXApp { } } } + logger.debug("initialize tlmgr: finishing stdout reader thread") } - tlmgrBusy.onChange({ Platform.runLater{ statusMenu.text = "Status: " + (if (tlmgrBusy.value) "Busy" else "Idle") }}) + tlmgrBusy.onChange({ + Platform.runLater { + statusLabel.text = if (tlmgrBusy.value) "Busy" else "Idle" + if (!tlmgrBusy.value) + actionLabel.text = "" + } + }) + 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) - } + logger.debug(s"tlmgr stdout reader terminated: ${value}") case Failure(e) => - errorText += "lineUpdateFunc(stdout) thread got interrupted -- probably old tlmgr, ignoring it!" - e.printStackTrace + logger.debug(s"tlmgr stdout reader terminated with error: ${e}") } + val stderrFuture = Future { var alive = true + logger.debug("initialize tlmgr: starting stderr reader thread") while (alive) { val s = errorLine.take if (s == null) @@ -1253,29 +1473,67 @@ object ApplicationMain extends JFXApp { else stderrLineUpdateFunc(s) } + logger.debug("initialize tlmgr: finishing stderr reader thread") } stderrFuture.onComplete { - case Success(value) => // println(s"tlmgr stderr reader terminated: ${value}") + case Success(value) => + logger.debug(s"tlmgr stderr reader terminated: ${value}") case Failure(e) => - errorText += "lineUpdateFunc(stderr) thread got interrupted -- probably old tlmgr, ignoring it!" - e.printStackTrace + logger.debug(s"tlmgr stderr reader terminated with failure: ${e}") } + tt } + def tlmgr_post_init() = { + + // check for tlmgr revision + tlmgr_send("version", (status,output) => { + logger.debug(s"Callback after version, got ${status} and ${output.mkString("\n")}") + output.foreach ( l => { + if (l.startsWith("revision ")) { + val tlmgrRev = l.stripPrefix("revision ") + if (tlmgrRev == "unknown") { + logger.debug("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() + logger.debug("Before loading tlpdb") + load_tlpdb_update_pkgs_view_no_json() + logger.debug("after loading tlpdb") + }) + } + def tlmgr_run_one_cmd(s: String, onCompleteFunc: (String, Array[String]) => Unit): Unit = { currentPromise = Promise[(String, Array[String])]() tlmgrBusy.value = true + actionLabel.text = s"[${s}]" currentPromise.future.onComplete { case Success((a, b)) => - // println("DEBUG current future completed!") + logger.debug("tlmgr run one cmd: current future completed!") Platform.runLater { + logger.debug("tlmgr run one cmd: running on complete function") onCompleteFunc(a, b) } case Failure(ex) => + logger.debug("Running tlmgr command did no succeed" + ex.getMessage) errorText += "Running a tlmgr command did not succeed: " + ex.getMessage } - // println(s"DEBUG sending ${s}") + logger.debug(s"sending to tlmgr: ${s}") tlmgr.send_command(s) } @@ -1284,63 +1542,47 @@ object ApplicationMain extends JFXApp { outputText.clear() outerrpane.expanded = false if (!currentPromise.isCompleted) { - // println(s"DEBUG tlmgr busy, put onto pending jobs: $s") + logger.debug(s"tlmgr busy, put onto pending jobs: $s") + logger.debug("Currently running job: " + currentPromise) pendingJobs += ((s, onCompleteFunc)) } else { + logger.debug(s"tlmgr_send sending ${s}") tlmgr_run_one_cmd(s, onCompleteFunc) } } def reinitialize_tlmgr(): Unit = { + logger.debug("reinit tlmgr: entering, clearing pending jobs") + pendingJobs.clear() + logger.debug("reinit tlmgr: cleared pending jobs") + // if (!currentPromise.isCompleted) { + // logger.debug("reinit tlmgr: current promise not complete, completing it") + // currentPromise.success(("",Array[String]())) + // logger.debug("reinit tlmgr: after completing current promise") + // } + logger.debug("reinit tlmgr: cleaning up tlmgr") tlmgr.cleanup() - // Thread.sleep(1000) + logger.debug("reinit tlmgr: sleeping 1s") + Thread.sleep(1000) + logger.debug("reinit tlmgr: initializaing new tlmgr") tlmgr = initialize_tlmgr() + logger.debug("reinit tlmgr: finished") tlmgr_post_init() - pkgstabs.getSelectionModel().select(0) + logger.debug("reinit tlmgr: post init done") + updLoaded = false + pkgstabs.getSelectionModel.select(0) } - def tlmgr_post_init():Unit = { - 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() - }) - } - - def defaultStdoutLineUpdateFunc(l: String) : Unit = { } // println(s"DEBUG: got ==$l== from tlmgr") } + def defaultStdoutLineUpdateFunc(l: String) : Unit = { logger.trace(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 index 30088ff69a6..b762a811a05 100644 --- 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 @@ -1,7 +1,14 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TLCockpit -import TLCockpit.ApplicationMain.{not_implemented_info, stage} +import TLCockpit.ApplicationMain.stage import TeXLive.{OsTools, TLOption} +import com.typesafe.scalalogging.LazyLogging import scalafx.Includes._ import scalafx.event.ActionEvent @@ -10,7 +17,7 @@ import scalafx.scene.Node import scalafx.scene.control._ import scalafx.scene.layout._ -class LocationDialog(locs: Map[String,String]) { +class LocationDialog(locs: Map[String,String]) extends LazyLogging { case class Result(selected: Map[String, String]) @@ -31,6 +38,8 @@ class LocationDialog(locs: Map[String,String]) { var crow = 0 + val mirrorlocs: Map[String, Map[String, Seq[String]]] = TLCockpit.ApplicationMain.parse_ctan_mirrors(TLCockpit.ApplicationMain.tlmgr.tlroot) + def do_one(tag: String, url: String): Unit = { val tagNode = new TextField() { text = tag @@ -42,7 +51,21 @@ class LocationDialog(locs: Map[String,String]) { grid.add(tagNode, 0, crow) grid.add(urlNode, 1, crow) newlocs += ((tagNode, urlNode)) - if (tag != "main") { + if (tag == "main") { + if (mirrorlocs.nonEmpty) { + grid.add(new MenuButton("Choose Mirror") { + items = mirrorlocs.toSeq.sortWith(_._1 < _._1) + .map(continent_pair => new Menu(continent_pair._1) { + items = continent_pair._2.toSeq.sortWith(_._1 < _._1) + .map(country_pair => new Menu(country_pair._1) { + items = country_pair._2.sortWith(_ < _).map(mirror => new MenuItem(mirror) { + onAction = (ae) => urlNode.text = mirror + }) + }) + }) + }, 2, crow) + } + } else { grid.add(new Button("Delete") { onAction = _ => { tagNode.disable = true @@ -62,7 +85,7 @@ class LocationDialog(locs: Map[String,String]) { val tagNode = new TextField() val urlNode = new TextField() newlocs += ((tagNode, urlNode)) - // println(s"current row = ${crow}") + logger.trace(s"current row = ${crow}") grid.children.remove(addButton) grid.add(tagNode, 0, crow) grid.add(urlNode, 1, crow) @@ -74,7 +97,7 @@ class LocationDialog(locs: Map[String,String]) { } }, 2, crow) crow += 1 - // println(s"adding addButton at row ${crow}") + logger.trace(s"adding addButton at row ${crow}") grid.add(addButton, 2, crow) dialog.dialogPane.value.scene.value.window.value.sizeToScene() } @@ -104,10 +127,10 @@ class LocationDialog(locs: Map[String,String]) { result match { case Some(Result(foo)) => - // println("Got resutl " + foo) + logger.debug("Got result " + foo) Some(foo) case Some(foo) => - // println("Got strange result " + foo) + logger.debug("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 index ca7228da7ee..497a5cb20de 100644 --- 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 @@ -1,7 +1,14 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TLCockpit import TLCockpit.ApplicationMain.{not_implemented_info, stage} import TeXLive.{OsTools, TLOption, TLPaperConf} +import com.typesafe.scalalogging.LazyLogging import scalafx.Includes._ import scalafx.collections.ObservableBuffer @@ -11,7 +18,7 @@ import scalafx.scene.Node import scalafx.scene.control._ import scalafx.scene.layout._ -class OptionsDialog(opts: List[TLOption]) { +class OptionsDialog(opts: List[TLOption]) extends LazyLogging { case class Result(selected: Map[String,String]) @@ -116,10 +123,10 @@ class OptionsDialog(opts: List[TLOption]) { result match { case Some(Result(foo)) => - // println("Got resutl " + foo) + logger.debug("Got result " + foo) Some(foo) case Some(foo) => - // println("Got strange result " + foo) + logger.debug("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 index a195c23f654..0b4f7edf63f 100644 --- 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 @@ -1,7 +1,14 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TLCockpit import TLCockpit.ApplicationMain.stage import TeXLive.TLPaperConf +import com.typesafe.scalalogging.LazyLogging import scalafx.Includes._ import scalafx.collections.ObservableBuffer @@ -9,7 +16,7 @@ import scalafx.geometry.{HPos, Insets} import scalafx.scene.control._ import scalafx.scene.layout._ -class PaperDialog(paperconf: Map[String, TLPaperConf]) { +class PaperDialog(paperconf: Map[String, TLPaperConf]) extends LazyLogging { case class Result(selected: Map[String,String]) @@ -70,10 +77,10 @@ class PaperDialog(paperconf: Map[String, TLPaperConf]) { result match { case Some(Result(foo)) => - // println("Got resutl " + foo) + logger.debug("Got result " + foo) Some(foo) case Some(foo) => - // println("Got strange result " + foo) + logger.debug("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 index 3b5ea6fc848..82585aa7591 100644 --- 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 @@ -1,8 +1,14 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TLCockpit -import TLCockpit.ApplicationMain.{tlpkgs,tlmgr,stage} +import TLCockpit.ApplicationMain.{stage, tlmgr, tlpkgs} import TLCockpit.Utils._ -import TeXLive.OsTools +import TeXLive.{OsTools, TLPackage} import scalafx.Includes._ import scalafx.collections.ObservableBuffer @@ -13,7 +19,7 @@ import scalafx.scene.control._ import scalafx.scene.input.MouseEvent import scalafx.scene.paint.Color -class PkgInfoDialog(pkg: String) extends Dialog { +class PkgInfoDialog(tlp: TLPackage) extends Dialog { val dialog = new Dialog() { initOwner(stage) title = s"Package Information for $pkg" @@ -21,7 +27,7 @@ class PkgInfoDialog(pkg: String) extends Dialog { resizable = true } dialog.dialogPane().buttonTypes = Seq(ButtonType.OK) - val isInstalled = tlpkgs(pkg).installed + val isInstalled = tlp.installed val grid = new GridPane() { hgap = 10 vgap = 10 @@ -37,7 +43,7 @@ class PkgInfoDialog(pkg: String) extends Dialog { } var crow = 0 - val tlp = tlpkgs(pkg) + val pkg = tlp.name crow = do_one("package", pkg, crow) crow = do_one("category", tlp.category, crow) crow = do_one("shortdesc", tlp.shortdesc.getOrElse(""), crow) @@ -48,44 +54,44 @@ class PkgInfoDialog(pkg: String) extends Dialog { 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 ""; + val binsizesum = tlp.binsize.values.toList.sum * 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) + if (catdata.version.isDefined) crow = do_one("cat-version", catdata.version.get, crow) - if (catdata.date != None) + if (catdata.date.isDefined) crow = do_one("cat-date", catdata.date.get, crow) - if (catdata.license != None) + if (catdata.license.isDefined) crow = do_one("cat-license", catdata.license.get, crow) - if (catdata.topics != None) + if (catdata.topics.isDefined) crow = do_one("cat-topics", catdata.topics.get, crow) - if (catdata.related != None) + if (catdata.related.isDefined) crow = do_one("cat-related", catdata.related.get, crow) // add files section //println(tlpkgs(pkg)) - val docFiles = tlpkgs(pkg).docfiles + val docFiles = tlp.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 + val runFiles = tlp.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 + val srcFiles = tlp.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 + val binFiles = tlp.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) 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 index 630cbfce061..b49c13c1624 100644 --- 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 @@ -1,3 +1,9 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TLCockpit import scalafx.beans.property.{ObjectProperty, StringProperty} 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 index fb364f12f80..aae40d5db37 100644 --- 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 @@ -1,3 +1,9 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TLCockpit object Utils { 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 index 3e28c582f2a..2302d308bdd 100644 --- 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 @@ -1,3 +1,9 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TeXLive import spray.json._ @@ -6,6 +12,7 @@ import spray.json._ object JsonProtocol extends DefaultJsonProtocol { implicit val catalogueDataFormat = jsonFormat6(CatalogueData) implicit val docfileFormat = jsonFormat3(DocFile) + implicit val tlpackageshortFormat = jsonFormat8(TLPackageShort) implicit val tlpackageFormat = jsonFormat19(TLPackage) implicit val tlbackupFormat = jsonFormat3(TLBackup) implicit val tlpaperconfFormat = jsonFormat3(TLPaperConf) 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 4590eb6b5c5..45a50ae884c 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 @@ -1,4 +1,9 @@ package TeXLive +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global 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 index c526ae50739..e647ef726eb 100644 --- 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 @@ -1,3 +1,9 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TeXLive // classes that wrap up structures in TeX Live @@ -10,6 +16,18 @@ case class DocFile(file: String, details: Option[String], language: Option[Strin case class TLPaperConf(program: String, file: String, options: List[String]) case class TLBackup(name: String, rev: String, date: String) +case class TLPackageShort +( + name: String, + shortdesc: Option[String], + var lrev: Long, + rrev: Long, + category: String, + depends: List[String], + var installed: Boolean, + available: Boolean +) + case class TLPackage ( name: String, @@ -54,3 +72,4 @@ case class TLOptions file_assocs: Option[Long] ) + 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 7d84e6dd008..5776daef76d 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 @@ -1,31 +1,39 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + package TeXLive import java.io._ import TeXLive.OsTools._ +import com.typesafe.scalalogging.LazyLogging import scala.collection.mutable.ArrayBuffer import scala.concurrent.SyncVar import scala.sys.process._ -class TlmgrProcess(updout: String => Unit, upderr: String => Unit) { +class TlmgrProcess(updout: String => Unit, upderr: String => Unit) extends LazyLogging { val inputString = new SyncVar[String] // used for the tlmgr process input // val outputString = new SyncVar[String] // used for the tlmgr process output // val errorBuffer: StringBuffer = new StringBuffer() // buffer used for both tmgr process error console AND logging - val tlroot = "kpsewhich -var-value SELFAUTOPARENT".!!.trim - // println("tlroot ==" + tlroot + "==") + val tlroot: String = "kpsewhich -var-value SELFAUTOPARENT".!!.trim + logger.debug("tlroot ==" + tlroot + "==") // set in only one place, in the main thread var process: Process = _ def send_command(input: String): Unit = { + logger.debug(s"send_command: ${input}") try { assert(!inputString.isSet) inputString.put(input) } catch { case exc: Throwable => - upderr("Main thread: " + + logger.warn("Main thread: " + (if (exc.isInstanceOf[NoSuchElementException]) "Timeout" else "Exception: " + exc)) } } @@ -39,18 +47,26 @@ class TlmgrProcess(updout: String => Unit, upderr: String => Unit) { } def start_process(): Boolean = { + logger.debug("tlmgr process: entering starting process") // process creation if (process == null) { - val procIO = new ProcessIO(inputFn(_), outputFn(_, updout), outputFn(_, upderr)) + logger.debug("tlmgr process: start_process: creating procIO") + val procIO = new ProcessIO(inputFn, outputFn(_, updout), outputFn(_, upderr)) val processBuilder: ProcessBuilder = Seq({if (isWindows) "tlmgr.bat" else "tlmgr"}, "-v", "--machine-readable", "shell") + logger.debug("tlmgr process: start_process: running new tlmgr process") process = processBuilder.run(procIO) } + logger.debug("tlmgr process: start_process: checking for being alive") process.isAlive() } def cleanup(): Unit = { if (process != null) { + logger.debug("tlmgr process: cleanup - sending quit") send_command("quit") + logger.debug("tlmgr process: cleanup - sleeping 2s") + Thread.sleep(1000) + logger.debug("tlmgr process: cleanup - destroying process") process.destroy() } } @@ -67,7 +83,7 @@ class TlmgrProcess(updout: String => Unit, upderr: String => Unit) { return } else { writer.write(input + "\n") - // println("writing " + input + " to process") + logger.trace("writing " + input + " to process") writer.flush() } } @@ -75,8 +91,7 @@ class TlmgrProcess(updout: String => Unit, upderr: String => Unit) { } catch { case exc: Throwable => stdin.close() - // println("Exception in inputFn thread: " + exc + "\n") - upderr("Input thread: Exception: " + exc + "\n") + logger.warn("Exception in inputFn thread: " + exc + "\n") } } @@ -86,19 +101,19 @@ class TlmgrProcess(updout: String => Unit, upderr: String => Unit) { var line: String = "" while (true) { line = reader.readLine - // println("DDD did read " + line + " from process") + logger.trace("DDD did read " + line + " from process") try { updfun(line) } catch { case exc: Throwable => - upderr("Update output line function failed, continuing anyway. Exception: " + exc) + logger.debug("Update output line function failed, continuing anyway. Exception: " + exc) } } outStr.close() } catch { case exc: Throwable => outStr.close() - upderr("Output thread: Exception: " + exc + "\n") + logger.warn("Exception in outputFn thread: " + 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 index 88b3bb3e41a..074a7a86153 100644 --- 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 @@ -1,3 +1,8 @@ +// TLCockpit +// Copyright 2017-2018 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr package object TeXLive { val tlBlockSize = 4096 // TL gives sizes in multiples of BlockSize! -- cgit v1.2.3