diff --git a/main/core/pom.xml b/main/core/pom.xml index 19fc63c77..c3c95927d 100644 --- a/main/core/pom.xml +++ b/main/core/pom.xml @@ -18,8 +18,8 @@ Cryptomator WebDAV and I/O module - 9.2.5.v20141112 - 2.9.0 + 9.2.10.v20150310 + 2.9.1 1.2 1.1 diff --git a/main/pom.xml b/main/pom.xml index 699883e6e..182f1a656 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -39,6 +39,7 @@ 4.0 3.3.2 1.10 + 3.1 2.4.4 1.10.19 @@ -110,6 +111,12 @@ commons-codec ${commons-codec.version} + + + commons-httpclient + commons-httpclient + ${commons-httpclient.version} + diff --git a/main/ui/pom.xml b/main/ui/pom.xml index c70c4875d..e1ca725e1 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -48,7 +48,11 @@ org.apache.commons commons-lang3 - + + commons-httpclient + commons-httpclient + + com.google.inject @@ -78,6 +82,7 @@ ${exec.mainClass} + ${project.version} diff --git a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java index 7889951e3..7035fe88c 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java +++ b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java @@ -57,14 +57,15 @@ public class MainApplication extends Application { } public MainApplication(Injector injector) { - this(injector.getInstance(ExecutorService.class), injector.getInstance(ControllerFactory.class), injector.getInstance(DeferredCloser.class)); + this(injector.getInstance(ExecutorService.class), injector.getInstance(ControllerFactory.class), injector.getInstance(DeferredCloser.class), injector.getInstance(MainApplicationReference.class)); } - public MainApplication(ExecutorService executorService, ControllerFactory controllerFactory, DeferredCloser closer) { + public MainApplication(ExecutorService executorService, ControllerFactory controllerFactory, DeferredCloser closer, MainApplicationReference appRef) { super(); this.executorService = executorService; this.controllerFactory = controllerFactory; this.closer = closer; + appRef.set(this); } @Override @@ -175,4 +176,25 @@ public class MainApplication extends Application { } } + /** + * Needed to inject MainApplication. Problem: Application needs to be set asap after injector creation. + */ + static class MainApplicationReference { + + private Application application; + + private void set(Application application) { + this.application = application; + } + + public Application get() { + if (application == null) { + throw new IllegalStateException("not yet ready."); + } else { + return application; + } + } + + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/MainModule.java b/main/ui/src/main/java/org/cryptomator/ui/MainModule.java index 37f07159a..bfa815eab 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/MainModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/MainModule.java @@ -8,22 +8,27 @@ ******************************************************************************/ package org.cryptomator.ui; +import java.util.Comparator; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javafx.application.Application; import javafx.util.Callback; +import javax.inject.Named; import javax.inject.Singleton; import org.cryptomator.crypto.Cryptor; import org.cryptomator.crypto.SamplingDecorator; import org.cryptomator.crypto.aes256.Aes256Cryptor; +import org.cryptomator.ui.MainApplication.MainApplicationReference; import org.cryptomator.ui.model.VaultFactory; import org.cryptomator.ui.model.VaultObjectMapperProvider; import org.cryptomator.ui.settings.Settings; import org.cryptomator.ui.settings.SettingsProvider; import org.cryptomator.ui.util.DeferredCloser; import org.cryptomator.ui.util.DeferredCloser.Closer; +import org.cryptomator.ui.util.SemVerComparator; import org.cryptomator.ui.util.mount.WebDavMounter; import org.cryptomator.ui.util.mount.WebDavMounterProvider; import org.cryptomator.webdav.WebDavServer; @@ -57,6 +62,24 @@ public class MainModule extends AbstractModule { return cls -> injector.getInstance(cls); } + @Provides + @Singleton + MainApplicationReference getApplicationBinding() { + return new MainApplicationReference(); + } + + @Provides + Application getApplication(MainApplicationReference ref) { + return ref.get(); + } + + @Provides + @Named("SemVer") + @Singleton + Comparator getSemVerComparator() { + return new SemVerComparator(); + } + @Provides @Singleton ExecutorService getExec() { diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java index be0f2abc8..88128d67b 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java @@ -8,22 +8,105 @@ ******************************************************************************/ package org.cryptomator.ui.controllers; +import java.io.IOException; import java.net.URL; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; import java.util.ResourceBundle; +import java.util.concurrent.ExecutorService; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.scene.control.Hyperlink; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javax.inject.Inject; +import javax.inject.Named; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.lang3.SystemUtils; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + public class WelcomeController implements Initializable { @FXML private ImageView botImageView; + @FXML + private Hyperlink updateLink; + + private final Application app; + private final Comparator semVerComparator; + private final ExecutorService executor; + private ResourceBundle rb; + + @Inject + public WelcomeController(Application app, @Named("SemVer") Comparator semVerComparator, ExecutorService executor) { + this.app = app; + this.semVerComparator = semVerComparator; + this.executor = executor; + } + @Override public void initialize(URL url, ResourceBundle rb) { + this.rb = rb; this.botImageView.setImage(new Image(WelcomeController.class.getResource("/bot_welcome.png").toString())); + executor.execute(this::checkForUpdates); + } + + private void checkForUpdates() { + final HttpClient client = new HttpClient(); + final HttpMethod method = new GetMethod("https://cryptomator.org/downloads/latestVersion.json"); + client.getParams().setConnectionManagerTimeout(5000); + try { + client.executeMethod(method); + if (method.getStatusCode() == HttpStatus.SC_OK) { + final byte[] responseData = method.getResponseBody(); + final ObjectMapper mapper = new ObjectMapper(); + final Map map = mapper.readValue(responseData, new TypeReference>() { + }); + this.compareVersions(map); + } + } catch (IOException e) { + // no error handling required. Maybe next time the version check is successful. + } + } + + private void compareVersions(final Map latestVersions) { + final String latestVersion; + if (SystemUtils.IS_OS_MAC_OSX) { + latestVersion = latestVersions.get("mac"); + } else if (SystemUtils.IS_OS_WINDOWS) { + latestVersion = latestVersions.get("win"); + } else if (SystemUtils.IS_OS_LINUX) { + latestVersion = latestVersions.get("linux"); + } else { + // no version check possible on unsupported OS + return; + } + final String currentVersion = WelcomeController.class.getPackage().getImplementationVersion(); + if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) { + final String msg = String.format(rb.getString("welcome.newVersionMessage"), latestVersion, currentVersion); + Platform.runLater(() -> { + this.updateLink.setText(msg); + this.updateLink.setVisible(true); + }); + } + } + + @FXML + public void didClickUpdateLink(ActionEvent event) { + app.getHostServices().showDocument("https://cryptomator.org/#download"); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/SemVerComparator.java b/main/ui/src/main/java/org/cryptomator/ui/util/SemVerComparator.java new file mode 100644 index 000000000..bf4a52791 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/SemVerComparator.java @@ -0,0 +1,34 @@ +package org.cryptomator.ui.util; + +import java.util.Comparator; + +import org.apache.commons.lang3.StringUtils; + +public class SemVerComparator implements Comparator { + + @Override + public int compare(String version1, String version2) { + final String[] vComps1 = StringUtils.split(version1, '.'); + final String[] vComps2 = StringUtils.split(version2, '.'); + final int commonCompCount = Math.min(vComps1.length, vComps2.length); + + for (int i = 0; i < commonCompCount; i++) { + int subversionComparisionResult = 0; + try { + final int v1 = Integer.parseInt(vComps1[i]); + final int v2 = Integer.parseInt(vComps2[i]); + subversionComparisionResult = v1 - v2; + } catch (NumberFormatException ex) { + // ok, lets compare this fragment lexicographically + subversionComparisionResult = vComps1[i].compareTo(vComps2[i]); + } + if (subversionComparisionResult != 0) { + return subversionComparisionResult; + } + } + + // all in common so far? longest version string wins: + return vComps1.length - vComps2.length; + } + +} \ No newline at end of file diff --git a/main/ui/src/main/resources/css/mac_theme.css b/main/ui/src/main/resources/css/mac_theme.css index 7a070830f..956ddbb17 100644 --- a/main/ui/src/main/resources/css/mac_theme.css +++ b/main/ui/src/main/resources/css/mac_theme.css @@ -293,6 +293,20 @@ -fx-text-fill: -fx-text-background-color; } +/******************************************************************************* + * * + * Hyperlink * + * * + ******************************************************************************/ + +.hyperlink { + -fx-cursor: hand; + -fx-text-fill: #0069D9; +} +.hyperlink:hover { + -fx-underline: true; +} + /******************************************************************************* * * * Button & ToggleButton * diff --git a/main/ui/src/main/resources/css/win_theme.css b/main/ui/src/main/resources/css/win_theme.css index 492b653cd..8f073556c 100644 --- a/main/ui/src/main/resources/css/win_theme.css +++ b/main/ui/src/main/resources/css/win_theme.css @@ -295,6 +295,20 @@ -fx-text-fill: -fx-text-background-color; } +/******************************************************************************* + * * + * Hyperlink * + * * + ******************************************************************************/ + +.hyperlink { + -fx-cursor: hand; + -fx-text-fill: #3399FF; +} +.hyperlink:hover { + -fx-underline: true; +} + /******************************************************************************* * * * Button & ToggleButton * diff --git a/main/ui/src/main/resources/fxml/welcome.fxml b/main/ui/src/main/resources/fxml/welcome.fxml index 4f4dad9e4..1e71b2b15 100644 --- a/main/ui/src/main/resources/fxml/welcome.fxml +++ b/main/ui/src/main/resources/fxml/welcome.fxml @@ -16,11 +16,13 @@ + -