diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index dccf9a9a2..b4e73e306 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -10,8 +10,8 @@ on: required: false env: - JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' + JAVA_DIST: 'temurin' + JAVA_VERSION: '23.0.1+11' jobs: get-version: @@ -29,12 +29,12 @@ jobs: include: - os: ubuntu-latest appimage-suffix: x86_64 - openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-x64_bin-jmods.zip' - openjfx-sha: 'd44bff3b94d5668fdee18a938d7b1269026d663d44765f02d29a9bdfd3fa1eb0' - - os: [self-hosted, Linux, ARM64] + openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip' + openjfx-sha: '2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6' + - os: ubuntu-24.04-arm appimage-suffix: aarch64 - openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-aarch64_bin-jmods.zip' - openjfx-sha: '3d5457136690c4f5bb9522d38b45218e045bdac13c24aa4c808c7c8d17d039c7' + openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-aarch64_bin-jmods.zip' + openjfx-sha: '09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b' steps: - uses: actions/checkout@v4 - name: Setup Java @@ -98,7 +98,7 @@ jobs: --dest appdir --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" @@ -116,6 +116,7 @@ jobs: --java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" --java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\"" + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" --resource-dir dist/linux/resources - name: Patch Cryptomator.AppDir run: | @@ -132,13 +133,13 @@ jobs: cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg - ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon - ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop + ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop + ln -s org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun - name: Download AppImageKit run: | - curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${{ matrix.appimage-suffix }}.AppImage -o appimagetool.AppImage + curl -L https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.appimage-suffix }}.AppImage -o appimagetool.AppImage chmod +x appimagetool.AppImage ./appimagetool.AppImage --appimage-extract - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 @@ -152,7 +153,7 @@ jobs: run: > ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.appimage-suffix }}.AppImage -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${{ matrix.appimage-suffix }}.AppImage.zsync' - --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback" + --sign --sign-key=615D449FE6E6A235 - name: Create detached GPG signatures run: | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage @@ -175,4 +176,4 @@ jobs: files: | cryptomator-*.AppImage cryptomator-*.zsync - cryptomator-*.asc \ No newline at end of file + cryptomator-*.asc diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f07eecae7..545513129 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,8 +6,8 @@ on: types: [labeled] env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 22 + JAVA_DIST: 'temurin' + JAVA_VERSION: 23 defaults: run: diff --git a/.github/workflows/check-jdk-updates.yml b/.github/workflows/check-jdk-updates.yml index c73546994..64456baae 100644 --- a/.github/workflows/check-jdk-updates.yml +++ b/.github/workflows/check-jdk-updates.yml @@ -1,56 +1,75 @@ -name: Checks JDK version for minor updates +name: Check JDK for non-major updates on: schedule: - cron: '0 0 1 * *' # run once a month at the first day of month + workflow_dispatch: env: - JDK_VERSION: '22.0.1+8' - JDK_VENDOR: zulu + JDK_VERSION: '23.0.1+11' + JDK_VENDOR: temurin + RUNTIME_VERSION_HELPER: > + public class Test { + public static void main(String[] args) { + System.out.println(Runtime.version()); + } + } jobs: - jdk-current: - name: Check out current version - runs-on: ubuntu-latest - outputs: - jdk-date: ${{ steps.get-data.outputs.jdk-date}} - steps: - - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JDK_VERSION }} - distribution: ${{ env.JDK_VENDOR }} - check-latest: false - - name: Read JAVA_VERSION_DATE and store in env variable - id: get-data - run: | - date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"') - echo "jdk-date=${date}" >> "$GITHUB_OUTPUT" - jdk-latest: + check-version: name: Checkout latest jdk version runs-on: ubuntu-latest - outputs: - jdk-date: ${{ steps.get-data.outputs.jdk-date}} - jdk-version: ${{ steps.get-data.outputs.jdk-version}} + env: + JDK_MAJOR_VERSION: 'toBeFilled' steps: - - uses: actions/setup-java@v4 + - name: Determine current major version + run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,20) >> "$env:GITHUB_ENV" + shell: pwsh + - name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }} + uses: actions/setup-java@v4 with: - java-version: 21 + java-version: ${{ env.JDK_MAJOR_VERSION}} distribution: ${{ env.JDK_VENDOR }} check-latest: true - - name: Read JAVA_VERSION_DATE and store in env variable - id: get-data + - name: Determine if update is available + id: determine + shell: pwsh run: | - date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"') - echo "jdk-date=${date}" >> "$GITHUB_OUTPUT" - version=$(cat ${JAVA_HOME}/release | grep "JAVA_RUNTIME_VERSION=\"" | awk -F'=' '{print $2}' | tr -d '"') - echo "jdk-version=${version}" >> "$GITHUB_OUTPUT" - notify: - name: Notifies for jdk update - runs-on: ubuntu-latest - needs: [jdk-current, jdk-latest] - if: ${{ needs.jdk-latest.outputs.jdk-date }} > ${{ needs.jdk-current.outputs.jdk-date }} - steps: - - name: Slack Notification + $latestVersion = 0,0,0,0 #INTERIM, UPDATE, PATCH and BUILD + $currentVersion = 0,0,0,0 + + # Get the latest JDK runtime version + "${env:RUNTIME_VERSION_HELPER}" | Set-Content -Path "GetRuntimeVersion.java" + $latestVersionString = & java GetRuntimeVersion.java + $runtimeVersionAndBuild = $latestVersionString.Split('+') + if($runtimeVersionAndBuild.Length -eq 2) { + $latestVersion[3]=$runtimeVersionAndBuild[1]; + } + $tmp=$runtimeVersionAndBuild[0].Split('.') + for($i=0;$i -lt $latestVersion.Length; $i++) { + $latestVersion[$i]=$tmp[$i+1]; + } + + # Get the current JDK version + $runtimeVersionAndBuild = '${{ env.JDK_VERSION}}'.Split('+') + if($runtimeVersionAndBuild.Length -eq 2) { + $currentVersion[3]=$runtimeVersionAndBuild[1]; + } + $tmp=$runtimeVersionAndBuild[0].Split('.') + for($i=0;$i -lt $currentVersion.Length; $i++) { + $currentVersion[$i]=$tmp[$i+1]; + } + + # compare + for($i=0; $i -lt $currentVersion.Length ; $i++) { + if($latestVersion[$i] -gt $currentVersion[$i]){ + echo 'UPDATE_AVAILABLE=true' >> "$env:GITHUB_OUTPUT" + echo "LATEST_JDK_VERSION='${latestVersionString}'" >> "$env:GITHUB_OUTPUT" + return 0; + } + } + - name: Notify + if: steps.determine.outputs.UPDATE_AVAILABLE == 'true' uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -59,6 +78,6 @@ jobs: SLACK_ICON_EMOJI: ':bot:' SLACK_CHANNEL: 'cryptomator-desktop' SLACK_TITLE: "JDK update available" - SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ needs.jdk-latest.outputs.jdk-version }}. See https://github.com/cryptomator/cryptomator/wiki/How-to-update-the-build-JDK for instructions." + SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ steps.determine.outputs.LATEST_JDK_VERSION }}. Check the Nextcloud collective for instructions." SLACK_FOOTER: false - MSG_MINIMAL: true + MSG_MINIMAL: true \ No newline at end of file diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 7ab8cc7ea..16c5bc530 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -16,14 +16,14 @@ on: type: boolean env: - JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' - COFFEELIBS_JDK: 22 - COFFEELIBS_JDK_VERSION: '22.0.2+9-0ppa1' - OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-x64_bin-jmods.zip' - OPENJFX_JMODS_AMD64_HASH: 'd44bff3b94d5668fdee18a938d7b1269026d663d44765f02d29a9bdfd3fa1eb0' - OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-aarch64_bin-jmods.zip' - OPENJFX_JMODS_AARCH64_HASH: '3d5457136690c4f5bb9522d38b45218e045bdac13c24aa4c808c7c8d17d039c7' + JAVA_DIST: 'temurin' + JAVA_VERSION: '23.0.1+11' + COFFEELIBS_JDK: 23 + COFFEELIBS_JDK_VERSION: '23.0.1+11-0ppa1' + OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip' + OPENJFX_JMODS_AMD64_HASH: '2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6' + OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-aarch64_bin-jmods.zip' + OPENJFX_JMODS_AARCH64_HASH: '09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b' jobs: build: diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index 40dfe97fa..00e30c984 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: 22 + java-version: 23 check-command: 'mvn -B validate -Pdependency-check -Djavafx.platform=linux' secrets: nvd-api-key: ${{ secrets.NVD_API_KEY }} diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 7dc22462f..5a5ab09b0 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -22,8 +22,8 @@ on: value: ${{ jobs.determine-version.outputs.type }} env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 22 + JAVA_DIST: 'temurin' + JAVA_VERSION: 23 jobs: determine-version: diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml index c90b721db..c7ef8b8b6 100644 --- a/.github/workflows/mac-dmg-x64.yml +++ b/.github/workflows/mac-dmg-x64.yml @@ -14,8 +14,8 @@ on: types: [published] env: - JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' + JAVA_DIST: 'temurin' + JAVA_VERSION: '23.0.1+11' jobs: get-version: @@ -35,8 +35,8 @@ jobs: architecture: x64 output-suffix: x64 fuse-lib: macFUSE - openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_osx-x64_bin-jmods.zip' - openjfx-sha: '115cb08bb59d880cfff6e51e0bf0dcc45785ed9d456b8b8425597b04da6ab3d4' + openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_osx-x64_bin-jmods.zip' + openjfx-sha: '8857965975c464a0e5d57709292ce357d0ebb39f6168c41d5ca38301e42c3c8e' steps: - uses: actions/checkout@v4 - name: Setup Java @@ -100,7 +100,7 @@ jobs: --dest appdir --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=org.cryptomator.jfuse.mac" diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 6bb463b65..d724e6ed2 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -15,8 +15,8 @@ on: type: boolean env: - JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' + JAVA_DIST: 'temurin' + JAVA_VERSION: '23.0.1+11' jobs: get-version: @@ -36,8 +36,8 @@ jobs: architecture: aarch64 output-suffix: arm64 fuse-lib: FUSE-T - openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_osx-aarch64_bin-jmods.zip' - openjfx-sha: '813c6748f7c99cb7a579d48b48a087b4682b1fad1fc1a4fe5f9b21cf872b15a7' + openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_osx-aarch64_bin-jmods.zip' + openjfx-sha: 'a800724a1f3e6757ecfa0bd5bf7ed64d2e6a7a3f5b3522650a70b8cfc7782fb6' steps: - uses: actions/checkout@v4 - name: Setup Java @@ -101,7 +101,7 @@ jobs: --dest appdir --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=org.cryptomator.jfuse.mac" diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index e6512a52e..5b0cb5111 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -4,8 +4,8 @@ on: pull_request: env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 22 + JAVA_DIST: 'temurin' + JAVA_VERSION: 23 defaults: run: diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index 0454dc08d..448d9a5a4 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -11,8 +11,8 @@ defaults: shell: bash env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 22 + JAVA_DIST: 'temurin' + JAVA_VERSION: 23 jobs: check-preconditions: diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 2209de339..14b9dd5aa 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -16,9 +16,9 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' - OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_windows-x64_bin-jmods.zip' - OPENJFX_JMODS_AMD64_HASH: 'f9376d200f5c5b85327d575c1ec1482e6455f19916577f7e2fc9be2f48bb29b6' + JAVA_VERSION: '23.0.1+11' + OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_windows-x64_bin-jmods.zip' + OPENJFX_JMODS_AMD64_HASH: 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3' WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi' WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe' @@ -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.accessibility,jdk.management.jfr,java.compiler + --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.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages @@ -110,7 +110,7 @@ jobs: --dest appdir --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" @@ -218,7 +218,7 @@ jobs: --dest installer --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}" --win-menu --win-dir-chooser @@ -304,7 +304,7 @@ jobs: -out dist/win/bundle/ -dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" -dBundleVendor="Skymatic GmbH" - -dBundleCopyright="(C) 2016 - 2024 Skymatic GmbH" + -dBundleCopyright="(C) 2016 - 2025 Skymatic GmbH" -dAboutUrl="https://cryptomator.org" -dHelpUrl="https://cryptomator.org/contact" -dUpdateUrl="https://cryptomator.org/downloads/" diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 655ed6260..1256745d3 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -14,17 +14,16 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index d76f76416..d2bf39f29 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ [![cryptomator](cryptomator.png)](https://cryptomator.org/) -[![Build](https://github.com/cryptomator/cryptomator/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild) +[![Build](https://github.com/cryptomator/cryptomator/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptomator/actions/workflows/build.yml?query=branch%3Adevelop) [![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptomator/badge.svg)](https://snyk.io/test/github/cryptomator/cryptomator) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cryptomator_cryptomator&metric=alert_status)](https://sonarcloud.io/dashboard?id=cryptomator_cryptomator) -[![Twitter](https://img.shields.io/badge/twitter-@Cryptomator-blue.svg?style=flat)](http://twitter.com/Cryptomator) +[![Mastodon](https://img.shields.io/mastodon/follow/176112?domain=mastodon.online&style=flat)](https://mastodon.online/@cryptomator) [![Crowdin](https://badges.crowdin.net/cryptomator/localized.svg)](https://translate.cryptomator.org/) [![Latest Release](https://img.shields.io/github/release/cryptomator/cryptomator.svg)](https://github.com/cryptomator/cryptomator/releases/latest) [![Community](https://img.shields.io/badge/help-Community-orange.svg)](https://community.cryptomator.org) @@ -32,9 +32,9 @@ Become our Gold Sponsor and showcase your brand to a targeted audience! Please c ### Special Shoutout -Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/opensource). +Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/company/opensource). -MacStadium +MacStadium --- @@ -54,7 +54,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator - File names get encrypted - Folder structure gets obfuscated - Use as many vaults in your Dropbox as you want, each having individual passwords -- Four thousand commits for the security of your data!! :tada: +- More than Five thousand commits for the security of your data!! :tada: ### Privacy @@ -72,13 +72,13 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator ### Security Architecture -For more information on the security details visit [cryptomator.org](https://docs.cryptomator.org/en/latest/security/architecture/). +For more information on the security details visit [cryptomator.org](https://docs.cryptomator.org/security/architecture/). ## Building ### Dependencies -* JDK 22 (e.g. temurin, zulu) +* JDK 23 (e.g. temurin, zulu) * Maven 3 ### Run Maven diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index e815fd5b7..63cdc09f9 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -25,10 +25,10 @@ cp ../../../target/cryptomator-*.jar ../../../target/mods JAVAFX_VERSION=22.0.2 JAVAFX_ARCH="x64" -JAVAFX_JMODS_SHA256='d44bff3b94d5668fdee18a938d7b1269026d663d44765f02d29a9bdfd3fa1eb0' +JAVAFX_JMODS_SHA256='2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6' if [ "${CPU_ARCH}" = "aarch64" ]; then JAVAFX_ARCH="aarch64" - JAVAFX_JMODS_SHA256='3d5457136690c4f5bb9522d38b45218e045bdac13c24aa4c808c7c8d17d039c7' + JAVAFX_JMODS_SHA256='09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b' fi # download javaFX jmods @@ -76,7 +76,7 @@ ${JAVA_HOME}/bin/jpackage \ --vendor "Skymatic GmbH" \ --java-options "--enable-preview" \ --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ - --copyright "(C) 2016 - 2024 Skymatic GmbH" \ + --copyright "(C) 2016 - 2025 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --app-version "${VERSION}.${REVISION_NO}" \ @@ -91,6 +91,7 @@ ${JAVA_HOME}/bin/jpackage \ --java-options "-Dcryptomator.showTrayIcon=true" \ --java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \ --java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \ + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \ --resource-dir ../resources # transform AppDir @@ -108,13 +109,13 @@ cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/ap cp ../common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg -ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon -ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop +ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop +ln -s org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun # load AppImageTool -curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${CPU_ARCH}.AppImage -o /tmp/appimagetool.AppImage +curl -L https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${CPU_ARCH}.AppImage -o /tmp/appimagetool.AppImage chmod +x /tmp/appimagetool.AppImage # create AppImage diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml index b15ba0356..d69716e06 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml +++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml @@ -1,11 +1,16 @@ - org.cryptomator.Cryptomator FSFAP GPL-3.0-or-later Cryptomator - Encryption made easy and optimized for the cloud + Encryption for your cloud made easy + + + encryption + security + privacy +

