From 98bcf63b2cb9b4abf85ecb4c521d8db937964aad Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Wed, 17 Apr 2024 11:40:58 +0200 Subject: [PATCH] remove lastUpdateCheck from Settings; add lastUpdateReminder and lastSuccessfulUpdateCheck implement updateTimeDifferenceMessage reorder UI elements refactor code for clarity and maintainability --- .../cryptomator/common/settings/Settings.java | 33 +++++---------- .../common/settings/SettingsJson.java | 7 +++- .../cryptomator/ui/fxapp/UpdateChecker.java | 41 +++++++++++++++++-- .../UpdatesPreferencesController.java | 21 +++++++--- .../UpdateReminderComponent.java | 20 ++++----- .../UpdateReminderController.java | 1 - .../resources/fxml/preferences_updates.fxml | 8 +++- src/main/resources/i18n/strings.properties | 7 +++- 8 files changed, 85 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/cryptomator/common/settings/Settings.java b/src/main/java/org/cryptomator/common/settings/Settings.java index 019f87b2a..3932dcb72 100644 --- a/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/src/main/java/org/cryptomator/common/settings/Settings.java @@ -25,10 +25,6 @@ import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.NodeOrientation; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.function.Consumer; public class Settings { @@ -48,8 +44,8 @@ public class Settings { static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess"; static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name(); static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false; - public static final String DEFAULT_LAST_UPDATE_CHECK = "2000-01-01T10:00:00"; - + static final String DEFAULT_LAST_UPDATE_REMINDER = "2000-01-01"; + public static final String DEFAULT_LAST_SUCCESSFUL_UPDATE_CHECK = "2000-01-01T00:00:00"; public final ObservableList directories; public final BooleanProperty askedForUpdateCheck; public final BooleanProperty checkForUpdates; @@ -71,7 +67,8 @@ public class Settings { public final IntegerProperty windowHeight; public final StringProperty language; public final StringProperty mountService; - public final StringProperty lastUpdateCheck; + public final StringProperty lastUpdateReminder; + public final StringProperty lastSuccessfulUpdateCheck; public final StringProperty latestVersion; private Consumer saveCmd; @@ -109,7 +106,8 @@ public class Settings { this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight); this.language = new SimpleStringProperty(this, "language", json.language); this.mountService = new SimpleStringProperty(this, "mountService", json.mountService); - this.lastUpdateCheck = new SimpleStringProperty(this, "lastUpdateCheck", json.lastUpdateCheck); + this.lastUpdateReminder = new SimpleStringProperty(this, "lastUpdateReminder", json.lastUpdateReminder); + this.lastSuccessfulUpdateCheck = new SimpleStringProperty(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck); this.latestVersion = new SimpleStringProperty(this, "latestVersion", json.latestVersion); this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList()); @@ -137,7 +135,8 @@ public class Settings { windowHeight.addListener(this::somethingChanged); language.addListener(this::somethingChanged); mountService.addListener(this::somethingChanged); - lastUpdateCheck.addListener(this::somethingChanged); + lastUpdateReminder.addListener(this::somethingChanged); + lastSuccessfulUpdateCheck.addListener(this::somethingChanged); latestVersion.addListener(this::somethingChanged); } @@ -168,19 +167,6 @@ public class Settings { }); } - var dateTimeString = !lastUpdateCheck.get().isEmpty() ? lastUpdateCheck.get() : DEFAULT_LAST_UPDATE_CHECK; - try { - LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_DATE_TIME); - lastUpdateCheck.set(dateTime.toString()); - } catch (DateTimeParseException e) { - try { - LocalDate date = LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_DATE); - lastUpdateCheck.set(LocalDateTime.of(date, LocalDate.MIN.atStartOfDay().toLocalTime()).toString()); - } catch (DateTimeParseException ex) { - LOG.error("The date/time format is invalid:" + dateTimeString, ex); - } - } - } SettingsJson serialized() { @@ -206,7 +192,8 @@ public class Settings { json.windowHeight = windowHeight.get(); json.language = language.get(); json.mountService = mountService.get(); - json.lastUpdateCheck = lastUpdateCheck.get(); + json.lastUpdateReminder = lastUpdateReminder.get(); + json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.get(); json.latestVersion = latestVersion.get(); return json; } diff --git a/src/main/java/org/cryptomator/common/settings/SettingsJson.java b/src/main/java/org/cryptomator/common/settings/SettingsJson.java index 2dbb514c0..4337089ae 100644 --- a/src/main/java/org/cryptomator/common/settings/SettingsJson.java +++ b/src/main/java/org/cryptomator/common/settings/SettingsJson.java @@ -80,8 +80,11 @@ class SettingsJson { @JsonProperty(value = "preferredVolumeImpl", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233 String preferredVolumeImpl; - @JsonProperty("lastUpdateCheck") - String lastUpdateCheck = Settings.DEFAULT_LAST_UPDATE_CHECK; + @JsonProperty("lastUpdateReminder") + String lastUpdateReminder = Settings.DEFAULT_LAST_UPDATE_REMINDER; + + @JsonProperty("lastSuccessfulUpdateCheck") + String lastSuccessfulUpdateCheck = Settings.DEFAULT_LAST_SUCCESSFUL_UPDATE_CHECK; @JsonProperty("latestVersion") String latestVersion; diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java index a4af88b93..f9bb65719 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java @@ -20,6 +20,7 @@ import javafx.concurrent.WorkerStateEvent; import javafx.util.Duration; import java.time.LocalDateTime; import java.util.Comparator; +import java.util.ResourceBundle; @FxApplicationScoped public class UpdateChecker { @@ -29,29 +30,36 @@ public class UpdateChecker { private final Environment env; private final Settings settings; + private final ResourceBundle resourceBundle; private final StringProperty latestVersionProperty = new SimpleStringProperty(); private final ScheduledService updateCheckerService; private final ObjectProperty state = new SimpleObjectProperty<>(UpdateCheckState.NOT_CHECKED); private final ObjectProperty updateCheckTimeProperty = new SimpleObjectProperty<>(); + private final StringProperty timeDifferenceMessageProperty = new SimpleStringProperty(); private final Comparator versionComparator = new SemVerComparator(); private final BooleanBinding updateAvailable; @Inject UpdateChecker(Settings settings, // Environment env, // + ResourceBundle resourceBundle, // ScheduledService updateCheckerService) { this.env = env; this.settings = settings; + this.resourceBundle = resourceBundle; this.updateCheckerService = updateCheckerService; this.latestVersionProperty.set(settings.latestVersion.get()); - this.updateCheckTimeProperty.set(LocalDateTime.parse(settings.lastUpdateCheck.get())); + this.updateCheckTimeProperty.set(LocalDateTime.parse(settings.lastSuccessfulUpdateCheck.get())); + this.updateAvailable = Bindings.createBooleanBinding(() -> { var latestVersion = latestVersionProperty.get(); return latestVersion != null && versionComparator.compare(getCurrentVersion(), latestVersion) < 0; }, latestVersionProperty); + updateTimeDifferenceMessage(); + this.latestVersionProperty.addListener((_, _, newValue) -> settings.latestVersion.set(newValue)); - this.updateCheckTimeProperty.addListener((_, _, newValue) -> settings.lastUpdateCheck.set(newValue.toString())); + this.updateCheckTimeProperty.addListener((_, _, newValue) -> settings.lastSuccessfulUpdateCheck.set(newValue.toString())); } public void automaticallyCheckForUpdatesIfEnabled() { @@ -74,6 +82,27 @@ public class UpdateChecker { updateCheckerService.start(); } + private void updateTimeDifferenceMessage() { + LocalDateTime updateCheckDate = updateCheckTimeProperty.get(); + if (updateCheckDate == null || updateCheckDate.equals(LocalDateTime.parse(Settings.DEFAULT_LAST_SUCCESSFUL_UPDATE_CHECK))) { + timeDifferenceMessageProperty.set(resourceBundle.getString("preferences.updates.lastUpdateCheck.never")); + return; + } + + var duration = java.time.Duration.between(updateCheckDate, LocalDateTime.now()); + + var hours = duration.toHours(); + var days = duration.toDays(); + + if (hours < 1) { + timeDifferenceMessageProperty.set(resourceBundle.getString("preferences.updates.lastUpdateCheck.recently")); + } else if (hours < 24) { + timeDifferenceMessageProperty.set(String.format(resourceBundle.getString("preferences.updates.lastUpdateCheck.hoursAgo"), hours)); + } else { + timeDifferenceMessageProperty.set(String.format(resourceBundle.getString("preferences.updates.lastUpdateCheck.daysAgo"), days)); + } + } + private void checkStarted(WorkerStateEvent event) { LOG.debug("Checking for updates..."); state.set(UpdateCheckState.IS_CHECKING); @@ -83,13 +112,14 @@ public class UpdateChecker { String latestVersion = updateCheckerService.getValue(); LOG.info("Current version: {}, latest version: {}", getCurrentVersion(), latestVersion); updateCheckTimeProperty.set(LocalDateTime.now()); + updateTimeDifferenceMessage(); latestVersionProperty.set(latestVersion); state.set(UpdateCheckState.CHECK_SUCCESSFUL); } private void checkFailed(WorkerStateEvent event) { - LOG.warn("Error checking for updates", event.getSource().getException()); state.set(UpdateCheckState.CHECK_FAILED); + LOG.warn("Error checking for updates", event.getSource().getException()); } public enum UpdateCheckState { @@ -108,7 +138,6 @@ public class UpdateChecker { return latestVersionProperty; } - public BooleanBinding updateAvailableProperty(){ return updateAvailable; } @@ -120,6 +149,10 @@ public class UpdateChecker { return updateCheckTimeProperty; } + public StringProperty timeDifferenceMessageProperty() { + return timeDifferenceMessageProperty; + } + public ObjectProperty updateCheckStateProperty() { return state; } diff --git a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java index 4c1a0d315..cc70fd66d 100644 --- a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java @@ -39,6 +39,7 @@ public class UpdatesPreferencesController implements FxController { private final ObjectBinding checkForUpdatesButtonState; private final ReadOnlyStringProperty latestVersion; private final ObjectProperty updateCheckDate; + private final ReadOnlyStringProperty timeDifferenceMessage; private final String currentVersion; private final BooleanBinding updateAvailable; private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false); @@ -58,6 +59,7 @@ public class UpdatesPreferencesController implements FxController { this.checkForUpdatesButtonState = Bindings.when(updateChecker.checkingForUpdatesProperty()).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY); this.latestVersion = updateChecker.latestVersionProperty(); this.updateCheckDate = updateChecker.updateCheckTimeProperty(); + this.timeDifferenceMessage = updateChecker.timeDifferenceMessageProperty(); this.currentVersion = updateChecker.getCurrentVersion(); this.updateAvailable = updateChecker.updateAvailableProperty(); this.updateCheckStateProperty = updateChecker.updateCheckStateProperty(); @@ -68,12 +70,11 @@ public class UpdatesPreferencesController implements FxController { BooleanBinding isUpdateSuccessfulAndCurrent = updateCheckStateProperty.isEqualTo(UpdateChecker.UpdateCheckState.CHECK_SUCCESSFUL).and(latestVersion.isEqualTo(currentVersion)); - updateCheckStateProperty.addListener((_, _, _) -> { if (isUpdateSuccessfulAndCurrent.get()) { - upToDateLabelVisibleProperty().set(true); + upToDateLabelVisible.set(true); PauseTransition delay = new PauseTransition(Duration.seconds(5)); - delay.setOnFinished(_ -> upToDateLabelVisibleProperty().set(false)); + delay.setOnFinished(_ -> upToDateLabelVisible.set(false)); delay.play(); } }); @@ -122,15 +123,23 @@ public class UpdatesPreferencesController implements FxController { public String getUpdateCheckDate() { DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault()); - return !updateCheckDate.get().equals(LocalDateTime.parse(Settings.DEFAULT_LAST_UPDATE_CHECK)) ? updateCheckDate.get().format(formatter) : "-"; + return !updateCheckDate.get().equals(LocalDateTime.parse(Settings.DEFAULT_LAST_SUCCESSFUL_UPDATE_CHECK)) ? updateCheckDate.get().format(formatter) : "-"; + } + + public ReadOnlyStringProperty timeDifferenceMessageProperty(){ + return timeDifferenceMessage; + } + + public String getTimeDifferenceMessage() { + return timeDifferenceMessage.get(); } public BooleanProperty upToDateLabelVisibleProperty() { return upToDateLabelVisible; } - public final boolean isUpToDateLabelVisible() { - return upToDateLabelVisibleProperty().get(); + public boolean isUpToDateLabelVisible() { + return upToDateLabelVisible.get(); } public BooleanBinding updateAvailableProperty() { diff --git a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java index 858c603a1..319a3e152 100644 --- a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java +++ b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java @@ -10,9 +10,7 @@ import org.slf4j.LoggerFactory; import javafx.scene.Scene; import javafx.stage.Stage; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; +import java.time.LocalDate; @UpdateReminderScoped @Subcomponent(modules = {UpdateReminderModule.class}) @@ -29,16 +27,12 @@ public interface UpdateReminderComponent { Settings settings(); default void checkAndShowUpdateReminderWindow() { - try { - var dateTime = LocalDateTime.parse(settings().lastUpdateCheck.get(), DateTimeFormatter.ISO_DATE_TIME); - if (dateTime.isBefore(LocalDateTime.now().minusDays(14)) && !settings().checkForUpdates.getValue()) { - Stage stage = window(); - stage.setScene(updateReminderScene().get()); - stage.sizeToScene(); - stage.show(); - } - } catch (DateTimeParseException e) { - LOG.error("Failed to parse last update check time '{}':", settings().lastUpdateCheck.get(), e); + if (LocalDate.parse(settings().lastUpdateReminder.get()).isBefore(LocalDate.now().minusDays(14)) && !settings().checkForUpdates.getValue()) { + settings().lastUpdateReminder.set(LocalDate.now().toString()); + Stage stage = window(); + stage.setScene(updateReminderScene().get()); + stage.sizeToScene(); + stage.show(); } } diff --git a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java index bd70115e3..4f657d706 100644 --- a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java +++ b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java @@ -28,7 +28,6 @@ public class UpdateReminderController implements FxController { @FXML public void cancel() { - updateChecker.updateCheckTimeProperty().set(LocalDateTime.now()); window.close(); } diff --git a/src/main/resources/fxml/preferences_updates.fxml b/src/main/resources/fxml/preferences_updates.fxml index f7b5383e0..398800e49 100644 --- a/src/main/resources/fxml/preferences_updates.fxml +++ b/src/main/resources/fxml/preferences_updates.fxml @@ -11,6 +11,7 @@ + - - @@ -42,6 +41,11 @@ + + + + +