mirror of
https://github.com/ij-plugins/ijp-imagej-launcher.git
synced 2024-11-13 16:29:01 -08:00
Compare commits
41 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6d8daca7b | ||
|
|
bbff825c9b | ||
|
|
6998af7cec | ||
|
|
1e3797fe19 | ||
|
|
1003fd308d | ||
|
|
e07360ec65 | ||
|
|
abcc138ea0 | ||
|
|
0ca11d6645 | ||
|
|
c8dec57570 | ||
|
|
79ecae6091 | ||
|
|
c2043dc24c | ||
|
|
4babd726f8 | ||
|
|
cbb9829f8e | ||
|
|
0bca3977ad | ||
|
|
be57049c49 | ||
|
|
d842833e34 | ||
|
|
da5ffe2701 | ||
|
|
8f158a0fe7 | ||
|
|
bb18354923 | ||
|
|
ad54989711 | ||
|
|
4e3fbe32a8 | ||
|
|
8a417ceedb | ||
|
|
9a0cb694f7 | ||
|
|
95638a462a | ||
|
|
374a162f29 | ||
|
|
11b248fc1f | ||
|
|
ce0c49c515 | ||
|
|
f2bb93871f | ||
|
|
eb8b6a9a17 | ||
|
|
faf6a879e5 | ||
|
|
60ad1b1816 | ||
|
|
8718c9310c | ||
|
|
c30b853f64 | ||
|
|
47674de560 | ||
|
|
87882dc6e3 | ||
|
|
1d5b8887a3 | ||
|
|
1f2c915159 | ||
|
|
231540bce5 | ||
|
|
63b3ad6f52 | ||
|
|
e2b6e2d550 | ||
|
|
1312fa24b1 |
23 changed files with 489 additions and 109 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@
|
||||||
.idea/
|
.idea/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
version = 3.7.4
|
version = 3.8.3
|
||||||
|
|
||||||
runner.dialect = scala3
|
runner.dialect = scala3
|
||||||
|
|
||||||
|
|
|
||||||
96
ReadMe.md
96
ReadMe.md
|
|
@ -15,6 +15,8 @@ IJP ImageJ Launcher is a clean implementation on the core function of starting [
|
||||||
* [Installing Fiji on Mac OS X Arm64](#installing-fiji-on-mac-os-x-arm64)
|
* [Installing Fiji on Mac OS X Arm64](#installing-fiji-on-mac-os-x-arm64)
|
||||||
* [Installing Fiji on Windows x64](#installing-fiji-on-windows-x64)
|
* [Installing Fiji on Windows x64](#installing-fiji-on-windows-x64)
|
||||||
* [Troubleshooting](#troubleshooting)
|
* [Troubleshooting](#troubleshooting)
|
||||||
|
* [Start-up log `~/.ijp_imagej_launcher.log`](#start-up-log-ijpimagejlauncherlog)
|
||||||
|
* [Starting from command prompt](#starting-from-command-prompt)
|
||||||
* [Developer Setup](#developer-setup)
|
* [Developer Setup](#developer-setup)
|
||||||
|
|
||||||
<!-- TOC -->
|
<!-- TOC -->
|
||||||
|
|
@ -33,7 +35,10 @@ the logic flow is too complex to correct without a significant rewrite.
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* Uses similar options to the original ImageJ Launcher, si IJP Launcher can be drop-in replacement
|
Here are the futures that are already implemented (see release notes for futures ofa specific release):
|
||||||
|
|
||||||
|
* Uses similar options to the original ImageJ Launcher, so IJP Launcher can be used as a drop-in replacement
|
||||||
|
* Intended to be used with Java 11 or newer (the original launcher can be used for Java 8)
|
||||||
* Provides native executable for various OS/Hardware systems
|
* Provides native executable for various OS/Hardware systems
|
||||||
- Windows
|
- Windows
|
||||||
- Mac OS X Arm64 (Apple Silicon)
|
- Mac OS X Arm64 (Apple Silicon)
|
||||||
|
|
@ -48,7 +53,7 @@ Features
|
||||||
- Search ImageJ directory for available Java executables
|
- Search ImageJ directory for available Java executables
|
||||||
* Determines the amount of memory used by JVM based on total system memory use 75% of the max
|
* Determines the amount of memory used by JVM based on total system memory use 75% of the max
|
||||||
* Determines available `imagej-launcher*.jar`
|
* Determines available `imagej-launcher*.jar`
|
||||||
* Performs updates pending after the last time ImageJ was closed
|
* **Performs updates** pending after the last time ImageJ was closed
|
||||||
|
|
||||||
Full List of Command Line Options
|
Full List of Command Line Options
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
@ -77,7 +82,7 @@ This example will show how to:
|
||||||
|
|
||||||
**1. Download FIJI without JRE**
|
**1. Download FIJI without JRE**
|
||||||
|
|
||||||
Go to https://fiji.sc/ and select "Download the no-JRE version".
|
Go to https://imagej.net/software/fiji/downloads and download the **"No JRE"** version (not specific to any OS).
|
||||||
That should get file called `fiji-nojre.zip`
|
That should get file called `fiji-nojre.zip`
|
||||||
|
|
||||||
**2. Unzip the `fiji-nojre.zip` in a folder of choice**
|
**2. Unzip the `fiji-nojre.zip` in a folder of choice**
|
||||||
|
|
@ -95,41 +100,49 @@ Inside the `Fiji.app` folder create a new folder called `java`.
|
||||||
In browser open https://adoptium.net/temurin/releases/
|
In browser open https://adoptium.net/temurin/releases/
|
||||||
Select:
|
Select:
|
||||||
|
|
||||||
* operating system: `macOS`
|
* Operating System: `macOS`
|
||||||
* architecture: `aarch64` also known as Apple Silicon or Arm64
|
* Architecture: `aarch64` also known as Apple Silicon or Arm64
|
||||||
* package: `JRE` (`JDK` is fine too, is larger and supports Java compilation)
|
* Package Type: `JRE` (`JDK` is fine too, is larger and supports Java compilation)
|
||||||
* version: `11-LTS` (`17-LTS` will work too, but you will not have JavaScript available, if you want to use it)
|
* Version: `11-LTS` (`17-LTS` will work too, but you will not have JavaScript available, if you want to use it)
|
||||||
|
|
||||||
Click on `tar.gz` button to download and save to the `java` folder you created earlier.
|
Click on `tar.gz` button to download and save to the `java` folder you created earlier.
|
||||||
You should have file like `OpenJDK11U-jre_x64_windows_hotspot_11.0.19_7.tar.gz`.
|
You should have file like `OpenJDK11U-jre_aarch64_mac_hotspot_11.0.20_8.tar.gz`.
|
||||||
|
|
||||||
**5. Uncompress into the `java` folder**
|
**5. Uncompress into the `Fiji.app/java` folder**
|
||||||
|
|
||||||
That will create folder like `jdk-11.0.19+7-jre`.
|
That will create folder like `jdk-11.0.20+8-jre`.
|
||||||
This is the Java VM that IJP ImageJ Launcher will use to start Fiji.
|
This is the Java VM that IJP ImageJ Launcher will use to start Fiji.
|
||||||
|
|
||||||
**6. Download the IJP ImageJ Launcher to the Fiji.app directory**
|
**6. Download the IJP ImageJ Launcher and uncompress**
|
||||||
|
|
||||||
Go to [Releases], download "IJP-ImageJ-Launcher-0.1.0-macosx-arm64"
|
Go to [Releases], download "IJP-ImageJ-Launcher-0.2.0-macosx-arm64.zip"
|
||||||
and "IJP-ImageJ-Launcher-0.1.0-macosx-arm64.command", save them to the `Fiji.app` folder.
|
|
||||||
|
|
||||||
The "*.command" file is a helper that can be used to launch Fiji without using command prompt.
|
Uncompress "IJP-ImageJ-Launcher-0.2.0-macosx-arm64.zip".
|
||||||
Future versions of the IJP Launcher, after v.0.1.0, may eliminate the need for using this file.
|
Inside you will get `ImageJ-macosx`.
|
||||||
|
|
||||||
**7. Start ImageJ**
|
**7. Add to Fiji.app**
|
||||||
|
|
||||||
In the `Fiji.app` folder double-click on `IJP-ImageL-Launcher-0.1.0-macosx-arm64.command` file (note the extension "*
|
Inside `Fiji.app` locate folder `Contents/MacOS`.
|
||||||
.command")
|
|
||||||
That should start Fiji.
|
|
||||||
You may need to open Settings and allow the IJP ImageJ Launcher to run.
|
|
||||||
|
|
||||||
You can also create an alis on the Desktop to avoid navigating to the `Fiji.app` folder each time.
|
Copy `ImageJ-macosx` to the `Contents/MacOS` folder, replacing `ImageJ-macosx` that was there.
|
||||||
Using Finder, press `Option`+`Command` and drag the *.command file to the Desktop.
|
|
||||||
The original *.command file will stay were it is and a new icon/alias (wth a little arrow at the bottom) will be created
|
**8. Move Fiji.app to the Application folder**
|
||||||
on the Desktop.
|
|
||||||
Now you can double-click on the new alias on the Desktop to start Fiji.
|
At this point you can move the `Fiji.app` folder to the Applications folder and use is as a regular msOS application.
|
||||||
You can rename the Desktop alias to whatever you like, for instance, `Fiji`, but do not change names of the downloaded
|
|
||||||
files, otherwise the alias (and *.command) may no longer work, and you will need to use terminal to start the launcher.
|
**9. Troubleshooting**
|
||||||
|
|
||||||
|
When you attempt to run Fiji with the new Launcher you may get a warning dialog
|
||||||
|

|
||||||
|
|
||||||
|
Possible work-around
|
||||||
|
|
||||||
|
1. Delete `Fuji.app` folder
|
||||||
|
2. Uncompressed `fiji-nojre.zip` to recreate `Fuji.app` folder, but do not make any changes to it yet. You may need to do it is different folder than before.
|
||||||
|
3. Control-clock on `Fuji.app` and select "Open". You will see dialog saying
|
||||||
|
"macOS cannot verify the developer of “Fiji”. Are you sure you want to open it?"
|
||||||
|
4. Click on "Open". You will see Fiji logo, but the application will close since it is not setup yet
|
||||||
|
5. Now you can repeat steps "3. Create place for Java (JRE)" to "7. Add to Fiji.app" above
|
||||||
|
|
||||||
If you have problems installing, please report in [Discussions] or [Image.sc Forum]
|
If you have problems installing, please report in [Discussions] or [Image.sc Forum]
|
||||||
|
|
||||||
|
|
@ -143,7 +156,7 @@ This example will show how to:
|
||||||
|
|
||||||
**1. Download FIJI without JRE**
|
**1. Download FIJI without JRE**
|
||||||
|
|
||||||
Go to https://fiji.sc/ and select "Download the no-JRE version".
|
Go to https://imagej.net/software/fiji/downloads and download the **"No JRE"** version (not specific to any OS).
|
||||||
That should get file called `fiji-nojre.zip`
|
That should get file called `fiji-nojre.zip`
|
||||||
|
|
||||||
**2. Unzip the `fiji-nojre.zip` in a folder of choice**
|
**2. Unzip the `fiji-nojre.zip` in a folder of choice**
|
||||||
|
|
@ -160,31 +173,31 @@ Inside the `Fiji.app` folder create a new folder called `java`.
|
||||||
In browser open https://adoptium.net/temurin/releases/
|
In browser open https://adoptium.net/temurin/releases/
|
||||||
Select:
|
Select:
|
||||||
|
|
||||||
* operating system: `Windows`
|
* Operating System: `Windows`
|
||||||
* architecture: `x64` also known as Apple Silicon or Arm64
|
* Architecture: `x64` also known as Apple Silicon or Arm64
|
||||||
* package: `JRE` (`JDK` is fine too, is larger and supports Java compilation)
|
* Package Type: `JRE` (`JDK` is fine too, is larger and supports Java compilation)
|
||||||
* version: `11-LTS` (`17-LTS` will work too, but you will not have JavaScript available, if you want to use it)
|
* Version: `11-LTS` (`17-LTS` will work too, but you will not have JavaScript available, if you want to use it)
|
||||||
|
|
||||||
Click on `.zip` button to download and save to the `java` folder you created earlier.
|
Click on `.zip` button to download and save to the `java` folder you created earlier.
|
||||||
You should have file like `OpenJDK11U-jre_x64_windows_hotspot_11.0.19_7.zip`.
|
You should have file like `OpenJDK11U-jre_x64_windows_hotspot_11.0.20_8.zip`.
|
||||||
|
|
||||||
**5. Uncompress into the `java` folder**
|
**5. Uncompress into the `Fiji.app/java` folder**
|
||||||
|
|
||||||
That will create folder like `jdk-11.0.19+7-jre`.
|
That will create folder like `jdk-11.0.20+8-jre`.
|
||||||
This is the Java VM that IJP ImageJ Launcher will use to start Fiji.
|
This is the Java VM that IJP ImageJ Launcher will use to start Fiji.
|
||||||
|
|
||||||
**6. Download the IJP ImageJ Launcher to the Fiji.app directory**
|
**6. Download the IJP ImageJ Launcher to the Fiji.app directory**
|
||||||
|
|
||||||
Go to [Releases], download "IJP-ImageJ-Launcher-0.1.0-windows_x64.exe", save it to the `Fiji.app` folder.
|
Go to [Releases], download "IJP-ImageJ-Launcher-0.2.0-windows_x64.exe", save it to the `Fiji.app` folder.
|
||||||
|
|
||||||
**7. Start ImageJ**
|
**7. Start ImageJ**
|
||||||
|
|
||||||
In the `Fiji.app` folder double-click on `IJP-ImageJ-Launcher-0.1.0-windows_x64.exe`.
|
In the `Fiji.app` folder double-click on `IJP-ImageJ-Launcher-0.2.0-windows_x64.exe`.
|
||||||
That should start Fiji.
|
That should start Fiji.
|
||||||
|
|
||||||
You can also create a shortcut on the Desktop to avoid navigating to the `Fiji.app` folder each time.
|
You can also create a shortcut on the Desktop to avoid navigating to the `Fiji.app` folder each time.
|
||||||
|
|
||||||
**_Left_**-click on the `IJP-ImageJ-Launcher-0.1.0-windows_x64.exe` and drag it to the Desktop.
|
**_Left_**-click on the `IJP-ImageJ-Launcher-0.2.0-windows_x64.exe` and drag it to the Desktop.
|
||||||
Once you release mouse button, a pop-up manu will open, select "Create shortcut here".
|
Once you release mouse button, a pop-up manu will open, select "Create shortcut here".
|
||||||
Now you can double-click on the new shortcut on the Desktop to start Fiji.
|
Now you can double-click on the new shortcut on the Desktop to start Fiji.
|
||||||
|
|
||||||
|
|
@ -194,6 +207,13 @@ If you have problems installing, please report in [Discussions] or [Image.sc For
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
|
#### Start-up log `~/.ijp_imagej_launcher.log`
|
||||||
|
|
||||||
|
The IJP-ImageJ-Launcher writes diagnostic info to a file `.ijp_imagej_launcher.log` in the users home directory.
|
||||||
|
The information recorded is some as using `--debug` on command line.
|
||||||
|
|
||||||
|
#### Starting from command prompt
|
||||||
|
|
||||||
You can start the IJP Image Launcher from the terminal and see diagnostic printouts that may help troubleshoot potential
|
You can start the IJP Image Launcher from the terminal and see diagnostic printouts that may help troubleshoot potential
|
||||||
issues.
|
issues.
|
||||||
|
|
||||||
|
|
|
||||||
BIN
assets/macOS_warning_dialog_01.png
Normal file
BIN
assets/macOS_warning_dialog_01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
25
build.sbt
25
build.sbt
|
|
@ -1,6 +1,6 @@
|
||||||
scalaVersion := "3.3.0"
|
scalaVersion := "3.3.3"
|
||||||
//name := "IJP-ImageJ-Launcher"
|
//name := "IJP-ImageJ-Launcher"
|
||||||
version := "0.1.0"
|
version := "0.2.0.1-SNAPSHOT"
|
||||||
versionScheme := Some("early-semver")
|
versionScheme := Some("early-semver")
|
||||||
organization := "net.sf.ij-plugins"
|
organization := "net.sf.ij-plugins"
|
||||||
homepage := Some(new URI("https://github.com/ij-plugins/ijp-imagej-launcher").toURL)
|
homepage := Some(new URI("https://github.com/ij-plugins/ijp-imagej-launcher").toURL)
|
||||||
|
|
@ -17,13 +17,25 @@ logLevel := Level.Info
|
||||||
|
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"com.github.scopt" %%% "scopt" % "4.1.0",
|
"com.github.scopt" %%% "scopt" % "4.1.0",
|
||||||
"com.lihaoyi" %%% "os-lib" % "0.9.1"
|
"com.lihaoyi" %%% "os-lib" % "0.9.3",
|
||||||
|
"org.scalatest" %%% "scalatest" % "3.2.18" % Test
|
||||||
|
)
|
||||||
|
|
||||||
|
scalacOptions ++= Seq(
|
||||||
|
"-unchecked",
|
||||||
|
"-deprecation",
|
||||||
|
"-explain",
|
||||||
|
"-explain-types",
|
||||||
|
"-rewrite",
|
||||||
|
"-source:3.3-migration",
|
||||||
|
// "-Wvalue-discard",
|
||||||
|
"-Wunused:all"
|
||||||
)
|
)
|
||||||
|
|
||||||
Compile / run / mainClass := Some("ij_plugins.imagej_launcher.Main")
|
Compile / run / mainClass := Some("ij_plugins.imagej_launcher.Main")
|
||||||
|
|
||||||
// import to add Scala Native options
|
// import to add Scala Native options
|
||||||
import scala.scalanative.build._
|
import scala.scalanative.build.*
|
||||||
|
|
||||||
// defaults set with common options shown
|
// defaults set with common options shown
|
||||||
nativeConfig ~= { c =>
|
nativeConfig ~= { c =>
|
||||||
|
|
@ -36,3 +48,8 @@ nativeConfig ~= { c =>
|
||||||
//nativeConfig ~= { c =>
|
//nativeConfig ~= { c =>
|
||||||
// c.withCompileOptions(c.compileOptions ++ Seq("-v"))
|
// c.withCompileOptions(c.compileOptions ++ Seq("-v"))
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
// Version info generation from SBT configuration
|
||||||
|
enablePlugins(BuildInfoPlugin)
|
||||||
|
buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion)
|
||||||
|
buildInfoPackage := "ij_plugins.imagej_launcher"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
DIR=$(cd "$(dirname "$0")" && pwd -P)
|
DIR=$(cd "$(dirname "$0")" && pwd -P)
|
||||||
echo "$DIR"
|
echo "$DIR"
|
||||||
"$DIR"/IJP-ImageL-Launcher-0.1.0-macosx-arm64 --debug --ij-dir "$DIR"
|
"$DIR"/IJP-ImageJ-Launcher-0.1.0-macosx-arm64 --debug --ij-dir "$DIR"
|
||||||
8
notes/v.0.2.0.md
Normal file
8
notes/v.0.2.0.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
Feature release: support better system integration on macOS - support installing ImageJ/Fiji in the application
|
||||||
|
directory.
|
||||||
|
See macOS installation info in the [ReadMe](https://github.com/ij-plugins/ijp-imagej-launcher)
|
||||||
|
|
||||||
|
* Use launcher location to infer `ij-dir`. #5
|
||||||
|
* Session log is saved to `~/.ijp_imagej_launcher.log` to facilitate troubleshooting when not running from command
|
||||||
|
prompt. The log is reset for each session. #6
|
||||||
|
* Better inference of ImageJ directory on macOS - consider launcher being in subdirectory "Contents/MacOS" #7
|
||||||
|
|
@ -1 +1,6 @@
|
||||||
sbt.version = 1.8.3
|
#
|
||||||
|
# Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved.
|
||||||
|
# Author's e-mail: jpsacha at gmail.com
|
||||||
|
#
|
||||||
|
|
||||||
|
sbt.version = 1.10.2
|
||||||
|
|
|
||||||
2
project/buildinfo.sbt
Normal file
2
project/buildinfo.sbt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// https://github.com/sbt/sbt-buildinfo
|
||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.12.0")
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.12")
|
// https://github.com/scala-native/scala-native
|
||||||
|
resolvers ++= Resolver.sonatypeOssRepos("snapshots")
|
||||||
|
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17")
|
||||||
|
|
|
||||||
40
src/main/resources/scala-native/argv0.c
Normal file
40
src/main/resources/scala-native/argv0.c
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/syslimits.h>
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t path_max() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return MAX_PATH;
|
||||||
|
#else
|
||||||
|
return PATH_MAX + 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_exe_path(char* exe_path, size_t size) {
|
||||||
|
|
||||||
|
exe_path[0] = 0;
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
// char arg1[20];
|
||||||
|
// sprintf( arg1, "/proc/%d/exe", getpid() );
|
||||||
|
// printf("arg1 = %d\n", arg1);
|
||||||
|
// readlink( arg1, exepath, PATH_MAX );
|
||||||
|
readlink( "/proc/self/exe", exe_path, size );
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
if (_NSGetExecutablePath(exe_path, &size) != 0)
|
||||||
|
printf("ERROR: buffer too small; need size %lu\n", size);
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
unsigned int len = GetModuleFileNameA(GetModuleHandleA(0x0), exe_path, size);
|
||||||
|
if (len == 0) // memory not sufficient or general error occurred
|
||||||
|
printf("ERROR: buffer too small or general error.\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
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
|
||||||
73
src/main/scala/ij_plugins/imagej_launcher/IJDir.scala
Normal file
73
src/main/scala/ij_plugins/imagej_launcher/IJDir.scala
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved.
|
||||||
|
* Author's e-mail: jpsacha at gmail.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ij_plugins.imagej_launcher
|
||||||
|
|
||||||
|
import ij_plugins.imagej_launcher.Main.Config
|
||||||
|
import os.{FilePath, Path}
|
||||||
|
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
|
||||||
|
object IJDir:
|
||||||
|
|
||||||
|
/** Name of an ImageJ sub-directory containing jars */
|
||||||
|
val jarsDirName = "jars"
|
||||||
|
|
||||||
|
/** Locate ImageJ directory */
|
||||||
|
def locate(config: Config)(using logger: Logger): Either[String, Path] =
|
||||||
|
logger.debug("Looking for ImageJ directory")
|
||||||
|
|
||||||
|
config.ijDir match
|
||||||
|
case Some(d) =>
|
||||||
|
logger.debug(s" Considering provided ij-dir: '$d'")
|
||||||
|
asPath(d.getPath).flatMap: p =>
|
||||||
|
logger.debug(s" '$p'")
|
||||||
|
if isIJDir(p) then Right(p)
|
||||||
|
else Left(s"ij-dir is not an ImageJ directory [$p]")
|
||||||
|
case None =>
|
||||||
|
logger.debug(" Considering application directory")
|
||||||
|
val appPath = Native.applicationPath()
|
||||||
|
logger.debug(s" Application directory: '$appPath'")
|
||||||
|
inferIJDir(appPath) match
|
||||||
|
case Some(p) =>
|
||||||
|
Right(p)
|
||||||
|
case None =>
|
||||||
|
logger.debug(" Application directory is not an ImageJ directory")
|
||||||
|
logger.debug(" Considering current working directory")
|
||||||
|
val cwd = os.pwd
|
||||||
|
logger.debug(s" Current working directory: '$cwd'")
|
||||||
|
inferIJDir(cwd) match
|
||||||
|
case Some(p) =>
|
||||||
|
Right(p)
|
||||||
|
case None =>
|
||||||
|
logger.debug(" Current working directory is not an ImageJ directory.")
|
||||||
|
Left("Cannot locate ImageJ directory.")
|
||||||
|
|
||||||
|
private def inferIJDir(path: Path): Option[Path] =
|
||||||
|
if isIJDir(path) then
|
||||||
|
Option(path)
|
||||||
|
else if path.endsWith(os.rel / "Contents" / "MacOS") then
|
||||||
|
Option(path / os.up / os.up)
|
||||||
|
else
|
||||||
|
None
|
||||||
|
|
||||||
|
private def isIJDir(path: Path): Boolean =
|
||||||
|
os.exists(path) &&
|
||||||
|
os.isDir(path) &&
|
||||||
|
os.list(path).exists(f => f.last == jarsDirName && os.isDir(f))
|
||||||
|
|
||||||
|
private def asPath(filePath: String): Either[String, Path] =
|
||||||
|
if filePath.trim.startsWith("~") then
|
||||||
|
try
|
||||||
|
Right(Path.expandUser(filePath))
|
||||||
|
catch
|
||||||
|
case NonFatal(ex) =>
|
||||||
|
Left(s"Not an absolute path: '$filePath' [${ex.getMessage}]")
|
||||||
|
else
|
||||||
|
FilePath(filePath) match
|
||||||
|
case p: Path => Right(p)
|
||||||
|
case _ => Left(s"Not an absolute path: '$filePath'")
|
||||||
|
|
||||||
|
end IJDir
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package ij_plugins.imagej_launcher
|
package ij_plugins.imagej_launcher
|
||||||
|
|
||||||
|
import ij_plugins.imagej_launcher.IJDir.jarsDirName
|
||||||
import ij_plugins.imagej_launcher.Launcher.javaExeFileName
|
import ij_plugins.imagej_launcher.Launcher.javaExeFileName
|
||||||
import ij_plugins.imagej_launcher.Main.Config
|
import ij_plugins.imagej_launcher.Main.Config
|
||||||
import os.Path
|
import os.Path
|
||||||
|
|
@ -12,9 +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):
|
||||||
|
|
||||||
private val jarsDirName = "jars"
|
|
||||||
|
|
||||||
def run(config: Config): Unit =
|
def run(config: Config): Unit =
|
||||||
prepareLaunch(config) match
|
prepareLaunch(config) match
|
||||||
|
|
@ -29,33 +28,16 @@ class Launcher(logger: Logger):
|
||||||
|
|
||||||
private def prepareLaunch(config: Config): Either[String, Seq[String]] =
|
private def prepareLaunch(config: Config): Either[String, Seq[String]] =
|
||||||
for
|
for
|
||||||
ijDir <- locateIJDir(config)
|
ijDir <- IJDir.locate(config)
|
||||||
_ <- Updater.update(Path(ijDir), config.dryRun, logger)
|
_ <- Updater.update(ijDir, config.dryRun)
|
||||||
launcherJar <- findImageJLauncherJar(ijDir)
|
ijConfig <- IJConfigFile.readFromDir(ijDir)
|
||||||
javaExe <- locateJavaExecutable(config, ijDir)
|
launcherJar <- findImageJLauncherJar(ijDir.toIO)
|
||||||
|
javaExe <- locateJavaExecutable(config, ijDir.toIO)
|
||||||
systemType <- determineSystemType()
|
systemType <- determineSystemType()
|
||||||
yield
|
yield
|
||||||
val maxMemoryMB = determineMaxMemoryMB()
|
val maxMemoryMB = determineMaxMemoryMB()
|
||||||
logger.info(s"Max memory to use: ${maxMemoryMB}MB")
|
logger.info(s"Max memory to use: ${maxMemoryMB}MB")
|
||||||
buildCommandLine(ijDir, javaExe, launcherJar, systemType, maxMemoryMB)
|
buildCommandLine(ijDir.toIO, javaExe, launcherJar, systemType, maxMemoryMB)
|
||||||
|
|
||||||
private def locateIJDir(config: Config): Either[String, File] =
|
|
||||||
logger.debug("Looking for ImageJ directory")
|
|
||||||
|
|
||||||
val dir = config.ijDir match
|
|
||||||
case Some(d) =>
|
|
||||||
logger.debug(" Considering provided ij-dir")
|
|
||||||
d
|
|
||||||
case None =>
|
|
||||||
logger.debug(" Considering current directory")
|
|
||||||
new File(".").getCanonicalFile
|
|
||||||
|
|
||||||
dir.listFiles().find(f => f.getName == jarsDirName && f.isDirectory) match
|
|
||||||
case Some(_) =>
|
|
||||||
logger.info(s" ImageJ directory set to: '$dir'")
|
|
||||||
Right(dir)
|
|
||||||
case None =>
|
|
||||||
Left(s"Cannot locate ImageJ directory. No subdirectory '$jarsDirName' in '$dir''")
|
|
||||||
|
|
||||||
private def findImageJLauncherJar(ijDir: File): Either[String, File] =
|
private def findImageJLauncherJar(ijDir: File): Either[String, File] =
|
||||||
logger.debug("Looking for 'imagej-launcher*.jar'")
|
logger.debug("Looking for 'imagej-launcher*.jar'")
|
||||||
|
|
@ -197,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 ...")
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,60 @@
|
||||||
|
|
||||||
package ij_plugins.imagej_launcher
|
package ij_plugins.imagej_launcher
|
||||||
|
|
||||||
import ij_plugins.imagej_launcher.Logger.Level
|
import ij_plugins.imagej_launcher.Logger.{Level, logToFile}
|
||||||
|
import os.Path
|
||||||
|
|
||||||
|
import java.nio.file.{Files, StandardOpenOption}
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
|
||||||
class Logger(val level: Level = Level.Info):
|
class Logger(val level: Level = Level.Info):
|
||||||
|
|
||||||
def debug(msg: String): Unit = pprint(Level.Debug, msg)
|
def debug(msg: String): Unit = pprint(Level.Debug, msg)
|
||||||
def info(msg: String): Unit = pprint(Level.Info, msg)
|
def info(msg: String): Unit = pprint(Level.Info, msg)
|
||||||
def error(msg: String): Unit = pprint(Level.Error, msg)
|
def error(msg: String): Unit = pprint(Level.Error, msg)
|
||||||
|
|
||||||
private def pprint(l: Level, message: String): Unit =
|
private def pprint(l: Level, message: String): Unit =
|
||||||
|
val m = f"${l.name}%-5s: $message"
|
||||||
if l.level <= level.level then println(f"${l.name}%-5s: $message")
|
if l.level <= level.level then println(f"${l.name}%-5s: $message")
|
||||||
|
logToFile(m)
|
||||||
|
|
||||||
object Logger:
|
object Logger:
|
||||||
|
|
||||||
|
private val LogFile: Path = os.home / ".ijp_imagej_launcher.log"
|
||||||
|
private var logFileReset: Boolean = true
|
||||||
|
|
||||||
enum Level(val level: Int, val name: String):
|
enum Level(val level: Int, val name: String):
|
||||||
case Off extends Level(0, "OFF")
|
case Off extends Level(0, "OFF")
|
||||||
case Error extends Level(200, "ERROR")
|
case Error extends Level(200, "ERROR")
|
||||||
case Info extends Level(400, "INFO")
|
case Info extends Level(400, "INFO")
|
||||||
case Debug extends Level(500, "DEBUG")
|
case Debug extends Level(500, "DEBUG")
|
||||||
case All extends Level(Int.MaxValue, "DEBUG")
|
case All extends Level(Int.MaxValue, "DEBUG")
|
||||||
|
|
||||||
|
private def logToFile(message: String): Unit =
|
||||||
|
try
|
||||||
|
if logFileReset && os.exists(LogFile) then
|
||||||
|
val _ = os.remove(LogFile)
|
||||||
|
logFileReset = false
|
||||||
|
|
||||||
|
val data = readableTimeStamp() + ": " + message + "\n"
|
||||||
|
val lines = data.getBytes()
|
||||||
|
val _ = Files.write(
|
||||||
|
LogFile.toNIO,
|
||||||
|
lines,
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.APPEND,
|
||||||
|
StandardOpenOption.WRITE
|
||||||
|
)
|
||||||
|
catch
|
||||||
|
case NonFatal(_) =>
|
||||||
|
// Ignore exceptions
|
||||||
|
|
||||||
|
private def readableTimeStamp(): String =
|
||||||
|
// Simple implementation returns GMT time (no date)
|
||||||
|
// Use custom code as DateTimeFormatter is not available in Scala Native 0.4.14
|
||||||
|
val t = System.currentTimeMillis()
|
||||||
|
val sss = t % 1000
|
||||||
|
val ss = (t / 1000L).toInt % 60
|
||||||
|
val mm = (t / (1000L * 60L)).toInt % 60
|
||||||
|
val hh = (t / (1000L * 60L * 60L)).toInt % 24
|
||||||
|
f"$hh%02d-$mm%02d-$ss%02d_$sss%03dZ"
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,14 @@
|
||||||
|
|
||||||
package ij_plugins.imagej_launcher
|
package ij_plugins.imagej_launcher
|
||||||
|
|
||||||
import scopt.{DefaultOEffectSetup, OParser}
|
import scopt.OParser
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object Main:
|
object Main:
|
||||||
private var logger = new Logger()
|
private var logger = new Logger()
|
||||||
private val AppName = "ijp-imagej-launcher"
|
private val AppName = "IJP-ImageJ-Launcher"
|
||||||
// private val AppVersion = s"${Version.version} [${Version.buildTimeStr}]"
|
private val AppVersion = BuildInfo.version
|
||||||
private val AppVersion = s"0.1.0"
|
|
||||||
private val VersionMessage = s"v.$AppVersion"
|
private val VersionMessage = s"v.$AppVersion"
|
||||||
private val AppDescription =
|
private val AppDescription =
|
||||||
"""Native launcher for ImageJ2
|
"""Native launcher for ImageJ2
|
||||||
|
|
@ -29,6 +28,7 @@ object Main:
|
||||||
case None =>
|
case None =>
|
||||||
|
|
||||||
private def parseCommandLine(args: Array[String]): Option[Config] =
|
private def parseCommandLine(args: Array[String]): Option[Config] =
|
||||||
|
logger.debug("Command line: " + args.map("'" + _ + "'").mkString(", "))
|
||||||
val builder = OParser.builder[Config]
|
val builder = OParser.builder[Config]
|
||||||
val parser1 =
|
val parser1 =
|
||||||
import builder.*
|
import builder.*
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,29 @@
|
||||||
package ij_plugins.imagej_launcher
|
package ij_plugins.imagej_launcher
|
||||||
|
|
||||||
import scala.scalanative.unsafe.extern
|
import os.{Path, up}
|
||||||
|
|
||||||
|
import scala.scalanative.unsafe
|
||||||
|
import scala.scalanative.unsafe.*
|
||||||
|
|
||||||
object Native:
|
object Native:
|
||||||
|
|
||||||
|
def applicationPath(): Path =
|
||||||
|
val maxPath: CSize = argv0.path_max()
|
||||||
|
// use Zone to manage native memory
|
||||||
|
val exePath =
|
||||||
|
Zone { implicit z =>
|
||||||
|
val buffer: CString = alloc[CChar](maxPath)
|
||||||
|
argv0.get_exe_path(buffer, maxPath)
|
||||||
|
unsafe.fromCString(buffer)
|
||||||
|
}
|
||||||
|
Path(exePath) / up
|
||||||
|
|
||||||
@extern
|
@extern
|
||||||
object mem:
|
object mem:
|
||||||
def determineTotalSystemMemory(): Long = extern
|
def determineTotalSystemMemory(): Long = extern
|
||||||
|
|
||||||
|
@extern
|
||||||
|
private object argv0:
|
||||||
|
def path_max(): CSize = extern
|
||||||
|
|
||||||
|
def get_exe_path(exe_path: CString, size: CSize): Unit = extern
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved.
|
||||||
|
* Author's e-mail: jpsacha at gmail.com
|
||||||
|
*/
|
||||||
|
|
||||||
package ij_plugins.imagej_launcher
|
package ij_plugins.imagej_launcher
|
||||||
|
|
||||||
import os.{Path, RelPath}
|
import os.Path
|
||||||
|
|
||||||
import scala.util.control.NonFatal
|
import scala.util.control.NonFatal
|
||||||
|
|
||||||
|
|
@ -13,7 +18,7 @@ object Updater:
|
||||||
* @param logger configured logger
|
* @param logger configured logger
|
||||||
* @return Number of files processed or an error message.
|
* @return Number of files processed or an error message.
|
||||||
*/
|
*/
|
||||||
def update(ijDir: Path, dryRun: Boolean, logger: Logger): Either[String, Long] =
|
def update(ijDir: Path, dryRun: Boolean)(using logger: Logger): Either[String, Long] =
|
||||||
try
|
try
|
||||||
val updateDir = ijDir / "update"
|
val updateDir = ijDir / "update"
|
||||||
// Count used only for debug info
|
// Count used only for debug info
|
||||||
|
|
@ -23,8 +28,7 @@ object Updater:
|
||||||
os.walk(updateDir)
|
os.walk(updateDir)
|
||||||
.filter(os.isFile)
|
.filter(os.isFile)
|
||||||
.foreach: src =>
|
.foreach: src =>
|
||||||
// val relativeDir = src.relativeTo(updateDir)
|
val dst = ijDir / src.relativeTo(updateDir)
|
||||||
val dst = ijDir / relativeTo(src, updateDir)
|
|
||||||
if os.size(src) == 0 then
|
if os.size(src) == 0 then
|
||||||
logger.debug(s"remove: $dst")
|
logger.debug(s"remove: $dst")
|
||||||
if !dryRun then os.remove(dst)
|
if !dryRun then os.remove(dst)
|
||||||
|
|
@ -35,7 +39,7 @@ object Updater:
|
||||||
if !dryRun then os.move(src, dst, replaceExisting = true, createFolders = true)
|
if !dryRun then os.move(src, dst, replaceExisting = true, createFolders = true)
|
||||||
count += 1
|
count += 1
|
||||||
logger.debug(s"Delete update directory: $updateDir")
|
logger.debug(s"Delete update directory: $updateDir")
|
||||||
if !dryRun then deleteEmptyDirs(updateDir, logger)
|
if !dryRun then deleteEmptyDirs(updateDir)
|
||||||
Right(count)
|
Right(count)
|
||||||
else
|
else
|
||||||
logger.info("No update found")
|
logger.info("No update found")
|
||||||
|
|
@ -45,30 +49,11 @@ object Updater:
|
||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
Left(s"Failed to perform update: ${ex.getMessage} - ${ex.getClass.getSimpleName}")
|
Left(s"Failed to perform update: ${ex.getMessage} - ${ex.getClass.getSimpleName}")
|
||||||
|
|
||||||
private def relativeTo(src: Path, base: Path): RelPath =
|
private def deleteEmptyDirs(dir: Path)(using logger: Logger): Unit =
|
||||||
// This does what src.relativeTo(base) should do
|
|
||||||
// Problems is in the native code on Windows,
|
|
||||||
// os.Path#relativeTo creates relative path by adding `../` at the beginning of the absolute path,
|
|
||||||
// co you may get `../C:\a\b` which leads to a exception soon after.
|
|
||||||
// The issue is with Scala Native implementation of java.nio.file.Path#relativize on Windows,
|
|
||||||
// See https://github.com/scala-native/scala-native/issues/3293
|
|
||||||
|
|
||||||
// This implementation is very limited but sufficient for our use.
|
|
||||||
// It assumes specific relation between src and base.
|
|
||||||
|
|
||||||
val srcStr = src.toString
|
|
||||||
val baseStr = base.toString
|
|
||||||
require(baseStr.nonEmpty)
|
|
||||||
require(srcStr.startsWith(baseStr))
|
|
||||||
|
|
||||||
val relStr = srcStr.drop(baseStr.length + 1)
|
|
||||||
RelPath(relStr)
|
|
||||||
|
|
||||||
private def deleteEmptyDirs(dir: Path, logger: Logger): Unit =
|
|
||||||
logger.debug(s"Cleaning directory: $dir")
|
logger.debug(s"Cleaning directory: $dir")
|
||||||
os.list(dir)
|
os.list(dir)
|
||||||
.filter(os.isDir)
|
.filter(os.isDir)
|
||||||
.foreach(p => deleteEmptyDirs(p, logger))
|
.foreach(p => deleteEmptyDirs(p))
|
||||||
|
|
||||||
if os.list(dir).isEmpty then
|
if os.list(dir).isEmpty then
|
||||||
logger.debug(s"Removing empty dir: $dir")
|
logger.debug(s"Removing empty dir: $dir")
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
28
src/test/scala/ij_plugins/imagej_launcher/PathSpec.scala
Normal file
28
src/test/scala/ij_plugins/imagej_launcher/PathSpec.scala
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
import org.scalatest.matchers.should
|
||||||
|
|
||||||
|
import java.nio.file.FileSystems
|
||||||
|
import scala.scalanative.runtime.Platform.isWindows
|
||||||
|
|
||||||
|
class PathSpec extends AnyFlatSpec with should.Matchers:
|
||||||
|
|
||||||
|
"A Path" should "should `relativize` jars on Windows (Scala Native #3293)" in {
|
||||||
|
if isWindows() then
|
||||||
|
// This to test that fix for Scala Native #3293 implemented, it should be part of Scala Native 0.4.13
|
||||||
|
// See https://github.com/scala-native/scala-native/issues/3293
|
||||||
|
|
||||||
|
// val src = Path.of("C:\\a\\b\\c.jar")
|
||||||
|
val src = FileSystems.getDefault.getPath("C:\\a\\b\\c.jar")
|
||||||
|
// val base = Path.of("C:\\a")
|
||||||
|
val base = FileSystems.getDefault.getPath("C:\\a")
|
||||||
|
|
||||||
|
val rel = base.relativize(src)
|
||||||
|
rel.toString should be("b\\c.jar")
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import os.Path
|
||||||
|
|
||||||
@main
|
@main
|
||||||
def updaterDemo(ijDir: String): Unit =
|
def updaterDemo(ijDir: String): Unit =
|
||||||
Updater.update(Path(ijDir), dryRun = false, new Logger(Logger.Level.Debug)) match
|
given logger: Logger = new Logger(Logger.Level.Debug)
|
||||||
|
Updater.update(Path(ijDir), dryRun = false) match
|
||||||
case Right(count) => println(s"Processed $count files")
|
case Right(count) => println(s"Processed $count files")
|
||||||
case Left(error) => println(s"Failed with error: $error")
|
case Left(error) => println(s"Failed with error: $error")
|
||||||
|
|
|
||||||
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