@@ -44,12 +49,16 @@ - Light theme - https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png + Encrypts your data, protects your privacy + https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png - Dark theme - https://user-images.githubusercontent.com/11858409/156986113-6c5d7801-86e0-4643-bc2f-aff9d95d3ce0.png + Dark theme available + https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_dark.png + + + Easy to use - work on encrypted files as if they were not + https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png @@ -74,6 +83,12 @@ + + https://github.com/cryptomator/cryptomator/releases/1.15.1 + + + https://github.com/cryptomator/cryptomator/releases/1.15.0 + https://github.com/cryptomator/cryptomator/releases/1.14.2 diff --git a/dist/linux/debian/control b/dist/linux/debian/control index 2a9bbae69..96d29acd4 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-22 (>= 22.0.1+8-0ppa1), libgtk-3-0, libxxf86vm1, libgl1 +Build-Depends: debhelper (>=10), coffeelibs-jdk-23 (>= 23.0.1+11-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/copyright b/dist/linux/debian/copyright index 322e549d0..f4155bf5a 100644 --- a/dist/linux/debian/copyright +++ b/dist/linux/debian/copyright @@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator Source: https://cryptomator.org Files: * -Copyright: 2016-2024 Skymatic GmbH +Copyright: 2016-2025 Skymatic GmbH License: GPL-3+ Files: debian/org.cryptomator.Cryptomator.appdata.xml -Copyright: 2016-2024 Skymatic GmbH +Copyright: 2016-2025 Skymatic GmbH License: FSFAP License: GPL-3+ diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index 81d14f66a..a899dba68 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-22-coffeelibs +JAVA_HOME = /usr/lib/jvm/java-23-coffeelibs DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH) ifeq ($(DEB_BUILD_ARCH),amd64) JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods @@ -45,7 +45,7 @@ override_dh_auto_build: --vendor "Skymatic GmbH" \ --java-options "--enable-preview" \ --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ - --copyright "(C) 2016 - 2024 Skymatic GmbH" \ + --copyright "(C) 2016 - 2025 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --java-options "-Dfile.encoding=\"utf-8\"" \ @@ -62,6 +62,7 @@ override_dh_auto_build: --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --java-options "-Dcryptomator.disableUpdateCheck=\"${DISABLE_UPDATE_CHECK}\"" \ --java-options "-Dcryptomator.integrationsLinux.autoStartCmd=\"cryptomator\"" \ + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \ --app-version "${VERSION_NUM}.${REVISION_NUM}" \ --resource-dir resources \ --verbose diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index bceabbc3f..dbbd818d9 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -24,7 +24,7 @@ rm -rf runtime dmg *.app *.dmg # set variables APP_NAME="Cryptomator" VENDOR="Skymatic GmbH" -COPYRIGHT_YEARS="2016 - 2024" +COPYRIGHT_YEARS="2016 - 2025" PACKAGE_IDENTIFIER="org.cryptomator" MAIN_JAR_GLOB="cryptomator-*.jar" MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator" @@ -32,15 +32,15 @@ REVISION_NO=`git rev-list --count HEAD` VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'` FUSE_LIB="FUSE-T" -JAVAFX_VERSION=22.0.2 +JAVAFX_VERSION=23.0.1 JAVAFX_ARCH="undefined" JAVAFX_JMODS_SHA256="undefined" if [ "$(machine)" = "arm64e" ]; then JAVAFX_ARCH="aarch64" - JAVAFX_JMODS_SHA256="813c6748f7c99cb7a579d48b48a087b4682b1fad1fc1a4fe5f9b21cf872b15a7" + JAVAFX_JMODS_SHA256="a800724a1f3e6757ecfa0bd5bf7ed64d2e6a7a3f5b3522650a70b8cfc7782fb6" else JAVAFX_ARCH="x64" - JAVAFX_JMODS_SHA256="115cb08bb59d880cfff6e51e0bf0dcc45785ed9d456b8b8425597b04da6ab3d4" + JAVAFX_JMODS_SHA256="8857965975c464a0e5d57709292ce357d0ebb39f6168c41d5ca38301e42c3c8e" fi JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip" diff --git a/dist/mac/dmg/resources/licenseTemplate.ftl b/dist/mac/dmg/resources/licenseTemplate.ftl index 45a523f51..606c1f9b9 100644 --- a/dist/mac/dmg/resources/licenseTemplate.ftl +++ b/dist/mac/dmg/resources/licenseTemplate.ftl @@ -17,7 +17,7 @@ \f1\b0 \ \ -\f0\b \'a9 2016 \'96 2024 Skymatic GmbH +\f0\b \'a9 2016 \'96 2025 Skymatic GmbH \f1\b0 \ \ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\ diff --git a/dist/win/build.bat b/dist/win/build.bat index 6dcb344c0..3532a69fd 100644 --- a/dist/win/build.bat +++ b/dist/win/build.bat @@ -11,7 +11,7 @@ SET HELP_URL="https://cryptomator.org/contact/" SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator" SET LOOPBACK_ALIAS="cryptomator-vault" -powershell -NoLogo -NoProfile -ExecutionPolicy Unrestricted -Command .\build.ps1^ +pwsh -NoLogo -NoProfile -ExecutionPolicy Unrestricted -Command .\build.ps1^ -AppName %APPNAME%^ -MainJarGlob "%MAIN_JAR_GLOB%"^ -ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^ diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 61da2106e..a733f4557 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -51,23 +51,23 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) { } ## download jfx jmods -$javaFxVersion='22.0.2' +$javaFxVersion='23.0.1' $javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip" -$javaFxJmodsSHA256 = 'f9376d200f5c5b85327d575c1ec1482e6455f19916577f7e2fc9be2f48bb29b6' +$javaFxJmodsSHA256 = 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3' $javaFxJmods = '.\resources\jfxJmods.zip' if( !(Test-Path -Path $javaFxJmods) ) { Write-Output "Downloading ${javaFxJmodsUrl}..." Invoke-WebRequest $javaFxJmodsUrl -OutFile $javaFxJmods # redirects are followed by default } -$jmodsChecksumActual = $(Get-FileHash -Path $javaFxJmods -Algorithm SHA256).Hash +$jmodsChecksumActual = $(Get-FileHash -Path $javaFxJmods -Algorithm SHA256).Hash.ToLower() if( $jmodsChecksumActual -ne $javaFxJmodsSHA256 ) { Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $javaFxJmodsSHA256 , actual: $jmodsChecksumActual" exit 1; } Expand-Archive -Path $javaFxJmods -Force -DestinationPath ".\resources\" -Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods" +Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods" -ErrorAction Ignore Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop ## create custom runtime @@ -75,7 +75,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.accessibility,jdk.management.jfr,java.compiler,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,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` --strip-native-commands ` --no-header-files ` --no-man-pages ` diff --git a/dist/win/bundle/resources/licenseTemplate.ftl b/dist/win/bundle/resources/licenseTemplate.ftl index 6940f9fe6..f0232dc73 100644 --- a/dist/win/bundle/resources/licenseTemplate.ftl +++ b/dist/win/bundle/resources/licenseTemplate.ftl @@ -10,7 +10,7 @@ \vieww12000\viewh15840\viewkind0 \pard\tx283\tx567\tx850\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par \par -\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par +\b\'a9 2016 \'96 2025 Skymatic GmbH \b0\par \par This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par \par diff --git a/dist/win/contrib/dokan1.dll b/dist/win/contrib/dokan1.dll deleted file mode 100755 index 194945aed..000000000 Binary files a/dist/win/contrib/dokan1.dll and /dev/null differ diff --git a/dist/win/resources/licenseTemplate.ftl b/dist/win/resources/licenseTemplate.ftl index 011fda3cb..de5590ba1 100644 --- a/dist/win/resources/licenseTemplate.ftl +++ b/dist/win/resources/licenseTemplate.ftl @@ -10,7 +10,7 @@ \vieww12000\viewh15840\viewkind0 \pard\tx283\tx567\tx850\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par \par -\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par +\b\'a9 2016 \'96 2025 Skymatic GmbH \b0\par \par This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par \par diff --git a/pom.xml b/pom.xml index 3290b3121..5aef0b15b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator cryptomator - 1.15.0-SNAPSHOT + 1.16.0-SNAPSHOT Cryptomator Desktop App @@ -26,50 +26,53 @@ UTF-8 - 22 + 23 org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents - 2.7.1 - 1.4.0 + 2.8.0 + 1.5.0 1.3.0 1.2.4 - 1.5.1 + 1.5.2 5.0.2 2.0.7 3.17.0 - 2.52 + 2.55 2.2 - 2.18.1 - 22.0.2 + 2.18.2 + 23.0.1 4.4.0 9.37.3 - 1.5.12 + 1.5.16 2.0.16 0.8.0 1.9.0 - 5.11.3 - 5.14.2 + 5.11.4 + 5.15.2 3.0 26.0.1 - 11.1.0 + 12.1.0 0.8.12 - 2.4.0 - 1.3.0 + 2.5.0 + 1.4.0 3.13.0 3.3.1 3.8.1 3.5.2 3.4.2 + + + @@ -201,6 +204,12 @@ 2.0.1 + + + com.github.ben-manes.caffeine + caffeine + 3.2.0 + org.junit.jupiter @@ -302,7 +311,6 @@ -Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled - --enable-preview @@ -329,11 +337,11 @@ - --enable-preview plain true + @{surefire.jacoco.args} -javaagent:${org.mockito:mockito-core:jar} @@ -343,6 +351,13 @@ org.apache.maven.plugins maven-dependency-plugin + + jar-paths-to-properties + validate + + properties + + copy-mods @@ -415,6 +430,9 @@ prepare-agent + + surefire.jacoco.args + report diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index b07fe3a6f..4dd4242b3 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,4 +1,5 @@ import ch.qos.logback.classic.spi.Configurator; +import org.cryptomator.networking.SSLContextWithPKCS12TrustStore; import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider; @@ -13,6 +14,9 @@ import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvid import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider; +import org.cryptomator.networking.SSLContextWithMacKeychain; +import org.cryptomator.networking.SSLContextProvider; +import org.cryptomator.networking.SSLContextWithWindowsCertStore; import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.logging.LogbackConfiguratorFactory; import org.cryptomator.ui.traymenu.AwtTrayMenuController; @@ -51,11 +55,14 @@ open module org.cryptomator.desktop { requires jakarta.inject; requires static javax.inject; requires java.compiler; + requires com.github.benmanes.caffeine; uses org.cryptomator.common.locationpresets.LocationPresetsProvider; + uses SSLContextProvider; provides TrayMenuController with AwtTrayMenuController; provides Configurator with LogbackConfiguratorFactory; + provides SSLContextProvider with SSLContextWithWindowsCertStore, SSLContextWithMacKeychain, SSLContextWithPKCS12TrustStore; provides LocationPresetsProvider with // DropboxWindowsLocationPresetsProvider, DropboxMacLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, // GoogleDriveMacLocationPresetsProvider, GoogleDriveWindowsLocationPresetsProvider, // diff --git a/src/main/java/org/cryptomator/common/keychain/KeychainManager.java b/src/main/java/org/cryptomator/common/keychain/KeychainManager.java index b2af2725f..ac03e5ed6 100644 --- a/src/main/java/org/cryptomator/common/keychain/KeychainManager.java +++ b/src/main/java/org/cryptomator/common/keychain/KeychainManager.java @@ -1,8 +1,7 @@ package org.cryptomator.common.keychain; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.integrations.keychain.KeychainAccessProvider; @@ -24,9 +23,9 @@ public class KeychainManager implements KeychainAccessProvider { @Inject KeychainManager(ObjectExpression selectedKeychain) { this.keychain = selectedKeychain; - this.passphraseStoredProperties = CacheBuilder.newBuilder() // + this.passphraseStoredProperties = Caffeine.newBuilder() // .weakValues() // - .build(CacheLoader.from(this::createStoredPassphraseProperty)); + .build(this::createStoredPassphraseProperty); keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll()); } @@ -124,7 +123,7 @@ public class KeychainManager implements KeychainAccessProvider { * @see #isPassphraseStored(String) */ public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) { - return passphraseStoredProperties.getUnchecked(key); + return passphraseStoredProperties.get(key); } private BooleanProperty createStoredPassphraseProperty(String key) { diff --git a/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java index 9cb428d1b..a1d4d0c0e 100644 --- a/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java +++ b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java @@ -67,7 +67,7 @@ public final class GoogleDriveMacLocationPresetsProvider implements LocationPres */ private String getDriveLocationString(Path accountPath) { String accountName = accountPath.getFileName().toString().replace("GoogleDrive-", ""); - return STR."Google Drive - \{accountName}"; + return "Google Drive - " + accountName; } /** diff --git a/src/main/java/org/cryptomator/common/mount/Mounter.java b/src/main/java/org/cryptomator/common/mount/Mounter.java index 6ca067305..89f8fb782 100644 --- a/src/main/java/org/cryptomator/common/mount/Mounter.java +++ b/src/main/java/org/cryptomator/common/mount/Mounter.java @@ -160,7 +160,7 @@ public class Mounter { var mountService = mountProviders.stream().filter(s -> s.getClass().getName().equals(vaultSettings.mountService.getValue())).findFirst().orElse(defaultMountService.getValue()); if (isConflictingMountService(mountService)) { - var msg = STR."\{mountService.getClass()} unavailable due to conflict with either of \{CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName())}"; + var msg = mountService.getClass() + " unavailable due to conflict with either of " + CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName()); throw new ConflictingMountServiceException(msg); } diff --git a/src/main/java/org/cryptomator/common/settings/VaultSettings.java b/src/main/java/org/cryptomator/common/settings/VaultSettings.java index fd21fc197..5112415b4 100644 --- a/src/main/java/org/cryptomator/common/settings/VaultSettings.java +++ b/src/main/java/org/cryptomator/common/settings/VaultSettings.java @@ -58,6 +58,7 @@ public class VaultSettings { public final StringExpression mountName; public final StringProperty mountService; public final IntegerProperty port; + public final StringProperty lastKnownKeyLoader; VaultSettings(VaultSettingsJson json) { this.id = json.id; @@ -74,6 +75,7 @@ public class VaultSettings { this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint)); this.mountService = new SimpleStringProperty(this, "mountService", json.mountService); this.port = new SimpleIntegerProperty(this, "port", json.port); + this.lastKnownKeyLoader = new SimpleStringProperty(this, "lastKnownKeyLoader", json.lastKnownKeyLoader); // mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318 this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> { final String name; @@ -99,7 +101,7 @@ public class VaultSettings { } Observable[] observables() { - return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService}; + return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService, lastKnownKeyLoader}; } public static VaultSettings withRandomId() { @@ -130,6 +132,7 @@ public class VaultSettings { json.mountPoint = mountPoint.map(Path::toString).getValue(); json.mountService = mountService.get(); json.port = port.get(); + json.lastKnownKeyLoader = lastKnownKeyLoader.get(); return json; } diff --git a/src/main/java/org/cryptomator/common/settings/VaultSettingsJson.java b/src/main/java/org/cryptomator/common/settings/VaultSettingsJson.java index 43aa204e8..870b74e07 100644 --- a/src/main/java/org/cryptomator/common/settings/VaultSettingsJson.java +++ b/src/main/java/org/cryptomator/common/settings/VaultSettingsJson.java @@ -48,6 +48,9 @@ class VaultSettingsJson { @JsonProperty("mountService") String mountService; + @JsonProperty("lastKnownKeyLoader") + String lastKnownKeyLoader; + @JsonProperty("port") int port = VaultSettings.DEFAULT_PORT; diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index ca4e86090..a76ac0ddd 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -44,6 +44,7 @@ import javafx.beans.property.SimpleBooleanProperty; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.ReadOnlyFileSystemException; import java.util.EnumSet; import java.util.Objects; import java.util.Set; @@ -115,15 +116,22 @@ public class Vault { private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException { Set flags = EnumSet.noneOf(FileSystemFlags.class); - if (vaultSettings.usesReadOnlyMode.get()) { + var createReadOnly = vaultSettings.usesReadOnlyMode.get(); + try { + FileSystemCapabilityChecker.assertWriteAccess(getPath()); + } catch (FileSystemCapabilityChecker.MissingCapabilityException e) { + if (!createReadOnly) { + throw new ReadOnlyFileSystemException(); + } + } + if (createReadOnly) { flags.add(FileSystemFlags.READONLY); } else if (vaultSettings.maxCleartextFilenameLength.get() == -1) { LOG.debug("Determining cleartext filename length limitations..."); - var checker = new FileSystemCapabilityChecker(); int shorteningThreshold = configCache.get().allegedShorteningThreshold(); - int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath()); + int ciphertextLimit = FileSystemCapabilityChecker.determineSupportedCiphertextFileNameLength(getPath()); if (ciphertextLimit < shorteningThreshold) { - int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath()); + int cleartextLimit = FileSystemCapabilityChecker.determineSupportedCleartextFileNameLength(getPath()); vaultSettings.maxCleartextFilenameLength.set(cleartextLimit); } else { vaultSettings.maxCleartextFilenameLength.setValue(UNLIMITED_FILENAME_LENGTH); diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index d0f3bc53b..32caa3205 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -29,6 +29,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.ResourceBundle; import java.util.stream.Stream; @@ -54,9 +55,9 @@ public class VaultListManager { @Inject public VaultListManager(ObservableList vaultList, // AutoLocker autoLocker, // - List mountServices, - VaultComponent.Factory vaultComponentFactory, - ResourceBundle resourceBundle, + List mountServices, // + VaultComponent.Factory vaultComponentFactory, // + ResourceBundle resourceBundle, // Settings settings) { this.vaultList = vaultList; this.autoLocker = autoLocker; @@ -123,6 +124,10 @@ public class VaultListManager { private Vault create(VaultSettings vaultSettings) { var wrapper = new VaultConfigCache(vaultSettings); try { + if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) { + var keyIdScheme = wrapper.get().getKeyId().getScheme(); + vaultSettings.lastKnownKeyLoader.set(keyIdScheme); + } var vaultState = determineVaultState(vaultSettings.path.get()); if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state wrapper.reloadConfig(); diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index a8d38c312..3e0a613ca 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -9,8 +9,9 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import dagger.Lazy; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; -import org.cryptomator.common.SubstitutingProperties; import org.cryptomator.common.ShutdownHook; +import org.cryptomator.common.SubstitutingProperties; +import org.cryptomator.networking.SSLContextProvider; import org.cryptomator.ipc.IpcCommunicator; import org.cryptomator.logging.DebugMode; import org.cryptomator.ui.fxapp.FxApplicationComponent; @@ -19,8 +20,10 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; +import javax.net.ssl.SSLContext; import javafx.application.Application; import javafx.stage.Stage; +import java.security.SecureRandom; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -48,14 +51,16 @@ public class Cryptomator { private final Environment env; private final Lazy ipcMessageHandler; private final ShutdownHook shutdownHook; + private final SecureRandom csprng; @Inject - Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook) { + Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook, SecureRandom csprng) { this.debugMode = debugMode; this.supportedLanguages = supportedLanguages; this.env = env; this.ipcMessageHandler = ipcMessageHandler; this.shutdownHook = shutdownHook; + this.csprng = csprng; } public static void main(String[] args) { @@ -89,7 +94,7 @@ public class Cryptomator { LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion(), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); debugMode.initialize(); supportedLanguages.applyPreferred(); - + changeDefaultSSLContext(); /* * Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args. * If no external process could be reached, the args will be handled by the loopback IPC endpoint. @@ -115,6 +120,17 @@ public class Cryptomator { } } + private void changeDefaultSSLContext() { + SSLContextProvider.loadAll().findFirst().ifPresent(p -> { + try { + var context = p.getContext(csprng); + SSLContext.setDefault(context); + } catch (SSLContextProvider.SSLContextBuildException e) { + LOG.warn("Failed to change default SSL context with provider {}", p.getClass().getName(), e); + } + }); + } + /** * Launches the JavaFX application, blocking the main thread until shuts down. * diff --git a/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java b/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java new file mode 100644 index 000000000..87e1b82ef --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java @@ -0,0 +1,33 @@ +package org.cryptomator.networking; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; + +abstract class SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + abstract KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException; + + @Override + public SSLContext getContext(SecureRandom csprng) throws SSLContextBuildException { + try { + KeyStore truststore = getTruststore(); + truststore.load(null, null); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(truststore); + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), csprng); + return context; + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | KeyManagementException | IOException e) { + throw new SSLContextBuildException(e); + } + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextProvider.java b/src/main/java/org/cryptomator/networking/SSLContextProvider.java new file mode 100644 index 000000000..3563c546c --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextProvider.java @@ -0,0 +1,24 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.IntegrationsLoader; + +import javax.net.ssl.SSLContext; +import java.security.SecureRandom; +import java.util.ServiceLoader; +import java.util.stream.Stream; + +public interface SSLContextProvider { + + SSLContext getContext(SecureRandom csprng) throws SSLContextBuildException; + + class SSLContextBuildException extends Exception { + + SSLContextBuildException(Throwable t) { + super(t); + } + } + + static Stream loadAll() { + return IntegrationsLoader.loadAll(ServiceLoader.load(SSLContextProvider.class), SSLContextProvider.class); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java b/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java new file mode 100644 index 000000000..0078fdfd3 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java @@ -0,0 +1,21 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.OperatingSystem; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +/** + * SSLContextProvider for macOS using the macOS Keychain as truststore + */ +@OperatingSystem(OperatingSystem.Value.MAC) +public class SSLContextWithMacKeychain extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + @Override + KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + return KeyStore.getInstance("KeychainStore-ROOT"); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java b/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java new file mode 100644 index 000000000..f530435f8 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java @@ -0,0 +1,42 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.CheckAvailability; +import org.cryptomator.integrations.common.OperatingSystem; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.Optional; + +/** + * SSLContextProvider for Linux using a PKCS#12 file as trust store + */ +@OperatingSystem(OperatingSystem.Value.LINUX) +@CheckAvailability +public class SSLContextWithPKCS12TrustStore extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + private static final String CERT_FILE_LOCATION_PROPERTY = "cryptomator.networking.truststore.p12Path"; + + @Override + KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + var pkcs12FilePath = Path.of(System.getProperty(CERT_FILE_LOCATION_PROPERTY)); + try { + return KeyStore.getInstance(pkcs12FilePath.toFile(), new char[]{}); + } catch (IllegalArgumentException e) { + throw new NoSuchFileException(pkcs12FilePath.toString()); + } + } + + @CheckAvailability + public static boolean isSupported() { + var pkcs12Path = System.getProperty(CERT_FILE_LOCATION_PROPERTY); + return Optional.ofNullable(pkcs12Path) // + .map(Path::of) // + .map(Files::exists).orElse(false); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java b/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java new file mode 100644 index 000000000..0f1ea04b5 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java @@ -0,0 +1,21 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.OperatingSystem; + +import java.security.KeyStore; +import java.security.KeyStoreException; + +/** + * SSLContextProvider for Windows using the Windows certificate store as trust store + *

