diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 9ced33c15..a82e89ba7 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -26,6 +26,7 @@ body: Examples: - Operating System: Windows 10 - Cryptomator: 1.5.16 + - OneDrive: 23.226 - LibreOffice: 7.1.4 value: | - Operating System: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0e12bbba9..3d2f42f8d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,7 +6,7 @@ updates: interval: "weekly" day: "monday" time: "06:00" - timezone: "UTC" + timezone: "Etc/UTC" groups: java-test-dependencies: patterns: diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 68127dbb6..80aa111cf 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -38,7 +38,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} @@ -84,7 +84,6 @@ jobs: --no-header-files --no-man-pages --strip-debug - --compress=1 - name: Prepare additional launcher run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties env: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d1eb5739..dc575baca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} diff --git a/.github/workflows/check-jdk-updates.yml b/.github/workflows/check-jdk-updates.yml index 30954b9e4..b1cdca4bb 100644 --- a/.github/workflows/check-jdk-updates.yml +++ b/.github/workflows/check-jdk-updates.yml @@ -15,7 +15,7 @@ jobs: outputs: jdk-date: ${{ steps.get-data.outputs.jdk-date}} steps: - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: java-version: ${{ env.JDK_VERSION }} distribution: ${{ env.JDK_VENDOR }} @@ -32,7 +32,7 @@ jobs: jdk-date: ${{ steps.get-data.outputs.jdk-date}} jdk-version: ${{ steps.get-data.outputs.jdk-version}} steps: - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: java-version: 21 distribution: ${{ env.JDK_VENDOR }} diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index f1cffb96b..00b49f4fc 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -46,7 +46,7 @@ jobs: sudo apt-get update sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }} libgtk2.0-0 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml new file mode 100644 index 000000000..590688b7d --- /dev/null +++ b/.github/workflows/dependency-check.yml @@ -0,0 +1,56 @@ +name: OWASP Maven Dependency Check +on: + schedule: + - cron: '0 8 * * 0' + workflow_dispatch: + + +jobs: + check-dependencies: + name: Check dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + show-progress: false + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + cache: 'maven' + - name: Cache NVD DB + uses: actions/cache@v3 + with: + path: ~/.m2/repository/org/owasp/dependency-check-data/ + key: dependency-check-${{ github.run_id }} + restore-keys: | + dependency-check + env: + SEGMENT_DOWNLOAD_TIMEOUT_MINS: 5 + - name: Run org.owasp:dependency-check plugin + id: dependency-check + continue-on-error: true + run: mvn -B validate -Pdependency-check + env: + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} + - name: Upload report on failure + if: steps.dependency-check.outcome == 'failure' + uses: actions/upload-artifact@v3 + with: + name: dependency-check-report + path: target/dependency-check-report.html + if-no-files-found: error + - name: Slack Notification on regular check + if: github.event_name == 'schedule' && steps.dependency-check.outcome == 'failure' + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_USERNAME: 'Cryptobot' + SLACK_ICON: false + SLACK_ICON_EMOJI: ':bot:' + SLACK_CHANNEL: 'cryptomator-desktop' + SLACK_TITLE: "Vulnerabilities in ${{ github.event.repository.name }} detected." + SLACK_MESSAGE: "Download the for more details." + SLACK_FOOTER: false + MSG_MINIMAL: true diff --git a/.github/workflows/dl-stats.yml b/.github/workflows/dl-stats.yml index dc87a2bbd..b16899520 100644 --- a/.github/workflows/dl-stats.yml +++ b/.github/workflows/dl-stats.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Get download count of latest releases id: get-stats - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const query = `query($owner:String!, $name:String!) { diff --git a/.github/workflows/error-db.yml b/.github/workflows/error-db.yml index e885af4a2..301713681 100644 --- a/.github/workflows/error-db.yml +++ b/.github/workflows/error-db.yml @@ -14,7 +14,7 @@ jobs: - name: Query Discussion Data if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted' id: query-data - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) { diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 1bed1cff8..ae2b60b4b 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -39,7 +39,7 @@ jobs: with: fetch-depth: 0 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 666e9f19c..f96ded641 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -49,7 +49,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} @@ -95,7 +95,6 @@ jobs: --no-header-files --no-man-pages --strip-debug - --compress=1 - name: Run jpackage run: > ${JAVA_HOME}/bin/jpackage diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index a8f4c5617..2730f5c24 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -18,7 +18,7 @@ jobs: if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index 1bbfb5d1a..1bbbc3c4d 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -10,12 +10,22 @@ defaults: run: shell: bash +env: + JAVA_DIST: 'zulu' + JAVA_VERSION: 21 + jobs: - release-check-precondition: + check-preconditions: name: Validate commits pushed to release/hotfix branch to fulfill release requirements runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: ${{ env.JAVA_DIST }} + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' - id: validate-pom-version name: Validate POM version run: | @@ -37,4 +47,19 @@ jobs: if ! grep -q "" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml" exit 1 - fi \ No newline at end of file + fi + - name: Cache NVD DB + uses: actions/cache@v3 + with: + path: ~/.m2/repository/org/owasp/dependency-check-data/ + key: dependency-check-${{ github.run_id }} + restore-keys: | + dependency-check + env: + SEGMENT_DOWNLOAD_TIMEOUT_MINS: 5 + - name: Run org.owasp:dependency-check plugin + id: dependency-check + continue-on-error: true + run: mvn -B verify -Pdependency-check -DskipTests + env: + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} \ No newline at end of file diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 1002718d0..79be3fd6e 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -19,7 +19,7 @@ env: OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77' 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/download/1.0.0-beta9/winfsp-uninstaller.exe' + WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0/winfsp-uninstaller.exe' defaults: run: @@ -41,7 +41,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} @@ -89,7 +89,6 @@ jobs: --no-header-files --no-man-pages --strip-debug - --compress=1 - name: Change win-console flag if debug is active if: ${{ inputs.isDebug }} run: echo "WIN_CONSOLE_FLAG=--win-console" >> $GITHUB_ENV @@ -275,7 +274,7 @@ jobs: path: dist/win/bundle/resources - name: Strip version info from msi file name run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} diff --git a/README.md b/README.md index ec021ab5d..560f2e4f8 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ For more information on the security details visit [cryptomator.org](https://doc ### Dependencies -* JDK 19 (e.g. temurin) +* JDK 21 (e.g. temurin, zulu) * Maven 3 ### Run Maven diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 0a4b7f65d..9b056afa5 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -8,11 +8,14 @@ REVISION_NO=`git rev-list --count HEAD` if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; } command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; } +command -v unzip >/dev/null 2>&1 || { echo >&2 "unzip not found."; exit 1; } VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout) SEMVER_STR=${VERSION} MACHINE_TYPE=$(uname -m) +if [[ ! "${MACHINE_TYPE}" =~ x86_64|aarch64 ]]; then echo "Platform ${MACHINE_TYPE} not supported"; exit 1; fi + mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR} # compile @@ -20,17 +23,44 @@ mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests cp ../../../LICENSE.txt ../../../target cp ../../../target/cryptomator-*.jar ../../../target/mods + +# download javaFX jmods +OPENJFX_URL='https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip' #by default we assume x64 +OPENJFX_SHA='f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c' +OPENJFX_URL_aarch64='https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip' +OPENJFX_SHA_aarch64='c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca' + +if [[ "${MACHINE_TYPE}" = "aarch64" ]]; then + OPENJFX_URL="${OPENJFX_URL_aarch64}"; + OPENJFX_SHA="${OPENJFX_SHA_aarch64}"; +fi + +curl -L ${OPENJFX_URL} -o openjfx-jmods.zip +echo "${OPENJFX_SHA} openjfx-jmods.zip" | shasum -a256 --check +mkdir -p openjfx-jmods +unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods +JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) +JMOD_VERSION=${JMOD_VERSION#*@} +JMOD_VERSION=${JMOD_VERSION%%.*} +POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) +POM_JFX_VERSION=${POM_JFX_VERSION#*@} +POM_JFX_VERSION=${POM_JFX_VERSION%%.*} +if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then + >&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION})" + exit 1 +fi + + # add runtime ${JAVA_HOME}/bin/jlink \ --verbose \ --output runtime \ - --module-path "${JAVA_HOME}/jmods" \ + --module-path "${JAVA_HOME}/jmods:openjfx-jmods" \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \ --strip-native-commands \ --no-header-files \ --no-man-pages \ - --strip-debug \ - --compress=1 + --strip-debug # create app dir envsubst '${SEMVER_STR} ${REVISION_NUM}' < ../launcher-gtk2.properties > launcher-gtk2.properties @@ -97,5 +127,5 @@ chmod +x /tmp/appimagetool.AppImage echo "" echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage" echo "" -echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage" -echo "" \ No newline at end of file +echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir runtime squashfs-root openjfx-jmods; rm launcher-gtk2.properties /tmp/appimagetool.AppImage openjfx-jmods.zip" +echo "" diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml index e28172efe..b6f05d102 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml +++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml @@ -66,6 +66,7 @@ + diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index d0a12e380..2de33e34b 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -31,8 +31,7 @@ override_dh_auto_build: --strip-native-commands \ --no-header-files \ --no-man-pages \ - --strip-debug \ - --compress=2 + --strip-debug $(JAVA_HOME)/bin/jpackage \ --type app-image \ --runtime-image runtime \ diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 2606a3778..d05463e65 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -78,8 +78,7 @@ Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\ja --strip-native-commands ` --no-header-files ` --no-man-pages ` - --strip-debug ` - --compress=1 + --strip-debug $appPath = ".\$AppName" if ($clean -and (Test-Path -Path $appPath)) { @@ -181,7 +180,7 @@ Write-Output "Downloading ${winfspMsiUrl}..." Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default # download legacy-winfsp uninstaller -$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe' +$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0/winfsp-uninstaller.exe' Write-Output "Downloading ${winfspUninstaller}..." Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default diff --git a/pom.xml b/pom.xml index b6ff690ef..666aee7c3 100644 --- a/pom.xml +++ b/pom.xml @@ -37,40 +37,40 @@ 1.3.0 1.2.4 1.2.2 - 1.4.0-beta2 - 4.0.0-beta5 + 1.4.0 + 4.0.0 2.0.0 2.0.5 3.14.0 - 2.48.1 + 2.49 2.2 32.1.3-jre 2.16.0 21.0.1 4.4.0 - 9.37.1 - 1.4.12 + 9.37.3 + 1.4.14 2.0.9 0.8.0 1.8.2 5.10.1 - 5.7.0 + 5.8.0 2.2 24.1.0 - 9.0.1 + 9.0.7 0.8.11 2.3.0 1.2.1 - 3.11.0 + 3.12.1 3.3.1 3.6.1 - 3.2.2 + 3.2.3 3.3.0 @@ -460,17 +460,19 @@ org.owasp dependency-check-maven - 24 + 24 0 true true suppression.xml + ${env.NVD_API_KEY} check + validate diff --git a/src/main/java/org/cryptomator/common/CommonsModule.java b/src/main/java/org/cryptomator/common/CommonsModule.java index 4ac07495e..a1e3c0950 100644 --- a/src/main/java/org/cryptomator/common/CommonsModule.java +++ b/src/main/java/org/cryptomator/common/CommonsModule.java @@ -5,10 +5,8 @@ *******************************************************************************/ package org.cryptomator.common; -import com.tobiasdiez.easybind.EasyBind; import dagger.Module; import dagger.Provides; -import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.keychain.KeychainModule; import org.cryptomator.common.mount.MountModule; import org.cryptomator.common.settings.Settings; @@ -22,8 +20,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Named; import javax.inject.Singleton; -import javafx.beans.value.ObservableValue; -import java.net.InetSocketAddress; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Comparator; @@ -136,13 +132,4 @@ public abstract class CommonsModule { LOG.error("Uncaught exception in " + thread.getName(), throwable); } - @Provides - @Singleton - static ObservableValue provideServerSocketAddressBinding(Settings settings) { - return settings.port.map(port -> { - String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost"; - return InetSocketAddress.createUnresolved(host, settings.port.intValue()); - }); - } - } diff --git a/src/main/java/org/cryptomator/common/mount/ActualMountService.java b/src/main/java/org/cryptomator/common/mount/ActualMountService.java deleted file mode 100644 index a96cc8e37..000000000 --- a/src/main/java/org/cryptomator/common/mount/ActualMountService.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.cryptomator.common.mount; - -import org.cryptomator.integrations.mount.MountService; - -public record ActualMountService(MountService service, boolean isDesired) { -} diff --git a/src/main/java/org/cryptomator/common/mount/ConflictingMountServiceException.java b/src/main/java/org/cryptomator/common/mount/ConflictingMountServiceException.java new file mode 100644 index 000000000..b4d87169a --- /dev/null +++ b/src/main/java/org/cryptomator/common/mount/ConflictingMountServiceException.java @@ -0,0 +1,14 @@ +package org.cryptomator.common.mount; + +import org.cryptomator.integrations.mount.MountFailedException; + +/** + * Thrown by {@link Mounter} to indicate that the selected mount service can not be used + * due to incompatibilities with a different mount service that is already in use. + */ +public class ConflictingMountServiceException extends MountFailedException { + + public ConflictingMountServiceException(String msg) { + super(msg); + } +} diff --git a/src/main/java/org/cryptomator/common/mount/MountModule.java b/src/main/java/org/cryptomator/common/mount/MountModule.java index cbcb23e82..72872855c 100644 --- a/src/main/java/org/cryptomator/common/mount/MountModule.java +++ b/src/main/java/org/cryptomator/common/mount/MountModule.java @@ -4,21 +4,18 @@ import dagger.Module; import dagger.Provides; import org.cryptomator.common.ObservableUtil; import org.cryptomator.common.settings.Settings; -import org.cryptomator.integrations.mount.Mount; import org.cryptomator.integrations.mount.MountService; import javax.inject.Named; import javax.inject.Singleton; import javafx.beans.value.ObservableValue; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; @Module public class MountModule { - private static final AtomicReference formerSelectedMountService = new AtomicReference<>(null); - private static final List problematicFuseMountServices = List.of("org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", "org.cryptomator.frontend.fuse.mount.FuseTMountProvider"); - @Provides @Singleton static List provideSupportedMountServices() { @@ -27,46 +24,18 @@ public class MountModule { @Provides @Singleton - @Named("FUPFMS") - static AtomicReference provideFirstUsedProblematicFuseMountService() { - return new AtomicReference<>(null); + static ObservableValue provideDefaultMountService(List mountProviders, Settings settings) { + var fallbackProvider = mountProviders.stream().findFirst().get(); //there should always be a mount provider, at least webDAV + return ObservableUtil.mapWithDefault(settings.mountService, // + serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), // + fallbackProvider); } @Provides @Singleton - static ObservableValue provideMountService(Settings settings, List serviceImpls, @Named("FUPFMS") AtomicReference fupfms) { - var fallbackProvider = serviceImpls.stream().findFirst().orElse(null); - - var observableMountService = ObservableUtil.mapWithDefault(settings.mountService, // - desiredServiceImpl -> { // - var serviceFromSettings = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); // - var targetedService = serviceFromSettings.orElse(fallbackProvider); - return applyWorkaroundForProblematicFuse(targetedService, serviceFromSettings.isPresent(), fupfms); - }, // - () -> { // - return applyWorkaroundForProblematicFuse(fallbackProvider, true, fupfms); - }); - return observableMountService; + @Named("usedMountServices") + static Set provideSetOfUsedMountServices() { + return ConcurrentHashMap.newKeySet(); } - //see https://github.com/cryptomator/cryptomator/issues/2786 - private synchronized static ActualMountService applyWorkaroundForProblematicFuse(MountService targetedService, boolean isDesired, AtomicReference firstUsedProblematicFuseMountService) { - //set the first used problematic fuse service if applicable - var targetIsProblematicFuse = isProblematicFuseService(targetedService); - if (targetIsProblematicFuse && firstUsedProblematicFuseMountService.get() == null) { - firstUsedProblematicFuseMountService.set(targetedService); - } - - //do not use the targeted mount service and fallback to former one, if the service is problematic _and_ not the first problematic one used. - if (targetIsProblematicFuse && !firstUsedProblematicFuseMountService.get().equals(targetedService)) { - return new ActualMountService(formerSelectedMountService.get(), false); - } else { - formerSelectedMountService.set(targetedService); - return new ActualMountService(targetedService, isDesired); - } - } - - public static boolean isProblematicFuseService(MountService service) { - return problematicFuseMountServices.contains(service.getClass().getName()); - } -} +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/common/mount/Mounter.java b/src/main/java/org/cryptomator/common/mount/Mounter.java index 101524ea3..b63a12b1f 100644 --- a/src/main/java/org/cryptomator/common/mount/Mounter.java +++ b/src/main/java/org/cryptomator/common/mount/Mounter.java @@ -9,11 +9,15 @@ import org.cryptomator.integrations.mount.MountFailedException; import org.cryptomator.integrations.mount.MountService; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; import javafx.beans.value.ObservableValue; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER; import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR; @@ -24,24 +28,39 @@ import static org.cryptomator.integrations.mount.MountCapability.UNMOUNT_FORCED; @Singleton public class Mounter { - private final Settings settings; + // mount providers (key) can not be used if any of the conflicting mount providers (values) are already in use + private static final Map> CONFLICTING_MOUNT_SERVICES = Map.of( + "org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", Set.of("org.cryptomator.frontend.fuse.mount.FuseTMountProvider"), + "org.cryptomator.frontend.fuse.mount.FuseTMountProvider", Set.of("org.cryptomator.frontend.fuse.mount.MacFuseMountProvider") + ); + private final Environment env; + private final Settings settings; private final WindowsDriveLetters driveLetters; - private final ObservableValue mountServiceObservable; + private final List mountProviders; + private final Set usedMountServices; + private final ObservableValue defaultMountService; @Inject - public Mounter(Settings settings, Environment env, WindowsDriveLetters driveLetters, ObservableValue mountServiceObservable) { - this.settings = settings; + public Mounter(Environment env, // + Settings settings, // + WindowsDriveLetters driveLetters, // + List mountProviders, // + @Named("usedMountServices") Set usedMountServices, // + ObservableValue defaultMountService) { this.env = env; + this.settings = settings; this.driveLetters = driveLetters; - this.mountServiceObservable = mountServiceObservable; + this.mountProviders = mountProviders; + this.usedMountServices = usedMountServices; + this.defaultMountService = defaultMountService; } private class SettledMounter { - private MountService service; - private MountBuilder builder; - private VaultSettings vaultSettings; + private final MountService service; + private final MountBuilder builder; + private final VaultSettings vaultSettings; public SettledMounter(MountService service, MountBuilder builder, VaultSettings vaultSettings) { this.service = service; @@ -53,8 +72,13 @@ public class Mounter { for (var capability : service.capabilities()) { switch (capability) { case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs"); - case LOOPBACK_PORT -> - builder.setLoopbackPort(settings.port.get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault) + case LOOPBACK_PORT -> { + if (vaultSettings.mountService.getValue() == null) { + builder.setLoopbackPort(settings.port.get()); + } else { + builder.setLoopbackPort(vaultSettings.port.get()); + } + } case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName); case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get()); case MOUNT_FLAGS -> { @@ -131,13 +155,26 @@ public class Mounter { } public MountHandle mount(VaultSettings vaultSettings, Path cryptoFsRoot) throws IOException, MountFailedException { - var mountService = this.mountServiceObservable.getValue().service(); + 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())}"; + throw new ConflictingMountServiceException(msg); + } + + usedMountServices.add(mountService); + var builder = mountService.forFileSystem(cryptoFsRoot); - var internal = new SettledMounter(mountService, builder, vaultSettings); + var internal = new SettledMounter(mountService, builder, vaultSettings); // FIXME: no need for an inner class var cleanup = internal.prepare(); return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup); } + public boolean isConflictingMountService(MountService service) { + var conflictingServices = CONFLICTING_MOUNT_SERVICES.getOrDefault(service.getClass().getName(), Set.of()); + return usedMountServices.stream().map(MountService::getClass).map(Class::getName).anyMatch(conflictingServices::contains); + } + public record MountHandle(Mount mountObj, boolean supportsUnmountForced, Runnable specialCleanup) { } diff --git a/src/main/java/org/cryptomator/common/settings/VaultSettings.java b/src/main/java/org/cryptomator/common/settings/VaultSettings.java index 6662f61ff..fd21fc197 100644 --- a/src/main/java/org/cryptomator/common/settings/VaultSettings.java +++ b/src/main/java/org/cryptomator/common/settings/VaultSettings.java @@ -8,7 +8,6 @@ package org.cryptomator.common.settings; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import com.google.common.io.BaseEncoding; -import org.apache.commons.lang3.SystemUtils; import org.jetbrains.annotations.VisibleForTesting; import javafx.beans.Observable; @@ -40,6 +39,7 @@ public class VaultSettings { static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK; static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false; static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60; + static final int DEFAULT_PORT = 42427; private static final Random RNG = new Random(); @@ -56,6 +56,8 @@ public class VaultSettings { public final IntegerProperty autoLockIdleSeconds; public final ObjectProperty mountPoint; public final StringExpression mountName; + public final StringProperty mountService; + public final IntegerProperty port; VaultSettings(VaultSettingsJson json) { this.id = json.id; @@ -70,6 +72,8 @@ public class VaultSettings { this.autoLockWhenIdle = new SimpleBooleanProperty(this, "autoLockWhenIdle", json.autoLockWhenIdle); this.autoLockIdleSeconds = new SimpleIntegerProperty(this, "autoLockIdleSeconds", json.autoLockIdleSeconds); 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); // 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; @@ -95,7 +99,7 @@ public class VaultSettings { } Observable[] observables() { - return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode}; + return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService}; } public static VaultSettings withRandomId() { @@ -124,6 +128,8 @@ public class VaultSettings { json.autoLockWhenIdle = autoLockWhenIdle.get(); json.autoLockIdleSeconds = autoLockIdleSeconds.get(); json.mountPoint = mountPoint.map(Path::toString).getValue(); + json.mountService = mountService.get(); + json.port = port.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 2381203e5..43aa204e8 100644 --- a/src/main/java/org/cryptomator/common/settings/VaultSettingsJson.java +++ b/src/main/java/org/cryptomator/common/settings/VaultSettingsJson.java @@ -45,6 +45,12 @@ class VaultSettingsJson { @JsonProperty("autoLockIdleSeconds") int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS; + @JsonProperty("mountService") + String mountService; + + @JsonProperty("port") + int port = VaultSettings.DEFAULT_PORT; + @Deprecated(since = "1.7.0") @JsonProperty(value = "winDriveLetter", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233 String winDriveLetter; diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index 2e1e34a78..ac913d316 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -11,7 +11,6 @@ package org.cryptomator.common.vaults; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Constants; import org.cryptomator.common.mount.Mounter; -import org.cryptomator.common.mount.WindowsDriveLetters; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.cryptofs.CryptoFileSystem; import org.cryptomator.cryptofs.CryptoFileSystemProperties; @@ -73,7 +72,13 @@ public class Vault { private final AtomicReference mountHandle = new AtomicReference<>(null); @Inject - Vault(VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty lastKnownException, VaultStats stats, WindowsDriveLetters windowsDriveLetters, Mounter mounter) { + Vault(VaultSettings vaultSettings, // + VaultConfigCache configCache, // + AtomicReference cryptoFileSystem, // + VaultState state, // + @Named("lastKnownException") ObjectProperty lastKnownException, // + VaultStats stats, // + Mounter mounter) { this.vaultSettings = vaultSettings; this.configCache = configCache; this.cryptoFileSystem = cryptoFileSystem; diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index 46542ccb9..9af0578cf 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -22,8 +22,9 @@ public enum FxmlFile { HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), // HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), // HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), // + HUB_REGISTER_DEVICE_ALREADY_EXISTS("/fxml/hub_register_device_already_exists.fxml"), // HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), // - HUB_SETUP_DEVICE("/fxml/hub_setup_device.fxml"), // + HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), // HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), // HUB_REQUIRE_ACCOUNT_INIT("/fxml/hub_require_account_init.fxml"), // LOCK_FORCED("/fxml/lock_forced.fxml"), // @@ -45,6 +46,7 @@ public enum FxmlFile { REMOVE_VAULT("/fxml/remove_vault.fxml"), // UPDATE_REMINDER("/fxml/update_reminder.fxml"), // UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"), + UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), // UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), // UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), // UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), // diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/DeviceAlreadyExistsException.java b/src/main/java/org/cryptomator/ui/keyloading/hub/DeviceAlreadyExistsException.java new file mode 100644 index 000000000..0c942e67b --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/DeviceAlreadyExistsException.java @@ -0,0 +1,12 @@ +package org.cryptomator.ui.keyloading.hub; + +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; + +/** + * Thrown, when Hub registerDevice-Request returns with 409 + */ +class DeviceAlreadyExistsException extends MasterkeyLoadingFailedException { + public DeviceAlreadyExistsException() { + super("Device already registered on this Hub instance"); + } +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java index 235fbf639..2076db9eb 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java @@ -135,10 +135,17 @@ public abstract class HubKeyLoadingModule { } @Provides - @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) + @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) @KeyLoadingScoped static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.HUB_SETUP_DEVICE); + return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE); + } + + @Provides + @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS) + @KeyLoadingScoped + static Scene provideHubRegisterDeviceAlreadyExistsScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS); } @Provides diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java index 9ea5e7735..40f845a63 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java @@ -1,7 +1,6 @@ package org.cryptomator.ui.keyloading.hub; import com.google.common.base.Preconditions; -import com.nimbusds.jose.JWEObject; import dagger.Lazy; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.keychain.NoKeychainAccessProviderException; @@ -44,6 +43,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy { this.window = window; this.keychainManager = keychainManager; window.setTitle(windowTitle); + window.setOnCloseRequest(_ -> result.cancel(true)); this.authFlowScene = authFlowScene; this.noKeychainScene = noKeychainScene; this.result = result; 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 c0681d4bb..e0f305fd2 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -11,7 +11,6 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingScoped; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,7 +31,6 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.time.Duration; -import java.time.Instant; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -51,7 +49,7 @@ public class ReceiveKeyController implements FxController { private final String deviceId; private final String bearerToken; private final CompletableFuture result; - private final Lazy setupDeviceScene; + private final Lazy registerDeviceScene; private final Lazy legacyRegisterDeviceScene; private final Lazy unauthorizedScene; private final Lazy accountInitializationScene; @@ -60,13 +58,13 @@ public class ReceiveKeyController implements FxController { private final HttpClient httpClient; @Inject - public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference tokenRef, CompletableFuture result, @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) Lazy setupDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy invalidLicenseScene) { + public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference tokenRef, CompletableFuture result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy registerDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy invalidLicenseScene) { this.window = window; this.hubConfig = hubConfig; this.deviceId = deviceId; this.bearerToken = Objects.requireNonNull(tokenRef.get()); this.result = result; - this.setupDeviceScene = setupDeviceScene; + this.registerDeviceScene = registerDeviceScene; this.legacyRegisterDeviceScene = legacyRegisterDeviceScene; this.unauthorizedScene = unauthorizedScene; this.accountInitializationScene = accountInitializationScene; @@ -141,7 +139,7 @@ public class ReceiveKeyController implements FxController { var device = JSON.reader().readValue(response.body(), DeviceDto.class); receivedBothEncryptedKeys(encryptedVaultKey, device.userPrivateKey); } - case 404 -> needsDeviceSetup(); // TODO: using the setup code, we can theoretically immediately unlock + case 404 -> needsDeviceRegistration(); // TODO: using the setup code, we can theoretically immediately unlock default -> throw new IllegalStateException("Unexpected response " + response.statusCode()); } } catch (IOException e) { @@ -149,8 +147,8 @@ public class ReceiveKeyController implements FxController { } } - private void needsDeviceSetup() { - window.setScene(setupDeviceScene.get()); + private void needsDeviceRegistration() { + window.setScene(registerDeviceScene.get()); } private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) throws IOException { 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 837dc5032..08af2492a 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java @@ -57,12 +57,12 @@ public class RegisterDeviceController implements FxController { private final String bearerToken; private final Lazy registerSuccessScene; private final Lazy registerFailedScene; + private final Lazy deviceAlreadyExistsScene; private final String deviceId; private final P384KeyPair deviceKeyPair; private final CompletableFuture result; private final HttpClient httpClient; - private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false); private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false); private final BooleanProperty workInProgress = new SimpleBooleanProperty(false); public TextField setupCodeField; @@ -70,7 +70,7 @@ public class RegisterDeviceController implements FxController { public Button registerBtn; @Inject - public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture result, @Named("bearerToken") AtomicReference bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy registerFailedScene) { + public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture result, @Named("bearerToken") AtomicReference bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy registerFailedScene, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS) Lazy deviceAlreadyExistsScene) { this.window = window; this.hubConfig = hubConfig; this.deviceId = deviceId; @@ -79,13 +79,13 @@ public class RegisterDeviceController implements FxController { this.bearerToken = Objects.requireNonNull(bearerToken.get()); this.registerSuccessScene = registerSuccessScene; this.registerFailedScene = registerFailedScene; + this.deviceAlreadyExistsScene = deviceAlreadyExistsScene; this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build(); } public void initialize() { deviceNameField.setText(determineHostname()); - deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false)); deviceNameField.disableProperty().bind(workInProgress); setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false)); setupCodeField.disableProperty().bind(workInProgress); @@ -146,7 +146,7 @@ public class RegisterDeviceController implements FxController { return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding()); }).whenCompleteAsync((response, throwable) -> { if (response != null) { - this.handleResponse(response); + this.handleRegisterDeviceResponse(response); } else { this.setupFailed(throwable); } @@ -170,12 +170,12 @@ public class RegisterDeviceController implements FxController { } } - private void handleResponse(HttpResponse response) { + private void handleRegisterDeviceResponse(HttpResponse response) { if (response.statusCode() == 201) { LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl); window.setScene(registerSuccessScene.get()); } else if (response.statusCode() == 409) { - deviceNameAlreadyExists.set(true); + setupFailed(new DeviceAlreadyExistsException()); } else { setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode())); } @@ -184,10 +184,13 @@ public class RegisterDeviceController implements FxController { private void setupFailed(Throwable cause) { switch (cause) { case CompletionException e when e.getCause() instanceof JWEHelper.InvalidJweKeyException -> invalidSetupCode.set(true); + case DeviceAlreadyExistsException e -> { + LOG.debug("Device already registered in hub instance {} for different user", hubConfig.authSuccessUrl); + window.setScene(deviceAlreadyExistsScene.get()); + } default -> { LOG.warn("Device setup failed.", cause); window.setScene(registerFailedScene.get()); - result.completeExceptionally(cause); } } } @@ -202,15 +205,6 @@ public class RegisterDeviceController implements FxController { } //--- Getters & Setters - - public BooleanProperty deviceNameAlreadyExistsProperty() { - return deviceNameAlreadyExists; - } - - public boolean getDeviceNameAlreadyExists() { - return deviceNameAlreadyExists.get(); - } - public BooleanProperty invalidSetupCodeProperty() { return invalidSetupCode; } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterFailedController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterFailedController.java index 57150390c..7df27b06a 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterFailedController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterFailedController.java @@ -1,6 +1,5 @@ package org.cryptomator.ui.keyloading.hub; -import com.nimbusds.jose.JWEObject; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.keyloading.KeyLoading; @@ -22,8 +21,8 @@ public class RegisterFailedController implements FxController { @FXML public void close() { + result.cancel(true); window.close(); } - } diff --git a/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java index 653c4c6e6..8cb49a679 100644 --- a/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java @@ -2,17 +2,14 @@ package org.cryptomator.ui.preferences; import dagger.Lazy; import org.cryptomator.common.ObservableUtil; -import org.cryptomator.common.mount.MountModule; import org.cryptomator.common.settings.Settings; import org.cryptomator.integrations.mount.MountCapability; import org.cryptomator.integrations.mount.MountService; import org.cryptomator.ui.common.FxController; import javax.inject.Inject; -import javax.inject.Named; import javafx.application.Application; import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanExpression; import javafx.beans.value.ObservableValue; import javafx.scene.control.Button; import javafx.scene.control.ChoiceBox; @@ -21,24 +18,22 @@ import javafx.util.StringConverter; import java.util.List; import java.util.Optional; import java.util.ResourceBundle; -import java.util.concurrent.atomic.AtomicReference; @PreferencesScoped public class VolumePreferencesController implements FxController { - private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/"; - private static final int MIN_PORT = 1024; - private static final int MAX_PORT = 65535; + public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/"; + public static final int MIN_PORT = 1024; + public static final int MAX_PORT = 65535; private final Settings settings; private final ObservableValue selectedMountService; private final ResourceBundle resourceBundle; - private final BooleanExpression loopbackPortSupported; + private final ObservableValue loopbackPortSupported; private final ObservableValue mountToDirSupported; private final ObservableValue mountToDriveLetterSupported; private final ObservableValue mountFlagsSupported; private final ObservableValue readonlySupported; - private final ObservableValue fuseRestartRequired; private final Lazy application; private final List mountProviders; public ChoiceBox volumeTypeChoiceBox; @@ -46,7 +41,10 @@ public class VolumePreferencesController implements FxController { public Button loopbackPortApplyButton; @Inject - VolumePreferencesController(Settings settings, Lazy application, List mountProviders, @Named("FUPFMS") AtomicReference firstUsedProblematicFuseMountService, ResourceBundle resourceBundle) { + VolumePreferencesController(Settings settings, // + Lazy application, // + List mountProviders, // + ResourceBundle resourceBundle) { this.settings = settings; this.application = application; this.mountProviders = mountProviders; @@ -54,17 +52,11 @@ public class VolumePreferencesController implements FxController { var fallbackProvider = mountProviders.stream().findFirst().orElse(null); this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService, serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider); - this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT))); + this.loopbackPortSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)); this.mountToDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT) || s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR)); this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER)); this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS)); this.readonlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY)); - this.fuseRestartRequired = selectedMountService.map(s -> {// - return firstUsedProblematicFuseMountService.get() != null // - && MountModule.isProblematicFuseService(s) // - && !firstUsedProblematicFuseMountService.get().equals(s); - }); - } public void initialize() { @@ -101,12 +93,12 @@ public class VolumePreferencesController implements FxController { /* Property Getters */ - public BooleanExpression loopbackPortSupportedProperty() { + public ObservableValue loopbackPortSupportedProperty() { return loopbackPortSupported; } public boolean isLoopbackPortSupported() { - return loopbackPortSupported.get(); + return loopbackPortSupported.getValue(); } public ObservableValue readonlySupportedProperty() { @@ -141,14 +133,6 @@ public class VolumePreferencesController implements FxController { return mountFlagsSupported.getValue(); } - public ObservableValue fuseRestartRequiredProperty() { - return fuseRestartRequired; - } - - public boolean getFuseRestartRequired() { - return fuseRestartRequired.getValue(); - } - /* Helpers */ private class MountServiceConverter extends StringConverter { diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java b/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java index 67e905200..825d0fc2d 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java @@ -19,16 +19,8 @@ import java.util.concurrent.Future; @Subcomponent(modules = {UnlockModule.class}) public interface UnlockComponent { - ExecutorService defaultExecutorService(); - UnlockWorkflow unlockWorkflow(); - default Future startUnlockWorkflow() { - UnlockWorkflow workflow = unlockWorkflow(); - defaultExecutorService().submit(workflow); - return workflow; - } - @Subcomponent.Factory interface Factory { UnlockComponent create(@BindsInstance @UnlockWindow Vault vault, @BindsInstance @Named("unlockWindowOwner") @Nullable Stage owner); diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java index f93999d21..95f13d383 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java @@ -81,6 +81,13 @@ abstract class UnlockModule { return fxmlLoaders.createScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT); } + @Provides + @FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART) + @UnlockScoped + static Scene provideRestartRequiredScene(@UnlockWindow FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.UNLOCK_REQUIRES_RESTART); + } + // ------------------ @Binds @@ -93,4 +100,9 @@ abstract class UnlockModule { @FxControllerKey(UnlockInvalidMountPointController.class) abstract FxController bindUnlockInvalidMountPointController(UnlockInvalidMountPointController controller); + @Binds + @IntoMap + @FxControllerKey(UnlockRequiresRestartController.class) + abstract FxController bindUnlockRequiresRestartController(UnlockRequiresRestartController controller); + } diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockRequiresRestartController.java b/src/main/java/org/cryptomator/ui/unlock/UnlockRequiresRestartController.java new file mode 100644 index 000000000..497194dff --- /dev/null +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockRequiresRestartController.java @@ -0,0 +1,47 @@ +package org.cryptomator.ui.unlock; + +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.fxapp.FxApplicationWindows; +import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; + +import javax.inject.Inject; +import javafx.fxml.FXML; +import javafx.stage.Stage; +import java.util.ResourceBundle; + +@UnlockScoped +public class UnlockRequiresRestartController implements FxController { + + private final Stage window; + private final ResourceBundle resourceBundle; + private final FxApplicationWindows appWindows; + private final Vault vault; + + @Inject + UnlockRequiresRestartController(@UnlockWindow Stage window, // + ResourceBundle resourceBundle, // + FxApplicationWindows appWindows, // + @UnlockWindow Vault vault) { + this.window = window; + this.resourceBundle = resourceBundle; + this.appWindows = appWindows; + this.vault = vault; + } + + public void initialize() { + window.setTitle(String.format(resourceBundle.getString("unlock.error.title"), vault.getDisplayName())); + } + + @FXML + public void close() { + window.close(); + } + + @FXML + public void closeAndOpenVaultOptions() { + appWindows.showVaultOptionsWindow(vault, SelectedVaultOptionsTab.MOUNT); + window.close(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 564d57ab6..98a49dec5 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -1,7 +1,7 @@ package org.cryptomator.ui.unlock; -import com.google.common.base.Throwables; import dagger.Lazy; +import org.cryptomator.common.mount.ConflictingMountServiceException; import org.cryptomator.common.mount.IllegalMountPointException; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; @@ -29,7 +29,7 @@ import java.io.IOException; * This class runs the unlock process and controls when to display which UI. */ @UnlockScoped -public class UnlockWorkflow extends Task { +public class UnlockWorkflow extends Task { private static final Logger LOG = LoggerFactory.getLogger(UnlockWorkflow.class); @@ -38,42 +38,44 @@ public class UnlockWorkflow extends Task { private final VaultService vaultService; private final Lazy successScene; private final Lazy invalidMountPointScene; + private final Lazy restartRequiredScene; private final FxApplicationWindows appWindows; private final KeyLoadingStrategy keyLoadingStrategy; private final ObjectProperty illegalMountPointException; @Inject - UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow ObjectProperty illegalMountPointException) { + UnlockWorkflow(@UnlockWindow Stage window, // + @UnlockWindow Vault vault, // + VaultService vaultService, // + @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy successScene, // + @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy invalidMountPointScene, // + @FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART) Lazy restartRequiredScene, // + FxApplicationWindows appWindows, // + @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, // + @UnlockWindow ObjectProperty illegalMountPointException) { this.window = window; this.vault = vault; this.vaultService = vaultService; this.successScene = successScene; this.invalidMountPointScene = invalidMountPointScene; + this.restartRequiredScene = restartRequiredScene; this.appWindows = appWindows; this.keyLoadingStrategy = keyLoadingStrategy; this.illegalMountPointException = illegalMountPointException; } @Override - protected Boolean call() throws InterruptedException, IOException, CryptoException, MountFailedException { - try { - attemptUnlock(); - return true; - } catch (UnlockCancelledException e) { - cancel(false); // set Tasks state to cancelled - return false; - } - } - - private void attemptUnlock() throws IOException, CryptoException, MountFailedException { + protected Void call() throws InterruptedException, IOException, CryptoException, MountFailedException { try { keyLoadingStrategy.use(vault::unlock); + return null; + } catch (UnlockCancelledException e) { + cancel(false); // set Tasks state to cancelled + return null; + } catch (IOException | RuntimeException | MountFailedException e) { + throw e; } catch (Exception e) { - Throwables.propagateIfPossible(e, IOException.class); - Throwables.propagateIfPossible(e, CryptoException.class); - Throwables.propagateIfPossible(e, IllegalMountPointException.class); - Throwables.propagateIfPossible(e, MountFailedException.class); - throw new IllegalStateException("unexpected exception type", e); + throw new IllegalStateException("Unexpected exception type", e); } } @@ -85,6 +87,13 @@ public class UnlockWorkflow extends Task { }); } + private void handleConflictingMountServiceException() { + Platform.runLater(() -> { + window.setScene(restartRequiredScene.get()); + window.show(); + }); + } + private void handleGenericError(Throwable e) { LOG.error("Unlock failed for technical reasons.", e); appWindows.showErrorWindow(e, window, null); @@ -113,10 +122,10 @@ public class UnlockWorkflow extends Task { protected void failed() { LOG.info("Unlock of '{}' failed.", vault.getDisplayName()); Throwable throwable = super.getException(); - if(throwable instanceof IllegalMountPointException impe) { - handleIllegalMountPointError(impe); - } else { - handleGenericError(throwable); + switch (throwable) { + case IllegalMountPointException e -> handleIllegalMountPointError(e); + case ConflictingMountServiceException _ -> handleConflictingMountServiceException(); + default -> handleGenericError(throwable); } vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED); } diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index 5eeab43e0..106623985 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -1,18 +1,25 @@ package org.cryptomator.ui.vaultoptions; import com.google.common.base.Strings; -import org.cryptomator.common.mount.ActualMountService; +import dagger.Lazy; +import org.cryptomator.common.ObservableUtil; +import org.cryptomator.common.mount.Mounter; import org.cryptomator.common.mount.WindowsDriveLetters; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.vaults.Vault; import org.cryptomator.integrations.mount.MountCapability; +import org.cryptomator.integrations.mount.MountService; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.preferences.SelectedPreferencesTab; +import org.cryptomator.ui.preferences.VolumePreferencesController; import javax.inject.Inject; +import javafx.application.Application; +import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; +import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.control.RadioButton; @@ -26,6 +33,8 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.List; +import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; @@ -36,14 +45,21 @@ public class MountOptionsController implements FxController { private final VaultSettings vaultSettings; private final WindowsDriveLetters windowsDriveLetters; private final ResourceBundle resourceBundle; + private final Lazy application; 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; private final FxApplicationWindows applicationWindows; + private final List mountProviders; + private final ObservableValue defaultMountService; + private final ObservableValue selectedMountService; + private final ObservableValue selectedMountServiceRequiresRestart; + private final ObservableValue loopbackPortChangeable; //-- FXML objects -- @@ -56,30 +72,58 @@ public class MountOptionsController implements FxController { public RadioButton mountPointDirBtn; public TextField directoryPathField; public ChoiceBox driveLetterSelection; + public ChoiceBox vaultVolumeTypeChoiceBox; + public TextField vaultLoopbackPortField; + public Button vaultLoopbackPortApplyButton; + @Inject - MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObservableValue mountService, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, FxApplicationWindows applicationWindows) { + MountOptionsController(@VaultOptionsWindow Stage window, // + @VaultOptionsWindow Vault vault, // + WindowsDriveLetters windowsDriveLetters, // + ResourceBundle resourceBundle, // + FxApplicationWindows applicationWindows, // + Lazy application, // + List mountProviders, // + Mounter mounter, // + ObservableValue defaultMountService) { this.window = window; this.vaultSettings = vault.getVaultSettings(); this.windowsDriveLetters = windowsDriveLetters; this.resourceBundle = resourceBundle; - this.defaultMountFlags = mountService.map(as -> { - if (as.service().hasCapability(MountCapability.MOUNT_FLAGS)) { - return as.service().getDefaultMountFlags(); + this.applicationWindows = applicationWindows; + this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString()); + this.application = application; + this.mountProviders = mountProviders; + this.defaultMountService = defaultMountService; + this.selectedMountService = Bindings.createObjectBinding(this::reselectMountService, defaultMountService, vaultSettings.mountService); + this.selectedMountServiceRequiresRestart = selectedMountService.map(mounter::isConflictingMountService); + + this.defaultMountFlags = selectedMountService.map(s -> { + if (s.hasCapability(MountCapability.MOUNT_FLAGS)) { + return s.getDefaultMountFlags(); } else { return ""; } }); - this.mountpointDirSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || as.service().hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT)); - this.mountpointDriveLetterSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER)); - this.mountFlagsSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_FLAGS)); - this.readOnlySupported = mountService.map(as -> as.service().hasCapability(MountCapability.READ_ONLY)); - this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString()); - this.applicationWindows = applicationWindows; + 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); + } + + private MountService reselectMountService() { + var desired = vaultSettings.mountService.getValue(); + var defaultMS = defaultMountService.getValue(); + return mountProviders.stream().filter(s -> s.getClass().getName().equals(desired)).findFirst().orElse(defaultMS); } @FXML public void initialize() { + defaultMountService.addListener((_, _, _) -> vaultVolumeTypeChoiceBox.setConverter(new MountServiceConverter())); + // readonly: readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode); @@ -106,6 +150,20 @@ public class MountOptionsController implements FxController { mountPointToggleGroup.selectToggle(mountPointDirBtn); } mountPointToggleGroup.selectedToggleProperty().addListener(this::selectedToggleChanged); + + vaultVolumeTypeChoiceBox.getItems().add(null); + vaultVolumeTypeChoiceBox.getItems().addAll(mountProviders); + vaultVolumeTypeChoiceBox.setConverter(new MountServiceConverter()); + vaultVolumeTypeChoiceBox.getSelectionModel().select(isDefaultMountServiceSelected() ? null : selectedMountService.getValue()); + vaultVolumeTypeChoiceBox.valueProperty().addListener((_, _, newProvider) -> { + var toSet = Optional.ofNullable(newProvider).map(nP -> nP.getClass().getName()).orElse(null); + vaultSettings.mountService.set(toSet); + }); + + vaultLoopbackPortField.setText(String.valueOf(vaultSettings.port.get())); + vaultLoopbackPortApplyButton.visibleProperty().bind(vaultSettings.port.asString().isNotEqualTo(vaultLoopbackPortField.textProperty())); + vaultLoopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, vaultLoopbackPortField.textProperty()).not()); + } @FXML @@ -229,6 +287,26 @@ public class MountOptionsController implements FxController { } + public void openDocs() { + application.get().getHostServices().showDocument(VolumePreferencesController.DOCS_MOUNTING_URL); + } + + private boolean validateLoopbackPort() { + try { + int port = Integer.parseInt(vaultLoopbackPortField.getText()); + return port == 0 // choose port automatically + || port >= VolumePreferencesController.MIN_PORT && port <= VolumePreferencesController.MAX_PORT; // port within range + } catch (NumberFormatException e) { + return false; + } + } + + public void doChangeLoopbackPort() { + if (validateLoopbackPort()) { + vaultSettings.port.set(Integer.parseInt(vaultLoopbackPortField.getText())); + } + } + //@formatter:off private static class NoDirSelectedException extends Exception {} //@formatter:on @@ -243,6 +321,14 @@ public class MountOptionsController implements FxController { return mountFlagsSupported.getValue(); } + public ObservableValue defaultMountServiceSelectedProperty() { + return defaultMountServiceSelected; + } + + public boolean isDefaultMountServiceSelected() { + return defaultMountServiceSelected.getValue(); + } + public ObservableValue mountpointDirSupportedProperty() { return mountpointDirSupported; } @@ -274,4 +360,37 @@ public class MountOptionsController implements FxController { public String getDirectoryPath() { return directoryPath.getValue(); } + + public ObservableValue selectedMountServiceRequiresRestartProperty() { + return selectedMountServiceRequiresRestart; + } + + public boolean getSelectedMountServiceRequiresRestart() { + return selectedMountServiceRequiresRestart.getValue(); + } + + public ObservableValue loopbackPortChangeableProperty() { + return loopbackPortChangeable; + } + + public boolean isLoopbackPortChangeable() { + return loopbackPortChangeable.getValue(); + } + + private class MountServiceConverter extends StringConverter { + + @Override + public String toString(MountService provider) { + if (provider == null) { + return String.format(resourceBundle.getString("vaultOptions.mount.volumeType.default"), defaultMountService.getValue().displayName()); + } else { + return provider.displayName(); + } + } + + @Override + public MountService fromString(String string) { + throw new UnsupportedOperationException(); + } + } } diff --git a/src/main/resources/fxml/hub_setup_device.fxml b/src/main/resources/fxml/hub_register_device.fxml similarity index 88% rename from src/main/resources/fxml/hub_setup_device.fxml rename to src/main/resources/fxml/hub_register_device.fxml index f83ad6a97..e1a1e39ff 100644 --- a/src/main/resources/fxml/hub_setup_device.fxml +++ b/src/main/resources/fxml/hub_register_device.fxml @@ -57,15 +57,6 @@ - -