added update notification

This commit is contained in:
Sebastian Stenzel
2015-03-14 12:34:11 +01:00
parent 3f8f0b1fa7
commit c7ecd612c9
14 changed files with 326 additions and 8 deletions

View File

@@ -18,8 +18,8 @@
<name>Cryptomator WebDAV and I/O module</name>
<properties>
<jetty.version>9.2.5.v20141112</jetty.version>
<jackrabbit.version>2.9.0</jackrabbit.version>
<jetty.version>9.2.10.v20150310</jetty.version>
<jackrabbit.version>2.9.1</jackrabbit.version>
<commons.transaction.version>1.2</commons.transaction.version>
<jta.version>1.1</jta.version>
</properties>

View File

@@ -39,6 +39,7 @@
<commons-collections.version>4.0</commons-collections.version>
<commons-lang3.version>3.3.2</commons-lang3.version>
<commons-codec.version>1.10</commons-codec.version>
<commons-httpclient.version>3.1</commons-httpclient.version>
<jackson-databind.version>2.4.4</jackson-databind.version>
<mockito.version>1.10.19</mockito.version>
</properties>
@@ -110,6 +111,12 @@
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<dependency>
<!-- org.apache.httpcomponents:httpclient is newer, but jackrabbit uses this version. We don't have a reason to upgrade -->
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>${commons-httpclient.version}</version>
</dependency>
<!-- Guava -->
<dependency>

View File

@@ -48,7 +48,11 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
</dependency>
<!-- DI -->
<dependency>
<groupId>com.google.inject</groupId>
@@ -78,6 +82,7 @@
<archive>
<manifestEntries>
<Main-Class>${exec.mainClass}</Main-Class>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>

View File

@@ -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;
}
}
}
}

View File

@@ -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<String> getSemVerComparator() {
return new SemVerComparator();
}
@Provides
@Singleton
ExecutorService getExec() {

View File

@@ -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<String> semVerComparator;
private final ExecutorService executor;
private ResourceBundle rb;
@Inject
public WelcomeController(Application app, @Named("SemVer") Comparator<String> 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<String, String> map = mapper.readValue(responseData, new TypeReference<HashMap<String, String>>() {
});
this.compareVersions(map);
}
} catch (IOException e) {
// no error handling required. Maybe next time the version check is successful.
}
}
private void compareVersions(final Map<String, String> 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");
}
}

View File

@@ -0,0 +1,34 @@
package org.cryptomator.ui.util;
import java.util.Comparator;
import org.apache.commons.lang3.StringUtils;
public class SemVerComparator implements Comparator<String> {
@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;
}
}

View File

@@ -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 *

View File

@@ -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 *

View File

@@ -16,11 +16,13 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.control.Hyperlink?>
<AnchorPane xmlns:fx="http://javafx.com/fxml" fx:controller="org.cryptomator.ui.controllers.WelcomeController">
<children>
<Label AnchorPane.leftAnchor="100.0" AnchorPane.topAnchor="20.0" style="-fx-font-size: 1.5em;" text="%welcome.welcomeLabel"/>
<Label AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="20.0" prefWidth="400.0" alignment="CENTER" style="-fx-font-size: 1.5em;" text="%welcome.welcomeLabel"/>
<Hyperlink AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="50.0" prefWidth="400.0" alignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" visible="false"/>
<ImageView fx:id="botImageView" AnchorPane.leftAnchor="100.0" AnchorPane.topAnchor="200.0" fitHeight="200.0" preserveRatio="true" smooth="false"/>

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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<String> 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")));
}
}