+ * In order to work, the jdk.crypto.mscapi jmod is needed + */ +@OperatingSystem(OperatingSystem.Value.WINDOWS) +public class SSLContextWithWindowsCertStore extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + @Override + KeyStore getTruststore() throws KeyStoreException { + return KeyStore.getInstance("WINDOWS-ROOT"); + } + +} diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultExpertSettingsController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultExpertSettingsController.java index dbf0a0c6e..a35bcdebe 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultExpertSettingsController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultExpertSettingsController.java @@ -26,7 +26,7 @@ public class CreateNewVaultExpertSettingsController implements FxController { public static final int MAX_SHORTENING_THRESHOLD = 220; public static final int MIN_SHORTENING_THRESHOLD = 36; - private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/en/1.7/security/architecture/#name-shortening"; + private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/architecture/#name-shortening"; private final Stage window; private final Lazy application; diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index f222d92c4..c8b7bee22 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -40,7 +40,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.util.Optional; import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; @@ -50,7 +49,7 @@ public class CreateNewVaultLocationController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class); private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home")); - private static final String TEMP_FILE_FORMAT = ".locationTest.cryptomator.tmp"; + private static final String TEMP_FILE_PREFIX = ".locationTest.cryptomator"; private final Stage window; private final Lazy chooseNameScene; @@ -126,16 +125,19 @@ public class CreateNewVaultLocationController implements FxController { private boolean isActuallyWritable(Path p) { - Path tmpFile = p.resolve(TEMP_FILE_FORMAT); - try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) { + Path tmpDir = null; + try { + tmpDir = Files.createTempDirectory(p, TEMP_FILE_PREFIX ); return true; } catch (IOException e) { return false; } finally { - try { - Files.deleteIfExists(tmpFile); - } catch (IOException e) { - LOG.warn("Unable to delete temporary file {}. Needs to be deleted manually.", tmpFile); + if (tmpDir != null) { + try { + Files.deleteIfExists(tmpDir); + } catch (IOException e) { + LOG.warn("Unable to delete temporary directory {}. Needs to be deleted manually.", tmpDir); + } } } } diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index ed58df863..2e3c2513f 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -12,7 +12,6 @@ public enum FxmlFile { CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), // CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), // CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), // - DOKANY_SUPPORT_END("/fxml/dokany_support_end.fxml"), // ERROR("/fxml/error.fxml"), // FORGET_PASSWORD("/fxml/forget_password.fxml"), // HEALTH_START("/fxml/health_start.fxml"), // @@ -47,9 +46,8 @@ public enum FxmlFile { RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), // RECOVERYKEY_RESET_VAULT_CONFIG_SUCCESS("/fxml/recoverykey_reset_vault_config_success.fxml"), // RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), // - REMOVE_CERT("/fxml/remove_cert.fxml"), // - REMOVE_VAULT("/fxml/remove_vault.fxml"), // SHARE_VAULT("/fxml/share_vault.fxml"), // + SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), // UPDATE_REMINDER("/fxml/update_reminder.fxml"), // UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"), UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), // diff --git a/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java b/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java index fd6d49b89..dc95f7051 100644 --- a/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java +++ b/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java @@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.fxapp.FxApplicationWindows; +import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy; import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; import org.jetbrains.annotations.VisibleForTesting; import org.slf4j.Logger; @@ -108,6 +109,7 @@ public class HubToPasswordConvertController implements FxController { .thenRunAsync(this::convertInternal, backgroundExecutorService) // .whenCompleteAsync((result, exception) -> { if (exception == null) { + vault.getVaultSettings().lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME); LOG.info("Conversion of vault {} succeeded.", vault.getPath()); window.setScene(successScene.get()); } else { diff --git a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java new file mode 100644 index 000000000..5107fe740 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java @@ -0,0 +1,90 @@ +package org.cryptomator.ui.dialogs; + +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.StageFactory; +import org.cryptomator.ui.controls.FontAwesome5Icon; +import org.cryptomator.ui.fxapp.FxApplicationScoped; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.collections.ObservableList; +import javafx.stage.Stage; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +@FxApplicationScoped +public class Dialogs { + + private final ResourceBundle resourceBundle; + private final StageFactory stageFactory; + + @Inject + public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory) { + this.resourceBundle = resourceBundle; + this.stageFactory = stageFactory; + } + + private static final Logger LOG = LoggerFactory.getLogger(Dialogs.class); + + private SimpleDialog.Builder createDialogBuilder() { + return new SimpleDialog.Builder(resourceBundle, stageFactory); + } + + public SimpleDialog.Builder prepareRemoveVaultDialog(Stage window, Vault vault, ObservableList vaults) { + return createDialogBuilder().setOwner(window) // + .setTitleKey("removeVault.title", vault.getDisplayName()) // + .setMessageKey("removeVault.message") // + .setDescriptionKey("removeVault.description") // + .setIcon(FontAwesome5Icon.QUESTION) // + .setOkButtonKey("removeVault.confirmBtn") // + .setCancelButtonKey("generic.button.cancel") // + .setOkAction(stage -> { + LOG.debug("Removing vault {}.", vault.getDisplayName()); + vaults.remove(vault); + stage.close(); + }); + } + + public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) { + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("removeCert.title") // + .setMessageKey("removeCert.message") // + .setDescriptionKey("removeCert.description") // + .setIcon(FontAwesome5Icon.QUESTION) // + .setOkButtonKey("removeCert.confirmBtn") // + .setCancelButtonKey("generic.button.cancel") // + .setOkAction(stage -> { + settings.licenseKey.set(null); + stage.close(); + }); + } + + public SimpleDialog.Builder prepareDokanySupportEndDialog(Stage window, Consumer cancelAction) { + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("dokanySupportEnd.title") // + .setMessageKey("dokanySupportEnd.message") // + .setDescriptionKey("dokanySupportEnd.description") // + .setIcon(FontAwesome5Icon.EXCLAMATION) // + .setOkButtonKey("generic.button.close") // + .setCancelButtonKey("dokanySupportEnd.preferencesBtn") // + .setOkAction(Stage::close) // + .setCancelAction(cancelAction); + } + + public SimpleDialog.Builder prepareRetryIfReadonlyDialog(Stage window, Consumer okAction) { + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("retryIfReadonly.title") // + .setMessageKey("retryIfReadonly.message") // + .setDescriptionKey("retryIfReadonly.description") // + .setIcon(FontAwesome5Icon.EXCLAMATION) // + .setOkButtonKey("retryIfReadonly.retry") // + .setCancelButtonKey("generic.button.close") // + .setOkAction(okAction) // + .setCancelAction(Stage::close); + } +} diff --git a/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java new file mode 100644 index 000000000..84d9e4f75 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java @@ -0,0 +1,140 @@ +package org.cryptomator.ui.dialogs; + +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; +import org.cryptomator.ui.common.StageFactory; +import org.cryptomator.ui.controls.FontAwesome5Icon; + +import javafx.scene.Scene; +import javafx.stage.Modality; +import javafx.stage.Stage; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.IllegalFormatException; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +public class SimpleDialog { + + private final ResourceBundle resourceBundle; + private final Stage dialogStage; + + SimpleDialog(Builder builder) throws IOException { + this.resourceBundle = builder.resourceBundle; + dialogStage = builder.stageFactory.create(); + dialogStage.initOwner(builder.owner); + dialogStage.initModality(Modality.WINDOW_MODAL); + dialogStage.setTitle(resolveText(builder.titleKey, builder.titleArgs)); + dialogStage.setResizable(false); + + FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( // + new SimpleDialogController(resolveText(builder.messageKey, null), // + resolveText(builder.descriptionKey, null), // + builder.icon, resolveText(builder.okButtonKey, null), // + resolveText(builder.cancelButtonKey, null), // + () -> builder.okAction.accept(dialogStage), // + () -> builder.cancelAction.accept(dialogStage)), // + Scene::new, builder.resourceBundle); + + dialogStage.setScene(new Scene(loaderFactory.load(FxmlFile.SIMPLE_DIALOG.getRessourcePathString()).getRoot())); + } + + public void showAndWait() { + dialogStage.showAndWait(); + } + + private String resolveText(String key, String[] args) { + if (key == null || key.isEmpty() || !resourceBundle.containsKey(key)) { + throw new IllegalArgumentException(String.format("Invalid key: '%s'. Key not found in ResourceBundle.", key)); + } + String text = resourceBundle.getString(key); + try { + return args != null && args.length > 0 ? String.format(text, (Object[]) args) : text; + } catch (IllegalFormatException e) { + throw new IllegalArgumentException("Formatting error: Check if arguments match placeholders in the text.", e); + } + } + + public static class Builder { + + private Stage owner; + private final ResourceBundle resourceBundle; + private final StageFactory stageFactory; + private String titleKey; + private String[] titleArgs; + private String messageKey; + private String descriptionKey; + private String okButtonKey; + private String cancelButtonKey; + + private FontAwesome5Icon icon; + private Consumer okAction = Stage::close; + private Consumer cancelAction = Stage::close; + + public Builder(ResourceBundle resourceBundle, StageFactory stageFactory) { + this.resourceBundle = resourceBundle; + this.stageFactory = stageFactory; + } + + public Builder setOwner(Stage owner) { + this.owner = owner; + return this; + } + + public Builder setTitleKey(String titleKey, String... args) { + this.titleKey = titleKey; + this.titleArgs = args; + return this; + } + + public Builder setMessageKey(String messageKey) { + this.messageKey = messageKey; + return this; + } + + public Builder setDescriptionKey(String descriptionKey) { + this.descriptionKey = descriptionKey; + return this; + } + + public Builder setIcon(FontAwesome5Icon icon) { + this.icon = icon; + return this; + } + + public Builder setOkButtonKey(String okButtonKey) { + this.okButtonKey = okButtonKey; + return this; + } + + public Builder setCancelButtonKey(String cancelButtonKey) { + this.cancelButtonKey = cancelButtonKey; + return this; + } + + public Builder setOkAction(Consumer okAction) { + this.okAction = okAction; + return this; + } + + public Builder setCancelAction(Consumer cancelAction) { + this.cancelAction = cancelAction; + return this; + } + + public SimpleDialog build() { + Objects.requireNonNull(titleKey, "SimpleDialog titleKey must be set."); + Objects.requireNonNull(messageKey, "SimpleDialog messageKey must be set."); + Objects.requireNonNull(descriptionKey, "SimpleDialog descriptionKey must be set."); + Objects.requireNonNull(okButtonKey, "SimpleDialog okButtonKey must be set."); + Objects.requireNonNull(cancelButtonKey, "SimpleDialog cancelButtonKey must be set."); + + try { + return new SimpleDialog(this); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create SimpleDialog.", e); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java new file mode 100644 index 000000000..0eee1b308 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java @@ -0,0 +1,61 @@ +package org.cryptomator.ui.dialogs; + +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.controls.FontAwesome5Icon; + +import javafx.fxml.FXML; + +public class SimpleDialogController implements FxController { + + private final String message; + private final String description; + private final FontAwesome5Icon icon; + private final String okButtonText; + private final String cancelButtonText; + private final Runnable okAction; + private final Runnable cancelAction; + + public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) { + this.message = message; + this.description = description; + this.icon = icon; + this.okButtonText = okButtonText; + this.cancelButtonText = cancelButtonText; + this.okAction = okAction; + this.cancelAction = cancelAction; + } + + public String getMessage() { + return message; + } + + public String getDescription() { + return description; + } + + public FontAwesome5Icon getIcon() { + return icon; + } + + public String getOkButtonText() { + return okButtonText; + } + + public String getCancelButtonText() { + return cancelButtonText; + } + + @FXML + private void handleOk() { + if (okAction != null) { + okAction.run(); + } + } + + @FXML + private void handleCancel() { + if (cancelAction != null) { + cancelAction.run(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndComponent.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndComponent.java deleted file mode 100644 index 48a5ab36c..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import dagger.Lazy; -import dagger.Subcomponent; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; - -import javafx.scene.Scene; -import javafx.stage.Stage; - -@DokanySupportEndScoped -@Subcomponent(modules = {DokanySupportEndModule.class}) -public interface DokanySupportEndComponent { - - @DokanySupportEndWindow - Stage window(); - - @FxmlScene(FxmlFile.DOKANY_SUPPORT_END) - Lazy dokanySupportEndScene(); - - - default void showDokanySupportEndWindow() { - Stage stage = window(); - stage.setScene(dokanySupportEndScene().get()); - stage.sizeToScene(); - stage.show(); - } - - @Subcomponent.Factory - interface Factory { - - DokanySupportEndComponent create(); - } -} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndController.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndController.java deleted file mode 100644 index a1d626402..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndController.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.preferences.SelectedPreferencesTab; - -import javax.inject.Inject; -import javafx.fxml.FXML; -import javafx.stage.Stage; - - -@DokanySupportEndScoped -public class DokanySupportEndController implements FxController { - - private final Stage window; - private final FxApplicationWindows applicationWindows; - - @Inject - DokanySupportEndController(@DokanySupportEndWindow Stage window, FxApplicationWindows applicationWindows) { - this.window = window; - this.applicationWindows = applicationWindows; - } - - @FXML - public void close() { - window.close(); - } - - public void openVolumePreferences() { - applicationWindows.showPreferencesWindow(SelectedPreferencesTab.VOLUME); - window.close(); - } - -} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndModule.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndModule.java deleted file mode 100644 index 64689eb26..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndModule.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; -import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.FxControllerKey; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.StageFactory; - -import javax.inject.Provider; -import javafx.scene.Scene; -import javafx.stage.Modality; -import javafx.stage.Stage; -import java.util.Map; -import java.util.ResourceBundle; - -@Module -abstract class DokanySupportEndModule { - - @Provides - @DokanySupportEndWindow - @DokanySupportEndScoped - static FxmlLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { - return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); - } - - @Provides - @DokanySupportEndWindow - @DokanySupportEndScoped - static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) { - Stage stage = factory.create(); - stage.setTitle(resourceBundle.getString("dokanySupportEnd.title")); - stage.setMinWidth(500); - stage.setMinHeight(100); - stage.initModality(Modality.APPLICATION_MODAL); - return stage; - } - - @Provides - @FxmlScene(FxmlFile.DOKANY_SUPPORT_END) - @DokanySupportEndScoped - static Scene provideDokanySupportEndScene(@DokanySupportEndWindow FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.DOKANY_SUPPORT_END); - } - - - @Binds - @IntoMap - @FxControllerKey(DokanySupportEndController.class) - abstract FxController bindDokanySupportEndController(DokanySupportEndController controller); - -} diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndScoped.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndScoped.java deleted file mode 100644 index 967e6f86f..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndScoped.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import javax.inject.Scope; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Scope -@Documented -@Retention(RetentionPolicy.RUNTIME) -@interface DokanySupportEndScoped { - -} diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndWindow.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndWindow.java deleted file mode 100644 index bb9b1617c..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndWindow.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import javax.inject.Qualifier; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Qualifier -@Documented -@Retention(RUNTIME) -@interface DokanySupportEndWindow { - -} diff --git a/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java b/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java index 719071ed2..65bc080ca 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java +++ b/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java @@ -43,8 +43,8 @@ public class AutoUnlocker { private CompletionStage unlockSequentially(Stream vaultStream) { // this is an attempt to run all the unlock workflows sequentially, i.e. start the next workflow only after completing/failing the previous workflow. return vaultStream.filter(Vault::isLocked).reduce(CompletableFuture.completedFuture(null), - (prevUnlock, nextVault) -> prevUnlock.thenCompose(unused -> appWindows.startUnlockWorkflow(nextVault, null)), - (prevUnlock, nextUnlock) -> nextUnlock.exceptionally(e -> null) // we don't care here about the exception, logged elsewhere + (prevUnlock, nextVault) -> prevUnlock.thenCompose(_ -> appWindows.startUnlockWorkflow(nextVault, null)), + (_, nextUnlock) -> nextUnlock.exceptionally(_ -> null) // we don't care here about the exception, logged elsewhere ); } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 0d542b8fe..af98e284c 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -7,14 +7,12 @@ package org.cryptomator.ui.fxapp; import dagger.Module; import dagger.Provides; -import org.cryptomator.ui.dokanysupportend.DokanySupportEndComponent; import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.health.HealthCheckComponent; import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.preferences.PreferencesComponent; import org.cryptomator.ui.quit.QuitComponent; -import org.cryptomator.ui.removecert.RemoveCertComponent; import org.cryptomator.ui.sharevault.ShareVaultComponent; import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.cryptomator.ui.unlock.UnlockComponent; @@ -35,8 +33,6 @@ import java.io.InputStream; ErrorComponent.class, // HealthCheckComponent.class, // UpdateReminderComponent.class, // - DokanySupportEndComponent.class, // - RemoveCertComponent.class, // ShareVaultComponent.class}) abstract class FxApplicationModule { diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index fcea61df6..4ea0ace1b 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -5,7 +5,8 @@ import dagger.Lazy; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.integrations.tray.TrayIntegrationProvider; -import org.cryptomator.ui.dokanysupportend.DokanySupportEndComponent; +import org.cryptomator.ui.dialogs.Dialogs; +import org.cryptomator.ui.dialogs.SimpleDialog; import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; @@ -49,13 +50,13 @@ public class FxApplicationWindows { private final QuitComponent.Builder quitWindowBuilder; private final UnlockComponent.Factory unlockWorkflowFactory; private final UpdateReminderComponent.Factory updateReminderWindowFactory; - private final DokanySupportEndComponent.Factory dokanySupportEndWindowBuilder; private final LockComponent.Factory lockWorkflowFactory; private final ErrorComponent.Factory errorWindowFactory; private final ExecutorService executor; private final VaultOptionsComponent.Factory vaultOptionsWindow; private final ShareVaultComponent.Factory shareVaultWindow; private final FilteredList visibleWindows; + private final Dialogs dialogs; @Inject public FxApplicationWindows(@PrimaryStage Stage primaryStage, // @@ -65,12 +66,12 @@ public class FxApplicationWindows { QuitComponent.Builder quitWindowBuilder, // UnlockComponent.Factory unlockWorkflowFactory, // UpdateReminderComponent.Factory updateReminderWindowFactory, // - DokanySupportEndComponent.Factory dokanySupportEndWindowBuilder, // LockComponent.Factory lockWorkflowFactory, // ErrorComponent.Factory errorWindowFactory, // VaultOptionsComponent.Factory vaultOptionsWindow, // ShareVaultComponent.Factory shareVaultWindow, // - ExecutorService executor) { + ExecutorService executor, // + Dialogs dialogs) { this.primaryStage = primaryStage; this.trayIntegration = trayIntegration; this.mainWindow = mainWindow; @@ -78,13 +79,13 @@ public class FxApplicationWindows { this.quitWindowBuilder = quitWindowBuilder; this.unlockWorkflowFactory = unlockWorkflowFactory; this.updateReminderWindowFactory = updateReminderWindowFactory; - this.dokanySupportEndWindowBuilder = dokanySupportEndWindowBuilder; this.lockWorkflowFactory = lockWorkflowFactory; this.errorWindowFactory = errorWindowFactory; this.executor = executor; this.vaultOptionsWindow = vaultOptionsWindow; this.shareVaultWindow = shareVaultWindow; this.visibleWindows = Window.getWindows().filtered(Window::isShowing); + this.dialogs = dialogs; } public void initialize() { @@ -93,17 +94,17 @@ public class FxApplicationWindows { // register preferences shortcut if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) { - desktop.setPreferencesHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ANY)); + desktop.setPreferencesHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ANY)); } // register preferences shortcut if (desktop.isSupported(Desktop.Action.APP_ABOUT)) { - desktop.setAboutHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ABOUT)); + desktop.setAboutHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ABOUT)); } // register app reopen listener if (desktop.isSupported(Desktop.Action.APP_EVENT_REOPENED)) { - desktop.addAppEventListener((AppReopenedListener) e -> showMainWindow()); + desktop.addAppEventListener((AppReopenedListener) _ -> showMainWindow()); } // observe visible windows @@ -135,11 +136,12 @@ public class FxApplicationWindows { } public CompletionStage showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) { - return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors); + return showMainWindow().thenApplyAsync(_ -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater) // + .whenComplete(this::reportErrors); } public void showQuitWindow(QuitResponse response, boolean forced) { - CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater); + CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response, forced), Platform::runLater); } public void showUpdateReminderWindow() { @@ -147,9 +149,15 @@ public class FxApplicationWindows { } public void showDokanySupportEndWindow() { - CompletableFuture.runAsync(() -> dokanySupportEndWindowBuilder.create().showDokanySupportEndWindow(), Platform::runLater); + CompletableFuture.runAsync(() -> createDokanySupportEndDialog().showAndWait(), Platform::runLater); } + private SimpleDialog createDokanySupportEndDialog() { + return dialogs.prepareDokanySupportEndDialog(mainWindow.get().window(), stage -> { + showPreferencesWindow(SelectedPreferencesTab.VOLUME); + stage.close(); + }).build(); + } public CompletionStage startUnlockWorkflow(Vault vault, @Nullable Stage owner) { return CompletableFuture.supplyAsync(() -> { @@ -157,8 +165,7 @@ public class FxApplicationWindows { LOG.debug("Start unlock workflow for {}", vault.getDisplayName()); return unlockWorkflowFactory.create(vault, owner).unlockWorkflow(); }, Platform::runLater) // - .thenAcceptAsync(UnlockWorkflow::run, executor) - .exceptionally(e -> { + .thenAcceptAsync(UnlockWorkflow::run, executor).exceptionally(e -> { showErrorWindow(e, owner == null ? primaryStage : owner, null); return null; }); diff --git a/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java index f3f0aff8e..b9af0f4a3 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java @@ -3,6 +3,8 @@ package org.cryptomator.ui.keyloading; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; +import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy; +import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,6 +30,33 @@ public interface KeyLoadingStrategy extends MasterkeyLoader { @Override Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException; + /** + * Determines whether the provided key loader scheme corresponds to a Hub Vault. + *

+ * This method compares the {@code keyLoader} parameter with the known Hub Vault schemes + * {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTP} and {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTPS}. + * + * @param keyLoader A string representing the key loader scheme to be checked. + * @return {@code true} if the given key loader scheme represents a Hub Vault; {@code false} otherwise. + */ + static boolean isHubVault(String keyLoader) { + return HubKeyLoadingStrategy.SCHEME_HUB_HTTP.equals(keyLoader) || HubKeyLoadingStrategy.SCHEME_HUB_HTTPS.equals(keyLoader); + } + + /** + * Determines whether the provided key loader scheme corresponds to a Masterkey File Vault. + *

+ * This method checks if the {@code keyLoader} parameter matches the known Masterkey File Vault scheme + * {@link MasterkeyFileLoadingStrategy#SCHEME}. + *

+ * + * @param keyLoader A string representing the key loader scheme to be checked. + * @return {@code true} if the given key loader scheme represents a Masterkey File Vault; {@code false} otherwise. + */ + static boolean isMasterkeyFileVault(String keyLoader) { + return MasterkeyFileLoadingStrategy.SCHEME.equals(keyLoader); + } + /** * Allows the loader to try and recover from an exception thrown during the last attempt. * diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java index eefad55a2..4697864f7 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java @@ -20,7 +20,7 @@ public class HubConfig { public String devicesResourceUrl; /** - * A collection of String template processors to construct URIs related to this Hub instance. + * A collection of functions to construct URIs related to this Hub instance. */ @JsonIgnore public final URIProcessors URIs = new URIProcessors(); @@ -49,14 +49,20 @@ public class HubConfig { public class URIProcessors { + public final URIProcessor API = this::fromApiEndpoint; + /** * Resolves paths relative to the /api/ endpoint of this Hub instance. */ - public final StringTemplate.Processor API = template -> { - var path = template.interpolate(); + public URI fromApiEndpoint(String path) { var relPath = path.startsWith("/") ? path.substring(1) : path; return getApiBaseUrl().resolve(relPath); - }; + } + } + @FunctionalInterface + public interface URIProcessor { + + URI resolve(String path); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java index 3bfb4ec8e..f94d882fa 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -88,7 +88,7 @@ public class ReceiveKeyController implements FxController { * STEP 0 (Request): GET /api/config */ private void requestApiConfig() { - var configUri = hubConfig.URIs.API."config"; + var configUri = hubConfig.URIs.API.resolve("config"); var request = HttpRequest.newBuilder(configUri) // .GET() // .timeout(REQ_TIMEOUT) // @@ -122,7 +122,7 @@ public class ReceiveKeyController implements FxController { * STEP 1 (Request): GET user key for this device */ private void requestDeviceData() { - var deviceUri = hubConfig.URIs.API."devices/\{deviceId}"; + var deviceUri = hubConfig.URIs.API.resolve("devices/" + deviceId); var request = HttpRequest.newBuilder(deviceUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // @@ -162,9 +162,10 @@ public class ReceiveKeyController implements FxController { * STEP 2 (Request): GET vault key for this user */ private void requestVaultMasterkey(String encryptedUserKey) { - var vaultKeyUri = hubConfig.URIs.API."vaults/\{vaultId}/access-token"; + var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token"); var request = HttpRequest.newBuilder(vaultKeyUri) // .header("Authorization", "Bearer " + bearerToken) // + .header("Hub-Device-ID", deviceId) // .GET() // .timeout(REQ_TIMEOUT) // .build(); @@ -205,7 +206,7 @@ public class ReceiveKeyController implements FxController { */ @Deprecated private void requestLegacyAccessToken() { - var legacyAccessTokenUri = hubConfig.URIs.API."vaults/\{vaultId}/keys/\{deviceId}"; + var legacyAccessTokenUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/keys/" + deviceId); var request = HttpRequest.newBuilder(legacyAccessTokenUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java index b00d49874..186d06208 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java @@ -115,7 +115,7 @@ public class RegisterDeviceController implements FxController { workInProgress.set(true); - var userReq = HttpRequest.newBuilder(hubConfig.URIs.API."users/me") // + var userReq = HttpRequest.newBuilder(hubConfig.URIs.API.resolve("users/me")) // .GET() // .timeout(REQ_TIMEOUT) // .header("Authorization", "Bearer " + bearerToken) // @@ -143,7 +143,7 @@ public class RegisterDeviceController implements FxController { var now = Instant.now().toString(); var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now); var json = toJson(dto); - var deviceUri = hubConfig.URIs.API."devices/\{deviceId}"; + var deviceUri = hubConfig.URIs.fromApiEndpoint("devices/" + deviceId); var putDeviceReq = HttpRequest.newBuilder(deviceUri) // .PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) // .timeout(REQ_TIMEOUT) // @@ -164,7 +164,7 @@ public class RegisterDeviceController implements FxController { private void migrateLegacyDevices(ECPublicKey userPublicKey) { try { // GET legacy access tokens - var getUri = hubConfig.URIs.API."devices/\{deviceId}/legacy-access-tokens"; + var getUri = hubConfig.URIs.API.resolve("devices/" + deviceId + "/legacy-access-tokens"); var getReq = HttpRequest.newBuilder(getUri).GET().timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build(); var getRes = httpClient.send(getReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (getRes.statusCode() != 200) { @@ -185,12 +185,12 @@ public class RegisterDeviceController implements FxController { LOG.warn("Failed to decrypt legacy access token for vault {}. Skipping migration.", entry.getKey()); } }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - var postUri = hubConfig.URIs.API."users/me/access-tokens"; + var postUri = hubConfig.URIs.fromApiEndpoint("users/me/access-tokens"); var postBody = JSON.writer().writeValueAsString(newAccessTokens); var postReq = HttpRequest.newBuilder(postUri).POST(HttpRequest.BodyPublishers.ofString(postBody)).timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build(); var postRes = httpClient.send(postReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (postRes.statusCode() != 200) { - throw new IOException(STR."Unexpected response from POST \{postUri}: \{postRes.statusCode()}"); + throw new IOException("Unexpected response from POST " + postUri + ": " + postRes.statusCode()); } } catch (IOException e) { // log and ignore: this is merely a best-effort attempt of migrating legacy devices. Failure is uncritical as this is merely a convenience feature. diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java index 999ff7882..13412dd27 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java @@ -1,13 +1,5 @@ package org.cryptomator.ui.mainwindow; -import javafx.beans.Observable; -import javafx.beans.binding.BooleanBinding; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.fxml.FXML; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; @@ -21,6 +13,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javafx.beans.Observable; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.layout.StackPane; +import javafx.stage.Screen; +import javafx.stage.Stage; @MainWindowScoped public class MainWindowController implements FxController { @@ -63,27 +64,46 @@ public class MainWindowController implements FxController { } window.focusedProperty().addListener(this::mainWindowFocusChanged); - if (!neverTouched()) { - window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight()); - window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth()); - window.setX(settings.windowXPosition.get()); - window.setY(settings.windowYPosition.get()); + int x = settings.windowXPosition.get(); + int y = settings.windowYPosition.get(); + int width = settings.windowWidth.get(); + int height = settings.windowHeight.get(); + if (windowPositionSaved(x, y, width, height) ) { + if(isWithinDisplayBounds(x, y, width, height)) { //use stored window position + window.setX(x); + window.setY(y); + window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth())); + window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight())); + } else if(isWithinDisplayBounds((int) window.getX(), (int) window.getY(), width, height)) { //just reset position of upper left corner, keep window size + window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth())); + window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight())); + } //else reset window completely } - window.widthProperty().addListener((_, _, _) -> savePositionalSettings()); - window.heightProperty().addListener((_, _, _) -> savePositionalSettings()); - window.xProperty().addListener((_, _, _) -> savePositionalSettings()); - window.yProperty().addListener((_, _, _) -> savePositionalSettings()); + + settings.windowXPosition.bind(window.xProperty()); + settings.windowYPosition.bind(window.yProperty()); + settings.windowWidth.bind(window.widthProperty()); + settings.windowHeight.bind(window.heightProperty()); } - private boolean neverTouched() { - return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0); + private boolean windowPositionSaved(int x, int y, int width, int height) { + return x != 0 || y != 0 || width != 0 || height != 0; } - public void savePositionalSettings() { - settings.windowWidth.setValue(window.getWidth()); - settings.windowHeight.setValue(window.getHeight()); - settings.windowXPosition.setValue(window.getX()); - settings.windowYPosition.setValue(window.getY()); + private boolean isWithinDisplayBounds(int x, int y, int width, int height) { + // define a rect which is inset on all sides from the window's rect: + final int shrinkedX = x + 20; // 20px left + final int shrinkedY = y + 5; // 5px top + final int shrinkedWidth = width - 40; // 20px left + 20px right + final int shrinkedHeigth = height - 25; // 5px top + 20px bottom + return isRectangleWithinBounds(shrinkedX, shrinkedY, 0, shrinkedHeigth) // Left pixel column + && isRectangleWithinBounds(shrinkedX + shrinkedWidth, shrinkedY, 0, shrinkedHeigth) // Right pixel column + && isRectangleWithinBounds(shrinkedX, shrinkedY, shrinkedWidth, 0) // Top pixel row + && isRectangleWithinBounds(shrinkedX, shrinkedY + shrinkedHeigth, shrinkedWidth, 0); // Bottom pixel row + } + + private boolean isRectangleWithinBounds(int x, int y, int width, int height) { + return !Screen.getScreensForRectangle(x, y, width, height).isEmpty(); } private void mainWindowFocusChanged(Observable observable) { diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index b563cf69c..a186fbe71 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -1,6 +1,7 @@ package org.cryptomator.ui.mainwindow; import dagger.Binds; +import dagger.Lazy; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; @@ -14,11 +15,12 @@ import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageInitializer; import org.cryptomator.ui.error.ErrorComponent; +import org.cryptomator.ui.fxapp.FxApplicationTerminator; import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.migration.MigrationComponent; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; -import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.stats.VaultStatisticsComponent; +import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent; import javax.inject.Named; @@ -31,17 +33,25 @@ import javafx.stage.Stage; import java.util.Map; import java.util.ResourceBundle; -@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class, RecoveryKeyComponent.class}) +@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class, RecoveryKeyComponent.class}) abstract class MainWindowModule { @Provides @MainWindow @MainWindowScoped - static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) { + static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer, FxApplicationTerminator terminator, Lazy trayMenu) { initializer.accept(stage); stage.setTitle("Cryptomator"); stage.setMinWidth(650); stage.setMinHeight(498); + stage.setOnCloseRequest(e -> { + if (!trayMenu.get().isInitialized()) { + terminator.terminate(); + e.consume(); + } else { + stage.close(); + } + }); return stage; } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java index 8363c6036..3815317a9 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java @@ -3,11 +3,12 @@ package org.cryptomator.ui.mainwindow; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; -import org.cryptomator.ui.removevault.RemoveVaultComponent; import javax.inject.Inject; import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.stage.FileChooser; import javafx.stage.Stage; @@ -20,20 +21,25 @@ import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB; public class VaultDetailMissingVaultController implements FxController { private final ObjectProperty vault; - private final RemoveVaultComponent.Builder removeVault; + private final ObservableList vaults; private final ResourceBundle resourceBundle; private final Stage window; private final RecoveryKeyComponent.Factory recoveryKeyWindow; - - + private final Dialogs dialogs; @Inject - public VaultDetailMissingVaultController(ObjectProperty vault, RemoveVaultComponent.Builder removeVault, ResourceBundle resourceBundle, @MainWindow Stage window, RecoveryKeyComponent.Factory recoveryKeyWindow) { + public VaultDetailMissingVaultController(ObjectProperty vault, // + ObservableList vaults, // + ResourceBundle resourceBundle, // + @MainWindow Stage window, // + Dialogs dialogs, // + RecoveryKeyComponent.Factory recoveryKeyWindow) { this.vault = vault; - this.removeVault = removeVault; + this.vaults = vaults; this.resourceBundle = resourceBundle; this.window = window; this.recoveryKeyWindow = recoveryKeyWindow; + this.dialogs = dialogs; } @FXML @@ -43,16 +49,16 @@ public class VaultDetailMissingVaultController implements FxController { @FXML void didClickRemoveVault() { - removeVault.vault(vault.get()).build().showRemoveVault(); + dialogs.prepareRemoveVaultDialog(window, vault.get(), vaults).build().showAndWait(); } @FXML - void restoreVaultConfig(){ + void restoreVaultConfig() { recoveryKeyWindow.create(vault.get(), window).showIsHubVaultDialogWindow(); } @FXML - void restoreMasterkey(){ + void restoreMasterkey() { recoveryKeyWindow.create(vault.get(), window).showRecoveryKeyRecoverWindow("Recover Masterkey"); } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java index 6e40d54b3..503e50ffd 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java @@ -3,12 +3,13 @@ package org.cryptomator.ui.mainwindow; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.removevault.RemoveVaultComponent; import javax.inject.Inject; import javax.inject.Named; import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.stage.Stage; @@ -18,14 +19,23 @@ public class VaultDetailUnknownErrorController implements FxController { private final ObjectProperty vault; private final FxApplicationWindows appWindows; private final Stage errorWindow; - private final RemoveVaultComponent.Builder removeVault; + private final ObservableList vaults; + private final Stage mainWindow; + private final Dialogs dialogs; @Inject - public VaultDetailUnknownErrorController(ObjectProperty vault, FxApplicationWindows appWindows, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) { + public VaultDetailUnknownErrorController(@MainWindow Stage mainWindow, // + ObjectProperty vault, // + ObservableList vaults, // + FxApplicationWindows appWindows, // + @Named("errorWindow") Stage errorWindow, // + Dialogs dialogs) { + this.mainWindow = mainWindow; this.vault = vault; + this.vaults = vaults; this.appWindows = appWindows; this.errorWindow = errorWindow; - this.removeVault = removeVault; + this.dialogs = dialogs; } @FXML @@ -40,6 +50,6 @@ public class VaultDetailUnknownErrorController implements FxController { @FXML void didClickRemoveVault() { - removeVault.vault(vault.get()).build().showRemoveVault(); + dialogs.prepareRemoveVaultDialog(mainWindow, vault.get(), vaults).build().showAndWait(); } } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java index 884171c8e..325b13d04 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java @@ -13,10 +13,16 @@ import javax.inject.Inject; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.layout.HBox; // unscoped because each cell needs its own controller public class VaultListCellController implements FxController { + private static final Insets COMPACT_INSETS = new Insets(6, 12, 6, 12); + private static final Insets DEFAULT_INSETS = new Insets(12); + private final ObjectProperty vault = new SimpleObjectProperty<>(); private final ObservableValue glyph; private final ObservableValue compactMode; @@ -25,6 +31,8 @@ public class VaultListCellController implements FxController { /* FXML */ public FontAwesome5IconView vaultStateView; + @FXML + public HBox vaultListCell; @Inject VaultListCellController(Settings settings) { @@ -37,6 +45,7 @@ public class VaultListCellController implements FxController { .onCondition(vault.flatMap(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals).orElse(false)) // .afterStop(() -> vaultStateView.setRotate(0)) // .build(); + this.vaultListCell.paddingProperty().bind(compactMode.map(c -> c ? COMPACT_INSETS : DEFAULT_INSETS)); } // TODO deduplicate w/ VaultDetailController diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java index 65838b96f..5a26967fa 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java @@ -5,8 +5,8 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; @@ -14,6 +14,7 @@ import javax.inject.Inject; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.stage.Stage; import java.util.EnumSet; @@ -35,23 +36,32 @@ public class VaultListContextMenuController implements FxController { private final FxApplicationWindows appWindows; private final VaultService vaultService; private final KeychainManager keychain; - private final RemoveVaultComponent.Builder removeVault; private final VaultOptionsComponent.Factory vaultOptionsWindow; private final ObservableValue selectedVaultState; private final ObservableValue selectedVaultPassphraseStored; private final ObservableValue selectedVaultRemovable; private final ObservableValue selectedVaultUnlockable; private final ObservableValue selectedVaultLockable; + private final ObservableList vaults; + private final Dialogs dialogs; @Inject - VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Factory vaultOptionsWindow) { + VaultListContextMenuController(ObjectProperty selectedVault, // + ObservableList vaults, // + @MainWindow Stage mainWindow, // + FxApplicationWindows appWindows, // + VaultService vaultService, // + KeychainManager keychain, // + VaultOptionsComponent.Factory vaultOptionsWindow, // + Dialogs dialogs) { this.selectedVault = selectedVault; + this.vaults = vaults; this.mainWindow = mainWindow; this.appWindows = appWindows; this.vaultService = vaultService; this.keychain = keychain; - this.removeVault = removeVault; this.vaultOptionsWindow = vaultOptionsWindow; + this.dialogs = dialogs; this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null); this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false); @@ -67,7 +77,7 @@ public class VaultListContextMenuController implements FxController { @FXML public void didClickRemoveVault() { var vault = Objects.requireNonNull(selectedVault.get()); - removeVault.vault(vault).build().showRemoveVault(); + dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait(); } @FXML diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index 4b9732e19..0c2c46204 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -9,9 +9,9 @@ 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.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.preferences.SelectedPreferencesTab; -import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +26,7 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.geometry.Side; +import javafx.scene.control.Button; import javafx.scene.control.ContextMenu; import javafx.scene.control.ListView; import javafx.scene.input.ContextMenuEvent; @@ -66,16 +67,17 @@ public class VaultListController implements FxController { private final VaultListCellFactory cellFactory; private final AddVaultWizardComponent.Builder addVaultWizard; private final BooleanBinding emptyVaultList; - private final RemoveVaultComponent.Builder removeVaultDialogue; private final VaultListManager vaultListManager; private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty(); private final ResourceBundle resourceBundle; private final FxApplicationWindows appWindows; private final ObservableValue cellSize; + private final Dialogs dialogs; + public ListView vaultList; public StackPane root; @FXML - private HBox addVaultButton; + private Button addVaultButton; @FXML private ContextMenu addVaultContextMenu; @@ -86,21 +88,21 @@ public class VaultListController implements FxController { VaultListCellFactory cellFactory, // VaultService vaultService, // AddVaultWizardComponent.Builder addVaultWizard, // - RemoveVaultComponent.Builder removeVaultDialogue, // VaultListManager vaultListManager, // ResourceBundle resourceBundle, // FxApplicationWindows appWindows, // - Settings settings) { + Settings settings, // + Dialogs dialogs) { this.mainWindow = mainWindow; this.vaults = vaults; this.selectedVault = selectedVault; this.cellFactory = cellFactory; this.vaultService = vaultService; this.addVaultWizard = addVaultWizard; - this.removeVaultDialogue = removeVaultDialogue; this.vaultListManager = vaultListManager; this.resourceBundle = resourceBundle; this.appWindows = appWindows; + this.dialogs = dialogs; this.emptyVaultList = Bindings.isEmpty(vaults); @@ -212,7 +214,7 @@ public class VaultListController implements FxController { private void pressedShortcutToRemoveVault() { final var vault = selectedVault.get(); if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) { - removeVaultDialogue.vault(vault).build().showRemoveVault(); + dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait(); } } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java b/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java index ccfbd3ad5..0972466dc 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java @@ -16,7 +16,7 @@ import javafx.fxml.FXML; public class WelcomeController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class); - private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/en/1.7/desktop/getting-started/"; + private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/desktop/getting-started/"; private final Application application; private final BooleanBinding noVaultPresent; diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java index 893ef3577..2ad93c1ab 100644 --- a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java +++ b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java @@ -10,7 +10,7 @@ import javafx.stage.Stage; public class MigrationImpossibleController implements FxController { - private static final String HELP_URI = "https://docs.cryptomator.org/en/1.7/help/manual-migration/"; + private static final String HELP_URI = "https://docs.cryptomator.org/help/manual-migration/"; private final Application application; private final Stage window; diff --git a/src/main/java/org/cryptomator/ui/preferences/SupporterCertificateController.java b/src/main/java/org/cryptomator/ui/preferences/SupporterCertificateController.java index e587e5f27..b36d7454e 100644 --- a/src/main/java/org/cryptomator/ui/preferences/SupporterCertificateController.java +++ b/src/main/java/org/cryptomator/ui/preferences/SupporterCertificateController.java @@ -5,7 +5,7 @@ import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.UiTheme; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.removecert.RemoveCertComponent; +import org.cryptomator.ui.dialogs.Dialogs; import javax.inject.Inject; import javafx.application.Application; @@ -15,6 +15,7 @@ import javafx.scene.control.TextArea; import javafx.scene.control.TextFormatter; import javafx.stage.Stage; + @PreferencesScoped public class SupporterCertificateController implements FxController { @@ -26,18 +27,22 @@ public class SupporterCertificateController implements FxController { private final Stage window; private final LicenseHolder licenseHolder; private final Settings settings; - private final RemoveCertComponent.Builder removeCert; + private final Dialogs dialogs; @FXML private TextArea supporterCertificateField; @Inject - SupporterCertificateController(Application application, @PreferencesWindow Stage window, LicenseHolder licenseHolder, Settings settings, RemoveCertComponent.Builder removeCert) { + SupporterCertificateController(Application application, // + @PreferencesWindow Stage window, // + LicenseHolder licenseHolder, // + Settings settings, // + Dialogs dialogs) { this.application = application; this.window = window; this.licenseHolder = licenseHolder; this.settings = settings; - this.removeCert = removeCert; + this.dialogs = dialogs; } @FXML @@ -84,10 +89,11 @@ public class SupporterCertificateController implements FxController { @FXML void didClickRemoveCert() { - removeCert.build().showRemoveCert(window); + dialogs.prepareRemoveCertDialog(window, settings).build().showAndWait(); } public LicenseHolder getLicenseHolder() { return licenseHolder; } + } diff --git a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java index afa05cc8c..f5a72290f 100644 --- a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java @@ -19,6 +19,8 @@ import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; import javafx.scene.control.ContentDisplay; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; @@ -32,7 +34,10 @@ import java.util.ResourceBundle; @PreferencesScoped public class UpdatesPreferencesController implements FxController { - private static final String DOWNLOADS_URI = "https://cryptomator.org/downloads"; + private static final String DOWNLOADS_URI_TEMPLATE = "https://cryptomator.org/downloads/" // + + "?utm_source=cryptomator-desktop" // + + "&utm_medium=update-notification&" // + + "utm_campaign=app-update-%s"; private final Application application; private final Environment environment; @@ -50,6 +55,7 @@ public class UpdatesPreferencesController implements FxController { private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false); private final DateTimeFormatter formatter; private final BooleanBinding upToDate; + private final String downloadsUri; /* FXML */ public CheckBox checkForUpdatesCheckbox; @@ -65,12 +71,13 @@ public class UpdatesPreferencesController implements FxController { this.latestVersion = updateChecker.latestVersionProperty(); this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty(); this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck); - this.currentVersion = updateChecker.getCurrentVersion(); + this.currentVersion = environment.getAppVersion(); 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); + this.downloadsUri = DOWNLOADS_URI_TEMPLATE.formatted(URLEncoder.encode(currentVersion, StandardCharsets.US_ASCII)); } public void initialize() { @@ -93,7 +100,7 @@ public class UpdatesPreferencesController implements FxController { @FXML public void visitDownloadsPage() { - application.getHostServices().showDocument(DOWNLOADS_URI); + application.getHostServices().showDocument(downloadsUri); } @FXML diff --git a/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java index 8cb49a679..538d0435b 100644 --- a/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java @@ -22,7 +22,7 @@ import java.util.ResourceBundle; @PreferencesScoped public class VolumePreferencesController implements FxController { - public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/"; + public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/desktop/volume-type/"; public static final int MIN_PORT = 1024; public static final int MAX_PORT = 65535; diff --git a/src/main/java/org/cryptomator/ui/removecert/RemoveCertComponent.java b/src/main/java/org/cryptomator/ui/removecert/RemoveCertComponent.java deleted file mode 100644 index 40fc867f9..000000000 --- a/src/main/java/org/cryptomator/ui/removecert/RemoveCertComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.ui.removecert; - -import dagger.Lazy; -import dagger.Subcomponent; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; - -import javafx.scene.Scene; -import javafx.stage.Stage; - -@RemoveCertScoped -@Subcomponent(modules = {RemoveCertModule.class}) -public interface RemoveCertComponent { - - @RemoveCertWindow - Stage window(); - - @FxmlScene(FxmlFile.REMOVE_CERT) - Lazy scene(); - - default void showRemoveCert(Stage owner) { - Stage stage = window(); - stage.setScene(scene().get()); - stage.sizeToScene(); - stage.initOwner(owner); - stage.show(); - } - - @Subcomponent.Builder - interface Builder { - RemoveCertComponent build(); - } - -} diff --git a/src/main/java/org/cryptomator/ui/removecert/RemoveCertController.java b/src/main/java/org/cryptomator/ui/removecert/RemoveCertController.java deleted file mode 100644 index 2ac08a02d..000000000 --- a/src/main/java/org/cryptomator/ui/removecert/RemoveCertController.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.cryptomator.ui.removecert; - -import org.cryptomator.common.settings.Settings; -import org.cryptomator.ui.common.FxController; - -import javax.inject.Inject; -import javafx.fxml.FXML; -import javafx.stage.Stage; - -@RemoveCertScoped -public class RemoveCertController implements FxController { - - private final Stage window; - private final Settings settings; - - @Inject - public RemoveCertController(@RemoveCertWindow Stage window, Settings settings) { - this.window = window; - this.settings = settings; - } - - @FXML - public void close() { - window.close(); - } - - @FXML - public void remove() { - settings.licenseKey.set(null); - window.close(); - } -} diff --git a/src/main/java/org/cryptomator/ui/removecert/RemoveCertModule.java b/src/main/java/org/cryptomator/ui/removecert/RemoveCertModule.java deleted file mode 100644 index fb4b8d22a..000000000 --- a/src/main/java/org/cryptomator/ui/removecert/RemoveCertModule.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.cryptomator.ui.removecert; - -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; -import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.FxControllerKey; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.StageFactory; - -import javax.inject.Provider; -import javafx.scene.Scene; -import javafx.stage.Modality; -import javafx.stage.Stage; -import java.util.Map; -import java.util.ResourceBundle; - -@Module -abstract class RemoveCertModule { - - @Provides - @RemoveCertWindow - @RemoveCertScoped - static FxmlLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { - return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); - } - - @Provides - @RemoveCertWindow - @RemoveCertScoped - static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) { - Stage stage = factory.create(); - stage.setTitle(resourceBundle.getString("removeCert.title")); - stage.setResizable(false); - stage.initModality(Modality.WINDOW_MODAL); - return stage; - } - - @Provides - @FxmlScene(FxmlFile.REMOVE_CERT) - @RemoveCertScoped - static Scene provideRemoveCertScene(@RemoveCertWindow FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.REMOVE_CERT); - } - - // ------------------ - - @Binds - @IntoMap - @FxControllerKey(RemoveCertController.class) - abstract FxController bindRemoveCertController(RemoveCertController controller); -} diff --git a/src/main/java/org/cryptomator/ui/removecert/RemoveCertScoped.java b/src/main/java/org/cryptomator/ui/removecert/RemoveCertScoped.java deleted file mode 100644 index d7df8b80c..000000000 --- a/src/main/java/org/cryptomator/ui/removecert/RemoveCertScoped.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cryptomator.ui.removecert; - -import javax.inject.Scope; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Scope -@Documented -@Retention(RetentionPolicy.RUNTIME) -public @interface RemoveCertScoped { - -} diff --git a/src/main/java/org/cryptomator/ui/removecert/RemoveCertWindow.java b/src/main/java/org/cryptomator/ui/removecert/RemoveCertWindow.java deleted file mode 100644 index cd226d794..000000000 --- a/src/main/java/org/cryptomator/ui/removecert/RemoveCertWindow.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.cryptomator.ui.removecert; - -import javax.inject.Qualifier; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Qualifier -@Documented -@Retention(RUNTIME) -@interface RemoveCertWindow { - -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultComponent.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultComponent.java deleted file mode 100644 index c8b3fd574..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultComponent.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.cryptomator.ui.removevault; - -import dagger.BindsInstance; -import dagger.Lazy; -import dagger.Subcomponent; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; - -import javafx.scene.Scene; -import javafx.stage.Stage; - -@RemoveVaultScoped -@Subcomponent(modules = {RemoveVaultModule.class}) -public interface RemoveVaultComponent { - - @RemoveVaultWindow - Stage window(); - - @FxmlScene(FxmlFile.REMOVE_VAULT) - Lazy scene(); - - default void showRemoveVault() { - Stage stage = window(); - stage.setScene(scene().get()); - stage.sizeToScene(); - stage.show(); - } - - @Subcomponent.Builder - interface Builder { - - @BindsInstance - Builder vault(@RemoveVaultWindow Vault vault); - - RemoveVaultComponent build(); - } - -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultController.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultController.java deleted file mode 100644 index e486d65f0..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultController.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cryptomator.ui.removevault; - -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.common.FxController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.stage.Stage; - -@RemoveVaultScoped -public class RemoveVaultController implements FxController { - - private static final Logger LOG = LoggerFactory.getLogger(RemoveVaultController.class); - - private final Stage window; - private final Vault vault; - private final ObservableList vaults; - - @Inject - public RemoveVaultController(@RemoveVaultWindow Stage window, @RemoveVaultWindow Vault vault, ObservableList vaults) { - this.window = window; - this.vault = vault; - this.vaults = vaults; - } - - @FXML - public void close() { - window.close(); - } - - @FXML - public void finish() { - vaults.remove(vault); - LOG.debug("Removing vault {}.", vault.getDisplayName()); - window.close(); - } -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java deleted file mode 100644 index 2fcac4add..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.cryptomator.ui.removevault; - -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.FxControllerKey; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.fxapp.PrimaryStage; - -import javax.inject.Provider; -import javafx.scene.Scene; -import javafx.stage.Modality; -import javafx.stage.Stage; -import java.util.Map; -import java.util.ResourceBundle; - -@Module -abstract class RemoveVaultModule { - - @Provides - @RemoveVaultWindow - @RemoveVaultScoped - static FxmlLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { - return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); - } - - @Provides - @RemoveVaultWindow - @RemoveVaultScoped - static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, @RemoveVaultWindow Vault vault, ResourceBundle resourceBundle) { - Stage stage = factory.create(); - stage.setTitle(String.format(resourceBundle.getString("removeVault.title"), vault.getDisplayName())); - stage.setResizable(false); - stage.initModality(Modality.WINDOW_MODAL); - stage.initOwner(primaryStage); - return stage; - } - - @Provides - @FxmlScene(FxmlFile.REMOVE_VAULT) - @RemoveVaultScoped - static Scene provideRemoveVaultScene(@RemoveVaultWindow FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.REMOVE_VAULT); - } - - // ------------------ - - @Binds - @IntoMap - @FxControllerKey(RemoveVaultController.class) - abstract FxController bindRemoveVaultController(RemoveVaultController controller); -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultScoped.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultScoped.java deleted file mode 100644 index aa2281541..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultScoped.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cryptomator.ui.removevault; - -import javax.inject.Scope; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Scope -@Documented -@Retention(RetentionPolicy.RUNTIME) -public @interface RemoveVaultScoped { - -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultWindow.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultWindow.java deleted file mode 100644 index 7348e213b..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultWindow.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.cryptomator.ui.removevault; - -import javax.inject.Qualifier; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Qualifier -@Documented -@Retention(RUNTIME) -@interface RemoveVaultWindow { - -} diff --git a/src/main/java/org/cryptomator/ui/sharevault/ShareVaultController.java b/src/main/java/org/cryptomator/ui/sharevault/ShareVaultController.java index 63230cbbf..859c9e38d 100644 --- a/src/main/java/org/cryptomator/ui/sharevault/ShareVaultController.java +++ b/src/main/java/org/cryptomator/ui/sharevault/ShareVaultController.java @@ -3,7 +3,7 @@ package org.cryptomator.ui.sharevault; import dagger.Lazy; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy; +import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import javax.inject.Inject; import javafx.application.Application; @@ -33,8 +33,7 @@ public class ShareVaultController implements FxController { this.window = window; this.application = application; this.vault = vault; - var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme(); - this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS)); + this.hubVault = KeyLoadingStrategy.isHubVault(vault.getVaultSettings().lastKnownKeyLoader.get()); } @FXML diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 804f4cd67..4b78bf693 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -10,6 +10,7 @@ import org.cryptomator.integrations.mount.MountFailedException; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; @@ -25,6 +26,8 @@ import javafx.scene.Scene; import javafx.stage.Screen; import javafx.stage.Stage; import java.io.IOException; +import java.nio.file.ReadOnlyFileSystemException; +import java.util.concurrent.TimeUnit; /** * A multi-step task that consists of background activities as well as user interaction. @@ -46,6 +49,7 @@ public class UnlockWorkflow extends Task { private final FxApplicationWindows appWindows; private final KeyLoadingStrategy keyLoadingStrategy; private final ObjectProperty illegalMountPointException; + private final Dialogs dialogs; @Inject UnlockWorkflow(@PrimaryStage Stage mainWindow, // @@ -57,7 +61,8 @@ public class UnlockWorkflow extends Task { @FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART) Lazy restartRequiredScene, // FxApplicationWindows appWindows, // @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, // - @UnlockWindow ObjectProperty illegalMountPointException) { + @UnlockWindow ObjectProperty illegalMountPointException, // + Dialogs dialogs) { this.mainWindow = mainWindow; this.window = window; this.vault = vault; @@ -68,6 +73,7 @@ public class UnlockWorkflow extends Task { this.appWindows = appWindows; this.keyLoadingStrategy = keyLoadingStrategy; this.illegalMountPointException = illegalMountPointException; + this.dialogs = dialogs; } @Override @@ -144,11 +150,36 @@ public class UnlockWorkflow extends Task { switch (throwable) { case IllegalMountPointException e -> handleIllegalMountPointError(e); case ConflictingMountServiceException _ -> handleConflictingMountServiceException(); + case ReadOnlyFileSystemException _ -> handleReadOnlyFileSystem(); default -> handleGenericError(throwable); } vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED); } + private void handleReadOnlyFileSystem() { + var readOnlyDialog = dialogs.prepareRetryIfReadonlyDialog(mainWindow, stage -> { + stage.close(); + this.retry(); + }).build(); + + Platform.runLater(readOnlyDialog::showAndWait); + } + + private void retry() { + try { + vault.getVaultSettings().usesReadOnlyMode.set(true); + var isLocked = vault.stateProperty().awaitState(VaultState.Value.LOCKED, 5, TimeUnit.SECONDS); + if (!isLocked) { + LOG.error("Vault did not changed to LOCKED state within 5 seconds. Aborting unlock retry."); + } else { + appWindows.startUnlockWorkflow(vault, mainWindow); + } + } catch (InterruptedException e) { + LOG.error("Waiting for LOCKED vault state was interrupted. Aborting unlock retry.", e); + Thread.currentThread().interrupt(); + } + } + @Override protected void cancelled() { LOG.debug("Unlock of '{}' canceled.", vault.getDisplayName()); diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index 106623985..a9a2042a3 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -17,6 +17,8 @@ import org.cryptomator.ui.preferences.VolumePreferencesController; import javax.inject.Inject; import javafx.application.Application; import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -50,7 +52,6 @@ public class MountOptionsController implements FxController { private final ObservableValue defaultMountFlags; private final ObservableValue mountpointDirSupported; private final ObservableValue mountpointDriveLetterSupported; - private final ObservableValue readOnlySupported; private final ObservableValue mountFlagsSupported; private final ObservableValue defaultMountServiceSelected; private final ObservableValue directoryPath; @@ -60,6 +61,7 @@ public class MountOptionsController implements FxController { private final ObservableValue selectedMountService; private final ObservableValue selectedMountServiceRequiresRestart; private final ObservableValue loopbackPortChangeable; + private final ObservableBooleanValue readOnlyOptionAllowed; //-- FXML objects -- @@ -108,10 +110,10 @@ public class MountOptionsController implements FxController { }); this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS)); this.defaultMountServiceSelected = ObservableUtil.mapWithDefault(vaultSettings.mountService, _ -> false, true); - this.readOnlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY)); this.mountpointDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT)); this.mountpointDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER)); this.loopbackPortChangeable = selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT) && vaultSettings.mountService.getValue() != null); + this.readOnlyOptionAllowed = BooleanBinding.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY))).or(vaultSettings.usesReadOnlyMode); } private MountService reselectMountService() { @@ -345,12 +347,12 @@ public class MountOptionsController implements FxController { return mountpointDriveLetterSupported.getValue(); } - public ObservableValue readOnlySupportedProperty() { - return readOnlySupported; + public ObservableValue readOnlyOptionAllowedProperty() { + return readOnlyOptionAllowed; } - public boolean isReadOnlySupported() { - return readOnlySupported.getValue(); + public boolean isReadOnlyOptionAllowed() { + return readOnlyOptionAllowed.getValue(); } public ObservableValue directoryPathProperty() { diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java index 78d228995..154eb30dd 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java @@ -3,6 +3,7 @@ package org.cryptomator.ui.vaultoptions; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy; import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy; import org.slf4j.Logger; @@ -42,11 +43,11 @@ public class VaultOptionsController implements FxController { window.setOnShowing(this::windowWillAppear); selectedTabProperty.addListener(observable -> this.selectChosenTab()); tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged()); - var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme(); - if(!vaultScheme.equals(MasterkeyFileLoadingStrategy.SCHEME)){ + var vaultKeyLoader = vault.getVaultSettings().lastKnownKeyLoader.get(); + if(!KeyLoadingStrategy.isMasterkeyFileVault(vaultKeyLoader)){ tabPane.getTabs().remove(keyTab); } - if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){ + if(!KeyLoadingStrategy.isHubVault(vaultKeyLoader)){ tabPane.getTabs().remove(hubTab); } diff --git a/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java b/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java index cc91d1afd..bd9535d1d 100644 --- a/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java +++ b/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java @@ -15,7 +15,7 @@ import java.io.UncheckedIOException; @WrongFileAlertScoped public class WrongFileAlertController implements FxController { - private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/en/1.7/desktop/accessing-vaults/"; + private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/desktop/accessing-vaults/"; private final Application app; private final Stage window; diff --git a/src/main/resources/css/dark_theme.css b/src/main/resources/css/dark_theme.css index dae0d1898..abb6efe57 100644 --- a/src/main/resources/css/dark_theme.css +++ b/src/main/resources/css/dark_theme.css @@ -183,19 +183,37 @@ } .main-window .button-bar { + -fx-min-height:42px; + -fx-max-height:42px; -fx-background-color: MAIN_BG; -fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent; -fx-border-width: 1px 0 0 0; } -.main-window .button-left { +.main-window .button-bar .button-left { -fx-border-color: CONTROL_BORDER_NORMAL; -fx-border-width: 0 1px 0 0; + -fx-background-color: MAIN_BG; + -fx-background-radius: 0px; + -fx-min-height: 42px; + -fx-max-height: 42px; } -.main-window .button-right { +.main-window .button-bar .button-right { -fx-border-color: CONTROL_BORDER_NORMAL; -fx-border-width: 0 0 0 1px; + -fx-background-color: MAIN_BG; + -fx-background-radius: 0px; + -fx-min-height: 42px; + -fx-max-height: 42px; +} + +.main-window .button-bar .button-left:armed { + -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED; +} + +.main-window .button-bar .button-right:armed { + -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED; } /******************************************************************************* diff --git a/src/main/resources/css/light_theme.css b/src/main/resources/css/light_theme.css index 84df05b10..516dd0b26 100644 --- a/src/main/resources/css/light_theme.css +++ b/src/main/resources/css/light_theme.css @@ -182,6 +182,8 @@ } .main-window .button-bar { + -fx-min-height:42px; + -fx-max-height:42px; -fx-background-color: MAIN_BG; -fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent; -fx-border-width: 1px 0 0 0; @@ -190,11 +192,27 @@ .main-window .button-bar .button-left { -fx-border-color: CONTROL_BORDER_NORMAL; -fx-border-width: 0 1px 0 0; + -fx-background-color: MAIN_BG; + -fx-background-radius: 0px; + -fx-min-height: 42px; + -fx-max-height: 42px; } .main-window .button-bar .button-right { -fx-border-color: CONTROL_BORDER_NORMAL; -fx-border-width: 0 0 0 1px; + -fx-background-color: MAIN_BG; + -fx-background-radius: 0px; + -fx-min-height: 42px; + -fx-max-height: 42px; +} + +.main-window .button-bar .button-left:armed { + -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED; +} + +.main-window .button-bar .button-right:armed { + -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED; } /******************************************************************************* diff --git a/src/main/resources/fxml/dokany_support_end.fxml b/src/main/resources/fxml/dokany_support_end.fxml deleted file mode 100644 index 423a54c72..000000000 --- a/src/main/resources/fxml/dokany_support_end.fxml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/vault_list_cell.fxml b/src/main/resources/fxml/vault_list_cell.fxml index c6cc8cf24..20df9b924 100644 --- a/src/main/resources/fxml/vault_list_cell.fxml +++ b/src/main/resources/fxml/vault_list_cell.fxml @@ -1,22 +1,19 @@ - - - - diff --git a/src/main/resources/fxml/vault_options_mount.fxml b/src/main/resources/fxml/vault_options_mount.fxml index d5df2534f..4f6295af8 100644 --- a/src/main/resources/fxml/vault_options_mount.fxml +++ b/src/main/resources/fxml/vault_options_mount.fxml @@ -54,7 +54,7 @@