mirror of
https://github.com/ij-plugins/ijp-imagej-launcher.git
synced 2024-11-13 16:29:01 -08:00
[WIP] Read ImageJ.cfg #3
This commit is contained in:
parent
60ad1b1816
commit
faf6a879e5
6 changed files with 159 additions and 2 deletions
114
src/main/scala/ij_plugins/imagej_launcher/IJConfigFile.scala
Normal file
114
src/main/scala/ij_plugins/imagej_launcher/IJConfigFile.scala
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved.
|
||||||
|
* Author's e-mail: jpsacha at gmail.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ij_plugins.imagej_launcher
|
||||||
|
|
||||||
|
import os.Path
|
||||||
|
|
||||||
|
object IJConfigFile:
|
||||||
|
|
||||||
|
private val FileName: String = "ImageJ.cfg"
|
||||||
|
private val MagicHeader: String = "# ImageJ startup properties"
|
||||||
|
private val MaxHeapMBKey: String = "maxheap.mb"
|
||||||
|
private val JvmArgsKey: String = "jvmargs"
|
||||||
|
private val LegacyModeKey: String = "legacy.mode"
|
||||||
|
private val AssignmentSymbol: String = "="
|
||||||
|
private val CommentSymbol: String = "#"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read `ImageJ.cfg` from a specified directory.
|
||||||
|
* The configuration file must be in ImageJ2 format (with magic header "# ImageJ startup properties").
|
||||||
|
*
|
||||||
|
* @param dir directory where `ImageJ.cfg` is located
|
||||||
|
* @param logger logger
|
||||||
|
* @return option containing the configuration file (Right).
|
||||||
|
* The option is empty if file is not present in input `dir`.
|
||||||
|
* An error message is returned if file is present but cannot be read without errors (Left).
|
||||||
|
*/
|
||||||
|
def readFromDir(dir: Path)(using logger: Logger): Either[String, Option[IJCConfig]] =
|
||||||
|
logger.debug(s"Reading ImageJ configuration from directory: $dir")
|
||||||
|
val path = dir / FileName
|
||||||
|
logger.debug(s" Looking for $FileName in: '$path'")
|
||||||
|
readFromFile(path)
|
||||||
|
|
||||||
|
private[imagej_launcher] def readFromFile(ijConfigFile: Path)(using logger: Logger): Either[String, Option[IJCConfig]] =
|
||||||
|
if os.exists(ijConfigFile) then
|
||||||
|
val lines = os.read.lines(ijConfigFile)
|
||||||
|
val cfgE =
|
||||||
|
lines.headOption match
|
||||||
|
case Some(line) =>
|
||||||
|
if line.trim.startsWith(MagicHeader) then
|
||||||
|
logger.debug(s" Parsing $FileName")
|
||||||
|
for
|
||||||
|
props <- parseProps(lines)
|
||||||
|
maxHeapMB <- parseLong(props, MaxHeapMBKey)
|
||||||
|
jvmArgs <- parseString(props, JvmArgsKey)
|
||||||
|
legacyMode <- parseBoolean(props, LegacyModeKey)
|
||||||
|
yield Option(IJCConfig(maxHeapMB, jvmArgs, legacyMode))
|
||||||
|
else
|
||||||
|
Left(s"$FileName is invalid, does not contain header: '$MagicHeader'")
|
||||||
|
case None =>
|
||||||
|
Left(s"$FileName is empty.")
|
||||||
|
|
||||||
|
// Add context to the error message, if there is one
|
||||||
|
cfgE.left.map(e => s"Failed to read $FileName. $e")
|
||||||
|
else
|
||||||
|
logger.debug(s" $FileName not found: $ijConfigFile")
|
||||||
|
Right(None)
|
||||||
|
|
||||||
|
private[imagej_launcher] def parseProps(lines: Seq[String]): Either[String, Map[String, String]] =
|
||||||
|
// Parse each line
|
||||||
|
val lineResults: Seq[Either[String, (String, String)]] =
|
||||||
|
lines
|
||||||
|
.zipWithIndex
|
||||||
|
.filter { case (line, _) => !line.startsWith(CommentSymbol) }
|
||||||
|
.map { case (line, lineNumber) =>
|
||||||
|
val index = line.indexOf(AssignmentSymbol)
|
||||||
|
if index > 0 then
|
||||||
|
val key = line.substring(0, index).trim
|
||||||
|
if !key.isBlank then
|
||||||
|
Right(key -> line.substring(index + 1).trim)
|
||||||
|
else
|
||||||
|
Left(s"Error in line $lineNumber: key cannot be empty: '$line'")
|
||||||
|
else if index == 0 then
|
||||||
|
Left(s"Error in line $lineNumber: line cannot start with an assignment symbol '$AssignmentSymbol': '$line'")
|
||||||
|
else
|
||||||
|
Left(s"Error in line $lineNumber: no assignment symbol '$AssignmentSymbol' present: '$line'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate lines with errors
|
||||||
|
val errors = lineResults.collect { case e: Left[?, ?] => e }
|
||||||
|
if errors.isEmpty then
|
||||||
|
// Convert key->value pairs to a map
|
||||||
|
val r =
|
||||||
|
lineResults
|
||||||
|
.collect { case e: Right[?, ?] => e }
|
||||||
|
.map(_.value)
|
||||||
|
.toMap
|
||||||
|
Right(r)
|
||||||
|
else
|
||||||
|
Left(errors.map(_.value).mkString("\n"))
|
||||||
|
|
||||||
|
private def parseLong(props: Map[String, String], key: String): Either[String, Long] =
|
||||||
|
parseString(props, key).flatMap: s =>
|
||||||
|
s.toLongOption match
|
||||||
|
case Some(v) => Right(v)
|
||||||
|
case None => Left(s"Failed to parse '$s' as an integer.")
|
||||||
|
|
||||||
|
private def parseString(props: Map[String, String], key: String): Either[String, String] =
|
||||||
|
props.get(key) match
|
||||||
|
case Some(v) => Right(v)
|
||||||
|
case None => Left(s"No key named '$key'.")
|
||||||
|
|
||||||
|
private def parseBoolean(props: Map[String, String], key: String): Either[String, Boolean] =
|
||||||
|
parseString(props, key).flatMap: s =>
|
||||||
|
s.toBooleanOption match
|
||||||
|
case Some(v) => Right(v)
|
||||||
|
case None => Left(s"Failed to parse '$s' as a boolean.")
|
||||||
|
|
||||||
|
/** Configuration loaded from `ImageJ.cfg`. */
|
||||||
|
case class IJCConfig(maxHeapMB: Long, jvmArgs: String, legacyMode: Boolean)
|
||||||
|
|
||||||
|
end IJConfigFile
|
||||||
|
|
@ -13,7 +13,7 @@ import os.Path
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ProcessBuilder.Redirect
|
import java.lang.ProcessBuilder.Redirect
|
||||||
|
|
||||||
class Launcher(logger: Logger):
|
class Launcher(using logger: Logger):
|
||||||
|
|
||||||
def run(config: Config): Unit =
|
def run(config: Config): Unit =
|
||||||
prepareLaunch(config) match
|
prepareLaunch(config) match
|
||||||
|
|
@ -30,6 +30,7 @@ class Launcher(logger: Logger):
|
||||||
for
|
for
|
||||||
ijDir <- IJDir.locate(config, logger)
|
ijDir <- IJDir.locate(config, logger)
|
||||||
_ <- Updater.update(ijDir, config.dryRun, logger)
|
_ <- Updater.update(ijDir, config.dryRun, logger)
|
||||||
|
ijConfig <- IJConfigFile.readFromDir(ijDir)
|
||||||
launcherJar <- findImageJLauncherJar(ijDir.toIO)
|
launcherJar <- findImageJLauncherJar(ijDir.toIO)
|
||||||
javaExe <- locateJavaExecutable(config, ijDir.toIO)
|
javaExe <- locateJavaExecutable(config, ijDir.toIO)
|
||||||
systemType <- determineSystemType()
|
systemType <- determineSystemType()
|
||||||
|
|
@ -178,6 +179,7 @@ class Launcher(logger: Logger):
|
||||||
"plugins",
|
"plugins",
|
||||||
"net.imagej.Main"
|
"net.imagej.Main"
|
||||||
)
|
)
|
||||||
|
end buildCommandLine
|
||||||
|
|
||||||
private def launch(command: Seq[String]): Unit =
|
private def launch(command: Seq[String]): Unit =
|
||||||
logger.debug("launchImageJ ...")
|
logger.debug("launchImageJ ...")
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ object Main:
|
||||||
|
|
||||||
private def setupLogger(logLevel: Logger.Level): Unit = logger = new Logger(logLevel)
|
private def setupLogger(logLevel: Logger.Level): Unit = logger = new Logger(logLevel)
|
||||||
|
|
||||||
private def runLauncher(config: Config): Unit = new Launcher(logger).run(config)
|
private def runLauncher(config: Config): Unit = new Launcher(using logger).run(config)
|
||||||
|
|
||||||
case class Config(
|
case class Config(
|
||||||
logLevel: Logger.Level = Logger.Level.Error,
|
logLevel: Logger.Level = Logger.Level.Error,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved.
|
||||||
|
* Author's e-mail: jpsacha at gmail.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ij_plugins.imagej_launcher
|
||||||
|
|
||||||
|
import org.scalatest.flatspec.AnyFlatSpec
|
||||||
|
|
||||||
|
class IJConfigFileTest extends AnyFlatSpec:
|
||||||
|
|
||||||
|
given logger:Logger = new Logger(Logger.Level.All)
|
||||||
|
|
||||||
|
"IJConfigFile" should "read valid config files" in:
|
||||||
|
val path = os.pwd / "test" / "data" / "config_valid" / "ImageJ.cfg"
|
||||||
|
|
||||||
|
IJConfigFile.readFromFile(path) match
|
||||||
|
case Right(cfg) =>
|
||||||
|
logger.debug(cfg.toString)
|
||||||
|
assert(cfg.nonEmpty)
|
||||||
|
case Left(err) =>
|
||||||
|
logger.debug(s"Unexpected error: $err")
|
||||||
|
fail()
|
||||||
|
|
||||||
|
it should "fail reading invalid config files" in:
|
||||||
|
val path = os.pwd / "test" / "data" / "config_invalid" / "ImageJ.cfg"
|
||||||
|
|
||||||
|
IJConfigFile.readFromFile(path) match
|
||||||
|
case Right(cfg) =>
|
||||||
|
logger.debug(cfg.toString)
|
||||||
|
fail(s"Expected to return errors when reading config, but read config as: $cfg")
|
||||||
|
case Left(err) =>
|
||||||
|
logger.debug(s"Expected error\n:$err")
|
||||||
4
test/data/config_invalid/ImageJ.cfg
Normal file
4
test/data/config_invalid/ImageJ.cfg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# ImageJ startup properties
|
||||||
|
maxheap.mb = 1024m
|
||||||
|
jvmargs = -XX:+HeapDumpOnOutOfMemoryError -Xincgc
|
||||||
|
legacy.mode = false
|
||||||
4
test/data/config_valid/ImageJ.cfg
Normal file
4
test/data/config_valid/ImageJ.cfg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# ImageJ startup properties
|
||||||
|
maxheap.mb = 1024
|
||||||
|
jvmargs = -XX:+HeapDumpOnOutOfMemoryError -Xincgc
|
||||||
|
legacy.mode = false
|
||||||
Loading…
Add table
Add a link
Reference in a new issue