Compare commits

..
157 changed files with 2808 additions and 3811 deletions
+56
View File
@@ -0,0 +1,56 @@
---
name: "Bug Report"
about: "Create a report to help us improve"
labels: type:bug
---
<!--
Please make sure to:
- Comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
- Search for existing similar issues first: https://github.com/cryptomator/cryptomator/issues?q=
⚠️ IMPORTANT: If you don't stick to this template, the issue will get closed.
-->
### Description
[Summarize your problem.]
### System Setup
* Operating system and version: [Windows/macOS/Linux + Version ( + Desktop Environment, if Linux)]
* Cryptomator version: [Shown in the settings]
* Volume type: [Dokany/FUSE/WebDAV, shown in the settings]
### Steps to Reproduce
1. [First step]
2. [Second step]
3. [and so on…]
#### Expected Behavior
[What you expect to happen.]
#### Actual Behavior
[What actually happens.]
#### Reproducibility
[Always/Intermittent/Only once]
### Additional Information
[Any additional information, log files, screenshots, configuration, or data that might be necessary to reproduce the issue.]
<!--
If you want to add the log file or screenshots, please add them as attachments. If your log file seems empty and doesn't show any errors, you may enable the debug mode first. Here is how to do that: https://community.cryptomator.org/t/how-do-i-enable-debug-mode/36
Then reproduce the problem to ensure all important information is contained in there. You may use test data or redact sensitive information from the log file.
Log file location:
- Windows: %appdata%/Cryptomator
- macOS: ~/Library/Logs/Cryptomator
- Linux: ~/.local/share/Cryptomator/logs
-->
-93
View File
@@ -1,93 +0,0 @@
name: Bug Report
description: Create a report to help us improve
labels: ["type:bug"]
body:
- type: checkboxes
id: terms
attributes:
label: Please agree to the following
options:
- label: I have searched [existing issues](https://github.com/cryptomator/cryptomator/issues?q=) for duplicates
required: true
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
required: true
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your problem.
validations:
required: true
- type: textarea
id: software-versions
attributes:
label: What software is involved?
description: |
Examples:
- Operating System: Windows 10
- Cryptomator: 1.5.16
- LibreOffice: 7.1.4
value: |
- Operating System:
- Cryptomator:
- …
validations:
required: true
- type: dropdown
id: volume-type
attributes:
label: Volume Type
description: What is selected under Settings → Virtual Drive?
multiple: true
options:
- FUSE
- Dokany
- WebDAV
validations:
required: false
- type: textarea
id: reproduction-steps
attributes:
label: Steps to Reproduce
value: |
1. [First Step]
2. [Second Step]
3. …
validations:
required: true
- type: textarea
id: expected-behaviour
attributes:
label: Expected Behavior
placeholder: What you expect to happen.
validations:
required: true
- type: textarea
id: actual-behaviour
attributes:
label: Actual Behavior
placeholder: What actually happens.
validations:
required: true
- type: dropdown
id: reproducibility
attributes:
label: Reproducibility
description: How often does the described behaviour occur?
options:
- Always
- Intermittent
- Only once
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant Log Output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: textarea
id: further-info
attributes:
label: Anything else?
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
+27
View File
@@ -0,0 +1,27 @@
---
name: "Feature Request"
about: "Suggest an idea for this project"
labels: type:feature-request
---
<!--
Please make sure to:
- Comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
- Search for existing similar issues first: https://github.com/cryptomator/cryptomator/issues?q=
-->
### Summary
[One paragraph explanation of the feature.]
### Motivation
[Why are we doing this? What use cases does it support? What is the expected outcome?]
### Considered Alternatives
[A clear and concise description of the alternative solutions you've considered.]
### Additional Context
[Add any other context or screenshots about the feature request here.]
-37
View File
@@ -1,37 +0,0 @@
name: Feature Request
description: Suggest an idea for this project
labels: ["type:feature-request"]
body:
- type: checkboxes
id: terms
attributes:
label: Please agree to the following
options:
- label: I have searched [existing issues](https://github.com/cryptomator/cryptomator/issues?q=) for duplicates
required: true
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
required: true
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your feature request.
validations:
required: true
- type: textarea
id: motivation
attributes:
label: Motivation
description: Who requires this feature? What problem does the user face? How would this feature solve the problem?
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Considered Alternatives
description: What current alternatives or workarounds have you considered? Is there a different way to solve the same problem?
- type: textarea
id: context
attributes:
label: Anything else?
description: Any context, suggestions, screenshots, or concepts you want to share?
+126 -13
View File
@@ -1,31 +1,27 @@
name: Build name: Build
on: on:
push: [push]
env:
JAVA_VERSION: 16
defaults:
run:
shell: bash
jobs: jobs:
test: build:
name: Compile and Test name: Build and Test
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@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: ${{ env.JAVA_VERSION }} java-version: 16
- uses: actions/cache@v2 - uses: actions/cache@v1
with: with:
path: ~/.m2/repository path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: | restore-keys: |
${{ runner.os }}-maven- ${{ runner.os }}-maven-
- name: Ensure to use tagged version
run: mvn versions:set -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags'
if: startsWith(github.ref, 'refs/tags/')
- name: Build and Test - name: Build and Test
run: mvn -B clean install jacoco:report -Pcoverage,dependency-check run: mvn -B clean install jacoco:report -Pcoverage,dependency-check
- name: Upload code coverage report - name: Upload code coverage report
@@ -33,4 +29,121 @@ jobs:
run: bash <(curl -Ls https://coverage.codacy.com/get.sh) run: bash <(curl -Ls https://coverage.codacy.com/get.sh)
env: env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
continue-on-error: true continue-on-error: true
### TODO: move to matrix build:
- name: Assemble buildkit-linux.zip
run: mvn -B clean package -DskipTests -Plinux
- name: Upload buildkit-linux.zip
uses: actions/upload-artifact@v1
with:
name: buildkit-linux.zip
path: target/buildkit-linux.zip
- name: Assemble buildkit-mac.zip
run: mvn -B clean package -DskipTests -Pmac
- name: Upload buildkit-mac.zip
uses: actions/upload-artifact@v1
with:
name: buildkit-mac.zip
path: target/buildkit-mac.zip
- name: Assemble buildkit-win.zip
run: mvn -B clean package -DskipTests -Pwindows
- name: Upload buildkit-win.zip
uses: actions/upload-artifact@v1
with:
name: buildkit-win.zip
path: target/buildkit-win.zip
release:
name: Draft a Release on GitHub Releases
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator'
steps:
- uses: actions/checkout@v2
- name: Download buildkit-linux.zip
uses: actions/download-artifact@v1
with:
name: buildkit-linux.zip
path: .
- name: Download buildkit-mac.zip
uses: actions/download-artifact@v1
with:
name: buildkit-mac.zip
path: .
- name: Download buildkit-win.zip
uses: actions/download-artifact@v1
with:
name: buildkit-win.zip
path: .
- name: Create tarball
run: git archive --prefix="cryptomator-${{ github.ref }}/" -o "cryptomator-${{ github.ref }}.tar.gz" ${{ github.ref }}
- name: Sign tarball with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a "cryptomator-${{ github.ref }}.tar.gz"
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: |
:construction: Work in Progress
TODO:
* [ ] add Linux appimage, zsync file and signature file
* [ ] add Windows installer and signature file
* [ ] add MacOs disk image and signature file
## What's new
## Bugfixes
## Misc
---
:scroll: A complete list of closed issues is available [here](LINK)
draft: true
prerelease: false
- name: Upload buildkit-linux.zip to GitHub Releases
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: buildkit-linux.zip
asset_name: buildkit-linux.zip
asset_content_type: application/zip
- name: Upload buildkit-mac.zip to GitHub Releases
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: buildkit-mac.zip
asset_name: buildkit-mac.zip
asset_content_type: application/zip
- name: Upload buildkit-win.zip to GitHub Releases
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: buildkit-win.zip
asset_name: buildkit-win.zip
asset_content_type: application/zip
- name: Upload tarball signature to GitHub Releases
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: "cryptomator-${{ github.ref }}.tar.gz.asc"
asset_name: "cryptomator-${{ github.ref }}.tar.gz.asc"
asset_content_type: application/octet-stream
-529
View File
@@ -1,529 +0,0 @@
name: Installers and Release
on:
workflow_dispatch:
push:
tags: # see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
- '[0-9]+.[0-9]+.[0-9]+'
- '[0-9]+.[0-9]+.[0-9]+-*'
env:
JAVA_VERSION: 16
defaults:
run:
shell: bash
jobs:
#
# Buildkit
#
buildkit:
name: Build ${{ matrix.profile }}-buildkit
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-latest
profile: linux
- os: windows-latest
profile: win
- os: macos-latest
profile: mac
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: ${{ env.JAVA_VERSION }}
- uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Ensure to use tagged version
run: mvn versions:set -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags'
if: startsWith(github.ref, 'refs/tags/')
- name: Build and Test
run: mvn -B clean package -Pdependency-check,${{ matrix.profile }}
- name: Patch buildkit
run: |
cp LICENSE.txt target
cp dist/${{ matrix.profile }}/launcher* target
cp target/cryptomator-*.jar target/mods
- name: Upload ${{ matrix.profile }}-buildkit
uses: actions/upload-artifact@v2
with:
name: ${{ matrix.profile }}-buildkit
path: |
target/libs
target/mods
target/LICENSE.txt
target/${{ matrix.launcher }}
if-no-files-found: error
#
# Release Metadata
#
metadata:
name: Determine Version Metadata
runs-on: ubuntu-latest
outputs:
versionStr: ${{ steps.versions.outputs.versionStr }}
versionNum: ${{ steps.versions.outputs.versionNum }}
revNum: ${{ steps.versions.outputs.revNum }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- id: versions
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION_NUM=`echo ${GITHUB_REF##*/} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
echo "::set-output name=versionStr::${GITHUB_REF##*/}"
echo "::set-output name=versionNum::${VERSION_NUM}"
else
echo "::set-output name=versionStr::SNAPSHOT"
echo "::set-output name=versionNum::99.0.0"
fi
echo "::set-output name=revNum::`git rev-list --count HEAD`"
#
# Application Directory
#
appdir:
name: Create ${{ matrix.profile }}-appdir
needs: [buildkit, metadata]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-latest
profile: linux
jpackageoptions: >
--app-version "${{ needs.metadata.outputs.versionNum }}.${{ needs.metadata.outputs.revNum }}"
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=false"
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.metadata.outputs.revNum }}\""
--resource-dir dist/linux/resources
- os: windows-latest
profile: win
jpackageoptions: >
--app-version "${{ needs.metadata.outputs.versionNum }}.${{ needs.metadata.outputs.revNum }}"
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
--java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.metadata.outputs.revNum }}\""
--resource-dir dist/win/resources
--icon dist/win/resources/Cryptomator.ico
- os: macos-latest
profile: mac
jpackageoptions: >
--app-version "${{ needs.metadata.outputs.versionNum }}"
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.metadata.outputs.revNum }}\""
--mac-package-identifier org.cryptomator
--resource-dir dist/mac/resources
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: ${{ env.JAVA_VERSION }}
- name: Download ${{ matrix.profile }}-buildkit
uses: actions/download-artifact@v2
with:
name: ${{ matrix.profile }}-buildkit
path: buildkit
- name: Create Runtime Image
run: >
${JAVA_HOME}/bin/jlink
--verbose
--output runtime
--module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility
--no-header-files
--no-man-pages
--strip-debug
--compress=1
- name: Create App Directory
run: >
${JAVA_HOME}/bin/jpackage
--verbose
--type app-image
--runtime-image runtime
--input buildkit/libs
--module-path buildkit/mods
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2021 Skymatic GmbH"
--java-options "-Xss5m"
--java-options "-Xmx256m"
${{ matrix.jpackageoptions }}
- name: Create appdir.tar
run: tar -cvf appdir.tar appdir
- name: Upload ${{ matrix.profile }}-appdir
uses: actions/upload-artifact@v2
with:
name: ${{ matrix.profile }}-appdir
path: appdir.tar
if-no-files-found: error
#
# Linux Cryptomator.AppImage
#
linux-appimage:
name: Build Cryptomator.AppImage
runs-on: ubuntu-latest
needs: [appdir, metadata]
steps:
- uses: actions/checkout@v2
- name: Download linux-appdir
uses: actions/download-artifact@v2
with:
name: linux-appdir
- name: Untar appdir.tar
run: |
tar -xvf appdir.tar
- name: Patch Cryptomator.AppDir
run: |
mv appdir/Cryptomator Cryptomator.AppDir
cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/
envsubst '${REVISION_NO}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
env:
REVISION_NO: ${{ needs.metadata.outputs.revNum }}
- name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27
run: |
JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'`
${JAVA_HOME}/bin/jar -xf lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/
mv jni/x86_64-Linux/* lib/app/libjffi.so
working-directory: Cryptomator.AppDir
- name: Download AppImageKit
run: |
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage
chmod +x appimagetool.AppImage
./appimagetool.AppImage --appimage-extract
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign Cryptomator.AppDir/AppRun
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Build AppImage
run: >
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.metadata.outputs.versionStr }}-x86_64.AppImage
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
- name: Upload AppImage
uses: actions/upload-artifact@v2
with:
name: linux-appimage
path: |
cryptomator-*.AppImage
cryptomator-*.AppImage.zsync
if-no-files-found: error
#
# macOS Cryptomator.app
#
mac-app:
name: Build Cryptomator.app
runs-on: macos-latest
needs: [appdir, metadata]
steps:
- uses: actions/checkout@v2
- name: Download mac-appdir
uses: actions/download-artifact@v2
with:
name: mac-appdir
- name: Untar appdir.tar
run: tar -xvf appdir.tar
- name: Patch Cryptomator.app
run: |
mv appdir/Cryptomator.app Cryptomator.app
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
env:
VERSION_NO: ${{ needs.metadata.outputs.versionNum }}
REVISION_NO: ${{ needs.metadata.outputs.revNum }}
- name: Install codesign certificate
env:
CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }}
CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }}
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12
KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
security set-keychain-settings -lut 900 $KEYCHAIN_PATH
security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Codesign
env:
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
run: |
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
JAR_FILENAME=$(basename ${JAR_PATH})
OUTPUT_PATH=${JAR_PATH%.*}
echo "Codesigning libs in ${JAR_FILENAME}..."
unzip -q ${JAR_PATH} -d ${OUTPUT_PATH}
find ${OUTPUT_PATH} -name '*.dylib' -exec codesign -s ${CODESIGN_IDENTITY} {} \;
find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign -s ${CODESIGN_IDENTITY} {} \;
rm ${JAR_PATH}
pushd ${OUTPUT_PATH} > /dev/null
zip -qr ../${JAR_FILENAME} *
popd > /dev/null
rm -r ${OUTPUT_PATH}
fi
done
echo "Codesigning Cryptomator.app..."
codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app
- name: Clean up codesign certificate
if: ${{ always() }}
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
- name: Create app.tar
run: tar -cvf app.tar Cryptomator.app
- name: Upload mac-app
uses: actions/upload-artifact@v2
with:
name: mac-app
path: app.tar
if-no-files-found: error
#
# macOS Cryptomator.dmg
#
mac-dmg:
name: Build Cryptomator.dmg
runs-on: macos-11
needs: [mac-app, metadata]
steps:
- uses: actions/checkout@v2
- name: Download mac-appdir
uses: actions/download-artifact@v2
with:
name: mac-app
- name: Untar app.tar
run: tar -xvf app.tar
- name: Prepare .dmg contents
run: |
mkdir dmg
mv Cryptomator.app dmg
cp dist/mac/dmg/resources/macFUSE.webloc dmg
ls -l dmg
- name: Install create-dmg
run: |
brew install create-dmg
create-dmg --help
- name: Create .dmg
run: >
create-dmg
--volname Cryptomator
--volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns"
--background "dist/mac/dmg/resources/Cryptomator-background.tiff"
--window-pos 400 100
--window-size 640 694
--icon-size 128
--icon "Cryptomator.app" 128 245
--hide-extension "Cryptomator.app"
--icon "macFUSE.webloc" 320 501
--hide-extension "macFUSE.webloc"
--app-drop-link 512 245
--eula "dist/mac/dmg/resources/license.rtf"
--icon ".background" 128 758
--icon ".fseventsd" 320 758
--icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NO}.dmg dmg
env:
VERSION_NO: ${{ needs.metadata.outputs.versionNum }}
- name: Install notarization credentials
env:
NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }}
NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }}
NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }}
run: |
# create temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db
security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH}
security set-keychain-settings -lut 900 ${KEYCHAIN_PATH}
security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH}
# import credentials from secrets
sudo xcode-select -s /Applications/Xcode_13.0.app
xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}"
- name: Notarize .dmg
env:
NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }}
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db
sudo xcode-select -s /Applications/Xcode_13.0.app
xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait
xcrun stapler staple Cryptomator-*.dmg
- name: Clean up notarization credentials
if: ${{ always() }}
run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db
- name: Upload mac-dmg
uses: actions/upload-artifact@v2
with:
name: mac-dmg
path: Cryptomator-*.dmg
if-no-files-found: error
#
# MSI package
#
win-msi:
name: Build Cryptomator.msi
runs-on: windows-latest
needs: [appdir, metadata]
steps:
- uses: actions/checkout@v2
- name: Download win-appdir
uses: actions/download-artifact@v2
with:
name: win-appdir
- name: Untar appdir.tar
run: tar -xvf appdir.tar
- uses: actions/setup-java@v1
with:
java-version: ${{ env.JAVA_VERSION }}
- name: Patch Application Directory
run: |
cp dist/win/contrib/* appdir/Cryptomator
- name: Fix permissions
run: attrib -r appdir/Cryptomator/Cryptomator.exe
shell: pwsh
- name: Codesign
uses: skymatic/code-sign-action@v1
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
description: Cryptomator
timestampUrl: 'http://timestamp.digicert.com'
folder: appdir/Cryptomator
recursive: true
- name: Create MSI
run: >
${JAVA_HOME}/bin/jpackage
--verbose
--type msi
--win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775
--app-image appdir/Cryptomator
--dest installer
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2021 Skymatic GmbH"
--app-version "${{ needs.metadata.outputs.versionNum }}"
--win-menu
--win-dir-chooser
--resource-dir dist/win/resources
--license-file dist/win/resources/license.rtf
--file-associations dist/win/resources/FAencryptedData.properties
--file-associations dist/win/resources/FAvaultFile.properties
env:
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
- name: Codesign MSI
uses: skymatic/code-sign-action@v1
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
description: Cryptomator Installer
timestampUrl: 'http://timestamp.digicert.com'
folder: installer
- name: Upload win-msi
uses: actions/upload-artifact@v2
with:
name: win-msi
path: installer/*.msi
if-no-files-found: error
#
# Release
#
release:
name: Draft a release on Github
runs-on: ubuntu-latest
needs: [metadata,linux-appimage,mac-dmg,win-msi]
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator'
steps:
- uses: actions/checkout@v2
- name: Create tarball
run: git archive --prefix="cryptomator-${{ needs.metadata.outputs.versionStr }}/" -o "cryptomator-${{ needs.metadata.outputs.versionStr }}.tar.gz" ${{ github.ref }}
- name: Download linux appimage
uses: actions/download-artifact@v2
with:
name: linux-appimage
- name: Download macOS dmg
uses: actions/download-artifact@v2
with:
name: mac-dmg
- name: Download Windows msi
uses: actions/download-artifact@v2
with:
name: win-msi
- name: Create detached GPG signature for all release files with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
for FILE in `find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.zsync" -o -name "*.tar.gz"`; do
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a ${FILE}
done
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Create release draft
uses: softprops/action-gh-release@v1
with:
draft: true
fail_on_unmatched_files: true
discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
*.AppImage
*.zsync
*.asc
*.dmg
*.msi
body: |
:construction: Work in Progress
## What's new
## Bugfixes
## Misc
---
:scroll: A complete list of closed issues is available [here](LINK)
+37
View File
@@ -0,0 +1,37 @@
name: Bug Report Triage
on:
issues:
types: [opened]
jobs:
closeTemplateViolation:
name: Validate bug report against issue template
runs-on: ubuntu-latest
if: contains(github.event.issue.labels.*.name, 'type:bug')
steps:
- name: Check "Description"
if: |
!contains(github.event.issue.body, env.MUST_CONTAIN)
|| contains(toJson(github.event.issue.body), env.MUST_NOT_CONTAIN)
run: exit 1
env:
MUST_CONTAIN: '### Description'
MUST_NOT_CONTAIN: '### Description\r\n\r\n[Summarize your problem.]\r\n\r\n### System Setup'
- name: Check "Steps to Reproduce"
if: |
!contains(github.event.issue.body, env.MUST_CONTAIN)
|| contains(toJson(github.event.issue.body), env.MUST_NOT_CONTAIN)
run: exit 1
env:
MUST_CONTAIN: '### Steps to Reproduce'
MUST_NOT_CONTAIN: '### Steps to Reproduce\r\n\r\n1. [First step]\r\n2. [Second step]\r\n3. [and so on…]\r\n\r\n#### Expected Behavior'
- name: Close issue if one of the checks failed
if: ${{ failure() }}
uses: peter-evans/close-issue@v1
with:
comment: |
This bug report did ignore our issue template. 😞
Auto-closing this issue, since it is most likely not useful.
_This decision was made by a bot. If you think the bot is wrong, let us know and we'll reopen this issue._
+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="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath=&quot;~/.config/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator/logs&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m" /> <option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath=&quot;~/.config/Cryptomator/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/.config/Cryptomator/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator/logs&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m" />
<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="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m" /> <option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/.config/Cryptomator-Dev/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m" />
<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 Windows" type="Application" factoryName="Application"> <configuration default="false" name="Cryptomator Windows" 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="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" /> <option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/AppData/Roaming/Cryptomator/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
<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 Windows Dev" type="Application" factoryName="Application"> <configuration default="false" name="Cryptomator Windows 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="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator-Dev&quot; -Dfuse.experimental=&quot;true&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" /> <option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator-Dev&quot; -Dfuse.experimental=&quot;true&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs> </envs>
<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="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" /> <option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/Library/Application Support/Cryptomator/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs> </envs>
<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="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" /> <option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/Library/Application Support/Cryptomator-Dev/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+9 -1
View File
@@ -28,6 +28,14 @@ Cryptomator is provided free of charge as an open-source project despite the hig
### Silver Sponsors ### Silver Sponsors
<table>
<tbody>
<tr>
<td><a href="https://thebestvpn.com/"><img src="https://cryptomator.org/img/sponsors/thebestvpn@2x.png" alt="TheBestVPN" height="64"></a></td>
</tr>
</tbody>
</table>
- [Jameson Lopp](https://www.lopp.net/) - [Jameson Lopp](https://www.lopp.net/)
--- ---
@@ -48,7 +56,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
- File names get encrypted - File names get encrypted
- Folder structure gets obfuscated - Folder structure gets obfuscated
- Use as many vaults in your Dropbox as you want, each having individual passwords - Use as many vaults in your Dropbox as you want, each having individual passwords
- Three thousand commits for the security of your data!! :tada: - Two thousand commits for the security of your data!! :tada:
### Privacy ### Privacy
+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>tarball</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>target/</directory>
<includes>
<include>version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>LICENSE.txt</include>
</includes>
<outputDirectory></outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>launcher-linux.sh</include>
</includes>
<outputDirectory></outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>cryptomator-*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/libs</directory>
<includes>
<include>*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
</fileSets>
</assembly>
+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>tarball</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>target/</directory>
<includes>
<include>version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>LICENSE.txt</include>
</includes>
<outputDirectory></outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>launcher-mac.sh</include>
</includes>
<outputDirectory></outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>cryptomator-*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/libs</directory>
<includes>
<include>*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
</fileSets>
</assembly>
+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>tarball</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>target/</directory>
<includes>
<include>version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>LICENSE.txt</include>
</includes>
<outputDirectory></outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>launcher-win.bat</include>
</includes>
<outputDirectory></outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>cryptomator-*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/libs</directory>
<includes>
<include>*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
</fileSets>
</assembly>
-4
View File
@@ -1,4 +0,0 @@
# created during build
Cryptomator.AppDir
*.AppImage
*.AppImage.zsync
-73
View File
@@ -1,73 +0,0 @@
#!/bin/bash
cd $(dirname $0)
REVISION_NO=`git rev-list --count HEAD`
# check preconditions
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; }
command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
# compile
mvn -B -f../../pom.xml clean package -DskipTests -Plinux
# add runtime
${JAVA_HOME}/bin/jlink \
--output runtime \
--module-path "${JAVA_HOME}/jmods" \
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility \
--no-header-files \
--no-man-pages \
--strip-debug \
--compress=1
# create app dir
${JAVA_HOME}/bin/jpackage \
--verbose \
--type app-image \
--runtime-image runtime \
--input buildkit/libs \
--module-path buildkit/mods \
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator \
--dest . \
--name Cryptomator \
--vendor "Skymatic GmbH" \
--copyright "(C) 2016 - 2021 Skymatic GmbH" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--app-version "${{ needs.metadata.outputs.versionNum }}.${{ needs.metadata.outputs.revNum }}" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.showTrayIcon=false" \
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.metadata.outputs.revNum }}\"" \
--resource-dir ../resources
# transform AppDir
mv Cryptomator Cryptomator.AppDir
cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/
chmod +x Cryptomator.AppDir/lib/runtime/bin/java
envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
# extract jffi
JFFI_NATIVE_JAR=`ls Cryptomator.AppDir/lib/app | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'`
${JAVA_HOME}/bin/jar -xf Cryptomator.AppDir/lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/
mv jni/x86_64-Linux/* Cryptomator.AppDir/lib/app/libjffi.so
rm -r jni/x86_64-Linux
# load AppImageTool
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /tmp/appimagetool.AppImage
chmod +x /tmp/appimagetool.AppImage
# create AppImage
/tmp/appimagetool.AppImage \
Cryptomator.AppDir \
cryptomator-SNAPSHOT-x86_64.AppImage \
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
-37
View File
@@ -1,37 +0,0 @@
#!/bin/sh
cd $(dirname $0)
# determine GTK version
GTK2_PRESENT=1 # initially false
GTK3_PRESENT=0 # initially true
if command -v dpkg &> /dev/null; then # do stuff for debian based things
GTK2_PRESENT=`dpkg -l libgtk* | grep -e '\^ii' | grep -e 'libgtk2-*' &> /dev/null; echo $?`
GTK3_PRESENT=`dpkg -l libgtk* | grep -e '\^ii' | grep -e 'libgtk-3-*' &> /dev/null; echo $?`
elif command -v rpm &> /dev/null; then # do stuff for rpm based things (including yum/dnf)
GTK2_PRESENT=`rpm -qa | grep -e '\^gtk2-[0-9][0-9]*' &> /dev/null; echo $?`
GTK3_PRESENT=`rpm -qa | grep -e '\^gtk3-[0-9][0-9]*' &> /dev/null; echo $?`
elif command -v pacman &> /dev/null; then # don't forget arch
GTK2_PRESENT=`pacman -Qi gtk2 &> /dev/null; echo $?`
GTK3_PRESENT=`pacman -Qi gtk3 &> /dev/null; echo $?`
fi
if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then
GTK_FLAG="-Djdk.gtk.version=2"
fi
# workaround for https://github.com/cryptomator/cryptomator-linux/issues/27
export LD_PRELOAD=lib/app/libjffi.so
# start Cryptomator
./lib/runtime/bin/java \
-p "lib/app/mods" \
-cp "lib/app/*" \
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json" \
-Dcryptomator.ipcSocketPath="~/.config/Cryptomator/ipc.socket" \
-Dcryptomator.buildNumber="appimage-${REVISION_NO}" \
$GTK_FLAG \
-Xss2m \
-Xmx512m \
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
@@ -1,10 +0,0 @@
[Desktop Entry]
Name=Cryptomator
Comment=Cloud Storage Encryption Utility
Exec=cryptomator %F
Icon=org.cryptomator.Cryptomator
Terminal=false
Type=Application
Categories=Utility;Security;FileTools;
StartupWMClass=org.cryptomator.launcher.Cryptomator
MimeType=application/vnd.cryptomator.encrypted;application/x-vnd.cryptomator.vault-metadata;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

@@ -1 +0,0 @@
<svg width="512" height="512" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg"><path d="m255.4 42.2c-76.9 0-119.4 65.3-119.4 129h238.7c.1-63.8-47.9-129-119.3-129z" fill="#cfcfcf"/><path d="m255.4 66.4c-62.5 0-97 53-97 104.8l97 11 97-11c0-51.8-39-104.8-97-104.8z" fill="#585e62"/><path d="m44.8 378.5c-5 0-9.1-4.1-9.1-9.1 0-4.6 3.5-8.6 8.1-9.1 10.7-1.2 18.4-10.8 17.2-21.5s-10.8-18.4-21.5-17.2-18.4 10.8-17.2 21.5c.3 2.5 1.1 5 2.3 7.3 2.3 4.5.5 10-4 12.2-4.3 2.1-9.4.6-11.9-3.4-10.1-18.2-3.4-41.1 14.8-51.2s41.1-3.4 51.2 14.8 3.4 41.2-14.8 51.2c-4.3 2.4-9.1 3.9-14 4.5-.4 0-.7 0-1.1 0z" fill="#585e62"/><path d="m82.6 258 2.4 1.2c11.1 5.7 15.4 19.2 9.8 30.2l-9.4 18.4c-5.7 11.1-19.2 15.4-30.2 9.8l-2.4-1.2c-11.1-5.7-15.4-19.2-9.8-30.2l9.4-18.4c5.6-11 19.2-15.4 30.2-9.8z" fill="#cfcfcf"/><path d="m86.6 221.3 25.4 13-19.6 38.3c-3.6 7-12.2 9.8-19.2 6.2-7-3.6-9.8-12.2-6.2-19.2z" fill="#585e62"/><circle cx="105.2" cy="218.4" fill="#cfcfcf" r="34.1"/><path d="m121.4 188.5c-13.7-7.6-30.8-4.8-41.5 6.7 18.8.9 33.4 16.8 32.5 35.7-.4 8.1-3.6 15.8-9.1 21.7 18.8.8 34.8-13.7 35.6-32.6.7-13-6.1-25.2-17.5-31.5z" fill="#b1b1b1"/><path d="m467.2 378.5c-.3 0-.7 0-1-.1-20.7-2.3-35.5-21-33.2-41.6 2.3-20.7 21-35.5 41.6-33.2 20.7 2.3 35.5 21 33.2 41.6-.6 4.9-2.1 9.7-4.5 14-2.3 4.5-7.7 6.3-12.2 4s-6.3-7.7-4-12.2c.1-.2.2-.4.3-.6 5.2-9.4 1.8-21.3-7.6-26.5s-21.3-1.8-26.5 7.6-1.8 21.3 7.6 26.5c2.2 1.2 4.7 2 7.2 2.3 5 .5 8.6 5 8 10-.4 4.8-4.3 8.3-8.9 8.2z" fill="#585e62"/><path d="m459.3 316.4-2.4 1.2c-11.1 5.7-24.6 1.3-30.2-9.8l-9.4-18.4c-5.7-11.1-1.3-24.6 9.8-30.2l2.4-1.2c11.1-5.7 24.6-1.3 30.2 9.8l9.4 18.4c5.6 11 1.2 24.6-9.8 30.2z" fill="#cfcfcf"/><path d="m438.8 278.9c-7 3.6-15.6.8-19.2-6.2l-19.6-38.3 25.4-13 19.6 38.3c3.6 7 .8 15.6-6.2 19.2z" fill="#585e62"/><circle cx="406.8" cy="218.4" fill="#cfcfcf" r="34.1"/><path d="m390.6 188.5c13.7-7.6 30.8-4.8 41.5 6.7-18.8.9-33.4 16.8-32.5 35.7.4 8.1 3.6 15.7 9.1 21.7-18.8.9-34.8-13.7-35.7-32.5-.6-13.1 6.2-25.3 17.6-31.6z" fill="#b1b1b1"/><path d="m117.2 469.8c-16.7 0-30.5-18.1-34.3-45.1-2.1-14.5-1-29.7 2.9-42.8 4.3-14.1 11.6-24.4 20.6-29 3.3-1.7 7-2.6 10.8-2.7h47.1v119.5z" fill="#585e62"/><path d="m163.8 469.8c-9.7 0-17.7-18.1-19.9-45.1-1.2-14.5-.6-29.7 1.7-42.8 2.5-14.1 6.7-24.4 11.9-29 2-1.8 4.1-2.7 6.2-2.7 2.2 0 4.2.9 6.3 2.6l22.6 19.9c7.9 7 10.6 29.1 9.2 46.4-1.1 13.3-4.5 23.7-9.2 27.9l-22.6 19.9c-1.9 2-4 2.9-6.2 2.9z" fill="#585e62"/><path d="m168 360.1 22.6 19.9c5.1 4.5 8.1 21.6 6.7 38.2-.9 11-3.6 19.1-6.7 21.9l-22.6 19.9c-8.5 7.5-17.2-8.8-19.5-36.4s2.7-56 11.2-63.5c2.8-2.4 5.7-2.3 8.3 0z" fill="#35393b"/><path d="m390.9 469.8c16.7 0 30.5-18.1 34.3-45.1 2.1-14.5 1-29.7-2.9-42.8-4.3-14.1-11.6-24.4-20.6-29-3.3-1.7-7-2.6-10.8-2.7h-47.1v119.5z" fill="#585e62"/><path d="m344.3 469.8c9.7 0 17.7-18.1 19.9-45.1 1.2-14.5.6-29.7-1.7-42.8-2.5-14.1-6.7-24.4-11.9-29-2-1.8-4.1-2.7-6.2-2.7s-4.2.9-6.3 2.6l-22.6 19.9c-7.9 7-10.6 29.1-9.2 46.4 1.1 13.3 4.5 23.7 9.2 27.9l22.6 19.9c1.9 2 4 2.9 6.2 2.9z" fill="#585e62"/><path d="m340.1 360.1-22.6 19.9c-5.1 4.5-8.1 21.6-6.7 38.1.9 11 3.6 19.1 6.7 21.9l22.6 19.9c8.5 7.5 17.2-8.8 19.5-36.4s-2.7-56-11.2-63.5c-2.8-2.3-5.7-2.2-8.3.1z" fill="#35393b"/><path d="m390 247.5c-2-6.8-2.1-13.9-.3-20.7 6.7-24.2 2.3-50.8 2.3-50.8-84.2-26.3-136.5-.2-136.5-.2s-52.2-26.2-136.5-.2c0 0-4.5 26.7 2.1 50.9 1.8 6.8 1.7 14-.3 20.7-2.3 8-4.9 20.2-4.9 35.8-.2 113.3 139.2 148.3 139.2 148.3s139.4-34.6 139.6-147.9c.1-15.7-2.4-27.9-4.7-35.9z" fill="#cfcfcf"/><path d="m255.2 410.1c-23.1-7.2-119-42.4-118.9-127.1 0-13.5 2.2-23.8 4.1-30.2 3.1-10.4 3.2-21.4.4-31.8-2.8-10.3-3.1-21.4-2.8-29.7 18.4-4.7 36.5-7 53.9-7 34.4.1 54.5 9.6 54.7 9.6l9.3 4.7 8.8-4.7c.1 0 20.2-9.4 54.6-9.4 17.4 0 35.4 2.4 53.8 7.1.3 8.3 0 19.4-2.9 29.7-2.8 10.4-2.7 21.5.3 31.8 1.8 6.3 4 16.7 4 30.2-.2 85.1-96.1 119.7-119.3 126.8z" fill="#49b04a"/><path d="m281.5 259.9c0-14.4-11.6-26.2-26.1-26.2s-26.2 11.6-26.2 26.1c0 12.2 8.4 22.8 20.4 25.5l-14.7 61.7 20.3 5.8 20.3-5.8-14.5-61.7c12-2.6 20.5-13.1 20.5-25.4z" fill="#35393b"/><g fill="#49b04a"><path d="m210.9 101.8c-15.2 0-27.6 12.3-27.6 27.6h55.2c0-15.2-12.3-27.6-27.6-27.6z"/><path d="m296.2 101.8c-15.2 0-27.6 12.3-27.6 27.6h55.2c0-15.2-12.4-27.6-27.6-27.6z"/><circle cx="223.8" cy="146.5" r="5.2"/><circle cx="244.4" cy="146.5" r="5.2"/><circle cx="265" cy="146.5" r="5.2"/><circle cx="285.6" cy="146.5" r="5.2"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2018 Armin Schrenk <armin.schrenk@zoho.eu> -->
<component type="desktop-application">
<id>org.cryptomator.Cryptomator</id>
<metadata_license>FSFAP</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<name>Cryptomator</name>
<summary>Multi-platform client-side encryption tool optimized for cloud storages</summary>
<description>
<p>
Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud.
</p>
<p>
Features:
<ul>
<li>Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory</li>
<li>Open Source means: No backdoors, control is better than trust</li>
<li>Client-side: No accounts, no data shared with any online service</li>
<li>Totally transparent: Just work on the virtual drive as if it were a USB flash drive</li>
<li>AES encryption with 256-bit key length</li>
<li>File names get encrypted</li>
<li>Folder structure gets obfuscated</li>
<li>Use as many vaults in your Dropbox as you want, each having individual passwords</li>
<li>One thousand commits for the security of your data!! :tada:</li>
</ul>
</p>
<p>
Privacy:
<ul>
<li>256-bit keys (unlimited strength policy bundled with native binaries)</li>
<li>Scrypt key derivation</li>
<li>Cryptographically secure random numbers for salts, IVs and the masterkey of course</li>
<li>Sensitive data is wiped from the heap asap</li>
<li>Lightweight: Complexity kills security</li>
</ul>
</p>
<p>
Consistency:
<ul>
<li>HMAC over file contents to recognize changed ciphertext before decryption</li>
<li>I/O operations are transactional and atomic, if the filesystems support it</li>
<li>Each file contains all information needed for decryption (except for the key of course), no common metadata means no Single Point of Failure</li>
</ul>
</p>
</description>
<categories>
<category>Office</category>
<category>Security</category>
<category>FileTools</category>
<category>Java</category>
</categories>
<url type="homepage">http://cryptomator.org</url>
<url type="bugtracker">https://github.com/cryptomator/cryptomator/issues</url>
<url type="faq">https://community.cryptomator.org/c/kb/faq</url>
<url type="help">https://community.cryptomator.org/</url>
<url type="donation">https://cryptomator.org/</url>
<content_rating type="oars-1.0">
<content_attribute id="violence-cartoon">none</content_attribute>
<content_attribute id="drugs-alcohol">none</content_attribute>
<content_attribute id="sex-nudity">none</content_attribute>
<content_attribute id="language-profanity">none</content_attribute>
<content_attribute id="social-info">mild</content_attribute> <!-- update checker conencts to https://api.cryptomator.org/updates/latestVersion.json -->
</content_rating>
<project_group>Cryptomator</project_group>
<provides>
<binary>cryptomator</binary>
</provides>
<launchable type="desktop-id">org.cryptomator.Cryptomator.desktop</launchable>
</component>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

-16
View File
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
Binary file not shown.
Binary file not shown.
-100
View File
@@ -1,100 +0,0 @@
{\rtf1\ansi\ansicpg1252\cocoartf2512
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;}
\paperw11900\paperh16840\vieww12000\viewh15840\viewkind0
\deftab720
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardeftab720\partightenfactor0
\f0\b\fs24 \cf0 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.
\f1\b0 \
\
\f0\b \'a9 2016 \'96 2021 Skymatic GmbH
\f1\b0 \
\
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\
\
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\
\
You should have received a copy of the GNU General Public License along with this program. If not, see {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/licenses/"}}{\fldrslt http://www.gnu.org/licenses/}}.\
\
\f0\b Cryptomator uses 49 third-party dependencies under the following licenses:
\f1\b0 \
Apache License v2.0:\
- jffi (com.github.jnr:jffi:1.2.23 - {\field{\*\fldinst{HYPERLINK "http://github.com/jnr/jffi"}}{\fldrslt http://github.com/jnr/jffi}})\
- jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - {\field{\*\fldinst{HYPERLINK "http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm"}}{\fldrslt http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm}})\
- jnr-constants (com.github.jnr:jnr-constants:0.9.15 - {\field{\*\fldinst{HYPERLINK "http://github.com/jnr/jnr-constants"}}{\fldrslt http://github.com/jnr/jnr-constants}})\
- jnr-ffi (com.github.jnr:jnr-ffi:2.1.12 - {\field{\*\fldinst{HYPERLINK "http://github.com/jnr/jnr-ffi"}}{\fldrslt http://github.com/jnr/jnr-ffi}})\
- FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - {\field{\*\fldinst{HYPERLINK "http://findbugs.sourceforge.net/"}}{\fldrslt http://findbugs.sourceforge.net/}})\
- Gson (com.google.code.gson:gson:2.8.6 - {\field{\*\fldinst{HYPERLINK "https://github.com/google/gson/gson"}}{\fldrslt https://github.com/google/gson/gson}})\
- Dagger (com.google.dagger:dagger:2.29.1 - {\field{\*\fldinst{HYPERLINK "https://github.com/google/dagger"}}{\fldrslt https://github.com/google/dagger}})\
- error-prone annotations (com.google.errorprone:error_prone_annotations:2.3.4 - {\field{\*\fldinst{HYPERLINK "http://nexus.sonatype.org/oss-repository-hosting.html/error_prone_parent/error_prone_annotation"}}{\fldrslt http://nexus.sonatype.org/oss-repository-hosting.html/error_prone_parent/error_prone_annotation}} )\
- Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - {\field{\*\fldinst{HYPERLINK "https://github.com/google/guava/failureaccess"}}{\fldrslt https://github.com/google/guava/failureaccess}})\
- Guava: Google Core Libraries for Java (com.google.guava:guava:30.0-jre - {\field{\*\fldinst{HYPERLINK "https://github.com/google/guava/guava"}}{\fldrslt https://github.com/google/guava/guava}})\
- Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - {\field{\*\fldinst{HYPERLINK "https://github.com/google/guava/listenablefuture"}}{\fldrslt https://github.com/google/guava/listenablefuture}})\
- J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - {\field{\*\fldinst{HYPERLINK "https://github.com/google/j2objc/"}}{\fldrslt https://github.com/google/j2objc/}})\
- Apache Commons CLI (commons-cli:commons-cli:1.4 - {\field{\*\fldinst{HYPERLINK "http://commons.apache.org/proper/commons-cli/"}}{\fldrslt http://commons.apache.org/proper/commons-cli/}})\
- javax.inject (javax.inject:javax.inject:1 - {\field{\*\fldinst{HYPERLINK "http://code.google.com/p/atinject/"}}{\fldrslt http://code.google.com/p/atinject/}})\
- Java Native Access (net.java.dev.jna:jna:5.6.0 - {\field{\*\fldinst{HYPERLINK "https://github.com/java-native-access/jna"}}{\fldrslt https://github.com/java-native-access/jna}})\
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.5.0 - {\field{\*\fldinst{HYPERLINK "https://github.com/java-native-access/jna"}}{\fldrslt https://github.com/java-native-access/jna}})\
- Apache Commons Lang (org.apache.commons:commons-lang3:3.11 - {\field{\*\fldinst{HYPERLINK "https://commons.apache.org/proper/commons-lang/"}}{\fldrslt https://commons.apache.org/proper/commons-lang/}})\
- Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.13 - {\field{\*\fldinst{HYPERLINK "http://hc.apache.org/httpcomponents-core-ga"}}{\fldrslt http://hc.apache.org/httpcomponents-core-ga}})\
- Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.3 - {\field{\*\fldinst{HYPERLINK "http://jackrabbit.apache.org/jackrabbit-webdav/"}}{\fldrslt http://jackrabbit.apache.org/jackrabbit-webdav/}})\
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-http"}}{\fldrslt https://eclipse.org/jetty/jetty-http}})\
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-io"}}{\fldrslt https://eclipse.org/jetty/jetty-io}})\
- Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-security"}}{\fldrslt https://eclipse.org/jetty/jetty-security}})\
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-server"}}{\fldrslt https://eclipse.org/jetty/jetty-server}})\
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-servlet"}}{\fldrslt https://eclipse.org/jetty/jetty-servlet}})\
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-util"}}{\fldrslt https://eclipse.org/jetty/jetty-util}})\
- Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-util-ajax"}}{\fldrslt https://eclipse.org/jetty/jetty-util-ajax}})\
- Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-webapp"}}{\fldrslt https://eclipse.org/jetty/jetty-webapp}})\
- Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-xml"}}{\fldrslt https://eclipse.org/jetty/jetty-xml}})\
BSD:\
- asm (org.ow2.asm:asm:7.1 - {\field{\*\fldinst{HYPERLINK "http://asm.ow2.org/"}}{\fldrslt http://asm.ow2.org/}})\
- asm-analysis (org.ow2.asm:asm-analysis:7.1 - {\field{\*\fldinst{HYPERLINK "http://asm.ow2.org/"}}{\fldrslt http://asm.ow2.org/}})\
- asm-commons (org.ow2.asm:asm-commons:7.1 - {\field{\*\fldinst{HYPERLINK "http://asm.ow2.org/"}}{\fldrslt http://asm.ow2.org/}})\
- asm-tree (org.ow2.asm:asm-tree:7.1 - {\field{\*\fldinst{HYPERLINK "http://asm.ow2.org/"}}{\fldrslt http://asm.ow2.org/}})\
- asm-util (org.ow2.asm:asm-util:7.1 - {\field{\*\fldinst{HYPERLINK "http://asm.ow2.org/"}}{\fldrslt http://asm.ow2.org/}})\
Eclipse Public License - Version 1.0:\
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-http"}}{\fldrslt https://eclipse.org/jetty/jetty-http}})\
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-io"}}{\fldrslt https://eclipse.org/jetty/jetty-io}})\
- Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-security"}}{\fldrslt https://eclipse.org/jetty/jetty-security}})\
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-server"}}{\fldrslt https://eclipse.org/jetty/jetty-server}})\
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-servlet"}}{\fldrslt https://eclipse.org/jetty/jetty-servlet}})\
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-util"}}{\fldrslt https://eclipse.org/jetty/jetty-util}})\
- Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-util-ajax"}}{\fldrslt https://eclipse.org/jetty/jetty-util-ajax}})\
- Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-webapp"}}{\fldrslt https://eclipse.org/jetty/jetty-webapp}})\
- Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.35.v20201120 - {\field{\*\fldinst{HYPERLINK "https://eclipse.org/jetty/jetty-xml"}}{\fldrslt https://eclipse.org/jetty/jetty-xml}})\
Eclipse Public License - v 2.0:\
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {\field{\*\fldinst{HYPERLINK "http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix"}}{\fldrslt http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix}})\
GPLv2:\
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {\field{\*\fldinst{HYPERLINK "http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix"}}{\fldrslt http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix}})\
GPLv2+CE:\
- Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - {\field{\*\fldinst{HYPERLINK "http://servlet-spec.java.net"}}{\fldrslt http://servlet-spec.java.net}})\
- javafx-base (org.openjfx:javafx-base:15 - {\field{\*\fldinst{HYPERLINK "https://openjdk.java.net/projects/openjfx/javafx-base/"}}{\fldrslt https://openjdk.java.net/projects/openjfx/javafx-base/}})\
- javafx-controls (org.openjfx:javafx-controls:15 - {\field{\*\fldinst{HYPERLINK "https://openjdk.java.net/projects/openjfx/javafx-controls/"}}{\fldrslt https://openjdk.java.net/projects/openjfx/javafx-controls/}})\
- javafx-fxml (org.openjfx:javafx-fxml:15 - {\field{\*\fldinst{HYPERLINK "https://openjdk.java.net/projects/openjfx/javafx-fxml/"}}{\fldrslt https://openjdk.java.net/projects/openjfx/javafx-fxml/}})\
- javafx-graphics (org.openjfx:javafx-graphics:15 - {\field{\*\fldinst{HYPERLINK "https://openjdk.java.net/projects/openjfx/javafx-graphics/"}}{\fldrslt https://openjdk.java.net/projects/openjfx/javafx-graphics/}})\
LGPL 2.1:\
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {\field{\*\fldinst{HYPERLINK "http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix"}}{\fldrslt http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix}})\
- Java Native Access (net.java.dev.jna:jna:5.6.0 - https://github.com/java-native-access/jna)\
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.5.0 - {\field{\*\fldinst{HYPERLINK "https://github.com/java-native-access/jna"}}{\fldrslt https://github.com/java-native-access/jna}})\
MIT License:\
- java jwt (com.auth0:java-jwt:3.12.0 - {\field{\*\fldinst{HYPERLINK "https://github.com/auth0/java-jwt"}}{\fldrslt https://github.com/auth0/java-jwt}})\
- jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - {\field{\*\fldinst{HYPERLINK "http://github.com/jnr/jnr-x86asm"}}{\fldrslt http://github.com/jnr/jnr-x86asm}})\
- jnr-fuse (com.github.serceman:jnr-fuse:0.5.4 - no url defined)\
- zxcvbn4j (com.nulab-inc:zxcvbn:1.3.0 - {\field{\*\fldinst{HYPERLINK "https://github.com/nulab/zxcvbn4j"}}{\fldrslt https://github.com/nulab/zxcvbn4j}})\
- Checker Qual (org.checkerframework:checker-qual:3.5.0 - {\field{\*\fldinst{HYPERLINK "https://checkerframework.org"}}{\fldrslt https://checkerframework.org}})\
- SLF4J API Module (org.slf4j:slf4j-api:1.7.30 - {\field{\*\fldinst{HYPERLINK "http://www.slf4j.org"}}{\fldrslt http://www.slf4j.org}})\
The BSD 2-Clause License:\
- EasyBind (com.tobiasdiez:easybind:2.1.0 - {\field{\*\fldinst{HYPERLINK "https://github.com/tobiasdiez/EasyBind"}}{\fldrslt https://github.com/tobiasdiez/EasyBind}})\
\
\f0\b Cryptomator uses other third-party assets under the following licenses:
\f1\b0 \
SIL OFL 1.1 License:\
- Font Awesome 5.12.0 ({\field{\*\fldinst{HYPERLINK "https://fontawesome.com/"}}{\fldrslt https://fontawesome.com/}})\
\
}
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>URL</key>
<string>https://osxfuse.github.io/</string>
</dict>
</plist>
Binary file not shown.
Binary file not shown.
-120
View File
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleExecutable</key>
<string>Cryptomator</string>
<key>CFBundleIconFile</key>
<string>Cryptomator.icns</string>
<key>CFBundleIdentifier</key>
<string>org.cryptomator</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Cryptomator</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>###BUNDLE_SHORT_VERSION_STRING###</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>CFBundleVersion</key>
<string>###BUNDLE_VERSION###</string>
<key>NSHumanReadableCopyright</key>
<string>cryptomator.org</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com.setolabs.Cryptomator</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>Cryptomator</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>
<!-- register .cryptomator extension -->
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>org.cryptomator.vault-metadata</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Cryptomator-Vault.icns</string>
<key>CFBundleTypeName</key>
<string>Cryptomator Vault Metadata</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.cryptomator.vault-metadata</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.utf8-plain-text</string>
<string>public.json</string>
</array>
<key>UTTypeDescription</key>
<string>Cryptomator Vault Metadata</string>
<key>UTTypeIconFile</key>
<string>Cryptomator-Vault.icns</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>cryptomator</string>
</array>
<key>public.mime-type</key>
<array>
<string>application/x-vnd.cryptomator.vault-metadata</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.cryptomator.encrypted-data</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Cryptomator Encrypted Data</string>
<key>UTTypeIconFile</key>
<string>Cryptomator-Vault.icns</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>c9r</string>
<string>c9s</string>
</array>
<key>public.mime-type</key>
<array>
<string>application/vnd.cryptomator.encrypted</string>
</array>
</dict>
</dict>
</array>
<!-- allow utilization of integrated GPU, see https://developer.apple.com/library/mac/qa/qa1734/_index.html -->
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>
-12
View File
@@ -1,12 +0,0 @@
@echo off
java ^
-p "app/mods" ^
-cp "app/*" ^
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
-Dcryptomator.ipcSocketPath="~/AppData/Roaming/Cryptomator/ipc.socket" ^
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
-Dcryptomator.mountPointsDir="~/Cryptomator" ^
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
-Xss20m ^
-Xmx512m ^
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

-4
View File
@@ -1,4 +0,0 @@
mime-type=application/vnd.cryptomator.encrypted
extension=c9r,c9s
description=Cryptomator Encrypted Data
icon=resources/Cryptomator.ico
-4
View File
@@ -1,4 +0,0 @@
mime-type=application/vnd.cryptomator.vault
extension=cryptomator
description=Cryptomator Vault File
icon=resources/Cryptomator.ico
Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

-89
View File
@@ -1,89 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Include>
<Fragment>
<!-- copy pasta from https://github.com/wixtoolset/wix3/blob/develop/src/ext/UIExtension/wixlib/WixUI_InstallDir.wxs with custom exit dialog-->
<UI Id="CustomWizard">
<TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
<TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
<TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
<Property Id="WixUI_Mode" Value="InstallDir" />
<DialogRef Id="BrowseDlg" />
<DialogRef Id="DiskCostDlg" />
<DialogRef Id="ErrorDlg" />
<DialogRef Id="FatalError" />
<DialogRef Id="FilesInUse" />
<DialogRef Id="MsiRMFilesInUse" />
<DialogRef Id="PrepareDlg" />
<DialogRef Id="ProgressDlg" />
<DialogRef Id="ResumeDlg" />
<DialogRef Id="UserExit" />
<Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish>
<Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
<!-- custom end dialog -->
<Publish Dialog="MyExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="LicenseAgreementDlg">NOT Installed</Publish>
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">Installed AND PATCH</Publish>
<Publish Dialog="LicenseAgreementDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg">1</Publish>
<Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg">LicenseAccepted = "1"</Publish>
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish>
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg" Order="1">NOT Installed</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">Installed AND NOT PATCH</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">Installed AND PATCH</Publish>
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>
<Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
<Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
<Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
<Property Id="ARPNOMODIFY" Value="1" />
<!-- copy pasta from https://github.com/wixtoolset/wix3/blob/develop/src/ext/UIExtension/wixlib/ExitDialog.wxs with adjustments-->
<Dialog Id="MyExitDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)">
<Control Id="Finish" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="!(loc.WixUIFinish)" />
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUICancel)" />
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="!(loc.ExitDialogBitmap)" />
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUIBack)" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Text="!(loc.ExitDialogDescription)" />
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.ExitDialogTitle)" />
<!-- TODO: localize? -->
<Control Id="Suggestion" Type="Text" X="135" Y="100" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
<Text>We recommend for the best user experience to download and install one of the following third party Windows drivers:</Text>
</Control>
<Control Id="WinFsp" Type="Hyperlink" X="140" Y="125" Width="220" Height="60" Transparent="yes">
<Text><![CDATA[• WinFsp (<a href="http://www.secfs.net/winfsp/rel/">Homepage</a>)]]></Text>
</Control>
<Control Id="Dokany" Type="Hyperlink" X="140" Y="137" Width="220" Height="60" Transparent="yes">
<Text><![CDATA[• Dokany (<a href="https://dokan-dev.github.io/">Homepage</a>)]]></Text>
</Control>
</Dialog>
<InstallUISequence>
<Show Dialog="MyExitDialog" Overridable="yes" OnExit="success"/>
</InstallUISequence>
<AdminUISequence>
<Show Dialog="MyExitDialog" Overridable="yes" OnExit="success"/>
</AdminUISequence>
</UI>
<UIRef Id="WixUI_Common" />
</Fragment>
</Include>
-85
View File
@@ -1,85 +0,0 @@
{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}}
{\colortbl ;\red0\green0\blue255;}
{\*\generator Riched20 10.0.17134}\viewkind4\uc1
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par
\par
\b\'a9 2016 \endash 2021 Skymatic GmbH\b0\par
\par
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par
\par
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par
\par
You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par
\par
\b Cryptomator uses 40 third-party dependencies under the following licenses:\b0\par
\tab Apache License v2.0:\par
\tab\tab - jffi (com.github.jnr:jffi:1.2.23 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jffi }}{\fldrslt{http://github.com/jnr/jffi\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - jnr-constants (com.github.jnr:jnr-constants:0.9.15 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-constants }}{\fldrslt{http://github.com/jnr/jnr-constants\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - jnr-ffi (com.github.jnr:jnr-ffi:2.1.12 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-ffi }}{\fldrslt{http://github.com/jnr/jnr-ffi\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Gson (com.google.code.gson:gson:2.8.7 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/gson/gson }}{\fldrslt{https://github.com/google/gson/gson\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Dagger (com.google.dagger:dagger:2.38.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/dagger }}{\fldrslt{https://github.com/google/dagger\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/guava/failureaccess }}{\fldrslt{https://github.com/google/guava/failureaccess\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Guava: Google Core Libraries for Java (com.google.guava:guava:30.1.1-jre - {{\field{\*\fldinst{HYPERLINK https://github.com/google/guava/guava }}{\fldrslt{https://github.com/google/guava/guava\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Apache Commons CLI (commons-cli:commons-cli:1.4 - {{\field{\*\fldinst{HYPERLINK http://commons.apache.org/proper/commons-cli/ }}{\fldrslt{http://commons.apache.org/proper/commons-cli/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - javax.inject (javax.inject:javax.inject:1 - {{\field{\*\fldinst{HYPERLINK http://code.google.com/p/atinject/ }}{\fldrslt{http://code.google.com/p/atinject/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Java Native Access (net.java.dev.jna:jna:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - {{\field{\*\fldinst{HYPERLINK https://commons.apache.org/proper/commons-lang/ }}{\fldrslt{https://commons.apache.org/proper/commons-lang/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.14 - {{\field{\*\fldinst{HYPERLINK http://hc.apache.org/httpcomponents-core-ga }}{\fldrslt{http://hc.apache.org/httpcomponents-core-ga\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.5 - {{\field{\*\fldinst{HYPERLINK http://jackrabbit.apache.org/jackrabbit-webdav/ }}{\fldrslt{http://jackrabbit.apache.org/jackrabbit-webdav/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-http }}{\fldrslt{https://eclipse.org/jetty/jetty-http\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-io }}{\fldrslt{https://eclipse.org/jetty/jetty-io\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-security }}{\fldrslt{https://eclipse.org/jetty/jetty-security\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-server }}{\fldrslt{https://eclipse.org/jetty/jetty-server\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-util }}{\fldrslt{https://eclipse.org/jetty/jetty-util\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet-api }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet-api\ul0\cf0}}}}\f0\fs16 )\par
\tab BSD:\par
\tab\tab - asm (org.ow2.asm:asm:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - asm-analysis (org.ow2.asm:asm-analysis:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - asm-commons (org.ow2.asm:asm-commons:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - asm-tree (org.ow2.asm:asm-tree:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - asm-util (org.ow2.asm:asm-util:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par
\tab Eclipse Public License - Version 1.0:\par
\tab\tab - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet-api }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet-api\ul0\cf0}}}}\f0\fs16 )\par
\tab Eclipse Public License - Version 2.0:\par
\tab\tab - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-http }}{\fldrslt{https://eclipse.org/jetty/jetty-http\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-io }}{\fldrslt{https://eclipse.org/jetty/jetty-io\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-security }}{\fldrslt{https://eclipse.org/jetty/jetty-security\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-server }}{\fldrslt{https://eclipse.org/jetty/jetty-server\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-util }}{\fldrslt{https://eclipse.org/jetty/jetty-util\ul0\cf0}}}}\f0\fs16 )\par
\tab Eclipse Public License - v 1.0:\par
\tab\tab - Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-classic }}{\fldrslt{http://logback.qos.ch/logback-classic\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Logback Core Module (ch.qos.logback:logback-core:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-core }}{\fldrslt{http://logback.qos.ch/logback-core\ul0\cf0}}}}\f0\fs16 )\par
\tab Eclipse Public License - v 2.0:\par
\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par
\tab GNU Lesser General Public License:\par
\tab\tab - Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-classic }}{\fldrslt{http://logback.qos.ch/logback-classic\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Logback Core Module (ch.qos.logback:logback-core:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-core }}{\fldrslt{http://logback.qos.ch/logback-core\ul0\cf0}}}}\f0\fs16 )\par
\tab GPLv2:\par
\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par
\tab GPLv2+CE:\par
\tab\tab - javafx-base (org.openjfx:javafx-base:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-base/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-base/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - javafx-controls (org.openjfx:javafx-controls:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-controls/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-controls/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - javafx-fxml (org.openjfx:javafx-fxml:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-fxml/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-fxml/\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - javafx-graphics (org.openjfx:javafx-graphics:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-graphics/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-graphics/\ul0\cf0}}}}\f0\fs16 )\par
\tab LGPL 2.1:\par
\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Java Native Access (net.java.dev.jna:jna:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par
\tab MIT License:\par
\tab\tab - java jwt (com.auth0:java-jwt:3.18.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/auth0/java-jwt }}{\fldrslt{https://github.com/auth0/java-jwt\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-x86asm }}{\fldrslt{http://github.com/jnr/jnr-x86asm\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - jnr-fuse (com.github.serceman:jnr-fuse:0.5.5 - {{\field{\*\fldinst{HYPERLINK https://github.com/SerCeMan/jnr-fuse }}{\fldrslt{https://github.com/SerCeMan/jnr-fuse\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - zxcvbn4j (com.nulab-inc:zxcvbn:1.5.2 - {{\field{\*\fldinst{HYPERLINK https://github.com/nulab/zxcvbn4j }}{\fldrslt{https://github.com/nulab/zxcvbn4j\ul0\cf0}}}}\f0\fs16 )\par
\tab\tab - SLF4J API Module (org.slf4j:slf4j-api:1.7.31 - {{\field{\*\fldinst{HYPERLINK http://www.slf4j.org }}{\fldrslt{http://www.slf4j.org\ul0\cf0}}}}\f0\fs16 )\par
\tab The BSD 2-Clause License:\par
\tab\tab - EasyBind (com.tobiasdiez:easybind:2.2 - {{\field{\*\fldinst{HYPERLINK https://github.com/tobiasdiez/EasyBind }}{\fldrslt{https://github.com/tobiasdiez/EasyBind\ul0\cf0}}}}\f0\fs16 )\par
\par
\b Cryptomator uses other third-party assets under the following licenses:\b0\par
\tab SIL OFL 1.1 License:\par
\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par
}
-135
View File
@@ -1,135 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Cryptomator's Jpackage Wix installer file
Remark: Expressions like $(var.name) are preprocessor variables, see also https://wixtoolset.org/documentation/manual/v3/overview/preprocessor.html
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?ifdef JpIsSystemWide ?>
<?define JpInstallScope="perMachine"?>
<?else?>
<?define JpInstallScope="perUser"?>
<?endif?>
<?define JpProductLanguage=1033 ?>
<?define JpInstallerVersion=200 ?>
<?define JpCompressedMsi=yes ?>
<?ifdef JpAllowUpgrades ?>
<?define JpUpgradeVersionOnlyDetectUpgrade="no"?>
<?else?>
<?define JpUpgradeVersionOnlyDetectUpgrade="yes"?>
<?endif?>
<?ifdef JpAllowDowngrades ?>
<?define JpUpgradeVersionOnlyDetectDowngrade="no"?>
<?else?>
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
<?endif?>
<?include $(var.JpConfigDir)/overrides.wxi ?>
<Product Id="$(var.JpProductCode)" Name="$(var.JpAppName)" Language="$(var.JpProductLanguage)" Version="$(var.JpAppVersion)" Manufacturer="$(var.JpAppVendor)" UpgradeCode="$(var.JpProductUpgradeCode)">
<Package Description="$(var.JpAppDescription)" Manufacturer="$(var.JpAppVendor)" InstallerVersion="$(var.JpInstallerVersion)" Compressed="$(var.JpCompressedMsi)" InstallScope="$(var.JpInstallScope)" Platform="x64" />
<Media Id="1" Cabinet="Data.cab" EmbedCab="yes" />
<Upgrade Id="$(var.JpProductUpgradeCode)">
<UpgradeVersion OnlyDetect="$(var.JpUpgradeVersionOnlyDetectUpgrade)" Property="JP_UPGRADABLE_FOUND" Maximum="$(var.JpAppVersion)" MigrateFeatures="yes" IncludeMaximum="$(var.JpUpgradeVersionOnlyDetectUpgrade)" />
<UpgradeVersion OnlyDetect="$(var.JpUpgradeVersionOnlyDetectDowngrade)" Property="JP_DOWNGRADABLE_FOUND" Minimum="$(var.JpAppVersion)" MigrateFeatures="yes" IncludeMinimum="$(var.JpUpgradeVersionOnlyDetectDowngrade)" />
</Upgrade>
<?ifndef JpAllowUpgrades ?>
<CustomAction Id="JpDisallowUpgrade" Error="!(loc.DisallowUpgradeErrorMessage)" />
<?endif?>
<?ifndef JpAllowDowngrades ?>
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
<?endif?>
<!-- Standard required root -->
<Directory Id="TARGETDIR" Name="SourceDir"/>
<Feature Id="DefaultFeature" Title="!(loc.MainFeatureTitle)" Level="1">
<ComponentGroupRef Id="Shortcuts"/>
<ComponentGroupRef Id="Files"/>
<ComponentGroupRef Id="FileAssociations"/>
</Feature>
<?ifdef JpInstallDirChooser ?>
<Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
<CustomAction Id="JpCheckInstallDir" BinaryKey="JpCaDll" DllEntry="CheckInstallDir" />
<?endif?>
<CustomAction Id="JpSetARPINSTALLLOCATION" Property="ARPINSTALLLOCATION" Value="[INSTALLDIR]" />
<?ifdef JpIcon ?>
<Property Id="ARPPRODUCTICON" Value="JpARPPRODUCTICON"/>
<Icon Id="JpARPPRODUCTICON" SourceFile="$(var.JpIcon)"/>
<?endif?>
<UI>
<?ifdef JpInstallDirChooser ?>
<Dialog Id="JpInvalidInstallDir" Width="300" Height="85" Title="[ProductName] Setup" NoMinimize="yes">
<Control Id="JpInvalidInstallDirYes" Type="PushButton" X="100" Y="55" Width="50" Height="15" Default="no" Cancel="no" Text="Yes">
<Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
</Control>
<Control Id="JpInvalidInstallDirNo" Type="PushButton" X="150" Y="55" Width="50" Height="15" Default="yes" Cancel="yes" Text="No">
<Publish Event="NewDialog" Value="InstallDirDlg">1</Publish>
</Control>
<Control Id="Text" Type="Text" X="25" Y="15" Width="250" Height="30" TabSkip="no">
<Text>!(loc.message.install.dir.exist)</Text>
</Control>
</Dialog>
<!--
Run our custom wizard in the default install directory.
-->
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR"/>
<!-- included at the end -->
<UIRef Id="CustomWizard" />
<Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="JpCheckInstallDir" Order="3">1</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="JpInvalidInstallDir" Order="5">INSTALLDIR_VALID="0"</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="5">INSTALLDIR_VALID="1"</Publish>
<?ifndef JpLicenseRtf ?>
<!--
No license file provided.
Override the dialog sequence in built-in dialog set "WixUI_InstallDir"
to exclude license dialog.
-->
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">1</Publish>
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">1</Publish>
<?endif?>
<?else?>
<?ifdef JpLicenseRtf ?>
<UIRef Id="WixUI_Minimal" />
<?endif?>
<?endif?>
</UI>
<?ifdef JpLicenseRtf ?>
<WixVariable Id="WixUILicenseRtf" Value="$(var.JpLicenseRtf)"/>
<?endif?>
<InstallExecuteSequence>
<Custom Action="JpSetARPINSTALLLOCATION" After="CostFinalize">Not Installed</Custom>
<?ifndef JpAllowUpgrades ?>
<Custom Action="JpDisallowUpgrade" After="FindRelatedProducts">JP_UPGRADABLE_FOUND</Custom>
<?endif?>
<?ifndef JpAllowDowngrades ?>
<Custom Action="JpDisallowDowngrade" After="FindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom>
<?endif?>
<RemoveExistingProducts Before="CostInitialize"/>
</InstallExecuteSequence>
<WixVariable Id="WixUIBannerBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\banner.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\background.bmp" />
</Product>
<?include $(env.JP_WIXWIZARD_RESOURCES)\customWizard.wxi ?>
</Wix>
+169 -64
View File
@@ -23,28 +23,24 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.jdk.version>16</project.jdk.version> <project.jdk.version>16</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now -->
<nonModularGroupIds>com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
<!-- cryptomator dependencies --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.1.0-beta11</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>2.1.0-beta8</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.0.0-rc1</cryptomator.integrations.version> <cryptomator.integrations.version>1.0.0-rc1</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.0.0-beta2</cryptomator.integrations.win.version> <cryptomator.integrations.win.version>1.0.0-beta2</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.0.0-beta2</cryptomator.integrations.mac.version> <cryptomator.integrations.mac.version>1.0.0-beta2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.0.0-beta1</cryptomator.integrations.linux.version> <cryptomator.integrations.linux.version>1.0.0-beta1</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>1.3.2</cryptomator.fuse.version> <cryptomator.fuse.version>1.3.1</cryptomator.fuse.version>
<cryptomator.dokany.version>1.3.1</cryptomator.dokany.version> <cryptomator.dokany.version>1.3.1</cryptomator.dokany.version>
<cryptomator.webdav.version>1.2.5</cryptomator.webdav.version> <cryptomator.webdav.version>1.2.4</cryptomator.webdav.version>
<!-- 3rd party dependencies --> <!-- 3rd party dependencies -->
<javafx.version>16</javafx.version> <javafx.version>16</javafx.version>
<commons-lang3.version>3.12.0</commons-lang3.version> <commons-lang3.version>3.12.0</commons-lang3.version>
<jwt.version>3.18.1</jwt.version> <jwt.version>3.17.0</jwt.version>
<easybind.version>2.2</easybind.version> <easybind.version>2.2</easybind.version>
<guava.version>30.1.1-jre</guava.version> <guava.version>30.1.1-jre</guava.version>
<dagger.version>2.38.1</dagger.version> <dagger.version>2.37</dagger.version>
<gson.version>2.8.7</gson.version> <gson.version>2.8.6</gson.version>
<zxcvbn.version>1.5.2</zxcvbn.version>
<slf4j.version>1.7.31</slf4j.version> <slf4j.version>1.7.31</slf4j.version>
<logback.version>1.2.3</logback.version> <logback.version>1.2.3</logback.version>
@@ -146,7 +142,7 @@
<dependency> <dependency>
<groupId>com.nulab-inc</groupId> <groupId>com.nulab-inc</groupId>
<artifactId>zxcvbn</artifactId> <artifactId>zxcvbn</artifactId>
<version>${zxcvbn.version}</version> <version>1.3.0</version>
</dependency> </dependency>
<!-- Google --> <!-- Google -->
@@ -154,29 +150,6 @@
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>${guava.version}</version> <version>${guava.version}</version>
<exclusions>
<!-- see https://github.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies -->
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>listenablefuture</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.j2objc</groupId>
<artifactId>j2objc-annotations</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.dagger</groupId> <groupId>com.google.dagger</groupId>
@@ -217,24 +190,11 @@
<dependency> <dependency>
<groupId>com.google.jimfs</groupId> <groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId> <artifactId>jimfs</artifactId>
<version>1.2</version> <version>1.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<repositories>
<repository>
<id>maven-central</id>
<name>Maven Central Repo</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
<repository>
<id>google-maven</id>
<name>Google Maven Repo</name>
<url>https://maven.google.com</url>
</repository>
</repositories>
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
@@ -251,12 +211,17 @@
<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>3.2.0</version> <version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<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>3.0.0-M5</version> <version>2.22.2</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
@@ -271,12 +236,12 @@
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version> <version>0.8.6</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.owasp</groupId> <groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId> <artifactId>dependency-check-maven</artifactId>
<version>6.2.2</version> <version>6.0.3</version>
</plugin> </plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
@@ -293,10 +258,6 @@
<version>${dagger.version}</version> <version>${dagger.version}</version>
</path> </path>
</annotationProcessorPaths> </annotationProcessorPaths>
<compilerArgs>
<arg>-Adagger.fastInit=enabled</arg>
<arg>-Adagger.formatGeneratedSource=enabled</arg>
</compilerArgs>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@@ -317,20 +278,38 @@
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<id>copy-mods</id> <id>copy-resources</id>
<phase>prepare-package</phase> <phase>prepare-package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>copy-resources</goal>
</goals> </goals>
<configuration> <configuration>
<includeScope>runtime</includeScope> <outputDirectory>${project.build.directory}</outputDirectory>
<outputDirectory>${project.build.directory}/mods</outputDirectory> <resources>
<excludeGroupIds>${nonModularGroupIds}</excludeGroupIds> <resource>
<directory>${project.basedir}/src/package</directory>
<includes>
<include>version.txt</include>
<include>ffi-version.txt</include>
<include>launcher-mac.sh</include>
<include>launcher-linux.sh</include>
<include>launcher-win.bat</include>
<include>LICENSE.txt</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</configuration> </configuration>
</execution> </execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution> <execution>
<id>copy-libs</id> <id>copy-libs</id>
<phase>prepare-package</phase> <phase>prepare-package</phase>
@@ -340,7 +319,7 @@
<configuration> <configuration>
<includeScope>runtime</includeScope> <includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/libs</outputDirectory> <outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeGroupIds>${nonModularGroupIds}</includeGroupIds> <excludeClassifiers>linux,mac,win</excludeClassifiers>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
@@ -451,6 +430,48 @@
<version>${cryptomator.integrations.mac.version}</version> <version>${cryptomator.integrations.mac.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble-mac</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-mac.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-mac</finalName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-mac-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>mac</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile> </profile>
<profile> <profile>
@@ -471,10 +492,52 @@
<version>${cryptomator.integrations.linux.version}</version> <version>${cryptomator.integrations.linux.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble-linux</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-linux.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-linux</finalName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-linux-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>linux</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile> </profile>
<profile> <profile>
<id>win</id> <id>windows</id>
<activation> <activation>
<os> <os>
<family>windows</family> <family>windows</family>
@@ -490,6 +553,48 @@
<version>${cryptomator.integrations.win.version}</version> <version>${cryptomator.integrations.win.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble-win</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-win.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-win</finalName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-win-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>win</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile> </profile>
</profiles> </profiles>
-59
View File
@@ -1,59 +0,0 @@
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
module org.cryptomator.desktop {
requires org.cryptomator.cryptofs;
requires org.cryptomator.frontend.dokany;
requires org.cryptomator.frontend.fuse;
requires org.cryptomator.frontend.webdav;
requires org.cryptomator.integrations.api;
requires java.desktop;
requires java.net.http;
requires javafx.base;
requires javafx.graphics;
requires javafx.controls;
requires javafx.fxml;
requires com.tobiasdiez.easybind;
requires com.google.common;
requires com.google.gson;
requires com.nulabinc.zxcvbn;
requires org.slf4j;
requires org.apache.commons.lang3;
requires dagger;
requires com.auth0.jwt;
/* TODO: filename-based modules: */
requires static javax.inject; /* ugly dagger/guava crap */
requires logback.classic;
requires logback.core;
uses AutoStartProvider;
uses KeychainAccessProvider;
uses TrayIntegrationProvider;
uses UiAppearanceProvider;
opens org.cryptomator.common.settings to com.google.gson;
opens org.cryptomator.common to javafx.fxml;
opens org.cryptomator.common.vaults to javafx.fxml;
opens org.cryptomator.ui.addvaultwizard to javafx.fxml;
opens org.cryptomator.ui.changepassword to javafx.fxml;
opens org.cryptomator.ui.common to javafx.fxml;
opens org.cryptomator.ui.controls to javafx.fxml;
opens org.cryptomator.ui.forgetPassword to javafx.fxml;
opens org.cryptomator.ui.fxapp to javafx.fxml;
opens org.cryptomator.ui.health to javafx.fxml;
opens org.cryptomator.ui.keyloading.masterkeyfile to javafx.fxml;
opens org.cryptomator.ui.mainwindow to javafx.fxml;
opens org.cryptomator.ui.migration to javafx.fxml;
opens org.cryptomator.ui.preferences to javafx.fxml;
opens org.cryptomator.ui.quit to javafx.fxml;
opens org.cryptomator.ui.recoverykey to javafx.fxml;
opens org.cryptomator.ui.removevault to javafx.fxml;
opens org.cryptomator.ui.stats to javafx.fxml;
opens org.cryptomator.ui.unlock to javafx.fxml;
opens org.cryptomator.ui.vaultoptions to javafx.fxml;
opens org.cryptomator.ui.wrongfilealert to javafx.fxml;
}
@@ -33,7 +33,7 @@ public class Environment {
LOG.debug("user.region: {}", System.getProperty("user.region")); LOG.debug("user.region: {}", System.getProperty("user.region"));
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile")); LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath")); LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath"));
LOG.debug("cryptomator.ipcSocketPath: {}", System.getProperty("cryptomator.ipcSocketPath")); LOG.debug("cryptomator.ipcPortPath: {}", System.getProperty("cryptomator.ipcPortPath"));
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath")); LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir")); LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir")); LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
@@ -51,8 +51,8 @@ public class Environment {
return getPaths("cryptomator.settingsPath"); return getPaths("cryptomator.settingsPath");
} }
public Stream<Path> ipcSocketPath() { public Stream<Path> getIpcPortPath() {
return getPaths("cryptomator.ipcSocketPath"); return getPaths("cryptomator.ipcPortPath");
} }
public Stream<Path> getKeychainPath() { public Stream<Path> getKeychainPath() {
@@ -1,8 +0,0 @@
package org.cryptomator.common;
/**
* Replacement for JSR-305 to avoid runtime dependencies. Used in Dagger components.
*/
public @interface Nullable {
}
@@ -43,7 +43,7 @@ public class VaultSettings {
private static final Random RNG = new Random(); private static final Random RNG = new Random();
private final String id; private final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty<>(); private final ObjectProperty<Path> path = new SimpleObjectProperty();
private final StringProperty displayName = new SimpleStringProperty(); private final StringProperty displayName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty(); private final StringProperty winDriveLetter = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP); private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
@@ -19,7 +19,6 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig; import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoader;
@@ -328,14 +327,6 @@ public class Vault {
return stats; return stats;
} }
/**
* Attempts to read the vault config file and parse it without verifying its integrity.
*
* @return an unverified vault config
* @throws VaultConfigLoadException if the read file cannot be properly parsed
* @throws IOException if reading the file fails
*
*/
public UnverifiedVaultConfig getUnverifiedVaultConfig() throws IOException { public UnverifiedVaultConfig getUnverifiedVaultConfig() throws IOException {
Path configPath = getPath().resolve(org.cryptomator.common.Constants.VAULTCONFIG_FILENAME); Path configPath = getPath().resolve(org.cryptomator.common.Constants.VAULTCONFIG_FILENAME);
String token = Files.readString(configPath, StandardCharsets.US_ASCII); String token = Files.readString(configPath, StandardCharsets.US_ASCII);
@@ -7,10 +7,10 @@ package org.cryptomator.common.vaults;
import dagger.BindsInstance; import dagger.BindsInstance;
import dagger.Subcomponent; import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.mountpoint.MountPointChooserModule; import org.cryptomator.common.mountpoint.MountPointChooserModule;
import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VaultSettings;
import javax.annotation.Nullable;
import javax.inject.Named; import javax.inject.Named;
@PerVault @PerVault
@@ -8,7 +8,6 @@ package org.cryptomator.common.vaults;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl; import org.cryptomator.common.settings.VolumeImpl;
@@ -16,6 +15,7 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Named; import javax.inject.Named;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding; import javafx.beans.binding.StringBinding;
@@ -1,65 +0,0 @@
package org.cryptomator.ipc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.UnixDomainSocketAddress;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.Executor;
class Client implements IpcCommunicator {
private static final Logger LOG = LoggerFactory.getLogger(Client.class);
private final SocketChannel socketChannel;
private Client(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
public static Client create(Path socketPath) throws IOException {
var address = UnixDomainSocketAddress.of(socketPath);
var socketChannel = SocketChannel.open(address);
LOG.info("Connected to IPC server on socket {}", socketPath);
return new Client(socketChannel);
}
@Override
public boolean isClient() {
return true;
}
@Override
public void listen(IpcMessageListener listener, Executor executor) {
executor.execute(() -> {
try {
while (socketChannel.isConnected()) {
var msg = IpcMessage.receive(socketChannel);
listener.handleMessage(msg);
}
} catch (IOException e) {
LOG.error("Failed to read IPC message", e);
}
});
}
@Override
public void send(IpcMessage message, Executor executor) {
executor.execute(() -> {
try {
message.send(socketChannel);
} catch (IOException e) {
LOG.error("Failed to send IPC message", e);
}
});
}
@Override
public void close() throws IOException {
socketChannel.close();
}
}
@@ -1,30 +0,0 @@
package org.cryptomator.ipc;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
record HandleLaunchArgsMessage(List<String> args) implements IpcMessage {
private static final char DELIMITER = '\n';
public static HandleLaunchArgsMessage decode(ByteBuffer encoded) {
var str = StandardCharsets.UTF_8.decode(encoded).toString();
var args = Splitter.on(DELIMITER).omitEmptyStrings().splitToList(str);
return new HandleLaunchArgsMessage(args);
}
@Override
public MessageType getMessageType() {
return MessageType.HANDLE_LAUNCH_ARGS;
}
@Override
public ByteBuffer encodePayload() {
var str = Joiner.on(DELIMITER).join(args);
return StandardCharsets.UTF_8.encode(str);
}
}
@@ -1,96 +0,0 @@
package org.cryptomator.ipc;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
public interface IpcCommunicator extends Closeable {
Logger LOG = LoggerFactory.getLogger(IpcCommunicator.class);
/**
* Attempts to establish a socket connection via one of the given paths.
* <p>
* If no connection to an existing sockets can be established, a new socket is created for the first given path.
* <p>
* If this fails as well, a fallback communicator is returned that allows process-internal communication mocking the API
* that would have been used for IPC.
*
* @param socketPaths The socket path(s)
* @return A communicator object that allows sending and receiving messages
*/
static IpcCommunicator create(Iterable<Path> socketPaths) {
Preconditions.checkArgument(socketPaths.iterator().hasNext(), "socketPaths must contain at least one element");
for (var p : socketPaths) {
try {
var attr = Files.readAttributes(p, BasicFileAttributes.class);
if (attr.isOther()) {
return Client.create(p);
}
} catch (IOException e) {
// attempt next socket path
}
}
// Didn't get any connection yet? I.e. we're the first app instance, so let's launch a server:
try {
return Server.create(socketPaths.iterator().next());
} catch (IOException e) {
LOG.warn("Failed to create IPC server", e);
return new LoopbackCommunicator();
}
}
boolean isClient();
/**
* Listens to incoming messages until the connection gets closed.
* @param listener The listener that should be notified of incoming messages
* @param executor An executor on which to listen. Listening will block, so you might want to use a background thread.
* @return
*/
void listen(IpcMessageListener listener, Executor executor);
/**
* Sends the given message.
*
* @param message The message to send
* @param executor An executor used to send the message. Sending will block, so you might want to use a background thread.
*/
void send(IpcMessage message, Executor executor);
default void sendRevealRunningApp() {
send(new RevealRunningAppMessage(), MoreExecutors.directExecutor());
}
default void sendHandleLaunchargs(List<String> args) {
send(new HandleLaunchArgsMessage(args), MoreExecutors.directExecutor());
}
/**
* Clean up resources.
*
* @implSpec Must be idempotent
* @throws IOException In case of I/O errors.
*/
@Override
void close() throws IOException;
default void closeUnchecked() throws UncheckedIOException {
try {
close();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
@@ -1,68 +0,0 @@
package org.cryptomator.ipc;
import org.cryptomator.cryptolib.common.ByteBuffers;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.function.Function;
// TODO make sealed, remove enum
interface IpcMessage {
enum MessageType {
REVEAL_RUNNING_APP(RevealRunningAppMessage::decode),
HANDLE_LAUNCH_ARGS(HandleLaunchArgsMessage::decode);
private final Function<ByteBuffer, IpcMessage> decoder;
MessageType(Function<ByteBuffer, IpcMessage> decoder) {
this.decoder = decoder;
}
static MessageType forOrdinal(int ordinal) {
try {
return values()[ordinal];
} catch (IndexOutOfBoundsException e) {
throw new IllegalArgumentException("No such message type: " + ordinal, e);
}
}
IpcMessage decodePayload(ByteBuffer payload) {
return decoder.apply(payload);
}
}
MessageType getMessageType();
ByteBuffer encodePayload();
static IpcMessage receive(ReadableByteChannel channel) throws IOException {
var header = ByteBuffer.allocate(2 * Integer.BYTES);
if (ByteBuffers.fill(channel, header) < header.capacity()) {
throw new EOFException();
}
header.flip();
int typeNo = header.getInt();
int length = header.getInt();
MessageType type = MessageType.forOrdinal(typeNo);
var payload = ByteBuffer.allocate(length);
ByteBuffers.fill(channel, payload);
payload.flip();
return type.decodePayload(payload);
}
default void send(WritableByteChannel channel) throws IOException {
var payload = encodePayload();
var buf = ByteBuffer.allocate(2 * Integer.BYTES + payload.remaining());
buf.putInt(getMessageType().ordinal()); // message type
buf.putInt(payload.remaining()); // message length
buf.put(payload); // message
buf.flip();
while (buf.hasRemaining()) {
channel.write(buf);
}
}
}
@@ -1,19 +0,0 @@
package org.cryptomator.ipc;
import java.util.List;
public interface IpcMessageListener {
default void handleMessage(IpcMessage message) {
if (message instanceof RevealRunningAppMessage) {
revealRunningApp();
} else if (message instanceof HandleLaunchArgsMessage m) {
handleLaunchArgs(m.args());
}
}
void revealRunningApp();
void handleLaunchArgs(List<String> args);
}
@@ -1,50 +0,0 @@
package org.cryptomator.ipc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;
class LoopbackCommunicator implements IpcCommunicator {
private static final Logger LOG = LoggerFactory.getLogger(LoopbackCommunicator.class);
private final TransferQueue<IpcMessage> transferQueue = new LinkedTransferQueue<>();
@Override
public boolean isClient() {
return false;
}
@Override
public void listen(IpcMessageListener listener, Executor executor) {
executor.execute(() -> {
try {
var msg = transferQueue.take();
listener.handleMessage(msg);
} catch (InterruptedException e) {
LOG.error("Failed to read IPC message", e);
Thread.currentThread().interrupt();
}
});
}
@Override
public void send(IpcMessage message, Executor executor) {
executor.execute(() -> {
try {
transferQueue.put(message);
} catch (InterruptedException e) {
LOG.error("Failed to send IPC message", e);
Thread.currentThread().interrupt();
}
});
}
@Override
public void close() {
// no-op
}
}
@@ -1,20 +0,0 @@
package org.cryptomator.ipc;
import java.nio.ByteBuffer;
public record RevealRunningAppMessage() implements IpcMessage {
static RevealRunningAppMessage decode(ByteBuffer ignored) {
return new RevealRunningAppMessage();
}
@Override
public MessageType getMessageType() {
return MessageType.REVEAL_RUNNING_APP;
}
@Override
public ByteBuffer encodePayload() {
return ByteBuffer.allocate(0);
}
}
@@ -1,82 +0,0 @@
package org.cryptomator.ipc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.EOFException;
import java.io.IOException;
import java.net.StandardProtocolFamily;
import java.net.UnixDomainSocketAddress;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.Executor;
class Server implements IpcCommunicator {
private static final Logger LOG = LoggerFactory.getLogger(Server.class);
private final ServerSocketChannel serverSocketChannel;
private final Path socketPath;
private Server(ServerSocketChannel serverSocketChannel, Path socketPath) {
this.serverSocketChannel = serverSocketChannel;
this.socketPath = socketPath;
}
public static Server create(Path socketPath) throws IOException {
var address = UnixDomainSocketAddress.of(socketPath);
var serverSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
serverSocketChannel.bind(address);
LOG.info("Spawning IPC server listening on socket {}", socketPath);
return new Server(serverSocketChannel, socketPath);
}
@Override
public boolean isClient() {
return false;
}
@Override
public void listen(IpcMessageListener listener, Executor executor) {
executor.execute(() -> {
while (serverSocketChannel.isOpen()) {
try (var ch = serverSocketChannel.accept()) {
while (ch.isConnected()) {
var msg = IpcMessage.receive(ch);
listener.handleMessage(msg);
}
} catch (AsynchronousCloseException e) {
return; // serverSocketChannel closed or listener interrupted
} catch (EOFException | ClosedChannelException e) {
// continue with next connected client
} catch (IOException e) {
LOG.error("Failed to read IPC message", e);
}
}
});
}
@Override
public void send(IpcMessage message, Executor executor) {
executor.execute(() -> {
try (var ch = serverSocketChannel.accept()) {
message.send(ch);
} catch (IOException e) {
LOG.error("Failed to send IPC message", e);
}
});
}
@Override
public void close() throws IOException {
try {
serverSocketChannel.close();
} finally {
Files.deleteIfExists(socketPath);
LOG.debug("IPC server closed");
}
}
}
@@ -5,12 +5,7 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.launcher; package org.cryptomator.launcher;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode; import org.cryptomator.logging.DebugMode;
import org.cryptomator.logging.LoggerConfiguration; import org.cryptomator.logging.LoggerConfiguration;
import org.cryptomator.ui.launcher.UiLauncher; import org.cryptomator.ui.launcher.UiLauncher;
@@ -21,10 +16,8 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@Singleton @Singleton
public class Cryptomator { public class Cryptomator {
@@ -36,28 +29,23 @@ public class Cryptomator {
private final LoggerConfiguration logConfig; private final LoggerConfiguration logConfig;
private final DebugMode debugMode; private final DebugMode debugMode;
private final Environment env; private final IpcFactory ipcFactory;
private final Lazy<IpcMessageHandler> ipcMessageHandler;
private final Optional<String> applicationVersion; private final Optional<String> applicationVersion;
private final CountDownLatch shutdownLatch; private final CountDownLatch shutdownLatch;
private final ShutdownHook shutdownHook; private final UiLauncher uiLauncher;
private final Lazy<UiLauncher> uiLauncher;
@Inject @Inject
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, Lazy<UiLauncher> uiLauncher) { Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, UiLauncher uiLauncher) {
this.logConfig = logConfig; this.logConfig = logConfig;
this.debugMode = debugMode; this.debugMode = debugMode;
this.env = env; this.ipcFactory = ipcFactory;
this.ipcMessageHandler = ipcMessageHandler;
this.applicationVersion = applicationVersion; this.applicationVersion = applicationVersion;
this.shutdownLatch = shutdownLatch; this.shutdownLatch = shutdownLatch;
this.shutdownHook = shutdownHook;
this.uiLauncher = uiLauncher; this.uiLauncher = uiLauncher;
} }
public static void main(String[] args) { public static void main(String[] args) {
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args); int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
LOG.info("Exit {}", exitCode);
System.exit(exitCode); // end remaining non-daemon threads. System.exit(exitCode); // end remaining non-daemon threads.
} }
@@ -76,24 +64,19 @@ public class Cryptomator {
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args. * Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
* If no external process could be reached, the args will be handled by the loopback IPC endpoint. * If no external process could be reached, the args will be handled by the loopback IPC endpoint.
*/ */
try (var communicator = IpcCommunicator.create(env.ipcSocketPath().toList())) { try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
if (communicator.isClient()) { endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
communicator.sendHandleLaunchargs(List.of(args)); if (endpoint.isConnectedToRemote()) {
communicator.sendRevealRunningApp(); endpoint.getRemote().revealRunningApp();
LOG.info("Found running application instance. Shutting down..."); LOG.info("Found running application instance. Shutting down...");
return 2; return 2;
} else { } else {
shutdownHook.runOnShutdown(communicator::closeUnchecked);
var executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IPC-%d").build());
var msgHandler = ipcMessageHandler.get();
msgHandler.handleLaunchArgs(List.of(args));
communicator.listen(msgHandler, executor);
LOG.debug("Did not find running application instance. Launching GUI..."); LOG.debug("Did not find running application instance. Launching GUI...");
return runGuiApplication(); return runGuiApplication();
} }
} catch (Throwable e) { } catch (IOException e) {
LOG.error("Running application failed", e); LOG.error("Failed to initiate inter-process communication.", e);
return 1; return runGuiApplication();
} }
} }
@@ -105,7 +88,7 @@ public class Cryptomator {
*/ */
private int runGuiApplication() { private int runGuiApplication() {
try { try {
uiLauncher.get().launch(); uiLauncher.launch();
shutdownLatch.await(); shutdownLatch.await();
LOG.info("UI shut down"); LOG.info("UI shut down");
return 0; return 0;
@@ -115,4 +98,5 @@ public class Cryptomator {
} }
} }
} }
@@ -22,7 +22,6 @@ import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -47,13 +46,13 @@ class FileOpenRequestHandler {
tryToEnqueueFileOpenRequest(launchEvent); tryToEnqueueFileOpenRequest(launchEvent);
} }
public void handleLaunchArgs(List<String> args) { public void handleLaunchArgs(String[] args) {
handleLaunchArgs(FileSystems.getDefault(), args); handleLaunchArgs(FileSystems.getDefault(), args);
} }
// visible for testing // visible for testing
void handleLaunchArgs(FileSystem fs, List<String> args) { void handleLaunchArgs(FileSystem fs, String[] args) {
Collection<Path> pathsToOpen = args.stream().map(str -> { Collection<Path> pathsToOpen = Arrays.stream(args).map(str -> {
try { try {
return fs.getPath(str); return fs.getPath(str);
} catch (InvalidPathException e) { } catch (InvalidPathException e) {
@@ -0,0 +1,258 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.launcher;
import com.google.common.io.MoreFiles;
import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.rmi.NotBoundException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.RMISocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* First running application on a machine opens a server socket. Further processes will connect as clients.
*/
@Singleton
class IpcFactory {
private static final Logger LOG = LoggerFactory.getLogger(IpcFactory.class);
private static final String RMI_NAME = "Cryptomator";
private final List<Path> portFilePaths;
private final IpcProtocolImpl ipcHandler;
@Inject
public IpcFactory(Environment env, IpcProtocolImpl ipcHandler) {
this.portFilePaths = env.getIpcPortPath().collect(Collectors.toUnmodifiableList());
this.ipcHandler = ipcHandler;
}
public IpcEndpoint create() {
if (portFilePaths.isEmpty()) {
LOG.warn("No IPC port file path specified.");
return new SelfEndpoint(ipcHandler);
} else {
System.setProperty("java.rmi.server.hostname", "localhost");
return attemptClientConnection().or(this::createServerEndpoint).orElseGet(() -> new SelfEndpoint(ipcHandler));
}
}
private Optional<IpcEndpoint> attemptClientConnection() {
for (Path portFilePath : portFilePaths) {
try {
int port = readPort(portFilePath);
LOG.debug("[Client] Connecting to port {}...", port);
Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
IpcProtocol remoteInterface = (IpcProtocol) registry.lookup(RMI_NAME);
return Optional.of(new ClientEndpoint(remoteInterface));
} catch (NotBoundException | IOException e) {
LOG.debug("[Client] Failed to connect.");
// continue with next portFilePath...
}
}
return Optional.empty();
}
private int readPort(Path portFilePath) throws IOException {
try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
LOG.debug("[Client] Reading IPC port from {}", portFilePath);
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
if (ch.read(buf) == Integer.BYTES) {
buf.flip();
return buf.getInt();
} else {
throw new IOException("Invalid IPC port file.");
}
}
}
private Optional<IpcEndpoint> createServerEndpoint() {
assert !portFilePaths.isEmpty();
Path portFilePath = portFilePaths.get(0);
try {
ServerSocket socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
Registry registry = LocateRegistry.createRegistry(0, csf, ssf);
UnicastRemoteObject.exportObject(ipcHandler, 0);
registry.rebind(RMI_NAME, ipcHandler);
writePort(portFilePath, socket.getLocalPort());
return Optional.of(new ServerEndpoint(ipcHandler, socket, registry, portFilePath));
} catch (IOException e) {
LOG.warn("[Server] Failed to create IPC server.", e);
return Optional.empty();
}
}
private void writePort(Path portFilePath, int port) throws IOException {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
buf.putInt(port);
buf.flip();
MoreFiles.createParentDirectories(portFilePath);
try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
if (ch.write(buf) != Integer.BYTES) {
throw new IOException("Did not write expected number of bytes.");
}
}
LOG.debug("[Server] Wrote IPC port {} to {}", port, portFilePath);
}
interface IpcEndpoint extends Closeable {
boolean isConnectedToRemote();
IpcProtocol getRemote();
}
static class SelfEndpoint implements IpcEndpoint {
protected final IpcProtocol remoteObject;
SelfEndpoint(IpcProtocol remoteObject) {
this.remoteObject = remoteObject;
}
@Override
public boolean isConnectedToRemote() {
return false;
}
@Override
public IpcProtocol getRemote() {
return remoteObject;
}
@Override
public void close() {
// no-op
}
}
static class ClientEndpoint implements IpcEndpoint {
private final IpcProtocol remoteInterface;
public ClientEndpoint(IpcProtocol remoteInterface) {
this.remoteInterface = remoteInterface;
}
public IpcProtocol getRemote() {
return remoteInterface;
}
@Override
public boolean isConnectedToRemote() {
return true;
}
@Override
public void close() {
// no-op
}
}
class ServerEndpoint extends SelfEndpoint {
private final ServerSocket socket;
private final Registry registry;
private final Path portFilePath;
private ServerEndpoint(IpcProtocol remoteObject, ServerSocket socket, Registry registry, Path portFilePath) {
super(remoteObject);
this.socket = socket;
this.registry = registry;
this.portFilePath = portFilePath;
}
@Override
public void close() {
try {
registry.unbind(RMI_NAME);
UnicastRemoteObject.unexportObject(remoteObject, true);
socket.close();
Files.deleteIfExists(portFilePath);
LOG.debug("[Server] Shut down");
} catch (NotBoundException | IOException e) {
LOG.warn("[Server] Error shutting down:", e);
}
}
}
/**
* Always returns the same pre-constructed server socket.
*/
private static class SingletonServerSocketFactory implements RMIServerSocketFactory {
private final ServerSocket socket;
public SingletonServerSocketFactory(ServerSocket socket) {
this.socket = socket;
}
@Override
public synchronized ServerSocket createServerSocket(int port) throws IOException {
if (port != 0) {
throw new IllegalArgumentException("This factory doesn't support specific ports.");
}
return this.socket;
}
}
/**
* Creates client sockets with short timeouts.
*/
private static class ClientSocketFactory implements RMIClientSocketFactory {
@Override
public Socket createSocket(String host, int port) throws IOException {
return new SocketWithFixedTimeout(host, port, 1000);
}
}
private static class SocketWithFixedTimeout extends Socket {
public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
super(host, port);
super.setSoTimeout(timeoutInMs);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
// do nothing, timeout is fixed
}
}
}
@@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.launcher;
import java.rmi.Remote;
import java.rmi.RemoteException;
interface IpcProtocol extends Remote {
void revealRunningApp() throws RemoteException;
void handleLaunchArgs(String... args) throws RemoteException;
}
@@ -1,6 +1,5 @@
package org.cryptomator.launcher; package org.cryptomator.launcher;
import org.cryptomator.ipc.IpcMessageListener;
import org.cryptomator.ui.launcher.AppLaunchEvent; import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -8,20 +7,20 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
@Singleton @Singleton
class IpcMessageHandler implements IpcMessageListener { class IpcProtocolImpl implements IpcProtocol {
private static final Logger LOG = LoggerFactory.getLogger(IpcMessageHandler.class); private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
private final FileOpenRequestHandler fileOpenRequestHandler; private final FileOpenRequestHandler fileOpenRequestHandler;
private final BlockingQueue<AppLaunchEvent> launchEventQueue; private final BlockingQueue<AppLaunchEvent> launchEventQueue;
@Inject @Inject
public IpcMessageHandler(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) { public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
this.fileOpenRequestHandler = fileOpenRequestHandler; this.fileOpenRequestHandler = fileOpenRequestHandler;
this.launchEventQueue = launchEventQueue; this.launchEventQueue = launchEventQueue;
} }
@@ -32,8 +31,8 @@ class IpcMessageHandler implements IpcMessageListener {
} }
@Override @Override
public void handleLaunchArgs(List<String> args) { public void handleLaunchArgs(String... args) {
LOG.debug("Received launch args: {}", args.stream().reduce((a, b) -> a + ", " + b).orElse("")); LOG.debug("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
fileOpenRequestHandler.handleLaunchArgs(args); fileOpenRequestHandler.handleLaunchArgs(args);
} }
@@ -76,7 +76,6 @@ public class CreateNewVaultPasswordController implements FxController {
private final BooleanProperty readyToCreateVault; private final BooleanProperty readyToCreateVault;
private final ObjectBinding<ContentDisplay> createVaultButtonState; private final ObjectBinding<ContentDisplay> createVaultButtonState;
/* FXML */
public ToggleGroup recoveryKeyChoice; public ToggleGroup recoveryKeyChoice;
public Toggle showRecoveryKey; public Toggle showRecoveryKey;
public Toggle skipRecoveryKey; public Toggle skipRecoveryKey;
@@ -107,7 +106,7 @@ public class CreateNewVaultPasswordController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
readyToCreateVault.bind(newPasswordSceneController.goodPasswordProperty().and(recoveryKeyChoice.selectedToggleProperty().isNotNull()).and(processing.not())); readyToCreateVault.bind(newPasswordSceneController.passwordsMatchAndSufficientProperty().and(recoveryKeyChoice.selectedToggleProperty().isNotNull()).and(processing.not()));
window.setOnHiding(event -> { window.setOnHiding(event -> {
newPasswordSceneController.passwordField.wipe(); newPasswordSceneController.passwordField.wipe();
newPasswordSceneController.reenterField.wipe(); newPasswordSceneController.reenterField.wipe();
@@ -62,7 +62,7 @@ public class ChangePasswordController implements FxController {
public void initialize() { public void initialize() {
BooleanBinding checkboxNotConfirmed = finalConfirmationCheckbox.selectedProperty().not(); BooleanBinding checkboxNotConfirmed = finalConfirmationCheckbox.selectedProperty().not();
BooleanBinding oldPasswordFieldEmpty = oldPasswordField.textProperty().isEmpty(); BooleanBinding oldPasswordFieldEmpty = oldPasswordField.textProperty().isEmpty();
finishButton.disableProperty().bind(checkboxNotConfirmed.or(oldPasswordFieldEmpty).or(newPasswordController.goodPasswordProperty().not())); finishButton.disableProperty().bind(checkboxNotConfirmed.or(oldPasswordFieldEmpty).or(newPasswordController.passwordsMatchAndSufficientProperty().not()));
window.setOnHiding(event -> { window.setOnHiding(event -> {
oldPasswordField.wipe(); oldPasswordField.wipe();
newPasswordController.passwordField.wipe(); newPasswordController.passwordField.wipe();
@@ -1,17 +1,11 @@
package org.cryptomator.ui.common; package org.cryptomator.ui.common;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.KeyValue; import javafx.animation.KeyValue;
import javafx.animation.RotateTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.beans.value.WritableValue; import javafx.beans.value.WritableValue;
import javafx.scene.Node;
import javafx.stage.Window; import javafx.stage.Window;
import javafx.util.Duration; import javafx.util.Duration;
import java.util.stream.IntStream;
public class Animations { public class Animations {
@@ -39,19 +33,4 @@ public class Animations {
); );
} }
public static SequentialTransition createDiscrete360Rotation(Node toAnimate) {
var animation = new SequentialTransition(IntStream.range(0, 8).mapToObj(i -> Animations.createDiscrete45Rotation()).toArray(Animation[]::new));
animation.setCycleCount(Animation.INDEFINITE);
animation.setNode(toAnimate);
return animation;
}
private static RotateTransition createDiscrete45Rotation() {
var animation = new RotateTransition(Duration.millis(100));
animation.setInterpolator(Interpolator.DISCRETE);
animation.setByAngle(45);
animation.setCycleCount(1);
return animation;
}
} }
@@ -1,84 +0,0 @@
package org.cryptomator.ui.common;
import com.tobiasdiez.easybind.EasyBind;
import com.tobiasdiez.easybind.Subscription;
import javafx.animation.Animation;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
/**
* Animation which starts and stops automatically based on an observable condition.
* <p>
* During creation the consumer can optionally define actions to be executed everytime before the animation starts and after it stops.
*/
public class AutoAnimator<T extends Animation> {
private final T animation;
private final ObservableValue<Boolean> condition;
private final Runnable beforeStart;
private final Runnable afterStop;
private final Subscription sub;
AutoAnimator(T animation, ObservableValue<Boolean> condition, Runnable beforeStart, Runnable afterStop) {
this.animation = animation;
this.condition = condition;
this.beforeStart = beforeStart;
this.afterStop = afterStop;
this.sub = EasyBind.subscribe(condition, this::togglePlay);
}
public void playFromStart() {
beforeStart.run();
animation.playFromStart();
}
public void stop() {
animation.stop();
afterStop.run();
}
private void togglePlay(boolean play) {
if (play) {
this.playFromStart();
} else {
this.stop();
}
}
public static Builder animate(Animation animation) {
return new Builder(animation);
}
public static class Builder {
private Animation animation;
private ObservableValue<Boolean> condition = new SimpleBooleanProperty(true);
private Runnable beforeStart = () -> {};
private Runnable afterStop = () -> {};
private Builder(Animation animation) {
this.animation = animation;
}
public Builder onCondition(ObservableValue<Boolean> condition) {
this.condition = condition;
return this;
}
public Builder beforeStart(Runnable beforeStart) {
this.beforeStart = beforeStart;
return this;
}
public Builder afterStop(Runnable afterStop) {
this.afterStop = afterStop;
return this;
}
public AutoAnimator build() {
return new AutoAnimator(animation, condition, beforeStart, afterStop);
}
}
}
@@ -2,8 +2,8 @@ package org.cryptomator.ui.common;
import dagger.BindsInstance; import dagger.BindsInstance;
import dagger.Subcomponent; import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import javax.annotation.Nullable;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
@@ -1,7 +1,6 @@
package org.cryptomator.ui.common; package org.cryptomator.ui.common;
import org.cryptomator.common.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@@ -12,7 +12,6 @@ public enum FxmlFile {
ERROR("/fxml/error.fxml"), // ERROR("/fxml/error.fxml"), //
FORGET_PASSWORD("/fxml/forget_password.fxml"), // FORGET_PASSWORD("/fxml/forget_password.fxml"), //
HEALTH_START("/fxml/health_start.fxml"), // HEALTH_START("/fxml/health_start.fxml"), //
HEALTH_START_FAIL("/fxml/health_start_fail.fxml"), //
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), // HEALTH_CHECK_LIST("/fxml/health_check_list.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"), //
@@ -5,6 +5,7 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -25,9 +26,11 @@ public class FxmlLoaderFactory {
/** /**
* @return A new FXMLLoader instance * @return A new FXMLLoader instance
*/ */
private FXMLLoader construct(String fxmlResourceName) { public FXMLLoader construct() {
var url = getClass().getResource(fxmlResourceName); FXMLLoader loader = new FXMLLoader();
return new FXMLLoader(url, resourceBundle, null, this::constructController); loader.setControllerFactory(this::constructController);
loader.setResources(resourceBundle);
return loader;
} }
/** /**
@@ -38,8 +41,10 @@ public class FxmlLoaderFactory {
* @throws IOException if an error occurs while loading the FXML file * @throws IOException if an error occurs while loading the FXML file
*/ */
public FXMLLoader load(String fxmlResourceName) throws IOException { public FXMLLoader load(String fxmlResourceName) throws IOException {
FXMLLoader loader = construct(fxmlResourceName); FXMLLoader loader = construct();
loader.load(); try (InputStream in = getClass().getResourceAsStream(fxmlResourceName)) {
loader.load(in);
}
return loader; return loader;
} }
@@ -4,12 +4,12 @@ import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.ui.controls.FontAwesome5IconView; import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField; import org.cryptomator.ui.controls.NiceSecurePasswordField;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@@ -20,7 +20,7 @@ public class NewPasswordController implements FxController {
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final PasswordStrengthUtil strengthRater; private final PasswordStrengthUtil strengthRater;
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1);
private final BooleanProperty goodPassword = new SimpleBooleanProperty(); private final ReadOnlyBooleanWrapper passwordsMatchAndSufficient = new ReadOnlyBooleanWrapper();
public NiceSecurePasswordField passwordField; public NiceSecurePasswordField passwordField;
public NiceSecurePasswordField reenterField; public NiceSecurePasswordField reenterField;
@@ -50,10 +50,11 @@ public class NewPasswordController implements FxController {
passwordMatchLabel.graphicProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(passwordMatchCheckmark).otherwise(passwordMatchCross)); passwordMatchLabel.graphicProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(passwordMatchCheckmark).otherwise(passwordMatchCross));
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("newPassword.passwordsMatch")).otherwise(resourceBundle.getString("newPassword.passwordsDoNotMatch"))); passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("newPassword.passwordsMatch")).otherwise(resourceBundle.getString("newPassword.passwordsDoNotMatch")));
BooleanBinding sufficientStrength = Bindings.createBooleanBinding(this::sufficientStrength, passwordField.textProperty()); passwordField.textProperty().addListener(this::passwordsDidChange);
goodPassword.bind(passwordsMatch.and(sufficientStrength)); reenterField.textProperty().addListener(this::passwordsDidChange);
} }
private FontAwesome5IconView getIconViewForPasswordStrengthLabel() { private FontAwesome5IconView getIconViewForPasswordStrengthLabel() {
if (passwordField.getCharacters().length() == 0) { if (passwordField.getCharacters().length() == 0) {
return null; return null;
@@ -66,24 +67,22 @@ public class NewPasswordController implements FxController {
} }
} }
private void passwordsDidChange(@SuppressWarnings("unused") Observable observable) {
if (passwordFieldsMatch() && strengthRater.fulfillsMinimumRequirements(passwordField.getCharacters())) {
passwordsMatchAndSufficient.setValue(true);
}
}
private boolean passwordFieldsMatch() { private boolean passwordFieldsMatch() {
return CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0; return CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0;
} }
private boolean sufficientStrength() { public ReadOnlyBooleanProperty passwordsMatchAndSufficientProperty() {
return strengthRater.fulfillsMinimumRequirements(passwordField.getCharacters()); return passwordsMatchAndSufficient.getReadOnlyProperty();
} }
/* Getter/Setter */ /* Getter/Setter */
public ReadOnlyBooleanProperty goodPasswordProperty() {
return goodPassword;
}
public boolean isGoodPassword() {
return goodPassword.get();
}
public IntegerProperty passwordStrengthProperty() { public IntegerProperty passwordStrengthProperty() {
return passwordStrength; return passwordStrength;
} }
@@ -8,8 +8,6 @@ public enum FontAwesome5Icon {
ARROW_UP("\uF062"), // ARROW_UP("\uF062"), //
BAN("\uF05E"), // BAN("\uF05E"), //
BUG("\uF188"), // BUG("\uF188"), //
CARET_DOWN("\uF0D7"), //
CARET_RIGHT("\uF0Da"), //
CHECK("\uF00C"), // CHECK("\uF00C"), //
CLOCK("\uF017"), // CLOCK("\uF017"), //
COG("\uF013"), // COG("\uF013"), //
@@ -22,7 +20,6 @@ public enum FontAwesome5Icon {
EXCLAMATION_TRIANGLE("\uF071"), // EXCLAMATION_TRIANGLE("\uF071"), //
EYE("\uF06E"), // EYE("\uF06E"), //
EYE_SLASH("\uF070"), // EYE_SLASH("\uF070"), //
FAST_FORWARD("\uF050"), //
FILE("\uF15B"), // FILE("\uF15B"), //
FILE_IMPORT("\uF56F"), // FILE_IMPORT("\uF56F"), //
FOLDER_OPEN("\uF07C"), // FOLDER_OPEN("\uF07C"), //
@@ -21,8 +21,8 @@ public class FontAwesome5IconView extends Text {
private static final String FONT_PATH = "/css/fontawesome5-free-solid.otf"; private static final String FONT_PATH = "/css/fontawesome5-free-solid.otf";
private static final Font FONT; private static final Font FONT;
protected final ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH); private ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
protected final DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE); private DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
static { static {
try { try {
@@ -42,7 +42,7 @@ public class FontAwesome5IconView extends Text {
} }
private void glyphChanged(@SuppressWarnings("unused") ObservableValue<? extends FontAwesome5Icon> observable, @SuppressWarnings("unused") FontAwesome5Icon oldValue, FontAwesome5Icon newValue) { private void glyphChanged(@SuppressWarnings("unused") ObservableValue<? extends FontAwesome5Icon> observable, @SuppressWarnings("unused") FontAwesome5Icon oldValue, FontAwesome5Icon newValue) {
setText(newValue == null ? null : newValue.unicode()); setText(newValue.unicode());
} }
private void glyphSizeChanged(@SuppressWarnings("unused") ObservableValue<? extends Number> observable, @SuppressWarnings("unused") Number oldValue, Number newValue) { private void glyphSizeChanged(@SuppressWarnings("unused") ObservableValue<? extends Number> observable, @SuppressWarnings("unused") Number oldValue, Number newValue) {
@@ -1,44 +1,69 @@
package org.cryptomator.ui.controls; package org.cryptomator.ui.controls;
import org.cryptomator.ui.common.Animations; import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.ui.common.AutoAnimator;
import javafx.beans.property.BooleanProperty; import javafx.animation.Interpolator;
import javafx.beans.property.ObjectProperty; import javafx.animation.RotateTransition;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.control.ProgressIndicator;
import javafx.util.Duration;
/** /**
* An animated progress spinner using the {@link FontAwesome5IconView} with the spinner glyph. * A progress indicator in the shape of {@link FontAwesome5Icon#SPINNER}. The single spinner segements are defined in the css in the `progress-indicator` class.
* <p> * <p>
* Using the default constructor, the animation is always played if the icon is visible. To animate on other conditions, use the constructor with the "spinning" property. * See also https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html#progressindicator
*/ */
public class FontAwesome5Spinner extends FontAwesome5IconView { public class FontAwesome5Spinner extends ProgressIndicator {
protected final BooleanProperty spinning = new SimpleBooleanProperty(this, "spinning", true); private static final double DEFAULT_GLYPH_SIZE = 12.0;
private AutoAnimator animator; private final FontAwesome5IconView boundingBox;
private final RotateTransition animation;
private final DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
public FontAwesome5Spinner() { public FontAwesome5Spinner() {
setGlyph(FontAwesome5Icon.SPINNER); this.boundingBox = new FontAwesome5IconView();
var animation = Animations.createDiscrete360Rotation(this); boundingBox.setGlyph(FontAwesome5Icon.SPINNER);
this.animator = AutoAnimator.animate(animation) // boundingBox.glyphSizeProperty().bind(glyphSize);
.afterStop(() -> setRotate(0)) //
.onCondition(spinning.and(visibleProperty())) // this.animation = new RotateTransition(Duration.millis(100), this);
.build(); animation.setInterpolator(Interpolator.DISCRETE);
animation.setByAngle(45);
animation.setCycleCount(1);
animation.setOnFinished(event -> {
double currentAngle = this.getRotate();
animation.setFromAngle(currentAngle);
animation.play();
});
EasyBind.subscribe(this.visibleProperty(), this::reset);
EasyBind.subscribe(boundingBox.glyphSizeProperty(), this::shrinkToGlyphSize);
} }
/* Getter/Setter */ private void shrinkToGlyphSize(Number newValue) {
setMaxSize(boundingBox.getBoundsInLocal().getWidth(), boundingBox.getBoundsInLocal().getHeight());
public BooleanProperty spinningProperty() {
return spinning;
} }
public boolean isSpinning() { private void reset(boolean flag) {
return spinning.get(); if (flag) {
setRotate(0);
animation.playFromStart();
} else {
animation.stop();
}
} }
public void setSpinning(boolean spinning) { public DoubleProperty glyphSizeProperty() {
this.spinning.set(spinning); return glyphSize;
}
public double getGlyphSize() {
return glyphSize.get();
}
public void setGlyphSize(double glyphSize) {
this.glyphSize.set(glyphSize);
} }
} }
@@ -0,0 +1,71 @@
package org.cryptomator.ui.controls;
import com.tobiasdiez.easybind.EasyBind;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Pos;
import javafx.scene.layout.StackPane;
/**
* Similar to the {@link FontAwesome5IconView}, except that if the spinner glyph is selected, an animated facsimile is used.
*/
public class FontAwesome5StatusView extends StackPane {
private static final double DEFAULT_GLYPH_SIZE = 12.0;
private static final FontAwesome5Icon DEFAULT_GLYPH = FontAwesome5Icon.ANCHOR;
private final FontAwesome5IconView staticIcon;
private final FontAwesome5Spinner animatedSpinner;
private final BooleanBinding isSpinnerGlyph;
private final ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
private final DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
public FontAwesome5StatusView() {
this.staticIcon = new FontAwesome5IconView();
this.animatedSpinner = new FontAwesome5Spinner();
this.isSpinnerGlyph = glyphProperty().isEqualTo(FontAwesome5Icon.SPINNER);
setAlignment(Pos.CENTER);
getChildren().addAll(staticIcon, animatedSpinner);
staticIcon.glyphProperty().bind(glyph);
staticIcon.glyphSizeProperty().bind(glyphSize);
animatedSpinner.glyphSizeProperty().bind(glyphSize);
EasyBind.subscribe(isSpinnerGlyph, this::showSpinner);
}
private void showSpinner(boolean isSpinner) {
animatedSpinner.setVisible(isSpinner);
staticIcon.setVisible(!isSpinner);
}
public ObjectProperty<FontAwesome5Icon> glyphProperty() {
return glyph;
}
public FontAwesome5Icon getGlyph() {
return glyph.get();
}
public void setGlyph(FontAwesome5Icon glyph) {
this.glyph.set(glyph == null ? DEFAULT_GLYPH : glyph);
}
public DoubleProperty glyphSizeProperty() {
return glyphSize;
}
public double getGlyphSize() {
return glyphSize.get();
}
public void setGlyphSize(double glyphSize) {
this.glyphSize.set(glyphSize);
}
}
@@ -188,12 +188,9 @@ public class FxApplication extends Application {
} }
private void applySystemTheme() { private void applySystemTheme() {
if (appearanceProvider.isPresent()) { appearanceProvider.ifPresent(appearanceProvider -> {
systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme()); systemInterfaceThemeChanged(appearanceProvider.getSystemTheme());
} else { });
LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme...");
applyLightTheme();
}
} }
private void applyLightTheme() { private void applyLightTheme() {
@@ -0,0 +1,36 @@
package org.cryptomator.ui.health;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import dagger.Lazy;
import javax.inject.Inject;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
public class BatchService extends Service<Void> {
private final Iterator<HealthCheckTask> remainingTasks;
@Inject
public BatchService(Iterable<HealthCheckTask> tasks) {
this.remainingTasks = tasks.iterator();
}
@Override
protected Task<Void> createTask() {
Preconditions.checkState(remainingTasks.hasNext(), "No remaining tasks");
return remainingTasks.next();
}
@Override
protected void succeeded() {
if (remainingTasks.hasNext()) {
this.restart();
}
}
}
@@ -1,103 +0,0 @@
package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.cryptofs.health.api.HealthCheck;
import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Check {
private final HealthCheck check;
private final BooleanProperty chosenForExecution = new SimpleBooleanProperty(false);
private final ObjectProperty<CheckState> state = new SimpleObjectProperty<>(CheckState.RUNNABLE);
private final ObservableList<Result> results = FXCollections.observableArrayList(Result::observables);
private final ObjectProperty<DiagnosticResult.Severity> highestResultSeverity = new SimpleObjectProperty<>(null);
private final ObjectProperty<Throwable> error = new SimpleObjectProperty<>(null);
private final BooleanBinding isInReRunState = state.isNotEqualTo(CheckState.RUNNING).or(state.isNotEqualTo(CheckState.SCHEDULED));
Check(HealthCheck check) {
this.check = check;
}
String getName() {
return check.name();
}
HealthCheck getHealthCheck() {
return check;
}
BooleanProperty chosenForExecutionProperty() {
return chosenForExecution;
}
boolean isChosenForExecution() {
return chosenForExecution.get();
}
ObjectProperty<CheckState> stateProperty() {
return state;
}
CheckState getState() {
return state.get();
}
void setState(CheckState newState) {
state.set(newState);
}
ObjectProperty<Throwable> errorProperty() {
return error;
}
Throwable getError() {
return error.get();
}
void setError(Throwable t) {
error.set(t);
}
ObjectProperty<DiagnosticResult.Severity> highestResultSeverityProperty() {
return highestResultSeverity;
}
DiagnosticResult.Severity getHighestResultSeverity() {
return highestResultSeverity.get();
}
void setHighestResultSeverity(DiagnosticResult.Severity severity) {
highestResultSeverity.set(severity);
}
boolean isInReRunState() {
return isInReRunState.get();
}
enum CheckState {
RUNNABLE,
SCHEDULED,
RUNNING,
SUCCEEDED,
SKIPPED,
ERROR,
CANCELLED;
}
ObservableList<Result> getResults() {
return results;
}
Observable[] observables() {
return new Observable[]{chosenForExecution, state, results, error};
}
}
@@ -12,64 +12,67 @@ import javafx.beans.binding.Binding;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.concurrent.Worker;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import java.time.Duration;
import java.util.ResourceBundle;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
@HealthCheckScoped @HealthCheckScoped
public class CheckDetailController implements FxController { public class CheckDetailController implements FxController {
private final EasyObservableList<Result> results; private final EasyObservableList<DiagnosticResult> results;
private final ObjectProperty<Check> check; private final OptionalBinding<Worker.State> taskState;
private final OptionalBinding<Check.CheckState> checkState; private final Binding<String> taskName;
private final Binding<String> checkName; private final Binding<String> taskDuration;
private final Binding<Boolean> checkRunning; private final Binding<Boolean> taskRunning;
private final Binding<Boolean> checkScheduled; private final Binding<Boolean> taskScheduled;
private final Binding<Boolean> checkFinished; private final Binding<Boolean> taskFinished;
private final Binding<Boolean> checkSkipped; private final Binding<Boolean> taskNotStarted;
private final Binding<Boolean> checkSucceeded; private final Binding<Boolean> taskSucceeded;
private final Binding<Boolean> checkFailed; private final Binding<Boolean> taskFailed;
private final Binding<Boolean> checkCancelled; private final Binding<Boolean> taskCancelled;
private final Binding<Number> countOfWarnSeverity; private final Binding<Number> countOfWarnSeverity;
private final Binding<Number> countOfCritSeverity; private final Binding<Number> countOfCritSeverity;
private final Binding<Boolean> warnOrCritsExist;
private final ResultListCellFactory resultListCellFactory; private final ResultListCellFactory resultListCellFactory;
private final ResourceBundle resourceBundle;
public ListView<Result> resultsListView; public ListView<DiagnosticResult> resultsListView;
private Subscription resultSubscription; private Subscription resultSubscription;
@Inject @Inject
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory) { public CheckDetailController(ObjectProperty<HealthCheckTask> selectedTask, ResultListCellFactory resultListCellFactory, ResourceBundle resourceBundle) {
this.resultListCellFactory = resultListCellFactory; this.resultListCellFactory = resultListCellFactory;
this.resourceBundle = resourceBundle;
this.results = EasyBind.wrapList(FXCollections.observableArrayList()); this.results = EasyBind.wrapList(FXCollections.observableArrayList());
this.check = selectedTask; this.taskState = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::stateProperty);
this.checkState = EasyBind.wrapNullable(selectedTask).mapObservable(Check::stateProperty); this.taskName = EasyBind.wrapNullable(selectedTask).map(HealthCheckTask::getTitle).orElse("");
this.checkName = EasyBind.wrapNullable(selectedTask).map(Check::getName).orElse(""); this.taskDuration = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::durationInMillisProperty).orElse(-1L).map(this::millisToReadAbleDuration);
this.checkRunning = checkState.map(Check.CheckState.RUNNING::equals).orElse(false); this.taskRunning = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::runningProperty).orElse(false); //TODO: DOES NOT WORK
this.checkScheduled = checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false); this.taskScheduled = taskState.map(Worker.State.SCHEDULED::equals).orElse(false);
this.checkSkipped = checkState.map(Check.CheckState.SKIPPED::equals).orElse(false); this.taskNotStarted = taskState.map(Worker.State.READY::equals).orElse(false);
this.checkSucceeded = checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false); this.taskSucceeded = taskState.map(Worker.State.SUCCEEDED::equals).orElse(false);
this.checkFailed = checkState.map(Check.CheckState.ERROR::equals).orElse(false); this.taskFailed = taskState.map(Worker.State.FAILED::equals).orElse(false);
this.checkCancelled = checkState.map(Check.CheckState.CANCELLED::equals).orElse(false); this.taskCancelled = taskState.map(Worker.State.CANCELLED::equals).orElse(false);
this.checkFinished = EasyBind.combine(checkSucceeded, checkFailed, checkCancelled, (a, b, c) -> a || b || c); this.taskFinished = EasyBind.combine(taskSucceeded, taskFailed, taskCancelled, (a, b, c) -> a || b || c);
this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN)); this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN));
this.countOfCritSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.CRITICAL)); this.countOfCritSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.CRITICAL));
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0) );
selectedTask.addListener(this::selectedTaskChanged); selectedTask.addListener(this::selectedTaskChanged);
} }
private void selectedTaskChanged(ObservableValue<? extends Check> observable, Check oldValue, Check newValue) { private void selectedTaskChanged(ObservableValue<? extends HealthCheckTask> observable, HealthCheckTask oldValue, HealthCheckTask newValue) {
if (resultSubscription != null) { if (resultSubscription != null) {
resultSubscription.unsubscribe(); resultSubscription.unsubscribe();
} }
if (newValue != null) { if (newValue != null) {
resultSubscription = EasyBind.bindContent(results, newValue.getResults()); resultSubscription = EasyBind.bindContent(results, newValue.results());
} }
} }
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) { private Function<Stream<? extends DiagnosticResult>, Long> countSeverity(DiagnosticResult.Severity severity) {
return stream -> stream.filter(item -> severity.equals(item.diagnosis().getSeverity())).count(); return stream -> stream.filter(item -> severity.equals(item.getSeverity())).count();
} }
@FXML @FXML
@@ -80,12 +83,20 @@ public class CheckDetailController implements FxController {
/* Getter/Setter */ /* Getter/Setter */
public String getCheckName() { public String getTaskName() {
return checkName.getValue(); return taskName.getValue();
} }
public Binding<String> checkNameProperty() { public Binding<String> taskNameProperty() {
return checkName; return taskName;
}
public String getTaskDuration() {
return taskDuration.getValue();
}
public Binding<String> taskDurationProperty() {
return taskDuration;
} }
public long getCountOfWarnSeverity() { public long getCountOfWarnSeverity() {
@@ -104,75 +115,77 @@ public class CheckDetailController implements FxController {
return countOfCritSeverity; return countOfCritSeverity;
} }
public boolean isCheckRunning() { public boolean isTaskRunning() {
return checkRunning.getValue(); return taskRunning.getValue();
} }
public Binding<Boolean> checkRunningProperty() { public Binding<Boolean> taskRunningProperty() {
return checkRunning; return taskRunning;
} }
public boolean isCheckFinished() { public boolean isTaskFinished() {
return checkFinished.getValue(); return taskFinished.getValue();
} }
public Binding<Boolean> checkFinishedProperty() { public Binding<Boolean> taskFinishedProperty() {
return checkFinished; return taskFinished;
} }
public boolean isCheckScheduled() { public boolean isTaskScheduled() {
return checkScheduled.getValue(); return taskScheduled.getValue();
} }
public Binding<Boolean> checkScheduledProperty() { public Binding<Boolean> taskScheduledProperty() {
return checkScheduled; return taskScheduled;
} }
public boolean isCheckSkipped() { public boolean isTaskNotStarted() {
return checkSkipped.getValue(); return taskNotStarted.getValue();
} }
public Binding<Boolean> checkSkippedProperty() { public Binding<Boolean> taskNotStartedProperty() {
return checkSkipped; return taskNotStarted;
} }
public boolean isCheckSucceeded() { public boolean isTaskSucceeded() {
return checkSucceeded.getValue(); return taskSucceeded.getValue();
} }
public Binding<Boolean> checkSucceededProperty() { public Binding<Boolean> taskSucceededProperty() {
return checkSucceeded; return taskSucceeded;
} }
public boolean isCheckFailed() { public boolean isTaskFailed() {
return checkFailed.getValue(); return taskFailed.getValue();
} }
public Binding<Boolean> checkFailedProperty() { public Binding<Boolean> taskFailedProperty() {
return checkFailed; return taskFailed;
} }
public boolean isCheckCancelled() { public boolean isTaskCancelled() {
return checkCancelled.getValue(); return taskCancelled.getValue();
} }
public Binding<Boolean> warnOrCritsExistProperty() { public Binding<Boolean> taskCancelledProperty() {
return warnOrCritsExist; return taskCancelled;
} }
public boolean isWarnOrCritsExist() { private String millisToReadAbleDuration(Number millis) {
return warnOrCritsExist.getValue(); Duration tmp = Duration.ofMillis(millis.longValue());
long hours = tmp.toHoursPart();
long minutes = tmp.toMinutesPart();
long seconds = tmp.toSecondsPart();
if (hours != 0) {
String hms_format = resourceBundle.getString("health.check.detail.hmsFormat");
return String.format(hms_format, hours, minutes, seconds);
} else if (minutes != 0) {
String ms_format = resourceBundle.getString("health.check.detail.msFormat");
return String.format(ms_format, minutes, seconds);
} else {
String s_format = resourceBundle.getString("health.check.detail.sFormat");
return String.format(s_format, seconds);
}
} }
public Binding<Boolean> checkCancelledProperty() {
return checkCancelled;
}
public ObjectProperty<Check> checkProperty() {
return check;
}
public Check getCheck() {
return check.get();
}
} }
@@ -1,109 +0,0 @@
package org.cryptomator.ui.health;
import com.google.common.collect.Comparators;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.concurrent.Task;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.util.List;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicReference;
@HealthCheckScoped
public class CheckExecutor {
private final Path vaultPath;
private final SecureRandom csprng;
private final Masterkey masterkey;
private final VaultConfig vaultConfig;
private final ExecutorService sequentialExecutor;
private final BlockingDeque<CheckTask> tasksToExecute;
@Inject
public CheckExecutor(@HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng) {
this.vaultPath = vault.getPath();
this.masterkey = masterkeyRef.get();
this.vaultConfig = vaultConfigRef.get();
this.csprng = csprng;
this.tasksToExecute = new LinkedBlockingDeque<>();
this.sequentialExecutor = Executors.newSingleThreadExecutor();
}
public synchronized void executeBatch(List<Check> checks) {
checks.stream().map(c -> {
c.setState(Check.CheckState.SCHEDULED);
var task = new CheckTask(c);
tasksToExecute.addLast(task);
return task;
}).forEach(sequentialExecutor::submit);
}
public synchronized void cancel() {
CheckTask task;
while ((task = tasksToExecute.pollLast()) != null) {
task.cancel(true);
}
}
private class CheckTask extends Task<Void> {
private final Check c;
private DiagnosticResult.Severity highestResultSeverity = DiagnosticResult.Severity.GOOD;
CheckTask(Check c) {
this.c = c;
}
@Override
protected Void call() throws Exception {
try (var masterkeyClone = masterkey.clone(); //
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
c.getHealthCheck().check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis)));
highestResultSeverity = Comparators.max(highestResultSeverity, diagnosis.getSeverity());
});
}
return null;
}
@Override
protected void running() {
c.setState(Check.CheckState.RUNNING);
}
@Override
protected void cancelled() {
c.setState(Check.CheckState.CANCELLED);
}
@Override
protected void succeeded() {
c.setState(Check.CheckState.SUCCEEDED);
c.setHighestResultSeverity(highestResultSeverity);
}
@Override
protected void failed() {
c.setState(Check.CheckState.ERROR);
c.setError(this.getException());
}
@Override
protected void done() {
tasksToExecute.remove(this);
}
}
}
@@ -0,0 +1,115 @@
package org.cryptomator.ui.health;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.util.Callback;
class CheckListCell extends ListCell<HealthCheckTask> {
private final FontAwesome5IconView stateIcon = new FontAwesome5IconView();
private final Callback<HealthCheckTask, BooleanProperty> selectedGetter;
private final ObjectProperty<State> stateProperty;
private CheckBox checkBox = new CheckBox();
private BooleanProperty selectedProperty;
CheckListCell(Callback<HealthCheckTask, BooleanProperty> selectedGetter, ObservableValue<Boolean> switchIndicator) {
this.selectedGetter = selectedGetter;
this.stateProperty = new SimpleObjectProperty<>(State.SELECTION);
switchIndicator.addListener(this::changeState);
setPadding(new Insets(6));
setAlignment(Pos.CENTER_LEFT);
setContentDisplay(ContentDisplay.LEFT);
getStyleClass().add("label");
}
private void changeState(ObservableValue<? extends Boolean> observableValue, boolean oldValue, boolean newValue) {
if (newValue) {
stateProperty.set(State.RUN);
} else {
stateProperty.set(State.SELECTION);
}
}
@Override
protected void updateItem(HealthCheckTask item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getTitle());
}
switch (stateProperty.get()) {
case SELECTION -> updateItemSelection(item, empty);
case RUN -> updateItemRun(item, empty);
}
}
private void updateItemSelection(HealthCheckTask item, boolean empty) {
if (!empty) {
setGraphic(checkBox);
if (selectedProperty != null) {
checkBox.selectedProperty().unbindBidirectional(selectedProperty);
}
selectedProperty = selectedGetter.call(item);
if (selectedProperty != null) {
checkBox.selectedProperty().bindBidirectional(selectedProperty);
}
} else {
setGraphic(null);
setText(null);
}
}
private void updateItemRun(HealthCheckTask item, boolean empty) {
if (item != null) {
item.stateProperty().addListener(this::stateChanged);
graphicProperty().bind(Bindings.createObjectBinding(() -> graphicForState(item.getState()), item.stateProperty()));
stateIcon.setGlyph(glyphForState(item.getState()));
} else {
graphicProperty().unbind();
setGraphic(null);
setText(null);
}
}
private void stateChanged(ObservableValue<? extends Worker.State> observable, Worker.State oldState, Worker.State newState) {
stateIcon.setGlyph(glyphForState(newState));
stateIcon.setVisible(true);
}
private Node graphicForState(Worker.State state) {
return switch (state) {
case READY -> null;
case SCHEDULED, RUNNING, FAILED, CANCELLED, SUCCEEDED -> stateIcon;
};
}
private FontAwesome5Icon glyphForState(Worker.State state) {
return switch (state) {
case READY -> FontAwesome5Icon.COG; //just a placeholder
case SCHEDULED -> FontAwesome5Icon.CLOCK;
case RUNNING -> FontAwesome5Icon.SPINNER;
case FAILED -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
case CANCELLED -> FontAwesome5Icon.BAN;
case SUCCEEDED -> FontAwesome5Icon.CHECK;
};
}
private enum State {
SELECTION,
RUN;
}
}
@@ -1,70 +0,0 @@
package org.cryptomator.ui.health;
import com.tobiasdiez.easybind.EasyBind;
import com.tobiasdiez.easybind.Subscription;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
import javafx.beans.binding.Binding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.CheckBox;
import java.util.ArrayList;
import java.util.List;
public class CheckListCellController implements FxController {
private final ObjectProperty<Check> check;
private final Binding<String> checkName;
private final Binding<Boolean> checkRunnable;
private final List<Subscription> subscriptions;
/* FXML */
public CheckBox forRunSelectedCheckBox;
@Inject
public CheckListCellController() {
check = new SimpleObjectProperty<>();
checkRunnable = EasyBind.wrapNullable(check).mapObservable(Check::stateProperty).map(Check.CheckState.RUNNABLE::equals).orElse(false);
checkName = EasyBind.wrapNullable(check).map(Check::getName).orElse("");
subscriptions = new ArrayList<>();
}
public void initialize() {
subscriptions.add(EasyBind.subscribe(check, c -> {
forRunSelectedCheckBox.selectedProperty().unbind();
if (c != null) {
forRunSelectedCheckBox.selectedProperty().bindBidirectional(c.chosenForExecutionProperty());
}
}));
}
public ObjectProperty<Check> checkProperty() {
return check;
}
public Check getCheck() {
return check.get();
}
public void setCheck(Check c) {
check.set(c);
}
public Binding<String> checkNameProperty() {
return checkName;
}
public String getCheckName() {
return checkName.getValue();
}
public Binding<Boolean> checkRunnableProperty() {
return checkRunnable;
}
public boolean isCheckRunnable() {
return checkRunnable.getValue();
}
}
@@ -1,58 +0,0 @@
package org.cryptomator.ui.health;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import javax.inject.Inject;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
import java.io.IOException;
import java.io.UncheckedIOException;
// unscoped because each cell needs its own controller
public class CheckListCellFactory implements Callback<ListView<Check>, ListCell<Check>> {
private final FxmlLoaderFactory fxmlLoaders;
@Inject
CheckListCellFactory(@HealthCheckWindow FxmlLoaderFactory fxmlLoaders) {
this.fxmlLoaders = fxmlLoaders;
}
@Override
public ListCell<Check> call(ListView<Check> param) {
try {
FXMLLoader fxmlLoader = fxmlLoaders.load("/fxml/health_check_listcell.fxml");
return new CheckListCellFactory.Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
} catch (IOException e) {
throw new UncheckedIOException("Failed to load /fxml/health_check_listcell.fxml.", e);
}
}
private static class Cell extends ListCell<Check> {
private final Parent node;
private final CheckListCellController controller;
public Cell(Parent node, CheckListCellController controller) {
this.node = node;
this.controller = controller;
}
@Override
protected void updateItem(Check item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
} else {
controller.setCheck(item);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(node);
}
}
}
}
@@ -1,6 +1,7 @@
package org.cryptomator.ui.health; package org.cryptomator.ui.health;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.tobiasdiez.easybind.EasyBind;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
@@ -8,100 +9,116 @@ 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.Binding;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.IntegerBinding; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList; import javafx.concurrent.Worker;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode; import javafx.scene.control.cell.CheckBoxListCell;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.util.StringConverter;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@HealthCheckScoped @HealthCheckScoped
public class CheckListController implements FxController { public class CheckListController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(CheckListController.class); private static final Logger LOG = LoggerFactory.getLogger(CheckListController.class);
private static final Set<Worker.State> END_STATES = Set.of(Worker.State.FAILED, Worker.State.CANCELLED, Worker.State.SUCCEEDED);
private final Stage window; private final Stage window;
private final ObservableList<Check> checks; private final ObservableList<HealthCheckTask> tasks;
private final CheckExecutor checkExecutor;
private final FilteredList<Check> chosenChecks;
private final ReportWriter reportWriter; private final ReportWriter reportWriter;
private final ObjectProperty<Check> selectedCheck; private final ExecutorService executorService;
private final BooleanBinding mainRunStarted; //TODO: rerunning not considered for now private final ObjectProperty<HealthCheckTask> selectedTask;
private final BooleanBinding somethingsRunning;
private final Lazy<ErrorComponent.Builder> errorComponentBuilder; private final Lazy<ErrorComponent.Builder> errorComponentBuilder;
private final IntegerBinding chosenTaskCount; private final SimpleObjectProperty<Worker<?>> runningTask;
private final Binding<Boolean> running;
private final Binding<Boolean> finished;
private final Map<HealthCheckTask, BooleanProperty> listPickIndicators;
private final IntegerProperty numberOfPickedChecks;
private final BooleanBinding anyCheckSelected; private final BooleanBinding anyCheckSelected;
private final CheckListCellFactory listCellFactory; private final BooleanProperty showResultScreen;
/* FXML */ /* FXML */
public ListView<Check> checksListView; public ListView<HealthCheckTask> checksListView;
@Inject @Inject
public CheckListController(@HealthCheckWindow Stage window, List<Check> checks, CheckExecutor checkExecutor, ReportWriter reportWriteTask, ObjectProperty<Check> selectedCheck, Lazy<ErrorComponent.Builder> errorComponentBuilder, CheckListCellFactory listCellFactory) { public CheckListController(@HealthCheckWindow Stage window, Lazy<Collection<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponentBuilder) {
this.window = window; this.window = window;
this.checks = FXCollections.observableList(checks, Check::observables); this.tasks = FXCollections.observableArrayList(tasks.get());
this.checkExecutor = checkExecutor;
this.listCellFactory = listCellFactory;
this.chosenChecks = this.checks.filtered(Check::isChosenForExecution);
this.reportWriter = reportWriteTask; this.reportWriter = reportWriteTask;
this.selectedCheck = selectedCheck; this.executorService = executorService;
this.selectedTask = selectedTask;
this.errorComponentBuilder = errorComponentBuilder; this.errorComponentBuilder = errorComponentBuilder;
this.chosenTaskCount = Bindings.size(this.chosenChecks); this.runningTask = new SimpleObjectProperty<>();
this.mainRunStarted = Bindings.isEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.RUNNABLE)); this.running = EasyBind.wrapNullable(runningTask).mapObservable(Worker::runningProperty).orElse(false);
this.somethingsRunning = Bindings.isNotEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.SCHEDULED || c.getState() == Check.CheckState.RUNNING)); this.finished = EasyBind.wrapNullable(runningTask).mapObservable(Worker::stateProperty).map(END_STATES::contains).orElse(false);
this.anyCheckSelected = selectedCheck.isNotNull(); this.listPickIndicators = new HashMap<>();
this.numberOfPickedChecks = new SimpleIntegerProperty(0);
this.tasks.forEach(task -> {
var entrySelectedProp = new SimpleBooleanProperty(false);
entrySelectedProp.addListener((observable, oldValue, newValue) -> numberOfPickedChecks.set(numberOfPickedChecks.get() + (newValue ? 1 : -1)));
listPickIndicators.put(task, entrySelectedProp);
});
this.anyCheckSelected = selectedTask.isNotNull();
this.showResultScreen = new SimpleBooleanProperty(false);
} }
@FXML @FXML
public void initialize() { public void initialize() {
checksListView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); checksListView.setItems(tasks);
checksListView.setItems(checks); checksListView.setCellFactory(view -> new CheckListCell(listPickIndicators::get, showResultScreen));
checksListView.setCellFactory(listCellFactory); selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty());
selectedCheck.bind(checksListView.getSelectionModel().selectedItemProperty());
} }
@FXML @FXML
public void selectAllChecks() { public void toggleSelectAll(ActionEvent event) {
checks.forEach(t -> t.chosenForExecutionProperty().set(true)); if (event.getSource() instanceof CheckBox c) {
} listPickIndicators.forEach( (task, pickProperty) -> pickProperty.set(c.isSelected()));
}
@FXML
public void deselectAllChecks() {
checks.forEach(t -> t.chosenForExecutionProperty().set(false));
} }
@FXML @FXML
public void runSelectedChecks() { public void runSelectedChecks() {
Preconditions.checkState(!mainRunStarted.get()); Preconditions.checkState(runningTask.get() == null);
Preconditions.checkState(!somethingsRunning.get()); var batch = checksListView.getItems().filtered(item -> listPickIndicators.get(item).get());
Preconditions.checkState(!chosenChecks.isEmpty()); var batchService = new BatchService(batch);
batchService.setExecutor(executorService);
checks.filtered(c -> !c.isChosenForExecution()).forEach(c -> c.setState(Check.CheckState.SKIPPED)); batchService.start();
checkExecutor.executeBatch(chosenChecks); runningTask.set(batchService);
checksListView.getSelectionModel().select(chosenChecks.get(0)); showResultScreen.set(true);
checksListView.getSelectionModel().select(batch.get(0));
checksListView.refresh(); checksListView.refresh();
window.sizeToScene(); window.sizeToScene();
} }
@FXML @FXML
public synchronized void cancelRun() { public synchronized void cancelCheck() {
Preconditions.checkState(somethingsRunning.get()); Preconditions.checkState(runningTask.get() != null);
checkExecutor.cancel(); runningTask.get().cancel();
} }
@FXML @FXML
public void exportResults() { public void exportResults() {
try { try {
reportWriter.writeReport(chosenChecks); reportWriter.writeReport(tasks);
} catch (IOException e) { } catch (IOException e) {
LOG.error("Failed to write health check report.", e); LOG.error("Failed to write health check report.", e);
errorComponentBuilder.get().cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene(); errorComponentBuilder.get().cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
@@ -110,11 +127,19 @@ public class CheckListController implements FxController {
/* Getter/Setter */ /* Getter/Setter */
public boolean isRunning() { public boolean isRunning() {
return somethingsRunning.getValue(); return running.getValue();
} }
public BooleanBinding runningProperty() { public Binding<Boolean> runningProperty() {
return somethingsRunning; return running;
}
public boolean isFinished() {
return finished.getValue();
}
public Binding<Boolean> finishedProperty() {
return finished;
} }
public boolean isAnyCheckSelected() { public boolean isAnyCheckSelected() {
@@ -125,20 +150,21 @@ public class CheckListController implements FxController {
return anyCheckSelected; return anyCheckSelected;
} }
public boolean isMainRunStarted() { public boolean getShowResultScreen() {
return mainRunStarted.get(); return showResultScreen.get();
} }
public BooleanBinding mainRunStartedProperty() { public BooleanProperty showResultScreenProperty() {
return mainRunStarted; return showResultScreen;
} }
public int getChosenTaskCount() { public int getNumberOfPickedChecks() {
return chosenTaskCount.getValue(); return numberOfPickedChecks.get();
} }
public IntegerBinding chosenTaskCountProperty() { public IntegerProperty numberOfPickedChecksProperty() {
return chosenTaskCount; return numberOfPickedChecks;
} }
} }
@@ -1,83 +0,0 @@
package org.cryptomator.ui.health;
import com.tobiasdiez.easybind.EasyBind;
import com.tobiasdiez.easybind.Subscription;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.AutoAnimator;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableObjectValue;
import java.util.List;
/**
* A {@link FontAwesome5IconView} that automatically sets the glyph depending on
* the {@link Check#stateProperty() state} and {@link Check#highestResultSeverityProperty() severity} of a HealthCheck.
*/
public class CheckStateIconView extends FontAwesome5IconView {
private final ObjectProperty<Check> check = new SimpleObjectProperty<>();
private final ObservableObjectValue<Check.CheckState> state;
private final ObservableObjectValue<DiagnosticResult.Severity> severity;
private final List<Subscription> subscriptions;
private final AutoAnimator onRunningRotator;
public CheckStateIconView() {
this.state = EasyBind.wrapNullable(check).mapObservable(Check::stateProperty).asOrdinary();
this.severity = EasyBind.wrapNullable(check).mapObservable(Check::highestResultSeverityProperty).asOrdinary();
this.glyph.bind(Bindings.createObjectBinding(this::glyphForState, state, severity));
this.subscriptions = List.of( //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-muted", Bindings.equal(state, Check.CheckState.SKIPPED).or(Bindings.equal(state, Check.CheckState.CANCELLED))), //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN).or(Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL))), //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-red", Bindings.equal(state, Check.CheckState.ERROR)) //
);
var animation = Animations.createDiscrete360Rotation(this);
this.onRunningRotator = AutoAnimator.animate(animation) //
.onCondition(Bindings.equal(state, Check.CheckState.RUNNING)) //
.afterStop(() -> setRotate(0)) //
.build();
}
private FontAwesome5Icon glyphForState() {
if (state.getValue() == null) {
return null;
}
return switch (state.getValue()) {
case RUNNABLE -> null;
case SKIPPED -> FontAwesome5Icon.FAST_FORWARD;
case SCHEDULED -> FontAwesome5Icon.CLOCK;
case RUNNING -> FontAwesome5Icon.SPINNER;
case ERROR -> FontAwesome5Icon.TIMES;
case CANCELLED -> FontAwesome5Icon.BAN;
case SUCCEEDED -> glyphIconForSeverity();
};
}
private FontAwesome5Icon glyphIconForSeverity() {
if (severity.getValue() == null) {
return null;
}
return switch (severity.getValue()) {
case GOOD, INFO -> FontAwesome5Icon.CHECK;
case WARN, CRITICAL -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
};
}
public ObjectProperty<Check> checkProperty() {
return check;
}
public void setCheck(Check c) {
check.set(c);
}
public Check getCheck() {
return check.get();
}
}
@@ -4,7 +4,6 @@ import dagger.BindsInstance;
import dagger.Lazy; import dagger.Lazy;
import dagger.Subcomponent; import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
@@ -16,27 +15,20 @@ import javafx.stage.Stage;
@Subcomponent(modules = {HealthCheckModule.class}) @Subcomponent(modules = {HealthCheckModule.class})
public interface HealthCheckComponent { public interface HealthCheckComponent {
LoadUnverifiedConfigResult loadConfig();
@HealthCheckWindow @HealthCheckWindow
Stage window(); Stage window();
@FxmlScene(FxmlFile.HEALTH_START) @Named("windowToClose")
Lazy<Scene> startScene(); Stage windowToClose();
@FxmlScene(FxmlFile.HEALTH_START_FAIL) @FxmlScene(FxmlFile.HEALTH_START)
Lazy<Scene> failScene(); Lazy<Scene> scene();
default Stage showHealthCheckWindow() { default Stage showHealthCheckWindow() {
Stage stage = window(); Stage stage = window();
// TODO reevaluate config loading, as soon as we have the new generic error screen stage.setScene(scene().get());
var unverifiedConf = loadConfig();
if (unverifiedConf.config() != null) {
stage.setScene(startScene().get());
} else {
stage.setScene(failScene().get());
}
stage.show(); stage.show();
windowToClose().close();
return stage; return stage;
} }
@@ -47,10 +39,9 @@ public interface HealthCheckComponent {
Builder vault(@HealthCheckWindow Vault vault); Builder vault(@HealthCheckWindow Vault vault);
@BindsInstance @BindsInstance
Builder owner(@Named("healthCheckOwner") Stage owner); Builder windowToClose(@Named("windowToClose") Stage window);
HealthCheckComponent build(); HealthCheckComponent build();
} }
record LoadUnverifiedConfigResult(VaultConfig.UnverifiedVaultConfig config, Throwable error) {}
} }
@@ -17,7 +17,9 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.keyloading.KeyLoadingComponent; import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.mainwindow.MainWindow;
import javax.annotation.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.ObjectProperty;
@@ -26,9 +28,8 @@ import javafx.beans.value.ChangeListener;
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.io.IOException; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -37,18 +38,6 @@ import java.util.concurrent.atomic.AtomicReference;
@Module(subcomponents = {KeyLoadingComponent.class}) @Module(subcomponents = {KeyLoadingComponent.class})
abstract class HealthCheckModule { abstract class HealthCheckModule {
// TODO reevaluate config loading, as soon as we have the new generic error screen
@Provides
@HealthCheckScoped
static HealthCheckComponent.LoadUnverifiedConfigResult provideLoadConfigResult(@HealthCheckWindow Vault vault) {
try {
return new HealthCheckComponent.LoadUnverifiedConfigResult(vault.getUnverifiedVaultConfig(), null);
} catch (IOException e) {
return new HealthCheckComponent.LoadUnverifiedConfigResult(null, e);
}
}
@Provides @Provides
@HealthCheckScoped @HealthCheckScoped
static AtomicReference<Masterkey> provideMasterkeyRef() { static AtomicReference<Masterkey> provideMasterkeyRef() {
@@ -63,20 +52,27 @@ abstract class HealthCheckModule {
@Provides @Provides
@HealthCheckScoped @HealthCheckScoped
static ObjectProperty<Check> provideSelectedCheck() { static Collection<HealthCheck> provideAvailableHealthChecks() {
return new SimpleObjectProperty<>(); return HealthCheck.allChecks();
} }
@Provides @Provides
@HealthCheckScoped @HealthCheckScoped
static List<Check> provideAvailableChecks() { static ObjectProperty<HealthCheckTask> provideSelectedHealthCheckTask() {
return HealthCheck.allChecks().stream().map(Check::new).toList(); return new SimpleObjectProperty<>();
}
/* Only inject with Lazy-Wrapper!*/
@Provides
@HealthCheckScoped
static Collection<HealthCheckTask> provideAvailableHealthCheckTasks(Collection<HealthCheck> availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng, ResourceBundle resourceBundle) {
return availableHealthChecks.stream().map(check -> new HealthCheckTask(vault.getPath(), vaultConfigRef.get(), masterkeyRef.get(), csprng, check, resourceBundle)).toList();
} }
@Provides @Provides
@HealthCheckWindow @HealthCheckWindow
@HealthCheckScoped @HealthCheckScoped
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) { static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @HealthCheckWindow Vault vault, @HealthCheckWindow Stage window) {
return compBuilder.vault(vault).window(window).build().keyloadingStrategy(); return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
} }
@@ -87,26 +83,14 @@ abstract class HealthCheckModule {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
} }
@Provides
@Named("unlockWindow")
@HealthCheckScoped
static Stage provideUnlockWindow (@HealthCheckWindow Stage window, @HealthCheckWindow Vault vault, StageFactory factory, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(window);
stage.setTitle(String.format(resourceBundle.getString("unlock.title"), vault.getDisplayName()));
stage.setResizable(false);
return stage;
}
@Provides @Provides
@HealthCheckWindow @HealthCheckWindow
@HealthCheckScoped @HealthCheckScoped
static Stage provideStage(StageFactory factory, @Named("healthCheckOwner") Stage owner, @HealthCheckWindow Vault vault, ChangeListener<Boolean> showingListener, ResourceBundle resourceBundle) { static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle, ChangeListener<Boolean> showingListener) {
Stage stage = factory.create(); Stage stage = factory.create();
stage.initModality(Modality.WINDOW_MODAL); stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner); stage.initOwner(owner);
stage.setTitle(String.format(resourceBundle.getString("health.title"), vault.getDisplayName())); stage.setTitle(resourceBundle.getString("health.title"));
stage.setResizable(true); stage.setResizable(true);
stage.showingProperty().addListener(showingListener); // bind masterkey lifecycle to window stage.showingProperty().addListener(showingListener); // bind masterkey lifecycle to window
return stage; return stage;
@@ -129,13 +113,6 @@ abstract class HealthCheckModule {
return fxmlLoaders.createScene(FxmlFile.HEALTH_START); return fxmlLoaders.createScene(FxmlFile.HEALTH_START);
} }
@Provides
@FxmlScene(FxmlFile.HEALTH_START_FAIL)
@HealthCheckScoped
static Scene provideHealthStartFailScene(@HealthCheckWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HEALTH_START_FAIL);
}
@Provides @Provides
@FxmlScene(FxmlFile.HEALTH_CHECK_LIST) @FxmlScene(FxmlFile.HEALTH_CHECK_LIST)
@HealthCheckScoped @HealthCheckScoped
@@ -148,11 +125,6 @@ abstract class HealthCheckModule {
@FxControllerKey(StartController.class) @FxControllerKey(StartController.class)
abstract FxController bindStartController(StartController controller); abstract FxController bindStartController(StartController controller);
@Binds
@IntoMap
@FxControllerKey(StartFailController.class)
abstract FxController bindStartFailController(StartFailController controller);
@Binds @Binds
@IntoMap @IntoMap
@FxControllerKey(CheckListController.class) @FxControllerKey(CheckListController.class)
@@ -168,8 +140,4 @@ abstract class HealthCheckModule {
@FxControllerKey(ResultListCellController.class) @FxControllerKey(ResultListCellController.class)
abstract FxController bindResultListCellController(ResultListCellController controller); abstract FxController bindResultListCellController(ResultListCellController controller);
@Binds
@IntoMap
@FxControllerKey(CheckListCellController.class)
abstract FxController bindCheckListCellController(CheckListCellController controller);
} }
@@ -0,0 +1,109 @@
package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.cryptofs.health.api.HealthCheck;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.concurrent.CancellationException;
class HealthCheckTask extends Task<Void> {
private static final Logger LOG = LoggerFactory.getLogger(HealthCheckTask.class);
private final Path vaultPath;
private final VaultConfig vaultConfig;
private final Masterkey masterkey;
private final SecureRandom csprng;
private final HealthCheck check;
private final ObservableList<DiagnosticResult> results;
private final LongProperty durationInMillis;
public HealthCheckTask(Path vaultPath, VaultConfig vaultConfig, Masterkey masterkey, SecureRandom csprng, HealthCheck check, ResourceBundle resourceBundle) {
this.vaultPath = Objects.requireNonNull(vaultPath);
this.vaultConfig = Objects.requireNonNull(vaultConfig);
this.masterkey = Objects.requireNonNull(masterkey);
this.csprng = Objects.requireNonNull(csprng);
this.check = Objects.requireNonNull(check);
this.results = FXCollections.observableArrayList();
try {
updateTitle(resourceBundle.getString("health." + check.identifier()));
} catch (MissingResourceException e) {
LOG.warn("Missing proper name for health check {}, falling back to default.", check.identifier());
updateTitle(check.identifier());
}
this.durationInMillis = new SimpleLongProperty(-1);
}
@Override
protected Void call() {
Instant start = Instant.now();
try (var masterkeyClone = masterkey.clone(); //
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, result -> {
if (isCancelled()) {
throw new CancellationException();
}
// FIXME: slowdown for demonstration purposes only:
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
if (isCancelled()) {
return;
} else {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
Platform.runLater(() -> results.add(result));
});
}
Platform.runLater(() ->durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
return null;
}
@Override
protected void scheduled() {
LOG.info("starting {}", check.identifier());
}
@Override
protected void done() {
LOG.info("finished {}", check.identifier());
}
/* Getter */
public ObservableList<DiagnosticResult> results() {
return results;
}
public HealthCheck getCheck() {
return check;
}
public LongProperty durationInMillisProperty() {
return durationInMillis;
}
public long getDurationInMillis() {
return durationInMillis.get();
}
}
@@ -1,12 +1,15 @@
package org.cryptomator.ui.health; package org.cryptomator.ui.health;
import com.google.common.base.Throwables; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.cryptomator.common.Environment; import org.cryptomator.common.Environment;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.VaultConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.application.Application; import javafx.application.Application;
import javafx.concurrent.Worker;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
@@ -25,10 +28,11 @@ import java.util.stream.Collectors;
@HealthCheckScoped @HealthCheckScoped
public class ReportWriter { public class ReportWriter {
private static final Logger LOG = LoggerFactory.getLogger(ReportWriter.class);
private static final String REPORT_HEADER = """ private static final String REPORT_HEADER = """
******************************************* **************************************
* Cryptomator Vault Health Report * * Cryptomator Vault Health Report *
******************************************* **************************************
Analyzed vault: %s (Current name "%s") Analyzed vault: %s (Current name "%s")
Vault storage path: %s Vault storage path: %s
"""; """;
@@ -54,35 +58,38 @@ public class ReportWriter {
this.exportDestination = env.getLogDir().orElse(Path.of(System.getProperty("user.home"))).resolve("healthReport_" + vault.getDisplayName() + "_" + TIME_STAMP.format(Instant.now()) + ".log"); this.exportDestination = env.getLogDir().orElse(Path.of(System.getProperty("user.home"))).resolve("healthReport_" + vault.getDisplayName() + "_" + TIME_STAMP.format(Instant.now()) + ".log");
} }
protected void writeReport(Collection<Check> performedChecks) throws IOException { protected void writeReport(Collection<HealthCheckTask> tasks) throws IOException {
try (var out = Files.newOutputStream(exportDestination, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); // try (var out = Files.newOutputStream(exportDestination, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); //
var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) { var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
writer.write(REPORT_HEADER.formatted(vaultConfig.getId(), vault.getDisplayName(), vault.getPath())); writer.write(REPORT_HEADER.formatted(vaultConfig.getId(), vault.getDisplayName(), vault.getPath()));
for (var check : performedChecks) { for (var task : tasks) {
writer.write(REPORT_CHECK_HEADER.formatted(check.getHealthCheck().name())); if (task.getState() == Worker.State.READY) {
switch (check.getState()) { LOG.debug("Skipping not performed check {}.", task.getCheck().identifier());
continue;
}
writer.write(REPORT_CHECK_HEADER.formatted(task.getCheck().identifier()));
switch (task.getState()) {
case SUCCEEDED -> { case SUCCEEDED -> {
writer.write("STATUS: SUCCESS\nRESULTS:\n"); writer.write("STATUS: SUCCESS\nRESULTS:\n");
for (var result : check.getResults()) { for (var result : task.results()) {
writer.write(REPORT_CHECK_RESULT.formatted(result.diagnosis().getSeverity(), result.getDescription())); writer.write(REPORT_CHECK_RESULT.formatted(result.getSeverity(), result.toString()));
} }
} }
case CANCELLED -> writer.write("STATUS: CANCELED\n"); case CANCELLED -> writer.write("STATUS: CANCELED\n");
case ERROR -> { case FAILED -> {
writer.write("STATUS: FAILED\nREASON:\n"); writer.write("STATUS: FAILED\nREASON:\n" + task.getCheck().identifier());
writer.write(prepareFailureMsg(check)); writer.write(prepareFailureMsg(task));
} }
case RUNNABLE, RUNNING, SCHEDULED -> throw new IllegalStateException("Checks are still running."); case RUNNING, SCHEDULED -> throw new IllegalStateException("Checks are still running.");
case SKIPPED -> {} //noop
} }
} }
} }
reveal(); reveal();
} }
private String prepareFailureMsg(Check check) { private String prepareFailureMsg(HealthCheckTask task) {
if (check.getError() != null) { if (task.getException() != null) {
return Throwables.getStackTraceAsString(check.getError()) // return ExceptionUtils.getStackTrace(task.getException()) //
.lines() // .lines() //
.map(line -> "\t\t" + line + "\n") // .map(line -> "\t\t" + line + "\n") //
.collect(Collectors.joining()); .collect(Collectors.joining());
@@ -1,40 +0,0 @@
package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
enum FixState {
NOT_FIXABLE,
FIXABLE,
FIXING,
FIXED,
FIX_FAILED
}
public static Result create(DiagnosticResult diagnosis) {
FixState initialState = diagnosis.getSeverity() == DiagnosticResult.Severity.WARN ? FixState.FIXABLE : FixState.NOT_FIXABLE;
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
}
public Observable[] observables() {
return new Observable[]{fixState};
}
public String getDescription() {
return diagnosis.toString();
}
public FixState getState() {
return fixState.get();
}
public void setState(FixState state) {
this.fixState.set(state);
}
}
@@ -10,24 +10,20 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.application.Platform; import javafx.scene.control.Alert;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@HealthCheckScoped @HealthCheckScoped
class ResultFixApplier { class ResultFixApplier {
private static final Logger LOG = LoggerFactory.getLogger(ResultFixApplier.class);
private final Path vaultPath; private final Path vaultPath;
private final SecureRandom csprng; private final SecureRandom csprng;
private final Masterkey masterkey; private final Masterkey masterkey;
private final VaultConfig vaultConfig; private final VaultConfig vaultConfig;
private final ExecutorService sequentialExecutor;
@Inject @Inject
public ResultFixApplier(@HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng) { public ResultFixApplier(@HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng) {
@@ -35,32 +31,18 @@ class ResultFixApplier {
this.masterkey = masterkeyRef.get(); this.masterkey = masterkeyRef.get();
this.vaultConfig = vaultConfigRef.get(); this.vaultConfig = vaultConfigRef.get();
this.csprng = csprng; this.csprng = csprng;
this.sequentialExecutor = Executors.newSingleThreadExecutor();
} }
public CompletionStage<Void> fix(Result result) { public void fix(DiagnosticResult result) {
Preconditions.checkArgument(result.getState() == Result.FixState.FIXABLE); Preconditions.checkArgument(result.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
result.setState(Result.FixState.FIXING);
return CompletableFuture.runAsync(() -> fix(result.diagnosis()), sequentialExecutor)
.whenCompleteAsync((unused, throwable) -> {
var fixed = throwable == null ? Result.FixState.FIXED : Result.FixState.FIX_FAILED;
result.setState(fixed);
}, Platform::runLater);
}
public void fix(DiagnosticResult diagnosis) {
Preconditions.checkArgument(diagnosis.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
try (var masterkeyClone = masterkey.clone(); // try (var masterkeyClone = masterkey.clone(); //
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) { var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
diagnosis.fix(vaultPath, vaultConfig, masterkeyClone, cryptor); result.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
} catch (Exception e) { } catch (Exception e) {
throw new FixFailedException(e); LOG.error("Failed to apply fix", e);
} Alert alert = new Alert(Alert.AlertType.ERROR, e.getMessage());
} alert.showAndWait();
//TODO: real error/not supported handling
public static class FixFailedException extends CompletionException {
private FixFailedException(Throwable cause) {
super(cause);
} }
} }
} }
@@ -1,212 +1,92 @@
package org.cryptomator.ui.health; package org.cryptomator.ui.health;
import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.EasyBind;
import com.tobiasdiez.easybind.Subscription;
import org.cryptomator.cryptofs.health.api.DiagnosticResult; import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.AutoAnimator;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon; import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView; import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.binding.Binding; import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableObjectValue; import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Tooltip; import javafx.scene.control.Button;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
// unscoped because each cell needs its own controller // unscoped because each cell needs its own controller
public class ResultListCellController implements FxController { public class ResultListCellController implements FxController {
private static final FontAwesome5Icon INFO_ICON = FontAwesome5Icon.INFO_CIRCLE;
private static final FontAwesome5Icon GOOD_ICON = FontAwesome5Icon.CHECK;
private static final FontAwesome5Icon WARN_ICON = FontAwesome5Icon.EXCLAMATION_TRIANGLE;
private static final FontAwesome5Icon CRIT_ICON = FontAwesome5Icon.TIMES;
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
private final ObjectProperty<Result> result;
private final ObservableObjectValue<DiagnosticResult.Severity> severity;
private final Binding<String> description;
private final ResultFixApplier fixApplier; private final ResultFixApplier fixApplier;
private final ObservableObjectValue<Result.FixState> fixState; private final ObjectProperty<DiagnosticResult> result;
private final ObjectBinding<FontAwesome5Icon> severityGlyph; private final Binding<String> description;
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
private final BooleanBinding fixable;
private final BooleanBinding fixing;
private final BooleanBinding fixed;
private final BooleanBinding fixFailed;
private final BooleanBinding fixRunningOrDone;
private final List<Subscription> subscriptions;
private final Tooltip fixSuccess;
private final Tooltip fixFail;
private AutoAnimator fixRunningRotator; public FontAwesome5IconView iconView;
public Button actionButton;
/* FXML */
public FontAwesome5IconView severityView;
public FontAwesome5IconView fixView;
@Inject @Inject
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) { public ResultListCellController(ResultFixApplier fixApplier) {
this.result = new SimpleObjectProperty<>(null); this.result = new SimpleObjectProperty<>(null);
this.severity = EasyBind.wrapNullable(result).map(r -> r.diagnosis().getSeverity()).asOrdinary(); this.description = EasyBind.wrapNullable(result).map(DiagnosticResult::toString).orElse("");
this.description = EasyBind.wrapNullable(result).map(Result::getDescription).orElse("");
this.fixApplier = fixApplier; this.fixApplier = fixApplier;
this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState).asOrdinary(); result.addListener(this::updateCellContent);
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
this.fixGlyph = Bindings.createObjectBinding(this::getFixGlyph, fixState);
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
this.fixFailed = Bindings.createBooleanBinding(this::isFixFailed, fixState);
this.fixRunningOrDone = fixing.or(fixed).or(fixFailed);
this.subscriptions = new ArrayList<>();
this.fixSuccess = new Tooltip(resourceBundle.getString("health.fix.successTip"));
this.fixFail = new Tooltip(resourceBundle.getString("health.fix.failTip"));
fixSuccess.setShowDelay(Duration.millis(100));
fixFail.setShowDelay(Duration.millis(100));
} }
@FXML private void updateCellContent(ObservableValue<? extends DiagnosticResult> observable, DiagnosticResult oldVal, DiagnosticResult newVal) {
public void initialize() { iconView.getStyleClass().clear();
// see getGlyph() for relevant glyphs: actionButton.setVisible(false);
subscriptions.addAll(List.of(EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-muted", Bindings.equal(severity, DiagnosticResult.Severity.INFO)), // //TODO: see comment in case WARN
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), // actionButton.setManaged(false);
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN)), // switch (newVal.getSeverity()) {
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-red", Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL)) // case INFO -> {
)); iconView.setGlyph(FontAwesome5Icon.INFO_CIRCLE);
var animation = Animations.createDiscrete360Rotation(fixView); iconView.getStyleClass().add("glyph-icon-muted");
this.fixRunningRotator = AutoAnimator.animate(animation) // }
.onCondition(Bindings.equal(fixState, Result.FixState.FIXING)) // case GOOD -> {
.afterStop(() -> fixView.setRotate(0)) // iconView.setGlyph(FontAwesome5Icon.CHECK);
.build(); iconView.getStyleClass().add("glyph-icon-primary");
} }
case WARN -> {
@FXML iconView.setGlyph(FontAwesome5Icon.EXCLAMATION_TRIANGLE);
public void fix() { iconView.getStyleClass().add("glyph-icon-orange");
Result r = result.get(); //TODO: Neither is any fix implemented, nor it is ensured, that only fix is executed at a time with good ui indication
if (r != null) { // before both are not fix, do not show the button
fixApplier.fix(r).whenCompleteAsync(this::fixFinished, Platform::runLater); //actionButton.setVisible(true);
}
case CRITICAL -> {
iconView.setGlyph(FontAwesome5Icon.TIMES);
iconView.getStyleClass().add("glyph-icon-red");
}
} }
} }
private void fixFinished(Void unused, Throwable exception) { @FXML
if (exception != null) { public void runResultAction() {
LOG.error("Failed to apply fix", exception); final var realResult = result.get();
Tooltip.install(fixView, fixFail); if (realResult != null) {
} else { fixApplier.fix(realResult);
Tooltip.install(fixView, fixSuccess);
} }
} }
/* Getter & Setter */ /* Getter & Setter */
public Result getResult() {
public DiagnosticResult getResult() {
return result.get(); return result.get();
} }
public void setResult(Result result) { public void setResult(DiagnosticResult result) {
this.result.set(result); this.result.set(result);
} }
public ObjectProperty<Result> resultProperty() { public ObjectProperty<DiagnosticResult> resultProperty() {
return result; return result;
} }
public Binding<String> descriptionProperty() {
return description;
}
public String getDescription() { public String getDescription() {
return description.getValue(); return description.getValue();
} }
public ObjectBinding<FontAwesome5Icon> severityGlyphProperty() { public Binding<String> descriptionProperty() {
return severityGlyph; return description;
} }
public FontAwesome5Icon getSeverityGlyph() {
var r = result.get();
if (r == null) {
return null;
}
return switch (r.diagnosis().getSeverity()) {
case INFO -> INFO_ICON;
case GOOD -> GOOD_ICON;
case WARN -> WARN_ICON;
case CRITICAL -> CRIT_ICON;
};
}
public ObjectBinding<FontAwesome5Icon> fixGlyphProperty() {
return fixGlyph;
}
public FontAwesome5Icon getFixGlyph() {
if (fixState.getValue() == null) {
return null;
}
return switch (fixState.getValue()) {
case NOT_FIXABLE, FIXABLE -> null;
case FIXING -> FontAwesome5Icon.SPINNER;
case FIXED -> FontAwesome5Icon.CHECK;
case FIX_FAILED -> FontAwesome5Icon.TIMES;
};
}
public BooleanBinding fixableProperty() {
return fixable;
}
public boolean isFixable() {
return Result.FixState.FIXABLE.equals(fixState.get());
}
public BooleanBinding fixingProperty() {
return fixing;
}
public boolean isFixing() {
return Result.FixState.FIXING.equals(fixState.get());
}
public BooleanBinding fixedProperty() {
return fixed;
}
public boolean isFixed() {
return Result.FixState.FIXED.equals(fixState.get());
}
public BooleanBinding fixFailedProperty() {
return fixFailed;
}
public Boolean isFixFailed() {
return Result.FixState.FIX_FAILED.equals(fixState.get());
}
public BooleanBinding fixRunningOrDoneProperty() {
return fixRunningOrDone;
}
public boolean isFixRunningOrDone() {
return fixRunningOrDone.get();
}
} }
@@ -1,6 +1,7 @@
package org.cryptomator.ui.health; package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlLoaderFactory;
import javax.inject.Inject; import javax.inject.Inject;
@@ -14,7 +15,7 @@ import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
@HealthCheckScoped @HealthCheckScoped
public class ResultListCellFactory implements Callback<ListView<Result>, ListCell<Result>> { public class ResultListCellFactory implements Callback<ListView<DiagnosticResult>, ListCell<DiagnosticResult>> {
private final FxmlLoaderFactory fxmlLoaders; private final FxmlLoaderFactory fxmlLoaders;
@@ -24,7 +25,7 @@ public class ResultListCellFactory implements Callback<ListView<Result>, ListCel
} }
@Override @Override
public ListCell<Result> call(ListView<Result> param) { public ListCell<DiagnosticResult> call(ListView<DiagnosticResult> param) {
try { try {
FXMLLoader fxmlLoader = fxmlLoaders.load("/fxml/health_result_listcell.fxml"); FXMLLoader fxmlLoader = fxmlLoaders.load("/fxml/health_result_listcell.fxml");
return new ResultListCellFactory.Cell(fxmlLoader.getRoot(), fxmlLoader.getController()); return new ResultListCellFactory.Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
@@ -33,7 +34,7 @@ public class ResultListCellFactory implements Callback<ListView<Result>, ListCel
} }
} }
private static class Cell extends ListCell<Result> { private static class Cell extends ListCell<DiagnosticResult> {
private final Parent node; private final Parent node;
private final ResultListCellController controller; private final ResultListCellController controller;
@@ -44,7 +45,7 @@ public class ResultListCellFactory implements Callback<ListView<Result>, ListCel
} }
@Override @Override
protected void updateItem(Result item, boolean empty) { protected void updateItem(DiagnosticResult item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item == null || empty) { if (item == null || empty) {
setText(null); setText(null);
@@ -1,11 +1,12 @@
package org.cryptomator.ui.health; package org.cryptomator.ui.health;
import com.google.common.base.Preconditions;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException; import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.cryptofs.VaultKeyInvalidException; import org.cryptomator.cryptofs.VaultKeyInvalidException;
import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
@@ -16,15 +17,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.util.concurrent.CompletableFuture; import java.io.IOException;
import java.util.concurrent.CompletionException; import java.util.Optional;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -34,8 +32,7 @@ public class StartController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(StartController.class); private static final Logger LOG = LoggerFactory.getLogger(StartController.class);
private final Stage window; private final Stage window;
private final Stage unlockWindow; private final Optional<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
private final ObjectProperty<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
private final KeyLoadingStrategy keyLoadingStrategy; private final KeyLoadingStrategy keyLoadingStrategy;
private final ExecutorService executor; private final ExecutorService executor;
private final AtomicReference<Masterkey> masterkeyRef; private final AtomicReference<Masterkey> masterkeyRef;
@@ -43,18 +40,29 @@ public class StartController implements FxController {
private final Lazy<Scene> checkScene; private final Lazy<Scene> checkScene;
private final Lazy<ErrorComponent.Builder> errorComponent; private final Lazy<ErrorComponent.Builder> errorComponent;
/* FXML */
@Inject @Inject
public StartController(@HealthCheckWindow Stage window, HealthCheckComponent.LoadUnverifiedConfigResult configLoadResult, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, Lazy<ErrorComponent.Builder> errorComponent, @Named("unlockWindow") Stage unlockWindow) { public StartController(@HealthCheckWindow Vault vault, @HealthCheckWindow Stage window, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, Lazy<ErrorComponent.Builder> errorComponent) {
Preconditions.checkNotNull(configLoadResult.config());
this.window = window; this.window = window;
this.unlockWindow = unlockWindow;
this.unverifiedVaultConfig = new SimpleObjectProperty<>(configLoadResult.config());
this.keyLoadingStrategy = keyLoadingStrategy; this.keyLoadingStrategy = keyLoadingStrategy;
this.executor = executor; this.executor = executor;
this.masterkeyRef = masterkeyRef; this.masterkeyRef = masterkeyRef;
this.vaultConfigRef = vaultConfigRef; this.vaultConfigRef = vaultConfigRef;
this.checkScene = checkScene; this.checkScene = checkScene;
this.errorComponent = errorComponent; this.errorComponent = errorComponent;
//TODO: this is ugly
//idea: delay the loading of the vault config and show a spinner (something like "check/load config") and react to the result of the loading
//or: load vault config in a previous step to see if it is loadable.
VaultConfig.UnverifiedVaultConfig tmp;
try {
tmp = vault.getUnverifiedVaultConfig();
} catch (IOException e) {
e.printStackTrace();
tmp = null;
}
this.unverifiedVaultConfig = Optional.ofNullable(tmp);
} }
@FXML @FXML
@@ -66,45 +74,41 @@ public class StartController implements FxController {
@FXML @FXML
public void next() { public void next() {
LOG.trace("StartController.next()"); LOG.trace("StartController.next()");
CompletableFuture.runAsync(this::loadKey, executor).whenCompleteAsync(this::loadedKey, Platform::runLater); executor.submit(this::loadKey);
} }
private void loadKey() { private void loadKey() {
assert !Platform.isFxApplicationThread(); assert !Platform.isFxApplicationThread();
assert unverifiedVaultConfig.get() != null; assert unverifiedVaultConfig.isPresent();
try { try (var masterkey = keyLoadingStrategy.loadKey(unverifiedVaultConfig.orElseThrow().getKeyId())) {
keyLoadingStrategy.use(this::verifyVaultConfig); var unverifiedCfg = unverifiedVaultConfig.get();
} catch (VaultConfigLoadException | UnlockCancelledException e) {
throw new LoadingFailedException(e);
}
}
private void verifyVaultConfig(KeyLoadingStrategy keyLoadingStrategy) throws VaultConfigLoadException {
var unverifiedCfg = unverifiedVaultConfig.get();
try (var masterkey = keyLoadingStrategy.loadKey(unverifiedCfg.getKeyId())) {
var verifiedCfg = unverifiedCfg.verify(masterkey.getEncoded(), unverifiedCfg.allegedVaultVersion()); var verifiedCfg = unverifiedCfg.verify(masterkey.getEncoded(), unverifiedCfg.allegedVaultVersion());
vaultConfigRef.set(verifiedCfg); vaultConfigRef.set(verifiedCfg);
var old = masterkeyRef.getAndSet(masterkey.clone()); var old = masterkeyRef.getAndSet(masterkey.clone());
if (old != null) { if (old != null) {
old.destroy(); old.destroy();
} }
Platform.runLater(this::loadedKey);
} catch (MasterkeyLoadingFailedException e) {
if (keyLoadingStrategy.recoverFromException(e)) {
// retry
loadKey();
} else {
Platform.runLater(() -> loadingKeyFailed(e));
}
} catch (VaultKeyInvalidException e) {
Platform.runLater(() -> loadingKeyFailed(e));
} catch (VaultConfigLoadException e) {
Platform.runLater(() -> loadingKeyFailed(e));
} }
} }
private void loadedKey(Void unused, Throwable exception) { private void loadedKey() {
assert Platform.isFxApplicationThread(); LOG.debug("Loaded valid key");
if (exception instanceof LoadingFailedException) { window.setScene(checkScene.get());
loadingKeyFailed(exception.getCause());
} else if (exception != null) {
loadingKeyFailed(exception);
} else {
LOG.debug("Loaded valid key");
unlockWindow.close();
window.setScene(checkScene.get());
}
} }
private void loadingKeyFailed(Throwable e) { private void loadingKeyFailed(Exception e) {
if (e instanceof UnlockCancelledException) { if (e instanceof UnlockCancelledException) {
// ok // ok
} else if (e instanceof VaultKeyInvalidException) { } else if (e instanceof VaultKeyInvalidException) {
@@ -116,12 +120,8 @@ public class StartController implements FxController {
} }
} }
/* internal types */ public boolean isInvalidConfig() {
return unverifiedVaultConfig.isEmpty();
private static class LoadingFailedException extends CompletionException {
LoadingFailedException(Throwable cause) {
super(cause);
}
} }
} }
@@ -1,79 +0,0 @@
package org.cryptomator.ui.health;
import com.google.common.base.Preconditions;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.TitledPane;
import javafx.stage.Stage;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
// TODO reevaluate config loading, as soon as we have the new generic error screen
@HealthCheckScoped
public class StartFailController implements FxController {
private final Stage window;
private final ObjectProperty<Throwable> loadError;
private final ObjectProperty<FontAwesome5Icon> moreInfoIcon;
/* FXML */
public TitledPane moreInfoPane;
@Inject
public StartFailController(@HealthCheckWindow Stage window, HealthCheckComponent.LoadUnverifiedConfigResult configLoadResult) {
Preconditions.checkNotNull(configLoadResult.error());
this.window = window;
this.loadError = new SimpleObjectProperty<>(configLoadResult.error());
this.moreInfoIcon = new SimpleObjectProperty<>(FontAwesome5Icon.CARET_RIGHT);
}
public void initialize() {
moreInfoPane.expandedProperty().addListener(this::setMoreInfoIcon);
}
private void setMoreInfoIcon(ObservableValue<? extends Boolean> observable, boolean wasExpanded, boolean willExpand) {
moreInfoIcon.set(willExpand ? FontAwesome5Icon.CARET_DOWN : FontAwesome5Icon.CARET_RIGHT);
}
@FXML
public void close() {
window.close();
}
/* Getter & Setter */
public ObjectProperty<FontAwesome5Icon> moreInfoIconProperty() {
return moreInfoIcon;
}
public FontAwesome5Icon getMoreInfoIcon() {
return moreInfoIcon.getValue();
}
public String getStackTrace() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
loadError.get().printStackTrace(new PrintStream(baos));
return baos.toString(StandardCharsets.UTF_8);
}
public String getLocalizedErrorMessage() {
return loadError.get().getLocalizedMessage();
}
public boolean isParseException() {
return loadError.get() instanceof VaultConfigLoadException;
}
public boolean isIoException() {
return !isParseException();
}
}
@@ -3,8 +3,6 @@ package org.cryptomator.ui.keyloading;
import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI; import java.net.URI;
@@ -14,8 +12,6 @@ import java.net.URI;
@FunctionalInterface @FunctionalInterface
public interface KeyLoadingStrategy extends MasterkeyLoader { public interface KeyLoadingStrategy extends MasterkeyLoader {
Logger LOG = LoggerFactory.getLogger(KeyLoadingStrategy.class);
/** /**
* Loads a master key. This might be a long-running operation, as it may require user input or expensive computations. * Loads a master key. This might be a long-running operation, as it may require user input or expensive computations.
* <p> * <p>
@@ -64,36 +60,4 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
}; };
} }
/**
* Makes the given <code>user</code> apply this key loading strategy. If the user fails with a {@link MasterkeyLoadingFailedException},
* an attempt is made to {@link #recoverFromException(MasterkeyLoadingFailedException) recover} from it. Any other exception will be rethrown.
*
* @param user Some method using this strategy. May be invoked multiple times in case of recoverable {@link MasterkeyLoadingFailedException}s
* @param <E> Optional exception type thrown by <code>user</code>
* @throws MasterkeyLoadingFailedException If a non-recoverable exception is thrown by <code>user</code>
* @throws E Exception thrown by <code>user</code> and rethrown by this method
*/
default <E extends Exception> void use(KeyLoadingStrategyUser<E> user) throws MasterkeyLoadingFailedException, E {
boolean success = false;
try {
user.use(this);
} catch (MasterkeyLoadingFailedException e) {
if (recoverFromException(e)) {
LOG.info("Unlock attempt threw {}. Reattempting...", e.getClass().getSimpleName());
use(user);
} else {
throw e;
}
} finally {
cleanup(success);
}
}
@FunctionalInterface
interface KeyLoadingStrategyUser<E extends Exception> {
void use(KeyLoadingStrategy strategy) throws MasterkeyLoadingFailedException, E;
}
} }

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