/*******************************************************************************
* Copyright (c) 2014, 2017 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.controllers;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.util.AsyncTaskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.VBox;
@Singleton
public class WelcomeController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
private final Application app;
private final Optional<String> applicationVersion;
private final Localization localization;
private final Settings settings;
private final Comparator<String> semVerComparator;
private final AsyncTaskService asyncTaskService;
@Inject
public WelcomeController(Application app, @Named("applicationVersion") Optional<String> applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator,
AsyncTaskService asyncTaskService) {
this.app = app;
this.applicationVersion = applicationVersion;
this.localization = localization;
this.settings = settings;
this.semVerComparator = semVerComparator;
this.asyncTaskService = asyncTaskService;
}
@FXML
private Node checkForUpdatesContainer;
@FXML
private Label checkForUpdatesStatus;
@FXML
private ProgressIndicator checkForUpdatesIndicator;
@FXML
private Hyperlink updateLink;
@FXML
private VBox root;
@Override
public void initialize(URL location, ResourceBundle resources) {
if (areUpdatesManagedExternally()) {
checkForUpdatesContainer.setVisible(false);
} else if (settings.checkForUpdates().get()) {
this.checkForUpdates();
}
}
@Override
public Parent getRoot() {
return root;
}
// ****************************************
// Check for updates
// ****************************************
private boolean areUpdatesManagedExternally() {
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
}
private void checkForUpdates() {
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
checkForUpdatesIndicator.setVisible(true);
asyncTaskService.asyncTaskOf(() -> {
RequestConfig requestConfig = RequestConfig.custom() //
.setConnectTimeout(5000) //
.setConnectionRequestTimeout(5000) //
.setSocketTimeout(5000) //
.build();
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
HttpClientBuilder httpClientBuilder = HttpClients.custom() //
.disableCookieManagement() //
.setDefaultRequestConfig(requestConfig) //
.setUserAgent(userAgent);
LOG.debug("Checking for updates...");
try (CloseableHttpClient client = httpClientBuilder.build()) {
HttpGet request = new HttpGet("https://api.cryptomator.org/updates/latestVersion.json");
try (CloseableHttpResponse response = client.execute(request)) {
if (response.getStatusLine().getStatusCode() == 200 && response.getEntity() != null) {
try (InputStream in = response.getEntity().getContent()) {
Gson gson = new GsonBuilder().setLenient().create();
Reader utf8Reader = new InputStreamReader(in, StandardCharsets.UTF_8);
Map<String, String> map = gson.fromJson(utf8Reader, new TypeToken<Map<String, String>>() {
}.getType());
if (map != null) {
this.compareVersions(map);
}
}
}
}
}
}).andFinally(() -> {
checkForUpdatesStatus.setText("");
checkForUpdatesIndicator.setVisible(false);
}).run();
}
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 = applicationVersion.orElse(null);
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion);
Platform.runLater(() -> {
this.updateLink.setText(msg);
this.updateLink.setVisible(true);
this.updateLink.setDisable(false);
});
}
}
@FXML
public void didClickUpdateLink(ActionEvent event) {
app.getHostServices().showDocument("https://cryptomator.org/");
}
}