diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml
new file mode 100644
index 000000000..ce23bac60
--- /dev/null
+++ b/.github/workflows/appimage.yml
@@ -0,0 +1,158 @@
+name: Build AppImage
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version'
+ required: false
+
+env:
+ JAVA_VERSION: 17
+
+jobs:
+ build:
+ name: Build AppImage
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ - name: Setup Java
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'temurin'
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'maven'
+ - id: versions
+ name: Apply version information
+ run: |
+ if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR=${GITHUB_REF##*/}
+ mvn versions:set -DnewVersion=${SEM_VER_STR}
+ elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR="${{ github.event.inputs.version }}"
+ mvn versions:set -DnewVersion=${SEM_VER_STR}
+ else
+ SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
+ fi
+ SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
+ REVCOUNT=`git rev-list --count HEAD`
+ echo "::set-output name=semVerStr::${SEM_VER_STR}"
+ echo "::set-output name=semVerNum::${SEM_VER_NUM}"
+ echo "::set-output name=revNum::${REVCOUNT}"
+ - name: Validate Version
+ uses: skymatic/semver-validation-action@v1
+ with:
+ version: ${{ steps.versions.outputs.semVerStr }}
+ - name: Run maven
+ run: mvn -B clean package -Pdependency-check,linux -DskipTests
+ - name: Patch target dir
+ run: |
+ cp LICENSE.txt target
+ cp dist/linux/launcher.sh target
+ cp target/cryptomator-*.jar target/mods
+ - name: Run jlink
+ run: >
+ ${JAVA_HOME}/bin/jlink
+ --verbose
+ --output runtime
+ --module-path "${JAVA_HOME}/jmods"
+ --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
+ --no-header-files
+ --no-man-pages
+ --strip-debug
+ --compress=1
+ - name: Run jpackage
+ run: >
+ ${JAVA_HOME}/bin/jpackage
+ --verbose
+ --type app-image
+ --runtime-image runtime
+ --input target/libs
+ --module-path target/mods
+ --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
+ --dest appdir
+ --name Cryptomator
+ --vendor "Skymatic GmbH"
+ --copyright "(C) 2016 - 2022 Skymatic GmbH"
+ --app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
+ --java-options "-Xss5m"
+ --java-options "-Xmx256m"
+ --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
+ --java-options "-Dfile.encoding=\"utf-8\""
+ --java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
+ --java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
+ --java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\""
+ --java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
+ --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
+ --java-options "-Dcryptomator.showTrayIcon=false"
+ --java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\""
+ --resource-dir dist/linux/resources
+ - name: Patch Cryptomator.AppDir
+ run: |
+ mv appdir/Cryptomator Cryptomator.AppDir
+ cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/
+ envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
+ cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png
+ cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png
+ cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg
+ cp dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml
+ 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 bin/cryptomator.sh Cryptomator.AppDir/AppRun
+ env:
+ REVISION_NO: ${{ steps.versions.outputs.revNum }}
+ SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
+ - name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27
+ run: |
+ JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'`
+ ${JAVA_HOME}/bin/jar -xf lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/
+ mv jni/x86_64-Linux/* lib/app/libjffi.so
+ working-directory: Cryptomator.AppDir
+ - name: Download AppImageKit
+ run: |
+ curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage
+ chmod +x appimagetool.AppImage
+ ./appimagetool.AppImage --appimage-extract
+ - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
+ run: |
+ echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
+ echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign README.md
+ env:
+ GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
+ GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
+ - name: Build AppImage
+ run: >
+ ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ steps.versions.outputs.semVerStr }}-x86_64.AppImage
+ -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
+ --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
+ - name: Create detached GPG signatures
+ run: |
+ gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
+ gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: appimage
+ path: |
+ cryptomator-*.AppImage
+ cryptomator-*.AppImage.zsync
+ cryptomator-*.asc
+ if-no-files-found: error
+ - name: Publish AppImage on GitHub Releases
+ if: startsWith(github.ref, 'refs/tags/')
+ uses: softprops/action-gh-release@v1
+ with:
+ fail_on_unmatched_files: true
+ token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+ files: |
+ cryptomator-*.AppImage
+ cryptomator-*.zsync
+ cryptomator-*.asc
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 750b64826..5270365b3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,6 +32,7 @@ jobs:
restore-keys: ${{ runner.os }}-sonar
- name: Build and Test
run: >
+ xvfb-run
mvn -B verify
jacoco:report
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
@@ -42,10 +43,27 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- - name: Upload code coverage report
- id: codacyCoverageReporter
- if: "github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'pr:safe')"
- run: bash <(curl -Ls https://coverage.codacy.com/get.sh)
+ - name: Sign source tarball with key 615D449FE6E6A235
+ if: startsWith(github.ref, 'refs/tags/')
+ run: |
+ git archive --prefix="cryptomator-${{ github.ref_name }}/" -o "cryptomator-${{ github.ref_name }}.tar.gz" ${{ github.ref }}
+ echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
+ echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
env:
- CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
- continue-on-error: true
\ No newline at end of file
+ GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
+ GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
+ - name: Draft a release
+ if: startsWith(github.ref, 'refs/tags/')
+ uses: softprops/action-gh-release@v1
+ with:
+ draft: true
+ discussion_category_name: releases
+ token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+ generate_release_notes: true
+ files: |
+ cryptomator-*.tar.gz.asc
+ fail_on_unmatched_files: true
+ body: |-
+ :construction: Work in Progress
+
+ ---
diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml
new file mode 100644
index 000000000..fb8b3a423
--- /dev/null
+++ b/.github/workflows/debian.yml
@@ -0,0 +1,124 @@
+name: Build Debian Package
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+ inputs:
+ dput:
+ description: 'Upload to PPA'
+ required: true
+ default: false
+ type: boolean
+ version:
+ description: 'Version'
+ required: false
+
+env:
+ JAVA_VERSION: 17
+
+jobs:
+ build:
+ name: Build Debian Package
+ runs-on: ubuntu-18.04
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ - name: Install build tools
+ run: |
+ sudo apt-get update
+ sudo apt-get install debhelper devscripts dput
+ - name: Setup Java
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'temurin'
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'maven'
+ - id: versions
+ name: Apply version information
+ run: |
+ if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR=${GITHUB_REF##*/}
+ mvn versions:set -DnewVersion=${SEM_VER_STR}
+ elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR="${{ github.event.inputs.version }}"
+ mvn versions:set -DnewVersion=${SEM_VER_STR}
+ else
+ SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
+ fi
+ SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
+ REVCOUNT=`git rev-list --count HEAD`
+ echo "::set-output name=semVerStr::${SEM_VER_STR}"
+ echo "::set-output name=semVerNum::${SEM_VER_NUM}"
+ echo "::set-output name=revNum::${REVCOUNT}"
+ echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}"
+ - name: Validate Version
+ uses: skymatic/semver-validation-action@v1
+ with:
+ version: ${{ steps.versions.outputs.semVerStr }}
+ - name: Run maven
+ run: mvn -B clean package -Pdependency-check,linux -DskipTests
+ - name: Create orig.tar.gz with common/ libs/ mods/
+ run: |
+ mkdir pkgdir
+ cp -r target/libs pkgdir
+ cp -r target/mods pkgdir
+ cp -r dist/linux/common/ pkgdir
+ cp target/cryptomator-*.jar pkgdir/mods
+ tar -cJf cryptomator_${{ steps.versions.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir .
+ - name: Patch and rename pkgdir
+ run: |
+ cp -r dist/linux/debian/ pkgdir
+ export RFC2822_TIMESTAMP=`date --rfc-2822`
+ envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
+ envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
+ find . -name "*.jar" >> pkgdir/debian/source/include-binaries
+ mv pkgdir cryptomator_${{ steps.versions.outputs.ppaVerStr }}
+ env:
+ SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
+ VERSION_NUM: ${{ steps.versions.outputs.semVerNum }}
+ REVISION_NUM: ${{ steps.versions.outputs.revNum }}
+ PPA_VERSION: ${{ steps.versions.outputs.ppaVerStr }}-0ppa1
+ - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
+ run: |
+ echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
+ echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign README.md
+ env:
+ GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
+ GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
+ - name: debuild
+ run: |
+ debuild -S -sa -d
+ debuild -b -sa -d
+ env:
+ DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback
+ DEBSIGN_KEYID: 615D449FE6E6A235
+ working-directory: cryptomator_${{ steps.versions.outputs.ppaVerStr }}
+ - name: Create detached GPG signatures
+ run: |
+ gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: linux-deb-package
+ path: |
+ cryptomator_*.dsc
+ cryptomator_*.orig.tar.xz
+ cryptomator_*.debian.tar.xz
+ cryptomator_*_source.buildinfo
+ cryptomator_*_source.changes
+ cryptomator_*_amd64.deb
+ cryptomator_*.asc
+ - name: Publish on PPA
+ if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.dput == 'true'
+ run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
+ - name: Publish Debian package on GitHub Releases
+ if: startsWith(github.ref, 'refs/tags/')
+ uses: softprops/action-gh-release@v1
+ with:
+ fail_on_unmatched_files: true
+ token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+ files: |
+ cryptomator_*_amd64.deb
+ cryptomator_*.asc
\ No newline at end of file
diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml
new file mode 100644
index 000000000..3e1e6ceac
--- /dev/null
+++ b/.github/workflows/mac-dmg.yml
@@ -0,0 +1,239 @@
+name: Build macOS .dmg
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version'
+ required: false
+
+env:
+ JAVA_VERSION: 17
+
+jobs:
+ build:
+ name: Build Cryptomator.app
+ runs-on: macos-11
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ - name: Setup Java
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'temurin'
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'maven'
+ - id: versions
+ name: Apply version information
+ run: |
+ if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR=${GITHUB_REF##*/}
+ mvn versions:set -DnewVersion=${SEM_VER_STR}
+ elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR="${{ github.event.inputs.version }}"
+ mvn versions:set -DnewVersion=${SEM_VER_STR}
+ else
+ SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
+ fi
+ SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
+ REVCOUNT=`git rev-list --count HEAD`
+ echo "::set-output name=semVerStr::${SEM_VER_STR}"
+ echo "::set-output name=semVerNum::${SEM_VER_NUM}"
+ echo "::set-output name=revNum::${REVCOUNT}"
+ - name: Validate Version
+ uses: skymatic/semver-validation-action@v1
+ with:
+ version: ${{ steps.versions.outputs.semVerStr }}
+ - name: Run maven
+ run: mvn -B clean package -Pdependency-check,mac -DskipTests
+ - name: Patch target dir
+ run: |
+ cp LICENSE.txt target
+ cp dist/mac/launcher.sh target
+ cp target/cryptomator-*.jar target/mods
+ - name: Run jlink
+ run: >
+ ${JAVA_HOME}/bin/jlink
+ --verbose
+ --output runtime
+ --module-path "${JAVA_HOME}/jmods"
+ --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
+ --strip-native-commands
+ --no-header-files
+ --no-man-pages
+ --strip-debug
+ --compress=1
+ - name: Run jpackage
+ run: >
+ ${JAVA_HOME}/bin/jpackage
+ --verbose
+ --type app-image
+ --runtime-image runtime
+ --input target/libs
+ --module-path target/mods
+ --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
+ --dest appdir
+ --name Cryptomator
+ --vendor "Skymatic GmbH"
+ --copyright "(C) 2016 - 2022 Skymatic GmbH"
+ --app-version "${{ steps.versions.outputs.semVerNum }}"
+ --java-options "-Xss5m"
+ --java-options "-Xmx256m"
+ --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
+ --java-options "-Dfile.encoding=\"utf-8\""
+ --java-options "-Dapple.awt.enableTemplateImages=true"
+ --java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
+ --java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
+ --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
+ --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
+ --java-options "-Dcryptomator.showTrayIcon=true"
+ --java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\""
+ --mac-package-identifier org.cryptomator
+ --resource-dir dist/mac/resources
+ - name: Patch Cryptomator.app
+ run: |
+ mv appdir/Cryptomator.app Cryptomator.app
+ mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
+ sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
+ sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
+ env:
+ VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
+ REVISION_NO: ${{ steps.versions.outputs.revNum }}
+ - name: Install codesign certificate
+ run: |
+ # create variables
+ CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12
+ KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db
+
+ # import certificate and provisioning profile from secrets
+ echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH
+
+ # create temporary keychain
+ security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
+ security set-keychain-settings -lut 900 $KEYCHAIN_PATH
+ security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
+
+ # import certificate to keychain
+ security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security list-keychain -d user -s $KEYCHAIN_PATH
+ env:
+ CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }}
+ CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }}
+ CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
+ - name: Codesign
+ run: |
+ find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
+ for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
+ if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
+ JAR_FILENAME=$(basename ${JAR_PATH})
+ OUTPUT_PATH=${JAR_PATH%.*}
+ echo "Codesigning libs in ${JAR_FILENAME}..."
+ unzip -q ${JAR_PATH} -d ${OUTPUT_PATH}
+ find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
+ find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
+ rm ${JAR_PATH}
+ pushd ${OUTPUT_PATH} > /dev/null
+ zip -qr ../${JAR_FILENAME} *
+ popd > /dev/null
+ rm -r ${OUTPUT_PATH}
+ fi
+ done
+ echo "Codesigning Cryptomator.app..."
+ codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app
+ env:
+ CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
+ - name: Prepare .dmg contents
+ run: |
+ mkdir dmg
+ mv Cryptomator.app dmg
+ cp dist/mac/dmg/resources/macFUSE.webloc dmg
+ ls -l dmg
+ - name: Install create-dmg
+ run: |
+ brew install create-dmg
+ create-dmg --help
+ - name: Create .dmg
+ run: >
+ create-dmg
+ --volname Cryptomator
+ --volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns"
+ --background "dist/mac/dmg/resources/Cryptomator-background.tiff"
+ --window-pos 400 100
+ --window-size 640 694
+ --icon-size 128
+ --icon "Cryptomator.app" 128 245
+ --hide-extension "Cryptomator.app"
+ --icon "macFUSE.webloc" 320 501
+ --hide-extension "macFUSE.webloc"
+ --app-drop-link 512 245
+ --eula "dist/mac/dmg/resources/license.rtf"
+ --icon ".background" 128 758
+ --icon ".fseventsd" 320 758
+ --icon ".VolumeIcon.icns" 512 758
+ Cryptomator-${VERSION_NO}.dmg dmg
+ env:
+ VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
+ - name: Install notarization credentials
+ if: startsWith(github.ref, 'refs/tags/')
+ run: |
+ # create temporary keychain
+ KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db
+ security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH}
+ security set-keychain-settings -lut 900 ${KEYCHAIN_PATH}
+ security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH}
+
+ # import credentials from secrets
+ sudo xcode-select -s /Applications/Xcode_13.0.app
+ xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}"
+ env:
+ NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }}
+ NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
+ NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }}
+ NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
+ NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }}
+ - name: Notarize .dmg
+ if: startsWith(github.ref, 'refs/tags/')
+ run: |
+ KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db
+ sudo xcode-select -s /Applications/Xcode_13.0.app
+ xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait
+ xcrun stapler staple Cryptomator-*.dmg
+ env:
+ NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }}
+ - name: Add possible alpha/beta tags to installer name
+ run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}.dmg
+ - name: Create detached GPG signature with key 615D449FE6E6A235
+ run: |
+ echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
+ echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.dmg
+ env:
+ GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
+ GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
+ - name: Clean up codesign certificate
+ if: ${{ always() }}
+ run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
+ continue-on-error: true
+ - name: Clean up notarization credentials
+ if: ${{ always() }}
+ run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db
+ continue-on-error: true
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: dmg
+ path: Cryptomator-*.dmg
+ if-no-files-found: error
+ - name: Publish dmg on GitHub Releases
+ if: startsWith(github.ref, 'refs/tags/')
+ uses: softprops/action-gh-release@v1
+ with:
+ fail_on_unmatched_files: true
+ token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+ files: |
+ Cryptomator-*.dmg
+ Cryptomator-*.asc
+
+
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index c529ca09b..79a0d5a21 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -23,4 +23,4 @@ jobs:
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Build and Test
- run: mvn -B clean install jacoco:report -Pcoverage,dependency-check
\ No newline at end of file
+ run: xvfb-run mvn -B clean install jacoco:report -Pcoverage,dependency-check
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index e4f7e8169..000000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,633 +0,0 @@
-name: Installers and Release
-
-on:
- workflow_dispatch:
- inputs:
- semver:
- description: 'SemVer'
- required: true
- default: '0.99.99-SNAPSHOT'
- push:
- tags: # see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
- - '[0-9]+.[0-9]+.[0-9]+'
- - '[0-9]+.[0-9]+.[0-9]+-*'
-
-env:
- JAVA_VERSION: 17
-
-defaults:
- run:
- shell: bash
-
-jobs:
-
-#
-# Buildkit
-#
- buildkit:
- name: Build ${{ matrix.profile }}-buildkit
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: true
- matrix:
- include:
- - os: ubuntu-latest
- profile: linux
- - os: windows-latest
- profile: win
- - os: macos-latest
- profile: mac
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v2
- with:
- distribution: 'temurin'
- java-version: ${{ env.JAVA_VERSION }}
- cache: 'maven'
- - name: Ensure to use tagged version
- run: mvn versions:set -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags'
- if: startsWith(github.ref, 'refs/tags/')
- - name: Build and Test
- run: mvn -B clean package -Pdependency-check,${{ matrix.profile }}
- - name: Patch buildkit
- run: |
- cp LICENSE.txt target
- cp dist/${{ matrix.profile }}/launcher* target
- cp target/cryptomator-*.jar target/mods
- - name: Upload ${{ matrix.profile }}-buildkit
- uses: actions/upload-artifact@v2
- with:
- name: ${{ matrix.profile }}-buildkit
- path: |
- target/libs
- target/mods
- target/LICENSE.txt
- target/launcher*
- if-no-files-found: error
-
-#
-# Release Metadata
-#
- metadata:
- name: Determine Version Metadata
- runs-on: ubuntu-latest
- outputs:
- semVerNum: ${{ steps.versions.outputs.semVerNum }}
- semVerStr: ${{ steps.versions.outputs.semVerStr }}
- ppaVerStr: ${{ steps.versions.outputs.ppaVerStr }}
- revNum: ${{ steps.versions.outputs.revNum }}
- steps:
- - uses: actions/checkout@v2
- with:
- fetch-depth: 0
- - id: versions
- run: |
- if [[ $GITHUB_REF == refs/tags/* ]]; then
- SEM_VER_STR=${GITHUB_REF##*/}
- else
- SEM_VER_STR=${{ github.event.inputs.semver }}
- fi
- SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
- REVCOUNT=`git rev-list --count HEAD`
- echo "::set-output name=semVerStr::${SEM_VER_STR}"
- echo "::set-output name=semVerNum::${SEM_VER_NUM}"
- echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}"
- echo "::set-output name=revNum::${REVCOUNT}"
- - uses: skymatic/semver-validation-action@v1
- with:
- version: ${{ steps.versions.outputs.semVerStr }}
-
-#
-# Application Directory
-#
- appdir:
- name: Create ${{ matrix.profile }}-appdir
- needs: [buildkit, metadata]
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: true
- matrix:
- include:
- - os: ubuntu-latest
- profile: linux
- jpackageoptions: >
- --app-version "${{ needs.metadata.outputs.semVerNum }}.${{ needs.metadata.outputs.revNum }}"
- --java-options "-Dfile.encoding=\"utf-8\""
- --java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
- --java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
- --java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\""
- --java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
- --java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\""
- --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
- --java-options "-Dcryptomator.showTrayIcon=false"
- --java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.metadata.outputs.revNum }}\""
- --resource-dir dist/linux/resources
- - os: windows-latest
- profile: win
- jpackageoptions: >
- --app-version "${{ needs.metadata.outputs.semVerNum }}.${{ needs.metadata.outputs.revNum }}"
- --java-options "-Dfile.encoding=\"utf-8\""
- --java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
- --java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
- --java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\""
- --java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
- --java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\""
- --java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\""
- --java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
- --java-options "-Dcryptomator.showTrayIcon=true"
- --java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.metadata.outputs.revNum }}\""
- --resource-dir dist/win/resources
- --icon dist/win/resources/Cryptomator.ico
- - os: macos-latest
- profile: mac
- jpackageoptions: >
- --app-version "${{ needs.metadata.outputs.semVerNum }}"
- --java-options "-Dfile.encoding=\"utf-8\""
- --java-options "-Dapple.awt.enableTemplateImages=true"
- --java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
- --java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
- --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
- --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
- --java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\""
- --java-options "-Dcryptomator.showTrayIcon=true"
- --java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.metadata.outputs.revNum }}\""
- --mac-package-identifier org.cryptomator
- --resource-dir dist/mac/resources
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v2
- with:
- distribution: 'temurin'
- java-version: ${{ env.JAVA_VERSION }}
- - name: Download ${{ matrix.profile }}-buildkit
- uses: actions/download-artifact@v2
- with:
- name: ${{ matrix.profile }}-buildkit
- path: buildkit
- - name: Create Runtime Image
- run: >
- ${JAVA_HOME}/bin/jlink
- --verbose
- --output runtime
- --module-path "${JAVA_HOME}/jmods"
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
- --no-header-files
- --no-man-pages
- --strip-debug
- --compress=1
- - name: Create App Directory
- run: >
- ${JAVA_HOME}/bin/jpackage
- --verbose
- --type app-image
- --runtime-image runtime
- --input buildkit/libs
- --module-path buildkit/mods
- --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
- --dest appdir
- --name Cryptomator
- --vendor "Skymatic GmbH"
- --copyright "(C) 2016 - 2022 Skymatic GmbH"
- --java-options "-Xss5m"
- --java-options "-Xmx256m"
- --java-options "-Dcryptomator.appVersion=\"${{ needs.metadata.outputs.semVerStr }}\""
- ${{ matrix.jpackageoptions }}
- - name: Create appdir.tar
- run: tar -cvf appdir.tar appdir
- - name: Upload ${{ matrix.profile }}-appdir
- uses: actions/upload-artifact@v2
- with:
- name: ${{ matrix.profile }}-appdir
- path: appdir.tar
- if-no-files-found: error
-
-#
-# Linux PPA Source Package
-#
- ppa:
- name: Upload source package to PPA
- needs: [buildkit, metadata]
- runs-on: ubuntu-18.04
- steps:
- - uses: actions/checkout@v2
- - name: install build tools
- run: |
- sudo apt-get update
- sudo apt-get install debhelper devscripts dput
- - name: Download linux-buildkit
- uses: actions/download-artifact@v2
- with:
- name: linux-buildkit
- path: pkgdir
- - name: create orig.tar.gz
- run: tar -cJf cryptomator_${{ needs.metadata.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir .
- - name: patch and rename pkgdir
- run: |
- cp -r dist/linux/debian/ pkgdir
- cp -r dist/linux/resources/ pkgdir
- export RFC2822_TIMESTAMP=`date --rfc-2822`
- envsubst '${VERSION_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
- envsubst '${VERSION_STR}' < dist/linux/debian/org.cryptomator.Cryptomator.desktop > pkgdir/debian/org.cryptomator.Cryptomator.desktop
- envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
- find . -name "*.jar" >> pkgdir/debian/source/include-binaries
- mv pkgdir cryptomator_${{ needs.metadata.outputs.ppaVerStr }}
- env:
- VERSION_STR: ${{ needs.metadata.outputs.semVerStr }}
- VERSION_NUM: ${{ needs.metadata.outputs.semVerNum }}
- REVISION_NUM: ${{ needs.metadata.outputs.revNum }}
- PPA_VERSION: ${{ needs.metadata.outputs.ppaVerStr }}-0ppa1
- - name: import gpg key 615D449FE6E6A235
- run: |
- echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
- echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign dist/linux/debian/rules
- env:
- GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
- GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- - name: debuild
- run: debuild -S -sa -d
- env:
- DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback
- DEBSIGN_KEYID: 615D449FE6E6A235
- working-directory: cryptomator_${{ needs.metadata.outputs.ppaVerStr }}
- - name: Upload artifacts
- uses: actions/upload-artifact@v2
- with:
- name: linux-deb-source-package
- path: |
- cryptomator_*.dsc
- cryptomator_*.orig.tar.xz
- cryptomator_*.debian.tar.xz
- cryptomator_*_source.changes
- cryptomator_*_source.buildinfo
- - name: dput to beta repo
- run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_${PPA_VERSION}_source.changes
- env:
- PPA_VERSION: ${{ needs.metadata.outputs.ppaVerStr }}-0ppa1
-
-#
-# Linux Cryptomator.AppImage
-#
- linux-appimage:
- name: Build Cryptomator.AppImage
- runs-on: ubuntu-latest
- needs: [appdir, metadata]
- steps:
- - uses: actions/checkout@v2
- - name: Download linux-appdir
- uses: actions/download-artifact@v2
- with:
- name: linux-appdir
- - name: Untar appdir.tar
- run: |
- tar -xvf appdir.tar
- - name: Patch Cryptomator.AppDir
- run: |
- mv appdir/Cryptomator Cryptomator.AppDir
- cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/
- envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
- 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 bin/cryptomator.sh Cryptomator.AppDir/AppRun
- env:
- REVISION_NO: ${{ needs.metadata.outputs.revNum }}
- SEMVER_STR: ${{ needs.metadata.outputs.semVerStr }}
- - name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27
- run: |
- JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'`
- ${JAVA_HOME}/bin/jar -xf lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/
- mv jni/x86_64-Linux/* lib/app/libjffi.so
- working-directory: Cryptomator.AppDir
- - name: Download AppImageKit
- run: |
- curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage
- chmod +x appimagetool.AppImage
- ./appimagetool.AppImage --appimage-extract
- - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
- run: |
- echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
- echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign Cryptomator.AppDir/AppRun
- env:
- GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
- GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- - name: Build AppImage
- run: >
- ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.metadata.outputs.semVerStr }}-x86_64.AppImage
- -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
- --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
- - name: Upload AppImage
- uses: actions/upload-artifact@v2
- with:
- name: linux-appimage
- path: |
- cryptomator-*.AppImage
- cryptomator-*.AppImage.zsync
- if-no-files-found: error
-
-#
-# macOS Cryptomator.app
-#
- mac-app:
- name: Build Cryptomator.app
- runs-on: macos-latest
- needs: [appdir, metadata]
- steps:
- - uses: actions/checkout@v2
- - name: Download mac-appdir
- uses: actions/download-artifact@v2
- with:
- name: mac-appdir
- - name: Untar appdir.tar
- run: tar -xvf appdir.tar
- - name: Patch Cryptomator.app
- run: |
- mv appdir/Cryptomator.app Cryptomator.app
- mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
- sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
- sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
- env:
- VERSION_NO: ${{ needs.metadata.outputs.semVerNum }}
- REVISION_NO: ${{ needs.metadata.outputs.revNum }}
- - name: Install codesign certificate
- env:
- CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }}
- CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }}
- CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
- run: |
- # create variables
- CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12
- KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db
-
- # import certificate and provisioning profile from secrets
- echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH
-
- # create temporary keychain
- security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
- security set-keychain-settings -lut 900 $KEYCHAIN_PATH
- security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
-
- # import certificate to keychain
- security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
- security list-keychain -d user -s $KEYCHAIN_PATH
- - name: Codesign
- env:
- CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
- run: |
- find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
- for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
- if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
- JAR_FILENAME=$(basename ${JAR_PATH})
- OUTPUT_PATH=${JAR_PATH%.*}
- echo "Codesigning libs in ${JAR_FILENAME}..."
- unzip -q ${JAR_PATH} -d ${OUTPUT_PATH}
- find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
- find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
- rm ${JAR_PATH}
- pushd ${OUTPUT_PATH} > /dev/null
- zip -qr ../${JAR_FILENAME} *
- popd > /dev/null
- rm -r ${OUTPUT_PATH}
- fi
- done
- echo "Codesigning Cryptomator.app..."
- codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app
- - name: Clean up codesign certificate
- if: ${{ always() }}
- run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
- - name: Create app.tar
- run: tar -cvf app.tar Cryptomator.app
- - name: Upload mac-app
- uses: actions/upload-artifact@v2
- with:
- name: mac-app
- path: app.tar
- if-no-files-found: error
-
-#
-# macOS Cryptomator.dmg
-#
- mac-dmg:
- name: Build Cryptomator.dmg
- runs-on: macos-11
- needs: [mac-app, metadata]
- steps:
- - uses: actions/checkout@v2
- - name: Download mac-appdir
- uses: actions/download-artifact@v2
- with:
- name: mac-app
- - name: Untar app.tar
- run: tar -xvf app.tar
- - name: Prepare .dmg contents
- run: |
- mkdir dmg
- mv Cryptomator.app dmg
- cp dist/mac/dmg/resources/macFUSE.webloc dmg
- ls -l dmg
- - name: Install create-dmg
- run: |
- brew install create-dmg
- create-dmg --help
- - name: Create .dmg
- run: >
- create-dmg
- --volname Cryptomator
- --volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns"
- --background "dist/mac/dmg/resources/Cryptomator-background.tiff"
- --window-pos 400 100
- --window-size 640 694
- --icon-size 128
- --icon "Cryptomator.app" 128 245
- --hide-extension "Cryptomator.app"
- --icon "macFUSE.webloc" 320 501
- --hide-extension "macFUSE.webloc"
- --app-drop-link 512 245
- --eula "dist/mac/dmg/resources/license.rtf"
- --icon ".background" 128 758
- --icon ".fseventsd" 320 758
- --icon ".VolumeIcon.icns" 512 758
- Cryptomator-${VERSION_NO}.dmg dmg
- env:
- VERSION_NO: ${{ needs.metadata.outputs.semVerNum }}
- - name: Install notarization credentials
- env:
- NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }}
- NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
- NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }}
- NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
- NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }}
- run: |
- # create temporary keychain
- KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db
- security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH}
- security set-keychain-settings -lut 900 ${KEYCHAIN_PATH}
- security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH}
-
- # import credentials from secrets
- sudo xcode-select -s /Applications/Xcode_13.0.app
- xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}"
- - name: Notarize .dmg
- env:
- NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }}
- run: |
- KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db
- sudo xcode-select -s /Applications/Xcode_13.0.app
- xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait
- xcrun stapler staple Cryptomator-*.dmg
- - name: Clean up notarization credentials
- if: ${{ always() }}
- run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db
- - name: Add possible alpha/beta tags to installer name
- run: mv Cryptomator-*.dmg Cryptomator-${{ needs.metadata.outputs.semVerStr }}.dmg
- - name: Upload mac-dmg
- uses: actions/upload-artifact@v2
- with:
- name: mac-dmg
- path: Cryptomator-*.dmg
- if-no-files-found: error
-
-#
-# MSI package
-#
- win-msi:
- name: Build Cryptomator.msi
- runs-on: windows-latest
- needs: [appdir, metadata]
- steps:
- - uses: actions/checkout@v2
- - name: Download win-appdir
- uses: actions/download-artifact@v2
- with:
- name: win-appdir
- - name: Untar appdir.tar
- run: tar -xvf appdir.tar
- - uses: actions/setup-java@v2
- with:
- distribution: 'temurin'
- java-version: ${{ env.JAVA_VERSION }}
- - name: Patch Application Directory
- run: |
- cp dist/win/contrib/* appdir/Cryptomator
- - name: Fix permissions
- run: attrib -r appdir/Cryptomator/Cryptomator.exe
- shell: pwsh
- - name: Codesign
- uses: skymatic/code-sign-action@v1
- with:
- certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
- password: ${{ secrets.WIN_CODESIGN_P12_PW }}
- certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
- description: Cryptomator
- timestampUrl: 'http://timestamp.digicert.com'
- folder: appdir/Cryptomator
- recursive: true
- - name: Create MSI
- run: >
- ${JAVA_HOME}/bin/jpackage
- --verbose
- --type msi
- --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775
- --app-image appdir/Cryptomator
- --dest installer
- --name Cryptomator
- --vendor "Skymatic GmbH"
- --copyright "(C) 2016 - 2022 Skymatic GmbH"
- --app-version "${{ needs.metadata.outputs.semVerNum }}"
- --win-menu
- --win-dir-chooser
- --win-shortcut-prompt
- --win-update-url "https:\\cryptomator.org"
- --win-menu-group Cryptomator
- --resource-dir dist/win/resources
- --license-file dist/win/resources/license.rtf
- --file-associations dist/win/resources/FAvaultFile.properties
- env:
- JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
- - name: Codesign MSI
- uses: skymatic/code-sign-action@v1
- with:
- certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
- password: ${{ secrets.WIN_CODESIGN_P12_PW }}
- certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
- description: Cryptomator Installer
- timestampUrl: 'http://timestamp.digicert.com'
- folder: installer
- - name: Add possible alpha/beta tags to installer name
- run: mv installer/Cryptomator-*.msi installer/Cryptomator-${{ needs.metadata.outputs.semVerStr }}-x64.msi
- - name: Upload win-msi
- uses: actions/upload-artifact@v2
- with:
- name: win-msi
- path: installer/*.msi
- if-no-files-found: error
-
-#
-# Release
-#
- release:
- name: Draft a release on Github
- runs-on: ubuntu-latest
- needs: [metadata,linux-appimage,mac-dmg,win-msi,ppa]
- if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator'
- steps:
- - uses: actions/checkout@v2
- - name: Create tarball
- run: git archive --prefix="cryptomator-${{ needs.metadata.outputs.semVerStr }}/" -o "cryptomator-${{ needs.metadata.outputs.semVerStr }}.tar.gz" ${{ github.ref }}
- - name: Download linux appimage
- uses: actions/download-artifact@v2
- with:
- name: linux-appimage
- - name: Download macOS dmg
- uses: actions/download-artifact@v2
- with:
- name: mac-dmg
- - name: Download Windows msi
- uses: actions/download-artifact@v2
- with:
- name: win-msi
- - name: Create detached GPG signature for all release files with key 615D449FE6E6A235
- run: |
- echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
- for FILE in `find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.zsync" -o -name "*.tar.gz"`; do
- echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a ${FILE}
- done
- env:
- GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
- GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- - name: Compute SHA256 checksums of release artifacts
- run: |
- SHA256_SUMS=`find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.tar.gz" | xargs sha256sum`
- echo "SHA256_SUMS<> $GITHUB_ENV
- echo "${SHA256_SUMS}" >> $GITHUB_ENV
- echo "EOF" >> $GITHUB_ENV
- continue-on-error: true
- - name: Create release draft
- uses: softprops/action-gh-release@v1
- with:
- draft: true
- fail_on_unmatched_files: true
- discussion_category_name: releases
- token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
- files: |
- *.AppImage
- *.zsync
- *.asc
- *.dmg
- *.msi
- body: |-
- :construction: Work in Progress
- ## What's New
- ## Bugfixes
- ## Misc
-
- ---
-
- :scroll: A complete list of closed issues is available [here](LINK).
-
- ---
-
- :floppy_disk: SHA-256 checksums of release artifacts:
- ```
- ${{ env.SHA256_SUMS }}
- ```
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
new file mode 100644
index 000000000..5b1ba1aaf
--- /dev/null
+++ b/.github/workflows/win-exe.yml
@@ -0,0 +1,322 @@
+name: Build Windows Installer
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version'
+ required: false
+
+env:
+ JAVA_VERSION: 17
+ WINFSP_MSI: https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ build-msi:
+ name: Build .msi Installer
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ - name: Setup Java
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'temurin'
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'maven'
+ - id: versions
+ name: Apply version information
+ run: |
+ if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR=${GITHUB_REF##*/}
+ mvn versions:set -DnewVersion=${SEM_VER_STR}
+ elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR="${{ github.event.inputs.version }}"
+ mvn versions:set -DnewVersion=${SEM_VER_STR}
+ else
+ SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
+ fi
+ SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
+ REVCOUNT=`git rev-list --count HEAD`
+ echo "::set-output name=semVerStr::${SEM_VER_STR}"
+ echo "::set-output name=semVerNum::${SEM_VER_NUM}"
+ echo "::set-output name=revNum::${REVCOUNT}"
+ - name: Validate Version
+ uses: skymatic/semver-validation-action@v1
+ with:
+ version: ${{ steps.versions.outputs.semVerStr }}
+ - name: Run maven
+ run: mvn -B clean package -Pdependency-check,win -DskipTests
+ - name: Patch target dir
+ run: |
+ cp LICENSE.txt target
+ cp dist/linux/launcher.sh target
+ cp target/cryptomator-*.jar target/mods
+ - name: Run jlink
+ run: >
+ ${JAVA_HOME}/bin/jlink
+ --verbose
+ --output runtime
+ --module-path "${JAVA_HOME}/jmods"
+ --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
+ --strip-native-commands
+ --no-header-files
+ --no-man-pages
+ --strip-debug
+ --compress=1
+ - name: Run jpackage
+ run: >
+ ${JAVA_HOME}/bin/jpackage
+ --verbose
+ --type app-image
+ --runtime-image runtime
+ --input target/libs
+ --module-path target/mods
+ --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
+ --dest appdir
+ --name Cryptomator
+ --vendor "Skymatic GmbH"
+ --copyright "(C) 2016 - 2022 Skymatic GmbH"
+ --app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
+ --java-options "-Xss5m"
+ --java-options "-Xmx256m"
+ --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
+ --java-options "-Dfile.encoding=\"utf-8\""
+ --java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
+ --java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
+ --java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\""
+ --java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
+ --java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\""
+ --java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
+ --java-options "-Dcryptomator.showTrayIcon=true"
+ --java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\""
+ --resource-dir dist/win/resources
+ --icon dist/win/resources/Cryptomator.ico
+ - name: Patch Application Directory
+ run: |
+ cp dist/win/contrib/* appdir/Cryptomator
+ - name: Fix permissions
+ run: attrib -r appdir/Cryptomator/Cryptomator.exe
+ shell: pwsh
+ - name: Codesign
+ uses: skymatic/code-sign-action@v1
+ with:
+ certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
+ password: ${{ secrets.WIN_CODESIGN_P12_PW }}
+ certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
+ description: Cryptomator
+ timestampUrl: 'http://timestamp.digicert.com'
+ folder: appdir/Cryptomator
+ recursive: true
+ - name: Generate license
+ run: >
+ mvn -B license:add-third-party
+ "-Dlicense.thirdPartyFilename=license.rtf"
+ "-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl"
+ "-Dlicense.outputDirectory=dist/win/resources"
+ - name: Create MSI
+ run: >
+ ${JAVA_HOME}/bin/jpackage
+ --verbose
+ --type msi
+ --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775
+ --app-image appdir/Cryptomator
+ --dest installer
+ --name Cryptomator
+ --vendor "Skymatic GmbH"
+ --copyright "(C) 2016 - 2022 Skymatic GmbH"
+ --app-version "${{ steps.versions.outputs.semVerNum }}"
+ --win-menu
+ --win-dir-chooser
+ --win-shortcut-prompt
+ --win-update-url "https:\\cryptomator.org"
+ --win-menu-group Cryptomator
+ --resource-dir dist/win/resources
+ --license-file dist/win/resources/license.rtf
+ --file-associations dist/win/resources/FAvaultFile.properties
+ env:
+ JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
+ - name: Codesign MSI
+ uses: skymatic/code-sign-action@v1
+ with:
+ certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
+ password: ${{ secrets.WIN_CODESIGN_P12_PW }}
+ certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
+ description: Cryptomator Installer
+ timestampUrl: 'http://timestamp.digicert.com'
+ folder: installer
+ - name: Add possible alpha/beta tags to installer name
+ run: mv installer/Cryptomator-*.msi Cryptomator-${{ steps.versions.outputs.semVerStr }}-x64.msi
+ - name: Create detached GPG signature with key 615D449FE6E6A235
+ run: |
+ echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
+ echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.msi
+ env:
+ GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
+ GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: msi
+ path: |
+ Cryptomator-*.msi
+ Cryptomator-*.asc
+ if-no-files-found: error
+ - name: Publish .msi on GitHub Releases
+ if: startsWith(github.ref, 'refs/tags/')
+ uses: softprops/action-gh-release@v1
+ with:
+ fail_on_unmatched_files: true
+ token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+ files: |
+ *.msi
+ *.asc
+ outputs:
+ semVerNum: ${{ steps.versions.outputs.semVerNum }}
+ semVerStr: ${{ steps.versions.outputs.semVerStr }}
+ revNum: ${{ steps.versions.outputs.revNum }}
+
+ build-exe:
+ name: Build .exe installer
+ runs-on: windows-latest
+ needs: [build-msi]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Download .msi
+ uses: actions/download-artifact@v2
+ with:
+ name: msi
+ 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@v2
+ with:
+ distribution: 'temurin'
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'maven'
+ - name: Generate license
+ run: >
+ mvn -B license:add-third-party
+ "-Dlicense.thirdPartyFilename=license.rtf"
+ "-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl"
+ "-Dlicense.outputDirectory=dist/win/bundle/resources"
+ - name: Download WinFsp
+ run:
+ curl --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }}
+ - name: Compile to wixObj file
+ run: >
+ "${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs
+ -ext WixBalExtension
+ -out dist/win/bundle/
+ -dBundleVersion="${{ needs.build-msi.outputs.semVerNum }}.${{ needs.build-msi.outputs.revNum }}"
+ -dBundleVendor="Skymatic GmbH"
+ -dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH"
+ -dAboutUrl="https://cryptomator.org"
+ -dHelpUrl="https://cryptomator.org/contact"
+ -dUpdateUrl="https://cryptomator.org/downloads/"
+ - name: Create executable with linker
+ run: >
+ "${WIX}/bin/light.exe" -b dist/win/ dist/win/bundle/bundleWithWinfsp.wixobj
+ -ext WixBalExtension
+ -out installer/unsigned/Cryptomator-Installer.exe
+ - name: Detach burn engine in preparation to sign
+ run: >
+ "${WIX}/bin/insignia.exe"
+ -ib installer/unsigned/Cryptomator-Installer.exe
+ -o tmp/engine.exe
+ - name: Codesign burn engine
+ uses: skymatic/code-sign-action@v1
+ with:
+ certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
+ password: ${{ secrets.WIN_CODESIGN_P12_PW }}
+ certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
+ description: Cryptomator Installer
+ timestampUrl: 'http://timestamp.digicert.com'
+ folder: tmp
+ - name: Reattach signed burn engine to installer
+ run : >
+ "${WIX}/bin/insignia.exe"
+ -ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
+ -o installer/Cryptomator-Installer.exe
+ - name: Codesign EXE
+ uses: skymatic/code-sign-action@v1
+ with:
+ certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
+ password: ${{ secrets.WIN_CODESIGN_P12_PW }}
+ certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
+ description: Cryptomator Installer
+ timestampUrl: 'http://timestamp.digicert.com'
+ folder: installer
+ - name: Add possible alpha/beta tags to installer name
+ run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe
+ - name: Create detached GPG signature with key 615D449FE6E6A235
+ run: |
+ echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
+ echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.exe
+ env:
+ GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
+ GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: exe
+ path: |
+ Cryptomator-*.exe
+ Cryptomator-*.asc
+ if-no-files-found: error
+ - name: Publish .msi on GitHub Releases
+ if: startsWith(github.ref, 'refs/tags/')
+ uses: softprops/action-gh-release@v1
+ with:
+ fail_on_unmatched_files: true
+ token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+ files: |
+ Cryptomator-*.exe
+ Cryptomator-*.asc
+
+ allowlist:
+ name: Anti Virus Allowlisting
+ if: startsWith(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+ needs: [build-msi, build-exe]
+ steps:
+ - name: Download .msi
+ uses: actions/download-artifact@v2
+ with:
+ name: msi
+ path: msi
+ - name: Download .exe
+ uses: actions/download-artifact@v2
+ with:
+ name: exe
+ path: exe
+ - name: Collect files
+ run: |
+ mkdir files
+ cp msi/*.msi files
+ cp exe/*.exe files
+ - name: Upload to Kaspersky
+ uses: SamKirkland/FTP-Deploy-Action@4.3.0
+ with:
+ protocol: ftps
+ server: allowlist.kaspersky-labs.com
+ port: 990
+ username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
+ password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
+ local-dir: files/
+ - name: Upload to Avast
+ uses: SamKirkland/FTP-Deploy-Action@4.3.0
+ with:
+ protocol: ftp
+ server: whitelisting.avast.com
+ port: 21
+ username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
+ password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
+ local-dir: files/
diff --git a/.gitignore b/.gitignore
index 8e239b35e..5c84c0dfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,7 +21,6 @@ pom.xml.versionsBackup
.idea/dictionaries/**
!.idea/dictionaries/dict_*
.idea/compiler.xml
-.idea/encodings.xml
.idea/jarRepositories.xml
.idea/uiDesigner.xml
.idea/**/libraries/
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 000000000..63574ec0a
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Cryptomator_Linux.xml b/.idea/runConfigurations/Cryptomator_Linux.xml
index b8339789b..6378f9a00 100644
--- a/.idea/runConfigurations/Cryptomator_Linux.xml
+++ b/.idea/runConfigurations/Cryptomator_Linux.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/.idea/runConfigurations/Cryptomator_Linux_Dev.xml b/.idea/runConfigurations/Cryptomator_Linux_Dev.xml
index 29031b1f3..169d630b8 100644
--- a/.idea/runConfigurations/Cryptomator_Linux_Dev.xml
+++ b/.idea/runConfigurations/Cryptomator_Linux_Dev.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/.idea/runConfigurations/Cryptomator_Windows.xml b/.idea/runConfigurations/Cryptomator_Windows.xml
index f87c15524..2e75463d1 100644
--- a/.idea/runConfigurations/Cryptomator_Windows.xml
+++ b/.idea/runConfigurations/Cryptomator_Windows.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/.idea/runConfigurations/Cryptomator_Windows_Dev.xml b/.idea/runConfigurations/Cryptomator_Windows_Dev.xml
index f31b0f04c..da9a7994f 100644
--- a/.idea/runConfigurations/Cryptomator_Windows_Dev.xml
+++ b/.idea/runConfigurations/Cryptomator_Windows_Dev.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/.idea/runConfigurations/Cryptomator_macOS.xml b/.idea/runConfigurations/Cryptomator_macOS.xml
index f240996e2..bf8c97166 100644
--- a/.idea/runConfigurations/Cryptomator_macOS.xml
+++ b/.idea/runConfigurations/Cryptomator_macOS.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/.idea/runConfigurations/Cryptomator_macOS_Dev.xml b/.idea/runConfigurations/Cryptomator_macOS_Dev.xml
index 7b3d28b38..3f7ce8c3a 100644
--- a/.idea/runConfigurations/Cryptomator_macOS_Dev.xml
+++ b/.idea/runConfigurations/Cryptomator_macOS_Dev.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/README.md b/README.md
index 7ffb1fba1..ded200883 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
[](https://snyk.io/test/github/cryptomator/cryptomator)
-[](https://www.codacy.com/gh/cryptomator/cryptomator/dashboard)
+[](https://sonarcloud.io/dashboard?id=cryptomator_cryptomator)
[](http://twitter.com/Cryptomator)
[](https://translate.cryptomator.org/)
[](https://github.com/cryptomator/cryptomator/releases/latest)
diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh
index af1f2291f..6dd670df2 100755
--- a/dist/linux/appimage/build.sh
+++ b/dist/linux/appimage/build.sh
@@ -9,6 +9,7 @@ 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; }
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
+SEMVER_STR=${VERSION}
# compile
mvn -B -f ../../../pom.xml clean package -DskipTests -Plinux
@@ -54,6 +55,12 @@ mv Cryptomator Cryptomator.AppDir
cp -r resources/AppDir/* Cryptomator.AppDir/
chmod +x Cryptomator.AppDir/lib/runtime/bin/java
envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
+cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png
+cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png
+cp ../common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg
+cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop
+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
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/applications/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/applications/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg
deleted file mode 100644
index b2e12a3c3..000000000
--- a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/metainfo/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/metainfo/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml b/dist/linux/appimage/resources/AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml
deleted file mode 100644
index ad4af6c70..000000000
--- a/dist/linux/appimage/resources/AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
- org.cryptomator.Cryptomator
- FSFAP
- GPL-3.0-or-later
- Cryptomator
- Multi-platform client-side encryption tool optimized for cloud storages
-
-
- Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud.
-
-
- Features:
-
- Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
- Open Source means: No backdoors, control is better than trust
- Client-side: No accounts, no data shared with any online service
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
- AES encryption with 256-bit key length
- File names get encrypted
- Folder structure gets obfuscated
- Use as many vaults in your Dropbox as you want, each having individual passwords
- One thousand commits for the security of your data!! :tada:
-
-
-
- Privacy:
-
- 256-bit keys (unlimited strength policy bundled with native binaries)
- Scrypt key derivation
- Cryptographically secure random numbers for salts, IVs and the masterkey of course
- Sensitive data is wiped from the heap asap
- Lightweight: Complexity kills security
-
-
-
- Consistency:
-
- HMAC over file contents to recognize changed ciphertext before decryption
- I/O operations are transactional and atomic, if the filesystems support it
- Each file contains all information needed for decryption (except for the key of course), no common metadata means no Single Point of Failure
-
-
-
-
- Office
- Security
- FileTools
- Java
-
- http://cryptomator.org
- https://github.com/cryptomator/cryptomator/issues
- https://community.cryptomator.org/c/kb/faq
- https://community.cryptomator.org/
- https://cryptomator.org/
-
- none
- none
- none
- none
- mild
-
- Cryptomator
-
- cryptomator
-
- org.cryptomator.Cryptomator.desktop
-
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/mime/packages/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/mime/packages/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/dist/linux/debian/cryptomator-vault.xml b/dist/linux/common/application-vnd.cryptomator.vault.xml
similarity index 77%
rename from dist/linux/debian/cryptomator-vault.xml
rename to dist/linux/common/application-vnd.cryptomator.vault.xml
index eeb4bc537..3b602f230 100644
--- a/dist/linux/debian/cryptomator-vault.xml
+++ b/dist/linux/common/application-vnd.cryptomator.vault.xml
@@ -1,6 +1,6 @@
-
+
Cryptomator Vault Metadata
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop b/dist/linux/common/org.cryptomator.Cryptomator.desktop
similarity index 55%
rename from dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop
rename to dist/linux/common/org.cryptomator.Cryptomator.desktop
index 3e1b34830..396c3acb7 100644
--- a/dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop
+++ b/dist/linux/common/org.cryptomator.Cryptomator.desktop
@@ -6,5 +6,6 @@ Icon=org.cryptomator.Cryptomator
Terminal=false
Type=Application
Categories=Utility;Security;FileTools;
-StartupWMClass=org.cryptomator.launcher.Cryptomator
-MimeType=application/vnd.cryptomator.encrypted;application/x-vnd.cryptomator.vault-metadata;
+StartupNotify=true
+StartupWMClass=org.cryptomator.launcher.Cryptomator$MainApp
+MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault;
diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml
new file mode 100644
index 000000000..58b95fb82
--- /dev/null
+++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml
@@ -0,0 +1,72 @@
+
+
+
+ org.cryptomator.Cryptomator
+ FSFAP
+ GPL-3.0-or-later
+ Cryptomator
+ Multi-platform client-side encryption tool optimized for cloud storages
+
+
+
+ Cryptomator provides transparent, client-side encryption for your cloud. Protect your documents from unauthorized
+ access. Cryptomator is free and open source software, so you can rest assured there are no backdoors.
+
+
+ Cryptomator encrypts file contents and names using AES. Your passphrase is protected against bruteforcing attempts
+ using scrypt. Directory structures get obfuscated. The only thing which cannot be encrypted without breaking your
+ cloud synchronization is the modification date of your files.
+
+
+ Cryptomator is a free and open source software licensed under the GPLv3. This allows anyone to check our code. It
+ is impossible to introduce backdoors for third parties. Also we cannot hide vulnerabilities. And the best thing
+ is: There is no need to trust us, as you can control us!
+
+
+ Vendor lock-ins are impossible. Even if we decided to stop development: The source code is already cloned by
+ hundreds of other developers. As you don't need an account, you will never stand in front of locked doors.
+
+
+
+
+ Office
+ Security
+ FileTools
+
+
+ org.cryptomator.Cryptomator.desktop
+
+ cryptomator
+ application/vnd.cryptomator.vault
+ application/vnd.cryptomator.encrypted
+
+
+
+
+ Light theme
+ https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png
+
+
+ Dark theme
+ https://user-images.githubusercontent.com/11858409/156986113-6c5d7801-86e0-4643-bc2f-aff9d95d3ce0.png
+
+
+
+ https://cryptomator.org/
+ https://github.com/cryptomator/cryptomator/issues/
+ https://cryptomator.org/donate
+ https://community.cryptomator.org/c/kb/faq
+ https://community.cryptomator.org/
+ https://translate.cryptomator.org
+
+ Skymatic GmbH
+
+
+ mild
+
+
+
+
+
+
+
diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.svg b/dist/linux/common/org.cryptomator.Cryptomator.svg
similarity index 99%
rename from dist/linux/debian/org.cryptomator.Cryptomator.svg
rename to dist/linux/common/org.cryptomator.Cryptomator.svg
index 19d80d49c..c76d99fb9 100644
--- a/dist/linux/debian/org.cryptomator.Cryptomator.svg
+++ b/dist/linux/common/org.cryptomator.Cryptomator.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png b/dist/linux/common/org.cryptomator.Cryptomator256.png
similarity index 100%
rename from dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png
rename to dist/linux/common/org.cryptomator.Cryptomator256.png
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png b/dist/linux/common/org.cryptomator.Cryptomator512.png
similarity index 100%
rename from dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png
rename to dist/linux/common/org.cryptomator.Cryptomator512.png
diff --git a/dist/linux/debian/cryptomator.install b/dist/linux/debian/cryptomator.install
index 0d5e0b31c..d3ff8cf1f 100644
--- a/dist/linux/debian/cryptomator.install
+++ b/dist/linux/debian/cryptomator.install
@@ -1,7 +1,8 @@
cryptomator usr/lib
debian/cryptomator.sh usr/lib/cryptomator/bin
-debian/org.cryptomator.Cryptomator.desktop usr/share/applications
-debian/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps
-debian/org.cryptomator.Cryptomator.png usr/share/icons/hicolor/512x512/apps
-debian/org.cryptomator.Cryptomator.appdata.xml usr/share/metainfo
-debian/cryptomator-vault.xml usr/share/mime/packages
\ No newline at end of file
+common/org.cryptomator.Cryptomator.desktop usr/share/applications
+common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps
+common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps
+common/org.cryptomator.Cryptomator512.png usr/share/icons/hicolor/512x512/apps
+common/org.cryptomator.Cryptomator.metainfo.xml usr/share/metainfo
+common/application-vnd.cryptomator.vault.xml usr/share/mime/packages
\ No newline at end of file
diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml b/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml
deleted file mode 100644
index ad4af6c70..000000000
--- a/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
- org.cryptomator.Cryptomator
- FSFAP
- GPL-3.0-or-later
- Cryptomator
- Multi-platform client-side encryption tool optimized for cloud storages
-
-
- Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud.
-
-
- Features:
-
- Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
- Open Source means: No backdoors, control is better than trust
- Client-side: No accounts, no data shared with any online service
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
- AES encryption with 256-bit key length
- File names get encrypted
- Folder structure gets obfuscated
- Use as many vaults in your Dropbox as you want, each having individual passwords
- One thousand commits for the security of your data!! :tada:
-
-
-
- Privacy:
-
- 256-bit keys (unlimited strength policy bundled with native binaries)
- Scrypt key derivation
- Cryptographically secure random numbers for salts, IVs and the masterkey of course
- Sensitive data is wiped from the heap asap
- Lightweight: Complexity kills security
-
-
-
- Consistency:
-
- HMAC over file contents to recognize changed ciphertext before decryption
- I/O operations are transactional and atomic, if the filesystems support it
- Each file contains all information needed for decryption (except for the key of course), no common metadata means no Single Point of Failure
-
-
-
-
- Office
- Security
- FileTools
- Java
-
- http://cryptomator.org
- https://github.com/cryptomator/cryptomator/issues
- https://community.cryptomator.org/c/kb/faq
- https://community.cryptomator.org/
- https://cryptomator.org/
-
- none
- none
- none
- none
- mild
-
- Cryptomator
-
- cryptomator
-
- org.cryptomator.Cryptomator.desktop
-
diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.desktop b/dist/linux/debian/org.cryptomator.Cryptomator.desktop
deleted file mode 100644
index d8a5925bd..000000000
--- a/dist/linux/debian/org.cryptomator.Cryptomator.desktop
+++ /dev/null
@@ -1,11 +0,0 @@
-[Desktop Entry]
-Name=Cryptomator
-Version=${VERSION_STR}
-Comment=Cloud Storage Encryption Utility
-Exec=/usr/bin/cryptomator %f
-Icon=org.cryptomator.Cryptomator
-Terminal=false
-Type=Application
-Categories=Utility;Security;FileTools;
-StartupWMClass=org.cryptomator.launcher.Cryptomator
-MimeType=application/vnd.cryptomator.encrypted;application/x-vnd.cryptomator.vault-metadata;
\ No newline at end of file
diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.png b/dist/linux/debian/org.cryptomator.Cryptomator.png
deleted file mode 100644
index 9c8635111..000000000
Binary files a/dist/linux/debian/org.cryptomator.Cryptomator.png and /dev/null differ
diff --git a/dist/linux/debian/postinst b/dist/linux/debian/postinst
index 2af574c6b..5668a5e29 100644
--- a/dist/linux/debian/postinst
+++ b/dist/linux/debian/postinst
@@ -24,7 +24,7 @@ case "$1" in
mkdir -p /usr/share/desktop-directories
fi
xdg-desktop-menu install --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop
- xdg-mime install /usr/share/mime/packages/cryptomator-vault.xml
+ xdg-mime install /usr/share/mime/packages/application-vnd.cryptomator.vault.xml
;;
abort-upgrade|abort-remove|abort-deconfigure)
diff --git a/dist/linux/debian/prerm b/dist/linux/debian/prerm
index cace6816e..41a54cf33 100644
--- a/dist/linux/debian/prerm
+++ b/dist/linux/debian/prerm
@@ -22,7 +22,7 @@ case "$1" in
echo Removing shortcut
xdg-desktop-menu uninstall --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop
- xdg-mime uninstall /usr/share/mime/packages/cryptomator-vault.xml
+ xdg-mime uninstall /usr/share/mime/packages/application-vnd.cryptomator.vault.xml
;;
failed-upgrade)
diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules
index b381a2331..e4f824394 100755
--- a/dist/linux/debian/rules
+++ b/dist/linux/debian/rules
@@ -11,8 +11,11 @@ override_dh_auto_clean:
rm -rf runtime
rm -rf cryptomator
rm -rf debian/cryptomator
+ rm -rf resources
override_dh_auto_build:
+ mkdir resources
+ ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
jlink \
--output runtime \
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
@@ -39,8 +42,8 @@ override_dh_auto_build:
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.showTrayIcon=false" \
- --java-options "-Dcryptomator.buildNumber=\"ppa-${REVISION_NUM}\"" \
- --java-options "-Dcryptomator.appVersion=\"${VERSION_STR}\"" \
+ --java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \
+ --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \
--app-version "${VERSION_NUM}.${REVISION_NUM}" \
--resource-dir resources \
--verbose
diff --git a/dist/linux/debian/source/include-binaries b/dist/linux/debian/source/include-binaries
index adc7cabd8..8b9254c65 100644
--- a/dist/linux/debian/source/include-binaries
+++ b/dist/linux/debian/source/include-binaries
@@ -1,2 +1,2 @@
-debian/org.cryptomator.Cryptomator.png
-resources/cryptomator.png
+common/org.cryptomator.Cryptomator256.png
+common/org.cryptomator.Cryptomator512.png
diff --git a/dist/mac/dmg/.gitignore b/dist/mac/dmg/.gitignore
index c186170c9..b8ef35283 100644
--- a/dist/mac/dmg/.gitignore
+++ b/dist/mac/dmg/.gitignore
@@ -1,4 +1,5 @@
# created during build
+Cryptomator.app/
runtime/
dmg/
-*.dmg
+*.dmg
\ No newline at end of file
diff --git a/dist/win/.gitignore b/dist/win/.gitignore
index 2b66ddbed..9cce929df 100644
--- a/dist/win/.gitignore
+++ b/dist/win/.gitignore
@@ -1,3 +1,7 @@
runtime
Cryptomator
-installer
\ No newline at end of file
+installer
+*.wixobj
+*.pdb
+*.msi
+license.rtf
\ No newline at end of file
diff --git a/dist/win/build.ps1 b/dist/win/build.ps1
index 2919e493a..e3ba36efd 100644
--- a/dist/win/build.ps1
+++ b/dist/win/build.ps1
@@ -1,11 +1,14 @@
+# check parameters
+$clean = $args[0] -eq "fresh"
+
# check preconditions
-if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null)
-{
+if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null)
+{
Write-Host "Unable to find git.exe in your PATH (try: choco install git)"
exit 1
}
-if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null)
-{
+if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null)
+{
Write-Host "Unable to find mvn.cmd in your PATH (try: choco install maven)"
exit 1
}
@@ -21,11 +24,19 @@ Write-Output "`$revisionNo=$revisionNo"
Write-Output "`$buildDir=$buildDir"
Write-Output "`$Env:JAVA_HOME=$Env:JAVA_HOME"
+$vendor = "Skymatic GmbH"
+$copyright = "(C) 2016 - 2022 Skymatic GmbH"
+
# compile
&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin
Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\..\target\mods"
# add runtime
+$runtimeImagePath = '.\runtime'
+if ($clean -and (Test-Path -Path $runtimeImagePath)) {
+ Remove-Item -Path $runtimeImagePath -Force -Recurse
+}
+
& "$Env:JAVA_HOME\bin\jlink" `
--verbose `
--output runtime `
@@ -36,6 +47,11 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\
--strip-debug `
--compress=1
+$appPath = '.\Cryptomator'
+if ($clean -and (Test-Path -Path $appPath)) {
+ Remove-Item -Path $appPath -Force -Recurse
+}
+
# create app dir
& "$Env:JAVA_HOME\bin\jpackage" `
--verbose `
@@ -46,8 +62,8 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator `
--dest . `
--name Cryptomator `
- --vendor "Skymatic GmbH" `
- --copyright "(C) 2016 - 2022 Skymatic GmbH" `
+ --vendor $vendor `
+ --copyright $copyright `
--java-options "-Xss5m" `
--java-options "-Xmx256m" `
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
@@ -64,11 +80,21 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\
--resource-dir resources `
--icon resources/Cryptomator.ico
+#Create RTF license for msi
+&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
+ "-Dlicense.thirdPartyFilename=license.rtf" `
+ "-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" `
+ "-Dlicense.outputDirectory=$buildDir\resources\"
+
# patch app dir
Copy-Item "contrib\*" -Destination "Cryptomator"
attrib -r "Cryptomator\Cryptomator.exe"
-# create .msi bundle
+$aboutUrl="https://cryptomator.org"
+$updateUrl="https://cryptomator.org/downloads/"
+$helpUrl="https://cryptomator.org/contact/"
+
+# create .msi
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
& "$Env:JAVA_HOME\bin\jpackage" `
--verbose `
@@ -77,14 +103,41 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
--app-image Cryptomator `
--dest installer `
--name Cryptomator `
- --vendor "Skymatic GmbH" `
- --copyright "(C) 2016 - 2022 Skymatic GmbH" `
+ --vendor $vendor `
+ --copyright $copyright `
--app-version "$semVerNo" `
--win-menu `
--win-dir-chooser `
--win-shortcut-prompt `
- --win-update-url "https:\\cryptomator.org" `
+ --win-update-url $updateUrl `
--win-menu-group Cryptomator `
--resource-dir resources `
+ --about-url $aboutUrl `
--license-file resources/license.rtf `
--file-associations resources/FAvaultFile.properties
+
+#Create RTF license for bundle
+&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
+ "-Dlicense.thirdPartyFilename=license.rtf" `
+ "-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" `
+ "-Dlicense.outputDirectory=$buildDir\bundle\resources\"
+
+# download Winfsp
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s
+$winfspMsiUrl = "https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi"
+Write-Output "Downloading ${winfspMsiUrl}..."
+Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
+
+# copy MSI to bundle resources
+Copy-Item ".\installer\Cryptomator-*.msi" -Destination ".\bundle\resources\Cryptomator.msi"
+
+# create bundle including winfsp
+& "$env:WIX\bin\candle.exe" .\bundle\bundleWithWinfsp.wxs -ext WixBalExtension -out bundle\ `
+ -dBundleVersion="$semVerNo.$revisionNo" `
+ -dBundleVendor="$vendor" `
+ -dBundleCopyright="$copyright" `
+ -dAboutUrl="$aboutUrl" `
+ -dHelpUrl="$helpUrl" `
+ -dUpdateUrl="$updateUrl"
+& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\Cryptomator-Installer.exe
\ No newline at end of file
diff --git a/dist/win/bundle/bundleWithWinfsp.wxs b/dist/win/bundle/bundleWithWinfsp.wxs
new file mode 100644
index 000000000..91093df7d
--- /dev/null
+++ b/dist/win/bundle/bundleWithWinfsp.wxs
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/win/bundle/customBootstrapperTheme.wxl b/dist/win/bundle/customBootstrapperTheme.wxl
new file mode 100644
index 000000000..cc464ce9c
--- /dev/null
+++ b/dist/win/bundle/customBootstrapperTheme.wxl
@@ -0,0 +1,64 @@
+
+
+
+
+
+ [WixBundleName] Setup
+ [WixBundleName]
+
+ This Setup will install [WixBundleName] and additional dependencies on your computer.
+ Version [WixBundleVersion]
+ Are you sure you want to cancel?
+ Previous version
+
+ /install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or
+ creates a complete local copy of the bundle in directory. Install is the default.
+
+/passive | /quiet - displays minimal UI with no prompts or displays no UI and
+ no prompts. By default UI and all prompts are displayed.
+
+/norestart - suppress any attempts to restart. By default UI will prompt before restart.
+/log log.txt - logs to a specific file. By default a log file is created in %TEMP%.
+ &Close
+ [WixBundleName] <a href="#">license terms</a>.
+ I &agree to the license terms and conditions
+ &Options
+ &Install
+ &Close
+
+ Install location:
+ &Browse
+ &OK
+ &Cancel
+
+ Processing:
+ Initializing...
+ &Cancel
+
+ &Repair
+ &Uninstall
+ &Close
+
+
+
+
+ &Launch
+ You must restart your computer before you can use the software.
+ &Restart
+ &Close
+
+
+
+
+ One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>.
+ You must restart your computer to complete the rollback of the software.
+ &Restart
+ &Close
+
+ The following applications are using files that need to be updated:
+ Close the &applications and attempt to restart them.
+ &Do not close applications. A reboot will be required.
+ &OK
+ &Cancel
+ No action was taken as a system reboot is required.
+
diff --git a/dist/win/bundle/customBootstrapperTheme.xml b/dist/win/bundle/customBootstrapperTheme.xml
new file mode 100644
index 000000000..792f51a35
--- /dev/null
+++ b/dist/win/bundle/customBootstrapperTheme.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+ #(loc.Caption)
+ Segoe UI
+ Segoe UI
+ Segoe UI
+ Segoe UI
+ Segoe UI
+
+
+ #(loc.Title)
+
+
+
+ #(loc.Title)
+ #(loc.HelpHeader)
+ #(loc.HelpText)
+ #(loc.HelpCloseButton)
+
+
+ #(loc.Title)
+
+ #(loc.InstallHeader)
+ #(loc.InstallMessage)
+
+ #(loc.InstallAcceptCheckbox)
+ #(loc.InstallVersion)
+ #(loc.InstallInstallButton)
+ #(loc.InstallCloseButton)
+
+
+
+ #(loc.Title)
+ #(loc.FilesInUseHeader)
+ #(loc.FilesInUseLabel)
+ A
+
+ #(loc.FilesInUseCloseRadioButton)
+ #(loc.FilesInUseDontCloseRadioButton)
+
+ #(loc.FilesInUseOkButton)
+ #(loc.FilesInUseCancelButton)
+
+
+ #(loc.Title)
+
+ #(loc.ProgressHeader)
+ #(loc.ProgressLabel)
+ #(loc.OverallProgressPackageText)
+
+ #(loc.ProgressCancelButton)
+
+
+
+ #(loc.Title)
+ #(loc.ModifyHeader)
+ #(loc.ModifyRepairButton)
+ #(loc.ModifyUninstallButton)
+ #(loc.ModifyCloseButton)
+
+
+ #(loc.Title)
+
+ #(loc.SuccessHeader)
+ #(loc.SuccessInstallHeader)
+ #(loc.SuccessRepairHeader)
+ #(loc.SuccessUninstallHeader)
+ #(loc.SuccessLaunchButton)
+ #(loc.SuccessRestartText)
+ #(loc.SuccessRestartButton)
+ #(loc.SuccessCloseButton)
+
+
+ #(loc.Title)
+
+ #(loc.FailureHeader)
+ #(loc.FailureInstallHeader)
+ #(loc.FailureUninstallHeader)
+ #(loc.FailureRepairHeader)
+ #(loc.FailureHyperlinkLogText)
+
+ #(loc.FailureRestartText)
+ #(loc.FailureRestartButton)
+ #(loc.FailureCloseButton)
+
+
diff --git a/dist/win/bundle/resources/Cryptomator.ico b/dist/win/bundle/resources/Cryptomator.ico
new file mode 100644
index 000000000..7d1d8be88
Binary files /dev/null and b/dist/win/bundle/resources/Cryptomator.ico differ
diff --git a/dist/win/bundle/resources/licenseTemplate.ftl b/dist/win/bundle/resources/licenseTemplate.ftl
new file mode 100644
index 000000000..bd137a1a1
--- /dev/null
+++ b/dist/win/bundle/resources/licenseTemplate.ftl
@@ -0,0 +1,41 @@
+<#function artifactFormat p>
+ <#if p.name?index_of('Unnamed') > -1>
+ <#return p.artifactId + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) ">
+ <#else>
+ <#return p.name + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) ">
+ #if>
+#function>
+{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}}
+{\colortbl ;\red0\green0\blue255;}
+\viewkind4\uc1
+\pard\tx566\tx1133\tx1700\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 \endash 2022 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
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par
+\par
+You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par
+\par
+
+\b Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:\b0\par
+<#list licenseMap as e>
+<#assign license = e.getKey()/>
+<#assign projects = e.getValue()/>
+<#if projects?size > 0>
+\tab ${license}:\par
+<#list projects as project>
+\tab\tab- ${artifactFormat(project)}\par
+#list>
+#if>
+#list>
+\par
+\b Cryptomator uses other third-party assets under the following licenses:\b0\par
+\tab SIL OFL 1.1 License:\par
+\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par
+\par
+\b Cryptomator dynamically links to third-party libraries under the following license:\b0\par
+\tab Uncategorized License:\par
+\tab\tab - WinFsp - Windows File System Proxy, Copyright (C) Bill Zissimopoulos ({{\field{\*\fldinst{HYPERLINK https://github.com/billziss-gh/winfsp }}{\fldrslt{https://github.com/billziss-gh/winfsp\ul0\cf0}}}}\f0\fs16 )\b\par
+}
\ No newline at end of file
diff --git a/dist/win/bundle/resources/logo.png b/dist/win/bundle/resources/logo.png
new file mode 100644
index 000000000..fdfdd6235
Binary files /dev/null and b/dist/win/bundle/resources/logo.png differ
diff --git a/dist/win/bundle/resources/logoSide.png b/dist/win/bundle/resources/logoSide.png
new file mode 100644
index 000000000..e1629e8d6
Binary files /dev/null and b/dist/win/bundle/resources/logoSide.png differ
diff --git a/dist/win/resources/customWizard.wxi b/dist/win/resources/customWizard.wxi
index 432681246..ce82c2933 100644
--- a/dist/win/resources/customWizard.wxi
+++ b/dist/win/resources/customWizard.wxi
@@ -13,7 +13,6 @@
-
@@ -24,8 +23,9 @@
1
"1"]]>
-
+
1
+ 1
NOT Installed
Installed AND PATCH
@@ -75,11 +75,34 @@
+
+
+
+ 1
+
+
+
+
+
+
+
+
+ Reason:
+ FOUNDRUNNINGAPP
+
+
+ Cryptomator was still running during installation.
+ FOUNDRUNNINGAPP
+
+
+
+
+
diff --git a/dist/win/resources/license.rtf b/dist/win/resources/license.rtf
deleted file mode 100644
index 28956ed42..000000000
--- a/dist/win/resources/license.rtf
+++ /dev/null
@@ -1,84 +0,0 @@
-{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}}
-{\colortbl ;\red0\green0\blue255;}
-{\*\generator Riched20 10.0.17134}\viewkind4\uc1
-\pard\tx566\tx1133\tx1700\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 \endash 2022 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
-This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par
-\par
-You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par
-\par
-\b Cryptomator uses 40 third-party dependencies under the following licenses:\b0\par
-\tab Apache License v2.0:\par
-\tab\tab - jffi (com.github.jnr:jffi:1.2.23 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jffi }}{\fldrslt{http://github.com/jnr/jffi\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - jnr-constants (com.github.jnr:jnr-constants:0.9.15 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-constants }}{\fldrslt{http://github.com/jnr/jnr-constants\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - jnr-ffi (com.github.jnr:jnr-ffi:2.1.12 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-ffi }}{\fldrslt{http://github.com/jnr/jnr-ffi\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Gson (com.google.code.gson:gson:2.8.7 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/gson/gson }}{\fldrslt{https://github.com/google/gson/gson\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Dagger (com.google.dagger:dagger:2.38.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/dagger }}{\fldrslt{https://github.com/google/dagger\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/guava/failureaccess }}{\fldrslt{https://github.com/google/guava/failureaccess\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Guava: Google Core Libraries for Java (com.google.guava:guava:30.1.1-jre - {{\field{\*\fldinst{HYPERLINK https://github.com/google/guava/guava }}{\fldrslt{https://github.com/google/guava/guava\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Apache Commons CLI (commons-cli:commons-cli:1.4 - {{\field{\*\fldinst{HYPERLINK http://commons.apache.org/proper/commons-cli/ }}{\fldrslt{http://commons.apache.org/proper/commons-cli/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - javax.inject (javax.inject:javax.inject:1 - {{\field{\*\fldinst{HYPERLINK http://code.google.com/p/atinject/ }}{\fldrslt{http://code.google.com/p/atinject/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Java Native Access (net.java.dev.jna:jna:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - {{\field{\*\fldinst{HYPERLINK https://commons.apache.org/proper/commons-lang/ }}{\fldrslt{https://commons.apache.org/proper/commons-lang/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.14 - {{\field{\*\fldinst{HYPERLINK http://hc.apache.org/httpcomponents-core-ga }}{\fldrslt{http://hc.apache.org/httpcomponents-core-ga\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.5 - {{\field{\*\fldinst{HYPERLINK http://jackrabbit.apache.org/jackrabbit-webdav/ }}{\fldrslt{http://jackrabbit.apache.org/jackrabbit-webdav/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-http }}{\fldrslt{https://eclipse.org/jetty/jetty-http\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-io }}{\fldrslt{https://eclipse.org/jetty/jetty-io\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-security }}{\fldrslt{https://eclipse.org/jetty/jetty-security\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-server }}{\fldrslt{https://eclipse.org/jetty/jetty-server\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-util }}{\fldrslt{https://eclipse.org/jetty/jetty-util\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet-api }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet-api\ul0\cf0}}}}\f0\fs16 )\par
-\tab BSD:\par
-\tab\tab - asm (org.ow2.asm:asm:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - asm-analysis (org.ow2.asm:asm-analysis:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - asm-commons (org.ow2.asm:asm-commons:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - asm-tree (org.ow2.asm:asm-tree:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - asm-util (org.ow2.asm:asm-util:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
-\tab Eclipse Public License - Version 1.0:\par
-\tab\tab - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet-api }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet-api\ul0\cf0}}}}\f0\fs16 )\par
-\tab Eclipse Public License - Version 2.0:\par
-\tab\tab - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-http }}{\fldrslt{https://eclipse.org/jetty/jetty-http\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-io }}{\fldrslt{https://eclipse.org/jetty/jetty-io\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-security }}{\fldrslt{https://eclipse.org/jetty/jetty-security\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-server }}{\fldrslt{https://eclipse.org/jetty/jetty-server\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-util }}{\fldrslt{https://eclipse.org/jetty/jetty-util\ul0\cf0}}}}\f0\fs16 )\par
-\tab Eclipse Public License - v 1.0:\par
-\tab\tab - Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-classic }}{\fldrslt{http://logback.qos.ch/logback-classic\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Logback Core Module (ch.qos.logback:logback-core:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-core }}{\fldrslt{http://logback.qos.ch/logback-core\ul0\cf0}}}}\f0\fs16 )\par
-\tab Eclipse Public License - v 2.0:\par
-\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par
-\tab GNU Lesser General Public License:\par
-\tab\tab - Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-classic }}{\fldrslt{http://logback.qos.ch/logback-classic\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Logback Core Module (ch.qos.logback:logback-core:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-core }}{\fldrslt{http://logback.qos.ch/logback-core\ul0\cf0}}}}\f0\fs16 )\par
-\tab GPLv2:\par
-\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par
-\tab GPLv2+CE:\par
-\tab\tab - javafx-base (org.openjfx:javafx-base:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-base/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-base/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - javafx-controls (org.openjfx:javafx-controls:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-controls/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-controls/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - javafx-fxml (org.openjfx:javafx-fxml:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-fxml/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-fxml/\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - javafx-graphics (org.openjfx:javafx-graphics:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-graphics/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-graphics/\ul0\cf0}}}}\f0\fs16 )\par
-\tab LGPL 2.1:\par
-\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Java Native Access (net.java.dev.jna:jna:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par
-\tab MIT License:\par
-\tab\tab - java jwt (com.auth0:java-jwt:3.18.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/auth0/java-jwt }}{\fldrslt{https://github.com/auth0/java-jwt\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-x86asm }}{\fldrslt{http://github.com/jnr/jnr-x86asm\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - jnr-fuse (com.github.serceman:jnr-fuse:0.5.5 - {{\field{\*\fldinst{HYPERLINK https://github.com/SerCeMan/jnr-fuse }}{\fldrslt{https://github.com/SerCeMan/jnr-fuse\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - zxcvbn4j (com.nulab-inc:zxcvbn:1.5.2 - {{\field{\*\fldinst{HYPERLINK https://github.com/nulab/zxcvbn4j }}{\fldrslt{https://github.com/nulab/zxcvbn4j\ul0\cf0}}}}\f0\fs16 )\par
-\tab\tab - SLF4J API Module (org.slf4j:slf4j-api:1.7.31 - {{\field{\*\fldinst{HYPERLINK http://www.slf4j.org }}{\fldrslt{http://www.slf4j.org\ul0\cf0}}}}\f0\fs16 )\par
-\tab The BSD 2-Clause License:\par
-\tab\tab - EasyBind (com.tobiasdiez:easybind:2.2 - {{\field{\*\fldinst{HYPERLINK https://github.com/tobiasdiez/EasyBind }}{\fldrslt{https://github.com/tobiasdiez/EasyBind\ul0\cf0}}}}\f0\fs16 )\par
-\par
-\b Cryptomator uses other third-party assets under the following licenses:\b0\par
-\tab SIL OFL 1.1 License:\par
-\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par
-}
diff --git a/dist/win/resources/licenseTemplate.ftl b/dist/win/resources/licenseTemplate.ftl
new file mode 100644
index 000000000..d442e6538
--- /dev/null
+++ b/dist/win/resources/licenseTemplate.ftl
@@ -0,0 +1,37 @@
+<#function artifactFormat p>
+ <#if p.name?index_of('Unnamed') > -1>
+ <#return p.artifactId + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) ">
+ <#else>
+ <#return p.name + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) ">
+ #if>
+#function>
+{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}}
+{\colortbl ;\red0\green0\blue255;}
+\viewkind4\uc1
+\pard\tx566\tx1133\tx1700\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 \endash 2022 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
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par
+\par
+You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par
+\par
+
+\b Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:\b0\par
+<#list licenseMap as e>
+<#assign license = e.getKey()/>
+<#assign projects = e.getValue()/>
+<#if projects?size > 0>
+\tab ${license}:\par
+<#list projects as project>
+\tab\tab- ${artifactFormat(project)}\par
+#list>
+#if>
+#list>
+\par
+\b Cryptomator uses other third-party assets under the following licenses:\b0\par
+\tab SIL OFL 1.1 License:\par
+\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par
+}
\ No newline at end of file
diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs
index 4954e1ea8..09b555caa 100644
--- a/dist/win/resources/main.wxs
+++ b/dist/win/resources/main.wxs
@@ -127,6 +127,20 @@
+
+
+
+
+
+
@@ -155,7 +169,12 @@
JP_DOWNGRADABLE_FOUND
-
+
+
+
+ FOUNDRUNNINGAPP
+
+
NOT Installed OR REINSTALL
diff --git a/pom.xml b/pom.xml
index aed926bc4..fa1470c96 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,32 +28,37 @@
2.1.0-beta3
- 2.3.1
+ 2.4.0
1.1.0-beta1
1.0.0
1.0.0
1.0.1
1.3.3
1.3.3
- 1.2.6
+ 1.2.7
- 17.0.2
+ 18
3.12.0
- 3.18.2
+ 3.19.1
2.2
- 31.0-jre
- 2.40.3
- 2.8.9
- 1.5.2
- 1.7.32
- 1.2.9
+ 31.1-jre
+ 2.41
+ 2.9.0
+ 1.6.0
+ 1.7.36
+ 1.2.11
10.0.6
5.8.1
- 3.12.4
+ 4.4.0
2.2
+
+
+ 23.0.0
+ 7.0.2
+ 0.8.7
@@ -248,6 +253,13 @@
1.2
test
+
+
+ org.jetbrains
+ annotations
+ ${jetbrains.annotations.version}
+ provided
+
@@ -256,7 +268,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.8.1
+ 3.10.1
org.apache.maven.plugins
@@ -266,7 +278,7 @@
org.apache.maven.plugins
maven-dependency-plugin
- 3.2.0
+ 3.3.0
org.apache.maven.plugins
@@ -281,17 +293,17 @@
org.apache.maven.plugins
maven-jar-plugin
- 3.2.0
+ 3.2.2
org.jacoco
jacoco-maven-plugin
- 0.8.7
+ ${jacoco.version}
org.owasp
dependency-check-maven
- 6.3.1
+ ${dependency-check.version}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index fe1da0f3e..ae2d4c320 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -4,7 +4,10 @@ import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
module org.cryptomator.desktop {
+ requires static org.jetbrains.annotations;
+
requires org.cryptomator.cryptolib;
+
requires org.cryptomator.cryptofs;
requires org.cryptomator.frontend.dokany;
requires org.cryptomator.frontend.fuse;
@@ -47,6 +50,8 @@ module org.cryptomator.desktop {
opens org.cryptomator.common.settings to com.google.gson;
opens org.cryptomator.ui.keyloading.hub to com.google.gson, javafx.fxml;
+ opens org.cryptomator.launcher to javafx.graphics;
+
opens org.cryptomator.common to javafx.fxml;
opens org.cryptomator.common.vaults to javafx.fxml;
opens org.cryptomator.ui.addvaultwizard to javafx.fxml;
diff --git a/src/main/java/org/cryptomator/common/Environment.java b/src/main/java/org/cryptomator/common/Environment.java
index 4d1ae5597..d1c18aa1e 100644
--- a/src/main/java/org/cryptomator/common/Environment.java
+++ b/src/main/java/org/cryptomator/common/Environment.java
@@ -94,11 +94,6 @@ public class Environment {
return Boolean.getBoolean("cryptomator.showTrayIcon");
}
- @Deprecated // TODO: remove as soon as custom mount path works properly on Win+Fuse
- public boolean useExperimentalFuse() {
- return Boolean.getBoolean("fuse.experimental");
- }
-
private int getInt(String propertyName, int defaultValue) {
String value = System.getProperty(propertyName);
try {
diff --git a/src/main/java/org/cryptomator/common/Passphrase.java b/src/main/java/org/cryptomator/common/Passphrase.java
new file mode 100644
index 000000000..cf64c7a10
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/Passphrase.java
@@ -0,0 +1,108 @@
+package org.cryptomator.common;
+
+import javax.security.auth.Destroyable;
+import java.util.Arrays;
+
+/**
+ * A destroyable CharSequence.
+ */
+public class Passphrase implements Destroyable, CharSequence {
+
+ private final char[] data;
+ private final int offset;
+ private final int length;
+ private boolean destroyed;
+
+ /**
+ * Wraps (doesn't copy) the given data.
+ *
+ * @param data The wrapped data. Any changes to this will be reflected in this passphrase
+ */
+ public Passphrase(char[] data) {
+ this(data, 0, data.length);
+ }
+
+ /**
+ * Wraps (doesn't copy) a subarray of the given data.
+ *
+ * @param data The wrapped data. Any changes to this will be reflected in this passphrase
+ * @param offset The subarray offset, i.e. the first character of this passphrase
+ * @param length The subarray length, i.e. the length of this passphrase
+ */
+ public Passphrase(char[] data, int offset, int length) {
+ if (offset < 0 || length < 0 || offset + length > data.length) {
+ throw new IndexOutOfBoundsException("[%1$d %1$d + %2$d[ not within [0, %3$d[".formatted(offset, length, data.length));
+ }
+ this.data = data;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ public static Passphrase copyOf(CharSequence cs) {
+ char[] result = new char[cs.length()];
+ for (int i = 0; i < cs.length(); i++) {
+ result[i] = cs.charAt(i);
+ }
+ return new Passphrase(result);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Passphrase that = (Passphrase) o;
+ // time-constant comparison
+ int diff = 0;
+ for (int i = 0; i < length; i++) {
+ diff |= charAt(i) ^ that.charAt(i);
+ }
+ return diff == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ // basically Arrays.hashCode, but only for a certain subarray
+ int result = 1;
+ for (int i = 0; i < length; i++) {
+ result = 31 * result + charAt(i);
+ }
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return new String(data, offset, length);
+ }
+
+ @Override
+ public int length() {
+ return length;
+ }
+
+ @Override
+ public char charAt(int index) {
+ if (index < 0 || index >= length) {
+ throw new IndexOutOfBoundsException("%d not within [0, %d[".formatted(index, length));
+ }
+ return data[offset + index];
+ }
+
+ @Override
+ public Passphrase subSequence(int start, int end) {
+ if (start < 0 || end < 0 || end > length || start > end) {
+ throw new IndexOutOfBoundsException("[%d, %d[ not within [0, %d[".formatted(start, end, length));
+ }
+ return new Passphrase(Arrays.copyOfRange(data, offset + start, offset + end));
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return destroyed;
+ }
+
+ @Override
+ public void destroy() {
+ Arrays.fill(data, offset, offset + length, '\0');
+ destroyed = true;
+ }
+}
diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java
index c55ede640..60d7a3dc9 100644
--- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java
+++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java
@@ -4,6 +4,7 @@ import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
+import org.cryptomator.common.vaults.MountPointRequirement;
import org.cryptomator.common.vaults.Volume;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,32 +12,33 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;
class CustomMountPointChooser implements MountPointChooser {
+ private static final String HIDEAWAY_PREFIX = ".~$";
+ private static final String HIDEAWAY_SUFFIX = ".tmp";
+ private static final String WIN_HIDDEN = "dos:hidden";
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
private final VaultSettings vaultSettings;
- private final Environment environment;
@Inject
- public CustomMountPointChooser(VaultSettings vaultSettings, Environment environment) {
+ public CustomMountPointChooser(VaultSettings vaultSettings) {
this.vaultSettings = vaultSettings;
- this.environment = environment;
}
@Override
public boolean isApplicable(Volume caller) {
- //Disable if useExperimentalFuse is required (Win + Fuse), but set to false
- return caller.getImplementationType() != VolumeImpl.FUSE || !SystemUtils.IS_OS_WINDOWS || environment.useExperimentalFuse();
+ return caller.getImplementationType() != VolumeImpl.WEBDAV;
}
@Override
@@ -47,49 +49,102 @@ class CustomMountPointChooser implements MountPointChooser {
@Override
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
- switch (caller.getMountPointRequirement()) {
- case PARENT_NO_MOUNT_POINT -> prepareParentNoMountPoint(mountPoint);
- case EMPTY_MOUNT_POINT -> prepareEmptyMountPoint(mountPoint);
- case NONE -> {
- //Requirement "NONE" doesn't make any sense here.
- //No need to prepare/verify a Mountpoint without requiring one...
+ return switch (caller.getMountPointRequirement()) {
+ case PARENT_NO_MOUNT_POINT -> {
+ prepareParentNoMountPoint(mountPoint);
+ LOG.debug("Successfully checked custom mount point: {}", mountPoint);
+ yield true;
+ }
+ case EMPTY_MOUNT_POINT -> {
+ prepareEmptyMountPoint(mountPoint);
+ LOG.debug("Successfully checked custom mount point: {}", mountPoint);
+ yield false;
+ }
+ case NONE, UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT -> {
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
}
- default -> {
- //Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT"
- throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
- }
- }
- LOG.debug("Successfully checked custom mount point: {}", mountPoint);
- return false;
+ };
}
- private void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
- //This the case on Windows when using FUSE
- //See https://github.com/billziss-gh/winfsp/issues/320
- Path parent = mountPoint.getParent();
- if (!Files.isDirectory(parent)) {
- throw new InvalidMountPointException(new NotDirectoryException(parent.toString()));
- }
- //We must use #notExists() here because notExists =/= !exists (see docs)
- if (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) {
- //File exists OR can't be determined
- throw new InvalidMountPointException(new FileAlreadyExistsException(mountPoint.toString()));
+ //This is case on Windows when using FUSE
+ //See https://github.com/billziss-gh/winfsp/issues/320
+ void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
+ Path hideaway = getHideaway(mountPoint);
+ var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS);
+ var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
+
+ //TODO: possible improvement by just deleting an _empty_ hideaway
+ if (mpExists && hideExists) { //both resources exist (whatever type)
+ throw new InvalidMountPointException(new FileAlreadyExistsException(hideaway.toString()));
+ } else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
+ throw new InvalidMountPointException(new NoSuchFileException(mountPoint.toString()));
+ } else if (!mpExists) { //only hideaway exists
+ checkIsDirectory(hideaway);
+ LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
+ try {
+ if (SystemUtils.IS_OS_WINDOWS) {
+ Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS);
+ }
+ } catch (IOException e) {
+ throw new InvalidMountPointException(e);
+ }
+ } else { //only mountpoint exists
+ try {
+ checkIsDirectory(mountPoint);
+ checkIsEmpty(mountPoint);
+
+ Files.move(mountPoint, hideaway);
+ if (SystemUtils.IS_OS_WINDOWS) {
+ Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS);
+ }
+ } catch (IOException e) {
+ throw new InvalidMountPointException(e);
+ }
}
}
private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException {
//This is the case for Windows when using Dokany and for Linux and Mac
- if (!Files.isDirectory(mountPoint)) {
- throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString()));
- }
- try (DirectoryStream ds = Files.newDirectoryStream(mountPoint)) {
- if (ds.iterator().hasNext()) {
- throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString()));
- }
+ checkIsDirectory(mountPoint);
+ try {
+ checkIsEmpty(mountPoint);
} catch (IOException exception) {
throw new InvalidMountPointException("IOException while checking folder content", exception);
}
}
+ @Override
+ public void cleanup(Volume caller, Path mountPoint) {
+ if (caller.getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT) {
+ Path hideaway = getHideaway(mountPoint);
+ try {
+ Files.move(hideaway, mountPoint);
+ if (SystemUtils.IS_OS_WINDOWS) {
+ Files.setAttribute(mountPoint, WIN_HIDDEN, false);
+ }
+ } catch (IOException e) {
+ LOG.error("Unable to clean up mountpoint {} for Winfsp mounting.", mountPoint, e);
+ }
+ }
+ }
+
+ private void checkIsDirectory(Path toCheck) throws InvalidMountPointException {
+ if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
+ throw new InvalidMountPointException(new NotDirectoryException(toCheck.toString()));
+ }
+ }
+
+ private void checkIsEmpty(Path toCheck) throws InvalidMountPointException, IOException {
+ try (var dirStream = Files.list(toCheck)) {
+ if (dirStream.findFirst().isPresent()) {
+ throw new InvalidMountPointException(new DirectoryNotEmptyException(toCheck.toString()));
+ }
+ }
+ }
+
+ //visible for testing
+ Path getHideaway(Path mountPoint) {
+ return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
+ }
+
}
diff --git a/src/main/java/org/cryptomator/common/settings/Settings.java b/src/main/java/org/cryptomator/common/settings/Settings.java
index 82f2fb794..8cc23bbfc 100644
--- a/src/main/java/org/cryptomator/common/settings/Settings.java
+++ b/src/main/java/org/cryptomator/common/settings/Settings.java
@@ -44,6 +44,7 @@ public class Settings {
public static final String DEFAULT_LICENSE_KEY = "";
public static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
public static final String DEFAULT_DISPLAY_CONFIGURATION = "";
+ public static final String DEFAULT_LANGUAGE = null;
private final ObservableList directories = FXCollections.observableArrayList(VaultSettings::observables);
@@ -66,6 +67,7 @@ public class Settings {
private final IntegerProperty windowWidth = new SimpleIntegerProperty();
private final IntegerProperty windowHeight = new SimpleIntegerProperty();
private final ObjectProperty displayConfiguration = new SimpleObjectProperty<>(DEFAULT_DISPLAY_CONFIGURATION);
+ private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE);
private Consumer saveCmd;
@@ -96,6 +98,7 @@ public class Settings {
windowWidth.addListener(this::somethingChanged);
windowHeight.addListener(this::somethingChanged);
displayConfiguration.addListener(this::somethingChanged);
+ language.addListener(this::somethingChanged);
}
void setSaveCmd(Consumer saveCmd) {
@@ -191,4 +194,8 @@ public class Settings {
public ObjectProperty displayConfigurationProperty() {
return displayConfiguration;
}
+
+ public StringProperty languageProperty() {
+ return language;
+ }
}
diff --git a/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java b/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
index 6d8d880e6..d10066f8f 100644
--- a/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
+++ b/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
@@ -57,6 +57,7 @@ public class SettingsJsonAdapter extends TypeAdapter {
out.name("windowWidth").value((value.windowWidthProperty().get()));
out.name("windowHeight").value((value.windowHeightProperty().get()));
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
+ out.name("language").value((value.languageProperty().get()));
out.endObject();
}
@@ -97,6 +98,7 @@ public class SettingsJsonAdapter extends TypeAdapter {
case "windowWidth" -> settings.windowWidthProperty().set(in.nextInt());
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
+ case "language" -> settings.languageProperty().set(in.nextString());
default -> {
LOG.warn("Unsupported vault setting found in JSON: " + name);
diff --git a/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java b/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java
index 68b61688b..4f3a8ff15 100644
--- a/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java
+++ b/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java
@@ -9,6 +9,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
-public @interface DefaultMountFlags {
+@interface DefaultMountFlags {
}
diff --git a/src/main/java/org/cryptomator/launcher/AppLaunchEvent.java b/src/main/java/org/cryptomator/launcher/AppLaunchEvent.java
new file mode 100644
index 000000000..7fde984e8
--- /dev/null
+++ b/src/main/java/org/cryptomator/launcher/AppLaunchEvent.java
@@ -0,0 +1,13 @@
+package org.cryptomator.launcher;
+
+import java.nio.file.Path;
+import java.util.Collection;
+
+public record AppLaunchEvent(AppLaunchEvent.EventType type, Collection pathsToOpen) {
+
+ public enum EventType {
+ REVEAL_APP,
+ OPEN_FILE
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 5502d804a..90f391ec6 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -13,17 +13,15 @@ import org.cryptomator.common.ShutdownHook;
import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.logging.LoggerConfiguration;
-import org.cryptomator.ui.launcher.UiLauncher;
+import org.cryptomator.ui.fxapp.FxApplicationComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
-import java.io.IOException;
+import javafx.application.Application;
+import javafx.stage.Stage;
import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@Singleton
@@ -36,21 +34,19 @@ public class Cryptomator {
private final LoggerConfiguration logConfig;
private final DebugMode debugMode;
+ private final SupportedLanguages supportedLanguages;
private final Environment env;
private final Lazy ipcMessageHandler;
- private final CountDownLatch shutdownLatch;
private final ShutdownHook shutdownHook;
- private final Lazy uiLauncher;
@Inject
- Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy ipcMessageHandler, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, Lazy uiLauncher) {
+ Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook) {
this.logConfig = logConfig;
this.debugMode = debugMode;
+ this.supportedLanguages = supportedLanguages;
this.env = env;
this.ipcMessageHandler = ipcMessageHandler;
- this.shutdownLatch = shutdownLatch;
this.shutdownHook = shutdownHook;
- this.uiLauncher = uiLauncher;
}
public static void main(String[] args) {
@@ -69,6 +65,7 @@ public class Cryptomator {
logConfig.init();
LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
debugMode.initialize();
+ supportedLanguages.applyPreferred();
/*
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
@@ -79,7 +76,7 @@ public class Cryptomator {
communicator.sendHandleLaunchargs(List.of(args));
communicator.sendRevealRunningApp();
LOG.info("Found running application instance. Shutting down...");
- return 2;
+ return 0;
} else {
shutdownHook.runOnShutdown(communicator::closeUnchecked);
var executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IPC-%d").build());
@@ -96,21 +93,38 @@ public class Cryptomator {
}
/**
- * Launches the JavaFX application and waits until shutdown is requested.
+ * Launches the JavaFX application, blocking the main thread until shuts down.
*
* @return Nonzero exit code in case of an error.
- * @implNote This method blocks until {@link #shutdownLatch} reached zero.
*/
private int runGuiApplication() {
try {
- uiLauncher.get().launch();
- shutdownLatch.await();
+ Application.launch(MainApp.class);
LOG.info("UI shut down");
return 0;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ } catch (Exception e) {
+ LOG.error("Terminating due to error", e);
return 1;
}
}
+ public static class MainApp extends Application {
+
+ @Override
+ public void start(Stage primaryStage) {
+ LOG.info("JavaFX application started.");
+ FxApplicationComponent component = CRYPTOMATOR_COMPONENT.fxAppComponentBuilder() //
+ .fxApplication(this) //
+ .primaryStage(primaryStage) //
+ .build();
+ component.application().start();
+ }
+
+ @Override
+ public void stop() {
+ LOG.info("JavaFX application stopped.");
+ }
+
+ }
+
}
diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
index 70bf9e772..b43c0eca0 100644
--- a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
+++ b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
@@ -3,14 +3,16 @@ package org.cryptomator.launcher;
import dagger.Component;
import org.cryptomator.common.CommonsModule;
import org.cryptomator.logging.LoggerModule;
-import org.cryptomator.ui.launcher.UiLauncherModule;
+import org.cryptomator.ui.fxapp.FxApplicationComponent;
import javax.inject.Singleton;
@Singleton
-@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class, UiLauncherModule.class})
+@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
public interface CryptomatorComponent {
Cryptomator application();
+ FxApplicationComponent.Builder fxAppComponentBuilder();
+
}
diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
index 906971492..e6aab0309 100644
--- a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
+++ b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
@@ -2,20 +2,55 @@ package org.cryptomator.launcher;
import dagger.Module;
import dagger.Provides;
+import org.cryptomator.common.PluginClassLoader;
+import org.cryptomator.integrations.autostart.AutoStartProvider;
+import org.cryptomator.integrations.tray.TrayIntegrationProvider;
+import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
+import org.cryptomator.ui.fxapp.FxApplicationComponent;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
+import java.util.ResourceBundle;
+import java.util.ServiceLoader;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
-@Module
+@Module(subcomponents = {FxApplicationComponent.class})
class CryptomatorModule {
@Provides
@Singleton
- @Named("shutdownLatch")
- static CountDownLatch provideShutdownLatch() {
- return new CountDownLatch(1);
+ static ResourceBundle provideLocalization() {
+ return ResourceBundle.getBundle("i18n.strings");
}
+ @Provides
+ @Singleton
+ @Named("launchEventQueue")
+ static BlockingQueue provideFileOpenRequests() {
+ return new ArrayBlockingQueue<>(10);
+ }
+
+ // TODO: still needed after integrations-api 1.1.0?
+
+ @Provides
+ @Singleton
+ static Optional provideAppearanceProvider(PluginClassLoader classLoader) {
+ return ServiceLoader.load(UiAppearanceProvider.class, classLoader).findFirst();
+ }
+
+ @Provides
+ @Singleton
+ static Optional provideAutostartProvider(PluginClassLoader classLoader) {
+ return ServiceLoader.load(AutoStartProvider.class, classLoader).findFirst();
+ }
+
+ @Provides
+ @Singleton
+ static Optional provideTrayIntegrationProvider(PluginClassLoader classLoader) {
+ return ServiceLoader.load(TrayIntegrationProvider.class, classLoader).findFirst();
+ }
+
+
}
diff --git a/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java b/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java
index b4e37e1f9..eb2418c69 100644
--- a/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java
+++ b/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java
@@ -6,7 +6,6 @@
*******************************************************************************/
package org.cryptomator.launcher;
-import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,12 +19,10 @@ import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
-import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
-import java.util.stream.Collectors;
@Singleton
class FileOpenRequestHandler {
diff --git a/src/main/java/org/cryptomator/launcher/IpcMessageHandler.java b/src/main/java/org/cryptomator/launcher/IpcMessageHandler.java
index 5c28d05a4..05565f97d 100644
--- a/src/main/java/org/cryptomator/launcher/IpcMessageHandler.java
+++ b/src/main/java/org/cryptomator/launcher/IpcMessageHandler.java
@@ -1,7 +1,6 @@
package org.cryptomator.launcher;
import org.cryptomator.ipc.IpcMessageListener;
-import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java
new file mode 100644
index 000000000..d473dcdf8
--- /dev/null
+++ b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java
@@ -0,0 +1,39 @@
+package org.cryptomator.launcher;
+
+import org.cryptomator.common.settings.Settings;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.List;
+import java.util.Locale;
+
+@Singleton
+public class SupportedLanguages {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
+ // these are BCP 47 language codes, not ISO. Note the "-" instead of the "_":
+ public static final List LANGUAGAE_TAGS = List.of("en", "ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", //
+ "hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", //
+ "sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW");
+
+ @Nullable
+ private final String preferredLanguage;
+
+ @Inject
+ public SupportedLanguages(Settings settings) {
+ this.preferredLanguage = settings.languageProperty().get();
+ }
+
+ public void applyPreferred() {
+ if (preferredLanguage == null) {
+ LOG.debug("Using system locale");
+ return;
+ }
+ var preferredLocale = Locale.forLanguageTag(preferredLanguage);
+ LOG.debug("Applying preferred locale {}", preferredLocale.getDisplayName(Locale.ENGLISH));
+ Locale.setDefault(preferredLocale);
+ }
+}
diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java
index 8a5a776ea..c6acbadf6 100644
--- a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java
+++ b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java
@@ -14,7 +14,7 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.cryptomator.ui.common.StageFactory;
-import org.cryptomator.ui.mainwindow.MainWindow;
+import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
import javax.inject.Named;
@@ -43,12 +43,12 @@ public abstract class AddVaultModule {
@Provides
@AddVaultWizardWindow
@AddVaultWizardScoped
- static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) {
+ static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
- stage.initOwner(owner);
+ stage.initOwner(primaryStage);
return stage;
}
diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java
index 99d01577f..e0306b4e7 100644
--- a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java
+++ b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java
@@ -2,25 +2,24 @@ package org.cryptomator.ui.addvaultwizard;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.fxapp.FxApplication;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
-import java.util.Optional;
@AddVaultWizardScoped
public class AddVaultSuccessController implements FxController {
- private final FxApplication fxApplication;
+ private final FxApplicationWindows appWindows;
private final Stage window;
private final ReadOnlyObjectProperty vault;
@Inject
- AddVaultSuccessController(FxApplication fxApplication, @AddVaultWizardWindow Stage window, @AddVaultWizardWindow ObjectProperty vault) {
- this.fxApplication = fxApplication;
+ AddVaultSuccessController(FxApplicationWindows appWindows, @AddVaultWizardWindow Stage window, @AddVaultWizardWindow ObjectProperty vault) {
+ this.appWindows = appWindows;
this.window = window;
this.vault = vault;
}
@@ -28,7 +27,7 @@ public class AddVaultSuccessController implements FxController {
@FXML
public void unlockAndClose() {
close();
- fxApplication.startUnlockWorkflow(vault.get(), Optional.of(window));
+ appWindows.startUnlockWorkflow(vault.get(), window);
}
@FXML
diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java
index 4fceaa929..01a8a6758 100644
--- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java
+++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java
@@ -4,10 +4,10 @@ import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
-import org.cryptomator.ui.common.ErrorComponent;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,9 +20,6 @@ import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
-import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ResourceBundle;
@@ -34,7 +31,7 @@ public class ChooseExistingVaultController implements FxController {
private final Stage window;
private final Lazy welcomeScene;
private final Lazy successScene;
- private final ErrorComponent.Builder errorComponent;
+ private final FxApplicationWindows appWindows;
private final ObjectProperty vaultPath;
private final ObjectProperty vault;
private final VaultListManager vaultListManager;
@@ -43,11 +40,11 @@ public class ChooseExistingVaultController implements FxController {
private Image screenshot;
@Inject
- ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, ErrorComponent.Builder errorComponent, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
+ ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, FxApplicationWindows appWindows, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
this.window = window;
this.welcomeScene = welcomeScene;
this.successScene = successScene;
- this.errorComponent = errorComponent;
+ this.appWindows = appWindows;
this.vaultPath = vaultPath;
this.vault = vault;
this.vaultListManager = vaultListManager;
@@ -82,7 +79,7 @@ public class ChooseExistingVaultController implements FxController {
window.setScene(successScene.get());
} catch (IOException e) {
LOG.error("Failed to open existing vault.", e);
- errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
+ appWindows.showErrorWindow(e, window, window.getScene());
}
}
}
diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
index 35f2be069..1fd463432 100644
--- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
+++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
@@ -35,14 +35,13 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
-import java.util.UUID;
@AddVaultWizardScoped
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 = "cryptomator-%s.tmp";
+ private static final String TEMP_FILE_FORMAT = ".locationTest.cryptomator.tmp";
private final Stage window;
private final Lazy chooseNameScene;
@@ -112,7 +111,7 @@ public class CreateNewVaultLocationController implements FxController {
}
private boolean isActuallyWritable(Path p) {
- Path tmpFile = p.resolve(String.format(TEMP_FILE_FORMAT, UUID.randomUUID()));
+ Path tmpFile = p.resolve(TEMP_FILE_FORMAT);
try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) {
return true;
} catch (IOException e) {
diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
index 578b90969..51a8a1147 100644
--- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
+++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
@@ -10,12 +10,12 @@ import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
-import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.Tasks;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.slf4j.Logger;
@@ -60,7 +60,7 @@ public class CreateNewVaultPasswordController implements FxController {
private final Lazy chooseLocationScene;
private final Lazy recoveryKeyScene;
private final Lazy successScene;
- private final ErrorComponent.Builder errorComponent;
+ private final FxApplicationWindows appWindows;
private final ExecutorService executor;
private final RecoveryKeyFactory recoveryKeyFactory;
private final StringProperty vaultNameProperty;
@@ -83,12 +83,12 @@ public class CreateNewVaultPasswordController implements FxController {
public NewPasswordController newPasswordSceneController;
@Inject
- CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, ErrorComponent.Builder errorComponent, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, ReadmeGenerator readmeGenerator, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) {
+ CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, FxApplicationWindows appWindows, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, ReadmeGenerator readmeGenerator, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) {
this.window = window;
this.chooseLocationScene = chooseLocationScene;
this.recoveryKeyScene = recoveryKeyScene;
this.successScene = successScene;
- this.errorComponent = errorComponent;
+ this.appWindows = appWindows;
this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory;
this.vaultNameProperty = vaultName;
@@ -127,7 +127,7 @@ public class CreateNewVaultPasswordController implements FxController {
Files.createDirectory(pathToVault);
} catch (IOException e) {
LOG.error("Failed to create vault directory.", e);
- errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
+ appWindows.showErrorWindow(e, window, window.getScene());
return;
}
@@ -152,7 +152,7 @@ public class CreateNewVaultPasswordController implements FxController {
window.setScene(recoveryKeyScene.get());
}).onError(IOException.class, e -> {
LOG.error("Failed to initialize vault.", e);
- errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
+ appWindows.showErrorWindow(e, window, window.getScene());
}).andFinally(() -> {
processing.set(false);
}).runOnce(executor);
@@ -168,7 +168,7 @@ public class CreateNewVaultPasswordController implements FxController {
window.setScene(successScene.get());
}).onError(IOException.class, e -> {
LOG.error("Failed to initialize vault.", e);
- errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
+ appWindows.showErrorWindow(e, window, window.getScene());
}).andFinally(() -> {
processing.set(false);
}).runOnce(executor);
diff --git a/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java b/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java
index c715f0466..200a70328 100644
--- a/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java
+++ b/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java
@@ -8,10 +8,10 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
-import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,7 +26,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
-import java.security.SecureRandom;
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -38,9 +37,8 @@ public class ChangePasswordController implements FxController {
private final Stage window;
private final Vault vault;
- private final ErrorComponent.Builder errorComponent;
+ private final FxApplicationWindows appWindows;
private final KeychainManager keychain;
- private final SecureRandom csprng;
private final MasterkeyFileAccess masterkeyFileAccess;
public NiceSecurePasswordField oldPasswordField;
@@ -49,12 +47,11 @@ public class ChangePasswordController implements FxController {
public NewPasswordController newPasswordController;
@Inject
- public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, ErrorComponent.Builder errorComponent, KeychainManager keychain, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) {
+ public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, FxApplicationWindows appWindows, KeychainManager keychain, MasterkeyFileAccess masterkeyFileAccess) {
this.window = window;
this.vault = vault;
- this.errorComponent = errorComponent;
+ this.appWindows = appWindows;
this.keychain = keychain;
- this.csprng = csprng;
this.masterkeyFileAccess = masterkeyFileAccess;
}
@@ -95,7 +92,7 @@ public class ChangePasswordController implements FxController {
oldPasswordField.requestFocus();
} catch (IOException | CryptoException e) {
LOG.error("Password change failed. Unable to perform operation.", e);
- errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
+ appWindows.showErrorWindow(e, window, window.getScene());
}
}
diff --git a/src/main/java/org/cryptomator/ui/common/ErrorComponent.java b/src/main/java/org/cryptomator/ui/common/ErrorComponent.java
index 92276f5bd..8cb430584 100644
--- a/src/main/java/org/cryptomator/ui/common/ErrorComponent.java
+++ b/src/main/java/org/cryptomator/ui/common/ErrorComponent.java
@@ -4,7 +4,6 @@ import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
-import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -16,34 +15,17 @@ public interface ErrorComponent {
@FxmlScene(FxmlFile.ERROR)
Scene scene();
- default void showErrorScene() {
- if (Platform.isFxApplicationThread()) {
- show();
- } else {
- Platform.runLater(this::show);
- }
- }
-
- private void show() {
+ default Stage show() {
Stage stage = window();
stage.setScene(scene());
stage.show();
+ return stage;
}
- @Subcomponent.Builder
- interface Builder {
-
- @BindsInstance
- Builder cause(Throwable cause);
-
- @BindsInstance
- Builder window(Stage window);
-
- @BindsInstance
- Builder returnToScene(@Nullable Scene previousScene);
-
- ErrorComponent build();
+ @Subcomponent.Factory
+ interface Factory {
+ ErrorComponent create(@BindsInstance Throwable cause, @BindsInstance Stage window, @BindsInstance @Nullable Scene previousScene);
}
}
diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java
index 518671de8..b9e6a990e 100644
--- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java
+++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java
@@ -46,7 +46,7 @@ public enum FxmlFile {
this.ressourcePathString = ressourcePathString;
}
- String getRessourcePathString() {
+ public String getRessourcePathString() {
return ressourcePathString;
}
}
diff --git a/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java b/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java
index c10054ef4..dcff93aab 100644
--- a/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java
+++ b/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java
@@ -22,6 +22,10 @@ public class FxmlLoaderFactory {
this.resourceBundle = resourceBundle;
}
+ public static FxmlLoaderFactory forController(T controller, Function sceneFactory, ResourceBundle resourceBundle) {
+ return new FxmlLoaderFactory(Map.of(controller.getClass(), () -> controller), sceneFactory, resourceBundle);
+ }
+
/**
* @return A new FXMLLoader instance
*/
diff --git a/src/main/java/org/cryptomator/ui/common/StageFactory.java b/src/main/java/org/cryptomator/ui/common/StageFactory.java
index 9a0dcb1c5..3a8c20cb3 100644
--- a/src/main/java/org/cryptomator/ui/common/StageFactory.java
+++ b/src/main/java/org/cryptomator/ui/common/StageFactory.java
@@ -1,23 +1,24 @@
package org.cryptomator.ui.common;
+import org.cryptomator.ui.fxapp.FxApplicationScoped;
+
+import javax.inject.Inject;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.function.Consumer;
+@FxApplicationScoped
public class StageFactory {
private final Consumer initializer;
- public StageFactory(Consumer initializer) {
+ @Inject
+ public StageFactory(StageInitializer initializer) {
this.initializer = initializer;
}
public Stage create() {
- return create(StageStyle.DECORATED);
- }
-
- public Stage create(StageStyle stageStyle) {
- Stage stage = new Stage(stageStyle);
+ Stage stage = new Stage(StageStyle.DECORATED);
initializer.accept(stage);
return stage;
}
diff --git a/src/main/java/org/cryptomator/ui/common/StageInitializer.java b/src/main/java/org/cryptomator/ui/common/StageInitializer.java
new file mode 100644
index 000000000..1534deb52
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/common/StageInitializer.java
@@ -0,0 +1,32 @@
+package org.cryptomator.ui.common;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.ui.fxapp.FxApplicationScoped;
+
+import javax.inject.Inject;
+import javafx.scene.image.Image;
+import javafx.stage.Stage;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Performs common setup for all stages
+ */
+@FxApplicationScoped
+public class StageInitializer implements Consumer {
+
+ private final List windowIcons;
+
+ @Inject
+ public StageInitializer() {
+ this.windowIcons = SystemUtils.IS_OS_MAC ? List.of() : List.of( //
+ new Image(StageInitializer.class.getResource("/img/window_icon_32.png").toString()), //
+ new Image(StageInitializer.class.getResource("/img/window_icon_512.png").toString()) //
+ );
+ }
+
+ @Override
+ public void accept(Stage stage) {
+ stage.getIcons().setAll(windowIcons);
+ }
+}
diff --git a/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java b/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java
deleted file mode 100644
index 12c394533..000000000
--- a/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.cryptomator.ui.common;
-
-import javafx.application.Platform;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.ReadOnlyBooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class UserInteractionLock> {
-
- private final Lock lock = new ReentrantLock();
- private final Condition condition = lock.newCondition();
- private final BooleanProperty awaitingInteraction = new SimpleBooleanProperty();
- private final AtomicBoolean interacted = new AtomicBoolean();
- private final AtomicReference state;
-
- public UserInteractionLock(E initialValue) {
- this.state = new AtomicReference<>(initialValue);
- }
-
- public synchronized void reset(E value) {
- state.set(value);
- interacted.set(false);
- }
-
- public void interacted(E result) {
- assert Platform.isFxApplicationThread();
- lock.lock();
- try {
- state.set(result);
- interacted.set(true);
- awaitingInteraction.set(false);
- condition.signal();
- } finally {
- lock.unlock();
- }
- }
-
- public E awaitInteraction() throws InterruptedException {
- assert !Platform.isFxApplicationThread();
- lock.lock();
- try {
- Platform.runLater(() -> awaitingInteraction.set(true));
- while (!interacted.get()) {
- condition.await();
- }
- return state.get();
- } finally {
- lock.unlock();
- }
- }
-
- public ReadOnlyBooleanProperty awaitingInteraction() {
- return awaitingInteraction;
- }
-
-}
diff --git a/src/main/java/org/cryptomator/ui/common/VaultService.java b/src/main/java/org/cryptomator/ui/common/VaultService.java
index b81ddec49..a6486f35f 100644
--- a/src/main/java/org/cryptomator/ui/common/VaultService.java
+++ b/src/main/java/org/cryptomator/ui/common/VaultService.java
@@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.concurrent.Task;
+import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -53,7 +54,9 @@ public class VaultService {
*
* @param vault The vault to lock
* @param forced Whether to attempt a forced lock
+ * @deprecated use {@link org.cryptomator.ui.fxapp.FxApplicationWindows#startLockWorkflow(Vault, Stage)}
*/
+ @Deprecated
public void lock(Vault vault, boolean forced) {
executorService.execute(createLockTask(vault, forced));
}
@@ -90,7 +93,7 @@ public class VaultService {
* @return Meta-Task that waits until all vaults are locked or fails after the first failure of a subtask
*/
public Task> createLockAllTask(Collection vaults, boolean forced) {
- List> lockTasks = vaults.stream().map(v -> new LockVaultTask(v, forced)).collect(Collectors.toUnmodifiableList());
+ List> lockTasks = vaults.stream().>map(v -> new LockVaultTask(v, forced)).toList();
lockTasks.forEach(executorService::execute);
Task> task = new WaitForTasksTask(lockTasks);
String vaultNames = vaults.stream().map(Vault::getDisplayName).collect(Collectors.joining(", "));
diff --git a/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java b/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java
index 553dda73b..4d09707b9 100644
--- a/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java
+++ b/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java
@@ -1,5 +1,7 @@
package org.cryptomator.ui.controls;
+import org.cryptomator.common.Passphrase;
+
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.StringProperty;
@@ -82,11 +84,7 @@ public class NiceSecurePasswordField extends StackPane {
return passwordField.textProperty();
}
- public char[] copyChars() {
- return passwordField.copyChars();
- }
-
- public CharSequence getCharacters() {
+ public Passphrase getCharacters() {
return passwordField.getCharacters();
}
diff --git a/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java b/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java
index 43193830b..27739a2f0 100644
--- a/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java
+++ b/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java
@@ -9,6 +9,7 @@
package org.cryptomator.ui.controls;
import com.google.common.base.Strings;
+import org.cryptomator.common.Passphrase;
import javafx.application.Platform;
import javafx.beans.NamedArg;
@@ -28,7 +29,6 @@ import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
-import java.nio.CharBuffer;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.Arrays;
@@ -212,8 +212,8 @@ public class SecurePasswordField extends TextField {
* @see #wipe()
*/
@Override
- public CharSequence getCharacters() {
- return CharBuffer.wrap(content, 0, length);
+ public Passphrase getCharacters() {
+ return new Passphrase(content, 0, length);
}
/**
diff --git a/src/main/java/org/cryptomator/ui/launcher/AppLaunchEventHandler.java b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java
similarity index 72%
rename from src/main/java/org/cryptomator/ui/launcher/AppLaunchEventHandler.java
rename to src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java
index 52ba838c0..39e40600e 100644
--- a/src/main/java/org/cryptomator/ui/launcher/AppLaunchEventHandler.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java
@@ -1,14 +1,14 @@
-package org.cryptomator.ui.launcher;
+package org.cryptomator.ui.fxapp;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
-import org.cryptomator.ui.fxapp.FxApplication;
+import org.cryptomator.launcher.AppLaunchEvent;
+import org.cryptomator.ui.common.VaultService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
-import javax.inject.Singleton;
import javafx.application.Platform;
import java.io.IOException;
import java.nio.file.Path;
@@ -17,22 +17,25 @@ import java.util.concurrent.ExecutorService;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
-@Singleton
+// TODO: use message bus
+@FxApplicationScoped
class AppLaunchEventHandler {
private static final Logger LOG = LoggerFactory.getLogger(AppLaunchEventHandler.class);
private final BlockingQueue launchEventQueue;
private final ExecutorService executorService;
- private final FxApplicationStarter fxApplicationStarter;
+ private final FxApplicationWindows appWindows;
private final VaultListManager vaultListManager;
+ private final VaultService vaultService;
@Inject
- public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue launchEventQueue, ExecutorService executorService, FxApplicationStarter fxApplicationStarter, VaultListManager vaultListManager) {
+ public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService) {
this.launchEventQueue = launchEventQueue;
this.executorService = executorService;
- this.fxApplicationStarter = fxApplicationStarter;
+ this.appWindows = appWindows;
this.vaultListManager = vaultListManager;
+ this.vaultService = vaultService;
}
public void startHandlingLaunchEvents() {
@@ -52,14 +55,12 @@ class AppLaunchEventHandler {
}
private void handleLaunchEvent(AppLaunchEvent event) {
- switch (event.getType()) {
- case REVEAL_APP -> fxApplicationStarter.get().thenAccept(FxApplication::showMainWindow);
- case OPEN_FILE -> fxApplicationStarter.get().thenRun(() -> {
- Platform.runLater(() -> {
- event.getPathsToOpen().forEach(this::addOrRevealVault);
- });
+ switch (event.type()) {
+ case REVEAL_APP -> appWindows.showMainWindow();
+ case OPEN_FILE -> Platform.runLater(() -> {
+ event.pathsToOpen().forEach(this::addOrRevealVault);
});
- default -> LOG.warn("Unsupported event type: {}", event.getType());
+ default -> LOG.warn("Unsupported event type: {}", event.type());
}
}
@@ -75,7 +76,7 @@ class AppLaunchEventHandler {
}
if (v.isUnlocked()) {
- fxApplicationStarter.get().thenAccept(app -> app.getVaultService().reveal(v));
+ vaultService.reveal(v);
}
LOG.debug("Added vault {}", potentialVaultPath);
} catch (IOException e) {
diff --git a/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java b/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java
new file mode 100644
index 000000000..9d6a73fa0
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java
@@ -0,0 +1,26 @@
+package org.cryptomator.ui.fxapp;
+
+import org.cryptomator.common.vaults.Vault;
+
+import javax.inject.Inject;
+import javafx.collections.ObservableList;
+
+@FxApplicationScoped
+public class AutoUnlocker {
+
+ private final ObservableList vaults;
+ private final FxApplicationWindows appWindows;
+
+ @Inject
+ public AutoUnlocker(ObservableList vaults, FxApplicationWindows appWindows) {
+ this.vaults = vaults;
+ this.appWindows = appWindows;
+ }
+
+ public void unlock() {
+ vaults.stream().filter(Vault::isLocked).filter(v -> v.getVaultSettings().unlockAfterStartup().get()).forEach(v -> {
+ appWindows.startUnlockWorkflow(v, null);
+ });
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/ExitingQuitResponse.java b/src/main/java/org/cryptomator/ui/fxapp/ExitingQuitResponse.java
new file mode 100644
index 000000000..ae2bcd438
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/fxapp/ExitingQuitResponse.java
@@ -0,0 +1,20 @@
+package org.cryptomator.ui.fxapp;
+
+import javafx.application.Platform;
+import java.awt.desktop.QuitResponse;
+
+record ExitingQuitResponse(QuitResponse delegate) implements QuitResponse {
+
+ @Override
+ public void performQuit() {
+ Platform.exit();
+ // TODO wait a moment for javafx to terminate?
+ delegate.performQuit();
+ }
+
+ @Override
+ public void cancelQuit() {
+ delegate.cancelQuit();
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
index 1812d38bd..55f76d321 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
@@ -1,213 +1,70 @@
package org.cryptomator.ui.fxapp;
import dagger.Lazy;
-import javafx.application.Application;
-import javafx.application.Platform;
-import javafx.beans.binding.Bindings;
-import javafx.beans.binding.BooleanBinding;
-import javafx.beans.value.ObservableValue;
-import javafx.collections.ObservableList;
-import javafx.stage.Stage;
-import javafx.stage.Window;
-import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
-import org.cryptomator.common.settings.UiTheme;
-import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.common.vaults.VaultListManager;
-import org.cryptomator.common.vaults.VaultState;
-import org.cryptomator.integrations.tray.TrayIntegrationProvider;
-import org.cryptomator.integrations.uiappearance.Theme;
-import org.cryptomator.integrations.uiappearance.UiAppearanceException;
-import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
-import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
-import org.cryptomator.ui.common.ErrorComponent;
-import org.cryptomator.ui.common.VaultService;
-import org.cryptomator.ui.lock.LockComponent;
-import org.cryptomator.ui.mainwindow.MainWindowComponent;
-import org.cryptomator.ui.preferences.PreferencesComponent;
-import org.cryptomator.ui.preferences.SelectedPreferencesTab;
-import org.cryptomator.ui.quit.QuitComponent;
-import org.cryptomator.ui.unlock.UnlockComponent;
+import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javax.inject.Provider;
-import java.awt.desktop.QuitResponse;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import javafx.application.Platform;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import java.awt.SystemTray;
@FxApplicationScoped
-public class FxApplication extends Application {
+public class FxApplication {
private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class);
private final Settings settings;
- private final Lazy mainWindow;
- private final Lazy preferencesWindow;
- private final Lazy quitWindow;
- private final Provider unlockWorkflowBuilderProvider;
- private final Provider lockWorkflowBuilderProvider;
- private final ErrorComponent.Builder errorWindowBuilder;
- private final Optional trayIntegration;
- private final Optional appearanceProvider;
- private final VaultService vaultService;
- private final LicenseHolder licenseHolder;
- private final ObservableList visibleWindows;
- private final BooleanBinding hasVisibleWindows;
- private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
+ private final AppLaunchEventHandler launchEventHandler;
+ private final Lazy trayMenu;
+ private final FxApplicationWindows appWindows;
+ private final FxApplicationStyle applicationStyle;
+ private final FxApplicationTerminator applicationTerminator;
+ private final AutoUnlocker autoUnlocker;
@Inject
- FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, Provider unlockWorkflowBuilderProvider, Provider lockWorkflowBuilderProvider, Lazy quitWindow, ErrorComponent.Builder errorWindowBuilder, Optional trayIntegration, Optional appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder) {
+ FxApplication(Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
this.settings = settings;
- this.mainWindow = mainWindow;
- this.preferencesWindow = preferencesWindow;
- this.unlockWorkflowBuilderProvider = unlockWorkflowBuilderProvider;
- this.lockWorkflowBuilderProvider = lockWorkflowBuilderProvider;
- this.quitWindow = quitWindow;
- this.errorWindowBuilder = errorWindowBuilder;
- this.trayIntegration = trayIntegration;
- this.appearanceProvider = appearanceProvider;
- this.vaultService = vaultService;
- this.licenseHolder = licenseHolder;
- this.visibleWindows = Stage.getWindows().filtered(Window::isShowing);
- this.hasVisibleWindows = Bindings.isNotEmpty(visibleWindows);
+ this.launchEventHandler = launchEventHandler;
+ this.trayMenu = trayMenu;
+ this.appWindows = appWindows;
+ this.applicationStyle = applicationStyle;
+ this.applicationTerminator = applicationTerminator;
+ this.autoUnlocker = autoUnlocker;
}
public void start() {
LOG.trace("FxApplication.start()");
- Platform.setImplicitExit(false);
+ applicationStyle.initialize();
+ appWindows.initialize();
+ applicationTerminator.initialize();
- hasVisibleWindows.addListener(this::hasVisibleStagesChanged);
-
- settings.theme().addListener(this::appThemeChanged);
- loadSelectedStyleSheet(settings.theme().get());
- }
-
- @Override
- public void start(Stage stage) {
- throw new UnsupportedOperationException("Use start() instead.");
- }
-
- private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
- LOG.debug("has visible stages: {}", newValue);
- if (newValue) {
- trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
+ // init system tray
+ final boolean hasTrayIcon;
+ if (SystemTray.isSupported() && settings.showTrayIcon().get()) {
+ trayMenu.get().initializeTrayIcon();
+ Platform.setImplicitExit(false); // don't quit when closing all windows
+ hasTrayIcon = true;
} else {
- trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray);
+ hasTrayIcon = false;
}
- }
- public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
- Platform.runLater(() -> {
- preferencesWindow.get().showPreferencesWindow(selectedTab);
- LOG.debug("Showing Preferences");
- });
- }
-
- public CompletionStage showMainWindow() {
- CompletableFuture future = new CompletableFuture<>();
- Platform.runLater(() -> {
- var win = mainWindow.get().showMainWindow();
- LOG.debug("Showing MainWindow");
- future.complete(win);
- });
- return future;
- }
-
- public void startUnlockWorkflow(Vault vault, Optional owner) {
- Platform.runLater(() -> {
- if (vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING)) {
- unlockWorkflowBuilderProvider.get().vault(vault).owner(owner).build().startUnlockWorkflow();
- LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
- } else {
- showMainWindow().thenAccept(mainWindow -> errorWindowBuilder.window(mainWindow).cause(new IllegalStateException("Unable to unlock vault in non-locked state.")));
+ // show main window
+ appWindows.showMainWindow().thenAccept(stage -> {
+ if (settings.startHidden().get()) {
+ if (hasTrayIcon) {
+ stage.hide();
+ } else {
+ stage.setIconified(true);
+ }
}
});
- }
- public void startLockWorkflow(Vault vault, Optional owner) {
- Platform.runLater(() -> {
- if (vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING)) {
- lockWorkflowBuilderProvider.get().vault(vault).owner(owner).build().startLockWorkflow();
- LOG.debug("Start lock workflow for {}", vault.getDisplayName());
- } else {
- showMainWindow().thenAccept(mainWindow -> errorWindowBuilder.window(mainWindow).cause(new IllegalStateException("Unable to lock vault in non-unlocked state.")));
- }
- });
- }
-
- public void showQuitWindow(QuitResponse response) {
- Platform.runLater(() -> {
- quitWindow.get().showQuitWindow(response);
- LOG.debug("Showing QuitWindow");
- });
- }
-
- public VaultService getVaultService() {
- return vaultService;
- }
-
- private void appThemeChanged(@SuppressWarnings("unused") ObservableValue extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
- if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
- try {
- appearanceProvider.get().removeListener(systemInterfaceThemeListener);
- } catch (UiAppearanceException e) {
- LOG.error("Failed to disable automatic theme switching.");
- }
- }
- loadSelectedStyleSheet(newValue);
- }
-
- private void loadSelectedStyleSheet(UiTheme desiredTheme) {
- UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
- switch (theme) {
- case LIGHT -> applyLightTheme();
- case DARK -> applyDarkTheme();
- case AUTOMATIC -> {
- appearanceProvider.ifPresent(appearanceProvider -> {
- try {
- appearanceProvider.addListener(systemInterfaceThemeListener);
- } catch (UiAppearanceException e) {
- LOG.error("Failed to enable automatic theme switching.");
- }
- });
- applySystemTheme();
- }
- }
- }
-
- private void systemInterfaceThemeChanged(Theme theme) {
- switch (theme) {
- case LIGHT -> applyLightTheme();
- case DARK -> applyDarkTheme();
- }
- }
-
- private void applySystemTheme() {
- if (appearanceProvider.isPresent()) {
- systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme());
- } else {
- LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme...");
- applyLightTheme();
- }
- }
-
- private void applyLightTheme() {
- Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString());
- appearanceProvider.ifPresent(appearanceProvider -> {
- appearanceProvider.adjustToTheme(Theme.LIGHT);
- });
- }
-
- private void applyDarkTheme() {
- Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString());
- appearanceProvider.ifPresent(appearanceProvider -> {
- appearanceProvider.adjustToTheme(Theme.DARK);
- });
+ launchEventHandler.startHandlingLaunchEvents();
+ autoUnlocker.unlock();
}
}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java
index 7d5fd55bf..2557aa9ee 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java
@@ -5,8 +5,12 @@
*******************************************************************************/
package org.cryptomator.ui.fxapp;
+import dagger.BindsInstance;
import dagger.Subcomponent;
+import javafx.application.Application;
+import javafx.stage.Stage;
+
@FxApplicationScoped
@Subcomponent(modules = FxApplicationModule.class)
public interface FxApplicationComponent {
@@ -16,6 +20,12 @@ public interface FxApplicationComponent {
@Subcomponent.Builder
interface Builder {
+ @BindsInstance
+ Builder fxApplication(Application application);
+
+ @BindsInstance
+ Builder primaryStage(@PrimaryStage Stage primaryStage);
+
FxApplicationComponent build();
}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
index 74c201372..85e46dffa 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
@@ -5,7 +5,6 @@
*******************************************************************************/
package org.cryptomator.ui.fxapp;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
@@ -15,67 +14,46 @@ 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.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import javax.inject.Named;
-import javafx.application.Application;
-import javafx.collections.ObservableSet;
import javafx.scene.image.Image;
-import javafx.stage.Stage;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.List;
-@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class})
+@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class})
abstract class FxApplicationModule {
- @Provides
- @Named("windowIcons")
- @FxApplicationScoped
- static List provideWindowIcons() {
- if (SystemUtils.IS_OS_MAC) {
- return Collections.emptyList();
- }
- try {
- return List.of( //
- createImageFromResource("/img/window_icon_32.png"), //
- createImageFromResource("/img/window_icon_512.png") //
- );
- } catch (IOException e) {
- throw new UncheckedIOException("Failed to load embedded resource.", e);
- }
- }
-
- @Provides
- @FxApplicationScoped
- static StageFactory provideStageFactory(@Named("windowIcons") List windowIcons) {
- return new StageFactory(stage -> {
- stage.getIcons().addAll(windowIcons);
- });
- }
-
private static Image createImageFromResource(String resourceName) throws IOException {
try (InputStream in = FxApplicationModule.class.getResourceAsStream(resourceName)) {
return new Image(in);
}
}
- @Binds
- abstract Application bindApplication(FxApplication application);
+ @Provides
+ @FxApplicationScoped
+ static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) {
+ return builder.build();
+ }
@Provides
+ @FxApplicationScoped
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
return builder.build();
}
@Provides
+ @FxApplicationScoped
static PreferencesComponent providePreferencesComponent(PreferencesComponent.Builder builder) {
return builder.build();
}
@Provides
+ @FxApplicationScoped
static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
return builder.build();
}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
new file mode 100644
index 000000000..da2a4a800
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -0,0 +1,94 @@
+package org.cryptomator.ui.fxapp;
+
+import org.cryptomator.common.LicenseHolder;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.UiTheme;
+import org.cryptomator.integrations.uiappearance.Theme;
+import org.cryptomator.integrations.uiappearance.UiAppearanceException;
+import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
+import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javafx.application.Application;
+import javafx.beans.value.ObservableValue;
+import java.util.Optional;
+
+@FxApplicationScoped
+public class FxApplicationStyle {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStyle.class);
+
+ private final Settings settings;
+ private final Optional appearanceProvider;
+ private final LicenseHolder licenseHolder;
+ private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
+
+ @Inject
+ public FxApplicationStyle(Settings settings, Optional appearanceProvider, LicenseHolder licenseHolder){
+ this.settings = settings;
+ this.appearanceProvider = appearanceProvider;
+ this.licenseHolder = licenseHolder;
+ }
+
+ public void initialize() {
+ settings.theme().addListener(this::appThemeChanged);
+ loadSelectedStyleSheet(settings.theme().get());
+ }
+
+ private void appThemeChanged(@SuppressWarnings("unused") ObservableValue extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
+ if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
+ try {
+ appearanceProvider.get().removeListener(systemInterfaceThemeListener);
+ } catch (UiAppearanceException e) {
+ LOG.error("Failed to disable automatic theme switching.");
+ }
+ }
+ loadSelectedStyleSheet(newValue);
+ }
+
+ private void loadSelectedStyleSheet(UiTheme desiredTheme) {
+ UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
+ switch (theme) {
+ case LIGHT -> applyLightTheme();
+ case DARK -> applyDarkTheme();
+ case AUTOMATIC -> {
+ appearanceProvider.ifPresent(provider -> {
+ try {
+ provider.addListener(systemInterfaceThemeListener);
+ } catch (UiAppearanceException e) {
+ LOG.error("Failed to enable automatic theme switching.");
+ }
+ });
+ applySystemTheme();
+ }
+ }
+ }
+
+ private void systemInterfaceThemeChanged(Theme theme) {
+ switch (theme) {
+ case LIGHT -> applyLightTheme();
+ case DARK -> applyDarkTheme();
+ }
+ }
+
+ private void applySystemTheme() {
+ if (appearanceProvider.isPresent()) {
+ systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme());
+ } else {
+ LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme...");
+ applyLightTheme();
+ }
+ }
+
+ private void applyLightTheme() {
+ Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString());
+ appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
+ }
+
+ private void applyDarkTheme() {
+ Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString());
+ appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
+ }
+}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java
new file mode 100644
index 000000000..7c7b07c1e
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java
@@ -0,0 +1,134 @@
+package org.cryptomator.ui.fxapp;
+
+import com.google.common.base.Preconditions;
+import org.cryptomator.common.ShutdownHook;
+import org.cryptomator.common.vaults.LockNotCompletedException;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultState;
+import org.cryptomator.common.vaults.Volume;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javafx.beans.Observable;
+import javafx.collections.ObservableList;
+import java.awt.Desktop;
+import java.awt.desktop.QuitResponse;
+import java.awt.desktop.QuitStrategy;
+import java.util.EnumSet;
+import java.util.EventObject;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.cryptomator.common.vaults.VaultState.Value.*;
+
+@FxApplicationScoped
+public class FxApplicationTerminator {
+
+ private static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
+ private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
+
+ private final ObservableList vaults;
+ private final ShutdownHook shutdownHook;
+ private final FxApplicationWindows appWindows;
+ private final AtomicBoolean allowQuitWithoutPrompt = new AtomicBoolean();
+
+ @Inject
+ public FxApplicationTerminator(ObservableList vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows) {
+ this.vaults = vaults;
+ this.shutdownHook = shutdownHook;
+ this.appWindows = appWindows;
+ }
+
+ public void initialize() {
+ Preconditions.checkState(Desktop.isDesktopSupported(), "java.awt.Desktop not supported");
+ Desktop desktop = Desktop.getDesktop();
+
+ // register quit handler
+ if (desktop.isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
+ desktop.setQuitHandler(this::handleQuitRequest);
+ }
+
+ // set quit strategy (cmd+q would call `System.exit(0)` otherwise)
+ if (desktop.isSupported(Desktop.Action.APP_QUIT_STRATEGY)) {
+ desktop.setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS);
+ }
+
+ // allow sudden termination?
+ vaultListChanged(vaults);
+ vaults.addListener(this::vaultListChanged);
+
+ shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
+ }
+
+ /**
+ * Gracefully terminates the application.
+ */
+ public void terminate() {
+ handleQuitRequest(null, new NoopQuitResponse());
+ }
+
+ private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
+ boolean allowSuddenTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
+ boolean stateChanged = allowQuitWithoutPrompt.compareAndSet(!allowSuddenTermination, allowSuddenTermination);
+ Desktop desktop = Desktop.getDesktop();
+ if (stateChanged && desktop.isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
+ if (allowSuddenTermination) {
+ LOG.debug("Enabling sudden termination");
+ desktop.enableSuddenTermination();
+ } else {
+ LOG.debug("Disabling sudden termination");
+ desktop.disableSuddenTermination();
+ }
+ }
+ }
+
+ /**
+ * Asks the app to quit. If confirmed, the JavaFX application will exit before giving a {@code response}.
+ *
+ * @param e ignored
+ * @param response a quit response that will be {@link ExitingQuitResponse decorated in order to exit the JavaFX application}.
+ */
+ private void handleQuitRequest(@SuppressWarnings("unused") @Nullable EventObject e, QuitResponse response) {
+ var exitingResponse = new ExitingQuitResponse(response);
+ if (allowQuitWithoutPrompt.get()) {
+ exitingResponse.performQuit();
+ } else {
+ appWindows.showQuitWindow(exitingResponse);
+ }
+ }
+
+ private void forceUnmountRemainingVaults() {
+ for (Vault vault : vaults) {
+ if (vault.isUnlocked()) {
+ try {
+ vault.lock(true);
+ } catch (Volume.VolumeException e) {
+ LOG.error("Failed to unmount vault " + vault.getPath(), e);
+ } catch (LockNotCompletedException e) {
+ LOG.error("Failed to lock vault " + vault.getPath(), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * A dummy QuitResponse that ignores the response.
+ *
+ * To be used with {@link #handleQuitRequest(EventObject, QuitResponse)} if the invoking method is not interested in the response.
+ */
+ private static class NoopQuitResponse implements QuitResponse {
+
+ @Override
+ public void performQuit() {
+ // no-op
+ }
+
+ @Override
+ public void cancelQuit() {
+ // no-op
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java
new file mode 100644
index 000000000..ba28f9bc4
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java
@@ -0,0 +1,155 @@
+package org.cryptomator.ui.fxapp;
+
+import com.google.common.base.Preconditions;
+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.common.ErrorComponent;
+import org.cryptomator.ui.lock.LockComponent;
+import org.cryptomator.ui.mainwindow.MainWindowComponent;
+import org.cryptomator.ui.preferences.PreferencesComponent;
+import org.cryptomator.ui.preferences.SelectedPreferencesTab;
+import org.cryptomator.ui.quit.QuitComponent;
+import org.cryptomator.ui.unlock.UnlockComponent;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javafx.application.Platform;
+import javafx.collections.ListChangeListener;
+import javafx.collections.transformation.FilteredList;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+import java.awt.Desktop;
+import java.awt.desktop.AppReopenedListener;
+import java.awt.desktop.QuitResponse;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutorService;
+
+@FxApplicationScoped
+public class FxApplicationWindows {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FxApplicationWindows.class);
+
+ private final Stage primaryStage;
+ private final Optional trayIntegration;
+ private final Lazy mainWindow;
+ private final Lazy preferencesWindow;
+ private final Lazy quitWindow;
+ private final UnlockComponent.Factory unlockWorkflowFactory;
+ private final LockComponent.Factory lockWorkflowFactory;
+ private final ErrorComponent.Factory errorWindowFactory;
+ private final ExecutorService executor;
+ private final FilteredList visibleWindows;
+
+ @Inject
+ public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional trayIntegration, Lazy mainWindow, Lazy preferencesWindow, Lazy quitWindow, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor) {
+ this.primaryStage = primaryStage;
+ this.trayIntegration = trayIntegration;
+ this.mainWindow = mainWindow;
+ this.preferencesWindow = preferencesWindow;
+ this.quitWindow = quitWindow;
+ this.unlockWorkflowFactory = unlockWorkflowFactory;
+ this.lockWorkflowFactory = lockWorkflowFactory;
+ this.errorWindowFactory = errorWindowFactory;
+ this.executor = executor;
+ this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
+ }
+
+ public void initialize() {
+ Preconditions.checkState(Desktop.isDesktopSupported(), "java.awt.Desktop not supported");
+ Desktop desktop = Desktop.getDesktop();
+
+ // register preferences shortcut
+ if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) {
+ desktop.setPreferencesHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ANY));
+ }
+
+ // register preferences shortcut
+ if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
+ desktop.setAboutHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
+ }
+
+ // register app reopen listener
+ if (desktop.isSupported(Desktop.Action.APP_EVENT_REOPENED)) {
+ desktop.addAppEventListener((AppReopenedListener) e -> showMainWindow());
+ }
+
+ // observe visible windows
+ if (trayIntegration.isPresent()) {
+ visibleWindows.addListener(this::visibleWindowsChanged);
+ }
+ }
+
+ private void visibleWindowsChanged(ListChangeListener.Change extends Window> change) {
+ int visibleWindows = change.getList().size();
+ LOG.debug("visible windows: {}", visibleWindows);
+ if (visibleWindows > 0) {
+ trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
+ } else {
+ trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray);
+ }
+ }
+
+ public CompletionStage showMainWindow() {
+ return CompletableFuture.supplyAsync(mainWindow.get()::showMainWindow, Platform::runLater).whenComplete(this::reportErrors);
+ }
+
+ public CompletionStage showPreferencesWindow(SelectedPreferencesTab selectedTab) {
+ return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
+ }
+
+ public CompletionStage showQuitWindow(QuitResponse response) {
+ return CompletableFuture.supplyAsync(() -> quitWindow.get().showQuitWindow(response), Platform::runLater).whenComplete(this::reportErrors);
+ }
+
+ public CompletionStage startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
+ return CompletableFuture.supplyAsync(() -> {
+ Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked.");
+ LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
+ return unlockWorkflowFactory.create(vault, owner).unlockWorkflow();
+ }, Platform::runLater) //
+ .thenCompose(unlockWorkflow -> CompletableFuture.runAsync(unlockWorkflow, executor)) //
+ .exceptionally(e -> {
+ showErrorWindow(e, owner == null ? primaryStage : owner, null);
+ return null;
+ });
+ }
+
+ public CompletionStage startLockWorkflow(Vault vault, @Nullable Stage owner) {
+ return CompletableFuture.supplyAsync(() -> {
+ Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING), "Vault not unlocked.");
+ LOG.debug("Start lock workflow for {}", vault.getDisplayName());
+ return lockWorkflowFactory.create(vault, owner).lockWorkflow();
+ }, Platform::runLater) //
+ .thenCompose(lockWorkflow -> CompletableFuture.runAsync(lockWorkflow, executor)) //
+ .exceptionally(e -> {
+ showErrorWindow(e, owner == null ? primaryStage : owner, null);
+ return null;
+ });
+ }
+
+ /**
+ * Displays the generic error scene in the given window.
+ *
+ * @param cause The exception to show
+ * @param window What window to display the scene in
+ * @param previousScene To what scene to return to when pressing "back". Back button will be hidden, if null
+ * @return A
+ */
+ public CompletionStage showErrorWindow(Throwable cause, Stage window, @Nullable Scene previousScene) {
+ return CompletableFuture.supplyAsync(() -> errorWindowFactory.create(cause, window, previousScene).show(), Platform::runLater).whenComplete(this::reportErrors);
+ }
+
+ private void reportErrors(@Nullable Stage stage, @Nullable Throwable error) {
+ if (error != null) {
+ LOG.error("Failed to display stage", error);
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/PrimaryStage.java b/src/main/java/org/cryptomator/ui/fxapp/PrimaryStage.java
new file mode 100644
index 000000000..e20b43525
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/fxapp/PrimaryStage.java
@@ -0,0 +1,14 @@
+package org.cryptomator.ui.fxapp;
+
+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)
+public @interface PrimaryStage {
+
+}
diff --git a/src/main/java/org/cryptomator/ui/health/CheckListController.java b/src/main/java/org/cryptomator/ui/health/CheckListController.java
index 75ecdef52..22ec37b48 100644
--- a/src/main/java/org/cryptomator/ui/health/CheckListController.java
+++ b/src/main/java/org/cryptomator/ui/health/CheckListController.java
@@ -1,9 +1,8 @@
package org.cryptomator.ui.health;
import com.google.common.base.Preconditions;
-import dagger.Lazy;
-import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,9 +14,7 @@ import javafx.beans.property.ObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
-import javafx.event.ActionEvent;
import javafx.fxml.FXML;
-import javafx.scene.control.CheckBox;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.stage.Stage;
@@ -37,7 +34,7 @@ public class CheckListController implements FxController {
private final ObjectProperty selectedCheck;
private final BooleanBinding mainRunStarted; //TODO: rerunning not considered for now
private final BooleanBinding somethingsRunning;
- private final Lazy errorComponentBuilder;
+ private final FxApplicationWindows appWindows;
private final IntegerBinding chosenTaskCount;
private final BooleanBinding anyCheckSelected;
private final CheckListCellFactory listCellFactory;
@@ -46,7 +43,7 @@ public class CheckListController implements FxController {
public ListView checksListView;
@Inject
- public CheckListController(@HealthCheckWindow Stage window, List checks, CheckExecutor checkExecutor, ReportWriter reportWriteTask, ObjectProperty selectedCheck, Lazy errorComponentBuilder, CheckListCellFactory listCellFactory) {
+ public CheckListController(@HealthCheckWindow Stage window, List checks, CheckExecutor checkExecutor, ReportWriter reportWriteTask, ObjectProperty selectedCheck, FxApplicationWindows appWindows, CheckListCellFactory listCellFactory) {
this.window = window;
this.checks = FXCollections.observableList(checks, Check::observables);
this.checkExecutor = checkExecutor;
@@ -54,7 +51,7 @@ public class CheckListController implements FxController {
this.chosenChecks = this.checks.filtered(Check::isChosenForExecution);
this.reportWriter = reportWriteTask;
this.selectedCheck = selectedCheck;
- this.errorComponentBuilder = errorComponentBuilder;
+ this.appWindows = appWindows;
this.chosenTaskCount = Bindings.size(this.chosenChecks);
this.mainRunStarted = Bindings.isEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.RUNNABLE));
this.somethingsRunning = Bindings.isNotEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.SCHEDULED || c.getState() == Check.CheckState.RUNNING));
@@ -104,7 +101,7 @@ public class CheckListController implements FxController {
reportWriter.writeReport(chosenChecks);
} catch (IOException e) {
LOG.error("Failed to write health check report.", e);
- errorComponentBuilder.get().cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
+ appWindows.showErrorWindow(e, window, window.getScene());
}
}
diff --git a/src/main/java/org/cryptomator/ui/health/StartController.java b/src/main/java/org/cryptomator/ui/health/StartController.java
index 44c3f3a8f..fa41a7fdc 100644
--- a/src/main/java/org/cryptomator/ui/health/StartController.java
+++ b/src/main/java/org/cryptomator/ui/health/StartController.java
@@ -7,10 +7,10 @@ import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.cryptofs.VaultKeyInvalidException;
import org.cryptomator.cryptolib.api.Masterkey;
-import org.cryptomator.ui.common.ErrorComponent;
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.KeyLoadingStrategy;
import org.cryptomator.ui.unlock.UnlockCancelledException;
import org.slf4j.Logger;
@@ -40,10 +40,10 @@ public class StartController implements FxController {
private final AtomicReference masterkeyRef;
private final AtomicReference vaultConfigRef;
private final Lazy checkScene;
- private final Lazy errorComponent;
+ private final FxApplicationWindows appWindows;
@Inject
- public StartController(@HealthCheckWindow Stage window, @HealthCheckWindow Vault vault, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy checkScene, Lazy errorComponent, @Named("unlockWindow") Stage unlockWindow) {
+ public StartController(@HealthCheckWindow Stage window, @HealthCheckWindow Vault vault, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy checkScene, FxApplicationWindows appWindows, @Named("unlockWindow") Stage unlockWindow) {
this.window = window;
this.unlockWindow = unlockWindow;
this.vaultConfig = vault.getVaultConfigCache();
@@ -52,7 +52,7 @@ public class StartController implements FxController {
this.masterkeyRef = masterkeyRef;
this.vaultConfigRef = vaultConfigRef;
this.checkScene = checkScene;
- this.errorComponent = errorComponent;
+ this.appWindows = appWindows;
}
@FXML
@@ -106,10 +106,10 @@ public class StartController implements FxController {
// ok
} else if (e instanceof VaultKeyInvalidException) {
LOG.error("Invalid key"); //TODO: specific error screen
- errorComponent.get().window(window).cause(e).build().showErrorScene();
+ appWindows.showErrorWindow(e, window, null);
} else {
LOG.error("Failed to load key.", e);
- errorComponent.get().window(window).cause(e).build().showErrorScene();
+ appWindows.showErrorWindow(e, window, null);
}
}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java
index 21240b97a..5dbb4adbd 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java
@@ -27,7 +27,7 @@ abstract class KeyLoadingModule {
@Provides
@KeyLoading
@KeyLoadingScoped
- static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Vault vault, Map> strategies) {
+ static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading Vault vault, Map> strategies) {
try {
String scheme = vault.getVaultConfigCache().get().getKeyId().getScheme();
var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java
new file mode 100644
index 000000000..a548cd47d
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java
@@ -0,0 +1,25 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+import dagger.Subcomponent;
+
+import javafx.scene.Scene;
+import java.nio.file.Path;
+import java.util.concurrent.CompletableFuture;
+
+@ChooseMasterkeyFileScoped
+@Subcomponent(modules = {ChooseMasterkeyFileModule.class})
+public interface ChooseMasterkeyFileComponent {
+
+ @ChooseMasterkeyFileScoped
+ Scene chooseMasterkeyScene();
+
+ @ChooseMasterkeyFileScoped
+ CompletableFuture result();
+
+ @Subcomponent.Builder
+ interface Builder {
+
+ ChooseMasterkeyFileComponent build();
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java
new file mode 100644
index 000000000..11cf7bd6b
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java
@@ -0,0 +1,57 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.keyloading.KeyLoading;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javafx.fxml.FXML;
+import javafx.stage.FileChooser;
+import javafx.stage.Stage;
+import javafx.stage.WindowEvent;
+import java.io.File;
+import java.nio.file.Path;
+import java.util.ResourceBundle;
+import java.util.concurrent.CompletableFuture;
+
+@ChooseMasterkeyFileScoped
+public class ChooseMasterkeyFileController implements FxController {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ChooseMasterkeyFileController.class);
+
+ private final Stage window;
+ private final CompletableFuture result;
+ private final ResourceBundle resourceBundle;
+
+ @Inject
+ public ChooseMasterkeyFileController(@KeyLoading Stage window, CompletableFuture result, ResourceBundle resourceBundle) {
+ this.window = window;
+ this.result = result;
+ this.resourceBundle = resourceBundle;
+ this.window.setOnHiding(this::windowClosed);
+ }
+
+ @FXML
+ public void cancel() {
+ window.close();
+ }
+
+ private void windowClosed(WindowEvent windowEvent) {
+ result.cancel(true);
+ }
+
+ @FXML
+ public void proceed() {
+ LOG.trace("proceed()");
+ FileChooser fileChooser = new FileChooser();
+ fileChooser.setTitle(resourceBundle.getString("unlock.chooseMasterkey.filePickerTitle"));
+ fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
+ File masterkeyFile = fileChooser.showOpenDialog(window);
+ if (masterkeyFile != null) {
+ LOG.debug("Chose masterkey file: {}", masterkeyFile);
+ result.complete(masterkeyFile.toPath());
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java
new file mode 100644
index 000000000..21ae2b26c
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java
@@ -0,0 +1,29 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+import dagger.Module;
+import dagger.Provides;
+import org.cryptomator.ui.common.DefaultSceneFactory;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlLoaderFactory;
+
+import javafx.scene.Scene;
+import java.nio.file.Path;
+import java.util.ResourceBundle;
+import java.util.concurrent.CompletableFuture;
+
+@Module
+interface ChooseMasterkeyFileModule {
+
+ @Provides
+ @ChooseMasterkeyFileScoped
+ static CompletableFuture provideResult() {
+ return new CompletableFuture<>();
+ }
+
+ @Provides
+ @ChooseMasterkeyFileScoped
+ static Scene provideChooseMasterkeyScene(ChooseMasterkeyFileController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
+ return FxmlLoaderFactory.forController(controller, sceneFactory, resourceBundle).createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE);
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java
new file mode 100644
index 000000000..4bf8c5c24
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java
@@ -0,0 +1,13 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+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 ChooseMasterkeyFileScoped {
+
+}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java
deleted file mode 100644
index 44d7ebfb0..000000000
--- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.cryptomator.ui.keyloading.masterkeyfile;
-
-import org.cryptomator.common.keychain.KeychainManager;
-import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.integrations.keychain.KeychainAccessException;
-import org.cryptomator.ui.keyloading.KeyLoading;
-import org.cryptomator.ui.keyloading.KeyLoadingScoped;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import java.nio.CharBuffer;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-@KeyLoadingScoped
-class MasterkeyFileLoadingFinisher {
-
- private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingFinisher.class);
-
- private final Vault vault;
- private final Optional storedPassword;
- private final AtomicReference enteredPassword;
- private final AtomicBoolean shouldSavePassword;
- private final KeychainManager keychain;
-
- @Inject
- MasterkeyFileLoadingFinisher(@KeyLoading Vault vault, @Named("savedPassword") Optional storedPassword, AtomicReference enteredPassword, @Named("savePassword") AtomicBoolean shouldSavePassword, KeychainManager keychain) {
- this.vault = vault;
- this.storedPassword = storedPassword;
- this.enteredPassword = enteredPassword;
- this.shouldSavePassword = shouldSavePassword;
- this.keychain = keychain;
- }
-
- public void cleanup(boolean successfullyUnlocked) {
- if (successfullyUnlocked && shouldSavePassword.get()) {
- savePasswordToSystemkeychain();
- }
- wipePassword(storedPassword.orElse(null));
- wipePassword(enteredPassword.getAndSet(null));
- }
-
- private void savePasswordToSystemkeychain() {
- if (keychain.isSupported()) {
- try {
- keychain.storePassphrase(vault.getId(), vault.getDisplayName(), CharBuffer.wrap(enteredPassword.get()));
- } catch (KeychainAccessException e) {
- LOG.error("Failed to store passphrase in system keychain.", e);
- }
- }
- }
-
- private void wipePassword(char[] pw) {
- if (pw != null) {
- Arrays.fill(pw, ' ');
- }
- }
-}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java
index 4b3b1055b..9375b0cff 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java
@@ -8,54 +8,17 @@ import dagger.multibindings.StringKey;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.keychain.KeychainAccessException;
-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.UserInteractionLock;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
-import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
-import javafx.scene.Scene;
-import javafx.stage.Stage;
-import java.nio.file.Path;
import java.util.Optional;
-import java.util.ResourceBundle;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-@Module(subcomponents = {ForgetPasswordComponent.class})
-public abstract class MasterkeyFileLoadingModule {
-
- private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class);
-
- public enum PasswordEntry {
- PASSWORD_ENTERED,
- CANCELLED
- }
-
- public enum MasterkeyFileProvision {
- MASTERKEYFILE_PROVIDED,
- CANCELLED
- }
-
- @Provides
- @KeyLoadingScoped
- static UserInteractionLock providePasswordEntryLock() {
- return new UserInteractionLock<>(null);
- }
-
- @Provides
- @KeyLoadingScoped
- static UserInteractionLock provideMasterkeyFileProvisionLock() {
- return new UserInteractionLock<>(null);
- }
+@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class, ChooseMasterkeyFileComponent.class})
+public interface MasterkeyFileLoadingModule {
@Provides
@Named("savedPassword")
@@ -67,67 +30,12 @@ public abstract class MasterkeyFileLoadingModule {
try {
return Optional.ofNullable(keychain.loadPassphrase(vault.getId()));
} catch (KeychainAccessException e) {
- LOG.error("Failed to load entry from system keychain.", e);
+ LoggerFactory.getLogger(MasterkeyFileLoadingModule.class).error("Failed to load entry from system keychain.", e);
return Optional.empty();
}
}
}
- @Provides
- @KeyLoadingScoped
- static AtomicReference provideUserProvidedMasterkeyPath() {
- return new AtomicReference<>();
- }
-
- @Provides
- @KeyLoadingScoped
- static AtomicReference providePassword(@Named("savedPassword") Optional storedPassword) {
- return new AtomicReference<>(storedPassword.orElse(null));
- }
-
- @Provides
- @Named("savePassword")
- @KeyLoadingScoped
- static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional storedPassword) {
- return new AtomicBoolean(storedPassword.isPresent());
- }
-
- @Provides
- @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD)
- @KeyLoadingScoped
- static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders, @KeyLoading Stage window, @KeyLoading Vault v, ResourceBundle resourceBundle) {
- var scene = fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD);
- scene.windowProperty().addListener((prop, oldVal, newVal) -> {
- if (window.equals(newVal)) {
- window.setTitle(String.format(resourceBundle.getString("unlock.title"), v.getDisplayName()));
- }
- });
- return scene;
- }
-
- @Provides
- @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE)
- @KeyLoadingScoped
- static Scene provideUnlockSelectMasterkeyFileScene(@KeyLoading FxmlLoaderFactory fxmlLoaders, @KeyLoading Stage window, @KeyLoading Vault v, ResourceBundle resourceBundle) {
- var scene = fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE);
- scene.windowProperty().addListener((prop, oldVal, newVal) -> {
- if (window.equals(newVal)) {
- window.setTitle(String.format(resourceBundle.getString("unlock.chooseMasterkey.title"), v.getDisplayName()));
- }
- });
- return scene;
- }
-
- @Binds
- @IntoMap
- @FxControllerKey(PassphraseEntryController.class)
- abstract FxController bindUnlockController(PassphraseEntryController controller);
-
- @Binds
- @IntoMap
- @FxControllerKey(SelectMasterkeyFileController.class)
- abstract FxController bindUnlockSelectMasterkeyFileController(SelectMasterkeyFileController controller);
-
@Binds
@IntoMap
@KeyLoadingScoped
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java
index 74b63b99a..b4964f9a0 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java
@@ -1,32 +1,33 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import com.google.common.base.Preconditions;
-import dagger.Lazy;
+import org.cryptomator.common.Passphrase;
+import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.common.BackupHelper;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
-import org.cryptomator.ui.common.FxmlFile;
-import org.cryptomator.ui.common.FxmlScene;
-import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.unlock.UnlockCancelledException;
import javax.inject.Inject;
+import javax.inject.Named;
import javafx.application.Platform;
-import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.io.IOException;
import java.net.URI;
-import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
@KeyLoading
public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
@@ -36,28 +37,26 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
private final Vault vault;
private final MasterkeyFileAccess masterkeyFileAccess;
private final Stage window;
- private final Lazy passphraseEntryScene;
- private final Lazy selectMasterkeyFileScene;
- private final UserInteractionLock passwordEntryLock;
- private final UserInteractionLock masterkeyFileProvisionLock;
- private final AtomicReference password;
- private final AtomicReference filePath;
- private final MasterkeyFileLoadingFinisher finisher;
+ private final PassphraseEntryComponent.Builder passphraseEntry;
+ private final ChooseMasterkeyFileComponent.Builder masterkeyFileChoice;
+ private final KeychainManager keychain;
+ private final ResourceBundle resourceBundle;
- private boolean wrongPassword;
+ private Passphrase passphrase;
+ private boolean savePassphrase;
+ private boolean wrongPassphrase;
@Inject
- public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy selectMasterkeyFileScene, UserInteractionLock passwordEntryLock, UserInteractionLock masterkeyFileProvisionLock, AtomicReference password, AtomicReference filePath, MasterkeyFileLoadingFinisher finisher) {
+ public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain, ResourceBundle resourceBundle) {
this.vault = vault;
this.masterkeyFileAccess = masterkeyFileAccess;
this.window = window;
- this.passphraseEntryScene = passphraseEntryScene;
- this.selectMasterkeyFileScene = selectMasterkeyFileScene;
- this.passwordEntryLock = passwordEntryLock;
- this.masterkeyFileProvisionLock = masterkeyFileProvisionLock;
- this.password = password;
- this.filePath = filePath;
- this.finisher = finisher;
+ this.passphraseEntry = passphraseEntry;
+ this.masterkeyFileChoice = masterkeyFileChoice;
+ this.keychain = keychain;
+ this.resourceBundle = resourceBundle;
+ this.passphrase = savedPassphrase.map(Passphrase::new).orElse(null);
+ this.savePassphrase = savedPassphrase.isPresent();
}
@Override
@@ -66,9 +65,11 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
try {
Path filePath = vault.getPath().resolve(keyId.getSchemeSpecificPart());
if (!Files.exists(filePath)) {
- filePath = getAlternateMasterkeyFilePath();
+ filePath = askUserForMasterkeyFilePath();
+ }
+ if (passphrase == null) {
+ askForPassphrase();
}
- CharSequence passphrase = getPassphrase();
var masterkey = masterkeyFileAccess.load(filePath, passphrase);
//backup
if (filePath.startsWith(vault.getPath())) {
@@ -90,8 +91,9 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
@Override
public boolean recoverFromException(MasterkeyLoadingFailedException exception) {
if (exception instanceof InvalidPassphraseException) {
- this.wrongPassword = true;
- password.set(null);
+ this.wrongPassphrase = true;
+ passphrase.destroy();
+ this.passphrase = null;
return true; // reattempting key load
} else {
return false; // nothing we can do
@@ -100,23 +102,29 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
@Override
public void cleanup(boolean unlockedSuccessfully) {
- finisher.cleanup(unlockedSuccessfully);
- }
-
- private Path getAlternateMasterkeyFilePath() throws UnlockCancelledException, InterruptedException {
- if (filePath.get() == null) {
- return switch (askUserForMasterkeyFilePath()) {
- case MASTERKEYFILE_PROVIDED -> filePath.get();
- case CANCELLED -> throw new UnlockCancelledException("Choosing masterkey file cancelled.");
- };
- } else {
- return filePath.get();
+ if (unlockedSuccessfully && savePassphrase) {
+ savePasswordToSystemkeychain(passphrase);
+ }
+ if (passphrase != null) {
+ passphrase.destroy();
}
}
- private MasterkeyFileLoadingModule.MasterkeyFileProvision askUserForMasterkeyFilePath() throws InterruptedException {
+ private void savePasswordToSystemkeychain(Passphrase passphrase) {
+ if (keychain.isSupported()) {
+ try {
+ keychain.storePassphrase(vault.getId(), vault.getDisplayName(), passphrase);
+ } catch (KeychainAccessException e) {
+ LOG.error("Failed to store passphrase in system keychain.", e);
+ }
+ }
+ }
+
+ private Path askUserForMasterkeyFilePath() throws InterruptedException {
+ var comp = masterkeyFileChoice.build();
Platform.runLater(() -> {
- window.setScene(selectMasterkeyFileScene.get());
+ window.setScene(comp.chooseMasterkeyScene());
+ window.setTitle(resourceBundle.getString("unlock.chooseMasterkey.title").formatted(vault.getDisplayName()));
window.show();
Window owner = window.getOwner();
if (owner != null) {
@@ -126,24 +134,20 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
window.centerOnScreen();
}
});
- return masterkeyFileProvisionLock.awaitInteraction();
- }
-
- private CharSequence getPassphrase() throws UnlockCancelledException, InterruptedException {
- if (password.get() == null) {
- return switch (askForPassphrase()) {
- case PASSWORD_ENTERED -> CharBuffer.wrap(password.get());
- case CANCELLED -> throw new UnlockCancelledException("Password entry cancelled.");
- };
- } else {
- // e.g. pre-filled from keychain or previous unlock attempt
- return CharBuffer.wrap(password.get());
+ try {
+ return comp.result().get();
+ } catch (CancellationException e) {
+ throw new UnlockCancelledException("Choosing masterkey file cancelled.");
+ } catch (ExecutionException e) {
+ throw new MasterkeyLoadingFailedException("Failed to select masterkey file.", e);
}
}
- private MasterkeyFileLoadingModule.PasswordEntry askForPassphrase() throws InterruptedException {
+ private void askForPassphrase() throws InterruptedException {
+ var comp = passphraseEntry.savedPassword(passphrase).build();
Platform.runLater(() -> {
- window.setScene(passphraseEntryScene.get());
+ window.setScene(comp.passphraseEntryScene());
+ window.setTitle(resourceBundle.getString("unlock.title").formatted(vault.getDisplayName()));
window.show();
Window owner = window.getOwner();
if (owner != null) {
@@ -152,11 +156,19 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
} else {
window.centerOnScreen();
}
- if (wrongPassword) {
+ if (wrongPassphrase) {
Animations.createShakeWindowAnimation(window).play();
}
});
- return passwordEntryLock.awaitInteraction();
+ try {
+ var result = comp.result().get();
+ this.passphrase = result.passphrase();
+ this.savePassphrase = result.savePassphrase();
+ } catch (CancellationException e) {
+ throw new UnlockCancelledException("Password entry cancelled.");
+ } catch (ExecutionException e) {
+ throw new MasterkeyLoadingFailedException("Failed to ask for password.", e);
+ }
}
}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java
new file mode 100644
index 000000000..5e072efd0
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java
@@ -0,0 +1,31 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+import org.cryptomator.common.Nullable;
+import org.cryptomator.common.Passphrase;
+
+import javax.inject.Named;
+import javafx.scene.Scene;
+import java.util.concurrent.CompletableFuture;
+
+@PassphraseEntryScoped
+@Subcomponent(modules = {PassphraseEntryModule.class})
+public interface PassphraseEntryComponent {
+
+ @PassphraseEntryScoped
+ Scene passphraseEntryScene();
+
+ @PassphraseEntryScoped
+ CompletableFuture result();
+
+ @Subcomponent.Builder
+ interface Builder {
+
+ @BindsInstance
+ PassphraseEntryComponent.Builder savedPassword(@Nullable @Named("savedPassword") Passphrase savedPassword);
+
+ PassphraseEntryComponent build();
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java
index 312118ad4..5d2b9b52a 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java
@@ -1,16 +1,14 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
+import org.cryptomator.common.Nullable;
+import org.cryptomator.common.Passphrase;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.common.WeakBindings;
-import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import org.cryptomator.ui.keyloading.KeyLoading;
-import org.cryptomator.ui.keyloading.KeyLoadingScoped;
-import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.PasswordEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,8 +19,8 @@ import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
+import javafx.application.Platform;
import javafx.beans.binding.Bindings;
-import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.BooleanProperty;
@@ -37,33 +35,27 @@ import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.CompletableFuture;
-@KeyLoadingScoped
+@PassphraseEntryScoped
public class PassphraseEntryController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(PassphraseEntryController.class);
private final Stage window;
private final Vault vault;
- private final AtomicReference password;
- private final AtomicBoolean savePassword;
- private final Optional savedPassword;
- private final UserInteractionLock passwordEntryLock;
+ private final CompletableFuture result;
+ private final Passphrase savedPassword;
private final ForgetPasswordComponent.Builder forgetPassword;
private final KeychainManager keychain;
- private final ObjectBinding unlockButtonContentDisplay;
- private final BooleanBinding userInteractionDisabled;
- private final BooleanProperty unlockButtonDisabled;
private final StringBinding vaultName;
+ private final BooleanProperty unlockInProgress = new SimpleBooleanProperty();
+ private final ObjectBinding unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, unlockInProgress);
+ private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty();
/* FXML */
public NiceSecurePasswordField passwordField;
public CheckBox savePasswordCheckbox;
- public FontAwesome5IconView unlockInProgressView;
public ImageView face;
public ImageView leftArm;
public ImageView rightArm;
@@ -72,29 +64,25 @@ public class PassphraseEntryController implements FxController {
public Animation unlockAnimation;
@Inject
- public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, AtomicReference password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional savedPassword, UserInteractionLock passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
+ public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
this.window = window;
this.vault = vault;
- this.password = password;
- this.savePassword = savePassword;
+ this.result = result;
this.savedPassword = savedPassword;
- this.passwordEntryLock = passwordEntryLock;
this.forgetPassword = forgetPassword;
this.keychain = keychain;
- this.unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, passwordEntryLock.awaitingInteraction());
- this.userInteractionDisabled = passwordEntryLock.awaitingInteraction().not();
- this.unlockButtonDisabled = new SimpleBooleanProperty();
this.vaultName = WeakBindings.bindString(vault.displayNameProperty());
- this.window.setOnHiding(this::windowClosed);
+ window.setOnHiding(this::windowClosed);
+ result.whenCompleteAsync((r, t) -> unlockInProgress.set(false), Platform::runLater);
}
@FXML
public void initialize() {
- savePasswordCheckbox.setSelected(savedPassword.isPresent());
- if (password.get() != null) {
- passwordField.setPassword(password.get());
+ if (savedPassword != null) {
+ savePasswordCheckbox.setSelected(true);
+ passwordField.setPassword(savedPassword);
}
- unlockButtonDisabled.bind(userInteractionDisabled.or(passwordField.textProperty().isEmpty()));
+ unlockButtonDisabled.bind(unlockInProgress.or(passwordField.textProperty().isEmpty()));
var leftArmTranslation = new Translate(24, 0);
var leftArmRotation = new Rotate(60, 16, 30, 0);
@@ -132,7 +120,7 @@ public class PassphraseEntryController implements FxController {
new KeyFrame(Duration.millis(1000), faceVisible) //
);
- passwordEntryLock.awaitingInteraction().addListener(observable -> stopUnlockAnimation());
+ result.whenCompleteAsync((r, t) -> stopUnlockAnimation());
}
@FXML
@@ -141,26 +129,20 @@ public class PassphraseEntryController implements FxController {
}
private void windowClosed(WindowEvent windowEvent) {
- // if not already interacted, mark this workflow as cancelled:
- if (passwordEntryLock.awaitingInteraction().get()) {
- LOG.debug("Unlock cancelled by user.");
- passwordEntryLock.interacted(PasswordEntry.CANCELLED);
+ if(!result.isDone()) {
+ result.cancel(true);
+ LOG.debug("Unlock canceled by user.");
}
+
}
@FXML
public void unlock() {
LOG.trace("UnlockController.unlock()");
+ unlockInProgress.set(true);
CharSequence pwFieldContents = passwordField.getCharacters();
- char[] newPw = new char[pwFieldContents.length()];
- for (int i = 0; i < pwFieldContents.length(); i++) {
- newPw[i] = pwFieldContents.charAt(i);
- }
- char[] oldPw = password.getAndSet(newPw);
- if (oldPw != null) {
- Arrays.fill(oldPw, ' ');
- }
- passwordEntryLock.interacted(PasswordEntry.PASSWORD_ENTERED);
+ Passphrase pw = Passphrase.copyOf(pwFieldContents);
+ result.complete(new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected()));
startUnlockAnimation();
}
@@ -184,8 +166,7 @@ public class PassphraseEntryController implements FxController {
@FXML
private void didClickSavePasswordCheckbox() {
- savePassword.set(savePasswordCheckbox.isSelected());
- if (!savePasswordCheckbox.isSelected() && savedPassword.isPresent()) {
+ if (!savePasswordCheckbox.isSelected() && savedPassword != null) {
forgetPassword.vault(vault).owner(window).build().showForgetPassword().thenAccept(forgotten -> savePasswordCheckbox.setSelected(!forgotten));
}
}
@@ -205,15 +186,15 @@ public class PassphraseEntryController implements FxController {
}
public ContentDisplay getUnlockButtonContentDisplay() {
- return passwordEntryLock.awaitingInteraction().get() ? ContentDisplay.TEXT_ONLY : ContentDisplay.LEFT;
+ return unlockInProgress.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
}
- public BooleanBinding userInteractionDisabledProperty() {
- return userInteractionDisabled;
+ public ReadOnlyBooleanProperty userInteractionDisabledProperty() {
+ return unlockInProgress;
}
public boolean isUserInteractionDisabled() {
- return userInteractionDisabled.get();
+ return unlockInProgress.get();
}
public ReadOnlyBooleanProperty unlockButtonDisabledProperty() {
@@ -227,4 +208,6 @@ public class PassphraseEntryController implements FxController {
public boolean isKeychainAccessAvailable() {
return keychain.isSupported();
}
+
+
}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java
new file mode 100644
index 000000000..2c65d440b
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java
@@ -0,0 +1,28 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+import dagger.Module;
+import dagger.Provides;
+import org.cryptomator.ui.common.DefaultSceneFactory;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlLoaderFactory;
+
+import javafx.scene.Scene;
+import java.util.ResourceBundle;
+import java.util.concurrent.CompletableFuture;
+
+@Module
+interface PassphraseEntryModule {
+
+ @Provides
+ @PassphraseEntryScoped
+ static CompletableFuture provideResult() {
+ return new CompletableFuture<>();
+ }
+
+ @Provides
+ @PassphraseEntryScoped
+ static Scene provideUnlockScene(PassphraseEntryController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
+ return FxmlLoaderFactory.forController(controller, sceneFactory, resourceBundle).createScene(FxmlFile.UNLOCK_ENTER_PASSWORD);
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java
new file mode 100644
index 000000000..19057acca
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java
@@ -0,0 +1,8 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+import org.cryptomator.common.Passphrase;
+
+// TODO: change to package-private, as soon as this works for Dagger -.-
+public record PassphraseEntryResult(Passphrase passphrase, boolean savePassphrase) {
+
+}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java
new file mode 100644
index 000000000..a077bcf81
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java
@@ -0,0 +1,13 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+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 PassphraseEntryScoped {
+
+}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java
deleted file mode 100644
index cc103784b..000000000
--- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.cryptomator.ui.keyloading.masterkeyfile;
-
-import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.common.UserInteractionLock;
-import org.cryptomator.ui.keyloading.KeyLoading;
-import org.cryptomator.ui.keyloading.KeyLoadingScoped;
-import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.MasterkeyFileProvision;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javafx.fxml.FXML;
-import javafx.stage.FileChooser;
-import javafx.stage.Stage;
-import javafx.stage.WindowEvent;
-import java.io.File;
-import java.nio.file.Path;
-import java.util.ResourceBundle;
-import java.util.concurrent.atomic.AtomicReference;
-
-@KeyLoadingScoped
-public class SelectMasterkeyFileController implements FxController {
-
- private static final Logger LOG = LoggerFactory.getLogger(SelectMasterkeyFileController.class);
-
- private final Stage window;
- private final AtomicReference masterkeyPath;
- private final UserInteractionLock masterkeyFileProvisionLock;
- private final ResourceBundle resourceBundle;
-
- @Inject
- public SelectMasterkeyFileController(@KeyLoading Stage window, AtomicReference masterkeyPath, UserInteractionLock masterkeyFileProvisionLock, ResourceBundle resourceBundle) {
- this.window = window;
- this.masterkeyPath = masterkeyPath;
- this.masterkeyFileProvisionLock = masterkeyFileProvisionLock;
- this.resourceBundle = resourceBundle;
- this.window.setOnHiding(this::windowClosed);
- }
-
- @FXML
- public void cancel() {
- window.close();
- }
-
- private void windowClosed(WindowEvent windowEvent) {
- // if not already interacted, mark this workflow as cancelled:
- if (masterkeyFileProvisionLock.awaitingInteraction().get()) {
- LOG.debug("Unlock cancelled by user.");
- masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELLED);
- }
- }
-
- @FXML
- public void proceed() {
- LOG.trace("proceed()");
- FileChooser fileChooser = new FileChooser();
- fileChooser.setTitle(resourceBundle.getString("unlock.chooseMasterkey.filePickerTitle"));
- fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
- File masterkeyFile = fileChooser.showOpenDialog(window);
- if (masterkeyFile != null) {
- LOG.debug("Chose masterkey file: {}", masterkeyFile);
- masterkeyPath.set(masterkeyFile.toPath());
- masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.MASTERKEYFILE_PROVIDED);
- }
- }
-
-}
diff --git a/src/main/java/org/cryptomator/ui/launcher/AppLaunchEvent.java b/src/main/java/org/cryptomator/ui/launcher/AppLaunchEvent.java
deleted file mode 100644
index 710c6d435..000000000
--- a/src/main/java/org/cryptomator/ui/launcher/AppLaunchEvent.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.cryptomator.ui.launcher;
-
-import java.nio.file.Path;
-import java.util.Collection;
-
-public class AppLaunchEvent {
-
- private final EventType type;
- private final Collection pathsToOpen;
-
- public enum EventType {
- REVEAL_APP,
- OPEN_FILE
- }
-
- public AppLaunchEvent(EventType type, Collection pathsToOpen) {
- this.type = type;
- this.pathsToOpen = pathsToOpen;
- }
-
- public EventType getType() {
- return type;
- }
-
- public Collection getPathsToOpen() {
- return pathsToOpen;
- }
-}
diff --git a/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java b/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java
deleted file mode 100644
index 66b75840b..000000000
--- a/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package org.cryptomator.ui.launcher;
-
-import org.cryptomator.common.ShutdownHook;
-import org.cryptomator.common.vaults.LockNotCompletedException;
-import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.common.vaults.VaultState;
-import org.cryptomator.common.vaults.Volume;
-import org.cryptomator.ui.preferences.SelectedPreferencesTab;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-import javafx.application.Platform;
-import javafx.beans.Observable;
-import javafx.collections.ObservableList;
-import java.awt.Desktop;
-import java.awt.EventQueue;
-import java.awt.desktop.AboutEvent;
-import java.awt.desktop.QuitResponse;
-import java.awt.desktop.QuitStrategy;
-import java.util.EnumSet;
-import java.util.EventObject;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static org.cryptomator.common.vaults.VaultState.Value.*;
-
-@Singleton
-public class AppLifecycleListener {
-
- private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class);
- public static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
-
- private final FxApplicationStarter fxApplicationStarter;
- private final CountDownLatch shutdownLatch;
- private final ObservableList vaults;
- private final AtomicBoolean allowQuitWithoutPrompt;
-
- @Inject
- AppLifecycleListener(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList vaults) {
- this.fxApplicationStarter = fxApplicationStarter;
- this.shutdownLatch = shutdownLatch;
- this.vaults = vaults;
- this.allowQuitWithoutPrompt = new AtomicBoolean(true);
- vaults.addListener(this::vaultListChanged);
-
- // register preferences shortcut
- if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) {
- Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow);
- }
-
- // register preferences shortcut
- if (Desktop.getDesktop().isSupported(Desktop.Action.APP_ABOUT)) {
- Desktop.getDesktop().setAboutHandler(this::showAboutWindow);
- }
-
- // register quit handler
- if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
- Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
- }
-
- // set quit strategy (cmd+q would call `System.exit(0)` otherwise)
- if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_STRATEGY)) {
- Desktop.getDesktop().setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS);
- }
-
- shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
- }
-
- /**
- * Gracefully terminates the application.
- */
- public void quit() {
- handleQuitRequest(null, new QuitResponse() {
- @Override
- public void performQuit() {
- // no-op
- }
-
- @Override
- public void cancelQuit() {
- // no-op
- }
- });
- }
-
- private void handleQuitRequest(@SuppressWarnings("unused") EventObject e, QuitResponse response) {
- QuitResponse decoratedQuitResponse = decorateQuitResponse(response);
- if (allowQuitWithoutPrompt.get()) {
- decoratedQuitResponse.performQuit();
- } else {
- fxApplicationStarter.get().thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
- }
- }
-
- private QuitResponse decorateQuitResponse(QuitResponse originalQuitResponse) {
- return new QuitResponse() {
- @Override
- public void performQuit() {
- Platform.exit(); // will be no-op, if JavaFX never started.
- shutdownLatch.countDown(); // main thread is waiting for this latch
- originalQuitResponse.performQuit();
- }
-
- @Override
- public void cancelQuit() {
- originalQuitResponse.cancelQuit();
- }
- };
- }
-
- private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
- assert Platform.isFxApplicationThread();
- boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
- boolean suddenTerminationChanged = allowQuitWithoutPrompt.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination);
- if (suddenTerminationChanged) {
- LOG.debug("Allow quitting without prompt: {}", allVaultsAllowTermination);
- }
- }
-
- private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
- fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
- }
-
- private void showAboutWindow(@SuppressWarnings("unused") AboutEvent aboutEvent) {
- fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ABOUT));
- }
-
- private void forceUnmountRemainingVaults() {
- for (Vault vault : vaults) {
- if (vault.isUnlocked()) {
- try {
- vault.lock(true);
- } catch (Volume.VolumeException e) {
- LOG.error("Failed to unmount vault " + vault.getPath(), e);
- } catch (LockNotCompletedException e) {
- LOG.error("Failed to lock vault " + vault.getPath(), e);
- }
- }
- }
- }
-
-}
diff --git a/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java b/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java
deleted file mode 100644
index 1799a9700..000000000
--- a/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.cryptomator.ui.launcher;
-
-import dagger.Lazy;
-import org.cryptomator.ui.fxapp.FxApplication;
-import org.cryptomator.ui.fxapp.FxApplicationComponent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javafx.application.Platform;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@Singleton
-public class FxApplicationStarter {
-
- private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStarter.class);
-
- private final Lazy fxAppComponent;
- private final ExecutorService executor;
- private final AtomicBoolean started;
- private final CompletableFuture future;
-
- @Inject
- public FxApplicationStarter(Lazy fxAppComponent, ExecutorService executor) {
- this.fxAppComponent = fxAppComponent;
- this.executor = executor;
- this.started = new AtomicBoolean();
- this.future = new CompletableFuture<>();
- }
-
- public CompletionStage get() {
- if (!started.getAndSet(true)) {
- start();
- }
- return future;
- }
-
- private void start() {
- executor.submit(() -> {
- LOG.debug("Starting JavaFX runtime...");
- Platform.startup(() -> {
- assert Platform.isFxApplicationThread();
- LOG.info("JavaFX Runtime started.");
- FxApplication app = fxAppComponent.get().application();
- app.start();
- future.complete(app);
- });
- });
- }
-}
diff --git a/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java b/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java
deleted file mode 100644
index 08461a56d..000000000
--- a/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.cryptomator.ui.launcher;
-
-import dagger.Lazy;
-import org.cryptomator.common.settings.Settings;
-import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.integrations.tray.TrayIntegrationProvider;
-import org.cryptomator.ui.fxapp.FxApplication;
-import org.cryptomator.ui.traymenu.TrayMenuComponent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javafx.collections.ObservableList;
-import java.awt.Desktop;
-import java.awt.SystemTray;
-import java.awt.desktop.AppReopenedListener;
-import java.util.Collection;
-import java.util.Optional;
-
-@Singleton
-public class UiLauncher {
-
- private static final Logger LOG = LoggerFactory.getLogger(UiLauncher.class);
-
- private final Settings settings;
- private final ObservableList vaults;
- private final Lazy trayMenu;
- private final FxApplicationStarter fxApplicationStarter;
- private final AppLaunchEventHandler launchEventHandler;
- private final Optional trayIntegration;
-
- @Inject
- public UiLauncher(Settings settings, ObservableList vaults, Lazy trayMenu, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional trayIntegration) {
- this.settings = settings;
- this.vaults = vaults;
- this.trayMenu = trayMenu;
- this.fxApplicationStarter = fxApplicationStarter;
- this.launchEventHandler = launchEventHandler;
- this.trayIntegration = trayIntegration;
- }
-
- public void launch() {
- boolean hidden = settings.startHidden().get();
- if (SystemTray.isSupported() && settings.showTrayIcon().get()) {
- trayMenu.get().initializeTrayIcon();
- launch(true, hidden);
- } else {
- launch(false, hidden);
- }
- }
-
- private void launch(boolean withTrayIcon, boolean hidden) {
- // start hidden, minimized or normal?
- if (withTrayIcon && hidden) {
- LOG.debug("Hiding application...");
- trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray);
- } else if (!withTrayIcon && hidden) {
- LOG.debug("Minimizing application...");
- showMainWindowAsync(true);
- } else {
- LOG.debug("Showing application...");
- showMainWindowAsync(false);
- }
-
- // register app reopen listener
- Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(false));
-
- // auto unlock
- Collection vaultsToAutoUnlock = vaults.filtered(this::shouldAttemptAutoUnlock);
- if (!vaultsToAutoUnlock.isEmpty()) {
- fxApplicationStarter.get().thenAccept(app -> {
- for (Vault vault : vaultsToAutoUnlock) {
- app.startUnlockWorkflow(vault, Optional.empty());
- }
- });
- }
-
- launchEventHandler.startHandlingLaunchEvents();
- }
-
- private boolean shouldAttemptAutoUnlock(Vault vault) {
- return vault.isLocked() && vault.getVaultSettings().unlockAfterStartup().get();
- }
-
- private void showMainWindowAsync(boolean minimize) {
- fxApplicationStarter.get().thenCompose(FxApplication::showMainWindow).thenAccept(win -> win.setIconified(minimize));
- }
-
-}
diff --git a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java b/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java
deleted file mode 100644
index c30efa30e..000000000
--- a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.cryptomator.ui.launcher;
-
-import dagger.Module;
-import dagger.Provides;
-import org.cryptomator.common.PluginClassLoader;
-import org.cryptomator.integrations.autostart.AutoStartProvider;
-import org.cryptomator.integrations.tray.TrayIntegrationProvider;
-import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
-import org.cryptomator.ui.fxapp.FxApplicationComponent;
-import org.cryptomator.ui.traymenu.TrayMenuComponent;
-
-import javax.inject.Named;
-import javax.inject.Singleton;
-import java.util.Optional;
-import java.util.ResourceBundle;
-import java.util.ServiceLoader;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-
-@Module(subcomponents = {TrayMenuComponent.class, FxApplicationComponent.class})
-public abstract class UiLauncherModule {
-
- @Provides
- @Singleton
- static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) {
- return builder.build();
- }
-
- @Provides
- @Singleton
- static FxApplicationComponent provideFxApplicationComponent(FxApplicationComponent.Builder builder) {
- return builder.build();
- }
-
- @Provides
- @Singleton
- static Optional provideAppearanceProvider(PluginClassLoader classLoader) {
- return ServiceLoader.load(UiAppearanceProvider.class, classLoader).findFirst();
- }
-
- @Provides
- @Singleton
- static Optional provideAutostartProvider(PluginClassLoader classLoader) {
- return ServiceLoader.load(AutoStartProvider.class, classLoader).findFirst();
- }
-
-
- @Provides
- @Singleton
- static Optional provideTrayIntegrationProvider(PluginClassLoader classLoader) {
- return ServiceLoader.load(TrayIntegrationProvider.class, classLoader).findFirst();
- }
-
- @Provides
- @Singleton
- static ResourceBundle provideLocalization() {
- return ResourceBundle.getBundle("i18n.strings");
- }
-
- @Provides
- @Singleton
- @Named("launchEventQueue")
- static BlockingQueue provideFileOpenRequests() {
- return new ArrayBlockingQueue<>(10);
- }
-
-}
diff --git a/src/main/java/org/cryptomator/ui/lock/LockComponent.java b/src/main/java/org/cryptomator/ui/lock/LockComponent.java
index 9796c88c7..eda81f7f6 100644
--- a/src/main/java/org/cryptomator/ui/lock/LockComponent.java
+++ b/src/main/java/org/cryptomator/ui/lock/LockComponent.java
@@ -2,11 +2,11 @@ package org.cryptomator.ui.lock;
import dagger.BindsInstance;
import dagger.Subcomponent;
+import org.cryptomator.common.Nullable;
import org.cryptomator.common.vaults.Vault;
import javax.inject.Named;
import javafx.stage.Stage;
-import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -25,15 +25,9 @@ public interface LockComponent {
return workflow;
}
- @Subcomponent.Builder
- interface Builder {
-
- @BindsInstance
- LockComponent.Builder vault(@LockWindow Vault vault);
-
- @BindsInstance
- LockComponent.Builder owner(@Named("lockWindowOwner") Optional owner);
-
- LockComponent build();
+ @Subcomponent.Factory
+ interface Factory {
+ LockComponent create(@BindsInstance @LockWindow Vault vault, @BindsInstance @Named("lockWindowOwner") @Nullable Stage owner);
}
+
}
diff --git a/src/main/java/org/cryptomator/ui/lock/LockForcedController.java b/src/main/java/org/cryptomator/ui/lock/LockForcedController.java
index c3a452acc..15cf119be 100644
--- a/src/main/java/org/cryptomator/ui/lock/LockForcedController.java
+++ b/src/main/java/org/cryptomator/ui/lock/LockForcedController.java
@@ -2,56 +2,48 @@ package org.cryptomator.ui.lock;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.common.UserInteractionLock;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
@LockScoped
public class LockForcedController implements FxController {
- private static final Logger LOG = LoggerFactory.getLogger(LockForcedController.class);
-
private final Stage window;
private final Vault vault;
- private final UserInteractionLock forceLockDecisionLock;
+ private final AtomicReference> forceRetryDecision;
@Inject
- public LockForcedController(@LockWindow Stage window, @LockWindow Vault vault, UserInteractionLock forceLockDecisionLock) {
+ public LockForcedController(@LockWindow Stage window, @LockWindow Vault vault, AtomicReference> forceRetryDecision) {
this.window = window;
this.vault = vault;
- this.forceLockDecisionLock = forceLockDecisionLock;
+ this.forceRetryDecision = forceRetryDecision;
this.window.setOnHiding(this::windowClosed);
}
@FXML
public void cancel() {
- forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL);
window.close();
}
@FXML
public void retry() {
- forceLockDecisionLock.interacted(LockModule.ForceLockDecision.RETRY);
+ forceRetryDecision.get().complete(false);
window.close();
}
@FXML
public void force() {
- forceLockDecisionLock.interacted(LockModule.ForceLockDecision.FORCE);
+ forceRetryDecision.get().complete(true);
window.close();
}
private void windowClosed(WindowEvent windowEvent) {
- // if not already interacted, set the decision to CANCEL
- if (forceLockDecisionLock.awaitingInteraction().get()) {
- LOG.debug("Lock canceled in force-lock-phase by user.");
- forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL);
- }
+ forceRetryDecision.get().cancel(true);
}
// ----- Getter & Setter -----
diff --git a/src/main/java/org/cryptomator/ui/lock/LockModule.java b/src/main/java/org/cryptomator/ui/lock/LockModule.java
index d1eb5f189..c5657a488 100644
--- a/src/main/java/org/cryptomator/ui/lock/LockModule.java
+++ b/src/main/java/org/cryptomator/ui/lock/LockModule.java
@@ -6,13 +6,13 @@ import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory;
-import org.cryptomator.ui.common.FxmlLoaderFactory;
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.common.UserInteractionLock;
+import org.jetbrains.annotations.Nullable;
import javax.inject.Named;
import javax.inject.Provider;
@@ -20,22 +20,17 @@ import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
-import java.util.Optional;
import java.util.ResourceBundle;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
@Module
abstract class LockModule {
- enum ForceLockDecision {
- CANCEL,
- RETRY,
- FORCE;
- }
-
@Provides
@LockScoped
- static UserInteractionLock provideForceLockDecisionLock() {
- return new UserInteractionLock<>(null);
+ static AtomicReference> provideForceRetryDecisionRef() {
+ return new AtomicReference<>();
}
@Provides
@@ -48,12 +43,12 @@ abstract class LockModule {
@Provides
@LockWindow
@LockScoped
- static Stage provideWindow(StageFactory factory, @LockWindow Vault vault, @Named("lockWindowOwner") Optional owner) {
+ static Stage provideWindow(StageFactory factory, @LockWindow Vault vault, @Nullable @Named("lockWindowOwner") Stage owner) {
Stage stage = factory.create();
stage.setTitle(vault.getDisplayName());
stage.setResizable(false);
- if (owner.isPresent()) {
- stage.initOwner(owner.get());
+ if (owner != null) {
+ stage.initOwner(owner);
stage.initModality(Modality.WINDOW_MODAL);
} else {
stage.initModality(Modality.APPLICATION_MODAL);
diff --git a/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java b/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java
index 00b25c507..2d4961dde 100644
--- a/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java
+++ b/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java
@@ -5,10 +5,9 @@ import org.cryptomator.common.vaults.LockNotCompletedException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
-import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
-import org.cryptomator.ui.common.UserInteractionLock;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,6 +17,10 @@ import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
/**
* The sequence of actions performed and checked during lock of a vault.
@@ -34,43 +37,48 @@ public class LockWorkflow extends Task {
private final Stage lockWindow;
private final Vault vault;
- private final UserInteractionLock forceLockDecisionLock;
+ private final AtomicReference> forceRetryDecision;
private final Lazy lockForcedScene;
private final Lazy lockFailedScene;
- private final ErrorComponent.Builder errorComponent;
+ private final FxApplicationWindows appWindows;
@Inject
- public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy lockFailedScene, ErrorComponent.Builder errorComponent) {
+ public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, AtomicReference> forceRetryDecision, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy lockFailedScene, FxApplicationWindows appWindows) {
this.lockWindow = lockWindow;
this.vault = vault;
- this.forceLockDecisionLock = forceLockDecisionLock;
+ this.forceRetryDecision = forceRetryDecision;
this.lockForcedScene = lockForcedScene;
this.lockFailedScene = lockFailedScene;
- this.errorComponent = errorComponent;
+ this.appWindows = appWindows;
}
@Override
- protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException {
+ protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException, ExecutionException {
lock(false);
return null;
}
- private void lock(boolean forced) throws InterruptedException {
+ private void lock(boolean forced) throws InterruptedException, ExecutionException {
try {
vault.lock(forced);
} catch (Volume.VolumeException | LockNotCompletedException e) {
LOG.info("Locking {} failed (forced: {}).", vault.getDisplayName(), forced, e);
- var decision = askUserForAction();
- switch (decision) {
- case RETRY -> lock(false);
- case FORCE -> lock(true);
- case CANCEL -> cancel(false);
- }
+ retryOrCancel();
}
}
- private LockModule.ForceLockDecision askUserForAction() throws InterruptedException {
- forceLockDecisionLock.reset(null);
+ private void retryOrCancel() throws ExecutionException, InterruptedException {
+ try {
+ boolean forced = askWhetherToUseTheForce().get();
+ lock(forced);
+ } catch (CancellationException e) {
+ cancel(false);
+ }
+ }
+
+ private CompletableFuture askWhetherToUseTheForce() {
+ var decision = new CompletableFuture();
+ forceRetryDecision.set(decision);
// show forcedLock dialogue ...
Platform.runLater(() -> {
lockWindow.setScene(lockForcedScene.get());
@@ -83,8 +91,7 @@ public class LockWorkflow extends Task {
lockWindow.centerOnScreen();
}
});
- // ... and wait for answer
- return forceLockDecisionLock.awaitInteraction();
+ return decision;
}
@Override
@@ -102,7 +109,7 @@ public class LockWorkflow extends Task {
lockWindow.setScene(lockFailedScene.get());
lockWindow.show();
} else {
- errorComponent.cause(throwable).window(lockWindow).build().showErrorScene();
+ appWindows.showErrorWindow(throwable, lockWindow, null);
}
}
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindow.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindow.java
index 51143e1f6..22f3616ea 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindow.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindow.java
@@ -9,6 +9,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
-public @interface MainWindow {
+@interface MainWindow {
}
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
index 6f63db888..94acba3cc 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
@@ -13,6 +13,8 @@ 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.common.StageInitializer;
+import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.migration.MigrationComponent;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
@@ -34,6 +36,18 @@ import java.util.ResourceBundle;
@Module(subcomponents = {AddVaultWizardComponent.class, HealthCheckComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
abstract class MainWindowModule {
+ @Provides
+ @MainWindow
+ @MainWindowScoped
+ static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) {
+ initializer.accept(stage);
+ stage.setTitle("Cryptomator");
+ stage.initStyle(StageStyle.UNDECORATED);
+ stage.setMinWidth(650);
+ stage.setMinHeight(440);
+ return stage;
+ }
+
@Provides
@MainWindowScoped
static ObjectProperty provideSelectedVault() {
@@ -47,22 +61,11 @@ abstract class MainWindowModule {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
- @Provides
- @MainWindow
- @MainWindowScoped
- static Stage provideStage(StageFactory factory) {
- Stage stage = factory.create(StageStyle.UNDECORATED);
- stage.setMinWidth(650);
- stage.setMinHeight(440);
- stage.setTitle("Cryptomator");
- return stage;
- }
-
@Provides
@MainWindowScoped
@Named("errorWindow")
static Stage provideErrorStage(@MainWindow Stage window, StageFactory factory, ResourceBundle resourceBundle) {
- Stage stage = factory.create(StageStyle.DECORATED);
+ Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("main.vaultDetail.error.windowTitle"));
stage.initModality(Modality.APPLICATION_MODAL);
stage.initOwner(window);
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java
index c8107415e..76eee0cb4 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java
@@ -3,9 +3,9 @@ package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.fxapp.FxApplication;
+import org.cryptomator.ui.fxapp.FxApplicationTerminator;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.UpdateChecker;
-import org.cryptomator.ui.launcher.AppLifecycleListener;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
@@ -25,9 +25,9 @@ public class MainWindowTitleController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
- private final AppLifecycleListener appLifecycle;
private final Stage window;
- private final FxApplication application;
+ private final FxApplicationTerminator terminator;
+ private final FxApplicationWindows appWindows;
private final boolean trayMenuInitialized;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
@@ -40,10 +40,10 @@ public class MainWindowTitleController implements FxController {
private double yOffset;
@Inject
- MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
- this.appLifecycle = appLifecycle;
+ MainWindowTitleController(@MainWindow Stage window, FxApplicationTerminator terminator, FxApplicationWindows appWindows, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
this.window = window;
- this.application = application;
+ this.terminator = terminator;
+ this.appWindows = appWindows;
this.trayMenuInitialized = trayMenu.isInitialized();
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
@@ -96,7 +96,7 @@ public class MainWindowTitleController implements FxController {
if (trayMenuInitialized) {
window.close();
} else {
- appLifecycle.quit();
+ terminator.terminate();
}
}
@@ -107,17 +107,17 @@ public class MainWindowTitleController implements FxController {
@FXML
public void showPreferences() {
- application.showPreferencesWindow(SelectedPreferencesTab.ANY);
+ appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
@FXML
public void showGeneralPreferences() {
- application.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
+ appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
}
@FXML
public void showDonationKeyPreferences() {
- application.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
+ appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
}
/* Getter/Setter */
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java
index 87a419a94..b38710023 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java
@@ -1,7 +1,6 @@
package org.cryptomator.ui.mainwindow;
import com.tobiasdiez.easybind.EasyBind;
-import com.tobiasdiez.easybind.Subscription;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.Animations;
@@ -9,9 +8,9 @@ import org.cryptomator.ui.common.AutoAnimator;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
-import org.cryptomator.ui.fxapp.FxApplication;
import javax.inject.Inject;
+import javafx.application.Application;
import javafx.beans.binding.Binding;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
@@ -22,7 +21,7 @@ import javafx.fxml.FXML;
public class VaultDetailController implements FxController {
private final ReadOnlyObjectProperty vault;
- private final FxApplication application;
+ private final Application application;
private final Binding glyph;
private final BooleanBinding anyVaultSelected;
@@ -33,7 +32,7 @@ public class VaultDetailController implements FxController {
@Inject
- VaultDetailController(ObjectProperty vault, FxApplication application) {
+ VaultDetailController(ObjectProperty vault, Application application) {
this.vault = vault;
this.application = application;
this.glyph = EasyBind.select(vault) //
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java
index 5fee2e6d1..dab1f7a54 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java
@@ -4,8 +4,7 @@ import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.fxapp.FxApplication;
-import org.cryptomator.ui.health.HealthCheckComponent;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
@@ -14,25 +13,23 @@ import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
-import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.stage.Stage;
-import java.util.Optional;
@MainWindowScoped
public class VaultDetailLockedController implements FxController {
private final ReadOnlyObjectProperty vault;
- private final FxApplication application;
+ private final FxApplicationWindows appWindows;
private final VaultOptionsComponent.Builder vaultOptionsWindow;
private final KeychainManager keychain;
private final Stage mainWindow;
private final BooleanExpression passwordSaved;
@Inject
- VaultDetailLockedController(ObjectProperty vault, FxApplication application, VaultOptionsComponent.Builder vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) {
+ VaultDetailLockedController(ObjectProperty vault, FxApplicationWindows appWindows, VaultOptionsComponent.Builder vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) {
this.vault = vault;
- this.application = application;
+ this.appWindows = appWindows;
this.vaultOptionsWindow = vaultOptionsWindow;
this.keychain = keychain;
this.mainWindow = mainWindow;
@@ -45,7 +42,7 @@ public class VaultDetailLockedController implements FxController {
@FXML
public void unlock() {
- application.startUnlockWorkflow(vault.get(), Optional.of(mainWindow));
+ appWindows.startUnlockWorkflow(vault.get(), mainWindow);
}
@FXML
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java
index 22365da7c..6e40d54b3 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java
@@ -1,15 +1,13 @@
package org.cryptomator.ui.mainwindow;
-import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
-import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import javax.inject.Inject;
import javax.inject.Named;
-import javafx.beans.binding.Binding;
import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -18,21 +16,21 @@ import javafx.stage.Stage;
public class VaultDetailUnknownErrorController implements FxController {
private final ObjectProperty vault;
- private final ErrorComponent.Builder errorComponentBuilder;
+ private final FxApplicationWindows appWindows;
private final Stage errorWindow;
private final RemoveVaultComponent.Builder removeVault;
@Inject
- public VaultDetailUnknownErrorController(ObjectProperty vault, ErrorComponent.Builder errorComponentBuilder, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) {
+ public VaultDetailUnknownErrorController(ObjectProperty vault, FxApplicationWindows appWindows, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) {
this.vault = vault;
- this.errorComponentBuilder = errorComponentBuilder;
+ this.appWindows = appWindows;
this.errorWindow = errorWindow;
this.removeVault = removeVault;
}
@FXML
public void showError() {
- errorComponentBuilder.window(errorWindow).cause(vault.get().getLastKnownException()).build().showErrorScene();
+ appWindows.showErrorWindow(vault.get().getLastKnownException(), errorWindow, null);
}
@FXML
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
index 0af909bbc..63d88a6a4 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
@@ -6,7 +6,7 @@ import com.google.common.cache.LoadingCache;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
-import org.cryptomator.ui.fxapp.FxApplication;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import javax.inject.Inject;
@@ -14,22 +14,21 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
-import java.util.Optional;
@MainWindowScoped
public class VaultDetailUnlockedController implements FxController {
private final ReadOnlyObjectProperty vault;
- private final FxApplication application;
+ private final FxApplicationWindows appWindows;
private final VaultService vaultService;
private final Stage mainWindow;
private final LoadingCache vaultStats;
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
@Inject
- public VaultDetailUnlockedController(ObjectProperty vault, FxApplication application, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
+ public VaultDetailUnlockedController(ObjectProperty vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
this.vault = vault;
- this.application = application;
+ this.appWindows = appWindows;
this.vaultService = vaultService;
this.mainWindow = mainWindow;
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
@@ -47,7 +46,7 @@ public class VaultDetailUnlockedController implements FxController {
@FXML
public void lock() {
- application.startLockWorkflow(vault.get(), Optional.of(mainWindow));
+ appWindows.startLockWorkflow(vault.get(), mainWindow);
}
@FXML
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java
index 145618fa8..c9d788b90 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java
@@ -7,7 +7,8 @@ import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.fxapp.FxApplication;
+import org.cryptomator.ui.common.VaultService;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
@@ -18,7 +19,6 @@ import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.EnumSet;
-import java.util.Optional;
import static org.cryptomator.common.vaults.VaultState.Value.*;
@@ -27,7 +27,8 @@ public class VaultListContextMenuController implements FxController {
private final ObservableOptionalValue selectedVault;
private final Stage mainWindow;
- private final FxApplication application;
+ private final FxApplicationWindows appWindows;
+ private final VaultService vaultService;
private final KeychainManager keychain;
private final RemoveVaultComponent.Builder removeVault;
private final VaultOptionsComponent.Builder vaultOptionsWindow;
@@ -38,10 +39,11 @@ public class VaultListContextMenuController implements FxController {
private final Binding selectedVaultLockable;
@Inject
- VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplication application, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) {
+ VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) {
this.selectedVault = EasyBind.wrapNullable(selectedVault);
this.mainWindow = mainWindow;
- this.application = application;
+ this.appWindows = appWindows;
+ this.vaultService = vaultService;
this.keychain = keychain;
this.removeVault = removeVault;
this.vaultOptionsWindow = vaultOptionsWindow;
@@ -74,22 +76,20 @@ public class VaultListContextMenuController implements FxController {
@FXML
public void didClickUnlockVault() {
selectedVault.ifValuePresent(v -> {
- application.startUnlockWorkflow(v, Optional.of(mainWindow));
+ appWindows.startUnlockWorkflow(v, mainWindow);
});
}
@FXML
public void didClickLockVault() {
selectedVault.ifValuePresent(v -> {
- application.startLockWorkflow(v, Optional.of(mainWindow));
+ appWindows.startLockWorkflow(v, mainWindow);
});
}
@FXML
public void didClickRevealVault() {
- selectedVault.ifValuePresent(v -> {
- application.getVaultService().reveal(v);
- });
+ selectedVault.ifValuePresent(vaultService::reveal);
}
// Getter and Setter
diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java
index 191fc7a8f..4799d0325 100644
--- a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java
+++ b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java
@@ -2,9 +2,9 @@ package org.cryptomator.ui.migration;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.fxapp.FxApplication;
import javax.inject.Inject;
+import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -12,13 +12,13 @@ public class MigrationImpossibleController implements FxController {
private static final String HELP_URI = "https://docs.cryptomator.org/en/1.5/help/manual-migration/";
- private final FxApplication fxApplication;
+ private final Application application;
private final Stage window;
private final Vault vault;
@Inject
- MigrationImpossibleController(FxApplication fxApplication, @MigrationWindow Stage window, @MigrationWindow Vault vault) {
- this.fxApplication = fxApplication;
+ MigrationImpossibleController(Application application, @MigrationWindow Stage window, @MigrationWindow Vault vault) {
+ this.application = application;
this.window = window;
this.vault = vault;
}
@@ -30,7 +30,7 @@ public class MigrationImpossibleController implements FxController {
@FXML
public void getMigrationHelp() {
- fxApplication.getHostServices().showDocument(HELP_URI);
+ application.getHostServices().showDocument(HELP_URI);
}
/* Getter/Setters */
diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationModule.java b/src/main/java/org/cryptomator/ui/migration/MigrationModule.java
index 44f6960b1..76f6ac161 100644
--- a/src/main/java/org/cryptomator/ui/migration/MigrationModule.java
+++ b/src/main/java/org/cryptomator/ui/migration/MigrationModule.java
@@ -6,13 +6,13 @@ import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.ui.common.DefaultSceneFactory;
-import org.cryptomator.ui.common.FxmlLoaderFactory;
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.mainwindow.MainWindow;
+import org.cryptomator.ui.fxapp.PrimaryStage;
import javax.inject.Named;
import javax.inject.Provider;
@@ -37,7 +37,7 @@ abstract class MigrationModule {
@Provides
@MigrationWindow
@MigrationScoped
- static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) {
+ static Stage provideStage(StageFactory factory, @PrimaryStage Stage owner, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("migration.title"));
stage.setResizable(false);
diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java b/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java
index 503814b25..c68456523 100644
--- a/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java
+++ b/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java
@@ -12,12 +12,12 @@ import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
-import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,7 +58,7 @@ public class MigrationRunController implements FxController {
private final ScheduledExecutorService scheduler;
private final KeychainManager keychain;
private final ObjectProperty missingCapability;
- private final ErrorComponent.Builder errorComponent;
+ private final FxApplicationWindows appWindows;
private final Lazy startScene;
private final Lazy successScene;
private final Lazy impossibleScene;
@@ -73,14 +73,14 @@ public class MigrationRunController implements FxController {
public NiceSecurePasswordField passwordField;
@Inject
- public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, KeychainManager keychain, @Named("capabilityErrorCause") ObjectProperty missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy impossibleScene, ErrorComponent.Builder errorComponent) {
+ public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, KeychainManager keychain, @Named("capabilityErrorCause") ObjectProperty missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy