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 @@
+
-
+
+
diff --git a/main/ui/src/main/resources/localization.properties b/main/ui/src/main/resources/localization.properties
index a237ad885..58a27de67 100644
--- a/main/ui/src/main/resources/localization.properties
+++ b/main/ui/src/main/resources/localization.properties
@@ -18,6 +18,7 @@ main.addDirectory.contextMenu.open=Add existing vault
# welcome.fxml
welcome.welcomeLabel=Welcome to Cryptomator
welcome.addButtonInstructionLabel=Start by adding a new vault
+welcome.newVersionMessage=Version %s can be downloaded. This is %s.
# initialize.fxml
initialize.label.password=Password
diff --git a/main/ui/src/test/java/org/cryptomator/ui/MainApplicationTest.java b/main/ui/src/test/java/org/cryptomator/ui/MainApplicationTest.java
index affbe1c53..62a2faba3 100644
--- a/main/ui/src/test/java/org/cryptomator/ui/MainApplicationTest.java
+++ b/main/ui/src/test/java/org/cryptomator/ui/MainApplicationTest.java
@@ -1,12 +1,12 @@
package org.cryptomator.ui;
-import static org.junit.Assert.*;
-
import org.junit.Test;
public class MainApplicationTest {
+
@Test
public void testInjection() throws Exception {
new MainApplication();
}
+
}
diff --git a/main/ui/src/test/java/org/cryptomator/ui/test/GuiceJUnitRunner.java b/main/ui/src/test/java/org/cryptomator/ui/test/GuiceJUnitRunner.java
new file mode 100644
index 000000000..a10c6ee45
--- /dev/null
+++ b/main/ui/src/test/java/org/cryptomator/ui/test/GuiceJUnitRunner.java
@@ -0,0 +1,62 @@
+package org.cryptomator.ui.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+/**
+ * Taken from http://fabiostrozzi.eu/2011/03/27/junit-tests-easy-guice/
+ */
+public class GuiceJUnitRunner extends BlockJUnit4ClassRunner {
+ private final Injector injector;
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ public @interface GuiceModules {
+ Class>[] value();
+ }
+
+ @Override
+ public Object createTest() throws Exception {
+ Object obj = super.createTest();
+ injector.injectMembers(obj);
+ return obj;
+ }
+
+ public GuiceJUnitRunner(Class> klass) throws InitializationError {
+ super(klass);
+ Class>[] classes = getModulesFor(klass);
+ injector = createInjectorFor(classes);
+ }
+
+ private Injector createInjectorFor(Class>[] classes) throws InitializationError {
+ Module[] modules = new Module[classes.length];
+ for (int i = 0; i < classes.length; i++) {
+ try {
+ modules[i] = (Module) (classes[i]).newInstance();
+ } catch (InstantiationException e) {
+ throw new InitializationError(e);
+ } catch (IllegalAccessException e) {
+ throw new InitializationError(e);
+ }
+ }
+ return Guice.createInjector(modules);
+ }
+
+ private Class>[] getModulesFor(Class> klass) throws InitializationError {
+ GuiceModules annotation = klass.getAnnotation(GuiceModules.class);
+ if (annotation == null)
+ throw new InitializationError("Missing @GuiceModules annotation for unit test '" + klass.getName() + "'");
+ return annotation.value();
+ }
+}
\ No newline at end of file
diff --git a/main/ui/src/test/java/org/cryptomator/ui/util/SemVerComparatorTest.java b/main/ui/src/test/java/org/cryptomator/ui/util/SemVerComparatorTest.java
new file mode 100644
index 000000000..74b1b9508
--- /dev/null
+++ b/main/ui/src/test/java/org/cryptomator/ui/util/SemVerComparatorTest.java
@@ -0,0 +1,51 @@
+package org.cryptomator.ui.util;
+
+import java.util.Comparator;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.cryptomator.ui.MainModule;
+import org.cryptomator.ui.test.GuiceJUnitRunner;
+import org.cryptomator.ui.test.GuiceJUnitRunner.GuiceModules;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(GuiceJUnitRunner.class)
+@GuiceModules(MainModule.class)
+public class SemVerComparatorTest {
+
+ @Inject
+ @Named("SemVer")
+ private Comparator semVerComparator;
+
+ // equal versions
+
+ @Test
+ public void compareEqualVersions() {
+ final int comparisonResult = semVerComparator.compare("1.23.4", "1.23.4");
+ Assert.assertEquals(0, Integer.signum(comparisonResult));
+ }
+
+ // newer versions in first argument
+
+ @Test
+ public void compareHigherToLowerVersions() {
+ Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
+ Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
+ Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
+ Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4a", "1.23.4")));
+ }
+
+ // newer versions in second argument
+
+ @Test
+ public void compareLowerToHigherVersions() {
+ Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
+ Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
+ Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
+ Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4a")));
+ }
+
+}