Compare commits

..
Author SHA1 Message Date
JaniruTEC 3b4ff4d3a2 Merge branch 'develop' into feature/1690-invalid-config-handling 2021-07-06 16:48:07 +02:00
JaniruTEC 1edc6e1189 Removed newline
See: https://github.com/cryptomator/cryptomator/pull/1691#discussion_r650773723
2021-07-06 16:45:39 +02:00
JaniruTEC 714a0c6664 Removed unnecessary super interface
See: https://github.com/cryptomator/cryptomator/pull/1691#discussion_r650770011

Also fixes: https://github.com/cryptomator/cryptomator/pull/1691#discussion_r650769229
2021-06-25 00:17:17 +02:00
JaniruTEC 3762441230 Renamed InvalidSettingsException to InvalidSettingException [skip ci] 2021-06-11 22:52:05 +02:00
JaniruTEC d0d161023d Added InvalidSettingsException
Added unchecked InvalidSettingsException as indicator for problems with the user config
Added AbstractInvalidSetting as interface for identifiers/handlers for such problems
Added InvalidSetting as enum implementation of AbstractInvalidSetting
2021-06-11 18:54:23 +02:00
JaniruTEC 6cd0fc6807 Updated the contract of the MountPointChooser-interface 2021-06-11 18:30:01 +02:00
159 changed files with 2755 additions and 3652 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
on:
push:
env:
JAVA_VERSION: 16
defaults:
run:
shell: bash
[push]
jobs:
test:
name: Compile and Test
build:
name: Build and Test
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: ${{ env.JAVA_VERSION }}
- uses: actions/cache@v2
java-version: 16
- uses: actions/cache@v1
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 install jacoco:report -Pcoverage,dependency-check
- name: Upload code coverage report
@@ -33,4 +29,121 @@ jobs:
run: bash <(curl -Ls https://coverage.codacy.com/get.sh)
env:
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">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.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">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.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">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.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">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.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">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.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">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.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">
<option name="Make" enabled="true" />
</method>
+8
View File
@@ -28,6 +28,14 @@ Cryptomator is provided free of charge as an open-source project despite the hig
### 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/)
---
+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.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.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.win.version>1.0.0-beta2</cryptomator.integrations.win.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.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.webdav.version>1.2.5</cryptomator.webdav.version>
<cryptomator.webdav.version>1.2.4</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<javafx.version>16</javafx.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>
<guava.version>30.1.1-jre</guava.version>
<dagger.version>2.38.1</dagger.version>
<gson.version>2.8.7</gson.version>
<zxcvbn.version>1.5.2</zxcvbn.version>
<dagger.version>2.37</dagger.version>
<gson.version>2.8.6</gson.version>
<slf4j.version>1.7.31</slf4j.version>
<logback.version>1.2.3</logback.version>
@@ -146,7 +142,7 @@
<dependency>
<groupId>com.nulab-inc</groupId>
<artifactId>zxcvbn</artifactId>
<version>${zxcvbn.version}</version>
<version>1.3.0</version>
</dependency>
<!-- Google -->
@@ -154,29 +150,6 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<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>
<groupId>com.google.dagger</groupId>
@@ -217,24 +190,11 @@
<dependency>
<groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId>
<version>1.2</version>
<version>1.1</version>
<scope>test</scope>
</dependency>
</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>
<pluginManagement>
<plugins>
@@ -251,12 +211,17 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<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>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
@@ -271,12 +236,12 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<version>0.8.6</version>
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>6.2.2</version>
<version>6.0.3</version>
</plugin>
</plugins>
</pluginManagement>
@@ -293,10 +258,6 @@
<version>${dagger.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Adagger.fastInit=enabled</arg>
<arg>-Adagger.formatGeneratedSource=enabled</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
@@ -317,20 +278,38 @@
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-mods</id>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
<goal>copy-resources</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/mods</outputDirectory>
<excludeGroupIds>${nonModularGroupIds}</excludeGroupIds>
<outputDirectory>${project.build.directory}</outputDirectory>
<resources>
<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>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-libs</id>
<phase>prepare-package</phase>
@@ -340,7 +319,7 @@
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeGroupIds>${nonModularGroupIds}</includeGroupIds>
<excludeClassifiers>linux,mac,win</excludeClassifiers>
</configuration>
</execution>
</executions>
@@ -451,6 +430,48 @@
<version>${cryptomator.integrations.mac.version}</version>
</dependency>
</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>
@@ -471,10 +492,52 @@
<version>${cryptomator.integrations.linux.version}</version>
</dependency>
</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>
<id>win</id>
<id>windows</id>
<activation>
<os>
<family>windows</family>
@@ -490,6 +553,48 @@
<version>${cryptomator.integrations.win.version}</version>
</dependency>
</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>
</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("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
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.logDir: {}", System.getProperty("cryptomator.logDir"));
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
@@ -51,8 +51,8 @@ public class Environment {
return getPaths("cryptomator.settingsPath");
}
public Stream<Path> ipcSocketPath() {
return getPaths("cryptomator.ipcSocketPath");
public Stream<Path> getIpcPortPath() {
return getPaths("cryptomator.ipcPortPath");
}
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 {
}
@@ -34,11 +34,13 @@ import java.util.SortedSet;
* this volume, even if {@code #prepare(Volume, Path)} fails.</i>
*
* <p>If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed
* <i>without</i> first calling the {@code #prepare(Volume, Path)} method of the current MPC.
* <i>without</i> calling the {@code #prepare(Volume, Path)} method of the current MPC first.
* This is repeated until<br>
* <ul>
* <li><b>either</b> a mountpoint is returned by {@code #chooseMountPoint(Volume)}
* and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation</li>
* <li><b>or</b> {@code #chooseMountPoint(Volume)} throws an exception,
* ending the entire operation</li>
* <li><b>or</b> no MPC remains and an {@link InvalidMountPointException} is thrown.</li>
* </ul>
* If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire
@@ -72,12 +74,13 @@ public interface MountPointChooser {
* Developers should override this method to find or extract a mountpoint for
* the volume <b>without</b> preparing it. Preparation should be done by
* {@link #prepare(Volume, Path)} instead.
* Exceptions in this method should be handled gracefully and result in returning
* {@link Optional#empty()} instead of throwing an exception.
* The Mountpoint-Choosing-Operation will fail if an exception occurs.
* Consequently developers should try to restrict throwing exceptions to those cases where
* aborting the entire operation is sensible. Failure to choose a suitable path should
* be indicated by returning {@link Optional#empty()} instead.
*
* @param caller The Volume that is calling the method to choose a mountpoint
* @return the chosen path or {@link Optional#empty()} if an exception occurred
* or no mountpoint could be found.
* @return the chosen path or {@link Optional#empty()} if no mountpoint could be found.
* @see #isApplicable(Volume)
* @see #prepare(Volume, Path)
*/
@@ -0,0 +1,54 @@
package org.cryptomator.common.settings;
import java.net.URI;
import java.util.Objects;
import java.util.ResourceBundle;
public enum InvalidSetting {
;
private final static String DEFAULT_TRACKER = "https://github.com/cryptomator/cryptomator/issues/";
private final static ResourceBundle BUNDLE = ResourceBundle.getBundle("i18n.strings");
private final static String UNKNOWN_KEY_FORMAT = "<Unknown key: %s>";
private final URI issue;
private final String resourceKey;
InvalidSetting(URI issue, String resourceKey) {
this.issue = issue;
this.resourceKey = Objects.requireNonNull(resourceKey);
}
InvalidSetting(int issueId, String resourceKey) {
this(onDefaultTracker(issueId), Objects.requireNonNull(resourceKey));
}
/**
* Returns the corresponding issue URI of this setting.<br>
* The issue URI usually resolves to a page on the
* <a href="https://github.com/cryptomator/cryptomator/issues">Cryptomator Bugtracker.</a>
*
* @return the issue URI or {@code null} if none is provided.
*/
public URI getIssueURI() {
return this.issue;
}
/**
* Returns a (preferably localized) message, that helps the user understand the
* issue in their configuration and how to fix it.
*
* @return the non-null description of the issue.
*/
public String getMessage() {
if (!BUNDLE.containsKey(this.resourceKey)) {
return UNKNOWN_KEY_FORMAT.formatted(this.resourceKey);
}
return BUNDLE.getString(this.resourceKey);
}
private static URI onDefaultTracker(int id) {
return URI.create(DEFAULT_TRACKER + id);
}
}
@@ -0,0 +1,41 @@
package org.cryptomator.common.settings;
public class InvalidSettingException extends RuntimeException {
private final InvalidSetting reason;
private final String additionalMessage;
public InvalidSettingException(InvalidSetting reason) {
this(reason, null, null);
}
public InvalidSettingException(InvalidSetting reason, String additionalMessage) {
this(reason, additionalMessage, null);
}
public InvalidSettingException(InvalidSetting reason, Throwable cause) {
this(reason, null, cause);
}
public InvalidSettingException(InvalidSetting reason, String additionalMessage, Throwable cause) {
super(composeMessage(reason, additionalMessage), cause);
this.reason = reason;
this.additionalMessage = additionalMessage;
}
public InvalidSetting getReason() {
return this.reason;
}
public String getAdditionalMessage() {
return this.additionalMessage;
}
private static String composeMessage(InvalidSetting reason, String additionalMessage) {
if (additionalMessage == null) {
return reason.getMessage();
}
return reason.getMessage() + "Additionally: " + additionalMessage;
}
}
@@ -43,7 +43,7 @@ public class VaultSettings {
private static final Random RNG = new Random();
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 winDriveLetter = new SimpleStringProperty();
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.VaultConfig;
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
@@ -328,14 +327,6 @@ public class Vault {
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 {
Path configPath = getPath().resolve(org.cryptomator.common.Constants.VAULTCONFIG_FILENAME);
String token = Files.readString(configPath, StandardCharsets.US_ASCII);
@@ -7,10 +7,10 @@ package org.cryptomator.common.vaults;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.mountpoint.MountPointChooserModule;
import org.cryptomator.common.settings.VaultSettings;
import javax.annotation.Nullable;
import javax.inject.Named;
@PerVault
@@ -8,7 +8,6 @@ package org.cryptomator.common.vaults;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
@@ -16,6 +15,7 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Named;
import javafx.beans.binding.Bindings;
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;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.logging.LoggerConfiguration;
import org.cryptomator.ui.launcher.UiLauncher;
@@ -21,10 +16,8 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@Singleton
public class Cryptomator {
@@ -36,28 +29,23 @@ public class Cryptomator {
private final LoggerConfiguration logConfig;
private final DebugMode debugMode;
private final Environment env;
private final Lazy<IpcMessageHandler> ipcMessageHandler;
private final IpcFactory ipcFactory;
private final Optional<String> applicationVersion;
private final CountDownLatch shutdownLatch;
private final ShutdownHook shutdownHook;
private final Lazy<UiLauncher> uiLauncher;
private final UiLauncher uiLauncher;
@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.debugMode = debugMode;
this.env = env;
this.ipcMessageHandler = ipcMessageHandler;
this.ipcFactory = ipcFactory;
this.applicationVersion = applicationVersion;
this.shutdownLatch = shutdownLatch;
this.shutdownHook = shutdownHook;
this.uiLauncher = uiLauncher;
}
public static void main(String[] args) {
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
LOG.info("Exit {}", exitCode);
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.
* 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())) {
if (communicator.isClient()) {
communicator.sendHandleLaunchargs(List.of(args));
communicator.sendRevealRunningApp();
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
if (endpoint.isConnectedToRemote()) {
endpoint.getRemote().revealRunningApp();
LOG.info("Found running application instance. Shutting down...");
return 2;
} 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...");
return runGuiApplication();
}
} catch (Throwable e) {
LOG.error("Running application failed", e);
return 1;
} catch (IOException e) {
LOG.error("Failed to initiate inter-process communication.", e);
return runGuiApplication();
}
}
@@ -105,7 +88,7 @@ public class Cryptomator {
*/
private int runGuiApplication() {
try {
uiLauncher.get().launch();
uiLauncher.launch();
shutdownLatch.await();
LOG.info("UI shut down");
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.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors;
@@ -47,13 +46,13 @@ class FileOpenRequestHandler {
tryToEnqueueFileOpenRequest(launchEvent);
}
public void handleLaunchArgs(List<String> args) {
public void handleLaunchArgs(String[] args) {
handleLaunchArgs(FileSystems.getDefault(), args);
}
// visible for testing
void handleLaunchArgs(FileSystem fs, List<String> args) {
Collection<Path> pathsToOpen = args.stream().map(str -> {
void handleLaunchArgs(FileSystem fs, String[] args) {
Collection<Path> pathsToOpen = Arrays.stream(args).map(str -> {
try {
return fs.getPath(str);
} 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;
import org.cryptomator.ipc.IpcMessageListener;
import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -8,20 +7,20 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
@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 BlockingQueue<AppLaunchEvent> launchEventQueue;
@Inject
public IpcMessageHandler(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
this.fileOpenRequestHandler = fileOpenRequestHandler;
this.launchEventQueue = launchEventQueue;
}
@@ -32,8 +31,8 @@ class IpcMessageHandler implements IpcMessageListener {
}
@Override
public void handleLaunchArgs(List<String> args) {
LOG.debug("Received launch args: {}", args.stream().reduce((a, b) -> a + ", " + b).orElse(""));
public void handleLaunchArgs(String... args) {
LOG.debug("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
fileOpenRequestHandler.handleLaunchArgs(args);
}
@@ -76,7 +76,6 @@ public class CreateNewVaultPasswordController implements FxController {
private final BooleanProperty readyToCreateVault;
private final ObjectBinding<ContentDisplay> createVaultButtonState;
/* FXML */
public ToggleGroup recoveryKeyChoice;
public Toggle showRecoveryKey;
public Toggle skipRecoveryKey;
@@ -107,7 +106,7 @@ public class CreateNewVaultPasswordController implements FxController {
@FXML
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 -> {
newPasswordSceneController.passwordField.wipe();
newPasswordSceneController.reenterField.wipe();
@@ -62,7 +62,7 @@ public class ChangePasswordController implements FxController {
public void initialize() {
BooleanBinding checkboxNotConfirmed = finalConfirmationCheckbox.selectedProperty().not();
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 -> {
oldPasswordField.wipe();
newPasswordController.passwordField.wipe();
@@ -1,17 +1,11 @@
package org.cryptomator.ui.common;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.RotateTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.beans.value.WritableValue;
import javafx.scene.Node;
import javafx.stage.Window;
import javafx.util.Duration;
import java.util.stream.IntStream;
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.Subcomponent;
import org.cryptomator.common.Nullable;
import javax.annotation.Nullable;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -1,7 +1,6 @@
package org.cryptomator.ui.common;
import org.cryptomator.common.Nullable;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.fxml.FXML;
@@ -12,7 +12,6 @@ public enum FxmlFile {
ERROR("/fxml/error.fxml"), //
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
HEALTH_START("/fxml/health_start.fxml"), //
HEALTH_START_FAIL("/fxml/health_start_fail.fxml"), //
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), //
LOCK_FAILED("/fxml/lock_failed.fxml"), //
@@ -5,6 +5,7 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Map;
import java.util.ResourceBundle;
@@ -25,9 +26,11 @@ public class FxmlLoaderFactory {
/**
* @return A new FXMLLoader instance
*/
private FXMLLoader construct(String fxmlResourceName) {
var url = getClass().getResource(fxmlResourceName);
return new FXMLLoader(url, resourceBundle, null, this::constructController);
public FXMLLoader construct() {
FXMLLoader loader = new FXMLLoader();
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
*/
public FXMLLoader load(String fxmlResourceName) throws IOException {
FXMLLoader loader = construct(fxmlResourceName);
loader.load();
FXMLLoader loader = construct();
try (InputStream in = getClass().getResourceAsStream(fxmlResourceName)) {
loader.load(in);
}
return loader;
}
@@ -4,12 +4,12 @@ import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
@@ -20,7 +20,7 @@ public class NewPasswordController implements FxController {
private final ResourceBundle resourceBundle;
private final PasswordStrengthUtil strengthRater;
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1);
private final BooleanProperty goodPassword = new SimpleBooleanProperty();
private final ReadOnlyBooleanWrapper passwordsMatchAndSufficient = new ReadOnlyBooleanWrapper();
public NiceSecurePasswordField passwordField;
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.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());
goodPassword.bind(passwordsMatch.and(sufficientStrength));
passwordField.textProperty().addListener(this::passwordsDidChange);
reenterField.textProperty().addListener(this::passwordsDidChange);
}
private FontAwesome5IconView getIconViewForPasswordStrengthLabel() {
if (passwordField.getCharacters().length() == 0) {
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() {
return CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0;
}
private boolean sufficientStrength() {
return strengthRater.fulfillsMinimumRequirements(passwordField.getCharacters());
public ReadOnlyBooleanProperty passwordsMatchAndSufficientProperty() {
return passwordsMatchAndSufficient.getReadOnlyProperty();
}
/* Getter/Setter */
public ReadOnlyBooleanProperty goodPasswordProperty() {
return goodPassword;
}
public boolean isGoodPassword() {
return goodPassword.get();
}
public IntegerProperty passwordStrengthProperty() {
return passwordStrength;
}
@@ -8,8 +8,6 @@ public enum FontAwesome5Icon {
ARROW_UP("\uF062"), //
BAN("\uF05E"), //
BUG("\uF188"), //
CARET_DOWN("\uF0D7"), //
CARET_RIGHT("\uF0Da"), //
CHECK("\uF00C"), //
CLOCK("\uF017"), //
COG("\uF013"), //
@@ -22,7 +20,6 @@ public enum FontAwesome5Icon {
EXCLAMATION_TRIANGLE("\uF071"), //
EYE("\uF06E"), //
EYE_SLASH("\uF070"), //
FAST_FORWARD("\uF050"), //
FILE("\uF15B"), //
FILE_IMPORT("\uF56F"), //
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 Font FONT;
protected final ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
protected final DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
private final ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
private final DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
static {
try {
@@ -1,44 +0,0 @@
package org.cryptomator.ui.controls;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.AutoAnimator;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
* An animated progress spinner using the {@link FontAwesome5IconView} with the spinner glyph.
* <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.
*/
public class FontAwesome5Spinner extends FontAwesome5IconView {
protected final BooleanProperty spinning = new SimpleBooleanProperty(this, "spinning", true);
private AutoAnimator animator;
public FontAwesome5Spinner() {
setGlyph(FontAwesome5Icon.SPINNER);
var animation = Animations.createDiscrete360Rotation(this);
this.animator = AutoAnimator.animate(animation) //
.afterStop(() -> setRotate(0)) //
.onCondition(spinning.and(visibleProperty())) //
.build();
}
/* Getter/Setter */
public BooleanProperty spinningProperty() {
return spinning;
}
public boolean isSpinning() {
return spinning.get();
}
public void setSpinning(boolean spinning) {
this.spinning.set(spinning);
}
}
@@ -188,12 +188,9 @@ public class FxApplication extends Application {
}
private void applySystemTheme() {
if (appearanceProvider.isPresent()) {
systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme());
} else {
LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme...");
applyLightTheme();
}
appearanceProvider.ifPresent(appearanceProvider -> {
systemInterfaceThemeChanged(appearanceProvider.getSystemTheme());
});
}
private void applyLightTheme() {
@@ -0,0 +1,35 @@
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;
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,8 +12,11 @@ import javafx.beans.binding.Binding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import java.time.Duration;
import java.util.ResourceBundle;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -21,50 +24,50 @@ import java.util.stream.Stream;
public class CheckDetailController implements FxController {
private final EasyObservableList<Result> results;
private final ObjectProperty<Check> check;
private final OptionalBinding<Check.CheckState> checkState;
private final Binding<String> checkName;
private final Binding<Boolean> checkRunning;
private final Binding<Boolean> checkScheduled;
private final Binding<Boolean> checkFinished;
private final Binding<Boolean> checkSkipped;
private final Binding<Boolean> checkSucceeded;
private final Binding<Boolean> checkFailed;
private final Binding<Boolean> checkCancelled;
private final OptionalBinding<Worker.State> taskState;
private final Binding<String> taskName;
private final Binding<String> taskDuration;
private final Binding<Boolean> taskRunning;
private final Binding<Boolean> taskScheduled;
private final Binding<Boolean> taskFinished;
private final Binding<Boolean> taskNotStarted;
private final Binding<Boolean> taskSucceeded;
private final Binding<Boolean> taskFailed;
private final Binding<Boolean> taskCancelled;
private final Binding<Number> countOfWarnSeverity;
private final Binding<Number> countOfCritSeverity;
private final Binding<Boolean> warnOrCritsExist;
private final ResultListCellFactory resultListCellFactory;
private final ResourceBundle resourceBundle;
public ListView<Result> resultsListView;
private Subscription resultSubscription;
@Inject
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory) {
public CheckDetailController(ObjectProperty<HealthCheckTask> selectedTask, ResultListCellFactory resultListCellFactory, ResourceBundle resourceBundle) {
this.resultListCellFactory = resultListCellFactory;
this.resourceBundle = resourceBundle;
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
this.check = selectedTask;
this.checkState = EasyBind.wrapNullable(selectedTask).mapObservable(Check::stateProperty);
this.checkName = EasyBind.wrapNullable(selectedTask).map(Check::getName).orElse("");
this.checkRunning = checkState.map(Check.CheckState.RUNNING::equals).orElse(false);
this.checkScheduled = checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false);
this.checkSkipped = checkState.map(Check.CheckState.SKIPPED::equals).orElse(false);
this.checkSucceeded = checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false);
this.checkFailed = checkState.map(Check.CheckState.ERROR::equals).orElse(false);
this.checkCancelled = checkState.map(Check.CheckState.CANCELLED::equals).orElse(false);
this.checkFinished = EasyBind.combine(checkSucceeded, checkFailed, checkCancelled, (a, b, c) -> a || b || c);
this.taskState = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::stateProperty);
this.taskName = EasyBind.wrapNullable(selectedTask).map(HealthCheckTask::getTitle).orElse("");
this.taskDuration = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::durationInMillisProperty).orElse(-1L).map(this::millisToReadAbleDuration);
this.taskRunning = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::runningProperty).orElse(false); //TODO: DOES NOT WORK
this.taskScheduled = taskState.map(Worker.State.SCHEDULED::equals).orElse(false);
this.taskNotStarted = taskState.map(Worker.State.READY::equals).orElse(false);
this.taskSucceeded = taskState.map(Worker.State.SUCCEEDED::equals).orElse(false);
this.taskFailed = taskState.map(Worker.State.FAILED::equals).orElse(false);
this.taskCancelled = taskState.map(Worker.State.CANCELLED::equals).orElse(false);
this.taskFinished = EasyBind.combine(taskSucceeded, taskFailed, taskCancelled, (a, b, c) -> a || b || c);
this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN));
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);
}
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) {
resultSubscription.unsubscribe();
}
if (newValue != null) {
resultSubscription = EasyBind.bindContent(results, newValue.getResults());
resultSubscription = EasyBind.bindContent(results, newValue.results());
}
}
@@ -80,12 +83,20 @@ public class CheckDetailController implements FxController {
/* Getter/Setter */
public String getCheckName() {
return checkName.getValue();
public String getTaskName() {
return taskName.getValue();
}
public Binding<String> checkNameProperty() {
return checkName;
public Binding<String> taskNameProperty() {
return taskName;
}
public String getTaskDuration() {
return taskDuration.getValue();
}
public Binding<String> taskDurationProperty() {
return taskDuration;
}
public long getCountOfWarnSeverity() {
@@ -104,75 +115,77 @@ public class CheckDetailController implements FxController {
return countOfCritSeverity;
}
public boolean isCheckRunning() {
return checkRunning.getValue();
public boolean isTaskRunning() {
return taskRunning.getValue();
}
public Binding<Boolean> checkRunningProperty() {
return checkRunning;
public Binding<Boolean> taskRunningProperty() {
return taskRunning;
}
public boolean isCheckFinished() {
return checkFinished.getValue();
public boolean isTaskFinished() {
return taskFinished.getValue();
}
public Binding<Boolean> checkFinishedProperty() {
return checkFinished;
public Binding<Boolean> taskFinishedProperty() {
return taskFinished;
}
public boolean isCheckScheduled() {
return checkScheduled.getValue();
public boolean isTaskScheduled() {
return taskScheduled.getValue();
}
public Binding<Boolean> checkScheduledProperty() {
return checkScheduled;
public Binding<Boolean> taskScheduledProperty() {
return taskScheduled;
}
public boolean isCheckSkipped() {
return checkSkipped.getValue();
public boolean isTaskNotStarted() {
return taskNotStarted.getValue();
}
public Binding<Boolean> checkSkippedProperty() {
return checkSkipped;
public Binding<Boolean> taskNotStartedProperty() {
return taskNotStarted;
}
public boolean isCheckSucceeded() {
return checkSucceeded.getValue();
public boolean isTaskSucceeded() {
return taskSucceeded.getValue();
}
public Binding<Boolean> checkSucceededProperty() {
return checkSucceeded;
public Binding<Boolean> taskSucceededProperty() {
return taskSucceeded;
}
public boolean isCheckFailed() {
return checkFailed.getValue();
public boolean isTaskFailed() {
return taskFailed.getValue();
}
public Binding<Boolean> checkFailedProperty() {
return checkFailed;
public Binding<Boolean> taskFailedProperty() {
return taskFailed;
}
public boolean isCheckCancelled() {
return checkCancelled.getValue();
public boolean isTaskCancelled() {
return taskCancelled.getValue();
}
public Binding<Boolean> warnOrCritsExistProperty() {
return warnOrCritsExist;
public Binding<Boolean> taskCancelledProperty() {
return taskCancelled;
}
public boolean isWarnOrCritsExist() {
return warnOrCritsExist.getValue();
private String millisToReadAbleDuration(Number millis) {
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,71 @@
package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import javafx.beans.binding.Bindings;
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 java.util.function.Predicate;
class CheckListCell extends ListCell<HealthCheckTask> {
private final FontAwesome5IconView stateIcon = new FontAwesome5IconView();
private CheckBox checkBox = new CheckBox();
CheckListCell() {
setPadding(new Insets(6));
setAlignment(Pos.CENTER_LEFT);
setContentDisplay(ContentDisplay.LEFT);
getStyleClass().add("label");
}
@Override
protected void updateItem(HealthCheckTask item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getTitle());
graphicProperty().bind(Bindings.createObjectBinding(() -> graphicForState(item.getState()), item.stateProperty()));
stateIcon.glyphProperty().bind(Bindings.createObjectBinding(() -> glyphForState(item), item.stateProperty()));
checkBox.selectedProperty().bindBidirectional(item.chosenForExecutionProperty());
} else {
graphicProperty().unbind();
setGraphic(null);
setText(null);
checkBox.selectedProperty().unbind();
}
}
private Node graphicForState(Worker.State state) {
return switch (state) {
case READY -> checkBox;
case SCHEDULED, RUNNING, FAILED, CANCELLED, SUCCEEDED -> stateIcon;
};
}
private FontAwesome5Icon glyphForState(HealthCheckTask item) {
return switch (item.getState()) {
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 -> checkFoundProblems(item) ? FontAwesome5Icon.EXCLAMATION_TRIANGLE : FontAwesome5Icon.CHECK;
};
}
private boolean checkFoundProblems(HealthCheckTask item) {
Predicate<DiagnosticResult.Severity> isProblem = severity -> switch (severity) {
case WARN, CRITICAL -> true;
case INFO, GOOD -> false;
};
return item.results().stream().map(Result::diagnosis).map(DiagnosticResult::getSeverity).anyMatch(isProblem);
}
}
@@ -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,8 @@
package org.cryptomator.ui.health;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.tobiasdiez.easybind.EasyBind;
import dagger.Lazy;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
@@ -8,100 +10,110 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.IntegerBinding;
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;
import javafx.collections.transformation.FilteredList;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@HealthCheckScoped
public class CheckListController implements FxController {
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 ObservableList<Check> checks;
private final CheckExecutor checkExecutor;
private final FilteredList<Check> chosenChecks;
private final ObservableList<HealthCheckTask> tasks;
private final FilteredList<HealthCheckTask> chosenTasks;
private final ReportWriter reportWriter;
private final ObjectProperty<Check> selectedCheck;
private final BooleanBinding mainRunStarted; //TODO: rerunning not considered for now
private final BooleanBinding somethingsRunning;
private final ExecutorService executorService;
private final ObjectProperty<HealthCheckTask> selectedTask;
private final Lazy<ErrorComponent.Builder> errorComponentBuilder;
private final SimpleObjectProperty<Worker<?>> runningTask;
private final Binding<Boolean> running;
private final Binding<Boolean> finished;
private final IntegerBinding chosenTaskCount;
private final BooleanBinding anyCheckSelected;
private final CheckListCellFactory listCellFactory;
private final BooleanProperty showResultScreen;
/* FXML */
public ListView<Check> checksListView;
public ListView<HealthCheckTask> checksListView;
@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<List<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponentBuilder) {
this.window = window;
this.checks = FXCollections.observableList(checks, Check::observables);
this.checkExecutor = checkExecutor;
this.listCellFactory = listCellFactory;
this.chosenChecks = this.checks.filtered(Check::isChosenForExecution);
this.tasks = FXCollections.observableList(tasks.get(), HealthCheckTask::observables);
this.chosenTasks = this.tasks.filtered(HealthCheckTask::isChosenForExecution);
this.reportWriter = reportWriteTask;
this.selectedCheck = selectedCheck;
this.executorService = executorService;
this.selectedTask = selectedTask;
this.errorComponentBuilder = errorComponentBuilder;
this.chosenTaskCount = Bindings.size(this.chosenChecks);
this.mainRunStarted = Bindings.isEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.RUNNABLE));
this.somethingsRunning = Bindings.isNotEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.SCHEDULED || c.getState() == Check.CheckState.RUNNING));
this.anyCheckSelected = selectedCheck.isNotNull();
this.runningTask = new SimpleObjectProperty<>();
this.running = EasyBind.wrapNullable(runningTask).mapObservable(Worker::runningProperty).orElse(false);
this.finished = EasyBind.wrapNullable(runningTask).mapObservable(Worker::stateProperty).map(END_STATES::contains).orElse(false);
this.chosenTaskCount = Bindings.size(this.chosenTasks);
this.anyCheckSelected = selectedTask.isNotNull();
this.showResultScreen = new SimpleBooleanProperty(false);
}
@FXML
public void initialize() {
checksListView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
checksListView.setItems(checks);
checksListView.setCellFactory(listCellFactory);
selectedCheck.bind(checksListView.getSelectionModel().selectedItemProperty());
checksListView.setItems(tasks);
checksListView.setCellFactory(view -> new CheckListCell());
selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty());
}
@FXML
public void selectAllChecks() {
checks.forEach(t -> t.chosenForExecutionProperty().set(true));
}
@FXML
public void deselectAllChecks() {
checks.forEach(t -> t.chosenForExecutionProperty().set(false));
public void toggleSelectAll(ActionEvent event) {
if (event.getSource() instanceof CheckBox c) {
tasks.forEach(t -> t.chosenForExecutionProperty().set(c.isSelected()));
}
}
@FXML
public void runSelectedChecks() {
Preconditions.checkState(!mainRunStarted.get());
Preconditions.checkState(!somethingsRunning.get());
Preconditions.checkState(!chosenChecks.isEmpty());
Preconditions.checkState(runningTask.get() == null);
checks.filtered(c -> !c.isChosenForExecution()).forEach(c -> c.setState(Check.CheckState.SKIPPED));
checkExecutor.executeBatch(chosenChecks);
checksListView.getSelectionModel().select(chosenChecks.get(0));
// prevent further interaction by cancelling non-chosen tasks:
tasks.filtered(Predicates.not(chosenTasks::contains)).forEach(HealthCheckTask::cancel);
// run chosen tasks:
var batchService = new BatchService(chosenTasks);
batchService.setExecutor(executorService);
batchService.start();
runningTask.set(batchService);
showResultScreen.set(true);
checksListView.getSelectionModel().select(chosenTasks.get(0));
checksListView.refresh();
window.sizeToScene();
}
@FXML
public synchronized void cancelRun() {
Preconditions.checkState(somethingsRunning.get());
checkExecutor.cancel();
public synchronized void cancelCheck() {
Preconditions.checkState(runningTask.get() != null);
runningTask.get().cancel();
}
@FXML
public void exportResults() {
try {
reportWriter.writeReport(chosenChecks);
reportWriter.writeReport(tasks);
} catch (IOException e) {
LOG.error("Failed to write health check report.", e);
errorComponentBuilder.get().cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
@@ -110,11 +122,19 @@ public class CheckListController implements FxController {
/* Getter/Setter */
public boolean isRunning() {
return somethingsRunning.getValue();
return running.getValue();
}
public BooleanBinding runningProperty() {
return somethingsRunning;
public Binding<Boolean> runningProperty() {
return running;
}
public boolean isFinished() {
return finished.getValue();
}
public Binding<Boolean> finishedProperty() {
return finished;
}
public boolean isAnyCheckSelected() {
@@ -125,12 +145,12 @@ public class CheckListController implements FxController {
return anyCheckSelected;
}
public boolean isMainRunStarted() {
return mainRunStarted.get();
public boolean getShowResultScreen() {
return showResultScreen.get();
}
public BooleanBinding mainRunStartedProperty() {
return mainRunStarted;
public BooleanProperty showResultScreenProperty() {
return showResultScreen;
}
public int getChosenTaskCount() {
@@ -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();
}
}
@@ -0,0 +1,41 @@
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.Cryptor;
import org.cryptomator.cryptolib.api.Masterkey;
import java.nio.file.Path;
import java.util.function.Consumer;
/**
* FIXME: Remove in production release
*/
public class DummyHealthChecks {
public static class DummyCheck1 implements HealthCheck {
@Override
public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> consumer) {
// no-op
}
}
public static class DummyCheck2 implements HealthCheck {
@Override
public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> consumer) {
// no-op
}
}
public static class DummyCheck3 implements HealthCheck {
@Override
public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> consumer) {
// no-op
}
}
}
@@ -4,7 +4,6 @@ import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -16,27 +15,20 @@ import javafx.stage.Stage;
@Subcomponent(modules = {HealthCheckModule.class})
public interface HealthCheckComponent {
LoadUnverifiedConfigResult loadConfig();
@HealthCheckWindow
Stage window();
@FxmlScene(FxmlFile.HEALTH_START)
Lazy<Scene> startScene();
@Named("windowToClose")
Stage windowToClose();
@FxmlScene(FxmlFile.HEALTH_START_FAIL)
Lazy<Scene> failScene();
@FxmlScene(FxmlFile.HEALTH_START)
Lazy<Scene> scene();
default Stage showHealthCheckWindow() {
Stage stage = window();
// TODO reevaluate config loading, as soon as we have the new generic error screen
var unverifiedConf = loadConfig();
if (unverifiedConf.config() != null) {
stage.setScene(startScene().get());
} else {
stage.setScene(failScene().get());
}
stage.setScene(scene().get());
stage.show();
windowToClose().close();
return stage;
}
@@ -47,10 +39,9 @@ public interface HealthCheckComponent {
Builder vault(@HealthCheckWindow Vault vault);
@BindsInstance
Builder owner(@Named("healthCheckOwner") Stage owner);
Builder windowToClose(@Named("windowToClose") Stage window);
HealthCheckComponent build();
}
record LoadUnverifiedConfigResult(VaultConfig.UnverifiedVaultConfig config, Throwable error) {}
}
@@ -17,8 +17,8 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.mainwindow.MainWindow;
import javax.inject.Named;
import javax.inject.Provider;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
@@ -26,8 +26,8 @@ import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.ArrayList;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -37,18 +37,6 @@ import java.util.concurrent.atomic.AtomicReference;
@Module(subcomponents = {KeyLoadingComponent.class})
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
@HealthCheckScoped
static AtomicReference<Masterkey> provideMasterkeyRef() {
@@ -63,20 +51,27 @@ abstract class HealthCheckModule {
@Provides
@HealthCheckScoped
static ObjectProperty<Check> provideSelectedCheck() {
return new SimpleObjectProperty<>();
static Collection<HealthCheck> provideAvailableHealthChecks() {
return HealthCheck.allChecks();
}
@Provides
@HealthCheckScoped
static List<Check> provideAvailableChecks() {
return HealthCheck.allChecks().stream().map(Check::new).toList();
static ObjectProperty<HealthCheckTask> provideSelectedHealthCheckTask() {
return new SimpleObjectProperty<>();
}
/* Only inject with Lazy-Wrapper!*/
@Provides
@HealthCheckScoped
static List<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
@HealthCheckWindow
@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();
}
@@ -87,26 +82,14 @@ abstract class HealthCheckModule {
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
@HealthCheckWindow
@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.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
stage.setTitle(String.format(resourceBundle.getString("health.title"), vault.getDisplayName()));
stage.setTitle(resourceBundle.getString("health.title"));
stage.setResizable(true);
stage.showingProperty().addListener(showingListener); // bind masterkey lifecycle to window
return stage;
@@ -129,13 +112,6 @@ abstract class HealthCheckModule {
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
@FxmlScene(FxmlFile.HEALTH_CHECK_LIST)
@HealthCheckScoped
@@ -148,11 +124,6 @@ abstract class HealthCheckModule {
@FxControllerKey(StartController.class)
abstract FxController bindStartController(StartController controller);
@Binds
@IntoMap
@FxControllerKey(StartFailController.class)
abstract FxController bindStartFailController(StartFailController controller);
@Binds
@IntoMap
@FxControllerKey(CheckListController.class)
@@ -168,8 +139,4 @@ abstract class HealthCheckModule {
@FxControllerKey(ResultListCellController.class)
abstract FxController bindResultListCellController(ResultListCellController controller);
@Binds
@IntoMap
@FxControllerKey(CheckListCellController.class)
abstract FxController bindCheckListCellController(CheckListCellController controller);
}
@@ -0,0 +1,114 @@
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.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleBooleanProperty;
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<Result> results;
private final LongProperty durationInMillis;
private final BooleanProperty chosenForExecution;
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(Result::observables);
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);
this.chosenForExecution = new SimpleBooleanProperty();
}
@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, diagnosis -> {
if (isCancelled()) {
throw new CancellationException();
}
Platform.runLater(() -> results.add(Result.create(diagnosis)));
});
}
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 */
Observable[] observables() {
return new Observable[]{results, chosenForExecution};
}
public ObservableList<Result> results() {
return results;
}
public HealthCheck getCheck() {
return check;
}
public LongProperty durationInMillisProperty() {
return durationInMillis;
}
public long getDurationInMillis() {
return durationInMillis.get();
}
public BooleanProperty chosenForExecutionProperty() {
return chosenForExecution;
}
public boolean isChosenForExecution() {
return chosenForExecution.get();
}
}
@@ -1,12 +1,15 @@
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.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.concurrent.Worker;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -25,10 +28,11 @@ import java.util.stream.Collectors;
@HealthCheckScoped
public class ReportWriter {
private static final Logger LOG = LoggerFactory.getLogger(ReportWriter.class);
private static final String REPORT_HEADER = """
*******************************************
* Cryptomator Vault Health Report *
*******************************************
**************************************
* Cryptomator Vault Health Report *
**************************************
Analyzed vault: %s (Current name "%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");
}
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); //
var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
writer.write(REPORT_HEADER.formatted(vaultConfig.getId(), vault.getDisplayName(), vault.getPath()));
for (var check : performedChecks) {
writer.write(REPORT_CHECK_HEADER.formatted(check.getHealthCheck().name()));
switch (check.getState()) {
for (var task : tasks) {
if (task.getState() == Worker.State.READY) {
LOG.debug("Skipping not performed check {}.", task.getCheck().identifier());
continue;
}
writer.write(REPORT_CHECK_HEADER.formatted(task.getCheck().identifier()));
switch (task.getState()) {
case SUCCEEDED -> {
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()));
}
}
case CANCELLED -> writer.write("STATUS: CANCELED\n");
case ERROR -> {
writer.write("STATUS: FAILED\nREASON:\n");
writer.write(prepareFailureMsg(check));
case FAILED -> {
writer.write("STATUS: FAILED\nREASON:\n" + task.getCheck().identifier());
writer.write(prepareFailureMsg(task));
}
case RUNNABLE, RUNNING, SCHEDULED -> throw new IllegalStateException("Checks are still running.");
case SKIPPED -> {} //noop
case RUNNING, SCHEDULED -> throw new IllegalStateException("Checks are still running.");
}
}
}
reveal();
}
private String prepareFailureMsg(Check check) {
if (check.getError() != null) {
return Throwables.getStackTraceAsString(check.getError()) //
private String prepareFailureMsg(HealthCheckTask task) {
if (task.getException() != null) {
return ExceptionUtils.getStackTrace(task.getException()) //
.lines() //
.map(line -> "\t\t" + line + "\n") //
.collect(Collectors.joining());
@@ -17,7 +17,10 @@ record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
}
public static Result create(DiagnosticResult diagnosis) {
FixState initialState = diagnosis.getSeverity() == DiagnosticResult.Severity.WARN ? FixState.FIXABLE : FixState.NOT_FIXABLE;
FixState initialState = switch (diagnosis.getSeverity()) {
case WARN -> FixState.FIXABLE;
default -> FixState.NOT_FIXABLE;
};
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
}
@@ -1,10 +1,7 @@
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 com.tobiasdiez.easybind.optional.OptionalBinding;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
@@ -19,80 +16,45 @@ import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableObjectValue;
import javafx.fxml.FXML;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javafx.scene.control.Button;
// unscoped because each cell needs its own controller
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 ObservableObjectValue<Result.FixState> fixState;
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
private final OptionalBinding<Result.FixState> fixState;
private final ObjectBinding<FontAwesome5Icon> glyph;
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;
/* FXML */
public FontAwesome5IconView severityView;
public FontAwesome5IconView fixView;
public FontAwesome5IconView iconView;
public Button fixButton;
@Inject
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
public ResultListCellController(ResultFixApplier fixApplier) {
this.result = new SimpleObjectProperty<>(null);
this.severity = EasyBind.wrapNullable(result).map(r -> r.diagnosis().getSeverity()).asOrdinary();
this.description = EasyBind.wrapNullable(result).map(Result::getDescription).orElse("");
this.fixApplier = fixApplier;
this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState).asOrdinary();
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
this.fixGlyph = Bindings.createObjectBinding(this::getFixGlyph, fixState);
this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState);
this.glyph = Bindings.createObjectBinding(this::getGlyph, result);
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
public void initialize() {
// see getGlyph() for relevant glyphs:
subscriptions.addAll(List.of(EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-muted", Bindings.equal(severity, DiagnosticResult.Severity.INFO)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-red", Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL)) //
));
var animation = Animations.createDiscrete360Rotation(fixView);
this.fixRunningRotator = AutoAnimator.animate(animation) //
.onCondition(Bindings.equal(fixState, Result.FixState.FIXING)) //
.afterStop(() -> fixView.setRotate(0)) //
.build();
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-muted", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.INFO_CIRCLE));
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-primary", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.CHECK));
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-orange", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.EXCLAMATION_TRIANGLE));
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-red", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.TIMES));
}
@FXML
@@ -106,9 +68,7 @@ public class ResultListCellController implements FxController {
private void fixFinished(Void unused, Throwable exception) {
if (exception != null) {
LOG.error("Failed to apply fix", exception);
Tooltip.install(fixView, fixFail);
} else {
Tooltip.install(fixView, fixSuccess);
// TODO ...
}
}
@@ -127,45 +87,29 @@ public class ResultListCellController implements FxController {
return result;
}
public Binding<String> descriptionProperty() {
return description;
}
public String getDescription() {
return description.getValue();
}
public ObjectBinding<FontAwesome5Icon> severityGlyphProperty() {
return severityGlyph;
public ObjectBinding<FontAwesome5Icon> glyphProperty() {
return glyph;
}
public FontAwesome5Icon getSeverityGlyph() {
public FontAwesome5Icon getGlyph() {
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;
case INFO -> FontAwesome5Icon.INFO_CIRCLE;
case GOOD -> FontAwesome5Icon.CHECK;
case WARN -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
case CRITICAL -> FontAwesome5Icon.TIMES;
};
}
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 Binding<String> descriptionProperty() {
return description;
}
public BooleanBinding fixableProperty() {
@@ -173,7 +117,7 @@ public class ResultListCellController implements FxController {
}
public boolean isFixable() {
return Result.FixState.FIXABLE.equals(fixState.get());
return fixState.get().map(Result.FixState.FIXABLE::equals).orElse(false);
}
public BooleanBinding fixingProperty() {
@@ -181,7 +125,7 @@ public class ResultListCellController implements FxController {
}
public boolean isFixing() {
return Result.FixState.FIXING.equals(fixState.get());
return fixState.get().map(Result.FixState.FIXING::equals).orElse(false);
}
public BooleanBinding fixedProperty() {
@@ -189,24 +133,7 @@ public class ResultListCellController implements FxController {
}
public boolean isFixed() {
return Result.FixState.FIXED.equals(fixState.get());
return fixState.get().map(Result.FixState.FIXED::equals).orElse(false);
}
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,11 +1,12 @@
package org.cryptomator.ui.health;
import com.google.common.base.Preconditions;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.cryptofs.VaultKeyInvalidException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
@@ -16,13 +17,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
@@ -33,28 +36,40 @@ public class StartController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(StartController.class);
private final Vault vault;
private final Stage window;
private final Stage unlockWindow;
private final ObjectProperty<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
private final CompletableFuture<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
private final KeyLoadingStrategy keyLoadingStrategy;
private final ExecutorService executor;
private final AtomicReference<Masterkey> masterkeyRef;
private final AtomicReference<VaultConfig> vaultConfigRef;
private final Lazy<Scene> checkScene;
private final Lazy<ErrorComponent.Builder> errorComponent;
private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.LOADING);
private final BooleanBinding loading = state.isEqualTo(State.LOADING);
private final BooleanBinding failed = state.isEqualTo(State.FAILED);
private final BooleanBinding loaded = state.isEqualTo(State.LOADED);
public enum State {
LOADING,
FAILED,
LOADED
}
/* FXML */
@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) {
Preconditions.checkNotNull(configLoadResult.config());
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) {
this.vault = vault;
this.window = window;
this.unlockWindow = unlockWindow;
this.unverifiedVaultConfig = new SimpleObjectProperty<>(configLoadResult.config());
this.unverifiedVaultConfig = CompletableFuture.supplyAsync(this::loadConfig, executor);
this.keyLoadingStrategy = keyLoadingStrategy;
this.executor = executor;
this.masterkeyRef = masterkeyRef;
this.vaultConfigRef = vaultConfigRef;
this.checkScene = checkScene;
this.errorComponent = errorComponent;
this.unverifiedVaultConfig.whenCompleteAsync(this::loadedConfig, Platform::runLater);
}
@FXML
@@ -69,18 +84,29 @@ public class StartController implements FxController {
CompletableFuture.runAsync(this::loadKey, executor).whenCompleteAsync(this::loadedKey, Platform::runLater);
}
private void loadKey() {
private VaultConfig.UnverifiedVaultConfig loadConfig() {
assert !Platform.isFxApplicationThread();
assert unverifiedVaultConfig.get() != null;
try {
keyLoadingStrategy.use(this::verifyVaultConfig);
} catch (VaultConfigLoadException | UnlockCancelledException e) {
throw new LoadingFailedException(e);
return this.vault.getUnverifiedVaultConfig();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void verifyVaultConfig(KeyLoadingStrategy keyLoadingStrategy) throws VaultConfigLoadException {
var unverifiedCfg = unverifiedVaultConfig.get();
private void loadedConfig(VaultConfig.UnverifiedVaultConfig cfg, Throwable exception) {
assert Platform.isFxApplicationThread();
if (exception != null) {
state.set(State.FAILED);
} else {
assert cfg != null;
state.set(State.LOADED);
}
}
private void loadKey() {
assert !Platform.isFxApplicationThread();
assert unverifiedVaultConfig.isDone();
var unverifiedCfg = unverifiedVaultConfig.join();
try (var masterkey = keyLoadingStrategy.loadKey(unverifiedCfg.getKeyId())) {
var verifiedCfg = unverifiedCfg.verify(masterkey.getEncoded(), unverifiedCfg.allegedVaultVersion());
vaultConfigRef.set(verifiedCfg);
@@ -88,6 +114,15 @@ public class StartController implements FxController {
if (old != null) {
old.destroy();
}
} catch (MasterkeyLoadingFailedException e) {
if (keyLoadingStrategy.recoverFromException(e)) {
// retry
loadKey();
} else {
throw new LoadingFailedException(e);
}
} catch (VaultConfigLoadException e) {
throw new LoadingFailedException(e);
}
}
@@ -99,7 +134,6 @@ public class StartController implements FxController {
loadingKeyFailed(exception);
} else {
LOG.debug("Loaded valid key");
unlockWindow.close();
window.setScene(checkScene.get());
}
}
@@ -116,10 +150,35 @@ public class StartController implements FxController {
}
}
/* Getter */
public BooleanBinding loadingProperty() {
return loading;
}
public boolean isLoading() {
return loading.get();
}
public BooleanBinding failedProperty() {
return failed;
}
public boolean isFailed() {
return failed.get();
}
public BooleanBinding loadedProperty() {
return loaded;
}
public boolean isLoaded() {
return loaded.get();
}
/* internal types */
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();
}
}

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