From 3a9a54213153c36b194d02a42c63dd1d3769d011 Mon Sep 17 00:00:00 2001 From: edvin Date: Sat, 20 Feb 2016 10:21:58 +0100 Subject: [PATCH 01/38] Release 1.0.7 --- CHANGELOG.md | 7 +++++-- README.md | 8 +++++++- src/main/java/fxlauncher/Launcher.java | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 286861f..03b1599 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Change Log All notable changes to this project will be documented in this file. -## [Unreleased] +## [1.0.7] - 2016-02-20 ### Added -- OS specific resources +- Support for platform specific resources + +### Changed +- Parameters are now passed to the Application instance ([#1])[https://github.com/edvin/fxlauncher/issues/1] ## [1.0.6] - 2016-02-10 - First feature complete release \ No newline at end of file diff --git a/README.md b/README.md index f3562a7..f4ed80d 100644 --- a/README.md +++ b/README.md @@ -58,4 +58,10 @@ Again, you are only distributing the launcher with the native installer, the res ### A note on classloaders FXLauncher uses a custom classloader to dynamically load the synchronized resources. This classloader is -then made available to the `FXMLLoader`. You can access it via `FXMLLoader.getDefaultClassLoader()`. \ No newline at end of file +then made available to the `FXMLLoader`. You can access it via `FXMLLoader.getDefaultClassLoader()`. + +### Platform specific resources + +From version 1.0.7, FXLauncher supports filtering of resources for the running platform. Any resource +that ends with `-[mac|win|linux].jar` will only be downloaded and put on the classpath on the corresponding +platform. The manifest enforces this though the `os` attribute in `app.xml`. \ No newline at end of file diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index ea507a8..a7415bf 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -1,5 +1,6 @@ package fxlauncher; +import com.sun.javafx.application.ParametersImpl; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; @@ -102,7 +103,8 @@ private void launchAppFromManifest() throws Exception { Platform.runLater(() -> { try { stage.close(); - app.start(primaryStage); + ParametersImpl.registerParameters(app, getParameters()); + app.start(primaryStage); } catch (Exception ex) { reportError("Failed to start application", ex); } From a53966ce597d4522dee4c73a43af7e2d0bb2a32f Mon Sep 17 00:00:00 2001 From: edvin Date: Tue, 23 Feb 2016 21:15:09 +0100 Subject: [PATCH 02/38] Maven Central Badge in README --- CHANGELOG.md | 2 +- README.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b1599..18fa125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. - Support for platform specific resources ### Changed -- Parameters are now passed to the Application instance ([#1])[https://github.com/edvin/fxlauncher/issues/1] +- Parameters are now passed to the Application instance (https://github.com/edvin/fxlauncher/issues/1) ## [1.0.6] - 2016-02-10 - First feature complete release \ No newline at end of file diff --git a/README.md b/README.md index f4ed80d..db78c87 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # FXLauncher +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/no.tornado/fxlauncher/badge.svg)](https://search.maven.org/#search|ga|1|no.tornado.fxlauncher) + Auto updating launcher for JavaFX Applications. Combined with JavaFX native packaging, you get a native installer with automatic app updates. From 09daf1730470fb8e03ff4e5ad92ea21f1fcc1fd5 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Wed, 2 Mar 2016 09:48:27 +0100 Subject: [PATCH 03/38] Manifest configurable parameters (https://github.com/edvin/fxlauncher/issues/2). Version 1.0.8 --- CHANGELOG.md | 6 ++ pom.xml | 2 +- src/main/java/fxlauncher/CreateManifest.java | 4 + src/main/java/fxlauncher/FXManifest.java | 2 + src/main/java/fxlauncher/Launcher.java | 20 ++-- src/main/java/fxlauncher/LauncherParams.java | 106 +++++++++++++++++++ 6 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 src/main/java/fxlauncher/LauncherParams.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 18fa125..a215518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +## [1.0.8] - 2016-03-02 + +### Added + +- Support for manifest configurable parameters (https://github.com/edvin/fxlauncher/issues/2) + ## [1.0.7] - 2016-02-20 ### Added diff --git a/pom.xml b/pom.xml index fb698d2..bb07b10 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.7 + 1.0.8 jar FX Launcher Auto updating launcher for JavaFX Applications diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index 4291416..9731fb6 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -13,6 +13,10 @@ public static void main(String[] args) throws IOException { String launchClass = args[1]; Path appPath = Paths.get(args[2]); FXManifest manifest = create(baseURI, launchClass, appPath); + + if (args.length == 4) + manifest.parameters = args[3]; + JAXB.marshal(manifest, appPath.resolve("app.xml").toFile()); } diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index 9b97a3d..0bc4545 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -26,6 +26,8 @@ public class FXManifest { String progressBarStyle = "-fx-pref-width: 200;"; @XmlElement String wrapperStyle = "-fx-spacing: 10; -fx-padding: 25;"; + @XmlElement + String parameters; public String getFilename() { return String.format("%s.xml", launchClass); diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index a7415bf..07eb049 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -57,11 +57,17 @@ public void start(Stage primaryStage) throws Exception { updateManifest(); createUpdateWrapper(); syncFiles(); + } catch (Exception ex) { + log.log(Level.WARNING, String.format("Error during %s phase", phase), ex); + } + + try { createApplication(); launchAppFromManifest(); } catch (Exception ex) { reportError(String.format("Error during %s phase", phase), ex); } + }).start(); } @@ -89,9 +95,9 @@ private void createUpdateWrapper() { public URLClassLoader createClassLoader() { List libs = manifest.files.stream() - .filter(LibraryFile::loadForCurrentPlatform) - .map(LibraryFile::toURL) - .collect(Collectors.toList()); + .filter(LibraryFile::loadForCurrentPlatform) + .map(LibraryFile::toURL) + .collect(Collectors.toList()); return new URLClassLoader(libs.toArray(new URL[libs.size()])); } @@ -103,8 +109,8 @@ private void launchAppFromManifest() throws Exception { Platform.runLater(() -> { try { stage.close(); - ParametersImpl.registerParameters(app, getParameters()); - app.start(primaryStage); + ParametersImpl.registerParameters(app, new LauncherParams(getParameters(), manifest)); + app.start(primaryStage); } catch (Exception ex) { reportError("Failed to start application", ex); } @@ -152,8 +158,8 @@ private void createApplication() throws Exception { URLClassLoader classLoader = createClassLoader(); FXMLLoader.setDefaultClassLoader(classLoader); - Thread.currentThread().setContextClassLoader(classLoader); - Platform.runLater(() -> Thread.currentThread().setContextClassLoader(classLoader)); + Thread.currentThread().setContextClassLoader(classLoader); + Platform.runLater(() -> Thread.currentThread().setContextClassLoader(classLoader)); Class appclass = (Class) classLoader.loadClass(manifest.launchClass); app = appclass.newInstance(); } diff --git a/src/main/java/fxlauncher/LauncherParams.java b/src/main/java/fxlauncher/LauncherParams.java new file mode 100644 index 0000000..d9678d8 --- /dev/null +++ b/src/main/java/fxlauncher/LauncherParams.java @@ -0,0 +1,106 @@ +package fxlauncher; + +import javafx.application.Application; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Implementation Application.Parameters that wraps the parameters given to the application + * at startup, and adds any manifest configured parameters unless they were overriden + * by the command line. + */ +public class LauncherParams extends Application.Parameters { + private List rawArgs; + private Map namedParams; + private List unnamedParams; + + public LauncherParams(Application.Parameters delegate, FXManifest manifest) { + rawArgs = new ArrayList<>(); + namedParams = new HashMap<>(); + unnamedParams = new ArrayList<>(); + + // Add all raw args from the parent application + rawArgs.addAll(delegate.getRaw()); + + // Add parameters from the manifest unless they were already specified on the command line + if (manifest.parameters != null) { + for (String arg : manifest.parameters.split("\\s")) { + if (arg != null) { + if (rawArgs.contains(arg)) + continue; + + if (arg.startsWith("--") && arg.contains("=")) { + String argname = arg.substring(0, arg.indexOf("=")); + if (rawArgs.stream().filter(a -> a.startsWith(argname)).findAny().isPresent()) + continue; + } + + rawArgs.add(arg); + } + } + } + + // Compute named and unnamed parameters + computeNamedParams(); + computeUnnamedParams(); + } + + public List getRaw() { + return rawArgs; + } + + public List getUnnamed() { + return unnamedParams; + } + + public Map getNamed() { + return namedParams; + } + + /** + * Returns true if the specified string is a named parameter of the + * form: --name=value + * + * @param arg the string to check + * @return true if the string matches the pattern for a named parameter. + */ + private boolean isNamedParam(String arg) { + return arg.startsWith("--") && (arg.indexOf('=') > 2 && validFirstChar(arg.charAt(2))); + } + + /** + * This method parses the current array of raw arguments looking for + * name,value pairs. These name,value pairs are then added to the map + * for this parameters object, and are of the form: --name=value. + */ + private void computeNamedParams() { + rawArgs.stream().filter(this::isNamedParam).forEach(arg -> { + final int eqIdx = arg.indexOf('='); + String key = arg.substring(2, eqIdx); + String value = arg.substring(eqIdx + 1); + namedParams.put(key, value); + }); + } + /** + * This method computes the list of unnamed parameters, by filtering the + * list of raw arguments, stripping out the named parameters. + */ + private void computeUnnamedParams() { + unnamedParams.addAll(rawArgs.stream().filter(arg -> !isNamedParam(arg)).collect(Collectors.toList())); + } + + /** + * Validate the first character of a key. It is valid if it is a letter or + * an "_" character. + * + * @param c the first char of a key string + * @return whether or not it is valid + */ + private boolean validFirstChar(char c) { + return Character.isLetter(c) || c == '_'; + } +} From 825ad8c31ce6c75d8232b045a6dd714ed878704a Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Mon, 14 Mar 2016 09:50:38 +0100 Subject: [PATCH 04/38] Release 1.0.9 with support for parameter configurable manifest location (https://github.com/edvin/fxlauncher/issues/3) --- CHANGELOG.md | 6 ++++++ README.md | 12 ++++++++++-- pom.xml | 2 +- src/main/java/fxlauncher/Launcher.java | 11 +++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a215518..ce1f36b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +## [1.0.9] - Unreleased + +### Added + +- App manifest location can be given as command line parameter (https://github.com/edvin/fxlauncher/issues/3) + ## [1.0.8] - 2016-03-02 ### Added diff --git a/README.md b/README.md index db78c87..5466837 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ See the launcher in action in this short [screencast](https://www.youtube.com/wa ## How does it work? -FXLauncher is a 14Kb jar that can be used to boot your application. It knows the location +FXLauncher is a 18Kb jar that can be used to boot your application. It knows the location of your application repository where you host all the app resources. See a manifest [example here](http://fxldemo.tornado.no/app.xml). FXLauncher will look up the @@ -35,7 +35,15 @@ Before each run, the launcher will synchronize all resources and seamlessly laun ## How to use FXLauncher See the QuickStart projects at the top of the README for information on integrating FXLauncher in your build system. + +## Adhoc usage + +FXLauncher can also be used to launch an application at an arbitrary url by specifying the `--app` parameter at startup: +```bash +java -jar fxlauncher.jar --app=http://remote/location/app.xml +``` + #### Native installers The native installer does not contain any application code, only the launcher. There is @@ -64,6 +72,6 @@ then made available to the `FXMLLoader`. You can access it via `FXMLLoader.getDe ### Platform specific resources -From version 1.0.7, FXLauncher supports filtering of resources for the running platform. Any resource +FXLauncher supports filtering of resources for the running platform. Any resource that ends with `-[mac|win|linux].jar` will only be downloaded and put on the classpath on the corresponding platform. The manifest enforces this though the `os` attribute in `app.xml`. \ No newline at end of file diff --git a/pom.xml b/pom.xml index bb07b10..86ed666 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.8 + 1.0.9 jar FX Launcher Auto updating launcher for JavaFX Applications diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index 07eb049..5021bf0 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -17,12 +17,14 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -191,6 +193,15 @@ private void reportError(String title, Throwable error) { } private void syncManifest() throws Exception { + Map namedParams = getParameters().getNamed(); + + if (namedParams.containsKey("app")) { + String manifestURL = namedParams.get("app"); + log.info(String.format("Loading manifest from parameter supplied location %s", manifestURL)); + manifest = JAXB.unmarshal(URI.create(manifestURL), FXManifest.class); + return; + } + URL embeddedManifest = Launcher.class.getResource("/app.xml"); manifest = JAXB.unmarshal(embeddedManifest, FXManifest.class); From 7b64af0720b7ad09ed14a32edc15bd4ede2c172a Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Mon, 14 Mar 2016 09:53:44 +0100 Subject: [PATCH 05/38] Updated readme for release 1.0.9 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce1f36b..e2dc590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. -## [1.0.9] - Unreleased +## [1.0.9] - 2016-03-14 ### Added From 4ce744051460a92107579510fadf17a7ee453e9e Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Thu, 17 Mar 2016 08:48:41 +0100 Subject: [PATCH 06/38] Added license badge to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5466837..298729b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # FXLauncher [![Maven Central](https://maven-badges.herokuapp.com/maven-central/no.tornado/fxlauncher/badge.svg)](https://search.maven.org/#search|ga|1|no.tornado.fxlauncher) +[![Apache License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) Auto updating launcher for JavaFX Applications. Combined with JavaFX native packaging, you get a native installer with automatic app updates. From 83c27a50168dbeb49e3a38411747719425152dd6 Mon Sep 17 00:00:00 2001 From: edvin Date: Mon, 21 Mar 2016 13:25:13 +0100 Subject: [PATCH 07/38] Add / if missing from base URL (https://github.com/edvin/fxlauncher/issues/6) --- CHANGELOG.md | 6 ++++++ pom.xml | 2 +- src/main/java/fxlauncher/FXManifest.java | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2dc590..65e24b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +## [Unreleased] + +### Changed + +- Add / if missing from base url (https://github.com/edvin/fxlauncher/issues/6) + ## [1.0.9] - 2016-03-14 ### Added diff --git a/pom.xml b/pom.xml index 86ed666..700fc98 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.9 + 1.0.10 jar FX Launcher Auto updating launcher for JavaFX Applications diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index 0bc4545..9faf940 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -34,7 +34,10 @@ public String getFilename() { } public URI getFXAppURI() { - return uri.resolve("app.xml"); + if (uri.getPath().endsWith("/")) + return uri.resolve("app.xml"); + + return URI.create(uri.toString() + "/app.xml"); } public Path getPath() { From 9c300204742604b8c2658c46bd874f45027c92fd Mon Sep 17 00:00:00 2001 From: edvin Date: Fri, 6 May 2016 13:15:23 +0200 Subject: [PATCH 08/38] Started working on https://github.com/edvin/fxlauncher/issues/9 --- CHANGELOG.md | 3 +- src/main/java/fxlauncher/FXManifest.java | 140 +++++++++++++++------- src/main/java/fxlauncher/Launcher.java | 27 +++-- src/main/java/fxlauncher/LibraryFile.java | 10 +- 4 files changed, 117 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65e24b5..89e251c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ # Change Log All notable changes to this project will be documented in this file. -## [Unreleased] +## [1.0.10] ### Changed +- Add optional `--cache-dir` program parameter `cacheDir` and manifest entry (https://github.com/edvin/fxlauncher/issues/9) - Add / if missing from base url (https://github.com/edvin/fxlauncher/issues/6) ## [1.0.9] - 2016-03-14 diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index 9faf940..1e03b8f 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -3,21 +3,25 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import java.io.IOException; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; @SuppressWarnings("unchecked") @XmlRootElement(name = "Application") public class FXManifest { - @XmlAttribute - URI uri; - @XmlAttribute(name = "launch") - String launchClass; - @XmlElement(name = "lib") - List files = new ArrayList<>(); + @XmlAttribute + URI uri; + @XmlAttribute(name = "launch") + String launchClass; + @XmlElement(name = "lib") + List files = new ArrayList<>(); @XmlElement String updateText = "Updating..."; @XmlElement @@ -26,50 +30,94 @@ public class FXManifest { String progressBarStyle = "-fx-pref-width: 200;"; @XmlElement String wrapperStyle = "-fx-spacing: 10; -fx-padding: 25;"; - @XmlElement - String parameters; + @XmlElement + String parameters; + @XmlElement + String cacheDir; + + public String getFilename() { + return String.format("%s.xml", launchClass); + } + + public URI getFXAppURI() { + if (uri.getPath().endsWith("/")) + return uri.resolve("app.xml"); + + return URI.create(uri.toString() + "/app.xml"); + } + + public Path getPath(Path cacheDir) { + return cacheDir.resolve(getFilename()); + } + + public Path resolveCacheDir(Map namedParams) { + if (namedParams == null) namedParams = Collections.emptyMap(); - public String getFilename() { - return String.format("%s.xml", launchClass); - } + String cacheDir = namedParams.containsKey("cache-dir") ? namedParams.get("cache-dir") : this.cacheDir; - public URI getFXAppURI() { - if (uri.getPath().endsWith("/")) - return uri.resolve("app.xml"); + if (cacheDir == null || cacheDir.isEmpty()) return Paths.get("."); - return URI.create(uri.toString() + "/app.xml"); - } + Path path; + + if (cacheDir.startsWith("USERLIB/")) { + switch (OS.current) { + case mac: + path = Paths.get(System.getProperty("user.home")) + .resolve("Library") + .resolve("Application Support") + .resolve(cacheDir.substring(8)); + break; + case win: + path = Paths.get(System.getProperty("user.home")) + .resolve("AppData") + .resolve(cacheDir.substring(8)); + break; + default: + path = Paths.get(System.getProperty("user.home")) + .resolve("." + cacheDir.substring(8)); + } + } else { + path = Paths.get(cacheDir); + } + + if (!Files.exists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return Paths.get(cacheDir); + } + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FXManifest that = (FXManifest) o; + + if (uri != null ? !uri.equals(that.uri) : that.uri != null) return false; + if (launchClass != null ? !launchClass.equals(that.launchClass) : that.launchClass != null) return false; + if (files != null ? !files.equals(that.files) : that.files != null) return false; + if (updateText != null ? !updateText.equals(that.updateText) : that.updateText != null) return false; + if (updateLabelStyle != null ? !updateLabelStyle.equals(that.updateLabelStyle) : that.updateLabelStyle != null) + return false; + if (progressBarStyle != null ? !progressBarStyle.equals(that.progressBarStyle) : that.progressBarStyle != null) + return false; + return wrapperStyle != null ? wrapperStyle.equals(that.wrapperStyle) : that.wrapperStyle == null; + + } - public Path getPath() { - return Paths.get(getFilename()); + public int hashCode() { + int result = uri != null ? uri.hashCode() : 0; + result = 31 * result + (launchClass != null ? launchClass.hashCode() : 0); + result = 31 * result + (files != null ? files.hashCode() : 0); + result = 31 * result + (updateText != null ? updateText.hashCode() : 0); + result = 31 * result + (updateLabelStyle != null ? updateLabelStyle.hashCode() : 0); + result = 31 * result + (progressBarStyle != null ? progressBarStyle.hashCode() : 0); + result = 31 * result + (wrapperStyle != null ? wrapperStyle.hashCode() : 0); + return result; } - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FXManifest that = (FXManifest) o; - - if (uri != null ? !uri.equals(that.uri) : that.uri != null) return false; - if (launchClass != null ? !launchClass.equals(that.launchClass) : that.launchClass != null) return false; - if (files != null ? !files.equals(that.files) : that.files != null) return false; - if (updateText != null ? !updateText.equals(that.updateText) : that.updateText != null) return false; - if (updateLabelStyle != null ? !updateLabelStyle.equals(that.updateLabelStyle) : that.updateLabelStyle != null) - return false; - if (progressBarStyle != null ? !progressBarStyle.equals(that.progressBarStyle) : that.progressBarStyle != null) - return false; - return wrapperStyle != null ? wrapperStyle.equals(that.wrapperStyle) : that.wrapperStyle == null; - - } - - public int hashCode() { - int result = uri != null ? uri.hashCode() : 0; - result = 31 * result + (launchClass != null ? launchClass.hashCode() : 0); - result = 31 * result + (files != null ? files.hashCode() : 0); - result = 31 * result + (updateText != null ? updateText.hashCode() : 0); - result = 31 * result + (updateLabelStyle != null ? updateLabelStyle.hashCode() : 0); - result = 31 * result + (progressBarStyle != null ? progressBarStyle.hashCode() : 0); - result = 31 * result + (wrapperStyle != null ? wrapperStyle.hashCode() : 0); - return result; - } } \ No newline at end of file diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index 5021bf0..317fd25 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -22,7 +22,6 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.logging.Level; @@ -58,7 +57,8 @@ public void start(Stage primaryStage) throws Exception { try { updateManifest(); createUpdateWrapper(); - syncFiles(); + Path cacheDir = manifest.resolveCacheDir(getParameters().getNamed()); + syncFiles(cacheDir); } catch (Exception ex) { log.log(Level.WARNING, String.format("Error during %s phase", phase), ex); } @@ -95,10 +95,10 @@ private void createUpdateWrapper() { }); } - public URLClassLoader createClassLoader() { + private URLClassLoader createClassLoader(Path cacheDir) { List libs = manifest.files.stream() .filter(LibraryFile::loadForCurrentPlatform) - .map(LibraryFile::toURL) + .map(it -> it.toURL(cacheDir)) .collect(Collectors.toList()); return new URLClassLoader(libs.toArray(new URL[libs.size()])); @@ -124,19 +124,19 @@ private void updateManifest() throws Exception { syncManifest(); } - private void syncFiles() throws Exception { + private void syncFiles(Path cacheDir) throws Exception { phase = "File Synchronization"; List needsUpdate = manifest.files.stream() .filter(LibraryFile::loadForCurrentPlatform) - .filter(LibraryFile::needsUpdate) + .filter(it -> it.needsUpdate(cacheDir)) .collect(Collectors.toList()); Long totalBytes = needsUpdate.stream().mapToLong(f -> f.size).sum(); Long totalWritten = 0L; for (LibraryFile lib : needsUpdate) { - Path target = Paths.get(lib.file).toAbsolutePath(); + Path target = cacheDir.resolve(lib.file).toAbsolutePath(); Files.createDirectories(target.getParent()); try (InputStream input = manifest.uri.resolve(lib.file).toURL().openStream(); @@ -158,7 +158,9 @@ private void syncFiles() throws Exception { private void createApplication() throws Exception { phase = "Create Application"; - URLClassLoader classLoader = createClassLoader(); + Path cacheDir = manifest.resolveCacheDir(getParameters().getNamed()); + + URLClassLoader classLoader = createClassLoader(cacheDir); FXMLLoader.setDefaultClassLoader(classLoader); Thread.currentThread().setContextClassLoader(classLoader); Platform.runLater(() -> Thread.currentThread().setContextClassLoader(classLoader)); @@ -205,8 +207,11 @@ private void syncManifest() throws Exception { URL embeddedManifest = Launcher.class.getResource("/app.xml"); manifest = JAXB.unmarshal(embeddedManifest, FXManifest.class); - if (Files.exists(manifest.getPath())) - manifest = JAXB.unmarshal(manifest.getPath().toFile(), FXManifest.class); + Path cacheDir = manifest.resolveCacheDir(namedParams); + Path manifestPath = manifest.getPath(cacheDir); + + if (Files.exists(manifestPath)) + manifest = JAXB.unmarshal(manifestPath.toFile(), FXManifest.class); try { FXManifest remoteManifest = JAXB.unmarshal(manifest.getFXAppURI(), FXManifest.class); @@ -215,7 +220,7 @@ private void syncManifest() throws Exception { log.info(String.format("No remote manifest at %s", manifest.getFXAppURI())); } else if (!remoteManifest.equals(manifest)) { manifest = remoteManifest; - JAXB.marshal(manifest, manifest.getPath().toFile()); + JAXB.marshal(manifest, manifestPath.toFile()); } } catch (Exception ex) { log.log(Level.WARNING, "Unable to update manifest", ex); diff --git a/src/main/java/fxlauncher/LibraryFile.java b/src/main/java/fxlauncher/LibraryFile.java index 93d18f5..d2b7347 100644 --- a/src/main/java/fxlauncher/LibraryFile.java +++ b/src/main/java/fxlauncher/LibraryFile.java @@ -22,8 +22,8 @@ public class LibraryFile { @XmlAttribute OS os; - public boolean needsUpdate() { - Path path = Paths.get(file); + public boolean needsUpdate(Path cacheDir) { + Path path = cacheDir.resolve(file); try { return !Files.exists(path) || Files.size(path) != size || checksum(path) != checksum; } catch (IOException e) { @@ -50,15 +50,15 @@ public boolean loadForCurrentPlatform() { return os == null || os == OS.current; } - public URL toURL() { + public URL toURL(Path cacheDir) { try { - return Paths.get(file).toFile().toURI().toURL(); + return cacheDir.resolve(file).toFile().toURI().toURL(); } catch (MalformedURLException whaat) { throw new RuntimeException(whaat); } } - public static long checksum(Path path) throws IOException { + private static long checksum(Path path) throws IOException { try (InputStream input = Files.newInputStream(path)) { Adler32 checksum = new Adler32(); byte[] buf = new byte[16384]; From 9234bc6be4e5da43f9d2a9ff9d517be473361333 Mon Sep 17 00:00:00 2001 From: edvin Date: Fri, 6 May 2016 14:56:07 +0200 Subject: [PATCH 09/38] CreateManifest supports the --cache-dir parameter --- src/main/java/fxlauncher/CreateManifest.java | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index 9731fb6..b416e90 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -1,10 +1,15 @@ package fxlauncher; +import com.sun.javafx.application.ParametersImpl; + import javax.xml.bind.JAXB; import java.io.IOException; import java.net.URI; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class CreateManifest { @@ -14,8 +19,21 @@ public static void main(String[] args) throws IOException { Path appPath = Paths.get(args[2]); FXManifest manifest = create(baseURI, launchClass, appPath); - if (args.length == 4) - manifest.parameters = args[3]; + if (args.length > 3) { + // Parse named parameters + List rawParams = new ArrayList<>(); + for (int i = 3; i < args.length; i++) + rawParams.add(args[i]); + ParametersImpl params = new ParametersImpl(rawParams); + Map named = params.getNamed(); + + // Configure cacheDir + if (named != null && named.containsKey("cache-dir")) + manifest.cacheDir = named.get("cache-dir"); + + // Add the raw parameter string to the manifest + manifest.parameters = args[3]; + } JAXB.marshal(manifest, appPath.resolve("app.xml").toFile()); } From 66bb5e07332b395164dc75f41934ce80ec8198cc Mon Sep 17 00:00:00 2001 From: edvin Date: Sat, 7 May 2016 15:06:17 +0200 Subject: [PATCH 10/38] CreateManifest supports the --cache-dir parameter --- src/main/java/fxlauncher/CreateManifest.java | 15 +++++++++++--- src/main/java/fxlauncher/FXManifest.java | 21 ++++++++++++-------- src/main/java/fxlauncher/Launcher.java | 1 + src/main/java/fxlauncher/LauncherParams.java | 2 +- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index b416e90..587afe9 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -8,6 +8,7 @@ import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -22,8 +23,7 @@ public static void main(String[] args) throws IOException { if (args.length > 3) { // Parse named parameters List rawParams = new ArrayList<>(); - for (int i = 3; i < args.length; i++) - rawParams.add(args[i]); + rawParams.addAll(Arrays.asList(args).subList(3, args.length)); ParametersImpl params = new ParametersImpl(rawParams); Map named = params.getNamed(); @@ -31,8 +31,17 @@ public static void main(String[] args) throws IOException { if (named != null && named.containsKey("cache-dir")) manifest.cacheDir = named.get("cache-dir"); + // Append the rest as manifest parameters + StringBuilder rest = new StringBuilder(); + for (String raw : params.getRaw()) { + if (raw.startsWith("--cache-dir=")) continue; + if (rest.length() > 0) rest.append(" "); + rest.append(raw); + } + // Add the raw parameter string to the manifest - manifest.parameters = args[3]; + if (rest.length() > 0) + manifest.parameters = rest.toString(); } JAXB.marshal(manifest, appPath.resolve("app.xml").toFile()); diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index 1e03b8f..18f5d1e 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -59,23 +59,28 @@ public Path resolveCacheDir(Map namedParams) { Path path; - if (cacheDir.startsWith("USERLIB/")) { + if (cacheDir.contains("USERLIB")) { + String replacement; switch (OS.current) { case mac: - path = Paths.get(System.getProperty("user.home")) + replacement = Paths.get(System.getProperty("user.home")) .resolve("Library") .resolve("Application Support") - .resolve(cacheDir.substring(8)); + .resolve(cacheDir.substring(8)) + .toString(); break; case win: - path = Paths.get(System.getProperty("user.home")) + replacement = Paths.get(System.getProperty("user.home")) .resolve("AppData") - .resolve(cacheDir.substring(8)); + .resolve(cacheDir.substring(8)) + .toString(); break; default: - path = Paths.get(System.getProperty("user.home")) - .resolve("." + cacheDir.substring(8)); + replacement = Paths.get(System.getProperty("user.home")) + .resolve("." + cacheDir.substring(8)) + .toString(); } + path = Paths.get(replacement); } else { path = Paths.get(cacheDir); } @@ -88,7 +93,7 @@ public Path resolveCacheDir(Map namedParams) { } } - return Paths.get(cacheDir); + return path; } public boolean equals(Object o) { diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index 317fd25..c929a82 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -58,6 +58,7 @@ public void start(Stage primaryStage) throws Exception { updateManifest(); createUpdateWrapper(); Path cacheDir = manifest.resolveCacheDir(getParameters().getNamed()); + log.info(String.format("Using cache dir %s", cacheDir)); syncFiles(cacheDir); } catch (Exception ex) { log.log(Level.WARNING, String.format("Error during %s phase", phase), ex); diff --git a/src/main/java/fxlauncher/LauncherParams.java b/src/main/java/fxlauncher/LauncherParams.java index d9678d8..bb65bd5 100644 --- a/src/main/java/fxlauncher/LauncherParams.java +++ b/src/main/java/fxlauncher/LauncherParams.java @@ -36,7 +36,7 @@ public LauncherParams(Application.Parameters delegate, FXManifest manifest) { if (arg.startsWith("--") && arg.contains("=")) { String argname = arg.substring(0, arg.indexOf("=")); if (rawArgs.stream().filter(a -> a.startsWith(argname)).findAny().isPresent()) - continue; + continue; } rawArgs.add(arg); From 6d0c916a9733ecb2ea5ed94fa78e8753f070159c Mon Sep 17 00:00:00 2001 From: edvin Date: Sat, 7 May 2016 17:33:23 +0200 Subject: [PATCH 11/38] Updated README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 298729b..d0d369d 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,12 @@ Check out these prebuilt installers for a more complex demo application - [Windows](http://fxsamples.tornado.no/CRMApplication-1.0.exe) - [Linux](http://fxsamples.tornado.no/crmapplication-1.0.deb) +## Specify cache directory + +By default, the artifacts are downloaded to the current working directory. This is usually fine for native installers, but if you distribute +your application via just the launcher jar, you might want to specify where the downloaded artifacts land. See the +[cache dir documentation](https://github.com/edvin/fxlauncher/wiki/Optional-Cache-Directory)for more information. + ## A slimmer alternative It is also possible to embed the launchar jar in a native installer system like Advanced Installer - same approach as above, From fb079c1e223fdc1adc9f910224be21792135113b Mon Sep 17 00:00:00 2001 From: edvin Date: Fri, 13 May 2016 21:47:34 +0200 Subject: [PATCH 12/38] Accept .war as LibraryFile --- src/main/java/fxlauncher/CreateManifest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index 587afe9..56c58b9 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -54,7 +54,7 @@ public static FXManifest create(URI baseURI, String launchClass, Path appPath) t Files.walkFileTree(appPath, new SimpleFileVisitor() { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (!Files.isDirectory(file) && file.toString().endsWith(".jar") && !file.getFileName().toString().startsWith("fxlauncher")) + if (!Files.isDirectory(file) && isJavaLibrary(file) && !file.getFileName().toString().startsWith("fxlauncher")) manifest.files.add(new LibraryFile(appPath, file)); return FileVisitResult.CONTINUE; } @@ -62,5 +62,10 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO return manifest; } + + private static boolean isJavaLibrary(Path file) { + String filename = file.getFileName().toString(); + return filename.endsWith(".jar") || filename.endsWith(".war"); + } } From f2f2919269928e0f3f4a7eb6c1021b7d7ca84786 Mon Sep 17 00:00:00 2001 From: edvin Date: Sun, 19 Jun 2016 14:40:37 +0200 Subject: [PATCH 13/38] Progress window is now closed after primaryStage is shown instead of right before app.start() is called (https://github.com/edvin/fxlauncher/issues/11) --- CHANGELOG.md | 8 +++++++- pom.xml | 2 +- src/main/java/fxlauncher/Launcher.java | 20 ++++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e251c..6564344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ # Change Log All notable changes to this project will be documented in this file. -## [1.0.10] +## [1.0.11] + +### Changed + +- Progress window is now closed after primaryStage is shown instead of right before app.start() is called + +## [1.0.10] - 2016-05-07 ### Changed diff --git a/pom.xml b/pom.xml index 700fc98..773a388 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.10 + 1.0.11 jar FX Launcher Auto updating launcher for JavaFX Applications diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index c929a82..b0c081e 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -1,6 +1,7 @@ package fxlauncher; import com.sun.javafx.application.ParametersImpl; +import com.sun.javafx.application.PlatformImpl; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; @@ -54,6 +55,7 @@ public void start(Stage primaryStage) throws Exception { stage.show(); new Thread(() -> { + Thread.currentThread().setName("FXLauncher-Thread"); try { updateManifest(); createUpdateWrapper(); @@ -109,10 +111,11 @@ private void launchAppFromManifest() throws Exception { phase = "Application Init"; app.init(); phase = "Application Start"; - Platform.runLater(() -> { + PlatformImpl.runAndWait(() -> { try { - stage.close(); - ParametersImpl.registerParameters(app, new LauncherParams(getParameters(), manifest)); + primaryStage.showingProperty().addListener(observable -> { + if (stage.isShowing()) stage.close(); + }); app.start(primaryStage); } catch (Exception ex) { reportError("Failed to start application", ex); @@ -166,7 +169,16 @@ private void createApplication() throws Exception { Thread.currentThread().setContextClassLoader(classLoader); Platform.runLater(() -> Thread.currentThread().setContextClassLoader(classLoader)); Class appclass = (Class) classLoader.loadClass(manifest.launchClass); - app = appclass.newInstance(); + + PlatformImpl.runAndWait(() -> { + try { + app = appclass.newInstance(); + ParametersImpl.registerParameters(app, new LauncherParams(getParameters(), manifest)); + PlatformImpl.setApplicationName(appclass); + } catch (Throwable t) { + reportError("Error creating app class", t); + } + }); } public void stop() throws Exception { From cd65015991525ef1fa7ef61fefb10bd8592e1ebd Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Tue, 9 Aug 2016 09:51:52 +0200 Subject: [PATCH 14/38] Artifacts in subfolders gets correct path delimiter in app manifest for Windows --- CHANGELOG.md | 6 ++++++ pom.xml | 2 +- src/main/java/fxlauncher/LibraryFile.java | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6564344..71ac74d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +## [1.0.12-SNAPSHOT] + +### Changed + +- Artifacts in subfolders gets correct path delimiter in app manifest for Windows + ## [1.0.11] ### Changed diff --git a/pom.xml b/pom.xml index 773a388..fff8c1e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.11 + 1.0.12-SNAPSHOT jar FX Launcher Auto updating launcher for JavaFX Applications diff --git a/src/main/java/fxlauncher/LibraryFile.java b/src/main/java/fxlauncher/LibraryFile.java index d2b7347..86ffc03 100644 --- a/src/main/java/fxlauncher/LibraryFile.java +++ b/src/main/java/fxlauncher/LibraryFile.java @@ -35,7 +35,7 @@ public LibraryFile() { } public LibraryFile(Path basepath, Path file) throws IOException { - this.file = basepath.relativize(file).toString(); + this.file = basepath.relativize(file).toString().replace("\\", "/"); this.size = Files.size(file); this.checksum = checksum(file); From 09c23747d4c617b36aba7dcf59742e78504a8aba Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Tue, 16 Aug 2016 12:55:41 +0200 Subject: [PATCH 15/38] Added manifest.acceptDowngrade and manifest.ts options to support https://github.com/edvin/fxlauncher/issues/20 --- src/main/java/fxlauncher/CreateManifest.java | 2 ++ src/main/java/fxlauncher/FXManifest.java | 28 +++++++++++++++----- src/main/java/fxlauncher/Launcher.java | 7 +++-- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index 56c58b9..6833df0 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -49,6 +49,8 @@ public static void main(String[] args) throws IOException { public static FXManifest create(URI baseURI, String launchClass, Path appPath) throws IOException { FXManifest manifest = new FXManifest(); + manifest.ts = System.currentTimeMillis(); + manifest.acceptDowngrade = true; manifest.uri = baseURI; manifest.launchClass = launchClass; diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index 18f5d1e..e362d69 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -16,6 +16,8 @@ @SuppressWarnings("unchecked") @XmlRootElement(name = "Application") public class FXManifest { + @XmlAttribute + Long ts; @XmlAttribute URI uri; @XmlAttribute(name = "launch") @@ -34,6 +36,8 @@ public class FXManifest { String parameters; @XmlElement String cacheDir; + @XmlElement + Boolean acceptDowngrade = false; public String getFilename() { return String.format("%s.xml", launchClass); @@ -96,33 +100,45 @@ public Path resolveCacheDir(Map namedParams) { return path; } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FXManifest that = (FXManifest) o; + if (ts != null ? !ts.equals(that.ts) : that.ts != null) return false; if (uri != null ? !uri.equals(that.uri) : that.uri != null) return false; if (launchClass != null ? !launchClass.equals(that.launchClass) : that.launchClass != null) return false; if (files != null ? !files.equals(that.files) : that.files != null) return false; if (updateText != null ? !updateText.equals(that.updateText) : that.updateText != null) return false; - if (updateLabelStyle != null ? !updateLabelStyle.equals(that.updateLabelStyle) : that.updateLabelStyle != null) - return false; - if (progressBarStyle != null ? !progressBarStyle.equals(that.progressBarStyle) : that.progressBarStyle != null) - return false; - return wrapperStyle != null ? wrapperStyle.equals(that.wrapperStyle) : that.wrapperStyle == null; + if (updateLabelStyle != null ? !updateLabelStyle.equals(that.updateLabelStyle) : that.updateLabelStyle != null) return false; + if (progressBarStyle != null ? !progressBarStyle.equals(that.progressBarStyle) : that.progressBarStyle != null) return false; + if (wrapperStyle != null ? !wrapperStyle.equals(that.wrapperStyle) : that.wrapperStyle != null) return false; + if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null) return false; + if (cacheDir != null ? !cacheDir.equals(that.cacheDir) : that.cacheDir != null) return false; + return acceptDowngrade != null ? acceptDowngrade.equals(that.acceptDowngrade) : that.acceptDowngrade == null; } + @Override public int hashCode() { - int result = uri != null ? uri.hashCode() : 0; + int result = ts != null ? ts.hashCode() : 0; + result = 31 * result + (uri != null ? uri.hashCode() : 0); result = 31 * result + (launchClass != null ? launchClass.hashCode() : 0); result = 31 * result + (files != null ? files.hashCode() : 0); result = 31 * result + (updateText != null ? updateText.hashCode() : 0); result = 31 * result + (updateLabelStyle != null ? updateLabelStyle.hashCode() : 0); result = 31 * result + (progressBarStyle != null ? progressBarStyle.hashCode() : 0); result = 31 * result + (wrapperStyle != null ? wrapperStyle.hashCode() : 0); + result = 31 * result + (parameters != null ? parameters.hashCode() : 0); + result = 31 * result + (cacheDir != null ? cacheDir.hashCode() : 0); + result = 31 * result + (acceptDowngrade != null ? acceptDowngrade.hashCode() : 0); return result; } + public boolean isNewerThan(FXManifest other) { + if (ts == null || other.ts == null) return false; + return ts > other.ts; + } } \ No newline at end of file diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index b0c081e..0b181c7 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -232,8 +232,11 @@ private void syncManifest() throws Exception { if (remoteManifest == null) { log.info(String.format("No remote manifest at %s", manifest.getFXAppURI())); } else if (!remoteManifest.equals(manifest)) { - manifest = remoteManifest; - JAXB.marshal(manifest, manifestPath.toFile()); + // Update to remote manifest if newer or we specifially accept downgrades + if (remoteManifest.isNewerThan(manifest) || manifest.acceptDowngrade) { + manifest = remoteManifest; + JAXB.marshal(manifest, manifestPath.toFile()); + } } } catch (Exception ex) { log.log(Level.WARNING, "Unable to update manifest", ex); From 5867dda0807c3272b251071afa72637a9366e1db Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Tue, 16 Aug 2016 13:25:41 +0200 Subject: [PATCH 16/38] Moved sign-artifacts to deploy to make it easier to check snapshots for others. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fff8c1e..3341d30 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ sign-artifacts - verify + deploy sign From 34ac52d2edc6b9ad28a45daf899ea246fec663df Mon Sep 17 00:00:00 2001 From: edvin Date: Tue, 16 Aug 2016 16:26:14 +0200 Subject: [PATCH 17/38] Removed acceptDowngrade = true override --- src/main/java/fxlauncher/CreateManifest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index 6833df0..54b6f27 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -50,7 +50,6 @@ public static void main(String[] args) throws IOException { public static FXManifest create(URI baseURI, String launchClass, Path appPath) throws IOException { FXManifest manifest = new FXManifest(); manifest.ts = System.currentTimeMillis(); - manifest.acceptDowngrade = true; manifest.uri = baseURI; manifest.launchClass = launchClass; From a266fefffcf09b6216b7f256e8e14a1b3923e8d3 Mon Sep 17 00:00:00 2001 From: edvin Date: Wed, 17 Aug 2016 08:40:46 +0200 Subject: [PATCH 18/38] Added parameter --accept-downgrade=true to manifest generation --- src/main/java/fxlauncher/CreateManifest.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index 54b6f27..f1d23c4 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -27,9 +27,15 @@ public static void main(String[] args) throws IOException { ParametersImpl params = new ParametersImpl(rawParams); Map named = params.getNamed(); - // Configure cacheDir - if (named != null && named.containsKey("cache-dir")) - manifest.cacheDir = named.get("cache-dir"); + if (named != null) { + // Configure cacheDir + if (named.containsKey("cache-dir")) + manifest.cacheDir = named.get("cache-dir"); + + // Configure acceptDowngrade + if (named.containsKey("accept-downgrade")) + manifest.acceptDowngrade = Boolean.valueOf(named.get("accept-downgrade")); + } // Append the rest as manifest parameters StringBuilder rest = new StringBuilder(); From 26d1ee6ef59ee7e6dfd02740734cd3dc60b72a96 Mon Sep 17 00:00:00 2001 From: edvin Date: Wed, 17 Aug 2016 08:41:10 +0200 Subject: [PATCH 19/38] Added parameter --accept-downgrade=true to manifest generation --- src/main/java/fxlauncher/CreateManifest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index f1d23c4..2897c24 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -41,6 +41,7 @@ public static void main(String[] args) throws IOException { StringBuilder rest = new StringBuilder(); for (String raw : params.getRaw()) { if (raw.startsWith("--cache-dir=")) continue; + if (raw.startsWith("--accept-downgrade=")) continue; if (rest.length() > 0) rest.append(" "); rest.append(raw); } From ab41d045a12f4a493e2a4a6b82332f5e691560a4 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Wed, 17 Aug 2016 22:35:25 +0200 Subject: [PATCH 20/38] FXLauncher 1.0.12 --- CHANGELOG.md | 3 ++- README.md | 7 +++++++ pom.xml | 4 ++-- src/main/java/fxlauncher/Launcher.java | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ac74d..1012a20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ # Change Log All notable changes to this project will be documented in this file. -## [1.0.12-SNAPSHOT] +## [1.0.12] ### Changed +- Added --accept-downgrade= parameter to CreateManifest. Default is to not accept downgrades (server version is older than local version) - Artifacts in subfolders gets correct path delimiter in app manifest for Windows ## [1.0.11] diff --git a/README.md b/README.md index d0d369d..d74dab5 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,13 @@ By default, the artifacts are downloaded to the current working directory. This your application via just the launcher jar, you might want to specify where the downloaded artifacts land. See the [cache dir documentation](https://github.com/edvin/fxlauncher/wiki/Optional-Cache-Directory)for more information. +## Accept downgrades + +Starting from version 1.0.12, FXLauncher will not download a remote version if the local version is newer. This is controlled +by comparing a timestamp in the manifest. Specifying `--accept-downgrades=true` as the last argument to CreateManifest will +allow you to make sure that the version you have published will always be used by your clients even if they have a newer version installed. +This option is also available in the Gradle plugin as `acceptDowngrades`. + ## A slimmer alternative It is also possible to embed the launchar jar in a native installer system like Advanced Installer - same approach as above, diff --git a/pom.xml b/pom.xml index fff8c1e..5baa7a5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.12-SNAPSHOT + 1.0.12 jar FX Launcher Auto updating launcher for JavaFX Applications @@ -119,7 +119,7 @@ sign-artifacts - verify + install sign diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index 0b181c7..a139047 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -162,7 +162,7 @@ private void syncFiles(Path cacheDir) throws Exception { private void createApplication() throws Exception { phase = "Create Application"; - Path cacheDir = manifest.resolveCacheDir(getParameters().getNamed()); + Path cacheDir = manifest.resolveCacheDir(getParameters() != null ? getParameters().getNamed() : null); URLClassLoader classLoader = createClassLoader(cacheDir); FXMLLoader.setDefaultClassLoader(classLoader); From 268e0138f3c66bdbc181901ca753352a0f71ecb1 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Fri, 19 Aug 2016 15:59:30 +0200 Subject: [PATCH 21/38] include-extensions --- CHANGELOG.md | 4 + pom.xml | 2 +- src/main/java/fxlauncher/CreateManifest.java | 86 ++++++++++++-------- 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1012a20..fc5be53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log All notable changes to this project will be documented in this file. +## [10.0.13-SNAPSHOT] + +## Added `--include-extensions` as a comma separated list of filename extensions to include of other resources from the build dir. By default it always includes `jar,war`. + ## [1.0.12] ### Changed diff --git a/pom.xml b/pom.xml index 5baa7a5..e70552b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.12 + 1.0.13-SNAPSHOT jar FX Launcher Auto updating launcher for JavaFX Applications diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index 2897c24..185c05a 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -11,8 +11,14 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class CreateManifest { + private static ArrayList includeExtensions = new ArrayList<>(); + + static { + includeExtensions.addAll(Arrays.asList("jar", "war")); + } public static void main(String[] args) throws IOException { URI baseURI = URI.create(args[0]); @@ -21,34 +27,43 @@ public static void main(String[] args) throws IOException { FXManifest manifest = create(baseURI, launchClass, appPath); if (args.length > 3) { - // Parse named parameters - List rawParams = new ArrayList<>(); - rawParams.addAll(Arrays.asList(args).subList(3, args.length)); - ParametersImpl params = new ParametersImpl(rawParams); - Map named = params.getNamed(); - - if (named != null) { - // Configure cacheDir - if (named.containsKey("cache-dir")) - manifest.cacheDir = named.get("cache-dir"); - - // Configure acceptDowngrade - if (named.containsKey("accept-downgrade")) - manifest.acceptDowngrade = Boolean.valueOf(named.get("accept-downgrade")); - } - - // Append the rest as manifest parameters - StringBuilder rest = new StringBuilder(); - for (String raw : params.getRaw()) { - if (raw.startsWith("--cache-dir=")) continue; - if (raw.startsWith("--accept-downgrade=")) continue; - if (rest.length() > 0) rest.append(" "); - rest.append(raw); - } - - // Add the raw parameter string to the manifest - if (rest.length() > 0) - manifest.parameters = rest.toString(); + // Parse named parameters + List rawParams = new ArrayList<>(); + rawParams.addAll(Arrays.asList(args).subList(3, args.length)); + ParametersImpl params = new ParametersImpl(rawParams); + Map named = params.getNamed(); + + if (named != null) { + // Configure cacheDir + if (named.containsKey("cache-dir")) + manifest.cacheDir = named.get("cache-dir"); + + // Configure acceptDowngrade + if (named.containsKey("accept-downgrade")) + manifest.acceptDowngrade = Boolean.valueOf(named.get("accept-downgrade")); + + // Add additional files with these extensions to manifest + if (named.containsKey("include-extensions")) + includeExtensions.addAll( + Arrays.stream(named.get("include-extensions").split(",")) + .filter(s -> s != null && !s.isEmpty()) + .collect(Collectors.toList()) + ); + } + + // Append the rest as manifest parameters + StringBuilder rest = new StringBuilder(); + for (String raw : params.getRaw()) { + if (raw.startsWith("--cache-dir=")) continue; + if (raw.startsWith("--accept-downgrade=")) continue; + if (raw.startsWith("--include-extensions=")) continue; + if (rest.length() > 0) rest.append(" "); + rest.append(raw); + } + + // Add the raw parameter string to the manifest + if (rest.length() > 0) + manifest.parameters = rest.toString(); } JAXB.marshal(manifest, appPath.resolve("app.xml").toFile()); @@ -62,7 +77,7 @@ public static FXManifest create(URI baseURI, String launchClass, Path appPath) t Files.walkFileTree(appPath, new SimpleFileVisitor() { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (!Files.isDirectory(file) && isJavaLibrary(file) && !file.getFileName().toString().startsWith("fxlauncher")) + if (!Files.isDirectory(file) && shouldIncludeInManifest(file) && !file.getFileName().toString().startsWith("fxlauncher")) manifest.files.add(new LibraryFile(appPath, file)); return FileVisitResult.CONTINUE; } @@ -71,9 +86,12 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO return manifest; } - private static boolean isJavaLibrary(Path file) { - String filename = file.getFileName().toString(); - return filename.endsWith(".jar") || filename.endsWith(".war"); - } - + private static boolean shouldIncludeInManifest(Path file) { + String filename = file.getFileName().toString(); + for (String ext : includeExtensions) { + if (filename.toLowerCase().endsWith(String.format(".%s", ext.toLowerCase()))) return true; + } + return false; + } + } From fde2553c2503c10ef9a93d38b2cea3636b3189c1 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Sun, 21 Aug 2016 22:25:13 +0200 Subject: [PATCH 22/38] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc5be53..1dcf43a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. ### Changed +- Added `include-extensions` parameter to CreateManaifest. By default only `jar` and `war` files are included, add more extensions via this comma separated list. - Added --accept-downgrade= parameter to CreateManifest. Default is to not accept downgrades (server version is older than local version) - Artifacts in subfolders gets correct path delimiter in app manifest for Windows From 8ad2a801c0b25aa91051320ec861dac6134039d4 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Wed, 24 Aug 2016 18:45:58 +0200 Subject: [PATCH 23/38] Added support for basic authentication via urlinfo in manifest url (user:pass@host/path) --- src/main/java/fxlauncher/FXManifest.java | 21 +++++++++++++++++---- src/main/java/fxlauncher/Launcher.java | 5 +++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index e362d69..d31fd9d 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -1,17 +1,18 @@ package fxlauncher; +import javax.xml.bind.JAXB; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; @SuppressWarnings("unchecked") @XmlRootElement(name = "Application") @@ -141,4 +142,16 @@ public boolean isNewerThan(FXManifest other) { if (ts == null || other.ts == null) return false; return ts > other.ts; } + + static FXManifest load(URI uri) throws IOException { + HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); + if (uri.getUserInfo() != null) { + byte[] payload = uri.getUserInfo().getBytes(StandardCharsets.UTF_8); + String encoded = Base64.getEncoder().encodeToString(payload); + connection.setRequestProperty("Authorization", String.format("Basic %s", encoded)); + } + try (InputStream input = connection.getInputStream()) { + return JAXB.unmarshal(input, FXManifest.class); + } + } } \ No newline at end of file diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index a139047..e1a506e 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -162,6 +162,7 @@ private void syncFiles(Path cacheDir) throws Exception { private void createApplication() throws Exception { phase = "Create Application"; + if (manifest == null) throw new IllegalArgumentException("Unable to retrieve embedded or remote manifest."); Path cacheDir = manifest.resolveCacheDir(getParameters() != null ? getParameters().getNamed() : null); URLClassLoader classLoader = createClassLoader(cacheDir); @@ -213,7 +214,7 @@ private void syncManifest() throws Exception { if (namedParams.containsKey("app")) { String manifestURL = namedParams.get("app"); log.info(String.format("Loading manifest from parameter supplied location %s", manifestURL)); - manifest = JAXB.unmarshal(URI.create(manifestURL), FXManifest.class); + manifest = FXManifest.load(URI.create(manifestURL)); return; } @@ -227,7 +228,7 @@ private void syncManifest() throws Exception { manifest = JAXB.unmarshal(manifestPath.toFile(), FXManifest.class); try { - FXManifest remoteManifest = JAXB.unmarshal(manifest.getFXAppURI(), FXManifest.class); + FXManifest remoteManifest = FXManifest.load(manifest.getFXAppURI()); if (remoteManifest == null) { log.info(String.format("No remote manifest at %s", manifest.getFXAppURI())); From 7c2409db136e959211392ad4364ef878b5cb30e9 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Wed, 24 Aug 2016 18:47:30 +0200 Subject: [PATCH 24/38] Updated CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dcf43a..07ae9fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ All notable changes to this project will be documented in this file. ## [10.0.13-SNAPSHOT] -## Added `--include-extensions` as a comma separated list of filename extensions to include of other resources from the build dir. By default it always includes `jar,war`. +- Basic Authentication support for manifest url (via https://user:pass@host/path) +- Added `--include-extensions` as a comma separated list of filename extensions to include of other resources from the build dir. By default it always includes `jar,war`. ## [1.0.12] From bac064bfe633df2a12785551d342e76aa98efce7 Mon Sep 17 00:00:00 2001 From: edvin Date: Fri, 9 Sep 2016 16:09:13 +0200 Subject: [PATCH 25/38] isNewerThan is true if old or new manifest is missing timestamp --- src/main/java/fxlauncher/FXManifest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index d31fd9d..15aaac3 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -139,8 +139,7 @@ public int hashCode() { } public boolean isNewerThan(FXManifest other) { - if (ts == null || other.ts == null) return false; - return ts > other.ts; + return ts == null || other.ts == null || ts > other.ts; } static FXManifest load(URI uri) throws IOException { From fa0c0678e0ede163c37879ead01df7ac20dcc284 Mon Sep 17 00:00:00 2001 From: edvin Date: Fri, 9 Sep 2016 16:21:59 +0200 Subject: [PATCH 26/38] Updated changelog --- CHANGELOG.md | 1 + src/main/java/fxlauncher/OS.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07ae9fd..22cd606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. ## [10.0.13-SNAPSHOT] +- Fixed bug: If updating from a manifest with no timestamp (pre 1.0.11), new version was considered older, so no upgrade was performed - Basic Authentication support for manifest url (via https://user:pass@host/path) - Added `--include-extensions` as a comma separated list of filename extensions to include of other resources from the build dir. By default it always includes `jar,war`. diff --git a/src/main/java/fxlauncher/OS.java b/src/main/java/fxlauncher/OS.java index 30b0dd3..427169b 100644 --- a/src/main/java/fxlauncher/OS.java +++ b/src/main/java/fxlauncher/OS.java @@ -1,6 +1,6 @@ package fxlauncher; -public enum OS { +enum OS { win, mac, linux, other; public static final OS current; From af3e2411bd9d9602388a4e369ff431e567d9968e Mon Sep 17 00:00:00 2001 From: edvin Date: Sat, 10 Sep 2016 19:44:57 +0200 Subject: [PATCH 27/38] UIProvider to support completely custom update UI --- .../java/fxlauncher/DefaultUIProvider.java | 37 ++ src/main/java/fxlauncher/Launcher.java | 437 +++++++++--------- src/main/java/fxlauncher/UIProvider.java | 33 ++ 3 files changed, 291 insertions(+), 216 deletions(-) create mode 100644 src/main/java/fxlauncher/DefaultUIProvider.java create mode 100644 src/main/java/fxlauncher/UIProvider.java diff --git a/src/main/java/fxlauncher/DefaultUIProvider.java b/src/main/java/fxlauncher/DefaultUIProvider.java new file mode 100644 index 0000000..4d2c3a3 --- /dev/null +++ b/src/main/java/fxlauncher/DefaultUIProvider.java @@ -0,0 +1,37 @@ +package fxlauncher; + +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +public class DefaultUIProvider implements UIProvider { + private ProgressBar progressBar; + + public Parent createLoader() { + StackPane root = new StackPane(new ProgressIndicator()); + root.setPrefSize(200, 80); + root.setPadding(new Insets(10)); + return root; + } + + public Parent createUpdater(FXManifest manifest) { + progressBar = new ProgressBar(); + progressBar.setStyle(manifest.progressBarStyle); + + Label label = new Label(manifest.updateText); + label.setStyle(manifest.updateLabelStyle); + + VBox wrapper = new VBox(label, progressBar); + wrapper.setStyle(manifest.wrapperStyle); + + return wrapper; + } + + public void updateProgress(double progress) { + progressBar.setProgress(progress); + } +} diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index e1a506e..ced64c3 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -5,11 +5,10 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; -import javafx.geometry.Insets; +import javafx.scene.Parent; import javafx.scene.Scene; -import javafx.scene.control.*; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; +import javafx.scene.control.Alert; +import javafx.scene.control.TextArea; import javafx.stage.Stage; import javafx.stage.StageStyle; @@ -23,225 +22,231 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import static com.sun.org.apache.xml.internal.security.keys.keyresolver.KeyResolver.iterator; + @SuppressWarnings("unchecked") public class Launcher extends Application { - private static final Logger log = Logger.getLogger("Launcher"); - - private FXManifest manifest; - private Application app; - private StackPane root; - private Stage primaryStage; - private ProgressBar progressBar; - private Stage stage; - private String phase; - - public void start(Stage primaryStage) throws Exception { - this.primaryStage = primaryStage; - - root = new StackPane(new ProgressIndicator()); - root.setPrefSize(200, 80); - root.setPadding(new Insets(10)); - - stage = new Stage(StageStyle.UNDECORATED); - - Scene scene = new Scene(root); - stage.setScene(scene); - stage.show(); - - new Thread(() -> { - Thread.currentThread().setName("FXLauncher-Thread"); - try { - updateManifest(); - createUpdateWrapper(); - Path cacheDir = manifest.resolveCacheDir(getParameters().getNamed()); - log.info(String.format("Using cache dir %s", cacheDir)); - syncFiles(cacheDir); - } catch (Exception ex) { - log.log(Level.WARNING, String.format("Error during %s phase", phase), ex); - } - - try { - createApplication(); - launchAppFromManifest(); - } catch (Exception ex) { - reportError(String.format("Error during %s phase", phase), ex); - } - - }).start(); - } - - public static void main(String[] args) { - launch(args); - } - - private void createUpdateWrapper() { - phase = "Update Wrapper Creation"; - - Platform.runLater(() -> { - progressBar = new ProgressBar(); - progressBar.setStyle(manifest.progressBarStyle); - - Label label = new Label(manifest.updateText); - label.setStyle(manifest.updateLabelStyle); - - VBox wrapper = new VBox(label, progressBar); - wrapper.setStyle(manifest.wrapperStyle); - - root.getChildren().clear(); - root.getChildren().add(wrapper); - }); - } - - private URLClassLoader createClassLoader(Path cacheDir) { - List libs = manifest.files.stream() - .filter(LibraryFile::loadForCurrentPlatform) - .map(it -> it.toURL(cacheDir)) - .collect(Collectors.toList()); - - return new URLClassLoader(libs.toArray(new URL[libs.size()])); - } - - private void launchAppFromManifest() throws Exception { - phase = "Application Init"; - app.init(); - phase = "Application Start"; - PlatformImpl.runAndWait(() -> { - try { - primaryStage.showingProperty().addListener(observable -> { - if (stage.isShowing()) stage.close(); - }); - app.start(primaryStage); - } catch (Exception ex) { - reportError("Failed to start application", ex); - } - }); - } - - private void updateManifest() throws Exception { - phase = "Update Manifest"; - syncManifest(); - } - - private void syncFiles(Path cacheDir) throws Exception { - phase = "File Synchronization"; - - List needsUpdate = manifest.files.stream() - .filter(LibraryFile::loadForCurrentPlatform) - .filter(it -> it.needsUpdate(cacheDir)) - .collect(Collectors.toList()); - - Long totalBytes = needsUpdate.stream().mapToLong(f -> f.size).sum(); - Long totalWritten = 0L; - - for (LibraryFile lib : needsUpdate) { - Path target = cacheDir.resolve(lib.file).toAbsolutePath(); - Files.createDirectories(target.getParent()); - - try (InputStream input = manifest.uri.resolve(lib.file).toURL().openStream(); - OutputStream output = Files.newOutputStream(target)) { - - byte[] buf = new byte[65536]; - - int read; - while ((read = input.read(buf)) > -1) { - output.write(buf, 0, read); - totalWritten += read; - Double progress = totalWritten.doubleValue() / totalBytes.doubleValue(); - Platform.runLater(() -> progressBar.setProgress(progress)); - } - } - } - } - - private void createApplication() throws Exception { - phase = "Create Application"; - - if (manifest == null) throw new IllegalArgumentException("Unable to retrieve embedded or remote manifest."); - Path cacheDir = manifest.resolveCacheDir(getParameters() != null ? getParameters().getNamed() : null); - - URLClassLoader classLoader = createClassLoader(cacheDir); - FXMLLoader.setDefaultClassLoader(classLoader); - Thread.currentThread().setContextClassLoader(classLoader); - Platform.runLater(() -> Thread.currentThread().setContextClassLoader(classLoader)); - Class appclass = (Class) classLoader.loadClass(manifest.launchClass); - - PlatformImpl.runAndWait(() -> { - try { - app = appclass.newInstance(); - ParametersImpl.registerParameters(app, new LauncherParams(getParameters(), manifest)); - PlatformImpl.setApplicationName(appclass); - } catch (Throwable t) { - reportError("Error creating app class", t); - } - }); - } - - public void stop() throws Exception { - if (app != null) - app.stop(); - } - - private void reportError(String title, Throwable error) { - log.log(Level.WARNING, title, error); - - Platform.runLater(() -> { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle(title); - alert.setHeaderText(title); - alert.getDialogPane().setPrefWidth(600); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PrintWriter writer = new PrintWriter(out); - error.printStackTrace(writer); - writer.close(); - TextArea text = new TextArea(out.toString()); - alert.getDialogPane().setContent(text); - - alert.showAndWait(); - Platform.exit(); - }); - } - - private void syncManifest() throws Exception { - Map namedParams = getParameters().getNamed(); - - if (namedParams.containsKey("app")) { - String manifestURL = namedParams.get("app"); - log.info(String.format("Loading manifest from parameter supplied location %s", manifestURL)); - manifest = FXManifest.load(URI.create(manifestURL)); - return; - } - - URL embeddedManifest = Launcher.class.getResource("/app.xml"); - manifest = JAXB.unmarshal(embeddedManifest, FXManifest.class); - - Path cacheDir = manifest.resolveCacheDir(namedParams); - Path manifestPath = manifest.getPath(cacheDir); - - if (Files.exists(manifestPath)) - manifest = JAXB.unmarshal(manifestPath.toFile(), FXManifest.class); - - try { - FXManifest remoteManifest = FXManifest.load(manifest.getFXAppURI()); - - if (remoteManifest == null) { - log.info(String.format("No remote manifest at %s", manifest.getFXAppURI())); - } else if (!remoteManifest.equals(manifest)) { - // Update to remote manifest if newer or we specifially accept downgrades - if (remoteManifest.isNewerThan(manifest) || manifest.acceptDowngrade) { - manifest = remoteManifest; - JAXB.marshal(manifest, manifestPath.toFile()); - } - } - } catch (Exception ex) { - log.log(Level.WARNING, "Unable to update manifest", ex); - } - } + private static final Logger log = Logger.getLogger("Launcher"); + + private FXManifest manifest; + private Application app; + private Stage primaryStage; + private Stage stage; + private String phase; + private UIProvider uiProvider; + + /** + * Initialize the UI Provider by looking for an UIProvider inside the launcher + * or fallback to the default UI. + * + * A custom implementation must be embedded inside the launcher jar, and + * /META-INF/services/fxlauncher.UIProvider must point to the new implementation class. + * + * You must do this manually/in your build right around the "embed manifest" step. + */ + public void init() throws Exception { + Iterator providers = ServiceLoader.load(UIProvider.class).iterator(); + uiProvider = providers.hasNext() ? providers.next() : new DefaultUIProvider(); + } + + public void start(Stage primaryStage) throws Exception { + this.primaryStage = primaryStage; + + Parent loader = uiProvider.createLoader(); + + stage = new Stage(StageStyle.UNDECORATED); + + Scene scene = new Scene(loader); + stage.setScene(scene); + stage.show(); + + new Thread(() -> { + Thread.currentThread().setName("FXLauncher-Thread"); + try { + updateManifest(); + createUpdateWrapper(); + Path cacheDir = manifest.resolveCacheDir(getParameters().getNamed()); + log.info(String.format("Using cache dir %s", cacheDir)); + syncFiles(cacheDir); + } catch (Exception ex) { + log.log(Level.WARNING, String.format("Error during %s phase", phase), ex); + } + + try { + createApplication(); + launchAppFromManifest(); + } catch (Exception ex) { + reportError(String.format("Error during %s phase", phase), ex); + } + + }).start(); + } + + public static void main(String[] args) { + launch(args); + } + + private void createUpdateWrapper() { + phase = "Update Wrapper Creation"; + + Platform.runLater(() -> { + Parent updater = uiProvider.createUpdater(manifest); + stage.getScene().setRoot(updater); + }); + } + + private URLClassLoader createClassLoader(Path cacheDir) { + List libs = manifest.files.stream() + .filter(LibraryFile::loadForCurrentPlatform) + .map(it -> it.toURL(cacheDir)) + .collect(Collectors.toList()); + + return new URLClassLoader(libs.toArray(new URL[libs.size()])); + } + + private void launchAppFromManifest() throws Exception { + phase = "Application Init"; + app.init(); + phase = "Application Start"; + PlatformImpl.runAndWait(() -> { + try { + primaryStage.showingProperty().addListener(observable -> { + if (stage.isShowing()) stage.close(); + }); + app.start(primaryStage); + } catch (Exception ex) { + reportError("Failed to start application", ex); + } + }); + } + + private void updateManifest() throws Exception { + phase = "Update Manifest"; + syncManifest(); + } + + private void syncFiles(Path cacheDir) throws Exception { + phase = "File Synchronization"; + + List needsUpdate = manifest.files.stream() + .filter(LibraryFile::loadForCurrentPlatform) + .filter(it -> it.needsUpdate(cacheDir)) + .collect(Collectors.toList()); + + Long totalBytes = needsUpdate.stream().mapToLong(f -> f.size).sum(); + Long totalWritten = 0L; + + for (LibraryFile lib : needsUpdate) { + Path target = cacheDir.resolve(lib.file).toAbsolutePath(); + Files.createDirectories(target.getParent()); + + try (InputStream input = manifest.uri.resolve(lib.file).toURL().openStream(); + OutputStream output = Files.newOutputStream(target)) { + + byte[] buf = new byte[65536]; + + int read; + while ((read = input.read(buf)) > -1) { + output.write(buf, 0, read); + totalWritten += read; + Double progress = totalWritten.doubleValue() / totalBytes.doubleValue(); + Platform.runLater(() -> uiProvider.updateProgress(progress)); + } + } + } + } + + private void createApplication() throws Exception { + phase = "Create Application"; + + if (manifest == null) throw new IllegalArgumentException("Unable to retrieve embedded or remote manifest."); + Path cacheDir = manifest.resolveCacheDir(getParameters() != null ? getParameters().getNamed() : null); + + URLClassLoader classLoader = createClassLoader(cacheDir); + FXMLLoader.setDefaultClassLoader(classLoader); + Thread.currentThread().setContextClassLoader(classLoader); + Platform.runLater(() -> Thread.currentThread().setContextClassLoader(classLoader)); + Class appclass = (Class) classLoader.loadClass(manifest.launchClass); + + PlatformImpl.runAndWait(() -> { + try { + app = appclass.newInstance(); + ParametersImpl.registerParameters(app, new LauncherParams(getParameters(), manifest)); + PlatformImpl.setApplicationName(appclass); + } catch (Throwable t) { + reportError("Error creating app class", t); + } + }); + } + + public void stop() throws Exception { + if (app != null) + app.stop(); + } + + private void reportError(String title, Throwable error) { + log.log(Level.WARNING, title, error); + + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(title); + alert.getDialogPane().setPrefWidth(600); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(out); + error.printStackTrace(writer); + writer.close(); + TextArea text = new TextArea(out.toString()); + alert.getDialogPane().setContent(text); + + alert.showAndWait(); + Platform.exit(); + }); + } + + private void syncManifest() throws Exception { + Map namedParams = getParameters().getNamed(); + + if (namedParams.containsKey("app")) { + String manifestURL = namedParams.get("app"); + log.info(String.format("Loading manifest from parameter supplied location %s", manifestURL)); + manifest = FXManifest.load(URI.create(manifestURL)); + return; + } + + URL embeddedManifest = Launcher.class.getResource("/app.xml"); + manifest = JAXB.unmarshal(embeddedManifest, FXManifest.class); + + Path cacheDir = manifest.resolveCacheDir(namedParams); + Path manifestPath = manifest.getPath(cacheDir); + + if (Files.exists(manifestPath)) + manifest = JAXB.unmarshal(manifestPath.toFile(), FXManifest.class); + + try { + FXManifest remoteManifest = FXManifest.load(manifest.getFXAppURI()); + + if (remoteManifest == null) { + log.info(String.format("No remote manifest at %s", manifest.getFXAppURI())); + } else if (!remoteManifest.equals(manifest)) { + // Update to remote manifest if newer or we specifially accept downgrades + if (remoteManifest.isNewerThan(manifest) || manifest.acceptDowngrade) { + manifest = remoteManifest; + JAXB.marshal(manifest, manifestPath.toFile()); + } + } + } catch (Exception ex) { + log.log(Level.WARNING, "Unable to update manifest", ex); + } + } } diff --git a/src/main/java/fxlauncher/UIProvider.java b/src/main/java/fxlauncher/UIProvider.java new file mode 100644 index 0000000..27b2e95 --- /dev/null +++ b/src/main/java/fxlauncher/UIProvider.java @@ -0,0 +1,33 @@ +package fxlauncher; + +import javafx.scene.Parent; + +public interface UIProvider { + /** + * Create the Node that will be displayed while the launcher is loading resources, + * before the update process starts. The default implementation is an intdeterminate + * progress indicator, but you can return any arbitrary scene graph. + * + * @return The launcher UI + */ + Parent createLoader(); + + /** + * Create the Node that will be displayed while the launcher is updating resources. + * + * This Node should update it's display whenever the {@link #updateProgress(double)} + * method is called. + * + * @see #updateProgress(double) + * @return The updater Node + */ + Parent createUpdater(FXManifest manifest); + + /** + * Called when the update/download progress is changing. The progress is a value between + * 0 and 1, indicating the completion rate of the update process. + * + * @param progress A number between 0 and 1 + */ + void updateProgress(double progress); +} From 9bf008487715e25f90e55b735dd455182d2f140e Mon Sep 17 00:00:00 2001 From: edvin Date: Sat, 10 Sep 2016 19:55:11 +0200 Subject: [PATCH 28/38] Updated README with info about UIProvider --- README.md | 9 ++++++++- src/main/java/fxlauncher/UIProvider.java | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d74dab5..24d4af5 100644 --- a/README.md +++ b/README.md @@ -88,4 +88,11 @@ then made available to the `FXMLLoader`. You can access it via `FXMLLoader.getDe FXLauncher supports filtering of resources for the running platform. Any resource that ends with `-[mac|win|linux].jar` will only be downloaded and put on the classpath on the corresponding -platform. The manifest enforces this though the `os` attribute in `app.xml`. \ No newline at end of file +platform. The manifest enforces this though the `os` attribute in `app.xml`. + +### Custom UI + +There are two ways to customize the appearance of the update UI. Either you can configure the +supported style properties in the manifest, or you can provide a custom implementation of the +[UIProvider](https://github.com/edvin/fxlauncher/blob/master/src/main/java/fxlauncher/UIProvider.java) +to completely customize the UI. \ No newline at end of file diff --git a/src/main/java/fxlauncher/UIProvider.java b/src/main/java/fxlauncher/UIProvider.java index 27b2e95..8eab07a 100644 --- a/src/main/java/fxlauncher/UIProvider.java +++ b/src/main/java/fxlauncher/UIProvider.java @@ -2,6 +2,28 @@ import javafx.scene.Parent; +/** + * The UIProvider is responsible for creating the loader screen and the updater screen. + * A default implementation is available in the {@link DefaultUIProvider} class, but you + * can provide a custom implementation to alter the appearance of the loader UI. + * + * Implement this interface and make sure to embed the classes inside the fxlauncher.jar + * right around the "embed manifest" step. You have to do this manually as there is no function + * in the plugin to support this yet. Basically you have to do the following two steps: + * + * 1. Copy the implementation classes into the fxlauncher.jar + * 2. Create META-INF/services/fxlauncher.UIProvider inside the fxlauncher.jar. The content must + * be a string with the fully qualified name of your implementation class. + * + * Typical example: + * + *
+ * # cd into directory with ui sources + * jar uf fxlauncher.jar -C my/package/MyUIProvider.class + * # cd into directory with META-INF folder + * jar uf fxlauncher.jar -C META-INF/services/fxlauncher.UIProvider + *
+ */ public interface UIProvider { /** * Create the Node that will be displayed while the launcher is loading resources, From 34ee13b798f8999eca6d6baaa1b9fe3c0f2cb511 Mon Sep 17 00:00:00 2001 From: edvin Date: Sat, 10 Sep 2016 22:14:59 +0200 Subject: [PATCH 29/38] Updated README with info about UIProvider --- src/main/java/fxlauncher/FXManifest.java | 22 +++++++++++----------- src/main/java/fxlauncher/Launcher.java | 2 -- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index 15aaac3..2819b41 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -18,27 +18,27 @@ @XmlRootElement(name = "Application") public class FXManifest { @XmlAttribute - Long ts; + public Long ts; @XmlAttribute - URI uri; + public URI uri; @XmlAttribute(name = "launch") - String launchClass; + public String launchClass; @XmlElement(name = "lib") - List files = new ArrayList<>(); + public List files = new ArrayList<>(); @XmlElement - String updateText = "Updating..."; + public String updateText = "Updating..."; @XmlElement - String updateLabelStyle = "-fx-font-weight: bold;"; + public String updateLabelStyle = "-fx-font-weight: bold;"; @XmlElement - String progressBarStyle = "-fx-pref-width: 200;"; + public String progressBarStyle = "-fx-pref-width: 200;"; @XmlElement - String wrapperStyle = "-fx-spacing: 10; -fx-padding: 25;"; + public String wrapperStyle = "-fx-spacing: 10; -fx-padding: 25;"; @XmlElement - String parameters; + public String parameters; @XmlElement - String cacheDir; + public String cacheDir; @XmlElement - Boolean acceptDowngrade = false; + public Boolean acceptDowngrade = false; public String getFilename() { return String.format("%s.xml", launchClass); diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index ced64c3..e1dc451 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -30,8 +30,6 @@ import java.util.logging.Logger; import java.util.stream.Collectors; -import static com.sun.org.apache.xml.internal.security.keys.keyresolver.KeyResolver.iterator; - @SuppressWarnings("unchecked") public class Launcher extends Application { private static final Logger log = Logger.getLogger("Launcher"); From 3bb0db12569d882c0467e0a072fc9e22ef8458b6 Mon Sep 17 00:00:00 2001 From: edvin Date: Sat, 10 Sep 2016 22:20:58 +0200 Subject: [PATCH 30/38] Loader gets access to Stage --- src/main/java/fxlauncher/DefaultUIProvider.java | 3 ++- src/main/java/fxlauncher/Launcher.java | 4 ++-- src/main/java/fxlauncher/UIProvider.java | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/fxlauncher/DefaultUIProvider.java b/src/main/java/fxlauncher/DefaultUIProvider.java index 4d2c3a3..6958db8 100644 --- a/src/main/java/fxlauncher/DefaultUIProvider.java +++ b/src/main/java/fxlauncher/DefaultUIProvider.java @@ -7,11 +7,12 @@ import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; +import javafx.stage.Stage; public class DefaultUIProvider implements UIProvider { private ProgressBar progressBar; - public Parent createLoader() { + public Parent createLoader(Stage stage) { StackPane root = new StackPane(new ProgressIndicator()); root.setPrefSize(200, 80); root.setPadding(new Insets(10)); diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index e1dc451..ec81040 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -58,10 +58,10 @@ public void init() throws Exception { public void start(Stage primaryStage) throws Exception { this.primaryStage = primaryStage; - Parent loader = uiProvider.createLoader(); - stage = new Stage(StageStyle.UNDECORATED); + Parent loader = uiProvider.createLoader(stage); + Scene scene = new Scene(loader); stage.setScene(scene); stage.show(); diff --git a/src/main/java/fxlauncher/UIProvider.java b/src/main/java/fxlauncher/UIProvider.java index 8eab07a..e652f28 100644 --- a/src/main/java/fxlauncher/UIProvider.java +++ b/src/main/java/fxlauncher/UIProvider.java @@ -1,6 +1,7 @@ package fxlauncher; import javafx.scene.Parent; +import javafx.stage.Stage; /** * The UIProvider is responsible for creating the loader screen and the updater screen. @@ -30,9 +31,11 @@ public interface UIProvider { * before the update process starts. The default implementation is an intdeterminate * progress indicator, but you can return any arbitrary scene graph. * + * This method is always called before {@link #createUpdater(FXManifest)} + * * @return The launcher UI */ - Parent createLoader(); + Parent createLoader(Stage stage); /** * Create the Node that will be displayed while the launcher is updating resources. From 5f5fcf6a68d51c70709c70257d36536e142d640e Mon Sep 17 00:00:00 2001 From: edvin Date: Sat, 10 Sep 2016 22:24:55 +0200 Subject: [PATCH 31/38] Updater gets access to Stage --- src/main/java/fxlauncher/DefaultUIProvider.java | 2 +- src/main/java/fxlauncher/Launcher.java | 2 +- src/main/java/fxlauncher/UIProvider.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/fxlauncher/DefaultUIProvider.java b/src/main/java/fxlauncher/DefaultUIProvider.java index 6958db8..c29ce2a 100644 --- a/src/main/java/fxlauncher/DefaultUIProvider.java +++ b/src/main/java/fxlauncher/DefaultUIProvider.java @@ -19,7 +19,7 @@ public Parent createLoader(Stage stage) { return root; } - public Parent createUpdater(FXManifest manifest) { + public Parent createUpdater(Stage stage, FXManifest manifest) { progressBar = new ProgressBar(); progressBar.setStyle(manifest.progressBarStyle); diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index ec81040..61c336e 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -96,7 +96,7 @@ private void createUpdateWrapper() { phase = "Update Wrapper Creation"; Platform.runLater(() -> { - Parent updater = uiProvider.createUpdater(manifest); + Parent updater = uiProvider.createUpdater(stage, manifest); stage.getScene().setRoot(updater); }); } diff --git a/src/main/java/fxlauncher/UIProvider.java b/src/main/java/fxlauncher/UIProvider.java index e652f28..676ea69 100644 --- a/src/main/java/fxlauncher/UIProvider.java +++ b/src/main/java/fxlauncher/UIProvider.java @@ -31,7 +31,7 @@ public interface UIProvider { * before the update process starts. The default implementation is an intdeterminate * progress indicator, but you can return any arbitrary scene graph. * - * This method is always called before {@link #createUpdater(FXManifest)} + * This method is always called before {@link #createUpdater(Stage, FXManifest)} * * @return The launcher UI */ @@ -46,7 +46,7 @@ public interface UIProvider { * @see #updateProgress(double) * @return The updater Node */ - Parent createUpdater(FXManifest manifest); + Parent createUpdater(Stage stage, FXManifest manifest); /** * Called when the update/download progress is changing. The progress is a value between From 8b7577c02e132fb938524cc4baa93ffedd1b2680 Mon Sep 17 00:00:00 2001 From: edvin Date: Sun, 11 Sep 2016 11:12:33 +0200 Subject: [PATCH 32/38] Prepare for 1.0.13 --- CHANGELOG.md | 3 ++- README.md | 3 ++- pom.xml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22cd606..8498297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,10 @@ All notable changes to this project will be documented in this file. ## [10.0.13-SNAPSHOT] -- Fixed bug: If updating from a manifest with no timestamp (pre 1.0.11), new version was considered older, so no upgrade was performed +- Support for fully customizable update UI, see (https://github.com/edvin/fxlauncher-custom-ui) - Basic Authentication support for manifest url (via https://user:pass@host/path) - Added `--include-extensions` as a comma separated list of filename extensions to include of other resources from the build dir. By default it always includes `jar,war`. +- Fixed bug: If updating from a manifest with no timestamp (pre 1.0.11), new version was considered older, so no upgrade was performed ## [1.0.12] diff --git a/README.md b/README.md index 24d4af5..56c1798 100644 --- a/README.md +++ b/README.md @@ -95,4 +95,5 @@ platform. The manifest enforces this though the `os` attribute in `app.xml`. There are two ways to customize the appearance of the update UI. Either you can configure the supported style properties in the manifest, or you can provide a custom implementation of the [UIProvider](https://github.com/edvin/fxlauncher/blob/master/src/main/java/fxlauncher/UIProvider.java) -to completely customize the UI. \ No newline at end of file +to completely customize the UI. Have a look at this [Custom UI Demo Project](https://github.com/edvin/fxlauncher-custom-ui) for +more information about customizing the updater. \ No newline at end of file diff --git a/pom.xml b/pom.xml index e70552b..d294f6e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.13-SNAPSHOT + 1.0.13 jar FX Launcher Auto updating launcher for JavaFX Applications From 69c8dae2184202f094aaf4b7bf3457e66bcb1d08 Mon Sep 17 00:00:00 2001 From: edvin Date: Sun, 11 Sep 2016 11:36:27 +0200 Subject: [PATCH 33/38] Added init(stage) step to UIProvider --- src/main/java/fxlauncher/DefaultUIProvider.java | 8 ++++++-- src/main/java/fxlauncher/Launcher.java | 7 ++++--- src/main/java/fxlauncher/UIProvider.java | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/fxlauncher/DefaultUIProvider.java b/src/main/java/fxlauncher/DefaultUIProvider.java index c29ce2a..46dafd4 100644 --- a/src/main/java/fxlauncher/DefaultUIProvider.java +++ b/src/main/java/fxlauncher/DefaultUIProvider.java @@ -12,14 +12,14 @@ public class DefaultUIProvider implements UIProvider { private ProgressBar progressBar; - public Parent createLoader(Stage stage) { + public Parent createLoader() { StackPane root = new StackPane(new ProgressIndicator()); root.setPrefSize(200, 80); root.setPadding(new Insets(10)); return root; } - public Parent createUpdater(Stage stage, FXManifest manifest) { + public Parent createUpdater(FXManifest manifest) { progressBar = new ProgressBar(); progressBar.setStyle(manifest.progressBarStyle); @@ -35,4 +35,8 @@ public Parent createUpdater(Stage stage, FXManifest manifest) { public void updateProgress(double progress) { progressBar.setProgress(progress); } + + public void init(Stage stage) { + + } } diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index 61c336e..f7aaee3 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -57,10 +57,11 @@ public void init() throws Exception { public void start(Stage primaryStage) throws Exception { this.primaryStage = primaryStage; - stage = new Stage(StageStyle.UNDECORATED); - Parent loader = uiProvider.createLoader(stage); + this.uiProvider.init(stage); + + Parent loader = uiProvider.createLoader(); Scene scene = new Scene(loader); stage.setScene(scene); @@ -96,7 +97,7 @@ private void createUpdateWrapper() { phase = "Update Wrapper Creation"; Platform.runLater(() -> { - Parent updater = uiProvider.createUpdater(stage, manifest); + Parent updater = uiProvider.createUpdater(manifest); stage.getScene().setRoot(updater); }); } diff --git a/src/main/java/fxlauncher/UIProvider.java b/src/main/java/fxlauncher/UIProvider.java index 676ea69..46d649a 100644 --- a/src/main/java/fxlauncher/UIProvider.java +++ b/src/main/java/fxlauncher/UIProvider.java @@ -26,16 +26,24 @@ * */ public interface UIProvider { + + /** + * Initialization method called before {@link #createLoader()} + * and {@link #createUpdater(FXManifest)}. This is a good place to add + * stylesheets and perform other configuration. + * + * @param stage The stage that will be used to contain the loader and updater. + */ + void init(Stage stage); + /** * Create the Node that will be displayed while the launcher is loading resources, * before the update process starts. The default implementation is an intdeterminate * progress indicator, but you can return any arbitrary scene graph. * - * This method is always called before {@link #createUpdater(Stage, FXManifest)} - * * @return The launcher UI */ - Parent createLoader(Stage stage); + Parent createLoader(); /** * Create the Node that will be displayed while the launcher is updating resources. @@ -46,7 +54,7 @@ public interface UIProvider { * @see #updateProgress(double) * @return The updater Node */ - Parent createUpdater(Stage stage, FXManifest manifest); + Parent createUpdater(FXManifest manifest); /** * Called when the update/download progress is changing. The progress is a value between From 41ccfaad0d8d8f9cf14fc16a3177be1a64fd810e Mon Sep 17 00:00:00 2001 From: edvin Date: Sun, 11 Sep 2016 11:44:36 +0200 Subject: [PATCH 34/38] Made sure init(stage) contains a scene when UIProvider.init is run --- src/main/java/fxlauncher/Launcher.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/fxlauncher/Launcher.java b/src/main/java/fxlauncher/Launcher.java index f7aaee3..2a5d15a 100644 --- a/src/main/java/fxlauncher/Launcher.java +++ b/src/main/java/fxlauncher/Launcher.java @@ -9,6 +9,7 @@ import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.TextArea; +import javafx.scene.layout.StackPane; import javafx.stage.Stage; import javafx.stage.StageStyle; @@ -40,6 +41,7 @@ public class Launcher extends Application { private Stage stage; private String phase; private UIProvider uiProvider; + private StackPane root; /** * Initialize the UI Provider by looking for an UIProvider inside the launcher @@ -58,13 +60,13 @@ public void init() throws Exception { public void start(Stage primaryStage) throws Exception { this.primaryStage = primaryStage; stage = new Stage(StageStyle.UNDECORATED); + root = new StackPane(); + Scene scene = new Scene(root); + stage.setScene(scene); this.uiProvider.init(stage); + root.getChildren().add(uiProvider.createLoader()); - Parent loader = uiProvider.createLoader(); - - Scene scene = new Scene(loader); - stage.setScene(scene); stage.show(); new Thread(() -> { @@ -98,7 +100,8 @@ private void createUpdateWrapper() { Platform.runLater(() -> { Parent updater = uiProvider.createUpdater(manifest); - stage.getScene().setRoot(updater); + root.getChildren().clear(); + root.getChildren().add(updater); }); } From 78bf1dc7214ef65a22e0c2db78aafc263766be88 Mon Sep 17 00:00:00 2001 From: edvin Date: Sun, 11 Sep 2016 21:24:56 +0200 Subject: [PATCH 35/38] Updated CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8498297..72b21ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. -## [10.0.13-SNAPSHOT] +## [1.0.13-SNAPSHOT] - Support for fully customizable update UI, see (https://github.com/edvin/fxlauncher-custom-ui) - Basic Authentication support for manifest url (via https://user:pass@host/path) From 781d97b5544070d9455dab26208ce0591e4ebef6 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Mon, 12 Sep 2016 08:39:38 +0200 Subject: [PATCH 36/38] Updated CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b21ab..ef3eb10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. -## [1.0.13-SNAPSHOT] +## [1.0.13] - 2016-09-12 - Support for fully customizable update UI, see (https://github.com/edvin/fxlauncher-custom-ui) - Basic Authentication support for manifest url (via https://user:pass@host/path) From eb8d6446cb6f3dbdaeb40e251c13fd2a0534ac60 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Mon, 12 Sep 2016 08:44:01 +0200 Subject: [PATCH 37/38] Linked to ui customize screencast in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56c1798..1d15446 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You can see the launcher in action in this [Demo Application](http://fxldemo.tor ### Video demonstration -See the launcher in action in this short [screencast](https://www.youtube.com/watch?v=NCP9wjRPQ14). +See the launcher in action in this short [screencast](https://www.youtube.com/watch?v=NCP9wjRPQ14). There is also a [video](https://www.youtube.com/watch?v=-6PlFVUgntU) about customizing the update user interface. ## How does it work? From a3024578de316e1cde532e4db140d7f2077a0171 Mon Sep 17 00:00:00 2001 From: Edvin Syse Date: Sat, 24 Sep 2016 17:35:41 +0200 Subject: [PATCH 38/38] `include-extensions` was consulted too early, leaving the matched files out of the manifest --- CHANGELOG.md | 4 ++++ pom.xml | 2 +- src/main/java/fxlauncher/CreateManifest.java | 16 ++++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef3eb10..871d519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log All notable changes to this project will be documented in this file. +## [10.0.14-SNAPSHOT] + +- `include-extensions` was consulted too early, leaving the matched files out of the manifest + ## [1.0.13] - 2016-09-12 - Support for fully customizable update UI, see (https://github.com/edvin/fxlauncher-custom-ui) diff --git a/pom.xml b/pom.xml index d294f6e..ba124aa 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ no.tornado fxlauncher - 1.0.13 + 1.0.14-SNAPSHOT jar FX Launcher Auto updating launcher for JavaFX Applications diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index 185c05a..5d69a6a 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -24,7 +24,10 @@ public static void main(String[] args) throws IOException { URI baseURI = URI.create(args[0]); String launchClass = args[1]; Path appPath = Paths.get(args[2]); - FXManifest manifest = create(baseURI, launchClass, appPath); + + String cacheDir = null; + Boolean acceptDowngrade = null; + String parameters = null; if (args.length > 3) { // Parse named parameters @@ -36,11 +39,11 @@ public static void main(String[] args) throws IOException { if (named != null) { // Configure cacheDir if (named.containsKey("cache-dir")) - manifest.cacheDir = named.get("cache-dir"); + cacheDir = named.get("cache-dir"); // Configure acceptDowngrade if (named.containsKey("accept-downgrade")) - manifest.acceptDowngrade = Boolean.valueOf(named.get("accept-downgrade")); + acceptDowngrade = Boolean.valueOf(named.get("accept-downgrade")); // Add additional files with these extensions to manifest if (named.containsKey("include-extensions")) @@ -63,9 +66,14 @@ public static void main(String[] args) throws IOException { // Add the raw parameter string to the manifest if (rest.length() > 0) - manifest.parameters = rest.toString(); + parameters = rest.toString(); } + FXManifest manifest = create(baseURI, launchClass, appPath); + if (cacheDir != null) manifest.cacheDir = cacheDir; + if (acceptDowngrade != null) manifest.acceptDowngrade = acceptDowngrade; + if (parameters != null) manifest.parameters = parameters; + JAXB.marshal(manifest, appPath.resolve("app.xml").toFile()); }