diff --git a/bin/get-firefly b/bin/get-firefly
new file mode 100644
index 0000000000..0fde541c48
--- /dev/null
+++ b/bin/get-firefly
@@ -0,0 +1,9 @@
+#!/bin/bash
+INSTALL_SCRIPT="https://raw.githubusercontent.com/Caltech-IPAC/firefly/refs/heads/dev/bin/install.sh"
+#todo remove next line before PR merge
+INSTALL_SCRIPT="https://raw.githubusercontent.com/Caltech-IPAC/firefly/refs/heads/FIREFLY-1980-standalone/bin/install.sh"
+
+curl -s ${INSTALL_SCRIPT} > ./install.sh
+chmod +x ./install.sh
+./install.sh -dontConfirm "$@"
+/bin/rm -f ./install.sh
\ No newline at end of file
diff --git a/bin/install.sh b/bin/install.sh
new file mode 100755
index 0000000000..369cf50b41
--- /dev/null
+++ b/bin/install.sh
@@ -0,0 +1,241 @@
+#!/bin/bash
+
+defaultInstallRelativePath="firefly"
+INSTALL_DIR="$PWD/$defaultInstallRelativePath"
+fireflyDir="${HOME}/.firefly"
+applicationPath="current"
+url=
+
+startScript="ff"
+altUrl=
+installJre="TRUE"
+initialInstall="TRUE"
+installType="installing"
+doExit="FALSE"
+doHelp="FALSE"
+confirm="TRUE"
+firstInvalid="TRUE"
+space=" "
+
+echo "params: " $*
+
+
+# --------------------------
+# define the isTrue function
+# --------------------------
+
+isTrue() {
+ v=$(echo "$1" | tr '[:upper:]' '[:lower:]')
+ if [[ "$v" == "true" || "$v" == "t" ]]; then return 0; else return 1; fi
+}
+
+# --------------------------
+# get the parameters
+# --------------------------
+
+while [ $# -gt 0 ]; do
+ arg="$1"
+ if [[ "$arg" == "-url" ]]; then
+ shift
+ altUrl=$1
+ elif [[ "$arg" == "-installDir" ]]; then
+ shift
+ enteredPath=$1
+ mkdir -p "$enteredPath"
+ INSTALL_DIR=$(realpath "$1")
+ elif [[ "$arg" == "-asUpdate" ]]; then
+ installJre="FALSE"
+ initialInstall="FALSE"
+ applicationPath="new"
+ installType="updating"
+ elif [[ "$arg" == "-dontConfirm" ]]; then
+ confirm="FALSE"
+ elif [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
+ doHelp="TRUE"
+ doExit="TRUE"
+ else
+ if isTrue $firstInvalid; then
+ echo "Invalid arguments passed."
+ firstInvalid="FALSE"
+ fi
+ echo "$space" "invalid argument:" "$arg"
+ doHelp="TRUE"
+ fi
+ shift
+done
+
+# --------------------------
+# Help on the parameters
+# --------------------------
+
+if isTrue $doHelp; then
+ echo "Options:"
+ echo "$space -url: the url or the path to the firefly install zip"
+ echo "$space -installDir: the firefly install dir, defaults to ./firefly"
+ echo "$space -dontConfirm: the firefly install dir, defaults to ./firefly"
+ echo "$space -asUpdate: stage the install as an auto update"
+ echo "$space --help, -h: this message and exit"
+ exit 0;
+fi
+
+
+# --------------------------
+# validate and/or override install dir
+# --------------------------
+
+if isTrue $confirm && isTrue $initialInstall && [ "$enteredPath" == "" ]; then
+ read -p "Enter installation directory [${enteredPath:-$defaultInstallRelativePath}]: " enteredPath
+fi
+
+
+if [ "$INSTALL_DIR" != "" ]; then
+ mkdir -p "$INSTALL_DIR"
+fi
+if [[ ! -w $INSTALL_DIR ]]; then
+ echo "Cannot write to the installation dir ${INSTALL_DIR:-$enteredPath}"
+ exit 1
+fi
+
+# --------------------------
+# The install work begin here
+# --------------------------
+
+echo "$installType in $INSTALL_DIR"
+
+PACKAGE_ASSET_NAME="standalone.zip"
+#todo remove next line before PR merge
+PACKAGE_ASSET_NAME="test_standalone.zip"
+applicationRoot="${INSTALL_DIR}/application"
+applicationDir="${applicationRoot}/${applicationPath}"
+binDir="${INSTALL_DIR}/bin"
+
+# --------------------------
+# make the directories,
+# --------------------------
+
+mkdir -p "$fireflyDir"
+mkdir -p "$fireflyDir/server"
+mkdir -p "$applicationDir"
+mkdir -p "$binDir"
+echo "$INSTALL_DIR" > "$fireflyDir/applicationPath.txt"
+rm -f "$applicationDir"/complete
+
+# --------------------------
+# make the directories,
+# --------------------------
+JQ=$(which jq)
+if [[ "$JQ" == '' ]]; then
+ name=$(uname)
+ if [[ "$name" == "Darwin" ]]; then
+ echo jq is is missing from mac os, install failed
+ exit 1
+ fi
+ arch=$(uname -m)
+
+ if [[ "$arch" == "x86_64" ]]; then
+ jqUrl="https://github.com/jqlang/jq/releases/latest/download/jq-linux-amd64"
+ else
+ jqUrl="https://github.com/jqlang/jq/releases/latest/download/jq-linux-arm64"
+ fi
+ echo "installing local jq..."
+ curl -sL "$jqUrl" -o "$binDir/jq"
+ chmod +x "$binDir/jq"
+ JQ="$binDir/jq"
+fi
+
+
+# --------------------------
+# determine the default location of the standalone.jar package
+# the default come from a github assert of the current firefly release
+# --------------------------
+
+targetPackageFile="${applicationDir}/standalone.zip"
+
+packageUrl=$(curl -s "https://api.github.com/repos/Caltech-IPAC/firefly/releases/latest" | \
+$JQ -r '.assets[] | [.name, .browser_download_url] | @tsv' | \
+while IFS=$'\t' read -r asset_name download_url; do
+ if [ "$asset_name" == $PACKAGE_ASSET_NAME ]; then
+ echo "$download_url"
+ fi
+done)
+if [ -z "$altUrl" ]; then
+ url=$packageUrl
+else
+ url=$altUrl
+fi
+
+if [ -z "$url" ]; then
+ echo "No package defined to download, could not find it as a github asset https://github.com/Caltech-IPAC/firefly/releases"
+ exit 0
+fi
+
+
+# --------------------------
+# Download or copy the standalone.zip, expand, then expand firefly.war
+# --------------------------
+
+echo "install from: $url"
+if [[ "$url" == http* ]]; then
+ curl -sL "$url" > "${targetPackageFile}"
+else
+ cp "$url" "${targetPackageFile}"
+fi
+echo "expanding firefly $targetPackageFile..."
+(cd "$applicationDir" && unzip -o "${targetPackageFile}" &> "${applicationDir}/standalone-expand.log")
+mkdir -p "$applicationDir/firefly-war"
+echo "expanding firefly.war..."
+(cd "$applicationDir/firefly-war" && unzip -o "${applicationDir}/firefly.war" &> "${applicationDir}/war-expand.log")
+
+# --------------------------
+# make the script executable, put some in correct place
+# --------------------------
+
+scriptPath=$(realpath "$0")
+cp "$scriptPath" "$applicationDir/install.sh"
+chmod 775 "$applicationDir/standalone_cleanup.sh" \
+ "$applicationDir/$startScript" \
+ "$applicationDir/startFireflyServer.sh" \
+ "$applicationDir/javaInstaller.sh" \
+ "$applicationDir/updater.sh" \
+ "$applicationDir/install.sh"
+/bin/mv "$applicationDir/updater.sh" "$applicationRoot"
+
+cp "$applicationDir/$startScript" "$binDir"
+chmod +x "$binDir/$startScript"
+
+# --------------------------
+# setup default port
+# --------------------------
+
+
+if isTrue $initialInstall; then
+ /bin/cp "$applicationDir/default_config.json" "$fireflyDir/config.json"
+ if [ ! -f "$fireflyDir/user_ops.sh" ]; then
+ echo "JAVA_OPS=" > "$fireflyDir/user_ops.sh"
+ fi
+fi
+
+# --------------------------
+# install java
+# --------------------------
+
+if isTrue $installJre; then
+ echo "installing java..."
+ JAVA=$("$applicationDir"/javaInstaller.sh)
+fi
+
+# --------------------------
+# success message
+# --------------------------
+
+if isTrue $initialInstall; then
+ echo
+ echo "Firefly successfully installed, to start Firefly use the $startScript command"
+ echo
+ echo ">>>>>>>>>>>>>>>>>>>>>> ${binDir#$PWD/}/ff start"
+ echo
+ echo "You might want to add the bin dir to your PATH: $binDir"
+fi
+
+
+touch "$applicationDir"/complete
\ No newline at end of file
diff --git a/buildScript/dependencies.gradle b/buildScript/dependencies.gradle
index 63b88a6061..c161bd450a 100644
--- a/buildScript/dependencies.gradle
+++ b/buildScript/dependencies.gradle
@@ -108,6 +108,8 @@ dependencies {
implementation 'org.asdf-format:asdf-core:0.1-alpha-10'
implementation 'edu.stsci:roman-datamodels:0.1-alpha-3'
+ // tomcat for standalone
+
}
diff --git a/config/web.xml b/config/web.xml
index 0bce306268..fec09b1874 100644
--- a/config/web.xml
+++ b/config/web.xml
@@ -89,29 +89,6 @@
/CmdSrv/async/*
-
- H2Console
- org.h2.server.web.WebServlet
-
- -webAllowOthers
- true
-
- 2
-
-
- H2Console
- /admin/db/*
-
-
-
- alertviewer
- /alertviewer.html
-
-
- alertviewer
- /alertviewer/*
-
-
diff --git a/docs/using-firefly-standalone.md b/docs/using-firefly-standalone.md
new file mode 100644
index 0000000000..4fa8f211de
--- /dev/null
+++ b/docs/using-firefly-standalone.md
@@ -0,0 +1,172 @@
+
+
+# Installing a personal Firefly instance
+
+Firefly can be installed directly on your macOS or Linux desktop machine.
+This is a full-featured installation that performs very well when working with local files.
+
+
+## Installing Firefly
+
+### Quick install
+
+```bash
+curl -L https://raw.githubusercontent.com/Caltech-IPAC/firefly/refs/heads/dev/bin/get-firefly | bash
+```
+#### Usage
+1. Change to the directory where you want to install Firefly.
+1. Run the command above.
+1. The installer will create a firefly directory containing the application and supporting files.
+
+
+### Advanced Install
+
+#### 1. Download the installer
+
+```bash
+curl -L https://raw.githubusercontent.com/Caltech-IPAC/firefly/refs/heads/dev/bin/install.sh -o install.sh
+```
+
+#### 2. Run the installer
+
+```bash
+chmod +x install.sh
+./install.sh
+```
+
+#### 3. Choose installation options
+
+The installer will prompt you for a destination directory.
+
+Use the following command to see all available options:
+
+```bash
+./install.sh -h
+```
+
+
+---
+
+## Starting Firefly
+
+After installation, Firefly provides instructions for starting the server.
+
+The Firefly server is managed using the `firefly/bin/ff` script.
+
+To see all available commands and options:
+
+```bash
+firefly/bin/ff --help
+```
+
+When Firefly starts, it automatically opens in your default web browser.
+
+The `ff` script uses commands as its primary argument.
+
+### Start Firefly
+
+```bash
+firefly/bin/ff start
+```
+
+### Start Firefly in the Background
+
+```bash
+firefly/bin/ff start --background
+```
+
+### Stop Firefly
+
+(Only needed when running in background mode.)
+
+```bash
+firefly/bin/ff stop
+```
+
+### Tail Log Files
+
+```bash
+firefly/bin/ff logs -f
+```
+
+### Check Server Status
+
+```bash
+firefly/bin/ff status
+```
+
+### Uninstall Firefly
+
+```bash
+firefly/bin/ff uninstall
+```
+
+---
+
+## macOS UI Integration
+
+On macOS, Firefly creates a menu bar icon on the right side of the system menu bar.
+
+Use the drop-down menu to control and monitor the Firefly server.
+
+## Advanced Configuration
+
+Firefly can be configured using the JSON file:
+
+```text
+~/.firefly/config.json
+```
+
+Edit this file to change the ports Firefly uses or to specify your own Java installation.
+
+Firefly requires Java 21 or later. By default, Firefly uses `"auto"` to automatically select a compatible Java runtime.
+
+If you want to use a Java installation already available on your system, replace the `java` entry in `config.json` with the path to your Java executable.
+
+### Default Configuration
+
+```json
+{
+ "ports": {
+ "firefly": 10233,
+ "redis": 10234
+ },
+ "java": "auto"
+}
+```
+
+### Example Custom Configuration
+
+This example changes the Firefly port and uses a local Java installation:
+
+```json
+{
+ "ports": {
+ "firefly": 7777,
+ "redis": 102346
+ },
+ "java": "/usr/bin/java"
+}
+```
+
+### Confirming firefly will run on your OS
+
+#### OSX
+Firefly requires macOS 15 or greater
+
+#### Linux
+
+Firefly requires that `libssl.so.3` is on your linux system.
+Check with the following command
+```bash
+ /sbin/ldconfig -p | grep libssl.so.3
+```
+#### Linux version with `libssl.so.3`
+- Debian 12 or later
+- Red Hat 9 or later
+- Ubuntu 22.04 LTS or later
+- Fedora all recent releases
+
+#### Windows
+Standalone Firefly is not supported on Windows
+
diff --git a/settings.gradle b/settings.gradle
index ccc464db07..d0a80321cf 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,7 +1,8 @@
rootProject.name = 'firefly_root'
-include 'firefly', 'firefly_data'
+include 'firefly', 'firefly_data', "standalone"
project(":firefly").projectDir = file('src/firefly')
project(":firefly_data").projectDir = file('src/firefly_data')
+project(":standalone").projectDir = file('src/standalone')
diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/util/VersionUtil.java b/src/firefly/java/edu/caltech/ipac/firefly/server/util/VersionUtil.java
index a340ecb394..718deb2eb4 100644
--- a/src/firefly/java/edu/caltech/ipac/firefly/server/util/VersionUtil.java
+++ b/src/firefly/java/edu/caltech/ipac/firefly/server/util/VersionUtil.java
@@ -64,32 +64,35 @@ public static void initVersion(ServletContext context) {
}
public static void ingestVersion(ServletContext context) {
-
- _version.setAppName(context.getServletContextName());
- _version.setConfigLastModTime(ServerContext.getConfigLastModTime());
-
- File confDir = ServerContext.getWebappConfigDir();
- Properties props = new Properties();
try {
+ _version.setAppName(context.getServletContextName());
+ _version.setConfigLastModTime(ServerContext.getConfigLastModTime());
+
+ File confDir = ServerContext.getWebappConfigDir();
+ Properties props = new Properties();
props.load(new FileInputStream(new File(confDir, VERSION_FILE)));
- _version.setMajor(getNum(props.getProperty(MAJOR)));
- _version.setMinor(getNum(props.getProperty(MINOR)));
- _version.setRev(props.getProperty(REV));
- _version.setVersionType(Version.convertVersionType(props.getProperty(TYPE)));
- _version.setBuild(getNum(props.getProperty(BUILD_NUMBER)));
- _version.setBuildDate(props.getProperty(BUILD_DATE));
- _version.setBuildTime(props.getProperty(BUILD_TIME));
- _version.setBuildTag(props.getProperty(BUILD_TAG));
- _version.setBuildCommit(props.getProperty(BUILD_COMMIT));
- _version.setBuildCommitFirefly(props.getProperty(BUILD_COMMIT_FIREFLY));
- _version.setBuildFireflyTag(props.getProperty(BUILD_FIREFLY_TAG));
- _version.setBuildFireflyBranch(props.getProperty(BUILD_FIREFLY_BRANCH));
- _version.setDevCycleTag(props.getProperty(DEV_CYCLE_TAG));
+ ingestVersion(props);
} catch (IOException e) {
// just ignore
}
}
+ public static void ingestVersion(Properties props) {
+ _version.setMajor(getNum(props.getProperty(MAJOR)));
+ _version.setMinor(getNum(props.getProperty(MINOR)));
+ _version.setRev(props.getProperty(REV));
+ _version.setVersionType(Version.convertVersionType(props.getProperty(TYPE)));
+ _version.setBuild(getNum(props.getProperty(BUILD_NUMBER)));
+ _version.setBuildDate(props.getProperty(BUILD_DATE));
+ _version.setBuildTime(props.getProperty(BUILD_TIME));
+ _version.setBuildTag(props.getProperty(BUILD_TAG));
+ _version.setBuildCommit(props.getProperty(BUILD_COMMIT));
+ _version.setBuildCommitFirefly(props.getProperty(BUILD_COMMIT_FIREFLY));
+ _version.setBuildFireflyTag(props.getProperty(BUILD_FIREFLY_TAG));
+ _version.setBuildFireflyBranch(props.getProperty(BUILD_FIREFLY_BRANCH));
+ _version.setDevCycleTag(props.getProperty(DEV_CYCLE_TAG));
+ }
+
public static Version getAppVersion() { return _version; }
private static int getNum(String s) {
diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisContext.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisContext.java
index 7f2bdc7ee8..a63cbd0ede 100644
--- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisContext.java
+++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisContext.java
@@ -27,7 +27,9 @@ public class VisContext {
static public void init() {
if (_initialized) return;
- System.setProperty("java.awt.headless", "true");
+// System.setProperty("java.awt.headless", "true");
+ boolean desktop= AppProperties.getBooleanProperty("runAsDesktopApplication", false);
+ System.setProperty("java.awt.headless", desktop ? "false" : "true"); //todo make this smart depending on context, no PR until it is done!!!!
initFootprints();
initCounters();
_initialized = true;
diff --git a/src/firefly/java/edu/caltech/ipac/util/StringUtils.java b/src/firefly/java/edu/caltech/ipac/util/StringUtils.java
index 4584c83640..bdfd01e4ac 100644
--- a/src/firefly/java/edu/caltech/ipac/util/StringUtils.java
+++ b/src/firefly/java/edu/caltech/ipac/util/StringUtils.java
@@ -4,7 +4,6 @@
package edu.caltech.ipac.util;
import edu.caltech.ipac.firefly.core.Util;
-import edu.caltech.ipac.firefly.server.util.Logger;
import javax.validation.constraints.NotNull;
import java.net.MalformedURLException;
@@ -45,8 +44,6 @@ public static enum Align {LEFT, RIGHT, MIDDLE}
public static final long GIG_HUNDREDTH= GIG / 100;
public static final long K = 1024;
- private static final Logger.LoggerImpl logger = Logger.getLogger();
-
public static String[] groupMatch(String regex, String val) {
return groupMatch(regex, val, 0);
}
diff --git a/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java b/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java
index 123e6462ae..e21bfc0403 100644
--- a/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java
+++ b/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java
@@ -58,7 +58,6 @@
public class URLDownload {
private static final int BUFFER_SIZE = FileUtil.BUFFER_SIZE;
- private static final Logger.LoggerImpl _log = Logger.getLogger();
private static final int MAX_REDIRECT= 2;
static {
@@ -83,7 +82,7 @@ public void checkServerTrusted(X509Certificate[] arg0, String arg1) { }
sc.init(null, trustAllCerts, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (KeyManagementException | NoSuchAlgorithmException e) {
- _log.error(e);
+ loggerError(e);
}
}
@@ -200,11 +199,13 @@ public static Map buildReqHeaders(URL url, Map r
if (requestHeaders== null) requestHeaders= Collections.emptyMap();
Map h = new HashMap<>(requestHeaders);
if (ops==null || ops.useCredentials) {
- var inputs= new HttpServiceInput(url.toString());
- var credentials= inputs.getHeaders();
- if (credentials!=null && !credentials.isEmpty()) {
- if (!credentials.keySet().stream().allMatch(h::containsKey)) h.putAll(credentials);
- }
+ try {
+ var inputs= new HttpServiceInput(url.toString());
+ var credentials= inputs.getHeaders();
+ if (credentials!=null && !credentials.isEmpty()) {
+ if (!credentials.keySet().stream().allMatch(h::containsKey)) h.putAll(credentials);
+ }
+ } catch (NoClassDefFoundError ignore) { }
}
return h;
}
@@ -247,7 +248,11 @@ private static String sendHeadersToCompactStr(Map> sendHeade
}
}
else {
- workBuff.append(sanitizeHeader(se.getKey(), String.valueOf(se.getValue())));
+ try {
+ workBuff.append(sanitizeHeader(se.getKey(), String.valueOf(se.getValue())));
+ } catch (NoClassDefFoundError e) {
+ workBuff.append(se.getKey()+"="+se.getValue());
+ }
}
outStr.append(workBuff.toString());
}
@@ -306,7 +311,7 @@ public static URLConnection makeConnection(URL url,
}
return conn;
} catch (IOException e) {
- logError(url,null,e);
+ loggerError(url,null,e);
throw e;
}
}
@@ -390,10 +395,10 @@ public static HttpResultInfo getDataFromURL(URL url,
if (!ops.logErrorsOnly) logSuccess(result,url,dlSeconds,reqProp, postData);
return result;
} catch (SSLException | SocketTimeoutException | UnknownHostException e) {
- logError(url, postData, e);
+ loggerError(url, postData, e);
return exceptionToResponse(e,requestHeaders);
} catch (IOException e) {
- logError(url, postData, e);
+ loggerError(url, postData, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e), e, getResponseCode(conn));
}
}
@@ -418,7 +423,7 @@ public static HttpResultInfo getHeader(URL url,
} catch (SSLException | SocketTimeoutException | UnknownHostException e) {
return exceptionToResponse(e,h);
} catch (IOException e) {
- logError(url, null, e);
+ loggerError(url, null, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e), e, -1);
}
}
@@ -476,10 +481,10 @@ private static HttpResultInfo getHeaderFromConnection(HttpURLConnection conn,
}
return result;
} catch (SSLException | SocketTimeoutException | UnknownHostException e) {
- logError(conn.getURL(), null , e);
+ loggerError(conn.getURL(), null , e);
return exceptionToResponse(e,requestHeaders);
} catch (IOException e) {
- logError(conn.getURL(), null, e);
+ loggerError(conn.getURL(), null, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e), e, getResponseCode(conn));
}
}
@@ -505,7 +510,7 @@ public static FileInfo getDataToFileUsingPost(URL url, Map postData,
Options ops= new Options(true, true, 0L, false, false, timeoutInSec, dl, false, false);
return getDataToFile(makeURLConnection(url, cookies, requestHeader), outfile, ops, postData,0);
} catch (IOException e) {
- logError(url, postData, e);
+ loggerError(url, postData, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e), e);
}
}
@@ -636,7 +641,7 @@ public static FileInfo getDataToFile(HttpURLConnection conn,
} catch (SSLException | SocketTimeoutException | UnknownHostException e) {
return exceptionToFileInfo(e);
} catch (IOException e) {
- logError(conn.getURL(), null, e);
+ loggerError(conn.getURL(), null, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e),e, getResponseCode(conn));
}
}
@@ -760,7 +765,7 @@ private static FileInfo checkAlreadyDownloaded(HttpURLConnection urlConn, File o
urlConn.setIfModifiedSince(outfile.lastModified());
if (getResponseCode(urlConn) == HttpURLConnection.HTTP_NOT_MODIFIED) {
String urlStr= urlConn.getURL().toString();
- _log.info(outfile.getName() + ": Not downloading, already have current version, from "+urlStr);
+ loggerInfo(outfile.getName() + ": Not downloading, already have current version, from "+urlStr);
retval = new FileInfo(outfile, getSuggestedFileName(urlConn), HttpURLConnection.HTTP_NOT_MODIFIED,
ResponseMessage.getHttpResponseMessage(HttpURLConnection.HTTP_NOT_MODIFIED));
retval.putAttribute(FileInfo.FILE_DOWNLOADED,false+"");
@@ -774,7 +779,7 @@ private static FileInfo checkAlreadyDownloaded(HttpURLConnection urlConn, File o
}
- private static void logError(URL url, Map postData, Exception e) {
+ private static void loggerError(URL url, Map postData, Exception e) {
List strList = new ArrayList<>(6);
strList.add("----------Network Error-----------");
if (url != null) {
@@ -788,7 +793,7 @@ private static void logError(URL url, Map postData, Exception e) {
strList.add(StringUtils.pad(20,"----------Exception "));
strList.add(e.toString());
}
- _log.warn(strList.toArray(new String[0]));
+ loggerInfo(strList.toArray(new String[0]));
}
private static void logHeaderForError(String originalUrl, Map postData, HttpURLConnection conn, Map> sendHeaders) {
@@ -861,9 +866,9 @@ private static void logHeaderForError(String originalUrl, Map postData
else {
outStr.add("No headers or status received, invalid http response, using work around");
}
- _log.info(outStr.toArray(new String[0]));
+ loggerInfo(outStr.toArray(new String[0]));
} catch (Exception e) {
- _log.info(e.getMessage() + ":" + " url=" + (conn.getURL()!=null ? conn.getURL().toString() : "none"));
+ loggerInfo(e.getMessage() + ":" + " url=" + (conn.getURL()!=null ? conn.getURL().toString() : "none"));
}
}
@@ -880,7 +885,7 @@ private static void logSuccess(FileInfo fileInfo, File outfile, URL url, double
"more response headers: "+otherHeadersToStr(fileInfo)
));
if (postData!=null) strList.add("post data: " +postDataLogString(postData));
- _log.info(strList.toArray(new String[0]));
+ loggerInfo(strList.toArray(new String[0]));
}
private static void logSuccess(HttpResultInfo r, URL url, double dSeconds, Map> sendHeaders, Map postData) {
@@ -889,7 +894,7 @@ private static void logSuccess(HttpResultInfo r, URL url, double dSeconds, Map values) {
return workBuff.toString();
}
+ private static void loggerInfo(String ...msgs) {
+ try {
+ Logger.getLogger().info(msgs);
+ } catch (NoClassDefFoundError t) {
+ Arrays.stream(msgs).forEach(System.out::println);
+ }
+ }
+
+ private static void loggerError(Throwable t, String ...msgs) {
+ try {
+ Logger.getLogger().error(t, msgs);
+ } catch (NoClassDefFoundError e) {
+ System.out.println(t.getMessage());
+ Arrays.stream(msgs).forEach(System.out::println);
+ }
+ }
+
public static class Options {
private boolean onlyIfModified;
private boolean uncompress;
diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java
index 4904666054..b39cfb8bad 100644
--- a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java
+++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java
@@ -249,6 +249,7 @@ public static void setDefaultFutureStretch(RangeValues defaultRangeValues) {
* @param lsstMasks mask array
* @deprecated
*/
+ @Deprecated
public synchronized void doStretchMask(
byte[] pixelData,
int startPixel,
diff --git a/src/standalone/assets/default_config.json b/src/standalone/assets/default_config.json
new file mode 100644
index 0000000000..720b616dc4
--- /dev/null
+++ b/src/standalone/assets/default_config.json
@@ -0,0 +1,7 @@
+{
+ "ports" : {
+ "firefly" : 10233,
+ "redis" : 10234
+ },
+ "java" : "auto"
+}
diff --git a/src/standalone/assets/ff b/src/standalone/assets/ff
new file mode 100644
index 0000000000..db0f3f8cee
--- /dev/null
+++ b/src/standalone/assets/ff
@@ -0,0 +1,129 @@
+#!/bin/bash
+
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+testFile="$SCRIPT_DIR/../application/current/startFireflyServer.sh"
+fireflyDir="${HOME}/.firefly"
+logsDir="${fireflyDir}/server/logs"
+pidFile="$fireflyDir/pid.txt"
+space=" "
+
+if [ -x "$testFile" ]; then
+ INSTALL_DIR=$(realpath "$SCRIPT_DIR/..")
+else
+ INSTALL_DIR=$(cat "$fireflyDir/applicationPath.txt")
+fi
+
+applicationDir="${INSTALL_DIR}/application/current"
+
+isTrue() {
+ v=$(echo "$1" | tr '[:upper:]' '[:lower:]')
+ if [[ "$v" == "true" || "$v" == "t" ]]; then return 0; else return 1; fi
+}
+
+
+stop() {
+ if [[ "$1" == "help" || "$1" == "-h" || "$1" == "--help" ]]; then
+ echo "stop the firefly server"
+ else
+ if [[ -f "$pidFile" && -s "$pidFile" && -r "$pidFile" ]]; then
+ pid=$(cat "$pidFile");
+ kill "$pid"
+ echo "Stopping firefly at process id $pid"
+ else
+ echo "Filefly is not running or pid file was lost"
+ fi
+ fi
+}
+
+uninstall() {
+ if [[ "$1" == "help" || "$1" == "-h" || "$1" == "--help" ]]; then
+ echo "Uninstall the firefly server."
+ echo "The uninstall command will remove $fireflyDir and $INSTALL_DIR"
+ else
+ echo "uninstall will remove $fireflyDir and $INSTALL_DIR"
+ read -n1 -s -p "do you want to uninstall the firefly server? (y/n [n]): " doUninstall
+ echo
+ doUninstallLower=$(echo "$doUninstall" | tr '[:upper:]' '[:lower:]')
+ if [ "$doUninstallLower" = "y" ]; then
+ echo removing "$fireflyDir"
+ /bin/rm -rf "$fireflyDir"
+ echo removing "$INSTALL_DIR"
+ exec /bin/rm -rf "$INSTALL_DIR"
+ fi
+ fi
+}
+
+logs() {
+ if [[ "$1" == "-f" || "$1" == "--follow" ]]; then
+ tail -f "$logsDir/application.log" "$logsDir/firefly.log"
+ elif [[ "$1" == "help" || "$1" == "-h" || "$1" == "--help" ]]; then
+ echo "show the log files"
+ echo "-f: tail the logs, -f will follow, like tail -f"
+ else
+ echo "------------------------------------------------------------"
+ echo "---------------------- application.log ---------------------"
+ echo "------------------------------------------------------------"
+ cat "$logsDir/application.log"
+ echo
+ echo
+ echo "------------------------------------------------------------"
+ echo "---------------------- firefly.log -------------------------"
+ echo "------------------------------------------------------------"
+ cat "$logsDir/firefly.log"
+ fi
+}
+
+stat() {
+ if [[ "$1" == "help" || "$1" == "-h" || "$1" == "--help" ]]; then
+ echo "show if the firefly server is running and healthy"
+ else
+ up=$(curl -sD - -o /dev/null http://localhost:8888/firefly/healthz | head -1)
+ if [[ "$up" == *200* ]]; then
+ echo "The Firefly server is running and healthy"
+ elif [[ "$up" == "" ]]; then
+ echo "The Firefly server is not running"
+ else
+ echo "The Firefly server is return an error: $up"
+ fi
+ fi
+}
+
+
+help() {
+ echo
+ echo "-------- Help -------- "
+ echo "The ff command controls the firefly server and provides utilities"
+ echo "commands"
+ echo "$space start: start the firefly server"
+ echo "$space stop: stop the firefly server"
+ echo "$space logs: show the log files"
+ echo "$space status: show the server status"
+ echo "$space uninstall: uninstall the firstly server"
+ echo "$space help: show this message"
+ echo "you may follow a command with -h to get help on that command"
+ echo "example- ff start -h"
+ echo "example- ff logs -f"
+}
+
+cmd=$1
+shift;
+if [[ "$cmd" == "start" || "$cmd" == "s" ]]; then
+ exec "$applicationDir/startFireflyServer.sh" "$@"
+elif [ "$cmd" == "stop" ]; then
+ stop "$@"
+elif [[ "$cmd" == "logs" || "$cmd" == "log" || "$cmd" == "l" ]]; then
+ logs "$@"
+elif [ "$cmd" == "status" ]; then
+ stat "$@"
+elif [ "$cmd" == "uninstall" ]; then
+ uninstall "$@"
+elif [[ "$cmd" == "help" || "$cmd" == "-h" || "$cmd" == "--help" ]]; then
+ help
+elif [[ "$cmd" == "" ]]; then
+ help
+else
+ echo "invalid command: $cmd"
+ help
+fi
+
diff --git a/src/standalone/assets/fireflyDockIcon.png b/src/standalone/assets/fireflyDockIcon.png
new file mode 100644
index 0000000000..9df5ee29d9
Binary files /dev/null and b/src/standalone/assets/fireflyDockIcon.png differ
diff --git a/src/standalone/assets/fireflySplash-old.png b/src/standalone/assets/fireflySplash-old.png
new file mode 100644
index 0000000000..3281c98ca5
Binary files /dev/null and b/src/standalone/assets/fireflySplash-old.png differ
diff --git a/src/standalone/assets/fireflySplash.png b/src/standalone/assets/fireflySplash.png
new file mode 100644
index 0000000000..e5761e206e
Binary files /dev/null and b/src/standalone/assets/fireflySplash.png differ
diff --git a/src/standalone/assets/javaInstaller.sh b/src/standalone/assets/javaInstaller.sh
new file mode 100755
index 0000000000..a113168870
--- /dev/null
+++ b/src/standalone/assets/javaInstaller.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+
+getJreKey() {
+ arch=$(uname -m)
+ name=$(uname)
+ jarKey="unknown"
+
+ if [[ "$name" == "Darwin" ]]; then
+ if [[ "$arch" == "arm64" ]]; then
+ jreKey="macOSArm64"
+ else
+ jreKey="macOSIntel"
+ fi
+ else
+ if [[ "$arch" == "aarch64" ]]; then
+ jreKey="linuxArm64"
+ elif [[ "$arch" == "x86_64" ]]; then
+ jreKey="linux64"
+ elif [[ "$arch" == *aarch* ]]; then
+ jreKey="linux64"
+ elif [[ "$arch" == *arm* ]]; then
+ jreKey="linux64"
+ elif [[ "$arch" == *amd64* ]]; then
+ jreKey="linux64"
+ elif [[ "$name" == "Linux" ]]; then
+ jreKey="linux64"
+ else
+ jreKey=
+ fi
+ fi
+
+ echo "$jreKey"
+}
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+INSTALL_DIR=$(cd "${SCRIPT_DIR}/../.." && pwd)
+binDir="${INSTALL_DIR}/bin"
+fireflyDir="${HOME}/.firefly"
+javaInstallation="${INSTALL_DIR}/javaInstallation"
+jreJsonFile="${INSTALL_DIR}/application/current/jreVersion.json"
+configJsonFile="$fireflyDir/config.json"
+JQ=$(which jq || echo "$binDir/jq")
+
+
+jreKey=$(getJreKey)
+
+javaOverride=$($JQ -r ".java" "$configJsonFile")
+if [[ $javaOverride != 'auto' && $javaOverride != "" && $javaOverride == *java ]]; then
+ JAVA=$javaOverride
+else
+ jreUrl=$($JQ -r ".$jreKey.url" "$jreJsonFile")
+ javaPath=$($JQ -r ".$jreKey.java" "$jreJsonFile")
+ JAVA="$javaInstallation/$javaPath"
+fi
+
+if [ -f "$JAVA" ]; then
+ echo $JAVA
+ exit 0
+fi
+
+mkdir "$javaInstallation"
+curl -L "$jreUrl" > "$javaInstallation/jre.tar.gz"
+(cd "$javaInstallation" && tar -xzvf jre.tar.gz &> $javaInstallation/jre_tar_expand.log)
+echo $JAVA
+
diff --git a/src/standalone/assets/jreVersion.json b/src/standalone/assets/jreVersion.json
new file mode 100644
index 0000000000..42b679aac7
--- /dev/null
+++ b/src/standalone/assets/jreVersion.json
@@ -0,0 +1,23 @@
+{
+ "javaVersion" : "21.0.11",
+ "linux64" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jre/hotspot/normal/eclipse",
+ "java" : "jdk-21.0.11+10-jre/bin/java"
+ },
+ "linuxArm64" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/linux/aarch64/jre/hotspot/normal/eclipse",
+ "java" : "jdk-21.0.11+10-jre/bin/java"
+ },
+ "macOSIntel" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/mac/x64/jre/hotspot/normal/eclipse",
+ "java" : "jdk-21.0.11+10-jre/Contents/Home/bin/java"
+ },
+ "macOSArm64" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/mac/aarch64/jre/hotspot/normal/eclipse",
+ "java" : "jdk-21.0.11+10-jre/Contents/Home/bin/java"
+ },
+ "window" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/windows/x64/jre/hotspot/normal/eclipse",
+ "java" : "don't know"
+ }
+}
\ No newline at end of file
diff --git a/src/standalone/assets/package-files.txt b/src/standalone/assets/package-files.txt
new file mode 100644
index 0000000000..5bc73bcdfc
--- /dev/null
+++ b/src/standalone/assets/package-files.txt
@@ -0,0 +1,6 @@
+
+firefly.war
+tomcat-annotations-api-11.0.21.jar
+tomcat-embed-core-11.0.21.jar
+tomcat-embed-websocket-11.0.21.jar
+standalone.jar
diff --git a/src/standalone/assets/standalone_cleanup.sh b/src/standalone/assets/standalone_cleanup.sh
new file mode 100644
index 0000000000..0735bf2b85
--- /dev/null
+++ b/src/standalone/assets/standalone_cleanup.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+cleanupMinutes=1440
+logfileMaxAge=7200
+rm_cmd=/bin/rm
+rm_cmd="echo will remove:"
+workarea_dir="${HOME}/.firefly/server/workarea/firefly"
+shared_workarea="${HOME}/.firefly/server/shared-workarea"
+find_cmd="/usr/bin/find"
+
+doCleanup() {
+ workarea="${1}"
+ log_dir="${workarea}/cleanup_logs"
+ mkdir -p "${log_dir}"
+
+ # cleanup old log files
+ ${find_cmd} "${log_dir}" -type f -mmin +${logfileMaxAge} -exec $rm_cmd '{}' \+
+
+ # cleanup old work files
+ timestamp=$(date +20%y%m%dT%H%M%S)
+ log_file="${log_dir}/cleanup.${timestamp}.log"
+ clean_dirs=("${workarea}/temp_files" "${workarea}/visualize/fits-cache" "${workarea}/visualize/users")
+ dirs_to_clear=("${workarea}/visualize/users" "${workarea}/temp_files")
+ echo "Cleanup: " $workarea
+ echo 'Cleanup: log file: ' "${log_file}"
+ {
+ echo "Cleaning up work files older that ${cleanupMinutes} minutes, dir: ${workarea}"
+ [[ -d "${workarea}/HiPS" ]] && ${find_cmd} "${workarea}/HiPS" -type f -mtime +90 -exec $rm_cmd '{}' \+ -print
+ [[ -d "${workarea}/stage" ]] && ${find_cmd} "${workarea}/stage" -type f -mtime +7 -exec $rm_cmd '{}' \+ -print
+ [[ -d "${workarea}/upload" ]] && ${find_cmd} "${workarea}/upload" -type f -mtime +7 -exec $rm_cmd '{}' \+ -print
+ [[ -d "${workarea}/perm_files" ]] && ${find_cmd} "${workarea}/perm_files" -type f -atime +1 -exec $rm_cmd '{}' \+ -print
+ for dir in "${clean_dirs[@]}"; do
+ if [ -d "${dir}" ]; then
+ ${find_cmd} "${dir}" -type f -amin +${cleanupMinutes} -exec $rm_cmd '{}' \+ -print
+ fi
+ done
+ for dir in "${dirs_to_clear[@]}"; do # remove empty directories excluding those at the starting level
+ [[ -d "${dir}" ]] && ${find_cmd} "${dir}" -mindepth 1 -depth -type d -empty -print -exec $rm_cmd '{}' \;
+ done
+ } > "${log_file}" 2>&1
+}
+
+
+# Remove temporary products for each Firefly workarea
+# Find app directories (should be only one, but loop for safety)
+doCleanup "${workarea_dir}"
+
diff --git a/src/standalone/assets/startFireflyServer.sh b/src/standalone/assets/startFireflyServer.sh
new file mode 100755
index 0000000000..6c35c5e770
--- /dev/null
+++ b/src/standalone/assets/startFireflyServer.sh
@@ -0,0 +1,260 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+INSTALL_DIR=$(cd "${SCRIPT_DIR}/../.." && pwd)
+fireflyDir="${HOME}/.firefly"
+fireflyServer="${HOME}/.firefly/server"
+applicationDir="${INSTALL_DIR}/application/current"
+appNew="${INSTALL_DIR}/application/new"
+applicationJars="${applicationDir}/jars"
+appLog="${fireflyServer}/logs/application.log"
+userOpsFile="${fireflyDir}/user_ops.sh"
+configJsonFile="$fireflyDir/config.json"
+ADMIN_USER="admin"
+ADMIN_PASSWORD="admin"
+MIN_JVM_SIZE=1G
+MAX_JVM_SIZE=10G
+binDir="${INSTALL_DIR}/bin"
+JQ=$(which jq || echo "$binDir/jq")
+
+# todo - i think we can remove serverConfigDir
+serverConfigDir="${HOME}/config"
+
+isTrue() {
+ v=$(echo "$1" | tr '[:upper:]' '[:lower:]')
+ if [[ "$v" == "true" || "$v" == "t" ]]; then return 0; else return 1; fi
+}
+
+
+getFireflyStatusOnPort() {
+ curl --max-time 12 -sD - -o /dev/null http://localhost:$1/firefly/healthz > /tmp/fireflyStatusCheck.txt
+ curlStat=$?
+ if [ $curlStat -eq 28 ]; then
+ echo "INUSE"
+ return;
+ else
+ up=$(head -1 /tmp/fireflyStatusCheck.txt)
+ fi
+
+ if [[ "$up" == *200* ]]; then
+ echo "UP"
+ elif [[ "$up" == "" ]]; then
+ echo "FREE"
+ else
+ echo "INUSE"
+ fi
+}
+
+debugParams=
+loggingLevel="INFO"
+doClean="FALSE"
+verbose="FALSE"
+doExit="FALSE"
+doHelp="FALSE"
+firstInvalid="TRUE"
+inBackground="TRUE"
+overridePort=""
+alreadyRunning="FALSE"
+space=" "
+
+while [ $# -gt 0 ]; do
+ arg="$1"
+ if [[ "$arg" == "--debug" || "$arg" == "-debug" ]]; then
+ debugParams="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005"
+ elif [ "$arg" == "--verbose" ]; then
+ verbose="TRUE"
+ loggingLevel="DEBUG"
+ elif [ "$arg" == "--clean" ]; then
+ doClean="TRUE"
+ elif [ "$arg" == "--cleanAndExit" ]; then
+ doClean="TRUE"
+ doExit="TRUE"
+ elif [[ "$arg" == "-f" || "$arg" == "--foreground" ]]; then
+ inBackground="FALSE"
+ elif [[ "$arg" == "--port" ]]; then
+ shift
+ overridePort=$1
+ elif [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
+ doHelp="TRUE"
+ doExit="TRUE"
+ else
+ if isTrue $firstInvalid; then
+ echo "Invalid arguments passed."
+ firstInvalid="FALSE"
+ fi
+ echo "$space" "invalid argument:" "$arg"
+ doHelp="TRUE"
+ fi
+ shift
+done
+
+if isTrue $doHelp; then
+ echo "Options:"
+ echo "$space --debug, -debug: start and pause in java debug mode on port 5005"
+ echo "$space --verbose: more startup logging and set java log level to debug"
+ echo "$space --clean: clean work area before startup"
+ echo "$space --cleanAndExit: clean work only and exit"
+ echo "$space -f, --foreground: start in foreground (start in background by default)"
+ echo "$space --port: a port number to override the default firefly port, it can also be set in ~/.firefly/config.json"
+ echo "$space --help, -h: this message and exit"
+ exit 0;
+fi
+
+if isTrue $doClean; then
+ /bin/rm -rf "${fireflyServer}/workarea"
+ echo "removing: ${fireflyServer}/workarea"
+ /bin/rm -rf "${fireflyServer}/temp"
+ echo "removing: ${fireflyServer}/temp"
+ /bin/rm -rf "${fireflyServer}/logs"
+ echo "removing: ${fireflyServer}/logs"
+ /bin/rm -rf "${fireflyServer}/work"
+ echo "removing: ${fireflyServer}/work"
+ if isTrue $doExit; then
+ exit 0;
+ fi
+ fi
+
+if [[ -d "$appNew" && -f "$appNew/complete" ]]; then
+ if [[ -f "$INSTALL_DIR/disableUpdate" ]]; then
+ echo ">>>>>>>>> Update available but disabled"
+ else
+ echo ">>>>>>>>> updating..."
+ exec "$appNew/../updater.sh"
+ fi
+fi
+
+if [ ! -f "$applicationDir/jars/firefly.jar" ]; then
+ FILES_FROM_WAR="WEB-INF/lib/firefly.jar WEB-INF/lib/json-simple-1.1.1.jar WEB-INF/config/version.tag"
+ (cd $applicationDir && unzip -oj firefly.war ${FILES_FROM_WAR} )
+fi
+
+redisPort=$($JQ -r ".ports.redis" "$configJsonFile")
+
+if [[ $overridePort == "" ]]; then
+ fireflyPort=$($JQ -r ".ports.firefly" "$configJsonFile")
+else
+ fireflyPort=$overridePort
+fi
+
+ffStat=$(getFireflyStatusOnPort "$fireflyPort")
+if [[ $ffStat == "UP" ]]; then
+ alreadyRunning="TRUE"
+elif [[ $ffStat == "FREE" ]]; then
+ alreadyRunning="FALSE"
+elif [[ $ffStat == "INUSE" ]]; then
+ echo "The port number $fireflyPort is being used by another application"
+ echo "You can change the port my editing ~/.firefly/config.json or by using the --port parameter"
+ exit 1;
+fi
+
+
+[ ! -d "${applicationJars}" ] && mkdir "$applicationJars"
+[ ! -d "${fireflyServer}/temp" ] && mkdir "${fireflyServer}/temp"
+[ ! -d "${fireflyServer}/logs" ] && mkdir "${fireflyServer}/logs"
+if isTrue $verbose; then
+ echo "$applicationDir/*.jar" to "$applicationJars"
+fi
+if ls $applicationDir/*.jar >/dev/null 2>&1; then
+ /bin/mv $applicationDir/*.jar "$applicationJars"
+fi
+
+
+
+JAVA_OPS=
+if [ -f "$userOpsFile" ]; then
+ source $userOpsFile
+ if isTrue $verbose; then
+ echo JAVA_OPS = $JAVA_OPS
+ fi
+fi
+
+
+name=$(uname)
+if [[ "$name" == "Darwin" ]]; then
+ splash="-splash:${applicationDir}/fireflySplash.png"
+ nameParam='-Xdock:name=Firefly Server'
+ runAsDesktopApplication="true"
+ headless="false"
+ #dockIcon="-Xdock:icon=${applicationDir}/fireflyDockIcon.png" --- keep around if we decide to read dock
+else
+ splash=
+ nameParam="-DdockPlaceHolder="
+ runAsDesktopApplication="false"
+ headless="true"
+ #dockIcon=
+fi
+
+PROPS=" \
+ -Dapple.awt.UIElement=true \
+ -Xms${MIN_JVM_SIZE} -Xmx${MAX_JVM_SIZE} ${debugParams} \
+ --add-opens java.base/java.util=ALL-UNNAMED \
+ -XX:+UnlockExperimentalVMOptions \
+ -XX:TrimNativeHeapInterval=30000 \
+ -XX:+UseZGC \
+ -Dnet.sf.ehcache.enableShutdownHook=true \
+ -Dlogging.level=${loggingLevel} \
+ -Djava.net.preferIPv4Stack=true \
+ -Dwork.directory=${fireflyServer}/workarea \
+ -DrunAsDesktopApplication=${runAsDesktopApplication} \
+ -Djava.awt.headless=${headless} \
+ -Dvisualize.fits.search.path=${HOME} \
+ -Dredis.db.dir=${fireflyServer}/temp/redis \
+ -Djava.io.tmpdir=${fireflyServer}/temp \
+ -Dalerts.dir=${fireflyServer}/alerts \
+ -Dserver_config_dir=${serverConfigDir} \
+ -Dfirefly.port=${fireflyPort}
+ -Dredis.port=${redisPort:-6379} \
+ -DADMIN_USER=${ADMIN_USER} \
+ -DADMIN_PASSWORD=${ADMIN_PASSWORD} \
+ -DADMIN_PROTECTED= \
+ -DuserHelpToLog=${inBackground} \
+ ${JAVA_OPS}
+ "
+
+
+JAVA=$("$applicationDir"/javaInstaller.sh)
+
+export CLASSPATH=${applicationJars}'/*'
+if isTrue $verbose; then
+ echo
+ echo Using classpath
+ echo $CLASSPATH
+ echo
+fi
+
+readyFile="$fireflyDir/ready-${fireflyPort}.txt"
+/bin/rm -f "$readyFile"
+{
+ echo "------------------------------------------------"
+ echo "---------- Starting Firefly server"
+ echo "---------- $(date)"
+ echo "------------------------------------------------"
+ echo ${JAVA} ${splash} "${nameParam}" ${PROPS} edu.caltech.ipac.app.FireflyApplication
+} >> "$appLog"
+
+if isTrue $inBackground; then
+ (cd "$applicationDir" && ${JAVA} ${splash} "${nameParam}" ${PROPS} edu.caltech.ipac.app.FireflyApplication &> "${fireflyServer}/logs/backgroundStart.log" &)
+ if isTrue $alreadyRunning; then
+ echo "Firefly is already running on port"
+ else
+ echo "Firefly server starting in background (it takes a few seconds)..."
+ fi
+
+ echo
+ echo "---------------------------------"
+ echo "Firefly URL: http://localhost:$fireflyPort/firefly/"
+ echo "---------------------------------"
+
+ if ! isTrue $alreadyRunning; then
+ echo -n "Firefly server waiting for init to complete..."
+ ready=$(cat "$readyFile" 2> /dev/null)
+ while ! isTrue $ready; do
+ sleep .5
+ ready=$(cat "$readyFile" 2> /dev/null)
+ done
+ echo "Ready"
+ fi
+else
+ (cd "$applicationDir" && ${JAVA} ${splash} "${nameParam}" ${PROPS} edu.caltech.ipac.app.FireflyApplication )
+fi
+
diff --git a/src/standalone/assets/updater.sh b/src/standalone/assets/updater.sh
new file mode 100644
index 0000000000..4bf9539dc4
--- /dev/null
+++ b/src/standalone/assets/updater.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+INSTALL_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
+applicationRoot="${INSTALL_DIR}/application"
+appNew="${applicationRoot}/new"
+appCurrent="${applicationRoot}/current"
+appOld="${applicationRoot}/old"
+
+
+if [[ -d "$appNew" && -d "$appCurrent" && -f "$appNew/complete" ]]; then
+ /bin/rm -rf "$appOld"
+ /bin/mv "$appCurrent" "$appOld"
+ /bin/mv "$appNew" "$appCurrent"
+fi
+
+exec "$appCurrent/startFireflyServer.sh"
+
diff --git a/src/standalone/build.gradle b/src/standalone/build.gradle
new file mode 100644
index 0000000000..1445217383
--- /dev/null
+++ b/src/standalone/build.gradle
@@ -0,0 +1,76 @@
+group 'edu.caltech.ipac'
+
+ext["app-name"] = 'standalone'
+
+configurations {
+ bundled
+}
+
+sourceSets {
+ main.java.srcDir '.'
+ main.resources {
+ srcDirs "."
+ exclude "**/*.gradle"
+ }
+}
+
+dependencies {
+ implementation ':firefly'
+ implementation 'org.apache.tomcat.embed:tomcat-embed-core:11.0.21'
+ implementation 'org.apache.tomcat.embed:tomcat-embed-websocket:11.0.21'
+ implementation 'org.apache.tomcat.embed:tomcat-embed-jasper:11.0.21'
+ bundled 'org.apache.tomcat.embed:tomcat-embed-core:11.0.21'
+ bundled 'org.apache.tomcat.embed:tomcat-embed-websocket:11.0.21'
+ bundled 'org.apache.tomcat.embed:tomcat-embed-jasper:11.0.21'
+}
+
+// sourceSets {
+// main.java {
+// srcDir '**/firefly_standalone/**'
+// }
+// }
+
+
+jar {
+ archiveFileName = 'firefly_standalone.jar'
+ include "**/*"
+ from sourceSets.main.allJava
+}
+
+
+def stageTarget= file("${buildDir}/zipFiles")
+def fireflyWar= project(':firefly').tasks.named('war')
+def fireflyData= project(':firefly_data').tasks.named('jar')
+def fireflyWarFile= file("$rootDir/build/dist/firefly.war")
+
+compileJava.dependsOn fireflyWar
+
+task stageFiles(type: Copy) {
+ dependsOn jar
+ into stageTarget
+// from { fireflyWar.get().archivePath } // show firefly-1.0.war
+ from { fireflyWarFile }
+ from { jar.archivePath }
+ from configurations.bundled
+
+ from("${rootDir}/src/standalone/assets")
+ }
+
+task zip(type: Jar, dependsOn: stageFiles) {
+ archiveFileName = "standalone.zip"
+ from "${buildDir}/zipFiles"
+ destinationDirectory = file ("$rootDir/build/dist")
+}
+
+task copyStandaloneDependencies(dependsOn: jar) {
+ doLast {
+ def homePath = System.properties['user.home']
+ def publishDir = project.group.replaceAll("\\.", "/") + "/${project.name}/${project.version}"
+ def repoDir = file("${homePath}/.m2/repository/" + publishDir)
+ copy {
+ from(configurations.bundled)
+ into repoDir
+ }
+ println "Firefly local maven directory: ${repoDir}"
+ }
+}
diff --git a/src/standalone/java/edu/caltech/ipac/app/FireflyApplication.java b/src/standalone/java/edu/caltech/ipac/app/FireflyApplication.java
new file mode 100644
index 0000000000..fa8938a88e
--- /dev/null
+++ b/src/standalone/java/edu/caltech/ipac/app/FireflyApplication.java
@@ -0,0 +1,519 @@
+package edu.caltech.ipac.app;
+
+import edu.caltech.ipac.firefly.server.util.VersionUtil;
+import edu.caltech.ipac.util.AppProperties;
+import edu.caltech.ipac.util.FileUtil;
+import edu.caltech.ipac.util.StringUtils;
+import edu.caltech.ipac.util.download.FailedRequestException;
+import edu.caltech.ipac.util.download.URLDownload;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.startup.Tomcat;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import javax.swing.*;
+import java.awt.AWTException;
+import java.awt.BorderLayout;
+import java.awt.Desktop;
+import java.awt.Image;
+import java.awt.MenuItem;
+import java.awt.PopupMenu;
+import java.awt.SplashScreen;
+import java.awt.SystemTray;
+import java.awt.Toolkit;
+import java.awt.TrayIcon;
+import java.awt.Window;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+
+
+public class FireflyApplication {
+
+ private static final File pwd = new File(System.getProperty("user.dir"));
+ private static final File ffDir = new File(System.getProperty("user.home"), ".firefly");
+ private static final File installDir = new File(pwd, "../..").getAbsoluteFile();
+ private static final File tomcatDir = new File(ffDir,"server");
+ private static final File tomcatTmp = new File(tomcatDir, "temp");
+ private static final File tomcatLogs = new File(tomcatDir, "logs");
+ private static final File applicationRoot = new File(installDir, "application");
+ private static final File applicationDir = new File(applicationRoot, "current");
+ private static final File cleanupScript= new File(applicationDir,"standalone_cleanup.sh");
+ private static final File installScript= new File(applicationDir,"install.sh");
+ private static final File configFile = new File(ffDir, "config.json");
+ private static final File dockIconFile= new File(applicationDir,"fireflyDockIcon.png");
+ private static final File applicationLogFile= new File(tomcatLogs, "application.log");
+ private static final File fireflyWarDir= new File(applicationDir, "firefly-war");
+ private static final File versionTagPropFile= new File(applicationDir, "version.tag");
+ private static final File versionTextOutFile= new File(ffDir, "version.txt");
+ private static final String compressibleMimeType= String.join(",", Arrays.asList(
+ "text/html", "text/plain", "text/css", "text/javascript",
+ "application/javascript", "application/json", "application/xml",
+ "text/xml", "application/x-votable+xml", "application/x-yaml", "application/ld+json",
+ "image/svg+xml", "text/csv", "application/xhtml+xml",
+ "application/rss+xml", "application/atom+xml", "application/x-font-ttf",
+ "font/otf", "font/woff", "font/woff2",
+ "application/octet-stream"
+ ));
+ private static final boolean useLogFile= true;
+ private static final int DEFAULT_PORT= 8888;
+ private static String fireflyVersion;
+ private static String javaVersion;
+ private static boolean updateAvailable= false;
+ private static PrintStream terminalOut= System.out;
+ private static boolean initComplete= false;
+ private static JLabel aboutLabel= null; // only used in desktop mode
+ private static MenuItem aboutItem = null; // only used in desktop mode
+ private static boolean firstUpdateCheck= true;
+
+
+
+ public static void start() throws LifecycleException, URISyntaxException, IOException, InterruptedException {
+ fireflyVersion= saveVersion();
+ javaVersion = System.getProperty("java.version");
+ boolean useDesktop= AppProperties.getBooleanProperty("runAsDesktopApplication", false);
+ ensureFireflyDir();
+ var port= getPort();
+ File readyTextOutFile= new File(ffDir, "ready-"+port+".txt");
+// File pidTextOutFile= new File(ffDir, "pid-"+port+".txt");
+ File pidTextOutFile= new File(ffDir, "pid.txt");
+
+ if (useDesktop) SwingUtilities.invokeLater(() -> initAboutLabel(port));
+
+
+ if (useLogFile) setupLogger();
+
+ Tomcat tomcat = new Tomcat();
+ tomcat.setBaseDir(tomcatDir.getAbsolutePath());
+ tomcat.setPort(port);
+
+
+ boolean tomcatStarted = false;
+ if (!isRunning(port)) {
+ var ignore= readyTextOutFile.delete();
+ if (useDesktop) setupUI(tomcat, port, pidTextOutFile, readyTextOutFile);
+ tomcat.addUser("admin", "admin");
+ tomcat.addWebapp("/firefly", fireflyWarDir.getAbsolutePath());
+ terminalOut.println("Firefly server starting (is takes a few seconds)...");
+ Connector connector= tomcat.getConnector();
+ connector.setPort(port);
+ connector.setProperty("compression", "on");
+ connector.setProperty("useSendfile", "false");
+ connector.setProperty("compressibleMimeType", compressibleMimeType);
+ tomcat.start();
+ savePid(pidTextOutFile);
+ tomcatStarted = true;
+ }
+
+ if (!tomcatStarted) {
+ terminalOut.println("Firefly is already running");
+ openBrowser(port,true);
+ fireflyReadyMessage(port, null);
+ System.exit(0);
+ }
+
+
+ initComplete= true;
+ if (useDesktop) {
+ hideSplash();
+ SwingUtilities.invokeLater(() -> updateAboutLabel(port));
+ openBrowser(port,true);
+ }
+ fireflyReadyMessage(port, readyTextOutFile);
+ Thread.sleep(5 * 1000); // 5 seconds
+ updateAvailable= doAutoUpdateCheck();
+ doWorkAreaCleanup();
+ while (tomcat.getServer().getState().isAvailable()) {
+ Thread.sleep(3600 * 1000); // 1 hour
+ if (!updateAvailable) updateAvailable= doAutoUpdateCheck();
+ doWorkAreaCleanup();
+ }
+
+ }
+
+ public static boolean doAutoUpdateCheck() {
+ boolean updateAvailable= false;
+ try {
+ var result= URLDownload.getDataFromURL(new URI("https://api.github.com/repos/Caltech-IPAC/firefly/releases/latest").toURL(),null,null);
+ var obj= (JSONObject) new JSONParser().parse(result.getResultAsString());
+ var availableVersion= (String) obj.get("name");
+ var newVerAvailable= isNewVersionAvailable(fireflyVersion,availableVersion);
+
+ String urlStr= null;
+ var assetsAry= (JSONArray)obj.get("assets");
+ if (newVerAvailable && assetsAry!=null && !assetsAry.isEmpty()) {
+ for(Object entry: assetsAry){
+ JSONObject asset= (JSONObject)entry;
+ if (StringUtils.areEqual((String)asset.get("name"),"standalone.zip")) {
+ urlStr= (String)asset.get("url");
+ }
+ }
+ }
+ String overrideUrlStr= null;
+// overrideUrlStr= "/Users/roby/dev/firefly/build/dist/standalone.zip"; // todo remove after testing
+ if (overrideUrlStr!=null) urlStr= overrideUrlStr;
+ updateAvailable= urlStr!=null;
+ if (updateAvailable) doUpdateInstall(urlStr);
+
+ String updateMsg= updateAvailable ? ", Update available (relaunch Firefly to finish update)" : "";
+
+ String msg= "**** Update Check: Current Version: "+fireflyVersion
+ + ", Available version: "+ availableVersion
+ + ", Java Version: "+ javaVersion + updateMsg;
+
+ if (firstUpdateCheck) terminalOut.println(msg);
+ System.out.println(msg);
+ firstUpdateCheck= false;
+
+ } catch (FailedRequestException | MalformedURLException | URISyntaxException | ParseException e) {
+ System.out.println(e.toString());
+ }
+ return updateAvailable;
+ }
+
+ public static boolean isNewVersionAvailable(String currVer, String availableVer) {
+ if (currVer==null) currVer= "0,0.0";
+ if (availableVer==null) availableVer= "0,0.0";
+ var cVer= currVer.split("\\.");
+ var nVer= availableVer.split("\\.");
+ if (cVer.length!=3 || nVer.length!=3) return false;
+ var curr= Arrays.stream(cVer).map((s) -> StringUtils.getInt(s,0)).toList();
+ var next= Arrays.stream(nVer).map((s) -> StringUtils.getInt(s,0)).toList();
+ return (next.get(0)>curr.get(0) || next.get(1)>curr.get(1) || next.get(2)>curr.get(2));
+ }
+
+
+ public static void savePid(File pidTextOutFile) {
+ FileUtil.writeStringToFile(pidTextOutFile,ProcessHandle.current().pid()+"");
+ }
+
+ public static String saveVersion() {
+ try {
+ Properties props = new Properties();
+ props.load(new FileInputStream(versionTagPropFile));
+ VersionUtil.ingestVersion(props);
+ var vInfo= VersionUtil.getVersionInfo();
+ var fVerList= vInfo.stream().filter(kv -> kv.getKey().equals("Firefly Version")).toList();
+ if (fVerList.size()==1) {
+ var vStr= fVerList.getFirst().getValue();
+
+ String major="0";
+ String minor="0";
+ String rev="0";
+ var phase1= vStr.split("-");
+ var realVStr= phase1[0];
+ var parts= realVStr.split("\\.");
+ if (parts.length>1) {
+ major= parts[0];
+ minor= parts[1];
+ if (parts.length>2) rev= parts[2];
+ }
+ var version= major+"."+minor+"."+rev;
+ FileUtil.writeStringToFile(versionTextOutFile, version);
+ return version;
+ }
+ } catch (IOException e) {
+ System.out.println("failed to get version: " + e.toString());
+ }
+ return null;
+ }
+
+ public static void doUpdateInstall(String packageUrl) {
+ ProcessBuilder pb = new ProcessBuilder(installScript.getAbsolutePath(),
+ "-url", packageUrl, "-asUpdate",
+ "-installDir", installDir.getAbsolutePath() );
+ try {
+ Process process = pb.start();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ System.out.println(line);
+ }
+ }
+ int exitCode = process.waitFor();
+ if (exitCode != 0) System.out.println("auto update job failed with code: " + exitCode);
+
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void setupLogger() throws IOException {
+ Logger logger= Logger.getLogger("");
+ for (Handler h : logger.getHandlers()) {
+ logger.removeHandler(h);
+ }
+
+
+ // setup logger
+ Handler fileHandler = new FileHandler(applicationLogFile.getAbsolutePath(), true);
+ fileHandler.setFormatter(new SimpleFormatter());
+ fileHandler.setLevel(Level.ALL);
+ logger.addHandler(fileHandler);
+
+ // set system out for stuff that logger misses
+ System.setOut(new PrintStream(new FileOutputStream(applicationLogFile,true)));
+
+
+ String terminalDevice = System.getProperty("os.name").toLowerCase().contains("win")
+ ? "CON" : "/dev/tty";
+ boolean helpToLog= AppProperties.getBooleanProperty("userHelpToLog", false);
+ terminalOut = helpToLog ? System.out : new PrintStream(new FileOutputStream(terminalDevice));
+ }
+
+ public static void doWorkAreaCleanup() {
+ ProcessBuilder pb = new ProcessBuilder(cleanupScript.getAbsolutePath()," --once");
+ try {
+ Process process = pb.start();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ System.out.println(line);
+ }
+ }
+ int exitCode = process.waitFor();
+ if (exitCode != 0) System.out.println("clean up job failed with code: " + exitCode);
+
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public static void openBrowser(int port, boolean doSleep) {
+ if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
+ try {
+ if (doSleep) Thread.sleep(500);
+ Desktop.getDesktop().browse(new URI(makeUrlString(port)));
+ } catch (URISyntaxException | InterruptedException | IOException ignore) {
+ System.out.println("Could not open browser");
+ }
+ }
+ }
+
+ public static String makeUrlString(int port) { return "http://localhost:"+port+"/firefly/";}
+
+ public static void ensureFireflyDir() {
+ confirmDirOrExit(ffDir);
+ confirmDirOrExit(tomcatDir);
+ confirmDirOrExit(tomcatTmp);
+ confirmDirOrExit(tomcatLogs);
+ }
+
+ public static int getPort() {
+ int portProp= AppProperties.getIntProperty("firefly.port",0);
+ if (portProp!=0) return portProp;
+ try {
+ if (!configFile.canRead()) return DEFAULT_PORT;
+ String pStr= FileUtil.readFile(configFile);
+ if (pStr==null) return DEFAULT_PORT;
+ var obj= (JSONObject) new JSONParser().parse(pStr);
+ var ports= (JSONObject)obj.get("ports");
+ if (ports==null) return DEFAULT_PORT;
+ Long port= (Long)ports.get("firefly");
+ if (port==null) return DEFAULT_PORT;
+ return port.intValue();
+ } catch (IOException | NumberFormatException | ParseException e) {
+ return DEFAULT_PORT;
+ }
+ }
+
+ private static void confirmDirOrExit(File dir) {
+ boolean exists = true;
+ if (!dir.exists()) {
+ exists = dir.mkdir();
+ }
+ if (!exists || !dir.canWrite()) {
+ System.out.println("Can't write to " + dir.getAbsolutePath() + " directory");
+ System.exit(0);
+ }
+ }
+
+ private static void setupUI(Tomcat tomcat, int port, File pidTextOutFile, File readyTextOutFile) {
+ System.setProperty("apple.awt.UIElement", "true");
+// setupDock(port);
+ setupTray(tomcat, port, pidTextOutFile, readyTextOutFile);
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ if (tomcat.getServer().getState().isAvailable()) {
+ System.out.println("Shutting down Firefly server...");
+ tomcat.stop();
+ tomcat.destroy();
+ var ignore= pidTextOutFile.delete();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ finally {
+ Runtime.getRuntime().halt(0);
+ }
+ }));
+ }
+
+// Keep this around- we might want to reenable the dock, todo - what does linux do with this code?
+// public static void setupDock(int port) {
+// //System.setProperty("apple.awt.UIElement", "false"); <<- this property should be set to false on the java command line
+// System.setProperty("apple.laf.useScreenMenuBar", "true");
+// System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Firefly");
+// if (Desktop.isDesktopSupported()) {
+// Desktop desktop = Desktop.getDesktop();
+// if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
+// desktop.setAboutHandler(e -> showAboutDialog(port) );
+// }
+// }
+//
+// }
+
+ public static void stopFireflyServer(Tomcat tomcat, File pidTextOutFile, File readyTextOutFile) {
+ try {
+ if (tomcat.getServer().getState().isAvailable()) {
+ System.out.println("Shutting down Firefly server...");
+ tomcat.stop();
+ tomcat.destroy();
+ pidTextOutFile.delete();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ finally {
+ Runtime.getRuntime().halt(0);
+ }
+ }
+
+ public static void initAboutLabel(int port) {
+ aboutLabel= new JLabel();
+ aboutLabel.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ try {
+ Desktop.getDesktop().browse(new URI(makeUrlString(port)));
+ } catch (Exception ignore) { }
+ }
+ });
+ }
+
+ public static void updateAboutLabel(int port) {
+ if (aboutLabel==null) return;
+ String outstr= String.format("Firefly Version: %s
Java Version: %s
",
+ fireflyVersion, javaVersion);
+ outstr+= String.format("To load Firefly: %s",makeUrlString(port), makeUrlString(port));
+ if (updateAvailable) outstr+= "
"+"Update available (relaunch Firefly to finish update)";
+ if (!initComplete)outstr+= "
"+"Server Initializing...";
+ aboutLabel.setText(outstr);
+ aboutLabel.setToolTipText(outstr);
+ if (aboutItem!=null) {
+ aboutItem.setLabel(initComplete ? "About Firefly" : "About Firefly (initializing...)");
+ }
+ }
+
+ public static void showAboutDialog(int port, JFrame frame) {
+ if (aboutLabel==null) return;
+ SwingUtilities.invokeLater(() -> {
+
+ updateAboutLabel(port);
+
+ JDialog aboutDialog = new JDialog(frame, "About Firefly", true);
+ aboutDialog.setLayout(new BorderLayout());
+
+ aboutLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
+ aboutDialog.add(aboutLabel, BorderLayout.CENTER);
+
+ aboutDialog.pack();
+ aboutDialog.setSize(450, 150);
+ aboutDialog.setLocationRelativeTo(null); // Center on screen
+ aboutDialog.setAlwaysOnTop(true);
+ aboutDialog.setVisible(true);
+ });
+ }
+
+
+
+ public static boolean isRunning(int port) {
+ try (ServerSocket serverSocket = new ServerSocket(port)) {
+ return false; // Port is available
+ } catch (IOException e) {
+ return true; // Port is in use
+ }
+ }
+
+ public static void hideSplash() {
+ SplashScreen splash = SplashScreen.getSplashScreen();
+ if (splash != null) splash.close();
+ }
+
+ public static void setupTray(Tomcat tomcat, int port, File pidTextOutFile, File readyTextOutFile) {
+ System.setProperty("apple.awt.enableTemplateImages", "false");
+ if (!SystemTray.isSupported()) {
+ System.out.println("SystemTray is not supported on this platform.");
+ return;
+ }
+ SystemTray tray = SystemTray.getSystemTray();
+ Image image = Toolkit.getDefaultToolkit().getImage(dockIconFile.getAbsolutePath());
+
+ var dummyAnchor = new JFrame();
+ dummyAnchor.setType(Window.Type.UTILITY);
+ dummyAnchor.setUndecorated(true);
+ dummyAnchor.setSize(1, 1);
+ dummyAnchor.setLocationRelativeTo(null);
+
+
+ // Create a popup menu for the icon
+ PopupMenu popup = new PopupMenu();
+ MenuItem exitItem = new MenuItem("Shutdown Firefly Server");
+ aboutItem = new MenuItem("About Firefly (initializing...)");
+ MenuItem openInBrowser = new MenuItem("Open in Browser: " + makeUrlString(port));
+ popup.add(openInBrowser);
+ popup.add(aboutItem);
+ popup.addSeparator();
+ popup.add(exitItem);
+ aboutItem.addActionListener(e -> showAboutDialog(port, dummyAnchor) );
+ TrayIcon trayIcon = new TrayIcon(image, "Firefly Server", popup);
+ trayIcon.setImageAutoSize(true); // Automatically scale the image
+ openInBrowser.addActionListener(e -> openBrowser(port, false));
+ exitItem.addActionListener(e -> stopFireflyServer(tomcat, pidTextOutFile, readyTextOutFile));
+
+ try {
+ tray.add(trayIcon);
+ } catch (AWTException e) {
+ System.err.println("TrayIcon could not be added.");
+ }
+ }
+
+
+ public static void fireflyReadyMessage(int port, File readyTextOutFile) throws IOException {
+ terminalOut.println("\n---------------------------------");
+ terminalOut.println("Firefly ready: use URL: " + makeUrlString(port));
+ terminalOut.println("---------------------------------\n");
+ if (readyTextOutFile!=null) FileUtil.writeStringToFile(readyTextOutFile,"TRUE");
+ }
+
+ public static void main(String[] args) {
+ try {
+ FireflyApplication.start();
+ } catch (Exception e) {
+ terminalOut.println("Error starting Firefly Application: " + e.getMessage());
+ e.printStackTrace();
+ }
+ Runtime.getRuntime().halt(0);
+ }
+}
+