mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 10:11:27 +00:00
Added new UpdateChecker that checks periodically (fixes #272) and added it to the main window (references #925)
This commit is contained in:
@@ -19,7 +19,7 @@ import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(includes = {KeychainModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class})
|
||||
@Module(includes = {KeychainModule.class, UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class})
|
||||
abstract class FxApplicationModule {
|
||||
|
||||
@Binds
|
||||
@@ -31,13 +31,13 @@ abstract class FxApplicationModule {
|
||||
static ObjectProperty<Vault> provideSelectedVault() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static ResourceBundle provideLocalization() {
|
||||
return ResourceBundle.getBundle("i18n.strings");
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
|
||||
return builder.build();
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.WorkerStateEvent;
|
||||
import javafx.util.Duration;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class UpdateChecker {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class);
|
||||
|
||||
private final Settings settings;
|
||||
private final Optional<String> applicationVersion;
|
||||
private final StringProperty latestVersionProperty;
|
||||
private final Comparator<String> semVerComparator;
|
||||
private final ScheduledService<String> updateCheckerService;
|
||||
|
||||
@Inject
|
||||
UpdateChecker(Settings settings, @Named("applicationVersion") Optional<String> applicationVersion, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
|
||||
this.settings = settings;
|
||||
this.applicationVersion = applicationVersion;
|
||||
this.latestVersionProperty = latestVersionProperty;
|
||||
this.semVerComparator = semVerComparator;
|
||||
this.updateCheckerService = updateCheckerService;
|
||||
}
|
||||
|
||||
public void startCheckingForUpdates(Duration initialDelay) {
|
||||
updateCheckerService.setDelay(initialDelay);
|
||||
updateCheckerService.setOnSucceeded(this::checkSucceeded);
|
||||
updateCheckerService.setOnFailed(this::checkFailed);
|
||||
updateCheckerService.restart();
|
||||
}
|
||||
|
||||
public ReadOnlyStringProperty latestVersionProperty() {
|
||||
return latestVersionProperty;
|
||||
}
|
||||
|
||||
private void checkSucceeded(WorkerStateEvent event) {
|
||||
String currentVersion = applicationVersion.orElse(null);
|
||||
String latestVersion = updateCheckerService.getValue();
|
||||
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
|
||||
|
||||
// TODO settings.lastVersionCheck = Instant.now()
|
||||
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
|
||||
// update is available!
|
||||
latestVersionProperty.set(latestVersion);
|
||||
} else {
|
||||
latestVersionProperty.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFailed(WorkerStateEvent event) {
|
||||
LOG.warn("Error checking for updates", event.getSource().getException());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@Module
|
||||
public abstract class UpdateCheckerModule {
|
||||
|
||||
private static final URI LATEST_VERSION_URI = URI.create("https://api.cryptomator.org/updates/latestVersion.json");
|
||||
private static final Duration UPDATE_CHECK_INTERVAL = Duration.hours(3);
|
||||
|
||||
@Provides
|
||||
@Named("latestVersion")
|
||||
@FxApplicationScoped
|
||||
static StringProperty provideLatestVersion() {
|
||||
return new SimpleStringProperty();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static HttpClient providesHttpClient() {
|
||||
return HttpClient.newHttpClient();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static HttpRequest providesCheckForUpdatesRequest(@Named("applicationVersion") Optional<String> applicationVersion) {
|
||||
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
return HttpRequest.newBuilder() //
|
||||
.uri(LATEST_VERSION_URI) //
|
||||
.header("User-Agent", userAgent)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static ScheduledService<String> provideCheckForUpdatesService(ExecutorService executor, HttpClient httpClient, HttpRequest checkForUpdatesRequest) {
|
||||
ScheduledService<String> service = new ScheduledService<>() {
|
||||
@Override
|
||||
protected Task<String> createTask() {
|
||||
return new UpdateCheckerTask(httpClient, checkForUpdatesRequest);
|
||||
}
|
||||
};
|
||||
service.setExecutor(executor);
|
||||
service.setPeriod(UPDATE_CHECK_INTERVAL);
|
||||
return service;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import javafx.concurrent.Task;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
public class UpdateCheckerTask extends Task<String> {
|
||||
|
||||
private static final long MAX_RESPONSE_SIZE = 10 * 1024; // 10kb should be sufficient. protect against flooding
|
||||
private static final Gson GSON = new GsonBuilder().setLenient().create();
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final HttpRequest checkForUpdatesRequest;
|
||||
|
||||
UpdateCheckerTask(HttpClient httpClient, HttpRequest checkForUpdatesRequest) {
|
||||
this.httpClient = httpClient;
|
||||
this.checkForUpdatesRequest = checkForUpdatesRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String call() throws Exception {
|
||||
HttpResponse<InputStream> response = httpClient.send(checkForUpdatesRequest, HttpResponse.BodyHandlers.ofInputStream());
|
||||
if (response.statusCode() == 200) {
|
||||
return processBody(response);
|
||||
} else {
|
||||
throw new IOException("Unexpected HTTP response code " + response.statusCode());
|
||||
}
|
||||
}
|
||||
|
||||
private String processBody(HttpResponse<InputStream> response) throws IOException {
|
||||
try (InputStream in = response.body(); //
|
||||
InputStream limitedIn = ByteStreams.limit(in, MAX_RESPONSE_SIZE); //
|
||||
Reader reader = new InputStreamReader(limitedIn, StandardCharsets.UTF_8)) {
|
||||
Map<String, String> map = GSON.fromJson(reader, new TypeToken<Map<String, String>>() {
|
||||
}.getType());
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
return map.get("mac");
|
||||
} else if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return map.get("win");
|
||||
} else if (SystemUtils.IS_OS_LINUX) {
|
||||
return map.get("linux");
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported operating system");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.cryptomator.ui.fxapp.FxApplication;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -17,20 +20,25 @@ import java.util.concurrent.CountDownLatch;
|
||||
public class MainWindowController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
|
||||
private static final Duration CHECK_FOR_UPDATES_DELAY = Duration.seconds(5);
|
||||
|
||||
private final Stage window;
|
||||
private final FxApplication application;
|
||||
private final boolean minimizeToSysTray;
|
||||
private final UpdateChecker updateChecker;
|
||||
private final BooleanBinding updateAvailable;
|
||||
public HBox titleBar;
|
||||
public Region resizer;
|
||||
private double xOffset;
|
||||
private double yOffset;
|
||||
|
||||
@Inject
|
||||
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray) {
|
||||
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker) {
|
||||
this.window = window;
|
||||
this.application = application;
|
||||
this.minimizeToSysTray = minimizeToSysTray;
|
||||
this.updateChecker = updateChecker;
|
||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -49,6 +57,7 @@ public class MainWindowController implements FxController {
|
||||
window.setWidth(event.getSceneX());
|
||||
window.setHeight(event.getSceneY());
|
||||
});
|
||||
updateChecker.startCheckingForUpdates(CHECK_FOR_UPDATES_DELAY);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -64,4 +73,14 @@ public class MainWindowController implements FxController {
|
||||
public void showPreferences() {
|
||||
application.showPreferencesWindow();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public BooleanBinding updateAvailableProperty() {
|
||||
return updateAvailable;
|
||||
}
|
||||
|
||||
public boolean isUpdateAvailable() {
|
||||
return updateAvailable.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class TrayMenuController {
|
||||
|
||||
// show window on start?
|
||||
if (!settings.startHidden().get()) {
|
||||
// TODO: schedule async to not delay tray menu initialization
|
||||
showMainWindow(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
|
||||
@@ -23,7 +24,11 @@
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences">
|
||||
<graphic>
|
||||
<FontAwesomeIconView styleClass="fa-icon" glyphName="COGS"/>
|
||||
<StackPane>
|
||||
<FontAwesomeIconView styleClass="fa-icon" glyphName="COGS"/>
|
||||
<!-- TODO style: -->
|
||||
<Circle visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" styleClass="update-indicator" fill="red" radius="4"/>
|
||||
</StackPane>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.settingsBtn.tooltip"/>
|
||||
|
||||
Reference in New Issue
Block a user