Compare commits

..
Author SHA1 Message Date
JaniruTEC 2bad933c85 First prototype: Moved logic of "getCiphertextPath"
Moved logic of "getCiphertextPath" from Vault to VaultState
2023-07-21 17:09:07 +02:00
160 changed files with 1173 additions and 4232 deletions
+1 -2
View File
@@ -43,7 +43,6 @@ body:
- WinFsp (Local Drive) - WinFsp (Local Drive)
- FUSE-T - FUSE-T
- macFUSE - macFUSE
- FUSE
- WebDAV (Windows Explorer) - WebDAV (Windows Explorer)
- WebDAV (AppleScript) - WebDAV (AppleScript)
- WebDAV (gio) - WebDAV (gio)
@@ -96,4 +95,4 @@ body:
id: further-info id: further-info
attributes: attributes:
label: Anything else? label: Anything else?
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue? description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
-24
View File
@@ -1,24 +0,0 @@
version: 2
updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "06:00"
timezone: "UTC"
groups:
maven-dependencies:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/" # even for `.github/workflows`
schedule:
interval: "monthly"
groups:
github-actions:
patterns:
- "*"
labels:
- "misc:ci"
+20 -50
View File
@@ -10,8 +10,7 @@ on:
required: false required: false
env: env:
JAVA_DIST: 'zulu' JAVA_VERSION: 20
JAVA_VERSION: '21.0.1+12'
jobs: jobs:
get-version: get-version:
@@ -21,50 +20,26 @@ jobs:
build: build:
name: Build AppImage name: Build AppImage
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
needs: [get-version] needs: [get-version]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
appimage-suffix: x86_64
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
openjfx-sha: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
- os: [self-hosted, Linux, ARM64]
appimage-suffix: aarch64
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
openjfx-sha: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
check-latest: true java-package: 'jdk+fx'
cache: 'maven' cache: 'maven'
- name: Ensure major jfx version in pom equals in jdk
- name: Download OpenJFX jmods shell: pwsh
id: download-jmods
run: | run: |
curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip $jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
echo "${{ matrix.openjfx-sha }} openjfx-jmods.zip" | shasum -a256 --check $jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
mkdir -p openjfx-jmods if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
- name: Ensure major jfx version in pom and in jmods is the same
run: |
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION})"
exit 1 exit 1
fi }
- name: Set version - name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven - name: Run maven
@@ -78,8 +53,8 @@ jobs:
${JAVA_HOME}/bin/jlink ${JAVA_HOME}/bin/jlink
--verbose --verbose
--output runtime --output runtime
--module-path "${JAVA_HOME}/jmods:openjfx-jmods" --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,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr
--strip-native-commands --strip-native-commands
--no-header-files --no-header-files
--no-man-pages --no-man-pages
@@ -105,7 +80,7 @@ jobs:
--copyright "(C) 2016 - 2023 Skymatic GmbH" --copyright "(C) 2016 - 2023 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
--java-options "--enable-preview" --java-options "--enable-preview"
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64"
--java-options "-Xss5m" --java-options "-Xss5m"
--java-options "-Xmx256m" --java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" --java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
@@ -117,8 +92,7 @@ jobs:
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" --java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" --java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\"" --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.showTrayIcon=false"
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\""
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties --add-launcher Cryptomator-gtk2=launcher-gtk2.properties
--resource-dir dist/linux/resources --resource-dir dist/linux/resources
@@ -129,10 +103,6 @@ jobs:
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.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.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.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg
cp dist/linux/common/org.cryptomator.Cryptomator.tray.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.tray.svg
cp dist/linux/common/org.cryptomator.Cryptomator.tray-unlocked.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.tray-unlocked.svg
cp dist/linux/common/org.cryptomator.Cryptomator.tray.svg Cryptomator.AppDir/usr/share/icons/hicolor/symbolic/apps/org.cryptomator.Cryptomator.tray-symbolic.svg
cp dist/linux/common/org.cryptomator.Cryptomator.tray-unlocked.svg Cryptomator.AppDir/usr/share/icons/hicolor/symbolic/apps/org.cryptomator.Cryptomator.tray-unlocked-symbolic.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.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/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 cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml
@@ -143,7 +113,7 @@ jobs:
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
- name: Download AppImageKit - name: Download AppImageKit
run: | run: |
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${{ matrix.appimage-suffix }}.AppImage -o appimagetool.AppImage curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage
chmod +x appimagetool.AppImage chmod +x appimagetool.AppImage
./appimagetool.AppImage --appimage-extract ./appimagetool.AppImage --appimage-extract
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
@@ -155,8 +125,8 @@ jobs:
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Build AppImage - name: Build AppImage
run: > run: >
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.appimage-suffix }}.AppImage ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-x86_64.AppImage
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${{ matrix.appimage-suffix }}.AppImage.zsync' -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback" --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
- name: Create detached GPG signatures - name: Create detached GPG signatures
run: | run: |
@@ -165,7 +135,7 @@ jobs:
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: appimage-${{ matrix.appimage-suffix }} name: appimage
path: | path: |
cryptomator-*.AppImage cryptomator-*.AppImage
cryptomator-*.AppImage.zsync cryptomator-*.AppImage.zsync
+3 -4
View File
@@ -6,8 +6,7 @@ on:
types: [labeled] types: [labeled]
env: env:
JAVA_DIST: 'zulu' JAVA_VERSION: 20
JAVA_VERSION: 21
defaults: defaults:
run: run:
@@ -18,10 +17,10 @@ jobs:
name: Compile and Test name: Compile and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
cache: 'maven' cache: 'maven'
- name: Cache SonarCloud packages - name: Cache SonarCloud packages
-64
View File
@@ -1,64 +0,0 @@
name: Checks JDK version for minor updates
on:
schedule:
- cron: '0 0 1 * *' # run once a month at the first day of month
env:
JDK_VERSION: '21.0.1+12'
JDK_VENDOR: zulu
jobs:
jdk-current:
name: Check out current version
runs-on: ubuntu-latest
outputs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
steps:
- uses: actions/setup-java@v3
with:
java-version: ${{ env.JDK_VERSION }}
distribution: ${{ env.JDK_VENDOR }}
check-latest: false
- name: Read JAVA_VERSION_DATE and store in env variable
id: get-data
run: |
date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-date=${date}" >> "$GITHUB_OUTPUT"
jdk-latest:
name: Checkout latest jdk version
runs-on: ubuntu-latest
outputs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
jdk-version: ${{ steps.get-data.outputs.jdk-version}}
steps:
- uses: actions/setup-java@v3
with:
java-version: 21
distribution: ${{ env.JDK_VENDOR }}
check-latest: true
- name: Read JAVA_VERSION_DATE and store in env variable
id: get-data
run: |
date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-date=${date}" >> "$GITHUB_OUTPUT"
version=$(cat ${JAVA_HOME}/release | grep "JAVA_RUNTIME_VERSION=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-version=${version}" >> "$GITHUB_OUTPUT"
notify:
name: Notifies for jdk update
runs-on: ubuntu-latest
needs: [jdk-current, jdk-latest]
if: ${{ needs.jdk-latest.outputs.jdk-date }} > ${{ needs.jdk-current.outputs.jdk-date }}
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "JDK update available"
SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ needs.jdk-latest.outputs.jdk-version }}. See https://github.com/cryptomator/cryptomator/wiki/How-to-update-the-build-JDK for instructions."
SLACK_FOOTER: false
MSG_MINIMAL: true
+8 -17
View File
@@ -16,21 +16,16 @@ on:
type: boolean type: boolean
env: env:
JAVA_DIST: 'zulu' JAVA_VERSION: 20
JAVA_VERSION: '21.0.1+12' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.1/openjfx-20.0.1_linux-x64_bin-jmods.zip'
COFFEELIBS_JDK: 21 OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.1/openjfx-20.0.1_linux-aarch64_bin-jmods.zip'
COFFEELIBS_JDK_VERSION: '21.0.1+12-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
jobs: jobs:
build: build:
name: Build Debian Package name: Build Debian Package
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- id: versions - id: versions
name: Get version information name: Get version information
run: | run: |
@@ -44,13 +39,12 @@ jobs:
run: | run: |
sudo add-apt-repository ppa:coffeelibs/openjdk sudo add-apt-repository ppa:coffeelibs/openjdk
sudo apt-get update sudo apt-get update
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }} libgtk2.0-0 sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.JAVA_VERSION }} libgtk2.0-0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
check-latest: true
cache: 'maven' cache: 'maven'
- name: Run maven - name: Run maven
run: mvn -B clean package -Pdependency-check,linux -DskipTests run: mvn -B clean package -Pdependency-check,linux -DskipTests
@@ -58,11 +52,9 @@ jobs:
id: download-jmods id: download-jmods
run: | run: |
curl -L ${{ env.OPENJFX_JMODS_AMD64 }} -o openjfx-amd64.zip curl -L ${{ env.OPENJFX_JMODS_AMD64 }} -o openjfx-amd64.zip
echo "${{ env.OPENJFX_JMODS_AMD64_HASH }} openjfx-amd64.zip" | shasum -a256 --check
mkdir -p jmods/amd64 mkdir -p jmods/amd64
unzip -j openjfx-amd64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/amd64 unzip -j openjfx-amd64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/amd64
curl -L ${{ env.OPENJFX_JMODS_AARCH64 }} -o openjfx-aarch64.zip curl -L ${{ env.OPENJFX_JMODS_AARCH64 }} -o openjfx-aarch64.zip
echo "${{ env.OPENJFX_JMODS_AARCH64_HASH }} openjfx-aarch64.zip" | shasum -a256 --check
mkdir -p jmods/aarch64 mkdir -p jmods/aarch64
unzip -j openjfx-aarch64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/aarch64 unzip -j openjfx-aarch64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/aarch64
- name: Ensure major jfx version in pom and in jmods is the same - name: Ensure major jfx version in pom and in jmods is the same
@@ -99,8 +91,7 @@ jobs:
run: | run: |
cp -r dist/linux/debian/ pkgdir cp -r dist/linux/debian/ pkgdir
export RFC2822_TIMESTAMP=`date --rfc-2822` export RFC2822_TIMESTAMP=`date --rfc-2822`
export DISABLE_UPDATE_CHECK=${{ inputs.dput }} envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM} ${DISABLE_UPDATE_CHECK}' < dist/linux/debian/rules > pkgdir/debian/rules
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
find . -name "*.jar" >> pkgdir/debian/source/include-binaries find . -name "*.jar" >> pkgdir/debian/source/include-binaries
mv pkgdir cryptomator_${{ inputs.ppaver }} mv pkgdir cryptomator_${{ inputs.ppaver }}
@@ -150,4 +141,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
run: | run: |
artifacts=$(ls | grep cryptomator*.deb) artifacts=$(ls | grep cryptomator*.deb)
gh release upload ${{ github.ref_name }} $artifacts gh release upload ${{ github.ref_name }} $artifacts
+3 -9
View File
@@ -2,7 +2,7 @@ name: Update Error Database
on: on:
discussion: discussion:
types: [created, edited, deleted, category_changed, answered, unanswered] types: [created, edited, category_changed, answered, unanswered]
discussion_comment: discussion_comment:
types: [created, edited, deleted] types: [created, edited, deleted]
@@ -12,7 +12,6 @@ jobs:
if: github.event.discussion.category.name == 'Errors' if: github.event.discussion.category.name == 'Errors'
steps: steps:
- name: Query Discussion Data - name: Query Discussion Data
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
id: query-data id: query-data
uses: actions/github-script@v6 uses: actions/github-script@v6
with: with:
@@ -48,13 +47,8 @@ jobs:
- name: Merge Error Code Data - name: Merge Error Code Data
run: | run: |
jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json
if [ ! -z "$DISCUSSION" ] echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json
then jq -s '.[0] * .[1]' original.json new.json > merged.json
echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json
jq -s '.[0] * .[1]' original.json new.json > merged.json
else
cat original.json | jq 'del(.[] | select(.url=="https://github.com/cryptomator/cryptomator/discussions/${{ github.event.discussion.number }}"))' > merged.json
fi
env: env:
DISCUSSION: ${{ steps.query-data.outputs.result }} DISCUSSION: ${{ steps.query-data.outputs.result }}
- name: Patch Gist - name: Patch Gist
+5 -4
View File
@@ -22,8 +22,9 @@ on:
value: ${{ jobs.determine-version.outputs.type }} value: ${{ jobs.determine-version.outputs.type }}
env: env:
JAVA_DIST: 'zulu' JAVA_VERSION: 20
JAVA_VERSION: 21 JAVA_DIST: 'temurin'
JAVA_CACHE: 'maven'
jobs: jobs:
determine-version: determine-version:
@@ -35,7 +36,7 @@ jobs:
revNum: ${{ steps.versions.outputs.revNum }} revNum: ${{ steps.versions.outputs.revNum }}
type: ${{ steps.versions.outputs.type}} type: ${{ steps.versions.outputs.type}}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Java - name: Setup Java
@@ -43,7 +44,7 @@ jobs:
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
cache: 'maven' cache: ${{ env.JAVA_CACHE }}
- id: versions - id: versions
name: Get version information name: Get version information
run: | run: |
+15 -34
View File
@@ -8,15 +8,9 @@ on:
version: version:
description: 'Version' description: 'Version'
required: false required: false
notarize:
description: 'Notarize'
required: true
default: false
type: boolean
env: env:
JAVA_DIST: 'zulu' JAVA_VERSION: 20
JAVA_VERSION: '21.0.1+12'
jobs: jobs:
get-version: get-version:
@@ -37,45 +31,31 @@ jobs:
output-suffix: x64 output-suffix: x64
xcode-path: '/Applications/Xcode_13.2.1.app' xcode-path: '/Applications/Xcode_13.2.1.app'
fuse-lib: macFUSE fuse-lib: macFUSE
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-x64_bin-jmods.zip'
openjfx-sha: '55b8ff7453d59c89ae129f6c9c5ad7b09a5d359568811b376ac1766c14d6a17c'
- os: [self-hosted, macOS, ARM64] - os: [self-hosted, macOS, ARM64]
architecture: aarch64 architecture: aarch64
output-suffix: arm64 output-suffix: arm64
xcode-path: '/Applications/Xcode_13.2.1.app' xcode-path: '/Applications/Xcode_13.2.1.app'
fuse-lib: FUSE-T fuse-lib: FUSE-T
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip'
openjfx-sha: 'c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
java-package: 'jdk+fx'
architecture: ${{ matrix.architecture }} architecture: ${{ matrix.architecture }}
check-latest: true
cache: 'maven' cache: 'maven'
- name: Download OpenJFX jmods - name: Ensure major jfx version in pom equals in jdk
id: download-jmods if: ${{ !contains(matrix.os, 'self-hosted') }}
shell: pwsh
run: | run: |
curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip $jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check $jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
mkdir -p openjfx-jmods/ if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
- name: Ensure major jfx version in pom and in jmods is the same
run: |
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
exit 1 exit 1
fi }
- name: Set version - name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven - name: Run maven
@@ -89,7 +69,7 @@ jobs:
${JAVA_HOME}/bin/jlink ${JAVA_HOME}/bin/jlink
--verbose --verbose
--output runtime --output runtime
--module-path "${JAVA_HOME}/jmods:openjfx-jmods" --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,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--strip-native-commands --strip-native-commands
--no-header-files --no-header-files
@@ -222,12 +202,13 @@ jobs:
--app-drop-link 512 245 --app-drop-link 512 245
--eula "dist/mac/dmg/resources/license.rtf" --eula "dist/mac/dmg/resources/license.rtf"
--icon ".background" 128 758 --icon ".background" 128 758
--icon ".fseventsd" 320 758
--icon ".VolumeIcon.icns" 512 758 --icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
env: env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }} VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
- name: Notarize .dmg - name: Notarize .dmg
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize if: startsWith(github.ref, 'refs/tags/')
uses: cocoalibs/xcode-notarization-action@v1 uses: cocoalibs/xcode-notarization-action@v1
with: with:
app-path: 'Cryptomator-*.dmg' app-path: 'Cryptomator-*.dmg'
+3 -4
View File
@@ -4,8 +4,7 @@ on:
pull_request: pull_request:
env: env:
JAVA_DIST: 'zulu' JAVA_VERSION: 20
JAVA_VERSION: 21
defaults: defaults:
run: run:
@@ -17,10 +16,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
cache: 'maven' cache: 'maven'
- name: Build and Test - name: Build and Test
+4 -1
View File
@@ -6,6 +6,9 @@ on:
- 'release/**' - 'release/**'
- 'hotfix/**' - 'hotfix/**'
env:
JAVA_VERSION: 20
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -15,7 +18,7 @@ jobs:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- id: validate-pom-version - id: validate-pom-version
name: Validate POM version name: Validate POM version
run: | run: |
+23 -59
View File
@@ -10,16 +10,15 @@ on:
required: false required: false
isDebug: isDebug:
description: 'Build debug version with console output' description: 'Build debug version with console output'
type: boolean type: boolean
env: env:
JAVA_DIST: 'zulu' JAVA_VERSION: 20
JAVA_VERSION: '21.0.1+12' JAVA_DIST: 'temurin'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip' JAVA_CACHE: 'maven'
OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77' JFX_JMODS_URL: 'https://download2.gluonhq.com/openjfx/20.0.1/openjfx-20.0.1_windows-x64_bin-jmods.zip'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi' JFX_JMODS_HASH: 'D00767334C43B8832B5CF10267D34CA8F563D187C4655B73EB6020DD79C054B5'
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe'
defaults: defaults:
run: run:
@@ -39,20 +38,20 @@ jobs:
LOOPBACK_ALIAS: 'cryptomator-vault' LOOPBACK_ALIAS: 'cryptomator-vault'
WIN_CONSOLE_FLAG: '' WIN_CONSOLE_FLAG: ''
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
check-latest: true java-package: 'jdk'
cache: 'maven' cache: ${{ env.JAVA_CACHE }}
- name: Download and extract JavaFX jmods from Gluon - name: Download and extract JavaFX jmods from Gluon
#In the last step we move all jmods files a dir level up because jmods are placed inside a directory in the zip #In the last step we move all jmods files a dir level up because jmods are placed inside a directory in the zip
run: | run: |
curl --output jfxjmods.zip -L "${{ env.OPENJFX_JMODS_AMD64 }}" curl --output jfxjmods.zip -L "${{ env.JFX_JMODS_URL }}"
if(!(Get-FileHash -Path jfxjmods.zip -Algorithm SHA256).Hash.ToLower().equals("${{ env.OPENJFX_JMODS_AMD64_HASH }}")) { if(!(Get-FileHash -Path jfxjmods.zip -Algorithm SHA256).Hash.equals("${{ env.JFX_JMODS_HASH }}")) {
throw "Wrong checksum of JMOD archive downloaded from ${{ env.OPENJFX_JMODS_AMD64 }}."; throw "Wrong checksum of JMOD archive downloaded from ${{ env.JFX_JMODS_URL }}.";
} }
Expand-Archive -Path jfxjmods.zip -DestinationPath jfxjmods Expand-Archive -Path jfxjmods.zip -DestinationPath jfxjmods
Get-ChildItem -Path jfxjmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent} Get-ChildItem -Path jfxjmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent}
@@ -145,29 +144,9 @@ jobs:
- name: Fix permissions - name: Fix permissions
run: attrib -r appdir/Cryptomator/Cryptomator.exe run: attrib -r appdir/Cryptomator/Cryptomator.exe
shell: pwsh shell: pwsh
- name: Extract jars with DLLs for Codesigning - name: Extract integrations DLL for code signing
shell: pwsh shell: pwsh
run: | run: gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --extract integrations.dll }
Add-Type -AssemblyName "System.io.compression.filesystem"
$jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods"
$jarExtractDir = New-Item -Path ".\appdir\jar-extract" -ItemType Directory
#for all jars inspect
Get-ChildItem -Path $jarFolder -Filter "*.jar" | ForEach-Object {
$jar = [Io.compression.zipfile]::OpenRead($_.FullName)
if (@($jar.Entries | Where-Object {$_.Name.ToString().EndsWith(".dll")} | Select-Object -First 1).Count -gt 0) {
#jars containing dlls extract
Set-Location $jarExtractDir
Expand-Archive -Path $_.FullName
}
$jar.Dispose()
}
- name: Extract wixhelper.dll for Codesigning #see https://github.com/cryptomator/cryptomator/issues/3130
shell: pwsh
run: |
New-Item -Path appdir/jpackage-jmod -ItemType Directory
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
- name: Codesign - name: Codesign
uses: skymatic/code-sign-action@v2 uses: skymatic/code-sign-action@v2
with: with:
@@ -176,22 +155,12 @@ jobs:
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
description: Cryptomator description: Cryptomator
timestampUrl: 'http://timestamp.digicert.com' timestampUrl: 'http://timestamp.digicert.com'
folder: appdir folder: appdir/Cryptomator
recursive: true recursive: true
- name: Replace DLLs inside jars with signed ones - name: Repack signed DLL into jar
shell: pwsh shell: pwsh
run: | run: |
$jarExtractDir = Resolve-Path ".\appdir\jar-extract" gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --update integrations.dll; Remove-Item integrations.dll}
$jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods"
Get-ChildItem -Path $jarExtractDir | ForEach-Object {
$jarName = $_.Name
$jarFile = "${jarFolder}\${jarName}.jar"
Set-Location $_
Get-ChildItem -Path $_ -Recurse -File "*.dll" | ForEach-Object {
# update jar with signed dll
jar --file="$jarFile" --update $(Resolve-Path -Relative -Path $_)
}
}
- name: Generate license for MSI - name: Generate license for MSI
run: > run: >
mvn -B license:add-third-party mvn -B license:add-third-party
@@ -225,7 +194,6 @@ jobs:
--file-associations dist/win/resources/FAvaultFile.properties --file-associations dist/win/resources/FAvaultFile.properties
env: env:
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
- name: Codesign MSI - name: Codesign MSI
uses: skymatic/code-sign-action@v2 uses: skymatic/code-sign-action@v2
with: with:
@@ -267,7 +235,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
needs: [get-version, build-msi] needs: [get-version, build-msi]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Download .msi - name: Download .msi
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -279,8 +247,7 @@ jobs:
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
check-latest: true cache: ${{ env.JAVA_CACHE }}
cache: 'maven'
- name: Generate license for exe - name: Generate license for exe
run: > run: >
mvn -B license:add-third-party mvn -B license:add-third-party
@@ -294,11 +261,8 @@ jobs:
shell: pwsh shell: pwsh
- name: Download WinFsp - name: Download WinFsp
run: | run: |
curl --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }} $winfspUrl = (Select-String -Path ".\dist\win\bundle\resources\winFspMetaData.wxi" -Pattern '<\?define BundledWinFspDownloadLink="(.+)".*?>').Matches.Groups[1].Value
shell: pwsh curl --output dist/win/bundle/resources/winfsp.msi -L $winfspUrl
- name: Download Legacy-WinFsp uninstaller
run: |
curl --output dist/win/bundle/resources/winfsp-uninstaller.exe -L ${{ env.WINFSP_UNINSTALLER }}
shell: pwsh shell: pwsh
- name: Compile to wixObj file - name: Compile to wixObj file
run: > run: >
@@ -395,7 +359,7 @@ jobs:
cp msi/*.msi files cp msi/*.msi files
cp exe/*.exe files cp exe/*.exe files
- name: Upload to Kaspersky - name: Upload to Kaspersky
uses: SamKirkland/FTP-Deploy-Action@v4.3.4 uses: SamKirkland/FTP-Deploy-Action@4.3.3
with: with:
protocol: ftps protocol: ftps
server: allowlist.kaspersky-labs.com server: allowlist.kaspersky-labs.com
@@ -404,7 +368,7 @@ jobs:
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }} password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
local-dir: files/ local-dir: files/
- name: Upload to Avast - name: Upload to Avast
uses: SamKirkland/FTP-Deploy-Action@v4.3.4 uses: SamKirkland/FTP-Deploy-Action@4.3.0
with: with:
protocol: ftp protocol: ftp
server: whitelisting.avast.com server: whitelisting.avast.com
+12 -11
View File
@@ -14,10 +14,10 @@
<option name="dagger.fastInit" value="enabled" /> <option name="dagger.fastInit" value="enabled" />
<option name="dagger.formatGeneratedSource" value="enabled" /> <option name="dagger.formatGeneratedSource" value="enabled" />
<processorPath useClasspath="false"> <processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.48.1/dagger-compiler-2.48.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.45/dagger-compiler-2.45.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.48.1/dagger-2.48.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.45/dagger-2.45.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" /> <entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.48.1/dagger-producers-2.48.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.45/dagger-producers-2.45.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
@@ -26,19 +26,20 @@
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" /> <entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.48.1/dagger-spi-2.48.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.45/dagger-spi-2.45.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.9.0-1.0.12/symbol-processing-api-1.9.0-1.0.12.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.7.0-1.0.6/symbol-processing-api-1.7.0-1.0.6.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar" /> <entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.0/kotlin-stdlib-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0.jar" /> <entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.0/kotlin-stdlib-common-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" /> <entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" /> <entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.0/kotlin-stdlib-jdk8-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.0/kotlin-stdlib-jdk7-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" /> <entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.5.0/kotlinx-metadata-jvm-0.5.0.jar" />
</processorPath> </processorPath>
<module name="cryptomator" /> <module name="cryptomator" />
</profile> </profile>
+1 -1
View File
@@ -8,7 +8,7 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21_PREVIEW" project-jdk-name="21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_20_PREVIEW" project-jdk-name="20" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application"> <configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" /> <option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application"> <configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" /> <option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+8 -14
View File
@@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
set -e
cd $(dirname $0) cd $(dirname $0)
REVISION_NO=`git rev-list --count HEAD` REVISION_NO=`git rev-list --count HEAD`
@@ -11,13 +10,13 @@ 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) VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
SEMVER_STR=${VERSION} SEMVER_STR=${VERSION}
MACHINE_TYPE=$(uname -m)
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR} mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
# compile # compile
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
cp ../../../LICENSE.txt ../../../target cp ../../../LICENSE.txt ../../../target
cp ../launcher.sh ../../../target
cp ../../../target/cryptomator-*.jar ../../../target/mods cp ../../../target/cryptomator-*.jar ../../../target/mods
# add runtime # add runtime
@@ -25,7 +24,7 @@ ${JAVA_HOME}/bin/jlink \
--verbose \ --verbose \
--output runtime \ --output runtime \
--module-path "${JAVA_HOME}/jmods" \ --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,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
--strip-native-commands \ --strip-native-commands \
--no-header-files \ --no-header-files \
--no-man-pages \ --no-man-pages \
@@ -45,7 +44,7 @@ ${JAVA_HOME}/bin/jpackage \
--name Cryptomator \ --name Cryptomator \
--vendor "Skymatic GmbH" \ --vendor "Skymatic GmbH" \
--java-options "--enable-preview" \ --java-options "--enable-preview" \
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" \
--copyright "(C) 2016 - 2023 Skymatic GmbH" \ --copyright "(C) 2016 - 2023 Skymatic GmbH" \
--java-options "-Xss5m" \ --java-options "-Xss5m" \
--java-options "-Xmx256m" \ --java-options "-Xmx256m" \
@@ -58,8 +57,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \ --java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \ --java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.showTrayIcon=true" \ --java-options "-Dcryptomator.showTrayIcon=false" \
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \ --java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \
--add-launcher cryptomator-gtk2=launcher-gtk2.properties \ --add-launcher cryptomator-gtk2=launcher-gtk2.properties \
--resource-dir ../resources --resource-dir ../resources
@@ -71,10 +69,6 @@ envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.Ap
cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png 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.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.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg
cp ../common/org.cryptomator.Cryptomator.tray.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.tray.svg
cp ../common/org.cryptomator.Cryptomator.tray-unlocked.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.tray-unlocked.svg
cp ../common/org.cryptomator.Cryptomator.tray.svg Cryptomator.AppDir/usr/share/icons/hicolor/symbolic/apps/org.cryptomator.Cryptomator.tray-symbolic.svg
cp ../common/org.cryptomator.Cryptomator.tray-unlocked.svg Cryptomator.AppDir/usr/share/icons/hicolor/symbolic/apps/org.cryptomator.Cryptomator.tray-unlocked-symbolic.svg
cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop 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/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 cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml
@@ -85,17 +79,17 @@ ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.App
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
# load AppImageTool # load AppImageTool
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${MACHINE_TYPE}.AppImage -o /tmp/appimagetool.AppImage curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /tmp/appimagetool.AppImage
chmod +x /tmp/appimagetool.AppImage chmod +x /tmp/appimagetool.AppImage
# create AppImage # create AppImage
/tmp/appimagetool.AppImage \ /tmp/appimagetool.AppImage \
Cryptomator.AppDir \ Cryptomator.AppDir \
cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage \ cryptomator-${SEMVER_STR}-x86_64.AppImage \
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${MACHINE_TYPE}.AppImage.zsync' -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
echo "" echo ""
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage" echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-x86_64.AppImage"
echo "" echo ""
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage" echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
echo "" echo ""
@@ -66,11 +66,6 @@
</content_rating> </content_rating>
<releases> <releases>
<release date="2023-09-20" version="1.10.1"/>
<release date="2023-09-11" version="1.10.0"/>
<release date="2023-08-11" version="1.9.4"/>
<release date="2023-08-07" version="1.9.3"/>
<release date="2023-07-24" version="1.9.2"/>
<release date="2023-06-07" version="1.9.1"/> <release date="2023-06-07" version="1.9.1"/>
<release date="2023-05-30" version="1.9.0"/> <release date="2023-05-30" version="1.9.0"/>
<release date="2023-04-25" version="1.8.0"/> <release date="2023-04-25" version="1.8.0"/>
@@ -1,12 +0,0 @@
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
<style
id="current-color-scheme" type="text/css">
.ColorScheme-Text {
color:#232629;
}
</style>
<g fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1" class="ColorScheme-Text" fill="currentColor">
<path d="m15.591 35.824c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.043c.051 1.03-.161 2.644-.509 2.834zm6.409-6.824h-2l.5-5a2 2 0 1 1 1 0zm-14.544-3.241.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm13.544-10.759a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm14.473 6a8.067 8.067 0 0 0 -8.08 8v2.141a3.891 3.891 0 0 0 -2.893 3.734v5.125a23.166 23.166 0 0 1 -4.174-1.623 7.857 7.857 0 0 1 -.027.878 3.263 3.263 0 0 1 -.729 2.074l-1.794 1.483a.379.379 0 0 1 -.276.188h-4c-1.324 0-2.346-1.336-2.653-3.343a7.058 7.058 0 0 1 .234-3.18 3.477 3.477 0 0 1 1.636-2.157 1.868 1.868 0 0 1 .783-.32h1.5a8.035 8.035 0 0 1 -1.5-5 11.1 11.1 0 0 1 .5-3 2.519 2.519 0 0 0 0-1.5 13.272 13.272 0 0 1 -.5-3.5c6.687-1.936 11 0 11 0s4.319-1.955 11 0"/>
<path d="m39 28h-10v-4a3.13 3.13 0 0 1 3-3 3.087 3.087 0 0 1 3 3v1a1.034 1.034 0 0 0 1 1h1a1.034 1.034 0 0 0 1-1v-1a6 6 0 0 0 -12 0v4h-1a2.073 2.073 0 0 0 -2 2v6a2.073 2.073 0 0 0 2 2h14a2.073 2.073 0 0 0 2-2v-6a2.073 2.073 0 0 0 -2-2zm-5.391 5.94a1.609 1.609 0 0 1 -3.217 0v-1.876a1.609 1.609 0 0 1 3.217 0z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

@@ -1,8 +0,0 @@
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text {
color:#232629;
}
</style>
<path d="m32.66 29.319a1.432 1.432 0 0 0 -.66-.319h-1.5a8.125 8.125 0 0 0 1.5-5 11.027 11.027 0 0 0 -.5-3 2.519 2.519 0 0 1 0-1.5 12.987 12.987 0 0 0 .5-3.5c-6.681-1.955-11 0-11 0s-4.313-1.936-11 0a13.272 13.272 0 0 0 .5 3.5 2.519 2.519 0 0 1 0 1.5 11.1 11.1 0 0 0 -.5 3 8.035 8.035 0 0 0 1.5 5h-1.5a1.868 1.868 0 0 0 -.783.319 3.477 3.477 0 0 0 -1.636 2.157 7.058 7.058 0 0 0 -.234 3.18c.307 2.008 1.329 3.344 2.653 3.344h4a.379.379 0 0 0 .277-.187l1.793-1.483a3.263 3.263 0 0 0 .729-2.074 7.857 7.857 0 0 0 .027-.878 23.166 23.166 0 0 0 4.174 1.622 24.4 24.4 0 0 0 4.051-1.614 7.848 7.848 0 0 0 .027.869 3.263 3.263 0 0 0 .729 2.074l1.793 1.484a.61.61 0 0 0 .4.187h4c1.324 0 2.223-1.336 2.529-3.343a7.057 7.057 0 0 0 -.234-3.18 3.477 3.477 0 0 0 -1.635-2.158zm-17.069 6.5c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.048c.051 1.03-.161 2.644-.509 2.834zm6.409-6.819h-2l.5-5a2 2 0 1 1 1 0zm6.38 7.921a.418.418 0 0 1 -.627.111c-.522-.433-1.439-1.2-1.458-1.208-.348-.189-.56-1.8-.505-2.828a15.84 15.84 0 0 0 2.9-2.037 9.322 9.322 0 0 1 -.31 5.962zm-20.924-11.162.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm33.217 1.2a3.021 3.021 0 0 0 -2.658-1.525 1.574 1.574 0 0 0 -.107-1.283l-.745-1.367a1.779 1.779 0 0 0 -1.317-.891l-.7-1.278a2.409 2.409 0 0 0 .229-2.661 2.283 2.283 0 0 0 -2.375-1.454 7.039 7.039 0 0 1 0 3 3.272 3.272 0 0 0 0 1.5c.047.152-.047.3 0 .5.227.04-.069.156.165.14l.653 1.2a1.579 1.579 0 0 0 -.019 1.557l.745 1.367a1.753 1.753 0 0 0 1.045.832 2.66 2.66 0 0 0 -.238 2.916 2.989 2.989 0 0 0 2.326 1.509.79.79 0 0 0 .082 0 .707.707 0 0 0 .717-.6.688.688 0 0 0 -.636-.749 1.546 1.546 0 0 1 -1.2-.78 1.408 1.408 0 0 1 .672-1.95 1.628 1.628 0 0 1 1.179-.089 1.512 1.512 0 0 1 .9.719 1.37 1.37 0 0 1 -.023 1.361.656.656 0 0 0 .284.92.748.748 0 0 0 .981-.266 2.648 2.648 0 0 0 .04-2.633zm-19.673-11.959a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2z" fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1" class="ColorScheme-Text" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

+1 -1
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org> Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils Section: utils
Priority: optional Priority: optional
Build-Depends: debhelper (>=10), coffeelibs-jdk-21 (= 21.0.1+12-0ppa1), libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1 Build-Depends: debhelper (>=10), coffeelibs-jdk-20, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
Standards-Version: 4.5.0 Standards-Version: 4.5.0
Homepage: https://cryptomator.org Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git Vcs-Git: https://github.com/cryptomator/cryptomator.git
-2
View File
@@ -1,8 +1,6 @@
cryptomator usr/lib cryptomator usr/lib
common/org.cryptomator.Cryptomator.desktop usr/share/applications common/org.cryptomator.Cryptomator.desktop usr/share/applications
common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps
common/org.cryptomator.Cryptomator.tray.svg usr/share/icons/hicolor/scalable/apps
common/org.cryptomator.Cryptomator.tray-unlocked.svg usr/share/icons/hicolor/scalable/apps
common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/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.Cryptomator512.png usr/share/icons/hicolor/512x512/apps
common/org.cryptomator.Cryptomator.metainfo.xml usr/share/metainfo common/org.cryptomator.Cryptomator.metainfo.xml usr/share/metainfo
-2
View File
@@ -1,3 +1 @@
usr/lib/cryptomator/bin/cryptomator usr/bin/cryptomator usr/lib/cryptomator/bin/cryptomator usr/bin/cryptomator
usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.tray.svg usr/share/icons/hicolor/symbolic/apps/org.cryptomator.Cryptomator.tray-symbolic.svg
usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.tray-unlocked.svg usr/share/icons/hicolor/symbolic/apps/org.cryptomator.Cryptomator.tray-unlocked-symbolic.svg
+4 -6
View File
@@ -4,7 +4,7 @@
# Uncomment this to turn on verbose mode. # Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1 #export DH_VERBOSE=1
JAVA_HOME = /usr/lib/jvm/java-21-coffeelibs JAVA_HOME = /usr/lib/jvm/java-20-coffeelibs
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH) DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
ifeq ($(DEB_BUILD_ARCH),amd64) ifeq ($(DEB_BUILD_ARCH),amd64)
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
@@ -27,7 +27,7 @@ override_dh_auto_build:
$(JAVA_HOME)/bin/jlink \ $(JAVA_HOME)/bin/jlink \
--output runtime \ --output runtime \
--module-path "${JMODS_PATH}" \ --module-path "${JMODS_PATH}" \
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
--strip-native-commands \ --strip-native-commands \
--no-header-files \ --no-header-files \
--no-man-pages \ --no-man-pages \
@@ -43,7 +43,7 @@ override_dh_auto_build:
--name cryptomator \ --name cryptomator \
--vendor "Skymatic GmbH" \ --vendor "Skymatic GmbH" \
--java-options "--enable-preview" \ --java-options "--enable-preview" \
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" \
--copyright "(C) 2016 - 2023 Skymatic GmbH" \ --copyright "(C) 2016 - 2023 Skymatic GmbH" \
--java-options "-Xss5m" \ --java-options "-Xss5m" \
--java-options "-Xmx256m" \ --java-options "-Xmx256m" \
@@ -55,11 +55,9 @@ override_dh_auto_build:
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \ --java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \ --java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.showTrayIcon=true" \ --java-options "-Dcryptomator.showTrayIcon=false" \
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"/usr/share/icons/hicolor/symbolic/apps\"" \
--java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \ --java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \
--java-options "-Dcryptomator.disableUpdateCheck=\"${DISABLE_UPDATE_CHECK}\"" \
--app-version "${VERSION_NUM}.${REVISION_NUM}" \ --app-version "${VERSION_NUM}.${REVISION_NUM}" \
--resource-dir resources \ --resource-dir resources \
--verbose --verbose
+3 -27
View File
@@ -29,14 +29,6 @@ REVISION_NO=`git rev-list --count HEAD`
VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'` VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'`
FUSE_LIB="FUSE-T" FUSE_LIB="FUSE-T"
ARCH="undefined"
if [ "$(machine)" = "arm64e" ]; then
ARCH="aarch64"
else
ARCH="x64"
fi
OPENJFX_JMODS="https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-${ARCH}_bin-jmods.zip"
# check preconditions # check preconditions
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found. Fix by 'brew install maven'."; exit 1; } command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found. Fix by 'brew install maven'."; exit 1; }
@@ -46,32 +38,15 @@ if [ -n "${CODESIGN_IDENTITY}" ]; then
if [[ ! `security find-identity -v -p codesigning | grep -w "${CODESIGN_IDENTITY}"` ]]; then echo "Given codesign identity is invalid."; exit 1; fi if [[ ! `security find-identity -v -p codesigning | grep -w "${CODESIGN_IDENTITY}"` ]]; then echo "Given codesign identity is invalid."; exit 1; fi
fi fi
# download and check jmods
curl -L ${OPENJFX_JMODS} -o openjfx-jmods.zip
mkdir -p openjfx-jmods/
unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn -f../../../pom.xml help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
exit 1
fi
# compile # compile
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
cp ../../../LICENSE.txt ../../../target
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
# add runtime # add runtime
${JAVA_HOME}/bin/jlink \ ${JAVA_HOME}/bin/jlink \
--output runtime \ --output runtime \
--module-path "${JAVA_HOME}/jmods:openjfx-jmods" \ --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,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
--strip-native-commands \ --strip-native-commands \
--no-header-files \ --no-header-files \
--no-man-pages \ --no-man-pages \
@@ -169,5 +144,6 @@ create-dmg \
--app-drop-link 512 245 \ --app-drop-link 512 245 \
--eula "resources/license.rtf" \ --eula "resources/license.rtf" \
--icon ".background" 128 758 \ --icon ".background" 128 758 \
--icon ".fseventsd" 320 758 \
--icon ".VolumeIcon.icns" 512 758 \ --icon ".VolumeIcon.icns" 512 758 \
${APP_NAME}-${VERSION_NO}.dmg dmg ${APP_NAME}-${VERSION_NO}.dmg dmg
Binary file not shown.
-2
View File
@@ -4,6 +4,4 @@ installer
*.wixobj *.wixobj
*.pdb *.pdb
*.msi *.msi
*.exe
*.jmod
license.rtf license.rtf
+1 -1
View File
@@ -11,7 +11,7 @@ SET HELP_URL="https://cryptomator.org/contact/"
SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator" SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
SET LOOPBACK_ALIAS="cryptomator-vault" SET LOOPBACK_ALIAS="cryptomator-vault"
powershell -NoLogo -NoProfile -ExecutionPolicy Unrestricted -Command .\build.ps1^ powershell -NoLogo -ExecutionPolicy Unrestricted -Command .\build.ps1^
-AppName %APPNAME%^ -AppName %APPNAME%^
-MainJarGlob "%MAIN_JAR_GLOB%"^ -MainJarGlob "%MAIN_JAR_GLOB%"^
-ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^ -ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^
+6 -15
View File
@@ -51,11 +51,10 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
} }
## download jfx jmods ## download jfx jmods
$jmodsVersion='20.0.2' $jfxJmodsChecksum = 'd00767334c43b8832b5cf10267d34ca8f563d187c4655b73eb6020dd79c054b5'
$jmodsUrl = "https://download2.gluonhq.com/openjfx/${jmodsVersion}/openjfx-${jmodsVersion}_windows-x64_bin-jmods.zip"
$jfxJmodsChecksum = '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
$jfxJmodsZip = '.\resources\jfxJmods.zip' $jfxJmodsZip = '.\resources\jfxJmods.zip'
if( !(Test-Path -Path $jfxJmodsZip) ) { if( !(Test-Path -Path $jfxJmodsZip) ) {
$jmodsUrl = "https://download2.gluonhq.com/openjfx/20.0.1/openjfx-20.0.1_windows-x64_bin-jmods.zip"
Write-Output "Downloading ${jmodsUrl}..." Write-Output "Downloading ${jmodsUrl}..."
Invoke-WebRequest $jmodsUrl -OutFile $jfxJmodsZip # redirects are followed by default Invoke-WebRequest $jmodsUrl -OutFile $jfxJmodsZip # redirects are followed by default
} }
@@ -63,17 +62,15 @@ if( !(Test-Path -Path $jfxJmodsZip) ) {
$jmodsChecksumActual = $(Get-FileHash -Path $jfxJmodsZip -Algorithm SHA256).Hash $jmodsChecksumActual = $(Get-FileHash -Path $jfxJmodsZip -Algorithm SHA256).Hash
if( $jmodsChecksumActual -ne $jfxJmodsChecksum ) { if( $jmodsChecksumActual -ne $jfxJmodsChecksum ) {
Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual" Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual"
exit 1; exit 1;
} }
Expand-Archive -Path $jfxJmodsZip -Force -DestinationPath ".\resources\" Expand-Archive -Force -Path $jfxJmodsZip -DestinationPath ".\resources\"
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods"
Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
& "$Env:JAVA_HOME\bin\jlink" ` & "$Env:JAVA_HOME\bin\jlink" `
--verbose ` --verbose `
--output runtime ` --output runtime `
--module-path "$Env:JAVA_HOME/jmods;$buildDir/resources/javafx-jmods" ` --module-path "$Env:JAVA_HOME/jmods;$buildDir/resources/javafx-jmods-20.0.1" `
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,javafx.base,javafx.graphics,javafx.controls,javafx.fxml `
--strip-native-commands ` --strip-native-commands `
--no-header-files ` --no-header-files `
@@ -144,7 +141,6 @@ try {
# create .msi # create .msi
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
$Env:JP_WIXHELPER_DIR = "."
& "$Env:JAVA_HOME\bin\jpackage" ` & "$Env:JAVA_HOME\bin\jpackage" `
--verbose ` --verbose `
--type msi ` --type msi `
@@ -176,15 +172,10 @@ $Env:JP_WIXHELPER_DIR = "."
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges" "-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges"
# download Winfsp # download Winfsp
$winfspMsiUrl= 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi' $winfspMsiUrl= (Select-String -Path ".\bundle\resources\winFspMetaData.wxi" -Pattern '<\?define BundledWinFspDownloadLink="(.+)".*?>').Matches.Groups[1].Value
Write-Output "Downloading ${winfspMsiUrl}..." Write-Output "Downloading ${winfspMsiUrl}..."
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
# download legacy-winfsp uninstaller
$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe'
Write-Output "Downloading ${winfspUninstaller}..."
Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default
# copy MSI to bundle resources # copy MSI to bundle resources
Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi" Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi"
+20 -25
View File
@@ -1,6 +1,5 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- For Built in variables, see https://wixtoolset.org/docs/tools/burn/builtin-variables/-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/bundle.html--> <!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/bundle.html-->
<!-- Attributes explicitly not used: <!-- Attributes explicitly not used:
@@ -11,10 +10,21 @@
AboutUrl="$(var.AboutUrl)" HelpUrl="$(var.HelpUrl)" UpdateUrl="$(var.UpdateUrl)" Copyright="$(var.BundleCopyright)" IconSourceFile="bundle\resources\Cryptomator.ico"> AboutUrl="$(var.AboutUrl)" HelpUrl="$(var.HelpUrl)" UpdateUrl="$(var.UpdateUrl)" Copyright="$(var.BundleCopyright)" IconSourceFile="bundle\resources\Cryptomator.ico">
<!-- detect outdated WinFsp installations --> <!-- detect outdated WinFsp installations -->
<?include "resources\winFspMetaData.wxi" ?>
<util:ProductSearch <util:ProductSearch
Variable="InstalledLegacyWinFspVersion" Variable="InstalledWinFspVersion"
Result="version" Result="version"
UpgradeCode="82F812D9-4083-4EF1-8BC8-0F1EDA05B46B"/> UpgradeCode="82F812D9-4083-4EF1-8BC8-0F1EDA05B46B"
/>
<!-- Note: The bundle engine takes the Message format literaly -->
<bal:Condition Message=
"The WinFsp driver used by Cryptomator is outdated and must be removed before the installation.
1. Open the view of installed apps
2. Search for &quot;WinFsp&quot;
3. Uninstall the listed application
4. Reboot your device
5. Restart this installer">(InstalledWinFspVersion = v0.0.0.0) OR ($(var.BundledWinFspVersion) &lt;= InstalledWinFspVersion)</bal:Condition>
<!-- for definition of the standard themes, see https://github.com/wixtoolset/wix3/blob/master/src/ext/BalExtension/wixstdba/Resources/--> <!-- for definition of the standard themes, see https://github.com/wixtoolset/wix3/blob/master/src/ext/BalExtension/wixstdba/Resources/-->
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLargeLicense"> <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLargeLicense">
@@ -26,41 +36,26 @@
SuppressOptionsUI="yes" SuppressOptionsUI="yes"
ThemeFile="bundle\customBootstrapperTheme.xml" ThemeFile="bundle\customBootstrapperTheme.xml"
LocalizationFile="bundle\customBootstrapperTheme.wxl" LocalizationFile="bundle\customBootstrapperTheme.wxl"
LogoFile="bundle\resources\logo.png"/> LogoFile="bundle\resources\logo.png"
/>
<Payload SourceFile="bundle\resources\logoSide.png" /> <Payload SourceFile="bundle\resources\logoSide.png" />
</BootstrapperApplicationRef> </BootstrapperApplicationRef>
<Chain> <Chain>
<ExePackage Cache="yes" PerMachine="yes" Permanent="no"
SourceFile="resources\winfsp-uninstaller.exe"
DisplayName="Removing outdated WinFsp Driver"
Description="Executable to remove old winfsp"
DetectCondition="false"
InstallCondition="(InstalledLegacyWinFspVersion &lt;&gt; v0.0.0.0) AND ((WixBundleAction = 7) OR (WixBundleAction = 5))">
<CommandLine Condition="WixBundleUILevel &lt;= 3" InstallArgument="-q -l &quot;[WixBundleLog].winfsp-uninstaller.log&quot;" RepairArgument="-q" UninstallArgument="-s" />
<!-- XML allows line breaks in attributes, hence keep the line breaks here -->
<CommandLine Condition="WixBundleUILevel &gt; 3" InstallArgument="-l &quot;[WixBundleLog].winfsp-uninstaller.log&quot; -t &quot;Cryptomator Installer&quot; -m &quot;Cryptomator requires a newer version of the WinFsp driver. The installer will now uninstall WinFsp, possibly reboot, and afterwards proceed with the installation.
Do you want to continue?&quot;" RepairArgument="-q" UninstallArgument="-s" />
<ExitCode Behavior="success" Value="0"/>
<ExitCode Behavior="success" Value="1"/>
<ExitCode Behavior="error" Value="2"/>
<ExitCode Behavior="error" Value="3"/>
<ExitCode Behavior="forceReboot" Value="4"/>
<ExitCode Behavior="success" Value="5"/>
</ExePackage>
<!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/msipackage.html--> <!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/msipackage.html-->
<MsiPackage <MsiPackage
SourceFile="resources\Cryptomator.msi" SourceFile="resources\Cryptomator.msi"
CacheId="cryptomator-bundle-cryptomator" CacheId="cryptomator-bundle-cryptomator"
DisplayInternalUI="no" DisplayInternalUI="no"
Visible="no"/> Visible="no"
/>
<MsiPackage <MsiPackage
SourceFile="resources\winfsp.msi" SourceFile="resources\winfsp.msi"
CacheId="cryptomator-bundle-winfsp" CacheId="cryptomator-bundle-winfsp"
Visible="yes" Visible="yes"
DisplayInternalUI="no" DisplayInternalUI="no"
Permanent="yes"/> Vital="no"
Permanent="yes"
/>
</Chain> </Chain>
</Bundle> </Bundle>
</Wix> </Wix>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Include xmlns="http://schemas.microsoft.com/wix/2006/wi">
<!-- A version number MUST be prefixed with letter "v", otherwise it is considered a normal string -->
<?define BundledWinFspVersion="v1.12.22339" ?>
<?define BundledWinFspDownloadLink="https://github.com/winfsp/winfsp/releases/download/v1.12.22339/winfsp-1.12.22339.msi" ?> <!-- Only used by external build scripts -->
</Include>
+1 -1
View File
@@ -3,5 +3,5 @@
::REPLACE ME ::REPLACE ME
cd %~dp0 cd %~dp0
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command .\patchWebDAV.ps1^ powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1^
-LoopbackAlias %LOOPBACK_ALIAS% -LoopbackAlias %LOOPBACK_ALIAS%
+1 -1
View File
@@ -2,4 +2,4 @@
:: see comments in file ./version170-migrate-settings.ps1 :: see comments in file ./version170-migrate-settings.ps1
cd %~dp0 cd %~dp0
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command .\version170-migrate-settings.ps1 powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\version170-migrate-settings.ps1
+3 -9
View File
@@ -70,7 +70,7 @@
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" /> <CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
<?endif?> <?endif?>
<Binary Id="JpCaDll" SourceFile="$(env.JP_WIXHELPER_DIR)\wixhelper.dll"/> <Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
<CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" /> <CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />
<?ifndef SkipCryptomatorLegacyCheck ?> <?ifndef SkipCryptomatorLegacyCheck ?>
@@ -132,17 +132,11 @@
<CustomAction Id="JpSetARPURLUPDATEINFO" Property="ARPURLUPDATEINFO" Value="$(var.JpUpdateURL)" /> <CustomAction Id="JpSetARPURLUPDATEINFO" Property="ARPURLUPDATEINFO" Value="$(var.JpUpdateURL)" />
<?endif?> <?endif?>
<Property Id="WixQuietExec64CmdTimeout" Value="20" />
<!-- Note for custom actions: Immediate CAs run BEFORE the files are installed, hence if you depend on installed files, the CAs must be deferred.-->
<!-- WebDAV patches --> <!-- WebDAV patches -->
<SetProperty Id="PatchWebDAV" Value="&quot;[INSTALLDIR]patchWebDAV.bat&quot;" <CustomAction Id="PatchWebDAV" Impersonate="no" ExeCommand="[INSTALLDIR]patchWebDAV.bat" Directory="INSTALLDIR" Execute="deferred" Return="asyncWait" />
Sequence="execute" Before="PatchWebDAV" />
<CustomAction Id="PatchWebDAV" BinaryKey="WixCA" DllEntry="WixQuietExec64" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- Special Settings migration for 1.7.0,. Should be removed eventually, for more info, see ../contrib/version170-migrate-settings.ps1--> <!-- Special Settings migration for 1.7.0,. Should be removed eventually, for more info, see ../contrib/version170-migrate-settings.ps1-->
<SetProperty Id="V170MigrateSettings" Value="&quot;[INSTALLDIR]version170-migrate-settings.bat&quot;" <CustomAction Id="V170MigrateSettings" Impersonate="no" ExeCommand="[INSTALLDIR]version170-migrate-settings.bat" Directory="INSTALLDIR" Execute="deferred" Return="asyncWait" />
Sequence="execute" Before="V170MigrateSettings" />
<CustomAction Id="V170MigrateSettings" BinaryKey="WixCA" DllEntry="WixQuietExec64" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- Running App detection and exit --> <!-- Running App detection and exit -->
<Property Id="FOUNDRUNNINGAPP" Admin="yes"/> <Property Id="FOUNDRUNNINGAPP" Admin="yes"/>
+33 -54
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId> <artifactId>cryptomator</artifactId>
<version>1.11.0</version> <version>1.10.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name> <name>Cryptomator Desktop App</name>
<organization> <organization>
@@ -26,52 +26,45 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.jdk.version>21</project.jdk.version> <project.jdk.version>20</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now --> <!-- Group IDs of jars that need to stay on the class path for now -->
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) --> <!-- Once hypfvieh, swiesend, purejava and integrations-linux have module-info, remove them-->
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds> <nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
<!-- cryptomator dependencies --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.6.7</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>2.6.6</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.3.0</cryptomator.integrations.version> <cryptomator.integrations.version>1.2.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.4</cryptomator.integrations.win.version> <cryptomator.integrations.win.version>1.2.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.2</cryptomator.integrations.mac.version> <cryptomator.integrations.mac.version>1.2.0</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.4.0-beta2</cryptomator.integrations.linux.version> <cryptomator.integrations.linux.version>1.2.1</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>4.0.0-beta4</cryptomator.fuse.version> <cryptomator.fuse.version>3.0.0</cryptomator.fuse.version>
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version> <cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
<cryptomator.webdav.version>2.0.5</cryptomator.webdav.version> <cryptomator.webdav.version>2.0.3</cryptomator.webdav.version>
<!-- 3rd party dependencies --> <!-- 3rd party dependencies -->
<commons-lang3.version>3.13.0</commons-lang3.version> <commons-lang3.version>3.12.0</commons-lang3.version>
<dagger.version>2.48.1</dagger.version> <dagger.version>2.45</dagger.version>
<easybind.version>2.2</easybind.version> <easybind.version>2.2</easybind.version>
<guava.version>32.1.3-jre</guava.version> <guava.version>32.0.1-jre</guava.version>
<jackson.version>2.15.3</jackson.version> <jackson.version>2.15.2</jackson.version>
<javafx.version>20.0.2</javafx.version> <javafx.version>20.0.1</javafx.version>
<jwt.version>4.4.0</jwt.version> <jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.37</nimbus-jose.version> <nimbus-jose.version>9.31</nimbus-jose.version>
<logback.version>1.4.11</logback.version> <logback.version>1.4.7</logback.version>
<slf4j.version>2.0.9</slf4j.version> <slf4j.version>2.0.7</slf4j.version>
<tinyoauth2.version>0.8.0</tinyoauth2.version> <tinyoauth2.version>0.5.1</tinyoauth2.version>
<zxcvbn.version>1.8.2</zxcvbn.version> <zxcvbn.version>1.7.0</zxcvbn.version>
<!-- test dependencies --> <!-- test dependencies -->
<junit.jupiter.version>5.10.0</junit.jupiter.version> <junit.jupiter.version>5.9.3</junit.jupiter.version>
<mockito.version>5.6.0</mockito.version> <mockito.version>5.3.1</mockito.version>
<hamcrest.version>2.2</hamcrest.version> <hamcrest.version>2.2</hamcrest.version>
<!-- build-time dependencies --> <!-- build-time dependencies -->
<jetbrains.annotations.version>24.0.1</jetbrains.annotations.version> <jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
<dependency-check.version>8.4.0</dependency-check.version> <dependency-check.version>8.1.2</dependency-check.version>
<jacoco.version>0.8.11</jacoco.version> <jacoco.version>0.8.9</jacoco.version>
<license-generator.version>2.2.0</license-generator.version>
<junit-tree-reporter.version>1.2.1</junit-tree-reporter.version>
<mvn-compiler.version>3.11.0</mvn-compiler.version>
<mvn-resources.version>3.3.1</mvn-resources.version>
<mvn-dependency.version>3.6.0</mvn-dependency.version>
<mvn-surefire.version>3.1.2</mvn-surefire.version>
<mvn-jar.version>3.3.0</mvn-jar.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -247,7 +240,7 @@
<dependency> <dependency>
<groupId>com.google.jimfs</groupId> <groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId> <artifactId>jimfs</artifactId>
<version>1.3.0</version> <version>1.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
@@ -265,32 +258,32 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>${mvn-compiler.version}</version> <version>3.10.1</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<version>${mvn-resources.version}</version> <version>3.3.0</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<version>${mvn-dependency.version}</version> <version>3.3.0</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>${mvn-surefire.version}</version> <version>3.0.0-M7</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId> <artifactId>license-maven-plugin</artifactId>
<version>${license-generator.version}</version> <version>2.0.0</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>${mvn-jar.version}</version> <version>3.3.0</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
@@ -339,22 +332,8 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<dependencies>
<dependency>
<groupId>me.fabriciorby</groupId>
<artifactId>maven-surefire-junit5-tree-reporter</artifactId>
<version>${junit-tree-reporter.version}</version>
</dependency>
</dependencies>
<configuration> <configuration>
<argLine>--enable-preview</argLine> <argLine>--enable-preview</argLine>
<reportFormat>plain</reportFormat>
<consoleOutputReporter>
<disable>true</disable>
</consoleOutputReporter>
<statelessTestsetInfoReporter
implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
</statelessTestsetInfoReporter>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@@ -2,7 +2,6 @@ package org.cryptomator.common;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -32,7 +31,6 @@ public class Environment {
private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber"; private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber";
private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir"; private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir";
private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon"; private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon";
private static final String DISABLE_UPDATE_CHECK_PROP_NAME = "cryptomator.disableUpdateCheck";
private Environment() {} private Environment() {}
@@ -45,16 +43,15 @@ public class Environment {
logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME); logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME);
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME); logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME); logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
logCryptomatorSystemProperty(P12_PATH_PROP_NAME);
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME); logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME); logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME); logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME); logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
logCryptomatorSystemProperty(APP_VERSION_PROP_NAME); logCryptomatorSystemProperty(APP_VERSION_PROP_NAME);
logCryptomatorSystemProperty(BUILD_NUMBER_PROP_NAME); logCryptomatorSystemProperty(BUILD_NUMBER_PROP_NAME);
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME); logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME);
logCryptomatorSystemProperty(DISABLE_UPDATE_CHECK_PROP_NAME); logCryptomatorSystemProperty(P12_PATH_PROP_NAME);
} }
public static Environment getInstance() { public static Environment getInstance() {
@@ -77,6 +74,10 @@ public class Environment {
return getPaths(SETTINGS_PATH_PROP_NAME); return getPaths(SETTINGS_PATH_PROP_NAME);
} }
public Stream<Path> getP12Path() {
return getPaths(P12_PATH_PROP_NAME);
}
public Stream<Path> getIpcSocketPath() { public Stream<Path> getIpcSocketPath() {
return getPaths(IPC_SOCKET_PATH_PROP_NAME); return getPaths(IPC_SOCKET_PATH_PROP_NAME);
} }
@@ -85,10 +86,6 @@ public class Environment {
return getPaths(KEYCHAIN_PATHS_PROP_NAME); return getPaths(KEYCHAIN_PATHS_PROP_NAME);
} }
public Stream<Path> getP12Path() {
return getPaths(P12_PATH_PROP_NAME);
}
public Optional<Path> getLogDir() { public Optional<Path> getLogDir() {
return getPath(LOG_DIR_PROP_NAME); return getPath(LOG_DIR_PROP_NAME);
} }
@@ -97,12 +94,12 @@ public class Environment {
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME)); return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
} }
public Optional<Path> getMountPointsDir() { public Optional<Path> getPluginDir() {
return getPath(MOUNTPOINT_DIR_PROP_NAME); return getPath(PLUGIN_DIR_PROP_NAME);
} }
public int getMinPwLength() { public Optional<Path> getMountPointsDir() {
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH); return getPath(MOUNTPOINT_DIR_PROP_NAME);
} }
/** /**
@@ -118,24 +115,20 @@ public class Environment {
return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME)); return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME));
} }
public Optional<Path> getPluginDir() { public int getMinPwLength() {
return getPath(PLUGIN_DIR_PROP_NAME); return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
} }
public boolean showTrayIcon() { public boolean showTrayIcon() {
return Boolean.getBoolean(TRAY_ICON_PROP_NAME); return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
} }
public boolean disableUpdateCheck() {
return Boolean.getBoolean(DISABLE_UPDATE_CHECK_PROP_NAME);
}
private Optional<Path> getPath(String propertyName) { private Optional<Path> getPath(String propertyName) {
String value = System.getProperty(propertyName); String value = System.getProperty(propertyName);
return Optional.ofNullable(value).map(Paths::get); return Optional.ofNullable(value).map(Paths::get);
} }
@VisibleForTesting // visible for testing
Stream<Path> getPaths(String propertyName) { Stream<Path> getPaths(String propertyName) {
Stream<String> rawSettingsPaths = getRawList(propertyName, System.getProperty("path.separator").charAt(0)); Stream<String> rawSettingsPaths = getRawList(propertyName, System.getProperty("path.separator").charAt(0));
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Path::of); return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Path::of);
@@ -3,7 +3,6 @@ package org.cryptomator.common;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import org.jetbrains.annotations.VisibleForTesting;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@@ -115,7 +114,7 @@ public class ErrorCode {
* @param bottomFrames Other stack frames, potentially forming the bottom of the stack of <code>allFrames</code> * @param bottomFrames Other stack frames, potentially forming the bottom of the stack of <code>allFrames</code>
* @return The number of additional frames in <code>allFrames</code>. In most cases this should be equal to the difference in size. * @return The number of additional frames in <code>allFrames</code>. In most cases this should be equal to the difference in size.
*/ */
@VisibleForTesting // visible for testing
static int countTopmostFrames(StackTraceElement[] allFrames, StackTraceElement[] bottomFrames) { static int countTopmostFrames(StackTraceElement[] allFrames, StackTraceElement[] bottomFrames) {
if (allFrames.length < bottomFrames.length) { if (allFrames.length < bottomFrames.length) {
// if frames had been stacked on top of bottomFrames, allFrames would be larger // if frames had been stacked on top of bottomFrames, allFrames would be larger
@@ -125,7 +124,7 @@ public class ErrorCode {
} }
} }
@VisibleForTesting // visible for testing
static <T> int commonSuffixLength(T[] set, T[] subset) { static <T> int commonSuffixLength(T[] set, T[] subset) {
Preconditions.checkArgument(set.length >= subset.length); Preconditions.checkArgument(set.length >= subset.length);
// iterate items backwards as long as they are identical // iterate items backwards as long as they are identical
@@ -62,7 +62,7 @@ public final class OneDriveWindowsLocationPresetsProvider implements LocationPre
ProcessBuilder command = new ProcessBuilder(args); ProcessBuilder command = new ProcessBuilder(args);
Process p = command.start(); Process p = command.start();
waitForSuccess(p, 3, "`reg query`"); waitForSuccess(p, 3, "`reg query`");
return p.inputReader(StandardCharsets.ISO_8859_1).lines().filter(outputFilter); return p.inputReader(StandardCharsets.UTF_8).lines().filter(outputFilter);
} }
@@ -83,8 +83,8 @@ public final class OneDriveWindowsLocationPresetsProvider implements LocationPre
throw new TimeoutException(cmdDescription + " timed out after " + timeoutSeconds + "s"); throw new TimeoutException(cmdDescription + " timed out after " + timeoutSeconds + "s");
} }
if (process.exitValue() != 0) { if (process.exitValue() != 0) {
@SuppressWarnings("resource") var stdout = process.inputReader(StandardCharsets.ISO_8859_1).lines().collect(Collectors.joining("\n")); @SuppressWarnings("resource") var stdout = process.inputReader(StandardCharsets.UTF_8).lines().collect(Collectors.joining("\n"));
@SuppressWarnings("resource") var stderr = process.errorReader(StandardCharsets.ISO_8859_1).lines().collect(Collectors.joining("\n")); @SuppressWarnings("resource") var stderr = process.errorReader(StandardCharsets.UTF_8).lines().collect(Collectors.joining("\n"));
throw new CommandFailedException(cmdDescription, process.exitValue(), stdout, stderr); throw new CommandFailedException(cmdDescription, process.exitValue(), stdout, stderr);
} }
} }
@@ -1,17 +0,0 @@
package org.cryptomator.common.mount;
import java.nio.file.Path;
public class HideawayNotDirectoryException extends IllegalMountPointException {
private final Path hideaway;
public HideawayNotDirectoryException(Path path, Path hideaway) {
super(path, "Existing hideaway (" + hideaway.toString() + ") for mountpoint is not a directory: " + path.toString());
this.hideaway = hideaway;
}
public Path getHideaway() {
return hideaway;
}
}
@@ -1,25 +1,9 @@
package org.cryptomator.common.mount; package org.cryptomator.common.mount;
import java.nio.file.Path;
/**
* Indicates that validation or preparation of a mountpoint failed due to a configuration error or an invalid system state.<br>
* Instances of this exception are usually caught and displayed to the user in an appropriate fashion, e.g. by {@link org.cryptomator.ui.unlock.UnlockInvalidMountPointController UnlockInvalidMountPointController.}
*/
public class IllegalMountPointException extends IllegalArgumentException { public class IllegalMountPointException extends IllegalArgumentException {
private final Path mountpoint; public IllegalMountPointException(String msg) {
public IllegalMountPointException(Path mountpoint) {
this(mountpoint, "The provided mountpoint has a problem: " + mountpoint.toString());
}
public IllegalMountPointException(Path mountpoint, String msg) {
super(msg); super(msg);
this.mountpoint = mountpoint;
} }
public Path getMountpoint() { }
return mountpoint;
}
}
@@ -1,10 +0,0 @@
package org.cryptomator.common.mount;
import java.nio.file.Path;
public class MountPointCleanupFailedException extends IllegalMountPointException {
public MountPointCleanupFailedException(Path path) {
super(path, "Mountpoint could not be cleared: " + path.toString());
}
}
@@ -1,10 +1,8 @@
package org.cryptomator.common.mount; package org.cryptomator.common.mount;
import java.nio.file.Path;
public class MountPointInUseException extends IllegalMountPointException { public class MountPointInUseException extends IllegalMountPointException {
public MountPointInUseException(Path path) { public MountPointInUseException(String msg) {
super(path); super(msg);
} }
} }
@@ -1,10 +0,0 @@
package org.cryptomator.common.mount;
import java.nio.file.Path;
public class MountPointNotEmptyDirectoryException extends IllegalMountPointException {
public MountPointNotEmptyDirectoryException(Path path, String msg) {
super(path, msg);
}
}
@@ -1,14 +0,0 @@
package org.cryptomator.common.mount;
import java.nio.file.Path;
public class MountPointNotExistingException extends IllegalMountPointException {
public MountPointNotExistingException(Path path, String msg) {
super(path, msg);
}
public MountPointNotExistingException(Path path) {
super(path, "Mountpoint does not exist: " + path);
}
}
@@ -0,0 +1,8 @@
package org.cryptomator.common.mount;
public class MountPointNotExistsException extends IllegalMountPointException {
public MountPointNotExistsException(String msg) {
super(msg);
}
}
@@ -1,10 +1,8 @@
package org.cryptomator.common.mount; package org.cryptomator.common.mount;
import java.nio.file.Path;
public class MountPointNotSupportedException extends IllegalMountPointException { public class MountPointNotSupportedException extends IllegalMountPointException {
public MountPointNotSupportedException(Path path, String msg) { public MountPointNotSupportedException(String msg) {
super(path, msg); super(msg);
} }
} }
@@ -0,0 +1,12 @@
package org.cryptomator.common.mount;
public class MountPointPreparationException extends RuntimeException {
public MountPointPreparationException(String msg) {
super(msg);
}
public MountPointPreparationException(Throwable cause) {
super(cause);
}
}
@@ -1,16 +1,17 @@
package org.cryptomator.common.mount; package org.cryptomator.common.mount;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.LinkOption; import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
public final class MountWithinParentUtil { public final class MountWithinParentUtil {
@@ -21,33 +22,31 @@ public final class MountWithinParentUtil {
private MountWithinParentUtil() {} private MountWithinParentUtil() {}
static void prepareParentNoMountPoint(Path mountPoint) throws IllegalMountPointException, IOException { static void prepareParentNoMountPoint(Path mountPoint) throws MountPointPreparationException {
Path hideaway = getHideaway(mountPoint); Path hideaway = getHideaway(mountPoint);
var mpState = getMountPointState(mountPoint); var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS);
var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS); var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
if (mpState == MountPointState.BROKEN_JUNCTION) { //TODO: possible improvement by just deleting an _empty_ hideaway
LOG.info("Mountpoint \"{}\" is still a junction. Deleting it.", mountPoint); if (mpExists && hideExists) { //both resources exist (whatever type)
Files.delete(mountPoint); //Throws if mountPoint is also a non-empty folder throw new MountPointPreparationException(new FileAlreadyExistsException(hideaway.toString()));
mpState = MountPointState.NOT_EXISTING; } else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
} throw new MountPointPreparationException(new NoSuchFileException(mountPoint.toString()));
} else if (!mpExists) { //only hideaway exists
if (mpState == MountPointState.NOT_EXISTING && !hideExists) { //neither mountpoint nor hideaway exist checkIsDirectory(hideaway);
throw new MountPointNotExistingException(mountPoint);
} else if (mpState == MountPointState.NOT_EXISTING) { //only hideaway exists
checkIsHideawayDirectory(mountPoint, hideaway);
LOG.info("Mountpoint {} seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint); LOG.info("Mountpoint {} seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
}
} else {
assert mpState == MountPointState.EMPTY_DIR;
try { try {
if (hideExists) { //... with hideaway if (SystemUtils.IS_OS_WINDOWS) {
removeResidualHideaway(mountPoint, hideaway); Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
} }
} catch (IOException e) {
throw new MountPointPreparationException(e);
}
} else { //only mountpoint exists
try {
checkIsDirectory(mountPoint);
checkIsEmpty(mountPoint);
//... (now) without hideaway
Files.move(mountPoint, hideaway); Files.move(mountPoint, hideaway);
if (SystemUtils.IS_OS_WINDOWS) { if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS); Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
@@ -55,66 +54,30 @@ public final class MountWithinParentUtil {
int attempts = 0; int attempts = 0;
while (!Files.notExists(mountPoint)) { while (!Files.notExists(mountPoint)) {
if (attempts >= 10) { if (attempts >= 10) {
throw new MountPointCleanupFailedException(mountPoint); throw new MountPointPreparationException("Path " + mountPoint + " could not be cleared");
} }
Thread.sleep(1000); Thread.sleep(1000);
attempts++; attempts++;
} }
} catch (IOException e) {
throw new MountPointPreparationException(e);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new RuntimeException(e); throw new MountPointPreparationException(e);
} }
} }
} }
@VisibleForTesting
static MountPointState getMountPointState(Path path) throws IOException, IllegalMountPointException {
if (Files.notExists(path, LinkOption.NOFOLLOW_LINKS)) {
return MountPointState.NOT_EXISTING;
}
if (!Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).isOther()) {
checkIsMountPointDirectory(path);
checkIsMountPointEmpty(path);
return MountPointState.EMPTY_DIR;
}
if (Files.exists(path /* FOLLOW_LINKS */)) { //Both junction and target exist
throw new MountPointInUseException(path);
}
return MountPointState.BROKEN_JUNCTION;
}
@VisibleForTesting
enum MountPointState {
NOT_EXISTING,
EMPTY_DIR,
BROKEN_JUNCTION;
}
@VisibleForTesting
static void removeResidualHideaway(Path mountPoint, Path hideaway) throws IOException {
checkIsHideawayDirectory(mountPoint, hideaway);
Files.delete(hideaway); //Fails if not empty
}
static void cleanup(Path mountPoint) { static void cleanup(Path mountPoint) {
Path hideaway = getHideaway(mountPoint); Path hideaway = getHideaway(mountPoint);
try { try {
waitForMountpointRestoration(mountPoint); waitForMountpointRestoration(mountPoint);
if (Files.notExists(hideaway, LinkOption.NOFOLLOW_LINKS)) {
LOG.error("Unable to restore hidden directory to mountpoint \"{}\": Directory does not exist.", mountPoint);
return;
}
Files.move(hideaway, mountPoint); Files.move(hideaway, mountPoint);
if (SystemUtils.IS_OS_WINDOWS) { if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(mountPoint, WIN_HIDDEN_ATTR, false); Files.setAttribute(mountPoint, WIN_HIDDEN_ATTR, false);
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error("Unable to restore hidden directory to mountpoint \"{}\".", mountPoint, e); LOG.error("Unable to restore hidden directory to mountpoint {}.", mountPoint, e);
} }
} }
@@ -136,27 +99,21 @@ public final class MountWithinParentUtil {
} }
} }
private static void checkIsMountPointDirectory(Path toCheck) throws IllegalMountPointException { private static void checkIsDirectory(Path toCheck) throws MountPointPreparationException {
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) { if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
throw new MountPointNotEmptyDirectoryException(toCheck, "Mountpoint is not a directory: " + toCheck); throw new MountPointPreparationException(new NotDirectoryException(toCheck.toString()));
} }
} }
private static void checkIsHideawayDirectory(Path mountPoint, Path hideawayToCheck) { private static void checkIsEmpty(Path toCheck) throws MountPointPreparationException, IOException {
if (!Files.isDirectory(hideawayToCheck, LinkOption.NOFOLLOW_LINKS)) {
throw new HideawayNotDirectoryException(mountPoint, hideawayToCheck);
}
}
private static void checkIsMountPointEmpty(Path toCheck) throws IllegalMountPointException, IOException {
try (var dirStream = Files.list(toCheck)) { try (var dirStream = Files.list(toCheck)) {
if (dirStream.findFirst().isPresent()) { if (dirStream.findFirst().isPresent()) {
throw new MountPointNotEmptyDirectoryException(toCheck, "Mountpoint directory is not empty: " + toCheck); throw new MountPointPreparationException(new DirectoryNotEmptyException(toCheck.toString()));
} }
} }
} }
@VisibleForTesting //visible for testing
static Path getHideaway(Path mountPoint) { static Path getHideaway(Path mountPoint) {
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX); return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
} }
@@ -99,7 +99,7 @@ public class Mounter {
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\"); var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
if (mpIsDriveLetter) { if (mpIsDriveLetter) {
if (driveLetters.getOccupied().contains(userChosenMountPoint)) { if (driveLetters.getOccupied().contains(userChosenMountPoint)) {
throw new MountPointInUseException(userChosenMountPoint); throw new MountPointInUseException(userChosenMountPoint.toString());
} }
} else if (canMountToParent && !canMountToDir) { } else if (canMountToParent && !canMountToDir) {
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint); MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
@@ -115,13 +115,13 @@ public class Mounter {
|| (!canMountToParent && !mpIsDriveLetter) // || (!canMountToParent && !mpIsDriveLetter) //
|| (!canMountToDir && !canMountToParent && !canMountToSystem && !canMountToDriveLetter); || (!canMountToDir && !canMountToParent && !canMountToSystem && !canMountToDriveLetter);
if (configNotSupported) { if (configNotSupported) {
throw new MountPointNotSupportedException(userChosenMountPoint, e.getMessage()); throw new MountPointNotSupportedException(e.getMessage());
} else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) { } else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) {
//mountpoint must exist //mountpoint must exist
throw new MountPointNotExistingException(userChosenMountPoint, e.getMessage()); throw new MountPointNotExistsException(e.getMessage());
} else { } else {
//TODO: add specific exception for !canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint) //TODO: add specific exception for !canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)
throw new IllegalMountPointException(userChosenMountPoint, e.getMessage()); throw new IllegalMountPointException(e.getMessage());
} }
} }
} }
@@ -65,6 +65,7 @@ public class Settings {
public final IntegerProperty windowYPosition; public final IntegerProperty windowYPosition;
public final IntegerProperty windowWidth; public final IntegerProperty windowWidth;
public final IntegerProperty windowHeight; public final IntegerProperty windowHeight;
public final StringProperty displayConfiguration;
public final StringProperty language; public final StringProperty language;
public final StringProperty mountService; public final StringProperty mountService;
public final StringProperty lastUpdateCheck; public final StringProperty lastUpdateCheck;
@@ -102,6 +103,7 @@ public class Settings {
this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition); this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition);
this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth); this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth);
this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight); this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight);
this.displayConfiguration = new SimpleStringProperty(this, "displayConfiguration", json.displayConfiguration);
this.language = new SimpleStringProperty(this, "language", json.language); this.language = new SimpleStringProperty(this, "language", json.language);
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService); this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
this.lastUpdateCheck = new SimpleStringProperty(this, "lastUpdateCheck", json.lastUpdateCheck); this.lastUpdateCheck = new SimpleStringProperty(this, "lastUpdateCheck", json.lastUpdateCheck);
@@ -129,6 +131,7 @@ public class Settings {
windowYPosition.addListener(this::somethingChanged); windowYPosition.addListener(this::somethingChanged);
windowWidth.addListener(this::somethingChanged); windowWidth.addListener(this::somethingChanged);
windowHeight.addListener(this::somethingChanged); windowHeight.addListener(this::somethingChanged);
displayConfiguration.addListener(this::somethingChanged);
language.addListener(this::somethingChanged); language.addListener(this::somethingChanged);
mountService.addListener(this::somethingChanged); mountService.addListener(this::somethingChanged);
lastUpdateCheck.addListener(this::somethingChanged); lastUpdateCheck.addListener(this::somethingChanged);
@@ -183,6 +186,7 @@ public class Settings {
json.windowYPosition = windowYPosition.get(); json.windowYPosition = windowYPosition.get();
json.windowWidth = windowWidth.get(); json.windowWidth = windowWidth.get();
json.windowHeight = windowHeight.get(); json.windowHeight = windowHeight.get();
json.displayConfiguration = displayConfiguration.get();
json.language = language.get(); json.language = language.get();
json.mountService = mountService.get(); json.mountService = mountService.get();
json.lastUpdateCheck = lastUpdateCheck.get(); json.lastUpdateCheck = lastUpdateCheck.get();
@@ -31,6 +31,9 @@ class SettingsJson {
@JsonProperty("theme") @JsonProperty("theme")
UiTheme theme = Settings.DEFAULT_THEME; UiTheme theme = Settings.DEFAULT_THEME;
@JsonProperty("displayConfiguration")
String displayConfiguration;
@JsonProperty("keychainProvider") @JsonProperty("keychainProvider")
String keychainProvider = Settings.DEFAULT_KEYCHAIN_PROVIDER; String keychainProvider = Settings.DEFAULT_KEYCHAIN_PROVIDER;
@@ -9,7 +9,6 @@ import com.google.common.base.CharMatcher;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.annotations.VisibleForTesting;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
@@ -127,7 +126,7 @@ public class VaultSettings {
return json; return json;
} }
@VisibleForTesting //visible for testing
static String normalizeDisplayName(String original) { static String normalizeDisplayName(String original) {
if (original.isBlank() || ".".equals(original) || "..".equals(original)) { if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
return "_"; return "_";
@@ -314,6 +314,10 @@ public class Vault {
return vaultSettings.path.get(); return vaultSettings.path.get();
} }
CryptoFileSystem getCryptoFileSystem() {
return cryptoFileSystem.get();
}
/** /**
* Gets from the cleartext path its ciphertext counterpart. * Gets from the cleartext path its ciphertext counterpart.
* *
@@ -322,23 +326,7 @@ public class Vault {
* @throws IllegalStateException if the vault is not unlocked * @throws IllegalStateException if the vault is not unlocked
*/ */
public Path getCiphertextPath(Path cleartextPath) throws IOException { public Path getCiphertextPath(Path cleartextPath) throws IOException {
if (!state.getValue().equals(VaultState.Value.UNLOCKED)) { return state.get().getCiphertextPath(this, cleartextPath);
throw new IllegalStateException("Vault is not unlocked");
}
var fs = cryptoFileSystem.get();
var osPathSeparator = cleartextPath.getFileSystem().getSeparator();
var cryptoFsPathSeparator = fs.getSeparator();
if (getMountPoint() instanceof Mountpoint.WithPath mp) {
var absoluteCryptoFsPath = cryptoFsPathSeparator + mp.path().relativize(cleartextPath).toString();
if (!cryptoFsPathSeparator.equals(osPathSeparator)) {
absoluteCryptoFsPath = absoluteCryptoFsPath.replace(osPathSeparator, cryptoFsPathSeparator);
}
var cryptoPath = fs.getPath(absoluteCryptoFsPath);
return fs.getCiphertextPath(cryptoPath);
} else {
throw new UnsupportedOperationException("URI mount points not supported.");
}
} }
public VaultConfigCache getVaultConfigCache() { public VaultConfigCache getVaultConfigCache() {
@@ -1,6 +1,7 @@
package org.cryptomator.common.vaults; package org.cryptomator.common.vaults;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.cryptomator.integrations.mount.Mountpoint;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -8,6 +9,8 @@ import javax.inject.Inject;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.value.ObservableObjectValue; import javafx.beans.value.ObservableObjectValue;
import javafx.beans.value.ObservableValueBase; import javafx.beans.value.ObservableValueBase;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Condition;
@@ -43,12 +46,40 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
/** /**
* Vault is unlocked * Vault is unlocked
*/ */
UNLOCKED, UNLOCKED {
/**
* Gets from the cleartext path its ciphertext counterpart.
*
* @return Local os path to the ciphertext resource
* @throws IOException if an I/O error occurs
* @throws IllegalStateException if the vault is not unlocked
*/
Path getCiphertextPath(Vault vault, Path cleartextPath) throws IOException {
var fs = vault.getCryptoFileSystem();
var osPathSeparator = cleartextPath.getFileSystem().getSeparator();
var cryptoFsPathSeparator = fs.getSeparator();
if (vault.getMountPoint() instanceof Mountpoint.WithPath mp) {
var absoluteCryptoFsPath = cryptoFsPathSeparator + mp.path().relativize(cleartextPath).toString();
if (!cryptoFsPathSeparator.equals(osPathSeparator)) {
absoluteCryptoFsPath = absoluteCryptoFsPath.replace(osPathSeparator, cryptoFsPathSeparator);
}
var cryptoPath = fs.getPath(absoluteCryptoFsPath);
return fs.getCiphertextPath(cryptoPath);
} else {
throw new UnsupportedOperationException("URI mount points not supported.");
}
}
},
/** /**
* Unknown state due to preceding unrecoverable exceptions. * Unknown state due to preceding unrecoverable exceptions.
*/ */
ERROR; ERROR;
Path getCiphertextPath(Vault vault, Path cleartextPath) throws IOException {
throw new IllegalStateException("Vault is not unlocked");
}
} }
private final AtomicReference<Value> value; private final AtomicReference<Value> value;
@@ -6,7 +6,6 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.launcher; package org.cryptomator.launcher;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -49,7 +48,7 @@ class FileOpenRequestHandler {
handleLaunchArgs(FileSystems.getDefault(), args); handleLaunchArgs(FileSystems.getDefault(), args);
} }
@VisibleForTesting // visible for testing
void handleLaunchArgs(FileSystem fs, List<String> args) { void handleLaunchArgs(FileSystem fs, List<String> args) {
Collection<Path> pathsToOpen = args.stream().map(str -> { Collection<Path> pathsToOpen = args.stream().map(str -> {
try { try {
@@ -5,7 +5,6 @@ import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.Configurator; import ch.qos.logback.classic.spi.Configurator;
import ch.qos.logback.classic.spi.ConfiguratorRank;
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender; import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.ConsoleAppender;
@@ -20,7 +19,6 @@ import org.cryptomator.common.Environment;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map; import java.util.Map;
@ConfiguratorRank(ConfiguratorRank.CUSTOM_NORMAL_PRIORITY)
public class LogbackConfigurator extends ContextAwareBase implements Configurator { public class LogbackConfigurator extends ContextAwareBase implements Configurator {
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"; private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
@@ -45,8 +45,9 @@ public abstract class AddVaultModule {
@Provides @Provides
@AddVaultWizardWindow @AddVaultWizardWindow
@AddVaultWizardScoped @AddVaultWizardScoped
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage) { static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) {
Stage stage = factory.create(); Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
stage.setResizable(false); stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL); stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(primaryStage); stage.initOwner(primaryStage);
@@ -89,6 +90,13 @@ public abstract class AddVaultModule {
// ------------------ // ------------------
@Provides
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
@AddVaultWizardScoped
static Scene provideWelcomeScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME);
}
@Provides @Provides
@FxmlScene(FxmlFile.ADDVAULT_EXISTING) @FxmlScene(FxmlFile.ADDVAULT_EXISTING)
@AddVaultWizardScoped @AddVaultWizardScoped
@@ -140,6 +148,11 @@ public abstract class AddVaultModule {
// ------------------ // ------------------
@Binds
@IntoMap
@FxControllerKey(AddVaultWelcomeController.class)
abstract FxController bindWelcomeController(AddVaultWelcomeController controller);
@Binds @Binds
@IntoMap @IntoMap
@FxControllerKey(ChooseExistingVaultController.class) @FxControllerKey(ChooseExistingVaultController.class)
@@ -0,0 +1,38 @@
package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.scene.Scene;
import javafx.stage.Stage;
@AddVaultWizardScoped
public class AddVaultWelcomeController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(AddVaultWelcomeController.class);
private final Stage window;
private final Lazy<Scene> chooseExistingVaultScene;
private final Lazy<Scene> createNewVaultScene;
@Inject
AddVaultWelcomeController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> chooseExistingVaultScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> createNewVaultScene) {
this.window = window;
this.chooseExistingVaultScene = chooseExistingVaultScene;
this.createNewVaultScene = createNewVaultScene;
}
public void createNewVault() {
LOG.debug("AddVaultWelcomeController.createNewVault()");
window.setScene(createNewVaultScene.get());
}
public void chooseExistingVault() {
LOG.debug("AddVaultWelcomeController.chooseExistingVault()");
window.setScene(chooseExistingVaultScene.get());
}
}
@@ -12,7 +12,6 @@ import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.util.ResourceBundle;
@AddVaultWizardScoped @AddVaultWizardScoped
@Subcomponent(modules = {AddVaultModule.class}) @Subcomponent(modules = {AddVaultModule.class})
@@ -21,23 +20,12 @@ public interface AddVaultWizardComponent {
@AddVaultWizardWindow @AddVaultWizardWindow
Stage window(); Stage window();
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) @FxmlScene(FxmlFile.ADDVAULT_WELCOME)
Lazy<Scene> sceneNew(); Lazy<Scene> scene();
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
Lazy<Scene> sceneExisting();
default void showAddNewVaultWizard(ResourceBundle resourceBundle) { default void showAddVaultWizard() {
Stage stage = window(); Stage stage = window();
stage.setScene(sceneNew().get()); stage.setScene(scene().get());
stage.setTitle(resourceBundle.getString("addvaultwizard.new.title"));
stage.sizeToScene();
stage.show();
}
default void showAddExistingVaultWizard(ResourceBundle resourceBundle) {
Stage stage = window();
stage.setScene(sceneExisting().get());
stage.setTitle(resourceBundle.getString("addvaultwizard.existing.title"));
stage.sizeToScene(); stage.sizeToScene();
stage.show(); stage.show();
} }
@@ -35,6 +35,7 @@ public class ChooseExistingVaultController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ChooseExistingVaultController.class); private static final Logger LOG = LoggerFactory.getLogger(ChooseExistingVaultController.class);
private final Stage window; private final Stage window;
private final Lazy<Scene> welcomeScene;
private final Lazy<Scene> successScene; private final Lazy<Scene> successScene;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
private final ObjectProperty<Path> vaultPath; private final ObjectProperty<Path> vaultPath;
@@ -44,15 +45,9 @@ public class ChooseExistingVaultController implements FxController {
private final ObservableValue<Image> screenshot; private final ObservableValue<Image> screenshot;
@Inject @Inject
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, // ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, FxApplicationStyle applicationStyle) {
@FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, //
FxApplicationWindows appWindows, //
ObjectProperty<Path> vaultPath, //
@AddVaultWizardWindow ObjectProperty<Vault> vault, //
VaultListManager vaultListManager, //
ResourceBundle resourceBundle, //
FxApplicationStyle applicationStyle) {
this.window = window; this.window = window;
this.welcomeScene = welcomeScene;
this.successScene = successScene; this.successScene = successScene;
this.appWindows = appWindows; this.appWindows = appWindows;
this.vaultPath = vaultPath; this.vaultPath = vaultPath;
@@ -75,6 +70,11 @@ public class ChooseExistingVaultController implements FxController {
return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString())); return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString()));
} }
@FXML
public void back() {
window.setScene(welcomeScene.get());
}
@FXML @FXML
public void chooseFileAndNext() { public void chooseFileAndNext() {
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
@@ -17,6 +17,7 @@ import javafx.scene.Scene;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ResourceBundle;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@AddVaultWizardScoped @AddVaultWizardScoped
@@ -26,17 +27,16 @@ public class CreateNewVaultNameController implements FxController {
public TextField textField; public TextField textField;
private final Stage window; private final Stage window;
private final Lazy<Scene> welcomeScene;
private final Lazy<Scene> chooseLocationScene; private final Lazy<Scene> chooseLocationScene;
private final ObjectProperty<Path> vaultPath; private final ObjectProperty<Path> vaultPath;
private final StringProperty vaultName; private final StringProperty vaultName;
private final BooleanBinding validVaultName; private final BooleanBinding validVaultName;
@Inject @Inject
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, // CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, //
ObjectProperty<Path> vaultPath, //
@Named("vaultName") StringProperty vaultName) {
this.window = window; this.window = window;
this.welcomeScene = welcomeScene;
this.chooseLocationScene = chooseLocationScene; this.chooseLocationScene = chooseLocationScene;
this.vaultPath = vaultPath; this.vaultPath = vaultPath;
this.vaultName = vaultName; this.vaultName = vaultName;
@@ -58,6 +58,11 @@ public class CreateNewVaultNameController implements FxController {
} }
} }
@FXML
public void back() {
window.setScene(welcomeScene.get());
}
@FXML @FXML
public void next() { public void next() {
window.setScene(chooseLocationScene.get()); window.setScene(chooseLocationScene.get());
@@ -1,7 +1,5 @@
package org.cryptomator.ui.addvaultwizard; package org.cryptomator.ui.addvaultwizard;
import org.jetbrains.annotations.VisibleForTesting;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -53,7 +51,7 @@ public class ReadmeGenerator {
resourceBundle.getString("addvault.new.readme.accessLocation.4"))); resourceBundle.getString("addvault.new.readme.accessLocation.4")));
} }
@VisibleForTesting // visible for testing
String createDocument(Iterable<String> paragraphs) { String createDocument(Iterable<String> paragraphs) {
StringBuilder sb = new StringBuilder(RTF_HEADER); StringBuilder sb = new StringBuilder(RTF_HEADER);
for (String p : paragraphs) { for (String p : paragraphs) {
@@ -65,7 +63,7 @@ public class ReadmeGenerator {
return sb.toString(); return sb.toString();
} }
@VisibleForTesting // visible for testing
String escapeNonAsciiChars(CharSequence input) { String escapeNonAsciiChars(CharSequence input) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
appendEscaped(sb, input); appendEscaped(sb, input);
@@ -8,6 +8,7 @@ public enum FxmlFile {
ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), // ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), //
ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), // ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), //
ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), // ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), //
ADDVAULT_WELCOME("/fxml/addvault_welcome.fxml"), //
CHANGEPASSWORD("/fxml/changepassword.fxml"), // CHANGEPASSWORD("/fxml/changepassword.fxml"), //
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), // CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), // CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
@@ -20,10 +21,9 @@ public enum FxmlFile {
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), // HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), // HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), // HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), // HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), //
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), // HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), // HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"),
HUB_SETUP_DEVICE("/fxml/hub_setup_device.fxml"), //
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), // HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), // LOCK_FORCED("/fxml/lock_forced.fxml"), //
LOCK_FAILED("/fxml/lock_failed.fxml"), // LOCK_FAILED("/fxml/lock_failed.fxml"), //
@@ -16,7 +16,6 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -117,7 +116,7 @@ public class HubToPasswordConvertController implements FxController {
}, Platform::runLater); // }, Platform::runLater); //
} }
@VisibleForTesting //visible for testing
void convertInternal() throws CompletionException, IllegalArgumentException { void convertInternal() throws CompletionException, IllegalArgumentException {
var passphrase = newPasswordController.getNewPassword(); var passphrase = newPasswordController.getNewPassword();
var vaultPath = vault.getPath(); var vaultPath = vault.getPath();
@@ -142,7 +141,7 @@ public class HubToPasswordConvertController implements FxController {
} }
} }
@VisibleForTesting //visible for testing
void backupHubConfig(Path hubConfigPath) throws IOException { void backupHubConfig(Path hubConfigPath) throws IOException {
byte[] hubConfigBytes = Files.readAllBytes(hubConfigPath); byte[] hubConfigBytes = Files.readAllBytes(hubConfigPath);
Path backupPath = hubConfigPath.resolveSibling(VAULTCONFIG_FILENAME + BackupHelper.generateFileIdSuffix(hubConfigBytes) + MASTERKEY_BACKUP_SUFFIX); Path backupPath = hubConfigPath.resolveSibling(VAULTCONFIG_FILENAME + BackupHelper.generateFileIdSuffix(hubConfigBytes) + MASTERKEY_BACKUP_SUFFIX);
@@ -150,7 +149,7 @@ public class HubToPasswordConvertController implements FxController {
LOG.debug("Successfully created hub config backup {}", backupPath.getFileName()); LOG.debug("Successfully created hub config backup {}", backupPath.getFileName());
} }
@VisibleForTesting //visible for testing
Path createPasswordConfig(Path passwordConfigPath, Path masterkeyFile, Passphrase passphrase) throws IOException, MasterkeyLoadingFailedException { Path createPasswordConfig(Path passwordConfigPath, Path masterkeyFile, Passphrase passphrase) throws IOException, MasterkeyLoadingFailedException {
var unverifiedVaultConfig = vault.getVaultConfigCache().get(); var unverifiedVaultConfig = vault.getVaultConfigCache().get();
try (var masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) { try (var masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) {
@@ -31,7 +31,6 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -43,21 +42,19 @@ public class ErrorController implements FxController {
private static final ObjectMapper JSON = new ObjectMapper(); private static final ObjectMapper JSON = new ObjectMapper();
private static final Logger LOG = LoggerFactory.getLogger(ErrorController.class); private static final Logger LOG = LoggerFactory.getLogger(ErrorController.class);
private static final String USER_AGENT_FORMAT = "Cryptomator/%s (Build %s) (%s %s %s)"; private static final String ERROR_CODES_URL = "https://gist.githubusercontent.com/cryptobot/accba9fb9555e7192271b85606f97230/raw/errorcodes.json";
private static final String ERROR_CODES_URL_FORMAT = "https://api.cryptomator.org/desktop/error-codes.json?error-code=%s";
private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s"; private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s";
private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s"; private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s";
private static final String SEARCH_ERRORCODE_DELIM = " OR "; private static final String SEARCH_ERRORCODE_DELIM = " OR ";
private static final String REPORT_BODY_TEMPLATE = """ private static final String REPORT_BODY_TEMPLATE = """
<!-- 💚 Thank you for reporting this error. -->
OS: %s / %s OS: %s / %s
App: %s / %s App: %s / %s
<!-- Please describe what happened as accurately as possible. --> <!-- Please describe what happened as accurately as possible. -->
Description:
<!-- 📋 Please also copy and paste the detail text from the error window. -->
<!-- 📋 Please also copy and paste the details from the error window. -->
Details: <!-- Text enclosed like this (chevrons, exclamation mark, two dashes) is not visible to others! -->
<!-- If the description or the detail text is missing, the discussion will be deleted. --> <!-- If the description or the detail text is missing, the discussion will be deleted. -->
"""; """;
@@ -68,13 +65,11 @@ public class ErrorController implements FxController {
private final Scene previousScene; private final Scene previousScene;
private final Stage window; private final Stage window;
private final Environment environment; private final Environment environment;
private final ExecutorService executorService;
private final BooleanProperty copiedDetails = new SimpleBooleanProperty(); private final BooleanProperty copiedDetails = new SimpleBooleanProperty();
private final ObjectProperty<ErrorDiscussion> matchingErrorDiscussion = new SimpleObjectProperty<>(); private final ObjectProperty<ErrorDiscussion> matchingErrorDiscussion = new SimpleObjectProperty<>();
private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull(); private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull();
private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty(); private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty();
private final BooleanProperty askedForLookupDatabasePermission = new SimpleBooleanProperty();
@Inject @Inject
ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment, ExecutorService executorService) { ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment, ExecutorService executorService) {
@@ -84,7 +79,15 @@ public class ErrorController implements FxController {
this.previousScene = previousScene; this.previousScene = previousScene;
this.window = window; this.window = window;
this.environment = environment; this.environment = environment;
this.executorService = executorService;
isLoadingHttpResponse.set(true);
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpRequest httpRequest = HttpRequest.newBuilder()//
.uri(URI.create(ERROR_CODES_URL))//
.build();
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())//
.thenAcceptAsync(this::loadHttpResponse, executorService)//
.whenCompleteAsync((r, e) -> isLoadingHttpResponse.set(false), Platform::runLater);
} }
@FXML @FXML
@@ -137,32 +140,6 @@ public class ErrorController implements FxController {
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> copiedDetails.set(false)); CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> copiedDetails.set(false));
} }
@FXML
public void dismiss() {
askedForLookupDatabasePermission.set(true);
}
@FXML
public void lookUpSolution() {
String userAgent = USER_AGENT_FORMAT.formatted( //
environment.getAppVersion(), //
environment.getBuildNumber().orElse("undefined"), //
System.getProperty("os.name"), //
System.getProperty("os.version"), //
System.getProperty("os.arch"));
isLoadingHttpResponse.set(true);
askedForLookupDatabasePermission.set(true);
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpRequest httpRequest = HttpRequest.newBuilder()//
.header("User-Agent", userAgent)
.timeout(Duration.ofSeconds(10))
.uri(URI.create(ERROR_CODES_URL_FORMAT.formatted(URLEncoder.encode(errorCode.toString(),StandardCharsets.UTF_8))))//
.build();
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())//
.thenAcceptAsync(this::loadHttpResponse, executorService)//
.whenCompleteAsync((r, e) -> isLoadingHttpResponse.set(false), Platform::runLater);
}
private void loadHttpResponse(HttpResponse<InputStream> response) { private void loadHttpResponse(HttpResponse<InputStream> response) {
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
LOG.error("Status code {} when trying to load {} ", response.statusCode(), response.uri()); LOG.error("Status code {} when trying to load {} ", response.statusCode(), response.uri());
@@ -316,12 +293,4 @@ public class ErrorController implements FxController {
return isLoadingHttpResponse.get(); return isLoadingHttpResponse.get();
} }
public BooleanProperty askedForLookupDatabasePermissionProperty() {
return askedForLookupDatabasePermission;
}
public boolean getAskedForLookupDatabasePermission() {
return askedForLookupDatabasePermission.get();
}
} }
@@ -1,7 +1,6 @@
package org.cryptomator.ui.fxapp; package org.cryptomator.ui.fxapp;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -18,7 +17,6 @@ public class FxApplication {
private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class);
private final long startupTime; private final long startupTime;
private final Environment environment;
private final Settings settings; private final Settings settings;
private final AppLaunchEventHandler launchEventHandler; private final AppLaunchEventHandler launchEventHandler;
private final Lazy<TrayMenuComponent> trayMenu; private final Lazy<TrayMenuComponent> trayMenu;
@@ -28,9 +26,8 @@ public class FxApplication {
private final AutoUnlocker autoUnlocker; private final AutoUnlocker autoUnlocker;
@Inject @Inject
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { FxApplication(@Named("startupTime") long startupTime, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
this.startupTime = startupTime; this.startupTime = startupTime;
this.environment = environment;
this.settings = settings; this.settings = settings;
this.launchEventHandler = launchEventHandler; this.launchEventHandler = launchEventHandler;
this.trayMenu = trayMenu; this.trayMenu = trayMenu;
@@ -71,9 +68,7 @@ public class FxApplication {
return null; return null;
}); });
if (!environment.disableUpdateCheck()) { appWindows.checkAndShowUpdateReminderWindow();
appWindows.checkAndShowUpdateReminderWindow();
}
launchEventHandler.startHandlingLaunchEvents(); launchEventHandler.startHandlingLaunchEvents();
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES); autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
@@ -22,23 +22,23 @@ public class UpdateChecker {
private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class); private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class);
private static final Duration AUTOCHECK_DELAY = Duration.seconds(5); private static final Duration AUTOCHECK_DELAY = Duration.seconds(5);
private final Environment env;
private final Settings settings; private final Settings settings;
private final String currentVersion;
private final StringProperty latestVersionProperty; private final StringProperty latestVersionProperty;
private final Comparator<String> semVerComparator; private final Comparator<String> semVerComparator;
private final ScheduledService<String> updateCheckerService; private final ScheduledService<String> updateCheckerService;
@Inject @Inject
UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) { UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
this.env = env;
this.settings = settings; this.settings = settings;
this.latestVersionProperty = latestVersionProperty; this.latestVersionProperty = latestVersionProperty;
this.semVerComparator = semVerComparator; this.semVerComparator = semVerComparator;
this.updateCheckerService = updateCheckerService; this.updateCheckerService = updateCheckerService;
this.currentVersion = env.getAppVersion();
} }
public void automaticallyCheckForUpdatesIfEnabled() { public void automaticallyCheckForUpdatesIfEnabled() {
if (!env.disableUpdateCheck() && settings.checkForUpdates.get()) { if (settings.checkForUpdates.get()) {
startCheckingForUpdates(AUTOCHECK_DELAY); startCheckingForUpdates(AUTOCHECK_DELAY);
} }
} }
@@ -63,9 +63,9 @@ public class UpdateChecker {
private void checkSucceeded(WorkerStateEvent event) { private void checkSucceeded(WorkerStateEvent event) {
String latestVersion = updateCheckerService.getValue(); String latestVersion = updateCheckerService.getValue();
LOG.info("Current version: {}, lastest version: {}", getCurrentVersion(), latestVersion); LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
if (semVerComparator.compare(getCurrentVersion(), latestVersion) < 0) { if (semVerComparator.compare(currentVersion, latestVersion) < 0) {
// update is available // update is available
latestVersionProperty.set(latestVersion); latestVersionProperty.set(latestVersion);
} else { } else {
@@ -88,7 +88,7 @@ public class UpdateChecker {
} }
public String getCurrentVersion() { public String getCurrentVersion() {
return env.getAppVersion(); return currentVersion;
} }
} }
@@ -28,7 +28,7 @@ public abstract class UpdateCheckerModule {
private static final Logger LOG = LoggerFactory.getLogger(UpdateCheckerModule.class); private static final Logger LOG = LoggerFactory.getLogger(UpdateCheckerModule.class);
private static final URI LATEST_VERSION_URI = URI.create("https://api.cryptomator.org/desktop/latest-version.json"); private static final URI LATEST_VERSION_URI = URI.create("https://api.cryptomator.org/updates/latestVersion.json");
private static final Duration UPDATE_CHECK_INTERVAL = Duration.hours(3); private static final Duration UPDATE_CHECK_INTERVAL = Duration.hours(3);
private static final Duration DISABLED_UPDATE_CHECK_INTERVAL = Duration.hours(100000); // Duration.INDEFINITE leads to overflows... private static final Duration DISABLED_UPDATE_CHECK_INTERVAL = Duration.hours(100000); // Duration.INDEFINITE leads to overflows...
@@ -63,7 +63,6 @@ public abstract class UpdateCheckerModule {
return HttpRequest.newBuilder() // return HttpRequest.newBuilder() //
.uri(LATEST_VERSION_URI) // .uri(LATEST_VERSION_URI) //
.header("User-Agent", userAgent) // .header("User-Agent", userAgent) //
.timeout(java.time.Duration.ofSeconds(10))
.build(); .build();
} }
@@ -101,16 +101,16 @@ public class StartController implements FxController {
} }
} }
private void loadingKeyFailed(Throwable t) { private void loadingKeyFailed(Throwable e) {
switch (t) { switch (e) {
case UnlockCancelledException e -> {} // ok // TODO: rename to _ with JEP 443 case UnlockCancelledException uce -> {} //ok
case VaultKeyInvalidException e -> { // TODO: rename to _ with JEP 443 case VaultKeyInvalidException vkie -> {
LOG.error("Invalid key"); // TODO: specific error screen LOG.error("Invalid key"); //TODO: specific error screen
appWindows.showErrorWindow(e, window, null); appWindows.showErrorWindow(e, window, null);
} }
default -> { default -> {
LOG.error("Failed to load key.", t); LOG.error("Failed to load key.", e);
appWindows.showErrorWindow(t, window, null); appWindows.showErrorWindow(e, window, null);
} }
} }
} }
@@ -35,13 +35,13 @@ public class AuthFlowController implements FxController {
private final String deviceId; private final String deviceId;
private final HubConfig hubConfig; private final HubConfig hubConfig;
private final AtomicReference<String> tokenRef; private final AtomicReference<String> tokenRef;
private final CompletableFuture<ReceivedKey> result; private final CompletableFuture<JWEObject> result;
private final Lazy<Scene> receiveKeyScene; private final Lazy<Scene> receiveKeyScene;
private final ObjectProperty<URI> authUri; private final ObjectProperty<URI> authUri;
private AuthFlowTask task; private AuthFlowTask task;
@Inject @Inject
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) { public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
this.application = application; this.application = application;
this.window = window; this.window = window;
this.executor = executor; this.executor = executor;
@@ -1,14 +1,13 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.coffeelibs.tinyoauth2client.AuthFlow;
import io.github.coffeelibs.tinyoauth2client.TinyOAuth2; import io.github.coffeelibs.tinyoauth2client.TinyOAuth2;
import io.github.coffeelibs.tinyoauth2client.http.response.Response; import io.github.coffeelibs.tinyoauth2client.http.response.Response;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.function.Consumer; import java.util.function.Consumer;
class AuthFlowTask extends Task<String> { class AuthFlowTask extends Task<String> {
@@ -22,7 +21,7 @@ class AuthFlowTask extends Task<String> {
/** /**
* Spawns a server and waits for the redirectUri to be called. * Spawns a server and waits for the redirectUri to be called.
* *
* @param hubConfig Configuration object holding parameters required by {@link io.github.coffeelibs.tinyoauth2client.AuthorizationCodeGrant} * @param hubConfig Configuration object holding parameters required by {@link AuthFlow}
* @param redirectUriConsumer A callback invoked with the redirectUri, as soon as the server has started * @param redirectUriConsumer A callback invoked with the redirectUri, as soon as the server has started
*/ */
public AuthFlowTask(HubConfig hubConfig, AuthFlowContext authFlowContext, Consumer<URI> redirectUriConsumer) { public AuthFlowTask(HubConfig hubConfig, AuthFlowContext authFlowContext, Consumer<URI> redirectUriConsumer) {
@@ -35,11 +34,10 @@ class AuthFlowTask extends Task<String> {
protected String call() throws IOException, InterruptedException { protected String call() throws IOException, InterruptedException {
var response = TinyOAuth2.client(hubConfig.clientId) // var response = TinyOAuth2.client(hubConfig.clientId) //
.withTokenEndpoint(URI.create(hubConfig.tokenEndpoint)) // .withTokenEndpoint(URI.create(hubConfig.tokenEndpoint)) //
.withRequestTimeout(Duration.ofSeconds(10)) // .authFlow(URI.create(hubConfig.authEndpoint)) //
.authorizationCodeGrant(URI.create(hubConfig.authEndpoint)) //
.setSuccessResponse(Response.redirect(URI.create(hubConfig.authSuccessUrl + "&device=" + authFlowContext.deviceId()))) // .setSuccessResponse(Response.redirect(URI.create(hubConfig.authSuccessUrl + "&device=" + authFlowContext.deviceId()))) //
.setErrorResponse(Response.redirect(URI.create(hubConfig.authErrorUrl + "&device=" + authFlowContext.deviceId()))) // .setErrorResponse(Response.redirect(URI.create(hubConfig.authErrorUrl + "&device=" + authFlowContext.deviceId()))) //
.authorize(HttpClient.newHttpClient(), redirectUriConsumer); .authorize(redirectUriConsumer);
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
throw new NotOkResponseException("Authorization returned status code " + response.statusCode()); throw new NotOkResponseException("Authorization returned status code " + response.statusCode());
} }
@@ -0,0 +1,5 @@
package org.cryptomator.ui.keyloading.hub;
record CreateDeviceDto(String id, String name, String publicKey) {
}
@@ -0,0 +1,19 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.CharStreams;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
class HttpHelper {
public static String readBody(HttpResponse<InputStream> response) throws IOException {
try (var in = response.body(); var reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
return CharStreams.toString(reader);
}
}
}
@@ -1,10 +1,6 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.URI;
// needs to be accessible by JSON decoder // needs to be accessible by JSON decoder
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@@ -13,19 +9,8 @@ public class HubConfig {
public String clientId; public String clientId;
public String authEndpoint; public String authEndpoint;
public String tokenEndpoint; public String tokenEndpoint;
public String devicesResourceUrl;
public String authSuccessUrl; public String authSuccessUrl;
public String authErrorUrl; public String authErrorUrl;
public @Nullable String apiBaseUrl;
@Deprecated // use apiBaseUrl + "/devices/"
public String devicesResourceUrl;
public URI getApiBaseUrl() {
if (apiBaseUrl != null) {
return URI.create(apiBaseUrl);
} else {
// legacy approach
assert devicesResourceUrl != null;
return URI.create(devicesResourceUrl + "/..").normalize();
}
}
} }
@@ -1,6 +1,7 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.JWEObject;
import dagger.Binds; import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -68,7 +69,7 @@ public abstract class HubKeyLoadingModule {
@Provides @Provides
@KeyLoadingScoped @KeyLoadingScoped
static CompletableFuture<ReceivedKey> provideResult() { static CompletableFuture<JWEObject> provideResult() {
return new CompletableFuture<>(); return new CompletableFuture<>();
} }
@@ -113,10 +114,10 @@ public abstract class HubKeyLoadingModule {
} }
@Provides @Provides
@FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE)
@KeyLoadingScoped @KeyLoadingScoped
static Scene provideHubLegacyRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE); return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE);
} }
@Provides @Provides
@@ -133,13 +134,6 @@ public abstract class HubKeyLoadingModule {
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_FAILED); return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_FAILED);
} }
@Provides
@FxmlScene(FxmlFile.HUB_SETUP_DEVICE)
@KeyLoadingScoped
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_SETUP_DEVICE);
}
@Provides @Provides
@FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE)
@KeyLoadingScoped @KeyLoadingScoped
@@ -172,11 +166,6 @@ public abstract class HubKeyLoadingModule {
@FxControllerKey(RegisterDeviceController.class) @FxControllerKey(RegisterDeviceController.class)
abstract FxController bindRegisterDeviceController(RegisterDeviceController controller); abstract FxController bindRegisterDeviceController(RegisterDeviceController controller);
@Binds
@IntoMap
@FxControllerKey(LegacyRegisterDeviceController.class)
abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller);
@Binds @Binds
@IntoMap @IntoMap
@FxControllerKey(RegisterSuccessController.class) @FxControllerKey(RegisterSuccessController.class)
@@ -36,11 +36,11 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
private final KeychainManager keychainManager; private final KeychainManager keychainManager;
private final Lazy<Scene> authFlowScene; private final Lazy<Scene> authFlowScene;
private final Lazy<Scene> noKeychainScene; private final Lazy<Scene> noKeychainScene;
private final CompletableFuture<ReceivedKey> result; private final CompletableFuture<JWEObject> result;
private final DeviceKey deviceKey; private final DeviceKey deviceKey;
@Inject @Inject
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) { public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
this.window = window; this.window = window;
this.keychainManager = keychainManager; this.keychainManager = keychainManager;
window.setTitle(windowTitle); window.setTitle(windowTitle);
@@ -60,7 +60,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
var keypair = deviceKey.get(); var keypair = deviceKey.get();
showWindow(authFlowScene); showWindow(authFlowScene);
var jwe = result.get(); var jwe = result.get();
return jwe.decryptMasterkey(keypair.getPrivate()); return JWEHelper.decrypt(jwe, keypair.getPrivate());
} catch (NoKeychainAccessProviderException e) { } catch (NoKeychainAccessProviderException e) {
showWindow(noKeychainScene); showWindow(noKeychainScene);
throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e); throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e);
@@ -2,103 +2,35 @@ package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDHDecrypter; import com.nimbusds.jose.crypto.ECDHDecrypter;
import com.nimbusds.jose.crypto.ECDHEncrypter;
import com.nimbusds.jose.crypto.PasswordBasedDecrypter;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.gen.JWKGenerator;
import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.function.Function;
class JWEHelper { class JWEHelper {
private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class); private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class);
private static final String JWE_PAYLOAD_KEY_FIELD = "key"; private static final String JWE_PAYLOAD_MASTERKEY_FIELD = "key";
private static final String EC_ALG = "EC";
private JWEHelper(){} private JWEHelper(){}
public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) {
try {
var encodedUserKey = Base64.getEncoder().encodeToString(userKey.getEncoded());
var keyGen = new ECKeyGenerator(Curve.P_384);
var ephemeralKeyPair = keyGen.generate();
var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build();
var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedUserKey));
var jwe = new JWEObject(header, payload);
jwe.encrypt(new ECDHEncrypter(deviceKey));
return jwe;
} catch (JOSEException e) {
throw new RuntimeException(e);
}
}
public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException { public static Masterkey decrypt(JWEObject jwe, ECPrivateKey privateKey) throws MasterkeyLoadingFailedException {
try {
jwe.decrypt(new PasswordBasedDecrypter(setupCode));
return decodeUserKey(jwe);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
}
public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException {
try {
jwe.decrypt(new ECDHDecrypter(deviceKey));
return decodeUserKey(jwe);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
}
private static ECPrivateKey decodeUserKey(JWEObject decryptedJwe) {
try {
var keySpec = readKey(decryptedJwe, JWE_PAYLOAD_KEY_FIELD, PKCS8EncodedKeySpec::new);
var factory = KeyFactory.getInstance(EC_ALG);
var privateKey = factory.generatePrivate(keySpec);
if (privateKey instanceof ECPrivateKey ecPrivateKey) {
return ecPrivateKey;
} else {
throw new IllegalStateException(EC_ALG + " key factory not generating ECPrivateKeys");
}
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(EC_ALG + " not supported");
} catch (InvalidKeySpecException e) {
LOG.warn("Unexpected JWE payload: {}", decryptedJwe.getPayload());
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
}
}
public static Masterkey decryptVaultKey(JWEObject jwe, ECPrivateKey privateKey) throws InvalidJweKeyException {
try { try {
jwe.decrypt(new ECDHDecrypter(privateKey)); jwe.decrypt(new ECDHDecrypter(privateKey));
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, Masterkey::new); return readKey(jwe);
} catch (JOSEException e) { } catch (JOSEException e) {
throw new InvalidJweKeyException(e); LOG.warn("Failed to decrypt JWE: {}", jwe);
throw new MasterkeyLoadingFailedException("Failed to decrypt JWE", e);
} }
} }
private static <T> T readKey(JWEObject jwe, String keyField, Function<byte[], T> rawKeyFactory) throws MasterkeyLoadingFailedException { private static Masterkey readKey(JWEObject jwe) throws MasterkeyLoadingFailedException {
Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED); Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED);
var fields = jwe.getPayload().toJSONObject(); var fields = jwe.getPayload().toJSONObject();
if (fields == null) { if (fields == null) {
@@ -107,11 +39,11 @@ class JWEHelper {
} }
var keyBytes = new byte[0]; var keyBytes = new byte[0];
try { try {
if (fields.get(keyField) instanceof String key) { if (fields.get(JWE_PAYLOAD_MASTERKEY_FIELD) instanceof String key) {
keyBytes = BaseEncoding.base64().decode(key); keyBytes = BaseEncoding.base64().decode(key);
return rawKeyFactory.apply(keyBytes); return new Masterkey(keyBytes);
} else { } else {
throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField); throw new IllegalArgumentException("JWE payload doesn't contain field " + JWE_PAYLOAD_MASTERKEY_FIELD);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
LOG.error("Unexpected JWE payload: {}", jwe.getPayload()); LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
@@ -120,11 +52,4 @@ class JWEHelper {
Arrays.fill(keyBytes, (byte) 0x00); Arrays.fill(keyBytes, (byte) 0x00);
} }
} }
public static class InvalidJweKeyException extends MasterkeyLoadingFailedException {
public InvalidJweKeyException(Throwable cause) {
super("Invalid key", cause);
}
}
} }
@@ -1,191 +0,0 @@
package org.cryptomator.ui.keyloading.hub;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dagger.Lazy;
import org.cryptomator.common.settings.DeviceKey;
import org.cryptomator.cryptolib.common.P384KeyPair;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
public class LegacyRegisterDeviceController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(LegacyRegisterDeviceController.class);
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final List<Integer> EXPECTED_RESPONSE_CODES = List.of(201, 409);
private final Stage window;
private final HubConfig hubConfig;
private final String bearerToken;
private final Lazy<Scene> registerSuccessScene;
private final Lazy<Scene> registerFailedScene;
private final String deviceId;
private final P384KeyPair keyPair;
private final CompletableFuture<ReceivedKey> result;
private final DecodedJWT jwt;
private final HttpClient httpClient;
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
public TextField deviceNameField;
public Button registerBtn;
@Inject
public LegacyRegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
this.window = window;
this.hubConfig = hubConfig;
this.deviceId = deviceId;
this.keyPair = Objects.requireNonNull(deviceKey.get());
this.result = result;
this.bearerToken = Objects.requireNonNull(bearerToken.get());
this.registerSuccessScene = registerSuccessScene;
this.registerFailedScene = registerFailedScene;
this.jwt = JWT.decode(this.bearerToken);
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
}
public void initialize() {
deviceNameField.setText(determineHostname());
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
}
private String determineHostname() {
try {
var hostName = InetAddress.getLocalHost().getHostName();
return Objects.requireNonNullElse(hostName, "");
} catch (IOException e) {
return "";
}
}
@FXML
public void register() {
deviceNameAlreadyExists.set(false);
registerBtn.setContentDisplay(ContentDisplay.LEFT);
registerBtn.setDisable(true);
var deviceUri = URI.create(hubConfig.devicesResourceUrl + deviceId);
var deviceKey = keyPair.getPublic().getEncoded();
var dto = new CreateDeviceDto();
dto.id = deviceId;
dto.name = deviceNameField.getText();
dto.publicKey = Base64.getUrlEncoder().withoutPadding().encodeToString(deviceKey);
var json = toJson(dto);
var request = HttpRequest.newBuilder(deviceUri) //
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
.header("Authorization", "Bearer " + bearerToken) //
.header("Content-Type", "application/json") //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) //
.thenApply(response -> {
if (EXPECTED_RESPONSE_CODES.contains(response.statusCode())) {
return response;
} else {
throw new RuntimeException("Server answered with unexpected status code " + response.statusCode());
}
}).handleAsync((response, throwable) -> {
if (response != null) {
this.handleResponse(response);
} else {
this.registrationFailed(throwable);
}
return null;
}, Platform::runLater);
}
private String toJson(CreateDeviceDto dto) {
try {
return JSON.writer().writeValueAsString(dto);
} catch (JacksonException e) {
throw new IllegalStateException("Failed to serialize DTO", e);
}
}
private void handleResponse(HttpResponse<Void> voidHttpResponse) {
assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode());
if (voidHttpResponse.statusCode() == 409) {
deviceNameAlreadyExists.set(true);
registerBtn.setContentDisplay(ContentDisplay.TEXT_ONLY);
registerBtn.setDisable(false);
} else {
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
window.setScene(registerSuccessScene.get());
}
}
private void registrationFailed(Throwable cause) {
LOG.warn("Device registration failed.", cause);
window.setScene(registerFailedScene.get());
result.completeExceptionally(cause);
}
@FXML
public void close() {
window.close();
}
private void windowClosed(WindowEvent windowEvent) {
result.cancel(true);
}
/* Getter */
public String getUserName() {
return jwt.getClaim("email").asString();
}
//--- Getters & Setters
public BooleanProperty deviceNameAlreadyExistsProperty() {
return deviceNameAlreadyExists;
}
public boolean getDeviceNameAlreadyExists() {
return deviceNameAlreadyExists.get();
}
private static class CreateDeviceDto {
public String id;
public String name;
public final String type = "DESKTOP";
public String publicKey;
}
}
@@ -1,8 +1,5 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWEObject;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
@@ -11,9 +8,6 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped; import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@@ -23,16 +17,14 @@ import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.text.ParseException; import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -41,32 +33,25 @@ import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped @KeyLoadingScoped
public class ReceiveKeyController implements FxController { public class ReceiveKeyController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ReceiveKeyController.class);
private static final String SCHEME_PREFIX = "hub+"; private static final String SCHEME_PREFIX = "hub+";
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10);
private final Stage window; private final Stage window;
private final HubConfig hubConfig;
private final String deviceId; private final String deviceId;
private final String bearerToken; private final String bearerToken;
private final CompletableFuture<ReceivedKey> result; private final CompletableFuture<JWEObject> result;
private final Lazy<Scene> setupDeviceScene; private final Lazy<Scene> registerDeviceScene;
private final Lazy<Scene> legacyRegisterDeviceScene;
private final Lazy<Scene> unauthorizedScene; private final Lazy<Scene> unauthorizedScene;
private final URI vaultBaseUri; private final URI vaultBaseUri;
private final Lazy<Scene> invalidLicenseScene; private final Lazy<Scene> invalidLicenseScene;
private final HttpClient httpClient; private final HttpClient httpClient;
@Inject @Inject
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) Lazy<Scene> setupDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) { public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
this.window = window; this.window = window;
this.hubConfig = hubConfig;
this.deviceId = deviceId; this.deviceId = deviceId;
this.bearerToken = Objects.requireNonNull(tokenRef.get()); this.bearerToken = Objects.requireNonNull(tokenRef.get());
this.result = result; this.result = result;
this.setupDeviceScene = setupDeviceScene; this.registerDeviceScene = registerDeviceScene;
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
this.unauthorizedScene = unauthorizedScene; this.unauthorizedScene = unauthorizedScene;
this.vaultBaseUri = getVaultBaseUri(vault); this.vaultBaseUri = getVaultBaseUri(vault);
this.invalidLicenseScene = invalidLicenseScene; this.invalidLicenseScene = invalidLicenseScene;
@@ -76,120 +61,23 @@ public class ReceiveKeyController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
requestVaultMasterkey(); var keyUri = appendPath(vaultBaseUri, "/keys/" + deviceId);
} var request = HttpRequest.newBuilder(keyUri) //
/**
* STEP 1 (Request): GET vault key for this user
*/
private void requestVaultMasterkey() {
var accessTokenUri = appendPath(vaultBaseUri, "/access-token");
var request = HttpRequest.newBuilder(accessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) // .header("Authorization", "Bearer " + bearerToken) //
.GET() // .GET() //
.timeout(REQ_TIMEOUT) //
.build(); .build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) // httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) //
.thenAcceptAsync(this::receivedVaultMasterkey, Platform::runLater) // .thenAcceptAsync(this::loadedExistingKey, Platform::runLater) //
.exceptionally(this::retrievalFailed); .exceptionally(this::retrievalFailed);
} }
/** private void loadedExistingKey(HttpResponse<InputStream> response) {
* STEP 1 (Response): GET vault key for this user
*
* @param response Response
*/
private void receivedVaultMasterkey(HttpResponse<String> response) {
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
switch (response.statusCode()) {
case 200 -> requestUserKey(response.body());
case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
case 404 -> requestLegacyAccessToken();
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
}
}
/**
* STEP 2 (Request): GET user key for this device
*/
private void requestUserKey(String encryptedVaultKey) {
var deviceTokenUri = URI.create(hubConfig.getApiBaseUrl() + "/devices/" + deviceId);
var request = HttpRequest.newBuilder(deviceTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
.thenAcceptAsync(response -> receivedUserKey(encryptedVaultKey, response), Platform::runLater) //
.exceptionally(this::retrievalFailed);
}
/**
* STEP 2 (Response): GET user key for this device
*
* @param response Response
*/
private void receivedUserKey(String encryptedVaultKey, HttpResponse<String> response) {
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
try { try {
switch (response.statusCode()) { switch (response.statusCode()) {
case 200 -> { case 200 -> retrievalSucceeded(response);
var device = JSON.reader().readValue(response.body(), DeviceDto.class);
receivedBothEncryptedKeys(encryptedVaultKey, device.userPrivateKey);
}
case 404 -> needsDeviceSetup(); // TODO: using the setup code, we can theoretically immediately unlock
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void needsDeviceSetup() {
window.setScene(setupDeviceScene.get());
}
private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) throws IOException {
try {
var vaultKeyJwe = JWEObject.parse(encryptedVaultKey);
var userKeyJwe = JWEObject.parse(encryptedUserKey);
result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe));
window.close();
} catch (ParseException e) {
throw new IOException("Failed to parse JWE", e);
}
}
/**
* LEGACY FALLBACK (Request): GET the legacy access token from Hub 1.x
*/
@Deprecated
private void requestLegacyAccessToken() {
var legacyAccessTokenUri = appendPath(vaultBaseUri, "/keys/" + deviceId);
var request = HttpRequest.newBuilder(legacyAccessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
.thenAcceptAsync(this::receivedLegacyAccessTokenResponse, Platform::runLater) //
.exceptionally(this::retrievalFailed);
}
/**
* LEGACY FALLBACK (Response)
*
* @param response Response
*/
@Deprecated
private void receivedLegacyAccessTokenResponse(HttpResponse<String> response) {
try {
switch (response.statusCode()) {
case 200 -> receivedLegacyAccessTokenSuccess(response.body());
case 402 -> licenseExceeded(); case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access case 403 -> accessNotGranted();
case 404 -> needsLegacyDeviceRegistration(); case 404 -> needsDeviceRegistration();
default -> throw new IOException("Unexpected response " + response.statusCode()); default -> throw new IOException("Unexpected response " + response.statusCode());
} }
} catch (IOException e) { } catch (IOException e) {
@@ -197,11 +85,10 @@ public class ReceiveKeyController implements FxController {
} }
} }
@Deprecated private void retrievalSucceeded(HttpResponse<InputStream> response) throws IOException {
private void receivedLegacyAccessTokenSuccess(String rawToken) throws IOException {
try { try {
var token = JWEObject.parse(rawToken); var string = HttpHelper.readBody(response);
result.complete(ReceivedKey.legacyDeviceKey(token)); result.complete(JWEObject.parse(string));
window.close(); window.close();
} catch (ParseException e) { } catch (ParseException e) {
throw new IOException("Failed to parse JWE", e); throw new IOException("Failed to parse JWE", e);
@@ -212,9 +99,8 @@ public class ReceiveKeyController implements FxController {
window.setScene(invalidLicenseScene.get()); window.setScene(invalidLicenseScene.get());
} }
@Deprecated private void needsDeviceRegistration() {
private void needsLegacyDeviceRegistration() { window.setScene(registerDeviceScene.get());
window.setScene(legacyRegisterDeviceScene.get());
} }
private void accessNotGranted() { private void accessNotGranted() {
@@ -246,17 +132,14 @@ public class ReceiveKeyController implements FxController {
private static URI getVaultBaseUri(Vault vault) { private static URI getVaultBaseUri(Vault vault) {
try { try {
var url = vault.getVaultConfigCache().get().getKeyId(); var kid = vault.getVaultConfigCache().get().getKeyId();
assert url.getScheme().startsWith(SCHEME_PREFIX); assert kid.getScheme().startsWith(SCHEME_PREFIX);
var correctedScheme = url.getScheme().substring(SCHEME_PREFIX.length()); var hubUriScheme = kid.getScheme().substring(SCHEME_PREFIX.length());
return new URI(correctedScheme, url.getSchemeSpecificPart(), url.getFragment()); return new URI(hubUriScheme, kid.getSchemeSpecificPart(), kid.getFragment());
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
throw new IllegalStateException("URI constructed from params known to be valid", e); throw new IllegalStateException("URI constructed from params known to be valid", e);
} }
} }
@JsonIgnoreProperties(ignoreUnknown = true)
private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
} }
@@ -1,45 +0,0 @@
package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import org.cryptomator.cryptolib.api.Masterkey;
import java.security.interfaces.ECPrivateKey;
@FunctionalInterface
interface ReceivedKey {
/**
* Decrypts the vault key.
*
* @param deviceKey This device's private key.
* @return The decrypted vault key
*/
Masterkey decryptMasterkey(ECPrivateKey deviceKey);
/**
* Creates an unlock response object from the user key + vault key.
*
* @param vaultKeyJwe a JWE containing the symmetric vault key, encrypted for this device's user.
* @param userKeyJwe a JWE containing the user's private key, encrypted for this device.
* @return Ciphertext received by Hub, which can be decrypted using this device's private key.
*/
static ReceivedKey vaultKeyAndUserKey(JWEObject vaultKeyJwe, JWEObject userKeyJwe) {
return deviceKey -> {
var userKey = JWEHelper.decryptUserKey(userKeyJwe, deviceKey);
return JWEHelper.decryptVaultKey(vaultKeyJwe, userKey);
};
}
/**
* Creates an unlock response object from the received legacy "access token" JWE.
*
* @param vaultKeyJwe a JWE containing the symmetric vault key, encrypted for this device.
* @return Ciphertext received by Hub, which can be decrypted using this device's private key.
* @deprecated Only for compatibility with Hub 1.0 - 1.2
*/
@Deprecated
static ReceivedKey legacyDeviceKey(JWEObject vaultKeyJwe) {
return deviceKey -> JWEHelper.decryptVaultKey(vaultKeyJwe, deviceKey);
}
}
@@ -1,7 +1,7 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.auth0.jwt.JWT;
import com.fasterxml.jackson.annotation.JsonProperty; import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
@@ -20,7 +20,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@@ -32,16 +31,14 @@ import javafx.stage.Stage;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.ParseException; import java.util.List;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -50,7 +47,7 @@ public class RegisterDeviceController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class); private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class);
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true); private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10); private static final List<Integer> EXPECTED_RESPONSE_CODES = List.of(201, 409);
private final Stage window; private final Stage window;
private final HubConfig hubConfig; private final HubConfig hubConfig;
@@ -58,27 +55,26 @@ public class RegisterDeviceController implements FxController {
private final Lazy<Scene> registerSuccessScene; private final Lazy<Scene> registerSuccessScene;
private final Lazy<Scene> registerFailedScene; private final Lazy<Scene> registerFailedScene;
private final String deviceId; private final String deviceId;
private final P384KeyPair deviceKeyPair; private final P384KeyPair keyPair;
private final CompletableFuture<ReceivedKey> result; private final CompletableFuture<JWEObject> result;
private final DecodedJWT jwt;
private final HttpClient httpClient; private final HttpClient httpClient;
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false); private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false);
private final BooleanProperty workInProgress = new SimpleBooleanProperty(false);
public TextField setupCodeField;
public TextField deviceNameField; public TextField deviceNameField;
public Button registerBtn; public Button registerBtn;
@Inject @Inject
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) { public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<JWEObject> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
this.window = window; this.window = window;
this.hubConfig = hubConfig; this.hubConfig = hubConfig;
this.deviceId = deviceId; this.deviceId = deviceId;
this.deviceKeyPair = Objects.requireNonNull(deviceKey.get()); this.keyPair = Objects.requireNonNull(deviceKey.get());
this.result = result; this.result = result;
this.bearerToken = Objects.requireNonNull(bearerToken.get()); this.bearerToken = Objects.requireNonNull(bearerToken.get());
this.registerSuccessScene = registerSuccessScene; this.registerSuccessScene = registerSuccessScene;
this.registerFailedScene = registerFailedScene; this.registerFailedScene = registerFailedScene;
this.jwt = JWT.decode(this.bearerToken);
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build(); this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
} }
@@ -86,13 +82,6 @@ public class RegisterDeviceController implements FxController {
public void initialize() { public void initialize() {
deviceNameField.setText(determineHostname()); deviceNameField.setText(determineHostname());
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false)); deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
deviceNameField.disableProperty().bind(workInProgress);
setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false));
setupCodeField.disableProperty().bind(workInProgress);
var missingSetupCode = setupCodeField.textProperty().isEmpty();
var missingDeviceName = deviceNameField.textProperty().isEmpty();
registerBtn.disableProperty().bind(workInProgress.or(missingSetupCode).or(missingDeviceName));
registerBtn.contentDisplayProperty().bind(Bindings.when(workInProgress).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY));
} }
private String determineHostname() { private String determineHostname() {
@@ -106,62 +95,35 @@ public class RegisterDeviceController implements FxController {
@FXML @FXML
public void register() { public void register() {
workInProgress.set(true); deviceNameAlreadyExists.set(false);
registerBtn.setContentDisplay(ContentDisplay.LEFT);
registerBtn.setDisable(true);
var apiRootUrl = hubConfig.getApiBaseUrl(); var keyUri = URI.create(hubConfig.devicesResourceUrl + deviceId);
var deviceKey = keyPair.getPublic().getEncoded();
var userReq = HttpRequest.newBuilder(apiRootUrl.resolve("users/me")) // var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64Url().omitPadding().encode(deviceKey));
.GET() // var json = toJson(dto);
.timeout(REQ_TIMEOUT) // var request = HttpRequest.newBuilder(keyUri) //
.header("Authorization", "Bearer " + bearerToken) // .header("Authorization", "Bearer " + bearerToken) //
.header("Content-Type", "application/json") // .header("Content-Type", "application/json").PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
.build(); .build();
httpClient.sendAsync(userReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) // httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) //
.thenApply(response -> { .thenApply(response -> {
if (response.statusCode() == 200) { if (EXPECTED_RESPONSE_CODES.contains(response.statusCode())) {
var dto = fromJson(response.body()); return response;
return Objects.requireNonNull(dto, "null or empty response body");
} else { } else {
throw new RuntimeException("Server answered with unexpected status code " + response.statusCode()); throw new RuntimeException("Server answered with unexpected status code " + response.statusCode());
} }
}).thenApply(user -> { }).handleAsync((response, throwable) -> {
try {
assert user.privateKey != null; // api/vaults/{v}/user-tokens/me would have returned 403, if user wasn't fully set up yet
var userKey = JWEHelper.decryptUserKey(JWEObject.parse(user.privateKey), setupCodeField.getText());
return JWEHelper.encryptUserKey(userKey, deviceKeyPair.getPublic());
} catch (ParseException e) {
throw new RuntimeException("Server answered with unparsable user key", e);
}
}).thenCompose(jwe -> {
var now = Instant.now().toString();
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now);
var json = toJson(dto);
var deviceUri = apiRootUrl.resolve("devices/" + deviceId);
var putDeviceReq = HttpRequest.newBuilder(deviceUri) //
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
.timeout(REQ_TIMEOUT) //
.header("Authorization", "Bearer " + bearerToken) //
.header("Content-Type", "application/json") //
.build();
return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding());
}).whenCompleteAsync((response, throwable) -> {
if (response != null) { if (response != null) {
this.handleResponse(response); this.handleResponse(response);
} else { } else {
this.setupFailed(throwable); this.registrationFailed(throwable);
} }
workInProgress.set(false); return null;
}, Platform::runLater); }, Platform::runLater);
} }
private UserDto fromJson(String json) {
try {
return JSON.reader().readValue(json, UserDto.class);
} catch (IOException e) {
throw new IllegalStateException("Failed to deserialize DTO", e);
}
}
private String toJson(CreateDeviceDto dto) { private String toJson(CreateDeviceDto dto) {
try { try {
return JSON.writer().writeValueAsString(dto); return JSON.writer().writeValueAsString(dto);
@@ -170,26 +132,23 @@ public class RegisterDeviceController implements FxController {
} }
} }
private void handleResponse(HttpResponse<Void> response) { private void handleResponse(HttpResponse<Void> voidHttpResponse) {
if (response.statusCode() == 201) { assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode());
if (voidHttpResponse.statusCode() == 409) {
deviceNameAlreadyExists.set(true);
registerBtn.setContentDisplay(ContentDisplay.TEXT_ONLY);
registerBtn.setDisable(false);
} else {
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl); LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
window.setScene(registerSuccessScene.get()); window.setScene(registerSuccessScene.get());
} else if (response.statusCode() == 409) {
deviceNameAlreadyExists.set(true);
} else {
setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode()));
} }
} }
private void setupFailed(Throwable cause) { private void registrationFailed(Throwable cause) {
switch (cause) { LOG.warn("Device registration failed.", cause);
case CompletionException e when e.getCause() instanceof JWEHelper.InvalidJweKeyException -> invalidSetupCode.set(true); window.setScene(registerFailedScene.get());
default -> { result.completeExceptionally(cause);
LOG.warn("Device setup failed.", cause);
window.setScene(registerFailedScene.get());
result.completeExceptionally(cause);
}
}
} }
@FXML @FXML
@@ -201,6 +160,13 @@ public class RegisterDeviceController implements FxController {
result.cancel(true); result.cancel(true);
} }
/* Getter */
public String getUserName() {
return jwt.getClaim("email").asString();
}
//--- Getters & Setters //--- Getters & Setters
public BooleanProperty deviceNameAlreadyExistsProperty() { public BooleanProperty deviceNameAlreadyExistsProperty() {
@@ -211,21 +177,5 @@ public class RegisterDeviceController implements FxController {
return deviceNameAlreadyExists.get(); return deviceNameAlreadyExists.get();
} }
public BooleanProperty invalidSetupCodeProperty() {
return invalidSetupCode;
}
public boolean isInvalidSetupCode() {
return invalidSetupCode.get();
}
@JsonIgnoreProperties(ignoreUnknown = true)
private record UserDto(String id, String name, String publicKey, String privateKey, String setupCode) {}
private record CreateDeviceDto(@JsonProperty(required = true) String id, //
@JsonProperty(required = true) String name, //
@JsonProperty(required = true) String publicKey, //
@JsonProperty(required = true, defaultValue = "DESKTOP") String type, //
@JsonProperty(required = true) String userPrivateKey, //
@JsonProperty(required = true) String creationTime) {}
} }
@@ -12,10 +12,10 @@ import java.util.concurrent.CompletableFuture;
public class RegisterFailedController implements FxController { public class RegisterFailedController implements FxController {
private final Stage window; private final Stage window;
private final CompletableFuture<ReceivedKey> result; private final CompletableFuture<JWEObject> result;
@Inject @Inject
public RegisterFailedController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result) { public RegisterFailedController(@KeyLoading Stage window, CompletableFuture<JWEObject> result) {
this.window = window; this.window = window;
this.result = result; this.result = result;
} }
@@ -15,10 +15,10 @@ import java.util.concurrent.CompletableFuture;
public class UnauthorizedDeviceController implements FxController { public class UnauthorizedDeviceController implements FxController {
private final Stage window; private final Stage window;
private final CompletableFuture<ReceivedKey> result; private final CompletableFuture<JWEObject> result;
@Inject @Inject
public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result) { public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture<JWEObject> result) {
this.window = window; this.window = window;
this.result = result; this.result = result;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
@@ -26,7 +26,6 @@ public interface MainWindowComponent {
default Stage showMainWindow() { default Stage showMainWindow() {
Stage stage = window(); Stage stage = window();
stage.setScene(scene().get()); stage.setScene(scene().get());
stage.setIconified(false);
stage.show(); stage.show();
stage.toFront(); stage.toFront();
stage.requestFocus(); stage.requestFocus();
@@ -16,7 +16,6 @@ import javafx.stage.Stage;
public class MainWindowSceneFactory extends DefaultSceneFactory { public class MainWindowSceneFactory extends DefaultSceneFactory {
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
protected static final KeyCodeCombination SHORTCUT_O = new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN);
private final Lazy<MainWindowTitleController> mainWindowTitleController; private final Lazy<MainWindowTitleController> mainWindowTitleController;
private final Lazy<VaultListController> vaultListController; private final Lazy<VaultListController> vaultListController;
@@ -35,7 +34,6 @@ public class MainWindowSceneFactory extends DefaultSceneFactory {
} else { } else {
scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close); scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
} }
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddNewVault); scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddVault);
scene.getAccelerators().put(SHORTCUT_O, vaultListController.get()::didClickAddExistingVault);
} }
} }
@@ -6,14 +6,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D; import javafx.geometry.Rectangle2D;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.stage.Screen; import javafx.stage.Screen;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.WindowEvent;
@MainWindow @MainWindow
public class ResizeController implements FxController { public class ResizeController implements FxController {
@@ -52,57 +53,48 @@ public class ResizeController implements FxController {
public void initialize() { public void initialize() {
LOG.trace("init ResizeController"); LOG.trace("init ResizeController");
if (!neverTouched()) { if (neverTouched()) {
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight()); settings.displayConfiguration.set(getMonitorSizes());
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth()); return;
window.setX(settings.windowXPosition.get()); } else {
window.setY(settings.windowYPosition.get()); if (didDisplayConfigurationChange()) {
//If the position is illegal, then the window appears on the main screen in the middle of the window.
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2);
window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2);
window.setWidth(window.getMinWidth());
window.setHeight(window.getMinHeight());
} else {
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
}
} }
savePositionalSettings();
window.setOnShowing(this::checkDisplayBounds);
} }
private boolean neverTouched() { private boolean neverTouched() {
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0); return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
} }
private void checkDisplayBounds(WindowEvent evt) { private boolean didDisplayConfigurationChange() {
// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000) String currentDisplayConfiguration = getMonitorSizes();
// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453 String settingsDisplayConfiguration = settings.displayConfiguration.get();
// If the position is (-32000, -32000), restore to the last saved position boolean configurationHasChanged = !settingsDisplayConfiguration.equals(currentDisplayConfiguration);
if (window.getX() == -32000 && window.getY() == -32000) { if (configurationHasChanged) settings.displayConfiguration.set(currentDisplayConfiguration);
window.setX(settings.windowXPosition.get()); return configurationHasChanged;
window.setY(settings.windowYPosition.get());
window.setWidth(settings.windowWidth.get());
window.setHeight(settings.windowHeight.get());
}
if (isOutOfDisplayBounds()) {
// If the position is illegal, then the window appears on the main screen in the middle of the window.
LOG.debug("Resetting window position due to insufficient screen overlap");
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2);
window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2);
window.setWidth(window.getMinWidth());
window.setHeight(window.getMinHeight());
savePositionalSettings();
}
} }
private boolean isOutOfDisplayBounds() { private String getMonitorSizes() {
// define a rect which is inset on all sides from the window's rect: ObservableList<Screen> screens = Screen.getScreens();
final double x = window.getX() + 20; // 20px left StringBuilder sb = new StringBuilder();
final double y = window.getY() + 5; // 5px top for (int i = 0; i < screens.size(); i++) {
final double w = window.getWidth() - 40; // 20px left + 20px right Rectangle2D screenBounds = screens.get(i).getBounds();
final double h = window.getHeight() - 25; // 5px top + 20px bottom if (!sb.isEmpty()) sb.append(" ");
return isRectangleOutOfScreen(x, y, 0, h) // Left pixel column sb.append("displayId: " + i + ", " + screenBounds.getWidth() + "x" + screenBounds.getHeight() + ";");
|| isRectangleOutOfScreen(x + w, y, 0, h) // Right pixel column }
|| isRectangleOutOfScreen(x, y, w, 0) // Top pixel row return sb.toString();
|| isRectangleOutOfScreen(x, y + h, w, 0); // Bottom pixel row
}
private boolean isRectangleOutOfScreen(double x, double y, double width, double height) {
return Screen.getScreensForRectangle(x, y, width, height).isEmpty();
} }
private void startResize(MouseEvent evt) { private void startResize(MouseEvent evt) {
@@ -191,4 +183,5 @@ public class ResizeController implements FxController {
public boolean isShowResizingArrows() { public boolean isShowResizingArrows() {
return showResizingArrows.get(); return showResizingArrows.get();
} }
} }
@@ -21,9 +21,6 @@ import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.input.ContextMenuEvent; import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.DragEvent; import javafx.scene.input.DragEvent;
@@ -37,7 +34,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.ResourceBundle;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -63,23 +59,12 @@ public class VaultListController implements FxController {
private final RemoveVaultComponent.Builder removeVaultDialogue; private final RemoveVaultComponent.Builder removeVaultDialogue;
private final VaultListManager vaultListManager; private final VaultListManager vaultListManager;
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty(); private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
private final ResourceBundle resourceBundle;
public ListView<Vault> vaultList; public ListView<Vault> vaultList;
public StackPane root; public StackPane root;
public Button addVaultBtn;
@FXML
private ContextMenu addVaultContextMenu;
@Inject @Inject
VaultListController(@MainWindow Stage mainWindow, // VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue, VaultListManager vaultListManager) {
ObservableList<Vault> vaults, //
ObjectProperty<Vault> selectedVault, //
VaultListCellFactory cellFactory, //
AddVaultWizardComponent.Builder addVaultWizard, //
RemoveVaultComponent.Builder removeVaultDialogue, //
VaultListManager vaultListManager, //
ResourceBundle resourceBundle) {
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
this.vaults = vaults; this.vaults = vaults;
this.selectedVault = selectedVault; this.selectedVault = selectedVault;
@@ -87,7 +72,6 @@ public class VaultListController implements FxController {
this.addVaultWizard = addVaultWizard; this.addVaultWizard = addVaultWizard;
this.removeVaultDialogue = removeVaultDialogue; this.removeVaultDialogue = removeVaultDialogue;
this.vaultListManager = vaultListManager; this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle;
this.emptyVaultList = Bindings.isEmpty(vaults); this.emptyVaultList = Bindings.isEmpty(vaults);
@@ -145,15 +129,6 @@ public class VaultListController implements FxController {
root.setOnDragExited(this::handleDragEvent); root.setOnDragExited(this::handleDragEvent);
} }
@FXML
private void toggleMenu() {
if (addVaultContextMenu.isShowing()) {
addVaultContextMenu.hide();
} else {
addVaultContextMenu.show(addVaultBtn, Side.BOTTOM, 0.0, 0.0);
}
}
private void deselect(MouseEvent released) { private void deselect(MouseEvent released) {
if (released.getY() > (vaultList.getItems().size() * vaultList.fixedCellSizeProperty().get())) { if (released.getY() > (vaultList.getItems().size() * vaultList.fixedCellSizeProperty().get())) {
vaultList.getSelectionModel().clearSelection(); vaultList.getSelectionModel().clearSelection();
@@ -169,13 +144,8 @@ public class VaultListController implements FxController {
} }
@FXML @FXML
public void didClickAddNewVault() { public void didClickAddVault() {
addVaultWizard.build().showAddNewVaultWizard(resourceBundle); addVaultWizard.build().showAddVaultWizard();
}
@FXML
public void didClickAddExistingVault() {
addVaultWizard.build().showAddExistingVaultWizard(resourceBundle);
} }
private void pressedShortcutToRemoveVault() { private void pressedShortcutToRemoveVault() {
@@ -1,6 +1,5 @@
package org.cryptomator.ui.preferences; package org.cryptomator.ui.preferences;
import org.cryptomator.common.Environment;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.UpdateChecker; import org.cryptomator.ui.fxapp.UpdateChecker;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -20,7 +19,6 @@ public class PreferencesController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(PreferencesController.class); private static final Logger LOG = LoggerFactory.getLogger(PreferencesController.class);
private final Environment env;
private final Stage window; private final Stage window;
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty; private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
private final BooleanBinding updateAvailable; private final BooleanBinding updateAvailable;
@@ -33,8 +31,7 @@ public class PreferencesController implements FxController {
public Tab aboutTab; public Tab aboutTab;
@Inject @Inject
public PreferencesController(Environment env, @PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) { public PreferencesController(@PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
this.env = env;
this.window = window; this.window = window;
this.selectedTabProperty = selectedTabProperty; this.selectedTabProperty = selectedTabProperty;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull(); this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
@@ -45,9 +42,6 @@ public class PreferencesController implements FxController {
window.setOnShowing(this::windowWillAppear); window.setOnShowing(this::windowWillAppear);
selectedTabProperty.addListener(observable -> this.selectChosenTab()); selectedTabProperty.addListener(observable -> this.selectChosenTab());
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged()); tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
if (env.disableUpdateCheck()) {
tabPane.getTabs().remove(updatesTab);
}
} }
private void selectChosenTab() { private void selectChosenTab() {
@@ -8,7 +8,6 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -59,7 +58,7 @@ public class RecoveryKeyFactory {
} }
} }
@VisibleForTesting // visible for testing
String createRecoveryKey(byte[] rawKey) { String createRecoveryKey(byte[] rawKey) {
Preconditions.checkArgument(rawKey.length == 64, "key should be 64 bytes"); Preconditions.checkArgument(rawKey.length == 64, "key should be 64 bytes");
byte[] paddedKey = Arrays.copyOf(rawKey, 66); byte[] paddedKey = Arrays.copyOf(rawKey, 66);
@@ -7,7 +7,6 @@ import org.cryptomator.integrations.common.Priority;
import org.cryptomator.integrations.tray.ActionItem; import org.cryptomator.integrations.tray.ActionItem;
import org.cryptomator.integrations.tray.SeparatorItem; import org.cryptomator.integrations.tray.SeparatorItem;
import org.cryptomator.integrations.tray.SubMenuItem; import org.cryptomator.integrations.tray.SubMenuItem;
import org.cryptomator.integrations.tray.TrayIconLoader;
import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.integrations.tray.TrayMenuException; import org.cryptomator.integrations.tray.TrayMenuException;
import org.cryptomator.integrations.tray.TrayMenuItem; import org.cryptomator.integrations.tray.TrayMenuItem;
@@ -15,7 +14,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.awt.AWTException; import java.awt.AWTException;
import java.awt.Image;
import java.awt.Menu; import java.awt.Menu;
import java.awt.MenuItem; import java.awt.MenuItem;
import java.awt.PopupMenu; import java.awt.PopupMenu;
@@ -25,12 +23,7 @@ import java.awt.TrayIcon;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
/**
* Fallback tray icon implementation using AWT. This will only be used if no better implementation is found.
* @see <a href="https://github.com/cryptomator/integrations-linux/blob/33f9a4685b781b55fcce399b8618818bfc08cbdf/src/main/java/org/cryptomator/linux/tray/AppindicatorTrayMenuController.java">preferred AppIndicator-based implementation used on Linux</a>
*/
@CheckAvailability @CheckAvailability
@Priority(Priority.FALLBACK) @Priority(Priority.FALLBACK)
public class AwtTrayMenuController implements TrayMenuController { public class AwtTrayMenuController implements TrayMenuController {
@@ -39,7 +32,6 @@ public class AwtTrayMenuController implements TrayMenuController {
private final PopupMenu menu = new PopupMenu(); private final PopupMenu menu = new PopupMenu();
private TrayIcon trayIcon; private TrayIcon trayIcon;
private Image image;
@CheckAvailability @CheckAvailability
public static boolean isAvailable() { public static boolean isAvailable() {
@@ -47,9 +39,8 @@ public class AwtTrayMenuController implements TrayMenuController {
} }
@Override @Override
public void showTrayIcon(Consumer<TrayIconLoader> iconLoader, Runnable defaultAction, String tooltip) throws TrayMenuException { public void showTrayIcon(byte[] imageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
TrayIconLoader.PngData callback = this::showTrayIconWithPngData; var image = Toolkit.getDefaultToolkit().createImage(imageData);
iconLoader.accept(callback);
trayIcon = new TrayIcon(image, tooltip, menu); trayIcon = new TrayIcon(image, tooltip, menu);
trayIcon.setImageAutoSize(true); trayIcon.setImageAutoSize(true);
@@ -65,17 +56,8 @@ public class AwtTrayMenuController implements TrayMenuController {
} }
} }
private void showTrayIconWithPngData(byte[] imageData) {
image = Toolkit.getDefaultToolkit().createImage(imageData);
}
@Override @Override
public void updateTrayIcon(Consumer<TrayIconLoader> iconLoader) { public void updateTrayIcon(byte[] imageData) {
TrayIconLoader.PngData callback = this::updateTrayIconWithPngData;
iconLoader.accept(callback);
}
private void updateTrayIconWithPngData(byte[] imageData) {
if (trayIcon == null) { if (trayIcon == null) {
throw new IllegalStateException("Failed to update the icon as it has not yet been added"); throw new IllegalStateException("Failed to update the icon as it has not yet been added");
} }
@@ -118,4 +100,5 @@ public class AwtTrayMenuController implements TrayMenuController {
} }
} }
} }
} }
@@ -7,7 +7,6 @@ import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.integrations.tray.ActionItem; import org.cryptomator.integrations.tray.ActionItem;
import org.cryptomator.integrations.tray.SeparatorItem; import org.cryptomator.integrations.tray.SeparatorItem;
import org.cryptomator.integrations.tray.SubMenuItem; import org.cryptomator.integrations.tray.SubMenuItem;
import org.cryptomator.integrations.tray.TrayIconLoader;
import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.integrations.tray.TrayMenuException; import org.cryptomator.integrations.tray.TrayMenuException;
import org.cryptomator.integrations.tray.TrayMenuItem; import org.cryptomator.integrations.tray.TrayMenuItem;
@@ -66,12 +65,7 @@ public class TrayMenuBuilder {
}); });
try { try {
trayMenu.showTrayIcon(loader -> { trayMenu.showTrayIcon(getAppropriateTrayIconImage(), this::showMainWindow, "Cryptomator");
switch (loader) {
case TrayIconLoader.PngData l -> l.loadPng(getAppropriateTrayIconImage());
case TrayIconLoader.FreedesktopIconName l -> l.lookupByName(getAppropriateFreedesktopIconName());
}
}, this::showMainWindow, "Cryptomator");
trayMenu.onBeforeOpenMenu(() -> { trayMenu.onBeforeOpenMenu(() -> {
for (Vault vault : vaults) { for (Vault vault : vaults) {
VaultListManager.redetermineVaultState(vault); VaultListManager.redetermineVaultState(vault);
@@ -90,12 +84,7 @@ public class TrayMenuBuilder {
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) { private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
assert Platform.isFxApplicationThread(); assert Platform.isFxApplicationThread();
trayMenu.updateTrayIcon(loader -> { trayMenu.updateTrayIcon(getAppropriateTrayIconImage());
switch (loader) {
case TrayIconLoader.PngData l -> l.loadPng(getAppropriateTrayIconImage());
case TrayIconLoader.FreedesktopIconName l -> l.lookupByName(getAppropriateFreedesktopIconName());
}
});
rebuildMenu(); rebuildMenu();
} }
@@ -184,8 +173,4 @@ public class TrayMenuBuilder {
} }
} }
private String getAppropriateFreedesktopIconName() {
boolean isAnyVaultUnlocked = vaults.stream().anyMatch(Vault::isUnlocked);
return isAnyVaultUnlocked ? "org.cryptomator.Cryptomator.tray-unlocked-symbolic" : "org.cryptomator.Cryptomator.tray-symbolic";
}
} }
@@ -1,12 +1,7 @@
package org.cryptomator.ui.unlock; package org.cryptomator.ui.unlock;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.mount.HideawayNotDirectoryException;
import org.cryptomator.common.mount.IllegalMountPointException;
import org.cryptomator.common.mount.MountPointCleanupFailedException;
import org.cryptomator.common.mount.MountPointInUseException; import org.cryptomator.common.mount.MountPointInUseException;
import org.cryptomator.common.mount.MountPointNotEmptyDirectoryException; import org.cryptomator.common.mount.MountPointNotExistsException;
import org.cryptomator.common.mount.MountPointNotExistingException;
import org.cryptomator.common.mount.MountPointNotSupportedException; import org.cryptomator.common.mount.MountPointNotSupportedException;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
@@ -14,15 +9,12 @@ import org.cryptomator.ui.controls.FormattedLabel;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.jetbrains.annotations.PropertyKey;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.nio.file.Path;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
//At the current point in time only the CustomMountPointChooser may cause this window to be shown. //At the current point in time only the CustomMountPointChooser may cause this window to be shown.
@UnlockScoped @UnlockScoped
@@ -31,31 +23,28 @@ public class UnlockInvalidMountPointController implements FxController {
private final Stage window; private final Stage window;
private final Vault vault; private final Vault vault;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
private final ResourceBundle resourceBundle;
private final ObservableValue<ExceptionType> exceptionType; private final ExceptionType exceptionType;
private final ObservableValue<Path> exceptionPath; private final String exceptionMessage;
private final ObservableValue<String> exceptionMessage;
private final ObservableValue<Path> hideawayPath;
private final ObservableValue<String> format;
private final ObservableValue<Boolean> showPreferences;
private final ObservableValue<Boolean> showVaultOptions;
public FormattedLabel dialogDescription; public FormattedLabel dialogDescription;
@Inject @Inject
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) { UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow AtomicReference<Throwable> unlockException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
this.window = window; this.window = window;
this.vault = vault; this.vault = vault;
this.appWindows = appWindows; this.appWindows = appWindows;
this.resourceBundle = resourceBundle;
this.exceptionType = illegalMountPointException.map(this::getExceptionType); var exc = unlockException.get();
this.exceptionPath = illegalMountPointException.map(IllegalMountPointException::getMountpoint); this.exceptionType = getExceptionType(exc);
this.exceptionMessage = illegalMountPointException.map(IllegalMountPointException::getMessage); this.exceptionMessage = exc.getMessage();
this.hideawayPath = illegalMountPointException.map(e -> e instanceof HideawayNotDirectoryException haeExc ? haeExc.getHideaway() : null); }
this.format = ObservableUtil.mapWithDefault(exceptionType, type -> resourceBundle.getString(type.translationKey), ""); @FXML
this.showPreferences = ObservableUtil.mapWithDefault(exceptionType, type -> type.action == ButtonAction.SHOW_PREFERENCES, false); public void initialize() {
this.showVaultOptions = ObservableUtil.mapWithDefault(exceptionType, type -> type.action == ButtonAction.SHOW_VAULT_OPTIONS, false); dialogDescription.setFormat(resourceBundle.getString(exceptionType.translationKey));
dialogDescription.setArg1(exceptionMessage);
} }
@FXML @FXML
@@ -78,11 +67,8 @@ public class UnlockInvalidMountPointController implements FxController {
private ExceptionType getExceptionType(Throwable unlockException) { private ExceptionType getExceptionType(Throwable unlockException) {
return switch (unlockException) { return switch (unlockException) {
case MountPointNotSupportedException x -> ExceptionType.NOT_SUPPORTED; case MountPointNotSupportedException x -> ExceptionType.NOT_SUPPORTED;
case MountPointNotExistingException x -> ExceptionType.NOT_EXISTING; case MountPointNotExistsException x -> ExceptionType.NOT_EXISTING;
case MountPointInUseException x -> ExceptionType.IN_USE; case MountPointInUseException x -> ExceptionType.IN_USE;
case HideawayNotDirectoryException x -> ExceptionType.HIDEAWAY_NOT_DIR;
case MountPointCleanupFailedException x -> ExceptionType.COULD_NOT_BE_CLEARED;
case MountPointNotEmptyDirectoryException x -> ExceptionType.NOT_EMPTY_DIRECTORY;
default -> ExceptionType.GENERIC; default -> ExceptionType.GENERIC;
}; };
} }
@@ -92,15 +78,12 @@ public class UnlockInvalidMountPointController implements FxController {
NOT_SUPPORTED("unlock.error.customPath.description.notSupported", ButtonAction.SHOW_PREFERENCES), NOT_SUPPORTED("unlock.error.customPath.description.notSupported", ButtonAction.SHOW_PREFERENCES),
NOT_EXISTING("unlock.error.customPath.description.notExists", ButtonAction.SHOW_VAULT_OPTIONS), NOT_EXISTING("unlock.error.customPath.description.notExists", ButtonAction.SHOW_VAULT_OPTIONS),
IN_USE("unlock.error.customPath.description.inUse", ButtonAction.SHOW_VAULT_OPTIONS), IN_USE("unlock.error.customPath.description.inUse", ButtonAction.SHOW_VAULT_OPTIONS),
HIDEAWAY_NOT_DIR("unlock.error.customPath.description.hideawayNotDir", ButtonAction.SHOW_VAULT_OPTIONS),
COULD_NOT_BE_CLEARED("unlock.error.customPath.description.couldNotBeCleaned", ButtonAction.SHOW_VAULT_OPTIONS),
NOT_EMPTY_DIRECTORY("unlock.error.customPath.description.notEmptyDir", ButtonAction.SHOW_VAULT_OPTIONS),
GENERIC("unlock.error.customPath.description.generic", ButtonAction.SHOW_PREFERENCES); GENERIC("unlock.error.customPath.description.generic", ButtonAction.SHOW_PREFERENCES);
private final String translationKey; private final String translationKey;
private final ButtonAction action; private final ButtonAction action;
ExceptionType(@PropertyKey(resourceBundle = "i18n.strings") String translationKey, ButtonAction action) { ExceptionType(String translationKey, ButtonAction action) {
this.translationKey = translationKey; this.translationKey = translationKey;
this.action = action; this.action = action;
} }
@@ -108,7 +91,6 @@ public class UnlockInvalidMountPointController implements FxController {
private enum ButtonAction { private enum ButtonAction {
//TODO Add option to show filesystem, e.g. for ExceptionType.HIDEAWAY_EXISTS
SHOW_PREFERENCES, SHOW_PREFERENCES,
SHOW_VAULT_OPTIONS; SHOW_VAULT_OPTIONS;
@@ -116,51 +98,11 @@ public class UnlockInvalidMountPointController implements FxController {
/* Getter */ /* Getter */
public Path getExceptionPath() { public boolean isShowPreferences() {
return exceptionPath.getValue(); return exceptionType.action == ButtonAction.SHOW_PREFERENCES;
} }
public ObservableValue<Path> exceptionPathProperty() { public boolean isShowVaultOptions() {
return exceptionPath; return exceptionType.action == ButtonAction.SHOW_VAULT_OPTIONS;
}
public String getFormat() {
return format.getValue();
}
public ObservableValue<String> formatProperty() {
return format;
}
public String getExceptionMessage() {
return exceptionMessage.getValue();
}
public ObservableValue<String> exceptionMessageProperty() {
return exceptionMessage;
}
public Path getHideawayPath() {
return hideawayPath.getValue();
}
public ObservableValue<Path> hideawayPathProperty() {
return hideawayPath;
}
public Boolean getShowPreferences() {
return showPreferences.getValue();
}
public ObservableValue<Boolean> showPreferencesProperty() {
return showPreferences;
}
public Boolean getShowVaultOptions() {
return showVaultOptions.getValue();
}
public ObservableValue<Boolean> showVaultOptionsProperty() {
return showVaultOptions;
} }
} }
@@ -4,7 +4,6 @@ import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
import org.cryptomator.common.mount.IllegalMountPointException;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
@@ -19,13 +18,12 @@ import org.jetbrains.annotations.Nullable;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Provider; import javax.inject.Provider;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
@Module(subcomponents = {KeyLoadingComponent.class}) @Module(subcomponents = {KeyLoadingComponent.class})
abstract class UnlockModule { abstract class UnlockModule {
@@ -63,8 +61,8 @@ abstract class UnlockModule {
@Provides @Provides
@UnlockWindow @UnlockWindow
@UnlockScoped @UnlockScoped
static ObjectProperty<IllegalMountPointException> illegalMountPointException() { static AtomicReference<Throwable> unlockException() {
return new SimpleObjectProperty<>(); return new AtomicReference<>();
} }
@Provides @Provides
@@ -17,11 +17,11 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* A multi-step task that consists of background activities as well as user interaction. * A multi-step task that consists of background activities as well as user interaction.
@@ -40,10 +40,10 @@ public class UnlockWorkflow extends Task<Boolean> {
private final Lazy<Scene> invalidMountPointScene; private final Lazy<Scene> invalidMountPointScene;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
private final KeyLoadingStrategy keyLoadingStrategy; private final KeyLoadingStrategy keyLoadingStrategy;
private final ObjectProperty<IllegalMountPointException> illegalMountPointException; private final AtomicReference<Throwable> unlockFailedException;
@Inject @Inject
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException) { UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow AtomicReference<Throwable> unlockFailedException) {
this.window = window; this.window = window;
this.vault = vault; this.vault = vault;
this.vaultService = vaultService; this.vaultService = vaultService;
@@ -51,7 +51,7 @@ public class UnlockWorkflow extends Task<Boolean> {
this.invalidMountPointScene = invalidMountPointScene; this.invalidMountPointScene = invalidMountPointScene;
this.appWindows = appWindows; this.appWindows = appWindows;
this.keyLoadingStrategy = keyLoadingStrategy; this.keyLoadingStrategy = keyLoadingStrategy;
this.illegalMountPointException = illegalMountPointException; this.unlockFailedException = unlockFailedException;
} }
@Override @Override
@@ -79,7 +79,7 @@ public class UnlockWorkflow extends Task<Boolean> {
private void handleIllegalMountPointError(IllegalMountPointException impe) { private void handleIllegalMountPointError(IllegalMountPointException impe) {
Platform.runLater(() -> { Platform.runLater(() -> {
illegalMountPointException.set(impe); unlockFailedException.set(impe);
window.setScene(invalidMountPointScene.get()); window.setScene(invalidMountPointScene.get());
window.show(); window.show();
}); });
-10
View File
@@ -795,16 +795,6 @@
-fx-scale-shape: false; -fx-scale-shape: false;
} }
/*******************************************************************************
* *
* Add Vault - MenuItem *
* *
******************************************************************************/
.add-vault-menu-item {
-fx-padding: 4px 8px;
}
/******************************************************************************* /*******************************************************************************
* * * *
* ProgressBar * * ProgressBar *
-10
View File
@@ -794,16 +794,6 @@
-fx-scale-shape: false; -fx-scale-shape: false;
} }
/*******************************************************************************
* *
* Add Vault - MenuItem *
* *
******************************************************************************/
.add-vault-menu-item {
-fx-padding: 4px 8px;
}
/******************************************************************************* /*******************************************************************************
* * * *
* ProgressBar * * ProgressBar *
@@ -24,8 +24,9 @@
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+X"> <ButtonBar buttonMinWidth="120" buttonOrder="B+X">
<buttons> <buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
<Button fx:id="finishButton" text="%addvaultwizard.existing.chooseBtn" ButtonBar.buttonData="NEXT_FORWARD" onAction="#chooseFileAndNext" defaultButton="true"/> <Button fx:id="finishButton" text="%addvaultwizard.existing.chooseBtn" ButtonBar.buttonData="NEXT_FORWARD" onAction="#chooseFileAndNext" defaultButton="true"/>
</buttons> </buttons>
</ButtonBar> </ButtonBar>
@@ -68,8 +68,9 @@
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+X"> <ButtonBar buttonMinWidth="120" buttonOrder="B+X">
<buttons> <buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!controller.validVaultName}"/> <Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!controller.validVaultName}"/>
</buttons> </buttons>
</ButtonBar> </ButtonBar>
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultWelcomeController"
prefWidth="450"
prefHeight="450"
spacing="12"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>
<children>
<Region VBox.vgrow="ALWAYS"/>
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
<Image url="@../img/logo.png"/>
</ImageView>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="CENTER" spacing="9">
<Button styleClass="button-large" text="%addvaultwizard.welcome.newButton" onAction="#createNewVault" prefWidth="Infinity">
<graphic>
<FontAwesome5IconView glyph="MAGIC" glyphSize="15"/>
</graphic>
</Button>
<Button styleClass="button-large" text="%addvaultwizard.welcome.existingButton" onAction="#chooseExistingVault" prefWidth="Infinity">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
</graphic>
</Button>
</VBox>
<Region VBox.vgrow="ALWAYS"/>
</children>
</VBox>

Some files were not shown because too many files have changed in this diff Show More