diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 4f4682e5a..5d0dc2d5b 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -11,7 +11,7 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '21.0.2+13' + JAVA_VERSION: '22.0.1+8' jobs: get-version: @@ -80,7 +80,7 @@ jobs: --verbose --output runtime --module-path "${JAVA_HOME}/jmods:openjfx-jmods" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net --strip-native-commands --no-header-files --no-man-pages diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3559e5fa2..c38bb49e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: 21 + JAVA_VERSION: 22 defaults: run: diff --git a/.github/workflows/check-jdk-updates.yml b/.github/workflows/check-jdk-updates.yml index c628571e8..c73546994 100644 --- a/.github/workflows/check-jdk-updates.yml +++ b/.github/workflows/check-jdk-updates.yml @@ -5,7 +5,7 @@ on: - cron: '0 0 1 * *' # run once a month at the first day of month env: - JDK_VERSION: '21.0.2+13' + JDK_VERSION: '22.0.1+8' JDK_VENDOR: zulu jobs: diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index dd7d2adf0..9c220843b 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -17,9 +17,9 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '21.0.2+13' - COFFEELIBS_JDK: 21 - COFFEELIBS_JDK_VERSION: '21.0.2+13-0ppa1' + JAVA_VERSION: '22.0.1+8' + COFFEELIBS_JDK: 22 + COFFEELIBS_JDK_VERSION: '22.0.1+8-0ppa1' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64_HASH: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8' OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip' diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index 21fed1912..02633a334 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -11,7 +11,7 @@ jobs: with: runner-os: 'ubuntu-latest' java-distribution: 'temurin' - java-version: 21 + java-version: 22 secrets: nvd-api-key: ${{ secrets.NVD_API_KEY }} slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 3dab1ce8b..7dc22462f 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -23,7 +23,7 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: 21 + JAVA_VERSION: 22 jobs: determine-version: diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 82e7ba728..7e6bde81b 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -16,7 +16,7 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '21.0.2+13' + JAVA_VERSION: '22.0.1+8' jobs: get-version: @@ -91,7 +91,7 @@ jobs: --verbose --output runtime --module-path "${JAVA_HOME}/jmods:openjfx-jmods" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr --strip-native-commands --no-header-files --no-man-pages diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 2730f5c24..4db444da5 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -5,7 +5,7 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: 21 + JAVA_VERSION: 22 defaults: run: diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index e5b977d79..160da33e9 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -12,7 +12,7 @@ defaults: env: JAVA_DIST: 'zulu' - JAVA_VERSION: 21 + JAVA_VERSION: 22 jobs: check-preconditions: diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 37e89e427..b6abcf0ec 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -16,7 +16,7 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '21.0.2+13' + JAVA_VERSION: '22.0.1+8' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_windows-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64_HASH: 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a' WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi' @@ -89,7 +89,7 @@ jobs: --verbose --output runtime --module-path "jfxjmods;${JAVA_HOME}/jmods" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr --strip-native-commands --no-header-files --no-man-pages diff --git a/.idea/misc.xml b/.idea/misc.xml index ee700a1c2..3bcfa174b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/dist/linux/appimage/.gitignore b/dist/linux/appimage/.gitignore index 1ed40c771..3e0cd2a39 100644 --- a/dist/linux/appimage/.gitignore +++ b/dist/linux/appimage/.gitignore @@ -1,4 +1,6 @@ -# created during build +# downloaded/created during build +openjfx-jmods.zip +*.jmod Cryptomator.AppDir *.AppImage *.AppImage.zsync \ No newline at end of file diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index a8775292a..091e66c00 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -56,7 +56,7 @@ ${JAVA_HOME}/bin/jlink \ --verbose \ --output runtime \ --module-path "${JAVA_HOME}/jmods:openjfx-jmods" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \ --strip-native-commands \ --no-header-files \ --no-man-pages \ diff --git a/dist/linux/debian/control b/dist/linux/debian/control index 86b397101..2a9bbae69 100644 --- a/dist/linux/debian/control +++ b/dist/linux/debian/control @@ -2,7 +2,7 @@ Source: cryptomator Maintainer: Cryptobot Section: utils Priority: optional -Build-Depends: debhelper (>=10), coffeelibs-jdk-21 (>= 21.0.2+12-0ppa1), libgtk-3-0, libxxf86vm1, libgl1 +Build-Depends: debhelper (>=10), coffeelibs-jdk-22 (>= 22.0.1+8-0ppa1), libgtk-3-0, libxxf86vm1, libgl1 Standards-Version: 4.5.0 Homepage: https://cryptomator.org Vcs-Git: https://github.com/cryptomator/cryptomator.git diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index 11650598d..1de6f7f6b 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -4,7 +4,7 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -JAVA_HOME = /usr/lib/jvm/java-21-coffeelibs +JAVA_HOME = /usr/lib/jvm/java-22-coffeelibs DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH) ifeq ($(DEB_BUILD_ARCH),amd64) JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods @@ -28,7 +28,7 @@ override_dh_auto_build: $(JAVA_HOME)/bin/jlink \ --output runtime \ --module-path "${JMODS_PATH}" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \ --strip-native-commands \ --no-header-files \ --no-man-pages \ diff --git a/dist/mac/dmg/.gitignore b/dist/mac/dmg/.gitignore index cdc73d89b..ebc5e2c5e 100644 --- a/dist/mac/dmg/.gitignore +++ b/dist/mac/dmg/.gitignore @@ -1,6 +1,8 @@ -# created during build +# downloaded/created during build Cryptomator.app/ runtime/ dmg/ *.dmg -license.rtf \ No newline at end of file +license.rtf +openjfx-jmods.zip +*.jmod \ No newline at end of file diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index df699aad1..ce2380289 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -71,7 +71,7 @@ cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods:openjfx-jmods" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr \ --strip-native-commands \ --no-header-files \ --no-man-pages \ diff --git a/dist/win/.gitignore b/dist/win/.gitignore index 32316fd59..7b2faa4b5 100644 --- a/dist/win/.gitignore +++ b/dist/win/.gitignore @@ -6,4 +6,5 @@ installer *.msi *.exe *.jmod +resources/jfxJmods.zip license.rtf \ No newline at end of file diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index d48f2bce3..117c0afb8 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -74,7 +74,7 @@ Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\ja --verbose ` --output runtime ` --module-path "$Env:JAVA_HOME/jmods;$buildDir/resources/javafx-jmods" ` - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` --strip-native-commands ` --no-header-files ` --no-man-pages ` diff --git a/dist/win/contrib/version170-migrate-settings.bat b/dist/win/contrib/version170-migrate-settings.bat deleted file mode 100644 index d94e062a1..000000000 --- a/dist/win/contrib/version170-migrate-settings.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -:: see comments in file ./version170-migrate-settings.ps1 - -cd %~dp0 -powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command .\version170-migrate-settings.ps1 diff --git a/dist/win/contrib/version170-migrate-settings.ps1 b/dist/win/contrib/version170-migrate-settings.ps1 deleted file mode 100644 index 8e019fe66..000000000 --- a/dist/win/contrib/version170-migrate-settings.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -# This script migrates Cryptomator settings for all local users on Windows in case the users uses custom directories as mountpoint -# See also https://github.com/cryptomator/cryptomator/pull/2654. -# -# TODO: This script should be evaluated in a yearly interval if it is still needed and if not, should be removed -# -#Requires -RunAsAdministrator - -#Get all active, local user profiles -$profileList = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -Get-ChildItem $profileList | ForEach-Object { - $profilePath = $_.GetValue("ProfileImagePath") - $settingsPath = "$profilePath\AppData\Roaming\Cryptomator\settings.json" - if(!(Test-Path -Path $settingsPath -PathType Leaf)) { - #No settings file, nothing to do. - return; - } - $settings = Get-Content -Path $settingsPath | ConvertFrom-Json - if($settings.preferredVolumeImpl -ne "FUSE") { - #Fuse not used, nothing to do - return; - } - - #check if customMountPoints are used - $atLeastOneCustomPath = $false; - foreach ($vault in $settings.directories){ - $atLeastOneCustomPath = $atLeastOneCustomPath -or ($vault.useCustomMountPath -eq "True") - } - - #if so, use WinFsp Local Drive - if( $atLeastOneCustomPath ) { - Add-Member -Force -InputObject $settings -Name "mountService" -Value "org.cryptomator.frontend.fuse.mount.WinFspMountProvider" -MemberType NoteProperty - $newSettings = $settings | Select-Object * -ExcludeProperty "preferredVolumeImpl" - ConvertTo-Json $newSettings | Set-Content -Path $settingsPath - } -} diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs index 2fe2eb348..335dd3511 100644 --- a/dist/win/resources/main.wxs +++ b/dist/win/resources/main.wxs @@ -139,11 +139,6 @@ Sequence="execute" Before="PatchWebDAV" /> - - - - NOT Installed OR REINSTALL - NOT Installed OR REINSTALL diff --git a/pom.xml b/pom.xml index e6b8d527d..e9cb5cc7c 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ UTF-8 - 21 + 22 @@ -37,23 +37,23 @@ 1.3.1 1.2.5 1.2.3 - 1.4.4 - 4.0.0 + 1.4.5 + 5.0.0 2.0.6 3.14.0 - 2.51 + 2.51.1 2.2 - 33.0.0-jre - 2.16.2 + 33.2.1-jre + 2.17.1 21.0.1 4.4.0 9.37.3 - 1.5.3 - 2.0.12 + 1.5.6 + 2.0.13 0.8.0 - 1.8.2 + 1.9.0 5.10.2 @@ -62,7 +62,7 @@ 24.1.0 - 9.1.0 + 9.2.0 0.8.12 2.4.0 1.2.1 @@ -70,11 +70,16 @@ 3.3.1 3.6.1 3.2.5 - 3.3.0 + 3.4.1 + + org.cryptomator + cryptolib + 2.2.0 + org.cryptomator cryptofs @@ -158,11 +163,18 @@ nimbus-jose-jwt ${nimbus-jose.version} + + com.fasterxml.jackson.core jackson-databind ${jackson.version} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4d956c180..cae32b384 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -31,13 +31,13 @@ open module org.cryptomator.desktop { requires javafx.graphics; requires javafx.controls; requires javafx.fxml; - requires jdk.crypto.ec; // 3rd party: requires ch.qos.logback.classic; requires ch.qos.logback.core; requires com.auth0.jwt; requires com.google.common; requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.datatype.jsr310; requires com.nimbusds.jose.jwt; requires com.nulabinc.zxcvbn; requires com.tobiasdiez.easybind; diff --git a/src/main/java/org/cryptomator/common/SubstitutingProperties.java b/src/main/java/org/cryptomator/common/SubstitutingProperties.java index 8ba98a2b6..0536e3554 100644 --- a/src/main/java/org/cryptomator/common/SubstitutingProperties.java +++ b/src/main/java/org/cryptomator/common/SubstitutingProperties.java @@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory; import java.util.Map; import java.util.Properties; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class SubstitutingProperties extends PropertiesDecorator { @@ -58,7 +59,7 @@ public class SubstitutingProperties extends PropertiesDecorator { LoggerFactory.getLogger(SubstitutingProperties.class).warn("Variable {} used for substitution not found in {}. Replaced with empty string.", key, src); return ""; } else { - return val.replace("\\", "\\\\"); + return Matcher.quoteReplacement(val); } } diff --git a/src/main/java/org/cryptomator/common/settings/Settings.java b/src/main/java/org/cryptomator/common/settings/Settings.java index 4e0e0df97..a54e71a4f 100644 --- a/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/src/main/java/org/cryptomator/common/settings/Settings.java @@ -25,6 +25,7 @@ import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.NodeOrientation; +import java.time.Instant; import java.util.function.Consumer; public class Settings { @@ -44,8 +45,7 @@ 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; - static final String DEFAULT_LAST_UPDATE_CHECK = "2000-01-01"; - + public static final Instant DEFAULT_TIMESTAMP = Instant.parse("2000-01-01T00:00:00Z"); public final ObservableList directories; public final BooleanProperty askedForUpdateCheck; public final BooleanProperty checkForUpdates; @@ -67,7 +67,7 @@ public class Settings { public final IntegerProperty windowHeight; public final StringProperty language; public final StringProperty mountService; - public final StringProperty lastUpdateCheck; + public final ObjectProperty lastSuccessfulUpdateCheck; private Consumer saveCmd; @@ -104,7 +104,7 @@ 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.lastSuccessfulUpdateCheck = new SimpleObjectProperty<>(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck); this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList()); @@ -131,7 +131,7 @@ public class Settings { windowHeight.addListener(this::somethingChanged); language.addListener(this::somethingChanged); mountService.addListener(this::somethingChanged); - lastUpdateCheck.addListener(this::somethingChanged); + lastSuccessfulUpdateCheck.addListener(this::somethingChanged); } @SuppressWarnings("deprecation") @@ -185,7 +185,7 @@ public class Settings { json.windowHeight = windowHeight.get(); json.language = language.get(); json.mountService = mountService.get(); - json.lastUpdateCheck = lastUpdateCheck.get(); + json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.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 2c7c963da..2ded82885 100644 --- a/src/main/java/org/cryptomator/common/settings/SettingsJson.java +++ b/src/main/java/org/cryptomator/common/settings/SettingsJson.java @@ -1,9 +1,11 @@ package org.cryptomator.common.settings; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) @@ -80,7 +82,8 @@ 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("lastSuccessfulUpdateCheck") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + Instant lastSuccessfulUpdateCheck = Settings.DEFAULT_TIMESTAMP; } diff --git a/src/main/java/org/cryptomator/common/settings/SettingsProvider.java b/src/main/java/org/cryptomator/common/settings/SettingsProvider.java index 586708ed1..a33b51027 100644 --- a/src/main/java/org/cryptomator/common/settings/SettingsProvider.java +++ b/src/main/java/org/cryptomator/common/settings/SettingsProvider.java @@ -10,6 +10,7 @@ package org.cryptomator.common.settings; import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.google.common.base.Suppliers; import org.cryptomator.common.Environment; import org.slf4j.Logger; @@ -36,7 +37,7 @@ import java.util.stream.Stream; @Singleton public class SettingsProvider implements Supplier { - private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true); + private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true).registerModule(new JavaTimeModule()); private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class); private static final long SAVE_DELAY_MS = 1000; diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java index 709eb2fe7..b857adcae 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java @@ -1,45 +1,57 @@ package org.cryptomator.ui.fxapp; import org.cryptomator.common.Environment; +import org.cryptomator.common.SemVerComparator; import org.cryptomator.common.settings.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javax.inject.Named; +import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.concurrent.ScheduledService; import javafx.concurrent.Worker; import javafx.concurrent.WorkerStateEvent; import javafx.util.Duration; +import java.time.Instant; import java.util.Comparator; @FxApplicationScoped public class UpdateChecker { private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class); - private static final Duration AUTOCHECK_DELAY = Duration.seconds(5); + private static final Duration AUTO_CHECK_DELAY = Duration.seconds(5); private final Environment env; private final Settings settings; - private final StringProperty latestVersionProperty; - private final Comparator semVerComparator; + private final StringProperty latestVersion = new SimpleStringProperty(); private final ScheduledService updateCheckerService; + private final ObjectProperty state = new SimpleObjectProperty<>(UpdateCheckState.NOT_CHECKED); + private final ObjectProperty lastSuccessfulUpdateCheck; + private final Comparator versionComparator = new SemVerComparator(); + private final BooleanBinding updateAvailable; + private final BooleanBinding checkFailed; @Inject - UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator semVerComparator, ScheduledService updateCheckerService) { + UpdateChecker(Settings settings, // + Environment env, // + ScheduledService updateCheckerService) { this.env = env; this.settings = settings; - this.latestVersionProperty = latestVersionProperty; - this.semVerComparator = semVerComparator; this.updateCheckerService = updateCheckerService; + this.lastSuccessfulUpdateCheck = settings.lastSuccessfulUpdateCheck; + this.updateAvailable = Bindings.createBooleanBinding(this::isUpdateAvailable, latestVersion); + this.checkFailed = Bindings.equal(UpdateCheckState.CHECK_FAILED, state); } public void automaticallyCheckForUpdatesIfEnabled() { if (!env.disableUpdateCheck() && settings.checkForUpdates.get()) { - startCheckingForUpdates(AUTOCHECK_DELAY); + startCheckingForUpdates(AUTO_CHECK_DELAY); } } @@ -59,36 +71,65 @@ public class UpdateChecker { private void checkStarted(WorkerStateEvent event) { LOG.debug("Checking for updates..."); + state.set(UpdateCheckState.IS_CHECKING); } private void checkSucceeded(WorkerStateEvent event) { - String latestVersion = updateCheckerService.getValue(); - LOG.info("Current version: {}, lastest version: {}", getCurrentVersion(), latestVersion); - - if (semVerComparator.compare(getCurrentVersion(), latestVersion) < 0) { - // update is available - latestVersionProperty.set(latestVersion); - } else { - latestVersionProperty.set(null); - } + var latestVersionString = updateCheckerService.getValue(); + LOG.info("Current version: {}, latest version: {}", getCurrentVersion(), latestVersionString); + lastSuccessfulUpdateCheck.set(Instant.now()); + latestVersion.set(latestVersionString); + state.set(UpdateCheckState.CHECK_SUCCESSFUL); } private void checkFailed(WorkerStateEvent event) { - LOG.warn("Error checking for updates", event.getSource().getException()); + state.set(UpdateCheckState.CHECK_FAILED); + } + + public enum UpdateCheckState { + NOT_CHECKED, + IS_CHECKING, + CHECK_SUCCESSFUL, + CHECK_FAILED; } /* Observable Properties */ - public BooleanBinding checkingForUpdatesProperty() { return updateCheckerService.stateProperty().isEqualTo(Worker.State.RUNNING); } public ReadOnlyStringProperty latestVersionProperty() { - return latestVersionProperty; + return latestVersion; + } + + public BooleanBinding updateAvailableProperty() { + return updateAvailable; + } + + public BooleanBinding checkFailedProperty() { + return checkFailed; + } + + public boolean isUpdateAvailable() { + String currentVersion = getCurrentVersion(); + String latestVersionString = latestVersion.get(); + + if (currentVersion == null || latestVersionString == null) { + return false; + } else { + return versionComparator.compare(currentVersion, latestVersionString) < 0; + } + } + + public ObjectProperty lastSuccessfulUpdateCheckProperty() { + return lastSuccessfulUpdateCheck; + } + + public ObjectProperty updateCheckStateProperty() { + return state; } public String getCurrentVersion() { return env.getAppVersion(); } - } diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java index b5f06d7e5..585180662 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java @@ -11,8 +11,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Named; import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; import javafx.concurrent.ScheduledService; import javafx.concurrent.Task; import javafx.util.Duration; @@ -32,13 +30,6 @@ public abstract class UpdateCheckerModule { private static final Duration UPDATE_CHECK_INTERVAL = Duration.hours(3); private static final Duration DISABLED_UPDATE_CHECK_INTERVAL = Duration.hours(100000); // Duration.INDEFINITE leads to overflows... - @Provides - @Named("latestVersion") - @FxApplicationScoped - static StringProperty provideLatestVersion() { - return new SimpleStringProperty(); - } - @Provides @FxApplicationScoped static Optional provideHttpClient() { diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java index 479a2d860..f3c92790d 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java @@ -46,7 +46,7 @@ public class MainWindowTitleController implements FxController { this.appWindows = appWindows; this.trayMenuInitialized = trayMenu.isInitialized(); this.updateChecker = updateChecker; - this.updateAvailable = updateChecker.latestVersionProperty().isNotNull(); + this.updateAvailable = updateChecker.updateAvailableProperty(); this.licenseHolder = licenseHolder; this.settings = settings; this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton, settings.showTrayIcon); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index 644f361cb..e8ea21fe4 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -7,6 +7,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptofs.DirStructure; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.VaultService; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.slf4j.Logger; @@ -58,6 +59,7 @@ public class VaultListController implements FxController { private final Stage mainWindow; private final ObservableList vaults; + private final VaultService vaultService; private final ObjectProperty selectedVault; private final VaultListCellFactory cellFactory; private final AddVaultWizardComponent.Builder addVaultWizard; @@ -79,6 +81,7 @@ public class VaultListController implements FxController { ObservableList vaults, // ObjectProperty selectedVault, // VaultListCellFactory cellFactory, // + VaultService vaultService, // AddVaultWizardComponent.Builder addVaultWizard, // RemoveVaultComponent.Builder removeVaultDialogue, // VaultListManager vaultListManager, // @@ -88,6 +91,7 @@ public class VaultListController implements FxController { this.vaults = vaults; this.selectedVault = selectedVault; this.cellFactory = cellFactory; + this.vaultService = vaultService; this.addVaultWizard = addVaultWizard; this.removeVaultDialogue = removeVaultDialogue; this.vaultListManager = vaultListManager; @@ -119,6 +123,9 @@ public class VaultListController implements FxController { Optional.ofNullable(selectedVault.get()) .filter(Vault::isLocked) .ifPresent(vault -> appWindows.startUnlockWorkflow(vault, mainWindow)); + Optional.ofNullable(selectedVault.get()) + .filter(Vault::isUnlocked) + .ifPresent(vaultService::reveal); } }); diff --git a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java index 630b82776..afa05cc8c 100644 --- a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java @@ -1,18 +1,33 @@ package org.cryptomator.ui.preferences; +import org.cryptomator.common.Environment; import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.UpdateChecker; import javax.inject.Inject; +import javafx.animation.PauseTransition; import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.ObjectBinding; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; import javafx.scene.control.ContentDisplay; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.Locale; +import java.util.ResourceBundle; + @PreferencesScoped public class UpdatesPreferencesController implements FxController { @@ -20,29 +35,55 @@ public class UpdatesPreferencesController implements FxController { private static final String DOWNLOADS_URI = "https://cryptomator.org/downloads"; private final Application application; + private final Environment environment; + private final ResourceBundle resourceBundle; private final Settings settings; private final UpdateChecker updateChecker; private final ObjectBinding checkForUpdatesButtonState; private final ReadOnlyStringProperty latestVersion; + private final ObservableValue lastSuccessfulUpdateCheck; + private final StringBinding lastUpdateCheckMessage; + private final ObservableValue timeDifferenceMessage; private final String currentVersion; private final BooleanBinding updateAvailable; + private final BooleanBinding checkFailed; + private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false); + private final DateTimeFormatter formatter; + private final BooleanBinding upToDate; /* FXML */ public CheckBox checkForUpdatesCheckbox; @Inject - UpdatesPreferencesController(Application application, Settings settings, UpdateChecker updateChecker) { + UpdatesPreferencesController(Application application, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker) { this.application = application; + this.environment = environment; + this.resourceBundle = resourceBundle; this.settings = settings; this.updateChecker = updateChecker; this.checkForUpdatesButtonState = Bindings.when(updateChecker.checkingForUpdatesProperty()).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY); this.latestVersion = updateChecker.latestVersionProperty(); - this.updateAvailable = latestVersion.isNotNull(); + this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty(); + this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck); this.currentVersion = updateChecker.getCurrentVersion(); + this.updateAvailable = updateChecker.updateAvailableProperty(); + this.formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault()); + this.upToDate = updateChecker.updateCheckStateProperty().isEqualTo(UpdateChecker.UpdateCheckState.CHECK_SUCCESSFUL).and(latestVersion.isEqualTo(currentVersion)); + this.checkFailed = updateChecker.checkFailedProperty(); + this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, lastSuccessfulUpdateCheck); } public void initialize() { checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates); + + upToDate.addListener((_, _, newVal) -> { + if (newVal) { + upToDateLabelVisible.set(true); + PauseTransition delay = new PauseTransition(javafx.util.Duration.seconds(5)); + delay.setOnFinished(_ -> upToDateLabelVisible.set(false)); + delay.play(); + } + }); } @FXML @@ -55,6 +96,11 @@ public class UpdatesPreferencesController implements FxController { application.getHostServices().showDocument(DOWNLOADS_URI); } + @FXML + public void showLogfileDirectory() { + environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString())); + } + /* Observable Properties */ public ObjectBinding checkForUpdatesButtonStateProperty() { @@ -77,6 +123,46 @@ public class UpdatesPreferencesController implements FxController { return currentVersion; } + public StringBinding lastUpdateCheckMessageProperty() { + return lastUpdateCheckMessage; + } + + public String getLastUpdateCheckMessage() { + Instant lastCheck = lastSuccessfulUpdateCheck.getValue(); + if (lastCheck != null && !lastCheck.equals(Settings.DEFAULT_TIMESTAMP)) { + return formatter.format(LocalDateTime.ofInstant(lastCheck, ZoneId.systemDefault())); + } else { + return "-"; + } + } + + public ObservableValue timeDifferenceMessageProperty() { + return timeDifferenceMessage; + } + + public String getTimeDifferenceMessage() { + var lastSuccessCheck = lastSuccessfulUpdateCheck.getValue(); + var duration = Duration.between(lastSuccessCheck, Instant.now()); + var hours = duration.toHours(); + if (lastSuccessCheck.equals(Settings.DEFAULT_TIMESTAMP)) { + return resourceBundle.getString("preferences.updates.lastUpdateCheck.never"); + } else if (hours < 1) { + return resourceBundle.getString("preferences.updates.lastUpdateCheck.recently"); + } else if (hours < 24) { + return String.format(resourceBundle.getString("preferences.updates.lastUpdateCheck.hoursAgo"), hours); + } else { + return String.format(resourceBundle.getString("preferences.updates.lastUpdateCheck.daysAgo"), duration.toDays()); + } + } + + public BooleanProperty upToDateLabelVisibleProperty() { + return upToDateLabelVisible; + } + + public boolean isUpToDateLabelVisible() { + return upToDateLabelVisible.get(); + } + public BooleanBinding updateAvailableProperty() { return updateAvailable; } @@ -84,4 +170,13 @@ public class UpdatesPreferencesController implements FxController { public boolean isUpdateAvailable() { return updateAvailable.get(); } + + public BooleanBinding checkFailedProperty() { + return checkFailed; + } + + public boolean isCheckFailed() { + return checkFailed.getValue(); + } + } diff --git a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java index aa13f30da..d2c10f8fd 100644 --- a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java +++ b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java @@ -8,7 +8,8 @@ import org.cryptomator.ui.common.FxmlScene; import javafx.scene.Scene; import javafx.stage.Stage; -import java.time.LocalDate; +import java.time.Duration; +import java.time.Instant; @UpdateReminderScoped @Subcomponent(modules = {UpdateReminderModule.class}) @@ -23,7 +24,8 @@ public interface UpdateReminderComponent { Settings settings(); default void checkAndShowUpdateReminderWindow() { - if (LocalDate.parse(settings().lastUpdateCheck.get()).isBefore(LocalDate.now().minusDays(14)) && !settings().checkForUpdates.getValue()) { + var now = Instant.now(); + if (!settings().checkForUpdates.getValue() && settings().lastSuccessfulUpdateCheck.get().isBefore(now.minus(Duration.ofDays(14)))) { Stage stage = window(); stage.setScene(updateReminderScene().get()); stage.sizeToScene(); @@ -33,6 +35,7 @@ public interface UpdateReminderComponent { @Subcomponent.Factory interface Factory { + UpdateReminderComponent create(); } } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java index 28ae0b5c6..183298c44 100644 --- a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java +++ b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java @@ -7,8 +7,6 @@ import org.cryptomator.ui.fxapp.UpdateChecker; import javax.inject.Inject; import javafx.fxml.FXML; import javafx.stage.Stage; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; @UpdateReminderScoped public class UpdateReminderController implements FxController { @@ -27,20 +25,17 @@ public class UpdateReminderController implements FxController { @FXML public void cancel() { - settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE)); window.close(); } @FXML public void once() { - settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE)); updateChecker.checkForUpdatesNow(); window.close(); } @FXML public void automatically() { - settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE)); updateChecker.checkForUpdatesNow(); settings.checkForUpdates.set(true); window.close(); diff --git a/src/main/resources/fxml/preferences_updates.fxml b/src/main/resources/fxml/preferences_updates.fxml index 3156d1c3c..d0910949b 100644 --- a/src/main/resources/fxml/preferences_updates.fxml +++ b/src/main/resources/fxml/preferences_updates.fxml @@ -1,13 +1,19 @@ + + + + - + + + - - + - + - - + + - - - + + + + + + + + + + + + + + + diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index f59522098..a16e3ac46 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -321,6 +321,14 @@ preferences.updates.currentVersion=Current Version: %s preferences.updates.autoUpdateCheck=Check for updates automatically preferences.updates.checkNowBtn=Check Now preferences.updates.updateAvailable=Update to version %s available. +preferences.updates.lastUpdateCheck=Last check: %s +preferences.updates.lastUpdateCheck.never=never +preferences.updates.lastUpdateCheck.recently=recently +preferences.updates.lastUpdateCheck.daysAgo=%s days ago +preferences.updates.lastUpdateCheck.hoursAgo=%s hours ago +preferences.updates.checkFailed=Looking for updates failed. Please check your internet connection or try again later. +preferences.updates.upToDate=Cryptomator is up-to-date. + ## Contribution preferences.contribute=Support Us preferences.contribute.registeredFor=Supporter certificate registered for %s diff --git a/src/test/java/org/cryptomator/common/settings/SettingsJsonTest.java b/src/test/java/org/cryptomator/common/settings/SettingsJsonTest.java index da78ee5bc..a73431681 100644 --- a/src/test/java/org/cryptomator/common/settings/SettingsJsonTest.java +++ b/src/test/java/org/cryptomator/common/settings/SettingsJsonTest.java @@ -3,6 +3,7 @@ package org.cryptomator.common.settings; import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -68,7 +69,7 @@ public class SettingsJsonTest { jsonObj.theme = UiTheme.DARK; jsonObj.showTrayIcon = false; - var jsonStr = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj); + var jsonStr = new ObjectMapper().registerModule(new JavaTimeModule()).writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj); MatcherAssert.assertThat(jsonStr, containsString("\"theme\" : \"DARK\"")); MatcherAssert.assertThat(jsonStr, containsString("\"showTrayIcon\" : false"));