mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 07:54:19 +00:00
Compare commits
253
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24839985e9 | ||
|
|
a7b2802f34 | ||
|
|
c85d0b4beb | ||
|
|
3dff3a8664 | ||
|
|
5b0bbf539b | ||
|
|
1ff7c40fd9 | ||
|
|
d0875ef49e | ||
|
|
e35ed9af72 | ||
|
|
ac43efe149 | ||
|
|
202a2ea79f | ||
|
|
c19a86a348 | ||
|
|
539dcaa85a | ||
|
|
fb91c1a461 | ||
|
|
bd75370dfd | ||
|
|
7a9a9cf4eb | ||
|
|
489e9fce90 | ||
|
|
d378d84832 | ||
|
|
69d73d0fb9 | ||
|
|
97c0cb4f5f | ||
|
|
b4faae7fa0 | ||
|
|
55d76b65cc | ||
|
|
fd6e1e6741 | ||
|
|
155558caa8 | ||
|
|
28217b2de3 | ||
|
|
b9e57ce895 | ||
|
|
95ea23ee6e | ||
|
|
c269184217 | ||
|
|
68d32957e4 | ||
|
|
a771f0a5b7 | ||
|
|
e56e6a8359 | ||
|
|
5f9b77241f | ||
|
|
fb54ee04a6 | ||
|
|
a106b04c7e | ||
|
|
285d48b70b | ||
|
|
0d8d7e4ee5 | ||
|
|
f232f9d958 | ||
|
|
88c3b0cc4f | ||
|
|
ef190db739 | ||
|
|
b378354bb9 | ||
|
|
822e863e27 | ||
|
|
40fbe4f59d | ||
|
|
bf779b5b41 | ||
|
|
ce5dbd14d8 | ||
|
|
48744018a2 | ||
|
|
bd9f79cb56 | ||
|
|
f4c4f72539 | ||
|
|
74718754ad | ||
|
|
eb595379d6 | ||
|
|
220782b84f | ||
|
|
1c104737c1 | ||
|
|
7c8a519919 | ||
|
|
ae1fa5851f | ||
|
|
86ccb1a58f | ||
|
|
c3fb58f7d0 | ||
|
|
b4c384acc9 | ||
|
|
8580e97791 | ||
|
|
5d348ee229 | ||
|
|
dda0ff545c | ||
|
|
708b5938ac | ||
|
|
a3cb8cd2f1 | ||
|
|
3587d53d15 | ||
|
|
2f5e2af8d9 | ||
|
|
8139cb8c38 | ||
|
|
09d5f71335 | ||
|
|
3a638735b7 | ||
|
|
f1f5f7e02f | ||
|
|
94fe8ca449 | ||
|
|
1251e1b873 | ||
|
|
50405c0bd5 | ||
|
|
774d24625c | ||
|
|
ba4902a5bc | ||
|
|
90408504e2 | ||
|
|
c8c44e0554 | ||
|
|
7afab6fe78 | ||
|
|
ad321dfe55 | ||
|
|
d6388d6205 | ||
|
|
b8326907bf | ||
|
|
5beeeae27f | ||
|
|
fb1f3c4757 | ||
|
|
5e32cbcdd0 | ||
|
|
08d741ecf5 | ||
|
|
23060a8497 | ||
|
|
47a32893f0 | ||
|
|
0f7ba4e00e | ||
|
|
0d75b999a3 | ||
|
|
b6d97528d2 | ||
|
|
94c53989b1 | ||
|
|
a72ea1b9f5 | ||
|
|
6395f17736 | ||
|
|
5c61944757 | ||
|
|
c641695a40 | ||
|
|
d93ef2e905 | ||
|
|
8b9377cf4e | ||
|
|
991b03e000 | ||
|
|
73d8c4098e | ||
|
|
6a52d15510 | ||
|
|
d07b1b96dd | ||
|
|
24d472ed8b | ||
|
|
6fe2d73c52 | ||
|
|
523e5d7904 | ||
|
|
304064d9ab | ||
|
|
0d49c60d4d | ||
|
|
0e0e00f116 | ||
|
|
77c0a3e265 | ||
|
|
1807888860 | ||
|
|
be7ac50928 | ||
|
|
9182c415eb | ||
|
|
1a6258eb17 | ||
|
|
9c0c6a91fb | ||
|
|
53436520c9 | ||
|
|
fd5aeaf90a | ||
|
|
f3fcfc913b | ||
|
|
0b0870e313 | ||
|
|
6704bb1f8c | ||
|
|
48d1938b99 | ||
|
|
6199277002 | ||
|
|
9fefe8e462 | ||
|
|
5fcd53be18 | ||
|
|
7147636428 | ||
|
|
690bd833e8 | ||
|
|
1e12590681 | ||
|
|
eea568ca4e | ||
|
|
8009205da7 | ||
|
|
c0f73a2802 | ||
|
|
8e902877a3 | ||
|
|
fd76c89393 | ||
|
|
975baf594c | ||
|
|
5a99c7bb61 | ||
|
|
e2898f0c8b | ||
|
|
cf2b236df5 | ||
|
|
819fa803f3 | ||
|
|
980e2b184b | ||
|
|
ebad677e84 | ||
|
|
4fda4a2b2e | ||
|
|
ffaf7ad360 | ||
|
|
76689705f5 | ||
|
|
1a358f06e8 | ||
|
|
db4d845a5b | ||
|
|
0d6d7b0dd6 | ||
|
|
9dfd8adcce | ||
|
|
433eecc6a8 | ||
|
|
36ed4554b2 | ||
|
|
c2dac30f2d | ||
|
|
63a89b5bcf | ||
|
|
12b53acd22 | ||
|
|
9105d1af16 | ||
|
|
de46b91420 | ||
|
|
67b40649bc | ||
|
|
c41af77108 | ||
|
|
ba66d4bfce | ||
|
|
c45a00377d | ||
|
|
f9d588320c | ||
|
|
c61087e697 | ||
|
|
0725a63b09 | ||
|
|
ebcd0adf78 | ||
|
|
0e7ec8eb37 | ||
|
|
0e9b115647 | ||
|
|
5ba137dda5 | ||
|
|
db928ba034 | ||
|
|
2cc01be236 | ||
|
|
ba2a4955d0 | ||
|
|
d1971e3c2a | ||
|
|
5665984a9b | ||
|
|
1f2225991a | ||
|
|
1f0fe161fd | ||
|
|
4ff8bbaa62 | ||
|
|
e188169916 | ||
|
|
1b9480293a | ||
|
|
d8b71db8e5 | ||
|
|
4bd99d2b66 | ||
|
|
61027852e1 | ||
|
|
512500aaef | ||
|
|
f9d28f20b3 | ||
|
|
7f79c554f7 | ||
|
|
3cbe4aa7eb | ||
|
|
06922717c4 | ||
|
|
32399c4a09 | ||
|
|
00a3692dcf | ||
|
|
83b198d491 | ||
|
|
8086ec1c10 | ||
|
|
b2b49f4d2f | ||
|
|
d6e14dab9a | ||
|
|
497ddcfc9c | ||
|
|
169039903d | ||
|
|
3641b34c2d | ||
|
|
42400d1f0c | ||
|
|
8f7f8ffb11 | ||
|
|
3016d35c9b | ||
|
|
f21b8a0f7a | ||
|
|
0c1bbc5bfd | ||
|
|
d932091021 | ||
|
|
216abf224e | ||
|
|
d5523f07bd | ||
|
|
e097bea1f5 | ||
|
|
920866b1a1 | ||
|
|
c41ffe2568 | ||
|
|
792fe1eafe | ||
|
|
348bc2b2ec | ||
|
|
7bac78bc5d | ||
|
|
65e1993b43 | ||
|
|
b98eb8c656 | ||
|
|
33ebc17619 | ||
|
|
ba7029968d | ||
|
|
a3fc68ab6f | ||
|
|
abf944a130 | ||
|
|
99bc4dbdc5 | ||
|
|
019292f1db | ||
|
|
2f9818aade | ||
|
|
9cec45dc1f | ||
|
|
5f55530b4a | ||
|
|
96de2556a9 | ||
|
|
db63a82d76 | ||
|
|
11aea06ecf | ||
|
|
932cfd2200 | ||
|
|
edaa2b7391 | ||
|
|
9984b2af9b | ||
|
|
34986f5919 | ||
|
|
23d9521724 | ||
|
|
4dc1d59305 | ||
|
|
74e3c28441 | ||
|
|
6e526ba796 | ||
|
|
b978044383 | ||
|
|
6325c6f450 | ||
|
|
dd79b46d72 | ||
|
|
d896fe21b5 | ||
|
|
6983d9d72d | ||
|
|
faf39c2507 | ||
|
|
083d0cfbc4 | ||
|
|
9ca8072ab6 | ||
|
|
3223ea2e5d | ||
|
|
83af5e796f | ||
|
|
9d9f118a32 | ||
|
|
1b0eb34734 | ||
|
|
79952418f4 | ||
|
|
65e26971ff | ||
|
|
13debaafbe | ||
|
|
5729e2885d | ||
|
|
043184cf53 | ||
|
|
38062f9a8d | ||
|
|
14d0d95f23 | ||
|
|
759c21e5cf | ||
|
|
d8ed9f854a | ||
|
|
1414ab4681 | ||
|
|
1486826883 | ||
|
|
dda7255d8e | ||
|
|
6caf132cfb | ||
|
|
dc310c009f | ||
|
|
445be3b668 | ||
|
|
f852ae0392 | ||
|
|
e5e606f3ae | ||
|
|
e3c609351f | ||
|
|
01412070ad | ||
|
|
4944a634fb |
@@ -37,14 +37,19 @@ body:
|
|||||||
id: volume-type
|
id: volume-type
|
||||||
attributes:
|
attributes:
|
||||||
label: Volume Type
|
label: Volume Type
|
||||||
description: What is selected under Settings → Virtual Drive?
|
description: What volume type is selected under Settings → Virtual Drive?
|
||||||
multiple: true
|
value: |
|
||||||
options:
|
- WinFsp
|
||||||
- FUSE
|
- WinFsp (Local Drive)
|
||||||
- Dokany
|
- FUSE-T
|
||||||
- WebDAV
|
- macFUSE
|
||||||
|
- WebDAV (Windows Explorer)
|
||||||
|
- WebDAV (AppleScript)
|
||||||
|
- WebDAV (gio)
|
||||||
|
- WebDAV (HTTP Address)
|
||||||
|
- Dokany (1.5)
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: reproduction-steps
|
id: reproduction-steps
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -13,46 +13,40 @@ env:
|
|||||||
JAVA_VERSION: 19
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
get-version:
|
||||||
|
uses: ./.github/workflows/get-version.yml
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.version }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-version]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
java-package: 'jdk+fx'
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- id: versions
|
- name: Ensure major jfx version in pom equals in jdk
|
||||||
name: Apply version information
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
exit 1
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
}
|
||||||
else
|
- name: Set version
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
fi
|
|
||||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
|
||||||
REVCOUNT=`git rev-list --count HEAD`
|
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
|
||||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
|
||||||
echo "::set-output name=revNum::${REVCOUNT}"
|
|
||||||
- name: Validate Version
|
|
||||||
uses: skymatic/semver-validation-action@v1
|
|
||||||
with:
|
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
cp dist/linux/launcher.sh target
|
|
||||||
cp target/cryptomator-*.jar target/mods
|
cp target/cryptomator-*.jar target/mods
|
||||||
- name: Run jlink
|
- name: Run jlink
|
||||||
run: >
|
run: >
|
||||||
@@ -60,7 +54,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -69,8 +63,8 @@ jobs:
|
|||||||
- name: Prepare additional launcher
|
- name: Prepare additional launcher
|
||||||
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
||||||
env:
|
env:
|
||||||
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
|
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
|
||||||
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
|
REVISION_NUM: ${{ needs.get-version.outputs.revNum }}
|
||||||
- name: Run jpackage
|
- name: Run jpackage
|
||||||
run: >
|
run: >
|
||||||
${JAVA_HOME}/bin/jpackage
|
${JAVA_HOME}/bin/jpackage
|
||||||
@@ -83,11 +77,13 @@ jobs:
|
|||||||
--dest appdir
|
--dest appdir
|
||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
--copyright "(C) 2016 - 2023 Skymatic GmbH"
|
||||||
--app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
|
--java-options "--enable-preview"
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64"
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||||
--java-options "-Dfile.encoding=\"utf-8\""
|
--java-options "-Dfile.encoding=\"utf-8\""
|
||||||
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
|
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
|
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
|
||||||
@@ -96,7 +92,7 @@ jobs:
|
|||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
|
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
|
||||||
--java-options "-Dcryptomator.showTrayIcon=false"
|
--java-options "-Dcryptomator.showTrayIcon=false"
|
||||||
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\""
|
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
|
||||||
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
|
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
|
||||||
--resource-dir dist/linux/resources
|
--resource-dir dist/linux/resources
|
||||||
- name: Patch Cryptomator.AppDir
|
- name: Patch Cryptomator.AppDir
|
||||||
@@ -114,12 +110,6 @@ jobs:
|
|||||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon
|
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 usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
|
||||||
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
||||||
- 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
|
- name: Download AppImageKit
|
||||||
run: |
|
run: |
|
||||||
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage
|
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage
|
||||||
@@ -134,7 +124,7 @@ jobs:
|
|||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
run: >
|
run: >
|
||||||
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ steps.versions.outputs.semVerStr }}-x86_64.AppImage
|
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-x86_64.AppImage
|
||||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
||||||
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
|
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
|
||||||
- name: Create detached GPG signatures
|
- name: Create detached GPG signatures
|
||||||
|
|||||||
@@ -42,15 +42,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
- name: Sign source tarball with key 615D449FE6E6A235
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
run: |
|
|
||||||
git archive --prefix="cryptomator-${{ github.ref_name }}/" -o "cryptomator-${{ github.ref_name }}.tar.gz" ${{ github.ref }}
|
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
|
||||||
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
|
|
||||||
env:
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
|
||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
|
||||||
- name: Draft a release
|
- name: Draft a release
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@@ -59,9 +50,6 @@ jobs:
|
|||||||
discussion_category_name: releases
|
discussion_category_name: releases
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
|
||||||
cryptomator-*.tar.gz.asc
|
|
||||||
fail_on_unmatched_files: true
|
|
||||||
body: |-
|
body: |-
|
||||||
:construction: Work in Progress
|
:construction: Work in Progress
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
name: Build Debian Package
|
name: Build Debian Package
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
|
ref:
|
||||||
|
description: 'GitHub Ref (e.g. refs/tags/1.6.16)'
|
||||||
|
required: true
|
||||||
|
semver:
|
||||||
|
description: 'SemVer String (e.g. 1.7.0-beta1)'
|
||||||
|
required: true
|
||||||
|
ppaver:
|
||||||
|
description: 'Base PPA Version String (e.g. 1.6.16+1.7.0~beta1) without -0ppa1'
|
||||||
|
required: true
|
||||||
dput:
|
dput:
|
||||||
description: 'Upload to PPA'
|
description: 'Upload to PPA'
|
||||||
required: true
|
required: true
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
version:
|
|
||||||
description: 'Version'
|
|
||||||
required: false
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 19
|
JAVA_VERSION: 19
|
||||||
|
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/19/openjfx-19_linux-x64_bin-jmods.zip'
|
||||||
|
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/19/openjfx-19_linux-aarch64_bin-jmods.zip'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -24,50 +30,69 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
- id: versions
|
||||||
|
name: Get version information
|
||||||
|
run: |
|
||||||
|
SEM_VER_STR="${{ inputs.semver }}"
|
||||||
|
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||||
|
REVCOUNT=`git rev-list --count HEAD`
|
||||||
|
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
|
||||||
|
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
|
||||||
|
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
|
||||||
- name: Install build tools
|
- name: Install build tools
|
||||||
run: |
|
run: |
|
||||||
sudo add-apt-repository ppa:coffeelibs/openjdk
|
sudo add-apt-repository ppa:coffeelibs/openjdk
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19
|
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19 libgtk2.0-0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- id: versions
|
|
||||||
name: Apply version information
|
|
||||||
run: |
|
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
|
||||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
|
||||||
else
|
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
|
||||||
fi
|
|
||||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
|
||||||
REVCOUNT=`git rev-list --count HEAD`
|
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
|
||||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
|
||||||
echo "::set-output name=revNum::${REVCOUNT}"
|
|
||||||
echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}"
|
|
||||||
- name: Validate Version
|
|
||||||
uses: skymatic/semver-validation-action@v1
|
|
||||||
with:
|
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
||||||
- name: Create orig.tar.gz with common/ libs/ mods/
|
- name: Download OpenJFX jmods
|
||||||
|
id: download-jmods
|
||||||
|
run: |
|
||||||
|
curl -L ${{ env.OPENJFX_JMODS_AMD64 }} -o openjfx-amd64.zip
|
||||||
|
mkdir -p jmods/amd64
|
||||||
|
unzip -j openjfx-amd64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/amd64
|
||||||
|
curl -L ${{ env.OPENJFX_JMODS_AARCH64 }} -o openjfx-aarch64.zip
|
||||||
|
mkdir -p jmods/aarch64
|
||||||
|
unzip -j openjfx-aarch64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/aarch64
|
||||||
|
- name: Ensure major jfx version in pom and in jmods is the same
|
||||||
|
run: |
|
||||||
|
JMOD_VERSION_AMD64=$(jmod describe jmods/amd64/javafx.base.jmod | head -1)
|
||||||
|
JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64#*@}
|
||||||
|
JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64%%.*}
|
||||||
|
JMOD_VERSION_AARCH64=$(jmod describe jmods/aarch64/javafx.base.jmod | head -1)
|
||||||
|
JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64#*@}
|
||||||
|
JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64%%.*}
|
||||||
|
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
|
||||||
|
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
||||||
|
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
||||||
|
|
||||||
|
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then
|
||||||
|
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION_AMD64})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AARCH64 ]; then
|
||||||
|
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != aarch64 jmod version (${JMOD_VERSION_AARCH64})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Create orig.tar.gz with common/ libs/ mods/ jmods/
|
||||||
run: |
|
run: |
|
||||||
mkdir pkgdir
|
mkdir pkgdir
|
||||||
cp -r target/libs pkgdir
|
cp -r target/libs pkgdir
|
||||||
cp -r target/mods pkgdir
|
cp -r target/mods pkgdir
|
||||||
|
cp -r jmods pkgdir
|
||||||
cp -r dist/linux/common/ pkgdir
|
cp -r dist/linux/common/ pkgdir
|
||||||
cp target/cryptomator-*.jar pkgdir/mods
|
cp target/cryptomator-*.jar pkgdir/mods
|
||||||
tar -cJf cryptomator_${{ steps.versions.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir .
|
tar -cJf cryptomator_${{ inputs.ppaver }}.orig.tar.xz -C pkgdir .
|
||||||
- name: Patch and rename pkgdir
|
- name: Patch and rename pkgdir
|
||||||
run: |
|
run: |
|
||||||
cp -r dist/linux/debian/ pkgdir
|
cp -r dist/linux/debian/ pkgdir
|
||||||
@@ -75,12 +100,12 @@ jobs:
|
|||||||
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
|
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
|
||||||
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
|
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
|
||||||
find . -name "*.jar" >> pkgdir/debian/source/include-binaries
|
find . -name "*.jar" >> pkgdir/debian/source/include-binaries
|
||||||
mv pkgdir cryptomator_${{ steps.versions.outputs.ppaVerStr }}
|
mv pkgdir cryptomator_${{ inputs.ppaver }}
|
||||||
env:
|
env:
|
||||||
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
|
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
|
||||||
VERSION_NUM: ${{ steps.versions.outputs.semVerNum }}
|
VERSION_NUM: ${{ steps.versions.outputs.semVerNum }}
|
||||||
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
|
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
|
||||||
PPA_VERSION: ${{ steps.versions.outputs.ppaVerStr }}-0ppa1
|
PPA_VERSION: ${{ inputs.ppaver }}-0ppa1
|
||||||
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
|
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
@@ -95,7 +120,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback
|
DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback
|
||||||
DEBSIGN_KEYID: 615D449FE6E6A235
|
DEBSIGN_KEYID: 615D449FE6E6A235
|
||||||
working-directory: cryptomator_${{ steps.versions.outputs.ppaVerStr }}
|
working-directory: cryptomator_${{ inputs.ppaver }}
|
||||||
- name: Create detached GPG signatures
|
- name: Create detached GPG signatures
|
||||||
run: |
|
run: |
|
||||||
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
|
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
|
||||||
@@ -112,14 +137,19 @@ jobs:
|
|||||||
cryptomator_*_amd64.deb
|
cryptomator_*_amd64.deb
|
||||||
cryptomator_*.asc
|
cryptomator_*.asc
|
||||||
- name: Publish on PPA
|
- name: Publish on PPA
|
||||||
if: startsWith(github.ref, 'refs/tags/') || inputs.dput
|
if: inputs.dput
|
||||||
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
|
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
|
||||||
|
|
||||||
|
# If ref is a tag, also upload to GitHub Releases:
|
||||||
|
- name: Determine tag name
|
||||||
|
if: startsWith(inputs.ref, 'refs/tags/')
|
||||||
|
run: |
|
||||||
|
REF=${{ inputs.ref }}
|
||||||
|
echo "TAG_NAME=${REF##*/}" >> $GITHUB_ENV
|
||||||
- name: Publish Debian package on GitHub Releases
|
- name: Publish Debian package on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(inputs.ref, 'refs/tags/')
|
||||||
uses: softprops/action-gh-release@v1
|
env:
|
||||||
with:
|
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
fail_on_unmatched_files: true
|
run: |
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
artifacts=$(ls | grep cryptomator*.deb)
|
||||||
files: |
|
gh release upload ${{ env.TAG_NAME }} $artifacts
|
||||||
cryptomator_*_amd64.deb
|
|
||||||
cryptomator_*.asc
|
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
jq -c 'select(.filename|endswith(".dmg")) | select(.filename|endswith("-arm64.dmg")|not) | {name: "github.releases.downloads", tags: ["file=dmg", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
jq -c 'select(.filename|endswith(".dmg")) | select(.filename|endswith("-arm64.dmg")|not) | {name: "github.releases.downloads", tags: ["file=dmg", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
||||||
|
|
||||||
RESULT=$(jq -s -c "." output.json)
|
RESULT=$(jq -s -c "." output.json)
|
||||||
echo "::set-output name=result::${RESULT}"
|
echo "result=${RESULT}" >> $GITHUB_OUTPUT
|
||||||
env:
|
env:
|
||||||
INTERVAL: 900
|
INTERVAL: 900
|
||||||
JSON_DATA: ${{ steps.get-stats.outputs.result }}
|
JSON_DATA: ${{ steps.get-stats.outputs.result }}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
name: Parse and Validate a version string or tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "A specific version to use"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
outputs:
|
||||||
|
semVerStr:
|
||||||
|
description: "The full version string."
|
||||||
|
value: ${{ jobs.determine-version.outputs.semVerStr}}
|
||||||
|
semVerNum:
|
||||||
|
description: "The numerical part of the version string"
|
||||||
|
value: ${{ jobs.determine-version.outputs.semVerNum}}
|
||||||
|
revNum:
|
||||||
|
description: "The revision number"
|
||||||
|
value: ${{ jobs.determine-version.outputs.revNum}}
|
||||||
|
versionType:
|
||||||
|
description: "Type of the version. Values are [stable, alpha, beta, rc, unknown]"
|
||||||
|
value: ${{ jobs.determine-version.outputs.type }}
|
||||||
|
|
||||||
|
env:
|
||||||
|
JAVA_VERSION: 19
|
||||||
|
JAVA_DIST: 'temurin'
|
||||||
|
JAVA_CACHE: 'maven'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
determine-version:
|
||||||
|
name: 'Determines the version following semver'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
semVerNum: ${{ steps.versions.outputs.semVerNum }}
|
||||||
|
semVerStr: ${{ steps.versions.outputs.semVerStr }}
|
||||||
|
revNum: ${{ steps.versions.outputs.revNum }}
|
||||||
|
type: ${{ steps.versions.outputs.type}}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
cache: ${{ env.JAVA_CACHE }}
|
||||||
|
- id: versions
|
||||||
|
name: Get version information
|
||||||
|
run: |
|
||||||
|
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
|
SEM_VER_STR=${GITHUB_REF##*/}
|
||||||
|
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
|
SEM_VER_STR="${{ github.event.inputs.version }}"
|
||||||
|
else
|
||||||
|
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||||
|
fi
|
||||||
|
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||||
|
REVCOUNT=`git rev-list --count HEAD`
|
||||||
|
TYPE="unknown"
|
||||||
|
if [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
TYPE="stable"
|
||||||
|
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-alpha[1-9]+$ ]]; then
|
||||||
|
TYPE="alpha"
|
||||||
|
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-beta[1-9]+$ ]]; then
|
||||||
|
TYPE="beta"
|
||||||
|
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-rc[1-9]$ ]]; then
|
||||||
|
TYPE="rc"
|
||||||
|
fi
|
||||||
|
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
|
||||||
|
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
|
||||||
|
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
|
||||||
|
echo "type=${TYPE}" >> $GITHUB_OUTPUT
|
||||||
|
- name: Validate Version
|
||||||
|
uses: skymatic/semver-validation-action@v2
|
||||||
|
with:
|
||||||
|
version: ${{ steps.versions.outputs.semVerStr }}
|
||||||
@@ -13,9 +13,15 @@ env:
|
|||||||
JAVA_VERSION: 19
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
get-version:
|
||||||
|
uses: ./.github/workflows/get-version.yml
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.version }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
|
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
needs: [get-version]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -30,42 +36,31 @@ jobs:
|
|||||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
java-package: 'jdk+fx'
|
||||||
architecture: ${{ matrix.architecture }}
|
architecture: ${{ matrix.architecture }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- id: versions
|
- name: Ensure major jfx version in pom equals in jdk
|
||||||
name: Apply version information
|
if: ${{ !contains(matrix.os, 'self-hosted') }}
|
||||||
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
exit 1
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
}
|
||||||
else
|
- name: Set version
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
fi
|
|
||||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
|
||||||
REVCOUNT=`git rev-list --count HEAD`
|
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
|
||||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
|
||||||
echo "::set-output name=revNum::${REVCOUNT}"
|
|
||||||
- name: Validate Version
|
|
||||||
uses: skymatic/semver-validation-action@v1
|
|
||||||
with:
|
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,mac -DskipTests
|
run: mvn -B clean package -Pdependency-check,mac -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
cp dist/mac/launcher.sh target
|
|
||||||
cp target/cryptomator-*.jar target/mods
|
cp target/cryptomator-*.jar target/mods
|
||||||
- name: Run jlink
|
- name: Run jlink
|
||||||
run: >
|
run: >
|
||||||
@@ -73,7 +68,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -91,22 +86,25 @@ jobs:
|
|||||||
--dest appdir
|
--dest appdir
|
||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
--copyright "(C) 2016 - 2023 Skymatic GmbH"
|
||||||
--app-version "${{ steps.versions.outputs.semVerNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||||
|
--java-options "--enable-preview"
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dfile.encoding=\"utf-8\""
|
--java-options "-Dfile.encoding=\"utf-8\""
|
||||||
--java-options "-Dapple.awt.enableTemplateImages=true"
|
--java-options "-Dapple.awt.enableTemplateImages=true"
|
||||||
--java-options "-Dsun.java2d.metal=true"
|
--java-options "-Dsun.java2d.metal=true"
|
||||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||||
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
|
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
|
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
|
||||||
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
|
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
|
||||||
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\""
|
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\""
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
|
||||||
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
||||||
|
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\""
|
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
|
||||||
--mac-package-identifier org.cryptomator
|
--mac-package-identifier org.cryptomator
|
||||||
--resource-dir dist/mac/resources
|
--resource-dir dist/mac/resources
|
||||||
- name: Patch Cryptomator.app
|
- name: Patch Cryptomator.app
|
||||||
@@ -116,8 +114,8 @@ jobs:
|
|||||||
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
|
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
|
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||||
env:
|
env:
|
||||||
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
|
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||||
REVISION_NO: ${{ steps.versions.outputs.revNum }}
|
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
||||||
- name: Generate license for dmg
|
- name: Generate license for dmg
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party
|
mvn -B license:add-third-party
|
||||||
@@ -151,6 +149,10 @@ jobs:
|
|||||||
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
|
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
|
||||||
- name: Codesign
|
- name: Codesign
|
||||||
run: |
|
run: |
|
||||||
|
echo "Codesigning jdk files..."
|
||||||
|
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||||
|
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name 'jspawnhelper' -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
|
||||||
|
echo "Codesigning jar contents..."
|
||||||
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
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
|
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
|
||||||
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
||||||
@@ -201,7 +203,7 @@ jobs:
|
|||||||
--icon ".VolumeIcon.icns" 512 758
|
--icon ".VolumeIcon.icns" 512 758
|
||||||
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
|
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
|
||||||
env:
|
env:
|
||||||
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
|
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||||
- name: Notarize .dmg
|
- name: Notarize .dmg
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: cocoalibs/xcode-notarization-action@v1
|
uses: cocoalibs/xcode-notarization-action@v1
|
||||||
@@ -212,7 +214,7 @@ jobs:
|
|||||||
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
||||||
xcode-path: ${{ matrix.xcode-path }}
|
xcode-path: ${{ matrix.xcode-path }}
|
||||||
- name: Add possible alpha/beta tags to installer name
|
- name: Add possible alpha/beta tags to installer name
|
||||||
run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
|
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
|
||||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: Post Release Publish Tasks
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
get-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download source tarball
|
||||||
|
run: |
|
||||||
|
curl -L -H "Accept: application/vnd.github+json" ${{ github.event.release.tarball_url }} --output cryptomator-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
- name: Sign source 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-*.tar.gz
|
||||||
|
env:
|
||||||
|
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||||
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
|
- name: Publish asc on GitHub Releases
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
|
files: |
|
||||||
|
cryptomator-*.tar.gz.asc
|
||||||
|
- name: Slack Notification
|
||||||
|
uses: rtCamp/action-slack-notify@v2
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
|
SLACK_ICON: false
|
||||||
|
SLACK_ICON_EMOJI: ':bot:'
|
||||||
|
SLACK_CHANNEL: 'cryptomator-desktop'
|
||||||
|
SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
|
||||||
|
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|build deb Package>."
|
||||||
|
SLACK_FOOTER: false
|
||||||
|
MSG_MINIMAL: true
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${SEM_VER_STR} == `mvn help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then
|
if [[ ${SEM_VER_STR} == `mvn help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "Version not set in POM"
|
echo "Version not set in POM"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -19,46 +19,42 @@ defaults:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
get-version:
|
||||||
|
uses: ./.github/workflows/get-version.yml
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.version }}
|
||||||
|
|
||||||
build-msi:
|
build-msi:
|
||||||
name: Build .msi Installer
|
name: Build .msi Installer
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
needs: [get-version]
|
||||||
|
env:
|
||||||
|
LOOPBACK_ALIAS: 'cryptomator-vault'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
java-package: 'jdk+fx'
|
||||||
cache: ${{ env.JAVA_CACHE }}
|
cache: ${{ env.JAVA_CACHE }}
|
||||||
- id: versions
|
- name: Ensure major jfx version in pom equals in jdk
|
||||||
name: Apply version information
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
exit 1
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
}
|
||||||
else
|
- name: Set version
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
fi
|
|
||||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
|
||||||
REVCOUNT=`git rev-list --count HEAD`
|
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
|
||||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
|
||||||
echo "::set-output name=revNum::${REVCOUNT}"
|
|
||||||
- name: Validate Version
|
|
||||||
uses: skymatic/semver-validation-action@v1
|
|
||||||
with:
|
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,win -DskipTests
|
run: mvn -B clean package -Pdependency-check,win -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
cp dist/linux/launcher.sh target
|
|
||||||
cp target/cryptomator-*.jar target/mods
|
cp target/cryptomator-*.jar target/mods
|
||||||
- name: Run jlink
|
- name: Run jlink
|
||||||
run: >
|
run: >
|
||||||
@@ -66,7 +62,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -84,11 +80,13 @@ jobs:
|
|||||||
--dest appdir
|
--dest appdir
|
||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
--copyright "(C) 2016 - 2023 Skymatic GmbH"
|
||||||
--app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
|
--java-options "--enable-preview"
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.win"
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||||
--java-options "-Dfile.encoding=\"utf-8\""
|
--java-options "-Dfile.encoding=\"utf-8\""
|
||||||
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
|
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
|
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
|
||||||
@@ -96,8 +94,9 @@ jobs:
|
|||||||
--java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\""
|
--java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\""
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
|
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
|
||||||
|
--java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\""
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||||
--java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\""
|
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
|
||||||
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
|
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
|
||||||
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"~/AppData/Roaming/Cryptomator/keychain.json\""
|
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"~/AppData/Roaming/Cryptomator/keychain.json\""
|
||||||
--resource-dir dist/win/resources
|
--resource-dir dist/win/resources
|
||||||
@@ -105,11 +104,24 @@ jobs:
|
|||||||
- name: Patch Application Directory
|
- name: Patch Application Directory
|
||||||
run: |
|
run: |
|
||||||
cp dist/win/contrib/* appdir/Cryptomator
|
cp dist/win/contrib/* appdir/Cryptomator
|
||||||
|
- name: Set LOOPBACK_ALIAS in patchWebDAV.bat
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$patchScript = "appdir\Cryptomator\patchWebDAV.bat"
|
||||||
|
try {
|
||||||
|
(Get-Content $patchScript ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"${{ env.LOOPBACK_ALIAS}}`"" | Set-Content $patchScript
|
||||||
|
} catch {
|
||||||
|
Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
- name: Fix permissions
|
- name: Fix permissions
|
||||||
run: attrib -r appdir/Cryptomator/Cryptomator.exe
|
run: attrib -r appdir/Cryptomator/Cryptomator.exe
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
- name: Extract integrations DLL for code signing
|
||||||
|
shell: pwsh
|
||||||
|
run: gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --extract integrations.dll }
|
||||||
- name: Codesign
|
- name: Codesign
|
||||||
uses: skymatic/code-sign-action@v1
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||||
@@ -118,6 +130,10 @@ jobs:
|
|||||||
timestampUrl: 'http://timestamp.digicert.com'
|
timestampUrl: 'http://timestamp.digicert.com'
|
||||||
folder: appdir/Cryptomator
|
folder: appdir/Cryptomator
|
||||||
recursive: true
|
recursive: true
|
||||||
|
- name: Repack signed DLL into jar
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --update integrations.dll; Remove-Item integrations.dll}
|
||||||
- name: Generate license for MSI
|
- name: Generate license for MSI
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party
|
mvn -B license:add-third-party
|
||||||
@@ -139,8 +155,8 @@ jobs:
|
|||||||
--dest installer
|
--dest installer
|
||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
--copyright "(C) 2016 - 2023 Skymatic GmbH"
|
||||||
--app-version "${{ steps.versions.outputs.semVerNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}"
|
||||||
--win-menu
|
--win-menu
|
||||||
--win-dir-chooser
|
--win-dir-chooser
|
||||||
--win-shortcut-prompt
|
--win-shortcut-prompt
|
||||||
@@ -152,7 +168,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
||||||
- name: Codesign MSI
|
- name: Codesign MSI
|
||||||
uses: skymatic/code-sign-action@v1
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||||
@@ -161,7 +177,7 @@ jobs:
|
|||||||
timestampUrl: 'http://timestamp.digicert.com'
|
timestampUrl: 'http://timestamp.digicert.com'
|
||||||
folder: installer
|
folder: installer
|
||||||
- name: Add possible alpha/beta tags to installer name
|
- name: Add possible alpha/beta tags to installer name
|
||||||
run: mv installer/Cryptomator-*.msi Cryptomator-${{ steps.versions.outputs.semVerStr }}-x64.msi
|
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.msi
|
||||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
@@ -186,23 +202,20 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
*.msi
|
*.msi
|
||||||
*.asc
|
*.asc
|
||||||
outputs:
|
|
||||||
semVerNum: ${{ steps.versions.outputs.semVerNum }}
|
|
||||||
semVerStr: ${{ steps.versions.outputs.semVerStr }}
|
|
||||||
revNum: ${{ steps.versions.outputs.revNum }}
|
|
||||||
|
|
||||||
call-winget-flow:
|
call-winget-flow:
|
||||||
needs: [build-msi]
|
needs: [get-version, build-msi]
|
||||||
if: github.event.action == 'published'
|
if: github.event.action == 'published' && needs.get-version.outputs.versionType == 'stable'
|
||||||
uses: ./.github/workflows/winget.yml
|
uses: ./.github/workflows/winget.yml
|
||||||
with:
|
with:
|
||||||
releaseTag: ${{ github.event.release.tag_name }}
|
releaseTag: ${{ github.event.release.tag_name }}
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
|
||||||
build-exe:
|
build-exe:
|
||||||
name: Build .exe installer
|
name: Build .exe installer
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: [build-msi]
|
needs: [get-version, build-msi]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Download .msi
|
- name: Download .msi
|
||||||
@@ -238,9 +251,9 @@ jobs:
|
|||||||
"${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs
|
"${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs
|
||||||
-ext WixBalExtension
|
-ext WixBalExtension
|
||||||
-out dist/win/bundle/
|
-out dist/win/bundle/
|
||||||
-dBundleVersion="${{ needs.build-msi.outputs.semVerNum }}.${{ needs.build-msi.outputs.revNum }}"
|
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
-dBundleVendor="Skymatic GmbH"
|
-dBundleVendor="Skymatic GmbH"
|
||||||
-dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH"
|
-dBundleCopyright="(C) 2016 - 2023 Skymatic GmbH"
|
||||||
-dAboutUrl="https://cryptomator.org"
|
-dAboutUrl="https://cryptomator.org"
|
||||||
-dHelpUrl="https://cryptomator.org/contact"
|
-dHelpUrl="https://cryptomator.org/contact"
|
||||||
-dUpdateUrl="https://cryptomator.org/downloads/"
|
-dUpdateUrl="https://cryptomator.org/downloads/"
|
||||||
@@ -255,7 +268,7 @@ jobs:
|
|||||||
-ib installer/unsigned/Cryptomator-Installer.exe
|
-ib installer/unsigned/Cryptomator-Installer.exe
|
||||||
-o tmp/engine.exe
|
-o tmp/engine.exe
|
||||||
- name: Codesign burn engine
|
- name: Codesign burn engine
|
||||||
uses: skymatic/code-sign-action@v1
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||||
@@ -269,7 +282,7 @@ jobs:
|
|||||||
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
|
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
|
||||||
-o installer/Cryptomator-Installer.exe
|
-o installer/Cryptomator-Installer.exe
|
||||||
- name: Codesign EXE
|
- name: Codesign EXE
|
||||||
uses: skymatic/code-sign-action@v1
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||||
@@ -278,7 +291,7 @@ jobs:
|
|||||||
timestampUrl: 'http://timestamp.digicert.com'
|
timestampUrl: 'http://timestamp.digicert.com'
|
||||||
folder: installer
|
folder: installer
|
||||||
- name: Add possible alpha/beta tags to installer name
|
- name: Add possible alpha/beta tags to installer name
|
||||||
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe
|
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.exe
|
||||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
@@ -311,12 +324,12 @@ jobs:
|
|||||||
needs: [build-msi, build-exe]
|
needs: [build-msi, build-exe]
|
||||||
steps:
|
steps:
|
||||||
- name: Download .msi
|
- name: Download .msi
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: msi
|
name: msi
|
||||||
path: msi
|
path: msi
|
||||||
- name: Download .exe
|
- name: Download .exe
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: exe
|
name: exe
|
||||||
path: exe
|
path: exe
|
||||||
@@ -326,7 +339,7 @@ jobs:
|
|||||||
cp msi/*.msi files
|
cp msi/*.msi files
|
||||||
cp exe/*.exe files
|
cp exe/*.exe files
|
||||||
- name: Upload to Kaspersky
|
- name: Upload to Kaspersky
|
||||||
uses: SamKirkland/FTP-Deploy-Action@4.3.0
|
uses: SamKirkland/FTP-Deploy-Action@4.3.3
|
||||||
with:
|
with:
|
||||||
protocol: ftps
|
protocol: ftps
|
||||||
server: allowlist.kaspersky-labs.com
|
server: allowlist.kaspersky-labs.com
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
name: Publish on winget repo
|
name: Publish on winget repo
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Get download url for msi artifacts
|
- name: Get download url for release assets
|
||||||
id: get-release-assets
|
id: get-release-assets
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Cryptomator is provided free of charge as an open-source project despite the hig
|
|||||||
<tr>
|
<tr>
|
||||||
<td><a href="https://mowcapital.com/"><img src="https://cryptomator.org/img/sponsors/mowcapital.svg" alt="Mow Capital" height="40"></a></td>
|
<td><a href="https://mowcapital.com/"><img src="https://cryptomator.org/img/sponsors/mowcapital.svg" alt="Mow Capital" height="40"></a></td>
|
||||||
<td><a href="https://www.easeus.com/"><img src="https://cryptomator.org/img/sponsors/easeus.png" alt="EaseUS" height="40"></a></td>
|
<td><a href="https://www.easeus.com/"><img src="https://cryptomator.org/img/sponsors/easeus.png" alt="EaseUS" height="40"></a></td>
|
||||||
|
<td><a href="https://www.hassmann-it-forensik.de/"><img src="https://cryptomator.org/img/sponsors/hassmannitforensik.png" alt="Hassmann IT-Forensik" height="40"></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -61,7 +62,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
|||||||
- File names get encrypted
|
- File names get encrypted
|
||||||
- Folder structure gets obfuscated
|
- Folder structure gets obfuscated
|
||||||
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
||||||
- Three thousand commits for the security of your data!! :tada:
|
- Four thousand commits for the security of your data!! :tada:
|
||||||
|
|
||||||
### Privacy
|
### Privacy
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ For more information on the security details visit [cryptomator.org](https://doc
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* JDK 17 (e.g. temurin)
|
* JDK 19 (e.g. temurin)
|
||||||
* Maven 3
|
* Maven 3
|
||||||
|
|
||||||
### Run Maven
|
### Run Maven
|
||||||
@@ -99,10 +100,6 @@ mvn clean install
|
|||||||
|
|
||||||
This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages.
|
This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages.
|
||||||
|
|
||||||
### Start Cryptomator
|
|
||||||
|
|
||||||
If you unzip the buildkit for your OS, you will find a launcher script with some basic settings. You might want to adjust these to your needs. To start Cryptomator, simply execute the launcher script from a terminal, e.g. `launcher-linux.sh`, if you're on a Linux system.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
|
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
|
||||||
|
|||||||
Vendored
+21
-14
@@ -11,15 +11,20 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
|
|||||||
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
|
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||||
SEMVER_STR=${VERSION}
|
SEMVER_STR=${VERSION}
|
||||||
|
|
||||||
|
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
||||||
|
|
||||||
# compile
|
# compile
|
||||||
mvn -B -f ../../../pom.xml clean package -DskipTests -Plinux
|
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
|
||||||
|
cp ../../../LICENSE.txt ../../../target
|
||||||
|
cp ../launcher.sh ../../../target
|
||||||
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
||||||
|
|
||||||
# add runtime
|
# add runtime
|
||||||
${JAVA_HOME}/bin/jlink \
|
${JAVA_HOME}/bin/jlink \
|
||||||
|
--verbose \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JAVA_HOME}/jmods" \
|
--module-path "${JAVA_HOME}/jmods" \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
@@ -27,7 +32,7 @@ ${JAVA_HOME}/bin/jlink \
|
|||||||
--compress=1
|
--compress=1
|
||||||
|
|
||||||
# create app dir
|
# create app dir
|
||||||
envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
envsubst '${SEMVER_STR} ${REVISION_NUM}' < ../launcher-gtk2.properties > launcher-gtk2.properties
|
||||||
${JAVA_HOME}/bin/jpackage \
|
${JAVA_HOME}/bin/jpackage \
|
||||||
--verbose \
|
--verbose \
|
||||||
--type app-image \
|
--type app-image \
|
||||||
@@ -35,10 +40,12 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--input ../../../target/libs \
|
--input ../../../target/libs \
|
||||||
--module-path ../../../target/mods \
|
--module-path ../../../target/mods \
|
||||||
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator \
|
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator \
|
||||||
--dest . \
|
--dest appdir \
|
||||||
--name Cryptomator \
|
--name Cryptomator \
|
||||||
--vendor "Skymatic GmbH" \
|
--vendor "Skymatic GmbH" \
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
|
--java-options "--enable-preview" \
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" \
|
||||||
|
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
|
||||||
--java-options "-Xss5m" \
|
--java-options "-Xss5m" \
|
||||||
--java-options "-Xmx256m" \
|
--java-options "-Xmx256m" \
|
||||||
--app-version "${VERSION}.${REVISION_NO}" \
|
--app-version "${VERSION}.${REVISION_NO}" \
|
||||||
@@ -46,6 +53,7 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
|
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \
|
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \
|
||||||
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
|
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
|
||||||
|
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" \
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
|
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
|
||||||
--java-options "-Dcryptomator.showTrayIcon=false" \
|
--java-options "-Dcryptomator.showTrayIcon=false" \
|
||||||
@@ -54,9 +62,8 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--resource-dir ../resources
|
--resource-dir ../resources
|
||||||
|
|
||||||
# transform AppDir
|
# transform AppDir
|
||||||
mv Cryptomator Cryptomator.AppDir
|
mv appdir/Cryptomator Cryptomator.AppDir
|
||||||
cp -r resources/AppDir/* Cryptomator.AppDir/
|
cp -r resources/AppDir/* Cryptomator.AppDir/
|
||||||
chmod +x Cryptomator.AppDir/lib/runtime/bin/java
|
|
||||||
envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
|
envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
|
||||||
cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png
|
cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png
|
||||||
cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png
|
cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png
|
||||||
@@ -70,12 +77,6 @@ ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryp
|
|||||||
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
|
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
|
||||||
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
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
|
# load AppImageTool
|
||||||
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /tmp/appimagetool.AppImage
|
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /tmp/appimagetool.AppImage
|
||||||
chmod +x /tmp/appimagetool.AppImage
|
chmod +x /tmp/appimagetool.AppImage
|
||||||
@@ -83,5 +84,11 @@ chmod +x /tmp/appimagetool.AppImage
|
|||||||
# create AppImage
|
# create AppImage
|
||||||
/tmp/appimagetool.AppImage \
|
/tmp/appimagetool.AppImage \
|
||||||
Cryptomator.AppDir \
|
Cryptomator.AppDir \
|
||||||
cryptomator-SNAPSHOT-x86_64.AppImage \
|
cryptomator-${SEMVER_STR}-x86_64.AppImage \
|
||||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-x86_64.AppImage"
|
||||||
|
echo ""
|
||||||
|
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
|
||||||
|
echo ""
|
||||||
@@ -15,9 +15,6 @@ elif command -v pacman &> /dev/null; then # don't forget arch
|
|||||||
GTK3_PRESENT=`pacman -Qi gtk3 &> /dev/null; echo $?`
|
GTK3_PRESENT=`pacman -Qi gtk3 &> /dev/null; echo $?`
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# workaround for https://github.com/cryptomator/cryptomator-linux/issues/27
|
|
||||||
export LD_PRELOAD=lib/app/libjffi.so
|
|
||||||
|
|
||||||
if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then
|
if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then
|
||||||
bin/Cryptomator-gtk2 $@
|
bin/Cryptomator-gtk2 $@
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
</content_rating>
|
</content_rating>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release date="2022-12-14" version="1.6.17"/>
|
||||||
|
<release date="2022-12-06" version="1.6.16"/>
|
||||||
<release date="2022-10-06" version="1.6.15"/>
|
<release date="2022-10-06" version="1.6.15"/>
|
||||||
<release date="2022-08-31" version="1.6.14"/>
|
<release date="2022-08-31" version="1.6.14"/>
|
||||||
<release date="2022-07-27" version="1.6.12"/>
|
<release date="2022-07-27" version="1.6.12"/>
|
||||||
|
|||||||
Vendored
+2
-2
@@ -2,7 +2,7 @@ Source: cryptomator
|
|||||||
Maintainer: Cryptobot <releases@cryptomator.org>
|
Maintainer: Cryptobot <releases@cryptomator.org>
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-19
|
Build-Depends: debhelper (>=10), coffeelibs-jdk-19, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
|
||||||
Standards-Version: 4.5.0
|
Standards-Version: 4.5.0
|
||||||
Homepage: https://cryptomator.org
|
Homepage: https://cryptomator.org
|
||||||
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
||||||
@@ -12,7 +12,7 @@ Package: cryptomator
|
|||||||
Architecture: any
|
Architecture: any
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}, libfuse2, xdg-utils, libjffi-jni
|
Depends: ${shlibs:Depends}, ${misc:Depends}, libfuse3-3
|
||||||
Recommends: gvfs-backends, gvfs-fuse, gnome-keyring
|
Recommends: gvfs-backends, gvfs-fuse, gnome-keyring
|
||||||
XB-AppName: Cryptomator
|
XB-AppName: Cryptomator
|
||||||
XB-Category: Utility;Security;FileTools;
|
XB-Category: Utility;Security;FileTools;
|
||||||
|
|||||||
Vendored
+2
-2
@@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator <info@cryptomator.org>
|
|||||||
Source: https://cryptomator.org
|
Source: https://cryptomator.org
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2016-2022 Skymatic GmbH
|
Copyright: 2016-2023 Skymatic GmbH
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: debian/org.cryptomator.Cryptomator.appdata.xml
|
Files: debian/org.cryptomator.Cryptomator.appdata.xml
|
||||||
Copyright: 2016-2022 Skymatic GmbH
|
Copyright: 2016-2023 Skymatic GmbH
|
||||||
License: FSFAP
|
License: FSFAP
|
||||||
|
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|||||||
-1
@@ -1,5 +1,4 @@
|
|||||||
cryptomator usr/lib
|
cryptomator usr/lib
|
||||||
debian/cryptomator.sh usr/lib/cryptomator/bin
|
|
||||||
common/org.cryptomator.Cryptomator.desktop usr/share/applications
|
common/org.cryptomator.Cryptomator.desktop usr/share/applications
|
||||||
common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps
|
common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps
|
||||||
common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps
|
common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
usr/lib/cryptomator/bin/cryptomator.sh usr/bin/cryptomator
|
usr/lib/cryptomator/bin/cryptomator usr/bin/cryptomator
|
||||||
|
|||||||
Vendored
-6
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# fix for https://github.com/cryptomator/cryptomator/issues/1370
|
|
||||||
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/jni/libjffi-1.2.so
|
|
||||||
|
|
||||||
/usr/lib/cryptomator/bin/cryptomator $@
|
|
||||||
Vendored
+11
-9
@@ -5,6 +5,12 @@
|
|||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
JAVA_HOME = /usr/lib/jvm/java-19-coffeelibs
|
JAVA_HOME = /usr/lib/jvm/java-19-coffeelibs
|
||||||
|
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
||||||
|
ifeq ($(DEB_BUILD_ARCH),amd64)
|
||||||
|
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
|
||||||
|
else ifeq ($(DEB_BUILD_ARCH),arm64)
|
||||||
|
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
|
||||||
|
endif
|
||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
@@ -20,7 +26,8 @@ override_dh_auto_build:
|
|||||||
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
||||||
$(JAVA_HOME)/bin/jlink \
|
$(JAVA_HOME)/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
--module-path "${JMODS_PATH}" \
|
||||||
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
@@ -35,7 +42,9 @@ override_dh_auto_build:
|
|||||||
--dest . \
|
--dest . \
|
||||||
--name cryptomator \
|
--name cryptomator \
|
||||||
--vendor "Skymatic GmbH" \
|
--vendor "Skymatic GmbH" \
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
|
--java-options "--enable-preview" \
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" \
|
||||||
|
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
|
||||||
--java-options "-Xss5m" \
|
--java-options "-Xss5m" \
|
||||||
--java-options "-Xmx256m" \
|
--java-options "-Xmx256m" \
|
||||||
--java-options "-Dfile.encoding=\"utf-8\"" \
|
--java-options "-Dfile.encoding=\"utf-8\"" \
|
||||||
@@ -51,10 +60,3 @@ override_dh_auto_build:
|
|||||||
--app-version "${VERSION_NUM}.${REVISION_NUM}" \
|
--app-version "${VERSION_NUM}.${REVISION_NUM}" \
|
||||||
--resource-dir resources \
|
--resource-dir resources \
|
||||||
--verbose
|
--verbose
|
||||||
|
|
||||||
override_dh_fixperms:
|
|
||||||
dh_fixperms
|
|
||||||
chmod +x debian/cryptomator/usr/lib/cryptomator/bin/cryptomator.sh
|
|
||||||
|
|
||||||
# override_dh_strip:
|
|
||||||
# no-op
|
|
||||||
|
|||||||
Vendored
+2
@@ -1,5 +1,7 @@
|
|||||||
java-options=-Xss5m \
|
java-options=-Xss5m \
|
||||||
-Xmx256m \
|
-Xmx256m \
|
||||||
|
--enable-preview \
|
||||||
|
--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64 \
|
||||||
-Dfile.encoding=\"utf-8\" \
|
-Dfile.encoding=\"utf-8\" \
|
||||||
-Dcryptomator.appVersion=\"${SEMVER_STR}\" \
|
-Dcryptomator.appVersion=\"${SEMVER_STR}\" \
|
||||||
-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\" \
|
-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\" \
|
||||||
|
|||||||
Vendored
-13
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
cd $(dirname $0)
|
|
||||||
java \
|
|
||||||
-p "mods" \
|
|
||||||
-cp "libs/*" \
|
|
||||||
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
|
|
||||||
-Dcryptomator.ipcSocketPath="~/.config/Cryptomator/ipc.socket" \
|
|
||||||
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
|
|
||||||
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
|
|
||||||
-Djdk.gtk.version=2 \
|
|
||||||
-Xss2m \
|
|
||||||
-Xmx512m \
|
|
||||||
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
|
||||||
Vendored
+4
-1
@@ -21,7 +21,7 @@ rm -rf runtime dmg *.app *.dmg
|
|||||||
# set variables
|
# set variables
|
||||||
APP_NAME="Cryptomator"
|
APP_NAME="Cryptomator"
|
||||||
VENDOR="Skymatic GmbH"
|
VENDOR="Skymatic GmbH"
|
||||||
COPYRIGHT_YEARS="2016 - 2022"
|
COPYRIGHT_YEARS="2016 - 2023"
|
||||||
PACKAGE_IDENTIFIER="org.cryptomator"
|
PACKAGE_IDENTIFIER="org.cryptomator"
|
||||||
MAIN_JAR_GLOB="cryptomator-*.jar"
|
MAIN_JAR_GLOB="cryptomator-*.jar"
|
||||||
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
||||||
@@ -65,6 +65,8 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--vendor "${VENDOR}" \
|
--vendor "${VENDOR}" \
|
||||||
--copyright "(C) ${COPYRIGHT_YEARS} ${VENDOR}" \
|
--copyright "(C) ${COPYRIGHT_YEARS} ${VENDOR}" \
|
||||||
--app-version "${VERSION_NO}" \
|
--app-version "${VERSION_NO}" \
|
||||||
|
--java-options "--enable-preview" \
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.mac" \
|
||||||
--java-options "-Xss5m" \
|
--java-options "-Xss5m" \
|
||||||
--java-options "-Xmx256m" \
|
--java-options "-Xmx256m" \
|
||||||
--java-options "-Dfile.encoding=\"utf-8\"" \
|
--java-options "-Dfile.encoding=\"utf-8\"" \
|
||||||
@@ -77,6 +79,7 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/${APP_NAME}/ipc.socket\"" \
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/${APP_NAME}/ipc.socket\"" \
|
||||||
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/${APP_NAME}/key.p12\"" \
|
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/${APP_NAME}/key.p12\"" \
|
||||||
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \
|
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \
|
||||||
|
--java-options "-Dcryptomator.mountPointsDir=\"~/${APP_NAME}\"" \
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true" \
|
--java-options "-Dcryptomator.showTrayIcon=true" \
|
||||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
|
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
|
||||||
--mac-package-identifier ${PACKAGE_IDENTIFIER} \
|
--mac-package-identifier ${PACKAGE_IDENTIFIER} \
|
||||||
|
|||||||
Binary file not shown.
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
\f1\b0 \
|
\f1\b0 \
|
||||||
\
|
\
|
||||||
|
|
||||||
\f0\b \'a9 2016 \'96 2022 Skymatic GmbH
|
\f0\b \'a9 2016 \'96 2023 Skymatic GmbH
|
||||||
\f1\b0 \
|
\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 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.\
|
||||||
|
|||||||
Vendored
-12
@@ -1,12 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
cd $(dirname $0)
|
|
||||||
java \
|
|
||||||
-p "mods" \
|
|
||||||
-cp "libs/*" \
|
|
||||||
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \
|
|
||||||
-Dcryptomator.ipcSocketPath="~/Library/Application Support/Cryptomator/ipc.socket" \
|
|
||||||
-Dcryptomator.logDir="~/Library/Logs/Cryptomator" \
|
|
||||||
-Dcryptomator.mountPointsDir="/Volumes" \
|
|
||||||
-Xss20m \
|
|
||||||
-Xmx512m \
|
|
||||||
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
|
||||||
Vendored
+3
-1
@@ -9,8 +9,9 @@ SET ABOUT_URL="https://cryptomator.org"
|
|||||||
SET UPDATE_URL="https://cryptomator.org/downloads/"
|
SET UPDATE_URL="https://cryptomator.org/downloads/"
|
||||||
SET HELP_URL="https://cryptomator.org/contact/"
|
SET HELP_URL="https://cryptomator.org/contact/"
|
||||||
SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
||||||
|
SET LOOPBACK_ALIAS="cryptomator-vault"
|
||||||
|
|
||||||
powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
powershell -NoLogo -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
||||||
-AppName %APPNAME%^
|
-AppName %APPNAME%^
|
||||||
-MainJarGlob "%MAIN_JAR_GLOB%"^
|
-MainJarGlob "%MAIN_JAR_GLOB%"^
|
||||||
-ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^
|
-ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^
|
||||||
@@ -20,4 +21,5 @@ powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
|||||||
-AboutUrl "%ABOUT_URL%"^
|
-AboutUrl "%ABOUT_URL%"^
|
||||||
-HelpUrl "%HELP_URL%"^
|
-HelpUrl "%HELP_URL%"^
|
||||||
-UpdateUrl "%UPDATE_URL%"^
|
-UpdateUrl "%UPDATE_URL%"^
|
||||||
|
-LoopbackAlias "%LOOPBACK_ALIAS%"^
|
||||||
-Clean 1
|
-Clean 1
|
||||||
Vendored
+13
-1
@@ -8,6 +8,7 @@ Param(
|
|||||||
[Parameter(Mandatory, HelpMessage="Please provide a help url")][string] $HelpUrl,
|
[Parameter(Mandatory, HelpMessage="Please provide a help url")][string] $HelpUrl,
|
||||||
[Parameter(Mandatory, HelpMessage="Please provide an update url")][string] $UpdateUrl,
|
[Parameter(Mandatory, HelpMessage="Please provide an update url")][string] $UpdateUrl,
|
||||||
[Parameter(Mandatory, HelpMessage="Please provide an about url")][string] $AboutUrl,
|
[Parameter(Mandatory, HelpMessage="Please provide an about url")][string] $AboutUrl,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide an alias for localhost")][string] $LoopbackAlias,
|
||||||
[bool] $clean
|
[bool] $clean
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,6 +75,8 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
|||||||
--name $AppName `
|
--name $AppName `
|
||||||
--vendor $Vendor `
|
--vendor $Vendor `
|
||||||
--copyright $copyright `
|
--copyright $copyright `
|
||||||
|
--java-options "--enable-preview" `
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.win" `
|
||||||
--java-options "-Xss5m" `
|
--java-options "-Xss5m" `
|
||||||
--java-options "-Xmx256m" `
|
--java-options "-Xmx256m" `
|
||||||
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
|
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
|
||||||
@@ -85,6 +88,7 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
|||||||
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" `
|
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" `
|
||||||
--java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" `
|
--java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" `
|
||||||
--java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" `
|
--java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" `
|
||||||
|
--java-options "-Dcryptomator.loopbackAlias=`"$LoopbackAlias`"" `
|
||||||
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" `
|
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" `
|
||||||
--java-options "-Dcryptomator.integrationsWin.keychainPaths=`"~/AppData/Roaming/$AppName/keychain.json`"" `
|
--java-options "-Dcryptomator.integrationsWin.keychainPaths=`"~/AppData/Roaming/$AppName/keychain.json`"" `
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true" `
|
--java-options "-Dcryptomator.showTrayIcon=true" `
|
||||||
@@ -105,6 +109,14 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
|||||||
# patch app dir
|
# patch app dir
|
||||||
Copy-Item "contrib\*" -Destination "$AppName"
|
Copy-Item "contrib\*" -Destination "$AppName"
|
||||||
attrib -r "$AppName\$AppName.exe"
|
attrib -r "$AppName\$AppName.exe"
|
||||||
|
# patch batch script to set hostfile
|
||||||
|
$webDAVPatcher = "$AppName\patchWebDAV.bat"
|
||||||
|
try {
|
||||||
|
(Get-Content $webDAVPatcher ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"$LoopbackAlias`"" | Set-Content $webDAVPatcher
|
||||||
|
} catch {
|
||||||
|
Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# create .msi
|
# create .msi
|
||||||
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
||||||
@@ -117,7 +129,7 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
|||||||
--name $AppName `
|
--name $AppName `
|
||||||
--vendor $Vendor `
|
--vendor $Vendor `
|
||||||
--copyright $copyright `
|
--copyright $copyright `
|
||||||
--app-version "$semVerNo" `
|
--app-version "$semVerNo.$revisionNo" `
|
||||||
--win-menu `
|
--win-menu `
|
||||||
--win-dir-chooser `
|
--win-dir-chooser `
|
||||||
--win-shortcut-prompt `
|
--win-shortcut-prompt `
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
\vieww12000\viewh15840\viewkind0
|
\vieww12000\viewh15840\viewkind0
|
||||||
\pard\tx283\tx567\tx850\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
|
\pard\tx283\tx567\tx850\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
|
\par
|
||||||
\b\'a9 2016 \'96 2022 Skymatic GmbH \b0\par
|
\b\'a9 2016 \'96 2023 Skymatic GmbH \b0\par
|
||||||
\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
|
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
|
\par
|
||||||
|
|||||||
Vendored
-12
@@ -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
|
|
||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
@echo off
|
@echo off
|
||||||
:: Default values for Cryptomator builds
|
:: Default values for Cryptomator builds
|
||||||
SET LOOPBACK_ALIAS="cryptomator-vault"
|
::REPLACE ME
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1^
|
powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1^
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@echo off
|
||||||
|
:: see comments in file ./version170-migrate-settings.ps1
|
||||||
|
|
||||||
|
cd %~dp0
|
||||||
|
powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\version170-migrate-settings.ps1
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# This script migrates Cryptomator settings for all local users on Windows in case the users uses custom directories as mountpoint
|
||||||
|
# See also https://github.com/cryptomator/cryptomator/pull/2654.
|
||||||
|
#
|
||||||
|
# TODO: This script should be evaluated in a yearly interval if it is still needed and if not, should be removed
|
||||||
|
#
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
|
||||||
|
#Get all active, local user profiles
|
||||||
|
$profileList = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
|
||||||
|
$localUsers = Get-LocalUser | Where-Object {$_.Enabled} | ForEach-Object { $_.Name}
|
||||||
|
|
||||||
|
Get-ChildItem $profileList | ForEach-Object { $_.GetValue("ProfileImagePath") } | Where-Object {
|
||||||
|
$profileNameMatches = ($_ | Select-String -Pattern "\\([^\\]+)$").Matches
|
||||||
|
if($profileNameMatches.Count -eq 1) {
|
||||||
|
#check if the last path part is contained in the local user name list
|
||||||
|
#otherwise do not touch it
|
||||||
|
return $localUsers.Contains($profileNameMatches[0].Groups[1].Value)
|
||||||
|
} else {
|
||||||
|
return $false;
|
||||||
|
}
|
||||||
|
} | ForEach-Object {
|
||||||
|
$settingsPath = "$_\AppData\Roaming\Cryptomator\settings.json"
|
||||||
|
if(!(Test-Path -Path $settingsPath -PathType Leaf)) {
|
||||||
|
#No settings file, nothing to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$settings = Get-Content -Path $settingsPath | ConvertFrom-Json
|
||||||
|
if($settings.preferredVolumeImpl -ne "FUSE") {
|
||||||
|
#Fuse not used, nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#check if customMountPoints are used
|
||||||
|
$atLeastOneCustomPath = $false;
|
||||||
|
foreach ($vault in $settings.directories){
|
||||||
|
$atLeastOneCustomPath = $atLeastOneCustomPath -or ($vault.useCustomMountPath -eq "True")
|
||||||
|
}
|
||||||
|
|
||||||
|
#if so, use WinFsp Local Drive
|
||||||
|
if( $atLeastOneCustomPath ) {
|
||||||
|
Add-Member -Force -InputObject $settings -Name "mountService" -Value "org.cryptomator.frontend.fuse.mount.WinFspMountProvider" -MemberType NoteProperty
|
||||||
|
$newSettings = $settings | Select-Object * -ExcludeProperty "preferredVolumeImpl"
|
||||||
|
ConvertTo-Json $newSettings | Set-Content -Path $settingsPath
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+2
@@ -9,4 +9,6 @@ java ^
|
|||||||
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
||||||
-Xss20m ^
|
-Xss20m ^
|
||||||
-Xmx512m ^
|
-Xmx512m ^
|
||||||
|
--enable-preview `
|
||||||
|
--enable-native-access=org.cryptomator.jfuse.win `
|
||||||
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
||||||
Vendored
+2
-5
@@ -65,13 +65,10 @@
|
|||||||
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.ExitDialogTitle)" />
|
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.ExitDialogTitle)" />
|
||||||
<!-- TODO: localize? -->
|
<!-- TODO: localize? -->
|
||||||
<Control Id="Suggestion" Type="Text" X="135" Y="100" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
|
<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>
|
<Text>We recommend for the best user experience to download and install the following third party Windows driver:</Text>
|
||||||
</Control>
|
</Control>
|
||||||
<Control Id="WinFsp" Type="Hyperlink" X="140" Y="125" Width="220" Height="60" Transparent="yes">
|
<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>
|
<Text><![CDATA[WinFsp (<a href="https://winfsp.dev/">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>
|
</Control>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
\vieww12000\viewh15840\viewkind0
|
\vieww12000\viewh15840\viewkind0
|
||||||
\pard\tx283\tx567\tx850\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
|
\pard\tx283\tx567\tx850\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
|
\par
|
||||||
\b\'a9 2016 \'96 2022 Skymatic GmbH \b0\par
|
\b\'a9 2016 \'96 2023 Skymatic GmbH \b0\par
|
||||||
\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
|
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
|
\par
|
||||||
|
|||||||
Vendored
+24
-15
@@ -23,6 +23,11 @@
|
|||||||
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
|
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
|
||||||
<?endif?>
|
<?endif?>
|
||||||
|
|
||||||
|
<!-- Cryptomator defaults -->
|
||||||
|
<?define IconFileEncryptedData= "Cryptomator-Vault.ico" ?>
|
||||||
|
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
|
||||||
|
<?define CloseApplicationTarget= "cryptomator.exe" ?>
|
||||||
|
|
||||||
<?include $(var.JpConfigDir)/overrides.wxi ?>
|
<?include $(var.JpConfigDir)/overrides.wxi ?>
|
||||||
|
|
||||||
<Product
|
<Product
|
||||||
@@ -49,7 +54,7 @@
|
|||||||
Property="JP_UPGRADABLE_FOUND"
|
Property="JP_UPGRADABLE_FOUND"
|
||||||
Maximum="$(var.JpAppVersion)"
|
Maximum="$(var.JpAppVersion)"
|
||||||
MigrateFeatures="yes"
|
MigrateFeatures="yes"
|
||||||
IncludeMaximum="$(var.JpUpgradeVersionOnlyDetectUpgrade)" />
|
IncludeMaximum="yes" /> <!-- TODO: check if this needs to be set to yes-->
|
||||||
<UpgradeVersion
|
<UpgradeVersion
|
||||||
OnlyDetect="$(var.JpUpgradeVersionOnlyDetectDowngrade)"
|
OnlyDetect="$(var.JpUpgradeVersionOnlyDetectDowngrade)"
|
||||||
Property="JP_DOWNGRADABLE_FOUND"
|
Property="JP_DOWNGRADABLE_FOUND"
|
||||||
@@ -65,16 +70,16 @@
|
|||||||
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
||||||
<?endif?>
|
<?endif?>
|
||||||
|
|
||||||
|
<?ifndef SkipCryptomatorLegacyCheck ?>
|
||||||
<!-- Looking for legacy Cryptomator versions-->
|
<!-- Block installation if innosetup entry of Cryptomator is found -->
|
||||||
<Property Id="OLDEXEINSTALLER">
|
<Property Id="OLDEXEINSTALLER">
|
||||||
<RegistrySearch Id="InnoSetupInstallation" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Cryptomator_is1" Type="raw" Name="DisplayName" />
|
<RegistrySearch Id="InnoSetupInstallation" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Cryptomator_is1" Type="raw" Name="DisplayName" />
|
||||||
</Property>
|
</Property>
|
||||||
<!-- Block installation if innosetup entry of Cryptomator is found -->
|
<!-- TODO: localize -->
|
||||||
<!-- TODO: localize -->
|
<Condition Message="A lower version of [ProductName] is already installed. Uninstall it first and then start the setup again. Setup will now exit.">
|
||||||
<Condition Message="A lower version of [ProductName] is already installed. Uninstall it first and then start the setup again. Setup will now exit.">
|
<![CDATA[Installed OR NOT OLDEXEINSTALLER]]>
|
||||||
<![CDATA[Installed OR NOT OLDEXEINSTALLER]]>
|
</Condition>
|
||||||
</Condition>
|
<?endif?>
|
||||||
<!-- Cryptomator uses UNIX Sockets, which are supported starting with Windows 10 v1803-->
|
<!-- Cryptomator uses UNIX Sockets, which are supported starting with Windows 10 v1803-->
|
||||||
<Property Id="WINDOWSBUILDNUMBER" Secure="yes">
|
<Property Id="WINDOWSBUILDNUMBER" Secure="yes">
|
||||||
<RegistrySearch Id="BuildNumberSearch" Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion" Name="CurrentBuildNumber" Type="raw" />
|
<RegistrySearch Id="BuildNumberSearch" Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion" Name="CurrentBuildNumber" Type="raw" />
|
||||||
@@ -86,7 +91,7 @@
|
|||||||
<!-- Non-Opening ProgID -->
|
<!-- Non-Opening ProgID -->
|
||||||
<DirectoryRef Id="INSTALLDIR">
|
<DirectoryRef Id="INSTALLDIR">
|
||||||
<Component Win64="yes" Id="nonStartingProgID" >
|
<Component Win64="yes" Id="nonStartingProgID" >
|
||||||
<File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\$(var.IconFileC9rC9s)" Name="$(var.IconFileC9rC9s)"></File>
|
<File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\$(var.IconFileEncryptedData)" Name="$(var.IconFileEncryptedData)"></File>
|
||||||
<ProgId Id="$(var.JpAppName).Encrypted.1" Description="$(var.JpAppName) Encrypted Data" Icon="IconFileForEncryptedData" IconIndex="0">
|
<ProgId Id="$(var.JpAppName).Encrypted.1" Description="$(var.JpAppName) Encrypted Data" Icon="IconFileForEncryptedData" IconIndex="0">
|
||||||
<Extension Id="c9r" Advertise="no" ContentType="$(var.ProgIdContentType)">
|
<Extension Id="c9r" Advertise="no" ContentType="$(var.ProgIdContentType)">
|
||||||
<MIME ContentType="$(var.ProgIdContentType)" Default="yes"></MIME>
|
<MIME ContentType="$(var.ProgIdContentType)" Default="yes"></MIME>
|
||||||
@@ -127,6 +132,9 @@
|
|||||||
<!-- WebDAV patches -->
|
<!-- WebDAV patches -->
|
||||||
<CustomAction Id="PatchWebDAV" Impersonate="no" ExeCommand="[INSTALLDIR]patchWebDAV.bat" Directory="INSTALLDIR" Execute="deferred" Return="asyncWait" />
|
<CustomAction Id="PatchWebDAV" Impersonate="no" ExeCommand="[INSTALLDIR]patchWebDAV.bat" Directory="INSTALLDIR" Execute="deferred" Return="asyncWait" />
|
||||||
|
|
||||||
|
<!-- Special Settings migration for 1.7.0,. Should be removed eventually, for more info, see ../contrib/version170-migrate-settings.ps1-->
|
||||||
|
<CustomAction Id="V170MigrateSettings" Impersonate="no" ExeCommand="[INSTALLDIR]version170-migrate-settings.bat" Directory="INSTALLDIR" Execute="deferred" Return="asyncWait" />
|
||||||
|
|
||||||
<!-- Running App detection and exit -->
|
<!-- Running App detection and exit -->
|
||||||
<Property Id="FOUNDRUNNINGAPP" Admin="yes"/>
|
<Property Id="FOUNDRUNNINGAPP" Admin="yes"/>
|
||||||
<util:CloseApplication
|
<util:CloseApplication
|
||||||
@@ -135,11 +143,11 @@
|
|||||||
CloseMessage="no"
|
CloseMessage="no"
|
||||||
RebootPrompt="no"
|
RebootPrompt="no"
|
||||||
PromptToContinue="yes"
|
PromptToContinue="yes"
|
||||||
Description="A running instance of $(var.JpAppName) is found. Please close it to continue."
|
Description="A running instance of $(var.JpAppName) is found, using files marked for update. Please close it to continue."
|
||||||
Property="FOUNDRUNNINGAPP"
|
Property="FOUNDRUNNINGAPP"
|
||||||
>
|
>
|
||||||
</util:CloseApplication>
|
</util:CloseApplication>
|
||||||
<CustomAction Id="FailOnRunningApp" Impersonate="no" ExeCommand="[SystemFolder]\cmd.exe /c "exit 1"" Directory="INSTALLDIR" Execute="immediate" Return="check" />
|
<CustomAction Id="FailOnRunningApp" Error="Installation aborted, because files marked for update are used by a running instance of $(var.JpAppName)."/>
|
||||||
|
|
||||||
<?ifdef JpIcon ?>
|
<?ifdef JpIcon ?>
|
||||||
<Property Id="ARPPRODUCTICON" Value="JpARPPRODUCTICON"/>
|
<Property Id="ARPPRODUCTICON" Value="JpARPPRODUCTICON"/>
|
||||||
@@ -174,9 +182,10 @@
|
|||||||
<Custom Action="WixCloseApplications" Before="InstallValidate"></Custom>
|
<Custom Action="WixCloseApplications" Before="InstallValidate"></Custom>
|
||||||
<Custom Action="FailOnRunningApp" After="WixCloseApplications" >FOUNDRUNNINGAPP</Custom>
|
<Custom Action="FailOnRunningApp" After="WixCloseApplications" >FOUNDRUNNINGAPP</Custom>
|
||||||
|
|
||||||
<RemoveExistingProducts After="InstallValidate"/>
|
<RemoveExistingProducts After="InstallValidate"/> <!-- Moved from CostInitialize, due to WixCloseApplications -->
|
||||||
|
|
||||||
<Custom Action="PatchWebDAV" After="InstallFiles">NOT Installed OR REINSTALL</Custom>
|
<Custom Action="PatchWebDAV" After="InstallFiles">NOT Installed OR REINSTALL</Custom>
|
||||||
|
<Custom Action="V170MigrateSettings" After="InstallFiles">NOT Installed OR REINSTALL</Custom>
|
||||||
</InstallExecuteSequence>
|
</InstallExecuteSequence>
|
||||||
|
|
||||||
<WixVariable Id="WixUIBannerBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\banner.bmp" />
|
<WixVariable Id="WixUIBannerBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\banner.bmp" />
|
||||||
|
|||||||
Vendored
+17
-6
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Stub by design -->
|
<!-- Stub by design -->
|
||||||
|
|
||||||
<!--
|
<!-- jPackage Section
|
||||||
overrides.wxi is a placeholder to set/alter WiX variables referenced from default
|
overrides.wxi is a placeholder to set/alter WiX variables referenced from default
|
||||||
main.wxs file.
|
main.wxs file.
|
||||||
|
|
||||||
@@ -30,10 +30,21 @@ Should be defined to enable upgrades and undefined to disable upgrades.
|
|||||||
Default value is `yes`.
|
Default value is `yes`.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Non-opening ProgID settings-->
|
<!-- Cryptomator Section
|
||||||
<?define IconFileC9rC9s= "Cryptomator-Vault.ico" ?>
|
|
||||||
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
|
|
||||||
|
|
||||||
<!-- Close Application util -->
|
Non-opening ProgID settings:
|
||||||
<?define CloseApplicationTarget= "cryptomator.exe" ?>
|
- IconFileEncryptedData
|
||||||
|
Full file name of icon file used for encrypted data files. Default is "Cryptomator-Vault.ico"
|
||||||
|
|
||||||
|
- ProgIdContentType
|
||||||
|
Media Type of the encrypted data files. Default is "application/vnd.cryptomator.encrypted"
|
||||||
|
|
||||||
|
Close Application settings:
|
||||||
|
- CloseApplicationTarget
|
||||||
|
Full name of executable to be checkd in the close application util. Default is "cryptomator.exe"
|
||||||
|
|
||||||
|
Legacy Installation settings:
|
||||||
|
- SkipCryptomatorLegacyCheck
|
||||||
|
Should be defined to disable checking for the inno setup installation of Cryptomator and undefined, to enable it.
|
||||||
|
-->
|
||||||
<Include/>
|
<Include/>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptomator</artifactId>
|
<artifactId>cryptomator</artifactId>
|
||||||
<version>1.7.0</version>
|
<version>1.7.0-SNAPSHOT</version>
|
||||||
<name>Cryptomator Desktop App</name>
|
<name>Cryptomator Desktop App</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
@@ -24,19 +24,19 @@
|
|||||||
<project.jdk.version>19</project.jdk.version>
|
<project.jdk.version>19</project.jdk.version>
|
||||||
|
|
||||||
<!-- Group IDs of jars that need to stay on the class path for now -->
|
<!-- Group IDs of jars that need to stay on the class path for now -->
|
||||||
<!-- Check progress on https://github.com/swiesend/secret-service/issues/31 to remove hypfvieh, swiesend and jnr -->
|
<!-- Once hypfvieh, swiesend, purejava and integrations-linux have module-info, remove them-->
|
||||||
<nonModularGroupIds>com.github.jnr,org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
|
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
|
||||||
|
|
||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptolib.version>2.1.0-rc1</cryptomator.cryptolib.version>
|
<cryptomator.cryptolib.version>2.1.1</cryptomator.cryptolib.version>
|
||||||
<cryptomator.cryptofs.version>2.4.5</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>2.6.1</cryptomator.cryptofs.version>
|
||||||
<cryptomator.integrations.version>1.2.0-beta2</cryptomator.integrations.version>
|
<cryptomator.integrations.version>1.2.0-beta4</cryptomator.integrations.version>
|
||||||
<cryptomator.integrations.win.version>1.1.2</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.2.0-beta3</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>1.1.2</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.2.0-beta2</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
|
<cryptomator.integrations.linux.version>1.2.0-beta2</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>2.0.0-beta1</cryptomator.fuse.version>
|
<cryptomator.fuse.version>2.0.0-beta5</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>2.0.0-beta1</cryptomator.dokany.version>
|
<cryptomator.dokany.version>2.0.0-beta2</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>2.0.0-beta1</cryptomator.webdav.version>
|
<cryptomator.webdav.version>2.0.0-beta5</cryptomator.webdav.version>
|
||||||
|
|
||||||
<!-- 3rd party dependencies -->
|
<!-- 3rd party dependencies -->
|
||||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||||
@@ -59,13 +59,14 @@
|
|||||||
|
|
||||||
<!-- build-time dependencies -->
|
<!-- build-time dependencies -->
|
||||||
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
|
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
|
||||||
<dependency-check.version>7.2.1</dependency-check.version>
|
<dependency-check.version>7.4.4</dependency-check.version>
|
||||||
<jacoco.version>0.8.8</jacoco.version>
|
<jacoco.version>0.8.8</jacoco.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Cryptomator Libs -->
|
<!-- Cryptomator Libs -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
<!-- needed due to https://github.com/cryptomator/cryptolib/issues/34-->
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptolib</artifactId>
|
<artifactId>cryptolib</artifactId>
|
||||||
<version>${cryptomator.cryptolib.version}</version>
|
<version>${cryptomator.cryptolib.version}</version>
|
||||||
@@ -373,6 +374,7 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
<executions>
|
<executions>
|
||||||
|
<!-- sort jars into two buckets (classpath and modulepath). exclude openjfx, which gets jlinked separately -->
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-mods</id>
|
<id>copy-mods</id>
|
||||||
<phase>prepare-package</phase>
|
<phase>prepare-package</phase>
|
||||||
@@ -382,7 +384,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<includeScope>runtime</includeScope>
|
<includeScope>runtime</includeScope>
|
||||||
<outputDirectory>${project.build.directory}/mods</outputDirectory>
|
<outputDirectory>${project.build.directory}/mods</outputDirectory>
|
||||||
<excludeGroupIds>${nonModularGroupIds}</excludeGroupIds>
|
<excludeGroupIds>org.openjfx,${nonModularGroupIds}</excludeGroupIds>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.cryptomator.common.settings.SettingsProvider;
|
|||||||
import org.cryptomator.common.vaults.VaultComponent;
|
import org.cryptomator.common.vaults.VaultComponent;
|
||||||
import org.cryptomator.common.vaults.VaultListModule;
|
import org.cryptomator.common.vaults.VaultListModule;
|
||||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
|
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ import java.net.InetSocketAddress;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.SynchronousQueue;
|
import java.util.concurrent.SynchronousQueue;
|
||||||
@@ -83,6 +85,13 @@ public abstract class CommonsModule {
|
|||||||
return new SemVerComparator();
|
return new SemVerComparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static Optional<RevealPathService> provideRevealPathService() {
|
||||||
|
return RevealPathService.get().findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static Settings provideSettings(SettingsProvider settingsProvider) {
|
static Settings provideSettings(SettingsProvider settingsProvider) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class Environment {
|
|||||||
private static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
|
private static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
|
||||||
private static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
|
private static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
|
||||||
private static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
|
private static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
|
||||||
|
private static final String LOOPBACK_ALIAS_PROP_NAME = "cryptomator.loopbackAlias";
|
||||||
private static final String MOUNTPOINT_DIR_PROP_NAME = "cryptomator.mountPointsDir";
|
private static final String MOUNTPOINT_DIR_PROP_NAME = "cryptomator.mountPointsDir";
|
||||||
private static final String MIN_PW_LENGTH_PROP_NAME = "cryptomator.minPwLength";
|
private static final String MIN_PW_LENGTH_PROP_NAME = "cryptomator.minPwLength";
|
||||||
private static final String APP_VERSION_PROP_NAME = "cryptomator.appVersion";
|
private static final String APP_VERSION_PROP_NAME = "cryptomator.appVersion";
|
||||||
@@ -45,6 +46,7 @@ public class Environment {
|
|||||||
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
|
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
|
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
|
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
|
||||||
|
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
|
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
|
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
|
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
|
||||||
@@ -90,6 +92,10 @@ public class Environment {
|
|||||||
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir);
|
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<String> getLoopbackAlias() {
|
||||||
|
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<Path> getPluginDir() {
|
public Optional<Path> getPluginDir() {
|
||||||
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir);
|
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||||
}
|
}
|
||||||
@@ -112,22 +118,13 @@ public class Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getMinPwLength() {
|
public int getMinPwLength() {
|
||||||
return getInt(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean showTrayIcon() {
|
public boolean showTrayIcon() {
|
||||||
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
|
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getInt(String propertyName, int defaultValue) {
|
|
||||||
String value = System.getProperty(propertyName);
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(value);
|
|
||||||
} catch (NumberFormatException e) { // includes "null" values
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Path> getPath(String propertyName) {
|
private Optional<Path> getPath(String propertyName) {
|
||||||
String value = System.getProperty(propertyName);
|
String value = System.getProperty(propertyName);
|
||||||
return Optional.ofNullable(value).map(Paths::get);
|
return Optional.ofNullable(value).map(Paths::get);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ObservableUtil {
|
||||||
|
|
||||||
|
public static <T, U> ObservableValue<U> mapWithDefault(ObservableValue<T> observable, Function<? super T, ? extends U> mapper, U defaultValue) {
|
||||||
|
return Bindings.createObjectBinding(() -> {
|
||||||
|
if (observable.getValue() == null) {
|
||||||
|
return defaultValue;
|
||||||
|
} else {
|
||||||
|
return mapper.apply(observable.getValue());
|
||||||
|
}
|
||||||
|
}, observable);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
|
||||||
|
public record ActualMountService(MountService service, boolean isDesired) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
public class IllegalMountPointException extends IllegalArgumentException {
|
||||||
|
|
||||||
|
public IllegalMountPointException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import org.cryptomator.common.settings.Settings;
|
|||||||
import org.cryptomator.integrations.mount.MountService;
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -18,13 +19,33 @@ public class MountModule {
|
|||||||
return MountService.get().toList();
|
return MountService.get().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//currently not used, because macFUSE and FUSE-T cannot be used in the same JVM
|
||||||
|
/*
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static ObservableValue<MountService> provideMountService(Settings settings, List<MountService> serviceImpls) {
|
static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls) {
|
||||||
return settings.mountService().map(desiredServiceImpl -> {
|
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
|
||||||
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
|
return ObservableUtil.mapWithDefault(settings.mountService(), //
|
||||||
return serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny().orElse(fallbackProvider);
|
desiredServiceImpl -> { //
|
||||||
});
|
var desiredService = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); //
|
||||||
|
return new ActualMountService(desiredService.orElse(fallbackProvider), desiredService.isPresent()); //
|
||||||
|
}, //
|
||||||
|
new ActualMountService(fallbackProvider, true));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static ActualMountService provideActualMountService(Settings settings, List<MountService> serviceImpls) {
|
||||||
|
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
|
||||||
|
var desiredService = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(settings.mountService().getValue())).findFirst(); //
|
||||||
|
return new ActualMountService(desiredService.orElse(fallbackProvider), desiredService.isPresent()); //
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static ObservableValue<ActualMountService> provideMountService(ActualMountService service) {
|
||||||
|
return new SimpleObjectProperty<>(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
public class MountPointNotExistsException extends IllegalMountPointException {
|
||||||
|
|
||||||
|
public MountPointNotExistsException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
public class MountPointNotSupportedException extends IllegalMountPointException {
|
||||||
|
|
||||||
|
public MountPointNotSupportedException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
public class MountPointPreparationException extends RuntimeException {
|
||||||
|
|
||||||
|
public MountPointPreparationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public final class MountWithinParentUtil {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Mounter.class);
|
||||||
|
private static final String HIDEAWAY_PREFIX = ".~$";
|
||||||
|
private static final String HIDEAWAY_SUFFIX = ".tmp";
|
||||||
|
private static final String WIN_HIDDEN_ATTR = "dos:hidden";
|
||||||
|
|
||||||
|
private MountWithinParentUtil() {}
|
||||||
|
|
||||||
|
static void prepareParentNoMountPoint(Path mountPoint) throws MountPointPreparationException {
|
||||||
|
Path hideaway = getHideaway(mountPoint);
|
||||||
|
var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS);
|
||||||
|
var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
|
||||||
|
|
||||||
|
//TODO: possible improvement by just deleting an _empty_ hideaway
|
||||||
|
if (mpExists && hideExists) { //both resources exist (whatever type)
|
||||||
|
throw new MountPointPreparationException(new FileAlreadyExistsException(hideaway.toString()));
|
||||||
|
} else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
|
||||||
|
throw new MountPointPreparationException(new NoSuchFileException(mountPoint.toString()));
|
||||||
|
} else if (!mpExists) { //only hideaway exists
|
||||||
|
checkIsDirectory(hideaway);
|
||||||
|
LOG.info("Mountpoint {} seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
|
||||||
|
try {
|
||||||
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MountPointPreparationException(e);
|
||||||
|
}
|
||||||
|
} else { //only mountpoint exists
|
||||||
|
try {
|
||||||
|
checkIsDirectory(mountPoint);
|
||||||
|
checkIsEmpty(mountPoint);
|
||||||
|
|
||||||
|
Files.move(mountPoint, hideaway);
|
||||||
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MountPointPreparationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup(Path mountPoint) {
|
||||||
|
Path hideaway = getHideaway(mountPoint);
|
||||||
|
try {
|
||||||
|
waitForMountpointRestoration(mountPoint);
|
||||||
|
Files.move(hideaway, mountPoint);
|
||||||
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
Files.setAttribute(mountPoint, WIN_HIDDEN_ATTR, false);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Unable to restore hidden directory to mountpoint {}.", mountPoint, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//on Windows removing the mountpoint takes some time, so we poll for at most 3 seconds
|
||||||
|
private static void waitForMountpointRestoration(Path mountPoint) throws FileAlreadyExistsException {
|
||||||
|
int attempts = 0;
|
||||||
|
while (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
attempts++;
|
||||||
|
if (attempts >= 5) {
|
||||||
|
throw new FileAlreadyExistsException("Timeout waiting for mountpoint cleanup for " + mountPoint + " .");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(300);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new FileAlreadyExistsException("Interrupted before mountpoint " + mountPoint + " was cleared");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkIsDirectory(Path toCheck) throws MountPointPreparationException {
|
||||||
|
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
throw new MountPointPreparationException(new NotDirectoryException(toCheck.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkIsEmpty(Path toCheck) throws MountPointPreparationException, IOException {
|
||||||
|
try (var dirStream = Files.list(toCheck)) {
|
||||||
|
if (dirStream.findFirst().isPresent()) {
|
||||||
|
throw new MountPointPreparationException(new DirectoryNotEmptyException(toCheck.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//visible for testing
|
||||||
|
static Path getHideaway(Path mountPoint) {
|
||||||
|
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.integrations.mount.Mount;
|
||||||
|
import org.cryptomator.integrations.mount.MountBuilder;
|
||||||
|
import org.cryptomator.integrations.mount.MountFailedException;
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_WITHIN_EXISTING_PARENT;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.UNMOUNT_FORCED;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class Mounter {
|
||||||
|
|
||||||
|
private final Settings settings;
|
||||||
|
private final Environment env;
|
||||||
|
private final WindowsDriveLetters driveLetters;
|
||||||
|
private final ObservableValue<ActualMountService> mountServiceObservable;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public Mounter(Settings settings, Environment env, WindowsDriveLetters driveLetters, ObservableValue<ActualMountService> mountServiceObservable) {
|
||||||
|
this.settings = settings;
|
||||||
|
this.env = env;
|
||||||
|
this.driveLetters = driveLetters;
|
||||||
|
this.mountServiceObservable = mountServiceObservable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SettledMounter {
|
||||||
|
|
||||||
|
private MountService service;
|
||||||
|
private MountBuilder builder;
|
||||||
|
private VaultSettings vaultSettings;
|
||||||
|
|
||||||
|
public SettledMounter(MountService service, MountBuilder builder, VaultSettings vaultSettings) {
|
||||||
|
this.service = service;
|
||||||
|
this.builder = builder;
|
||||||
|
this.vaultSettings = vaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
Runnable prepare() throws IOException {
|
||||||
|
for (var capability : service.capabilities()) {
|
||||||
|
switch (capability) {
|
||||||
|
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
|
||||||
|
case LOOPBACK_PORT ->
|
||||||
|
builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
|
||||||
|
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
|
||||||
|
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
|
||||||
|
case MOUNT_FLAGS -> {
|
||||||
|
var mountFlags = vaultSettings.mountFlags().get();
|
||||||
|
if (mountFlags == null || mountFlags.isBlank()) {
|
||||||
|
builder.setMountFlags(service.getDefaultMountFlags());
|
||||||
|
} else {
|
||||||
|
builder.setMountFlags(mountFlags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
|
||||||
|
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prepareMountPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Runnable prepareMountPoint() throws IOException {
|
||||||
|
Runnable cleanup = () -> {};
|
||||||
|
var userChosenMountPoint = vaultSettings.getMountPoint();
|
||||||
|
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
|
||||||
|
var canMountToDriveLetter = service.hasCapability(MOUNT_AS_DRIVE_LETTER);
|
||||||
|
var canMountToParent = service.hasCapability(MOUNT_WITHIN_EXISTING_PARENT);
|
||||||
|
var canMountToDir = service.hasCapability(MOUNT_TO_EXISTING_DIR);
|
||||||
|
var canMountToSystem = service.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH);
|
||||||
|
|
||||||
|
if (userChosenMountPoint == null) {
|
||||||
|
if (canMountToSystem) {
|
||||||
|
// no need to set a mount point
|
||||||
|
} else if (canMountToDriveLetter) {
|
||||||
|
builder.setMountpoint(driveLetters.getFirstDesiredAvailable().orElseThrow()); //TODO: catch exception and translate
|
||||||
|
} else if (canMountToParent) {
|
||||||
|
Files.createDirectories(defaultMountPointBase);
|
||||||
|
builder.setMountpoint(defaultMountPointBase);
|
||||||
|
} else if (canMountToDir) {
|
||||||
|
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
|
||||||
|
Files.createDirectories(mountPoint);
|
||||||
|
builder.setMountpoint(mountPoint);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
|
||||||
|
if (!mpIsDriveLetter && canMountToParent && !canMountToDir) {
|
||||||
|
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
|
||||||
|
cleanup = () -> {
|
||||||
|
MountWithinParentUtil.cleanup(userChosenMountPoint);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
builder.setMountpoint(userChosenMountPoint);
|
||||||
|
} catch (IllegalArgumentException | UnsupportedOperationException e) {
|
||||||
|
var configNotSupported = (!canMountToDriveLetter && mpIsDriveLetter) //mounting as driveletter, albeit not supported
|
||||||
|
|| (!canMountToDir && !mpIsDriveLetter) //mounting to directory, albeit not supported
|
||||||
|
|| (!canMountToParent && !mpIsDriveLetter) //
|
||||||
|
|| (!canMountToDir && !canMountToParent && !canMountToSystem && !canMountToDriveLetter);
|
||||||
|
if (configNotSupported) {
|
||||||
|
throw new MountPointNotSupportedException(e.getMessage());
|
||||||
|
} else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) {
|
||||||
|
//mountpoint must exist
|
||||||
|
throw new MountPointNotExistsException(e.getMessage());
|
||||||
|
} else {
|
||||||
|
//TODO: add specific exception for !canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)
|
||||||
|
throw new IllegalMountPointException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public MountHandle mount(VaultSettings vaultSettings, Path cryptoFsRoot) throws IOException, MountFailedException {
|
||||||
|
var mountService = this.mountServiceObservable.getValue().service();
|
||||||
|
var builder = mountService.forFileSystem(cryptoFsRoot);
|
||||||
|
var internal = new SettledMounter(mountService, builder, vaultSettings);
|
||||||
|
var cleanup = internal.prepare();
|
||||||
|
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record MountHandle(Mount mountObj, boolean supportsUnmountForced, Runnable specialCleanup) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -76,6 +76,11 @@ public class DeviceKey {
|
|||||||
|
|
||||||
private P384KeyPair createAndStoreNewKeyPair(char[] passphrase, Path p12File) throws IOException {
|
private P384KeyPair createAndStoreNewKeyPair(char[] passphrase, Path p12File) throws IOException {
|
||||||
var keyPair = P384KeyPair.generate();
|
var keyPair = P384KeyPair.generate();
|
||||||
|
var tmpFile = p12File.resolveSibling(p12File.getFileName().toString() + ".tmp");
|
||||||
|
if(Files.exists(tmpFile)) {
|
||||||
|
LOG.debug("Leftover from devicekey creation detected. Deleting {}", tmpFile);
|
||||||
|
Files.delete(tmpFile);
|
||||||
|
}
|
||||||
LOG.debug("Store new device key to {}", p12File);
|
LOG.debug("Store new device key to {}", p12File);
|
||||||
keyPair.store(p12File, passphrase);
|
keyPair.store(p12File, passphrase);
|
||||||
return keyPair;
|
return keyPair;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.google.gson.TypeAdapter;
|
|||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonToken;
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -36,6 +37,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
@Override
|
@Override
|
||||||
public void write(JsonWriter out, Settings value) throws IOException {
|
public void write(JsonWriter out, Settings value) throws IOException {
|
||||||
out.beginObject();
|
out.beginObject();
|
||||||
|
out.name("lastWrittenByVersion").value(env.getAppVersion()+env.getBuildNumber().map("-"::concat).orElse(""));
|
||||||
out.name("directories");
|
out.name("directories");
|
||||||
writeVaultSettingsArray(out, value.getDirectories());
|
writeVaultSettingsArray(out, value.getDirectories());
|
||||||
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
||||||
@@ -73,7 +75,9 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
@Override
|
@Override
|
||||||
public Settings read(JsonReader in) throws IOException {
|
public Settings read(JsonReader in) throws IOException {
|
||||||
Settings settings = new Settings(env);
|
Settings settings = new Settings(env);
|
||||||
|
//1.6.x legacy
|
||||||
|
String volumeImpl = null;
|
||||||
|
//legacy end
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
String name = in.nextName();
|
String name = in.nextName();
|
||||||
@@ -105,18 +109,49 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
settings.mountService().set(in.nextString());
|
settings.mountService().set(in.nextString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//1.6.x legacy
|
||||||
|
case "preferredVolumeImpl" -> volumeImpl = in.nextString();
|
||||||
|
//legacy end
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
in.endObject();
|
in.endObject();
|
||||||
|
|
||||||
|
//1.6.x legacy
|
||||||
|
if (volumeImpl != null) {
|
||||||
|
settings.mountService().set(convertLegacyVolumeImplToMountService(volumeImpl));
|
||||||
|
}
|
||||||
|
//legacy end
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String convertLegacyVolumeImplToMountService(String volumeImpl) {
|
||||||
|
if (volumeImpl.equals("Dokany")) {
|
||||||
|
return "org.cryptomator.frontend.dokany.mount.DokanyMountProvider";
|
||||||
|
} else if (volumeImpl.equals("FUSE")) {
|
||||||
|
if(SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
return "org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider";
|
||||||
|
} else if (SystemUtils.IS_OS_MAC) {
|
||||||
|
return "org.cryptomator.frontend.fuse.mount.MacFuseMountProvider";
|
||||||
|
} else {
|
||||||
|
return "org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
return "org.cryptomator.frontend.webdav.mount.WindowsMounter";
|
||||||
|
} else if (SystemUtils.IS_OS_MAC) {
|
||||||
|
return "org.cryptomator.frontend.webdav.mount.MacAppleScriptMounter";
|
||||||
|
} else {
|
||||||
|
return "org.cryptomator.frontend.webdav.mount.LinuxGioMounter";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private UiTheme parseUiTheme(String uiThemeName) {
|
private UiTheme parseUiTheme(String uiThemeName) {
|
||||||
try {
|
try {
|
||||||
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class VaultSettings {
|
|||||||
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
||||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
|
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
|
||||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS); //TODO: remove empty default mount flags and let this property be null if not used
|
||||||
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
||||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||||
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
|
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: usesCustomMountPath, customMountPath and winDriveLetter removed
|
|
||||||
// -> migration required
|
|
||||||
public VaultSettings read(JsonReader in) throws IOException {
|
public VaultSettings read(JsonReader in) throws IOException {
|
||||||
String id = null;
|
String id = null;
|
||||||
String path = null;
|
String path = null;
|
||||||
@@ -55,6 +53,12 @@ class VaultSettingsJsonAdapter {
|
|||||||
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
||||||
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
|
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
|
||||||
|
|
||||||
|
//legacy from 1.6.x
|
||||||
|
boolean useCustomMountPath = false;
|
||||||
|
String customMountPath = "";
|
||||||
|
String winDriveLetter = "";
|
||||||
|
//legacy end
|
||||||
|
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
String name = in.nextName();
|
String name = in.nextName();
|
||||||
@@ -78,6 +82,11 @@ class VaultSettingsJsonAdapter {
|
|||||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||||
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
|
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
|
||||||
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
|
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
|
||||||
|
//legacy from 1.6.x
|
||||||
|
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
||||||
|
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
|
||||||
|
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
||||||
|
//legacy end
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
@@ -102,6 +111,13 @@ class VaultSettingsJsonAdapter {
|
|||||||
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
|
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
|
||||||
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
|
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
|
||||||
vaultSettings.mountPoint().set(mountPoint);
|
vaultSettings.mountPoint().set(mountPoint);
|
||||||
|
//legacy from 1.6.x
|
||||||
|
if(useCustomMountPath && !customMountPath.isBlank()) {
|
||||||
|
vaultSettings.mountPoint().set(parseMountPoint(customMountPath));
|
||||||
|
} else if(!winDriveLetter.isBlank() ) {
|
||||||
|
vaultSettings.mountPoint().set(parseMountPoint(winDriveLetter+":\\"));
|
||||||
|
}
|
||||||
|
//legacy end
|
||||||
return vaultSettings;
|
return vaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.cryptomator.common.settings;
|
|
||||||
|
|
||||||
public enum VolumeImpl {
|
|
||||||
WEBDAV("WebDAV"),
|
|
||||||
FUSE("FUSE"),
|
|
||||||
DOKANY("Dokany");
|
|
||||||
|
|
||||||
private String displayName;
|
|
||||||
|
|
||||||
VolumeImpl(String displayName) {
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayName() {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -8,12 +8,10 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.Constants;
|
import org.cryptomator.common.Constants;
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.mount.Mounter;
|
||||||
import org.cryptomator.common.mount.WindowsDriveLetters;
|
import org.cryptomator.common.mount.WindowsDriveLetters;
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
@@ -23,11 +21,7 @@ import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
|||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
import org.cryptomator.integrations.mount.Mount;
|
|
||||||
import org.cryptomator.integrations.mount.MountBuilder;
|
|
||||||
import org.cryptomator.integrations.mount.MountCapability;
|
|
||||||
import org.cryptomator.integrations.mount.MountFailedException;
|
import org.cryptomator.integrations.mount.MountFailedException;
|
||||||
import org.cryptomator.integrations.mount.MountService;
|
|
||||||
import org.cryptomator.integrations.mount.Mountpoint;
|
import org.cryptomator.integrations.mount.Mountpoint;
|
||||||
import org.cryptomator.integrations.mount.UnmountFailedException;
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -44,9 +38,7 @@ import javafx.beans.property.BooleanProperty;
|
|||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
@@ -54,11 +46,6 @@ import java.util.Objects;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
|
|
||||||
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
|
|
||||||
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH;
|
|
||||||
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_WITHIN_EXISTING_PARENT;
|
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
public class Vault {
|
public class Vault {
|
||||||
|
|
||||||
@@ -66,14 +53,10 @@ public class Vault {
|
|||||||
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
||||||
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
|
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
|
||||||
|
|
||||||
private final Environment env;
|
|
||||||
private final Settings settings;
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||||
private final VaultState state;
|
private final VaultState state;
|
||||||
private final ObjectProperty<Exception> lastKnownException;
|
private final ObjectProperty<Exception> lastKnownException;
|
||||||
private final ObservableValue<MountService> mountService;
|
|
||||||
private final ObservableValue<String> defaultMountFlags;
|
|
||||||
private final VaultConfigCache configCache;
|
private final VaultConfigCache configCache;
|
||||||
private final VaultStats stats;
|
private final VaultStats stats;
|
||||||
private final StringBinding displayablePath;
|
private final StringBinding displayablePath;
|
||||||
@@ -84,22 +67,18 @@ public class Vault {
|
|||||||
private final BooleanBinding needsMigration;
|
private final BooleanBinding needsMigration;
|
||||||
private final BooleanBinding unknownError;
|
private final BooleanBinding unknownError;
|
||||||
private final ObjectBinding<Mountpoint> mountPoint;
|
private final ObjectBinding<Mountpoint> mountPoint;
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
private final Mounter mounter;
|
||||||
private final BooleanProperty showingStats;
|
private final BooleanProperty showingStats;
|
||||||
|
|
||||||
private AtomicReference<MountHandle> mountHandle = new AtomicReference<>(null);
|
private AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Vault(Environment env, Settings settings, VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, ObservableValue<MountService> mountService, VaultStats stats, WindowsDriveLetters windowsDriveLetters) {
|
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats, WindowsDriveLetters windowsDriveLetters, Mounter mounter) {
|
||||||
this.env = env;
|
|
||||||
this.settings = settings;
|
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.configCache = configCache;
|
this.configCache = configCache;
|
||||||
this.cryptoFileSystem = cryptoFileSystem;
|
this.cryptoFileSystem = cryptoFileSystem;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.lastKnownException = lastKnownException;
|
this.lastKnownException = lastKnownException;
|
||||||
this.mountService = mountService;
|
|
||||||
this.defaultMountFlags = mountService.map(MountService::getDefaultMountFlags);
|
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||||
@@ -109,7 +88,7 @@ public class Vault {
|
|||||||
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
||||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
this.mounter = mounter;
|
||||||
this.showingStats = new SimpleBooleanProperty(false);
|
this.showingStats = new SimpleBooleanProperty(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,45 +138,6 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MountBuilder prepareMount(Path cryptoRoot) throws IOException {
|
|
||||||
var mountProvider = mountService.getValue();
|
|
||||||
var builder = mountProvider.forFileSystem(cryptoRoot);
|
|
||||||
|
|
||||||
for (var capability : mountProvider.capabilities()) {
|
|
||||||
switch (capability) {
|
|
||||||
case FILE_SYSTEM_NAME -> builder.setFileSystemName("crypto");
|
|
||||||
case LOOPBACK_PORT -> builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings?
|
|
||||||
case LOOPBACK_HOST_NAME -> builder.setLoopbackHostName("cryptomator-vault"); //TODO: Read from system property
|
|
||||||
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
|
|
||||||
case MOUNT_FLAGS -> builder.setMountFlags(defaultMountFlags.getValue()); // TODO use custom mount flags (pre-populated with default mount flags)
|
|
||||||
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
|
|
||||||
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var userChosenMountPoint = vaultSettings.getMountPoint();
|
|
||||||
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
|
|
||||||
if (userChosenMountPoint == null) {
|
|
||||||
if (mountProvider.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
|
|
||||||
// no need to set a mount point
|
|
||||||
} else if (mountProvider.hasCapability(MOUNT_AS_DRIVE_LETTER)) {
|
|
||||||
builder.setMountpoint(windowsDriveLetters.getFirstDesiredAvailable().orElseThrow());
|
|
||||||
} else if (mountProvider.hasCapability(MOUNT_WITHIN_EXISTING_PARENT)) {
|
|
||||||
Files.createDirectories(defaultMountPointBase);
|
|
||||||
builder.setMountpoint(defaultMountPointBase);
|
|
||||||
} else if (mountProvider.hasCapability(MOUNT_TO_EXISTING_DIR) ) {
|
|
||||||
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
|
|
||||||
Files.createDirectories(mountPoint);
|
|
||||||
builder.setMountpoint(mountPoint);
|
|
||||||
}
|
|
||||||
} else if (mountProvider.hasCapability(MOUNT_TO_EXISTING_DIR) || mountProvider.hasCapability(MOUNT_WITHIN_EXISTING_PARENT) || mountProvider.hasCapability(MOUNT_AS_DRIVE_LETTER)) {
|
|
||||||
// TODO: move the mount point away in case of MOUNT_WITHIN_EXISTING_PARENT?
|
|
||||||
builder.setMountpoint(userChosenMountPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, MountFailedException {
|
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, MountFailedException {
|
||||||
if (cryptoFileSystem.get() != null) {
|
if (cryptoFileSystem.get() != null) {
|
||||||
throw new IllegalStateException("Already unlocked.");
|
throw new IllegalStateException("Already unlocked.");
|
||||||
@@ -207,8 +147,7 @@ public class Vault {
|
|||||||
try {
|
try {
|
||||||
cryptoFileSystem.set(fs);
|
cryptoFileSystem.set(fs);
|
||||||
var rootPath = fs.getRootDirectories().iterator().next();
|
var rootPath = fs.getRootDirectories().iterator().next();
|
||||||
var supportsForcedUnmount = mountService.getValue().hasCapability(MountCapability.UNMOUNT_FORCED);
|
var mountHandle = mounter.mount(vaultSettings, rootPath);
|
||||||
var mountHandle = new MountHandle(prepareMount(rootPath).mount(), supportsForcedUnmount);
|
|
||||||
success = this.mountHandle.compareAndSet(null, mountHandle);
|
success = this.mountHandle.compareAndSet(null, mountHandle);
|
||||||
} finally {
|
} finally {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -217,7 +156,6 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public synchronized void lock(boolean forced) throws UnmountFailedException, IOException {
|
public synchronized void lock(boolean forced) throws UnmountFailedException, IOException {
|
||||||
var mountHandle = this.mountHandle.get();
|
var mountHandle = this.mountHandle.get();
|
||||||
if (mountHandle == null) {
|
if (mountHandle == null) {
|
||||||
@@ -225,14 +163,15 @@ public class Vault {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forced && mountHandle.supportsUnmountForced) {
|
if (forced && mountHandle.supportsUnmountForced()) {
|
||||||
mountHandle.mount.unmountForced();
|
mountHandle.mountObj().unmountForced();
|
||||||
} else {
|
} else {
|
||||||
mountHandle.mount.unmount();
|
mountHandle.mountObj().unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mountHandle.mount.close();
|
mountHandle.mountObj().close();
|
||||||
|
mountHandle.specialCleanup().run();
|
||||||
} finally {
|
} finally {
|
||||||
destroyCryptoFileSystem();
|
destroyCryptoFileSystem();
|
||||||
}
|
}
|
||||||
@@ -327,7 +266,7 @@ public class Vault {
|
|||||||
|
|
||||||
public Mountpoint getMountPoint() {
|
public Mountpoint getMountPoint() {
|
||||||
var handle = mountHandle.get();
|
var handle = mountHandle.get();
|
||||||
return handle == null ? null : handle.mount.getMountpoint();
|
return handle == null ? null : handle.mountObj().getMountpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding displayablePathProperty() {
|
public StringBinding displayablePathProperty() {
|
||||||
@@ -375,35 +314,26 @@ public class Vault {
|
|||||||
return vaultSettings.path().getValue();
|
return vaultSettings.path().getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHavingCustomMountFlags() {
|
/**
|
||||||
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
|
* Gets from the cleartext path its ciphertext counterpart.
|
||||||
}
|
* The cleartext path has to start from the vault root (by starting with "/").
|
||||||
|
*
|
||||||
public ObservableValue<String> defaultMountFlagsProperty() {
|
* @return Local os path to the ciphertext resource
|
||||||
return defaultMountFlags;
|
* @throws IOException if an I/O error occurs
|
||||||
}
|
*/
|
||||||
|
public Path getCiphertextPath(String cleartextPath) throws IOException {
|
||||||
public String getDefaultMountFlags() {
|
if (!cleartextPath.startsWith("/")) {
|
||||||
return defaultMountFlags.getValue();
|
throw new IllegalArgumentException("Input path must be absolute from vault root by starting with \"/\".");
|
||||||
}
|
|
||||||
|
|
||||||
public String getEffectiveMountFlags() {
|
|
||||||
String mountFlags = vaultSettings.mountFlags().get();
|
|
||||||
if (Strings.isNullOrEmpty(mountFlags)) {
|
|
||||||
return ""; //TODO: should the provider provide dem defaults??
|
|
||||||
} else {
|
|
||||||
return mountFlags;
|
|
||||||
}
|
}
|
||||||
|
var fs = cryptoFileSystem.get();
|
||||||
|
var cryptoPath = fs.getPath(cleartextPath);
|
||||||
|
return fs.getCiphertextPath(cryptoPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultConfigCache getVaultConfigCache() {
|
public VaultConfigCache getVaultConfigCache() {
|
||||||
return configCache;
|
return configCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCustomMountFlags(String mountFlags) {
|
|
||||||
vaultSettings.mountFlags().set(mountFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return vaultSettings.getId();
|
return vaultSettings.getId();
|
||||||
}
|
}
|
||||||
@@ -426,15 +356,12 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* TODO: reactivate/ needed at all?
|
|
||||||
public boolean supportsForcedUnmount() {
|
public boolean supportsForcedUnmount() {
|
||||||
return volume.supportsForcedUnmount();
|
var mh = mountHandle.get();
|
||||||
|
if (mh == null) {
|
||||||
|
throw new IllegalStateException("Vault is not mounted");
|
||||||
|
}
|
||||||
|
return mountHandle.get().supportsUnmountForced();
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
private record MountHandle(Mount mount, boolean supportsUnmountForced) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,8 @@ public class VaultStats {
|
|||||||
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
||||||
private final LongProperty filesRead = new SimpleLongProperty();
|
private final LongProperty filesRead = new SimpleLongProperty();
|
||||||
private final LongProperty filesWritten = new SimpleLongProperty();
|
private final LongProperty filesWritten = new SimpleLongProperty();
|
||||||
|
private final LongProperty filesAccessed = new SimpleLongProperty();
|
||||||
|
private final LongProperty totalFilesAccessed = new SimpleLongProperty();
|
||||||
private final ObjectProperty<Instant> lastActivity = new SimpleObjectProperty<>();
|
private final ObjectProperty<Instant> lastActivity = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -82,6 +84,8 @@ public class VaultStats {
|
|||||||
var oldAccessCount = filesRead.get() + filesWritten.get();
|
var oldAccessCount = filesRead.get() + filesWritten.get();
|
||||||
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
||||||
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
||||||
|
filesAccessed.set(stats.map(CryptoFileSystemStats::pollAmountOfAccesses).orElse(0L));
|
||||||
|
totalFilesAccessed.set(stats.map(CryptoFileSystemStats::pollTotalAmountOfAccesses).orElse(0L));
|
||||||
var newAccessCount = filesRead.get() + filesWritten.get();
|
var newAccessCount = filesRead.get() + filesWritten.get();
|
||||||
|
|
||||||
// check for any I/O activity
|
// check for any I/O activity
|
||||||
@@ -188,6 +192,19 @@ public class VaultStats {
|
|||||||
|
|
||||||
public long getFilesWritten() {return filesWritten.get();}
|
public long getFilesWritten() {return filesWritten.get();}
|
||||||
|
|
||||||
|
public LongProperty filesAccessed() {
|
||||||
|
return filesAccessed;}
|
||||||
|
|
||||||
|
public long getFilesAccessed() {return filesAccessed.get();}
|
||||||
|
|
||||||
|
public LongProperty totalFilesAccessed(){
|
||||||
|
return totalFilesAccessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalFilesAccessed(){
|
||||||
|
return totalFilesAccessed.get();
|
||||||
|
}
|
||||||
|
|
||||||
public ObjectProperty<Instant> lastActivityProperty() {
|
public ObjectProperty<Instant> lastActivityProperty() {
|
||||||
return lastActivity;
|
return lastActivity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ public class LogbackConfigurator extends ContextAwareBase implements Configurato
|
|||||||
upgrades.addAppender(upgrade);
|
upgrades.addAppender(upgrade);
|
||||||
upgrades.addAppender(file);
|
upgrades.addAppender(file);
|
||||||
upgrades.setAdditive(false);
|
upgrades.setAdditive(false);
|
||||||
|
|
||||||
|
// configure fuse file locking logger:
|
||||||
|
Logger fuseLocking = context.getLogger("org.cryptomator.frontend.fuse.locks");
|
||||||
|
fuseLocking.setLevel(Level.OFF);
|
||||||
}
|
}
|
||||||
return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
|
return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-12
@@ -2,19 +2,20 @@ package org.cryptomator.ui.addvaultwizard;
|
|||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.common.settings.UiTheme;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.integrations.uiappearance.Theme;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationStyle;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
@@ -23,6 +24,7 @@ import javafx.stage.Stage;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
||||||
@@ -40,12 +42,10 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
private final ObjectProperty<Vault> vault;
|
private final ObjectProperty<Vault> vault;
|
||||||
private final VaultListManager vaultListManager;
|
private final VaultListManager vaultListManager;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final Settings settings;
|
private final ObservableValue<Image> screenshot;
|
||||||
|
|
||||||
private Image screenshot;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, Settings settings) {
|
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, FxApplicationStyle applicationStyle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.welcomeScene = welcomeScene;
|
this.welcomeScene = welcomeScene;
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
@@ -54,16 +54,20 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.vaultListManager = vaultListManager;
|
this.vaultListManager = vaultListManager;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.settings = settings;
|
this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
private Image selectScreenshot(Theme theme) {
|
||||||
public void initialize() {
|
String imageResourcePath;
|
||||||
if (SystemUtils.IS_OS_MAC) {
|
if (SystemUtils.IS_OS_MAC) {
|
||||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-mac"+(UiTheme.LIGHT == settings.theme().get()? "":"-dark")+".png").toString());
|
imageResourcePath = switch (theme) {
|
||||||
|
case LIGHT -> "/img/select-masterkey-mac.png";
|
||||||
|
case DARK -> "/img/select-masterkey-mac-dark.png";
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-win.png").toString());
|
imageResourcePath = "/img/select-masterkey-win.png";
|
||||||
}
|
}
|
||||||
|
return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -92,8 +96,13 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
|
|
||||||
/* Getter */
|
/* Getter */
|
||||||
|
|
||||||
public Image getScreenshot() {
|
public ObservableValue<Image> screenshotProperty() {
|
||||||
return screenshot;
|
return screenshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Image getScreenshot() {
|
||||||
|
return screenshot.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -176,7 +176,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
// 2. initialize vault:
|
// 2. initialize vault:
|
||||||
try {
|
try {
|
||||||
MasterkeyLoader loader = ignored -> masterkey.copy();
|
MasterkeyLoader loader = ignored -> masterkey.copy();
|
||||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).withKeyLoader(loader).build();
|
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_GCM).withKeyLoader(loader).build();
|
||||||
CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID);
|
CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID);
|
||||||
|
|
||||||
// 3. write vault-internal readme file:
|
// 3. write vault-internal readme file:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public enum FxmlFile {
|
|||||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||||
HEALTH_START("/fxml/health_start.fxml"), //
|
HEALTH_START("/fxml/health_start.fxml"), //
|
||||||
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
|
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
|
||||||
|
HUB_NO_KEYCHAIN("/fxml/hub_no_keychain.fxml"), //
|
||||||
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
|
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
|
||||||
HUB_LICENSE_EXCEEDED("/fxml/hub_license_exceeded.fxml"), //
|
HUB_LICENSE_EXCEEDED("/fxml/hub_license_exceeded.fxml"), //
|
||||||
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
|
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ public enum FontAwesome5Icon {
|
|||||||
EYE_SLASH("\uF070"), //
|
EYE_SLASH("\uF070"), //
|
||||||
FAST_FORWARD("\uF050"), //
|
FAST_FORWARD("\uF050"), //
|
||||||
FILE("\uF15B"), //
|
FILE("\uF15B"), //
|
||||||
|
FILE_DOWNLOAD("\uF56D"), //
|
||||||
FILE_IMPORT("\uF56F"), //
|
FILE_IMPORT("\uF56F"), //
|
||||||
FOLDER_OPEN("\uF07C"), //
|
FOLDER_OPEN("\uF07C"), //
|
||||||
|
FUNNEL("\uF0B0"), //
|
||||||
HAND_HOLDING_HEART("\uF4BE"), //
|
HAND_HOLDING_HEART("\uF4BE"), //
|
||||||
HEART("\uF004"), //
|
HEART("\uF004"), //
|
||||||
HDD("\uF0A0"), //
|
HDD("\uF0A0"), //
|
||||||
@@ -40,6 +42,7 @@ public enum FontAwesome5Icon {
|
|||||||
PLUS("\uF067"), //
|
PLUS("\uF067"), //
|
||||||
PRINT("\uF02F"), //
|
PRINT("\uF02F"), //
|
||||||
QUESTION("\uF128"), //
|
QUESTION("\uF128"), //
|
||||||
|
QUESTION_CIRCLE("\uf059"), //
|
||||||
REDO("\uF01E"), //
|
REDO("\uF01E"), //
|
||||||
SEARCH("\uF002"), //
|
SEARCH("\uF002"), //
|
||||||
SPINNER("\uF110"), //
|
SPINNER("\uF110"), //
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -24,9 +26,10 @@ public class FxApplicationStyle {
|
|||||||
private final Optional<UiAppearanceProvider> appearanceProvider;
|
private final Optional<UiAppearanceProvider> appearanceProvider;
|
||||||
private final LicenseHolder licenseHolder;
|
private final LicenseHolder licenseHolder;
|
||||||
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
|
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
|
||||||
|
private final ObjectProperty<Theme> appliedTheme = new SimpleObjectProperty<>(Theme.LIGHT);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder){
|
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.appearanceProvider = appearanceProvider;
|
this.appearanceProvider = appearanceProvider;
|
||||||
this.licenseHolder = licenseHolder;
|
this.licenseHolder = licenseHolder;
|
||||||
@@ -91,6 +94,7 @@ public class FxApplicationStyle {
|
|||||||
} else {
|
} else {
|
||||||
Application.setUserAgentStylesheet(stylesheet.toString());
|
Application.setUserAgentStylesheet(stylesheet.toString());
|
||||||
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
|
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
|
||||||
|
appliedTheme.set(Theme.LIGHT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +107,11 @@ public class FxApplicationStyle {
|
|||||||
} else {
|
} else {
|
||||||
Application.setUserAgentStylesheet(stylesheet.toString());
|
Application.setUserAgentStylesheet(stylesheet.toString());
|
||||||
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
|
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
|
||||||
|
appliedTheme.set(Theme.DARK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Theme> appliedThemeProperty() {
|
||||||
|
return appliedTheme;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,34 @@ import org.cryptomator.ui.common.FxController;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.binding.Binding;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.binding.BooleanExpression;
|
import javafx.beans.binding.BooleanExpression;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.ChoiceBox;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.cryptomator.cryptofs.health.api.DiagnosticResult.Severity;
|
||||||
|
import static org.cryptomator.ui.health.Result.FixState.FIXABLE;
|
||||||
|
import static org.cryptomator.ui.health.Result.FixState.FIXED;
|
||||||
|
import static org.cryptomator.ui.health.Result.FixState.FIXING;
|
||||||
|
import static org.cryptomator.ui.health.Result.FixState.FIX_FAILED;
|
||||||
|
import static org.cryptomator.ui.health.Result.FixState.NOT_FIXABLE;
|
||||||
|
|
||||||
@HealthCheckScoped
|
@HealthCheckScoped
|
||||||
public class CheckDetailController implements FxController {
|
public class CheckDetailController implements FxController {
|
||||||
|
|
||||||
@@ -35,30 +54,48 @@ public class CheckDetailController implements FxController {
|
|||||||
private final Binding<Number> countOfCritSeverity;
|
private final Binding<Number> countOfCritSeverity;
|
||||||
private final Binding<Boolean> warnOrCritsExist;
|
private final Binding<Boolean> warnOrCritsExist;
|
||||||
private final ResultListCellFactory resultListCellFactory;
|
private final ResultListCellFactory resultListCellFactory;
|
||||||
|
private final ResultFixApplier resultFixApplier;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
|
private final BooleanProperty fixAllInfoResultsExecuted;
|
||||||
|
private final BooleanBinding fixAllInfoResultsPossible;
|
||||||
|
private final ObjectProperty<Predicate<Result>> resultsFilter;
|
||||||
|
|
||||||
public ListView<Result> resultsListView;
|
public ListView<Result> resultsListView;
|
||||||
|
public ChoiceBox<DiagnosticResult.Severity> severityChoiceBox;
|
||||||
|
public ChoiceBox<Result.FixState> fixStateChoiceBox;
|
||||||
private Subscription resultSubscription;
|
private Subscription resultSubscription;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory) {
|
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory, ResultFixApplier resultFixApplier, ResourceBundle resourceBundle) {
|
||||||
this.resultListCellFactory = resultListCellFactory;
|
this.resultListCellFactory = resultListCellFactory;
|
||||||
|
this.resultFixApplier = resultFixApplier;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
|
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
|
||||||
this.check = selectedTask;
|
this.check = selectedTask;
|
||||||
this.checkState = selectedTask.flatMap(Check::stateProperty);
|
this.checkState = selectedTask.flatMap(Check::stateProperty);
|
||||||
this.checkName = selectedTask.map(Check::getName).orElse("");
|
this.checkName = selectedTask.map(Check::getName).orElse("");
|
||||||
this.checkRunning = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.RUNNING::equals).orElse(false));
|
this.checkRunning = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.RUNNING::equals).orElse(false));
|
||||||
this.checkScheduled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false));
|
this.checkScheduled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false));
|
||||||
this.checkSkipped =BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SKIPPED::equals).orElse(false));
|
this.checkSkipped = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SKIPPED::equals).orElse(false));
|
||||||
this.checkSucceeded = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false));
|
this.checkSucceeded = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false));
|
||||||
this.checkFailed = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.ERROR::equals).orElse(false));
|
this.checkFailed = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.ERROR::equals).orElse(false));
|
||||||
this.checkCancelled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.CANCELLED::equals).orElse(false));
|
this.checkCancelled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.CANCELLED::equals).orElse(false));
|
||||||
this.checkFinished = checkSucceeded.or(checkFailed).or(checkCancelled);
|
this.checkFinished = checkSucceeded.or(checkFailed).or(checkCancelled);
|
||||||
this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN));
|
this.countOfWarnSeverity = results.reduce(countSeverity(Severity.WARN));
|
||||||
this.countOfCritSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.CRITICAL));
|
this.countOfCritSeverity = results.reduce(countSeverity(Severity.CRITICAL));
|
||||||
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0) );
|
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0));
|
||||||
|
this.fixAllInfoResultsExecuted = new SimpleBooleanProperty(false);
|
||||||
|
this.fixAllInfoResultsPossible = Bindings.createBooleanBinding(() -> results.stream().anyMatch(this::isFixableInfoResult), results) //
|
||||||
|
.and(fixAllInfoResultsExecuted.not());
|
||||||
|
this.resultsFilter = new SimpleObjectProperty<>(r -> true);
|
||||||
selectedTask.addListener(this::selectedTaskChanged);
|
selectedTask.addListener(this::selectedTaskChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isFixableInfoResult(Result r) {
|
||||||
|
return r.diagnosis().getSeverity() == Severity.INFO && r.getState() == FIXABLE;
|
||||||
|
}
|
||||||
|
|
||||||
private void selectedTaskChanged(ObservableValue<? extends Check> observable, Check oldValue, Check newValue) {
|
private void selectedTaskChanged(ObservableValue<? extends Check> observable, Check oldValue, Check newValue) {
|
||||||
if (resultSubscription != null) {
|
if (resultSubscription != null) {
|
||||||
resultSubscription.unsubscribe();
|
resultSubscription.unsubscribe();
|
||||||
@@ -66,6 +103,8 @@ public class CheckDetailController implements FxController {
|
|||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
resultSubscription = EasyBind.bindContent(results, newValue.getResults());
|
resultSubscription = EasyBind.bindContent(results, newValue.getResults());
|
||||||
}
|
}
|
||||||
|
severityChoiceBox.setValue(null);
|
||||||
|
fixStateChoiceBox.setValue(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
|
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
|
||||||
@@ -74,8 +113,110 @@ public class CheckDetailController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
resultsListView.setItems(results);
|
resultsListView.setItems(results.filtered(resultsFilter));
|
||||||
resultsListView.setCellFactory(resultListCellFactory);
|
resultsListView.setCellFactory(resultListCellFactory);
|
||||||
|
|
||||||
|
severityChoiceBox.getItems().add(null);
|
||||||
|
severityChoiceBox.getItems().addAll(Arrays.stream(DiagnosticResult.Severity.values()).toList());
|
||||||
|
severityChoiceBox.setConverter(new SeverityStringifier());
|
||||||
|
severityChoiceBox.setValue(null);
|
||||||
|
|
||||||
|
fixStateChoiceBox.getItems().add(null);
|
||||||
|
fixStateChoiceBox.getItems().addAll(Arrays.stream(Result.FixState.values()).toList());
|
||||||
|
fixStateChoiceBox.setConverter(new FixStateStringifier());
|
||||||
|
fixStateChoiceBox.setValue(null);
|
||||||
|
|
||||||
|
resultsFilter.bind(Bindings.createObjectBinding(() -> this::filterResults, severityChoiceBox.valueProperty(), fixStateChoiceBox.valueProperty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filterResults(Result r) {
|
||||||
|
var desiredFixState = fixStateChoiceBox.getValue();
|
||||||
|
var desiredSeverity = severityChoiceBox.getValue();
|
||||||
|
return (desiredFixState == null || r.getState() == desiredFixState) && (desiredSeverity == null || r.diagnosis().getSeverity() == desiredSeverity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void fixAllInfoResults() {
|
||||||
|
fixAllInfoResultsExecuted.setValue(true);
|
||||||
|
results.stream().filter(this::isFixableInfoResult).forEach(resultFixApplier::fix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void copyResultDetails() {
|
||||||
|
var result = resultsListView.getSelectionModel().getSelectedItem();
|
||||||
|
if (result != null) {
|
||||||
|
ClipboardContent clipboardContent = new ClipboardContent();
|
||||||
|
clipboardContent.putString(result.diagnosis().toString());
|
||||||
|
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- Internal classes -- */
|
||||||
|
|
||||||
|
class SeverityStringifier extends StringConverter<Severity> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(Severity object) {
|
||||||
|
if (object == null) {
|
||||||
|
return resourceBundle.getString("health.result.severityFilter.all");
|
||||||
|
}
|
||||||
|
return switch (object) {
|
||||||
|
case GOOD -> resourceBundle.getString("health.result.severityFilter.good");
|
||||||
|
case INFO -> resourceBundle.getString("health.result.severityFilter.info");
|
||||||
|
case WARN -> resourceBundle.getString("health.result.severityFilter.warn");
|
||||||
|
case CRITICAL -> resourceBundle.getString("health.result.severityFilter.crit");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Severity fromString(String string) {
|
||||||
|
if (resourceBundle.getString("health.result.severityFilter.good").equals(string)) {
|
||||||
|
return Severity.GOOD;
|
||||||
|
} else if (resourceBundle.getString("health.result.severityFilter.info").equals(string)) {
|
||||||
|
return Severity.INFO;
|
||||||
|
} else if (resourceBundle.getString("health.result.severityFilter.warn").equals(string)) {
|
||||||
|
return Severity.WARN;
|
||||||
|
} else if (resourceBundle.getString("health.result.severityFilter.crit").equals(string)) {
|
||||||
|
return Severity.CRITICAL;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FixStateStringifier extends StringConverter<Result.FixState> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(Result.FixState object) {
|
||||||
|
if (object == null) {
|
||||||
|
return resourceBundle.getString("health.result.fixStateFilter.all");
|
||||||
|
}
|
||||||
|
return switch (object) {
|
||||||
|
case FIXABLE -> resourceBundle.getString("health.result.fixStateFilter.fixable");
|
||||||
|
case NOT_FIXABLE -> resourceBundle.getString("health.result.fixStateFilter.notFixable");
|
||||||
|
case FIXING -> resourceBundle.getString("health.result.fixStateFilter.fixing");
|
||||||
|
case FIXED -> resourceBundle.getString("health.result.fixStateFilter.fixed");
|
||||||
|
case FIX_FAILED -> resourceBundle.getString("health.result.fixStateFilter.fixFailed");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result.FixState fromString(String string) {
|
||||||
|
if (resourceBundle.getString("health.result.fixStateFilter.fixable").equals(string)) {
|
||||||
|
return FIXABLE;
|
||||||
|
} else if (resourceBundle.getString("health.result.fixStateFilter.notFixable").equals(string)) {
|
||||||
|
return NOT_FIXABLE;
|
||||||
|
} else if (resourceBundle.getString("health.result.fixStateFilter.fixing").equals(string)) {
|
||||||
|
return FIXING;
|
||||||
|
} else if (resourceBundle.getString("health.result.fixStateFilter.fixed").equals(string)) {
|
||||||
|
return FIXED;
|
||||||
|
} else if (resourceBundle.getString("health.result.fixStateFilter.fixFailed").equals(string)) {
|
||||||
|
return FIX_FAILED;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
@@ -175,4 +316,12 @@ public class CheckDetailController implements FxController {
|
|||||||
public Check getCheck() {
|
public Check getCheck() {
|
||||||
return check.get();
|
return check.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> fixAllInfoResultsPossibleProperty() {
|
||||||
|
return fixAllInfoResultsPossible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getFixAllInfoResultsPossible() {
|
||||||
|
return fixAllInfoResultsPossible.getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class CheckExecutor {
|
|||||||
try (var masterkeyClone = masterkey.copy(); //
|
try (var masterkeyClone = masterkey.copy(); //
|
||||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||||
c.getHealthCheck().check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
|
c.getHealthCheck().check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
|
||||||
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis)));
|
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis, vaultPath, vaultConfig, masterkeyClone, cryptor)));
|
||||||
highestResultSeverity = Comparators.max(highestResultSeverity, diagnosis.getSeverity());
|
highestResultSeverity = Comparators.max(highestResultSeverity, diagnosis.getSeverity());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class CheckStateIconView extends FontAwesome5IconView {
|
|||||||
this.severity = EasyBind.wrapNullable(check).mapObservable(Check::highestResultSeverityProperty).asOrdinary();
|
this.severity = EasyBind.wrapNullable(check).mapObservable(Check::highestResultSeverityProperty).asOrdinary();
|
||||||
this.glyph.bind(Bindings.createObjectBinding(this::glyphForState, state, severity));
|
this.glyph.bind(Bindings.createObjectBinding(this::glyphForState, state, severity));
|
||||||
this.subscriptions = List.of( //
|
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-muted", Bindings.equal(state, Check.CheckState.SKIPPED).or(Bindings.equal(state, Check.CheckState.CANCELLED)).or(Bindings.equal(severity, DiagnosticResult.Severity.INFO))), //
|
||||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
|
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-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)) //
|
EasyBind.includeWhen(getStyleClass(), "glyph-icon-red", Bindings.equal(state, Check.CheckState.ERROR)) //
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package org.cryptomator.ui.health;
|
package org.cryptomator.ui.health;
|
||||||
|
|
||||||
|
import org.cryptomator.cryptofs.VaultConfig;
|
||||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||||
|
import org.cryptomator.cryptolib.api.Cryptor;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
|
record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
|
||||||
|
|
||||||
@@ -16,8 +20,8 @@ record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
|
|||||||
FIX_FAILED
|
FIX_FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result create(DiagnosticResult diagnosis) {
|
public static Result create(DiagnosticResult diagnosis, Path vaultPath, VaultConfig config, Masterkey masterkey, Cryptor cryptor) {
|
||||||
FixState initialState = diagnosis.getSeverity() == DiagnosticResult.Severity.WARN ? FixState.FIXABLE : FixState.NOT_FIXABLE;
|
FixState initialState = diagnosis.getFix(vaultPath, config, masterkey, cryptor).map( _f -> FixState.FIXABLE).orElse(FixState.NOT_FIXABLE);
|
||||||
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
|
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
@HealthCheckScoped
|
@HealthCheckScoped
|
||||||
class ResultFixApplier {
|
class ResultFixApplier {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ResultFixApplier.class);
|
||||||
|
|
||||||
private final Path vaultPath;
|
private final Path vaultPath;
|
||||||
private final SecureRandom csprng;
|
private final SecureRandom csprng;
|
||||||
private final Masterkey masterkey;
|
private final Masterkey masterkey;
|
||||||
@@ -40,25 +42,34 @@ class ResultFixApplier {
|
|||||||
|
|
||||||
public CompletionStage<Void> fix(Result result) {
|
public CompletionStage<Void> fix(Result result) {
|
||||||
Preconditions.checkArgument(result.getState() == Result.FixState.FIXABLE);
|
Preconditions.checkArgument(result.getState() == Result.FixState.FIXABLE);
|
||||||
result.setState(Result.FixState.FIXING);
|
return CompletableFuture.runAsync(() -> result.setState(Result.FixState.FIXING), Platform::runLater) //
|
||||||
return CompletableFuture.runAsync(() -> fix(result.diagnosis()), sequentialExecutor)
|
.thenRunAsync(() -> fix(result.diagnosis()), sequentialExecutor) //
|
||||||
.whenCompleteAsync((unused, throwable) -> {
|
.whenCompleteAsync((unused, throwable) -> {
|
||||||
var fixed = throwable == null ? Result.FixState.FIXED : Result.FixState.FIX_FAILED;
|
final Result.FixState s;
|
||||||
result.setState(fixed);
|
if (throwable == null) {
|
||||||
|
LOG.debug("Fix for {} applied successful.", result.diagnosis().getClass().getName());
|
||||||
|
s = Result.FixState.FIXED;
|
||||||
|
} else {
|
||||||
|
LOG.error("Failed to apply fix for {}", result.diagnosis().getClass().getName(), throwable);
|
||||||
|
s = Result.FixState.FIX_FAILED;
|
||||||
|
}
|
||||||
|
result.setState(s);
|
||||||
}, Platform::runLater);
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fix(DiagnosticResult diagnosis) {
|
private void fix(DiagnosticResult diagnosis) {
|
||||||
Preconditions.checkArgument(diagnosis.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
|
|
||||||
try (var masterkeyClone = masterkey.copy(); //
|
try (var masterkeyClone = masterkey.copy(); //
|
||||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||||
diagnosis.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
|
diagnosis.getFix(vaultPath, vaultConfig, masterkeyClone, cryptor) //
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No fix for diagnosis " + diagnosis.getClass().getName() + " implemented.")) //
|
||||||
|
.apply();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new FixFailedException(e);
|
throw new FixFailedException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class FixFailedException extends CompletionException {
|
public static class FixFailedException extends CompletionException {
|
||||||
|
|
||||||
private FixFailedException(Throwable cause) {
|
private FixFailedException(Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
@@ -34,7 +33,6 @@ public class ResultListCellController implements FxController {
|
|||||||
private static final FontAwesome5Icon WARN_ICON = FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
private static final FontAwesome5Icon WARN_ICON = FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||||
private static final FontAwesome5Icon CRIT_ICON = FontAwesome5Icon.TIMES;
|
private static final FontAwesome5Icon CRIT_ICON = FontAwesome5Icon.TIMES;
|
||||||
|
|
||||||
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
|
|
||||||
|
|
||||||
private final ObjectProperty<Result> result;
|
private final ObjectProperty<Result> result;
|
||||||
private final ObservableValue<DiagnosticResult.Severity> severity;
|
private final ObservableValue<DiagnosticResult.Severity> severity;
|
||||||
@@ -42,17 +40,17 @@ public class ResultListCellController implements FxController {
|
|||||||
private final ResultFixApplier fixApplier;
|
private final ResultFixApplier fixApplier;
|
||||||
private final ObservableValue<Result.FixState> fixState;
|
private final ObservableValue<Result.FixState> fixState;
|
||||||
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
|
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
|
||||||
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
|
|
||||||
private final BooleanBinding fixable;
|
private final BooleanBinding fixable;
|
||||||
private final BooleanBinding fixing;
|
private final BooleanBinding fixing;
|
||||||
private final BooleanBinding fixed;
|
private final BooleanBinding fixed;
|
||||||
private final BooleanBinding fixFailed;
|
private final BooleanBinding fixFailed;
|
||||||
private final BooleanBinding fixRunningOrDone;
|
private final BooleanBinding fixRunningOrDone;
|
||||||
|
private final ObservableValue<FontAwesome5Icon> fixGlyph;
|
||||||
private final List<Subscription> subscriptions;
|
private final List<Subscription> subscriptions;
|
||||||
private final Tooltip fixSuccess;
|
private final Tooltip fixStateTip;
|
||||||
private final Tooltip fixFail;
|
private final Tooltip severityTip;
|
||||||
|
|
||||||
private AutoAnimator fixRunningRotator;
|
private AutoAnimator fixRunningRotator;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
/* FXML */
|
/* FXML */
|
||||||
public FontAwesome5IconView severityView;
|
public FontAwesome5IconView severityView;
|
||||||
@@ -60,23 +58,54 @@ public class ResultListCellController implements FxController {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
|
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
this.result = new SimpleObjectProperty<>(null);
|
this.result = new SimpleObjectProperty<>(null);
|
||||||
this.severity = result.map(Result::diagnosis).map(DiagnosticResult::getSeverity);
|
this.severity = result.map(Result::diagnosis).map(DiagnosticResult::getSeverity);
|
||||||
this.description = result.map(Result::getDescription).orElse("");
|
this.description = result.map(Result::getDescription).orElse("");
|
||||||
this.fixApplier = fixApplier;
|
this.fixApplier = fixApplier;
|
||||||
this.fixState = result.flatMap(Result::fixState);
|
this.fixState = result.flatMap(Result::fixState);
|
||||||
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
|
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
|
||||||
this.fixGlyph = Bindings.createObjectBinding(this::getFixGlyph, fixState);
|
|
||||||
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
|
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
|
||||||
this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
|
this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
|
||||||
this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
|
this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
|
||||||
this.fixFailed = Bindings.createBooleanBinding(this::isFixFailed, fixState);
|
this.fixFailed = Bindings.createBooleanBinding(this::isFixFailed, fixState);
|
||||||
this.fixRunningOrDone = fixing.or(fixed).or(fixFailed);
|
this.fixRunningOrDone = fixing.or(fixed).or(fixFailed);
|
||||||
|
this.fixGlyph = fixState.map(this::getFixGlyph);
|
||||||
this.subscriptions = new ArrayList<>();
|
this.subscriptions = new ArrayList<>();
|
||||||
this.fixSuccess = new Tooltip(resourceBundle.getString("health.fix.successTip"));
|
|
||||||
this.fixFail = new Tooltip(resourceBundle.getString("health.fix.failTip"));
|
this.fixStateTip = new Tooltip();
|
||||||
fixSuccess.setShowDelay(Duration.millis(100));
|
fixStateTip.textProperty().bind(fixState.map(this::getFixStateDescription));
|
||||||
fixFail.setShowDelay(Duration.millis(100));
|
fixStateTip.setShowDelay(Duration.millis(100));
|
||||||
|
|
||||||
|
this.severityTip = new Tooltip();
|
||||||
|
severityTip.textProperty().bind(severity.map(this::getSeverityDescription));
|
||||||
|
severityTip.setShowDelay(Duration.millis(150));
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontAwesome5Icon getFixGlyph(Result.FixState state) {
|
||||||
|
return switch (state) {
|
||||||
|
case FIXING -> FontAwesome5Icon.SPINNER;
|
||||||
|
case FIXED -> FontAwesome5Icon.CHECK;
|
||||||
|
case FIX_FAILED -> FontAwesome5Icon.TIMES;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFixStateDescription(Result.FixState fixState) {
|
||||||
|
return switch (fixState) {
|
||||||
|
case FIXED -> resourceBundle.getString("health.fix.successTip");
|
||||||
|
case FIX_FAILED -> resourceBundle.getString("health.fix.failTip");
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSeverityDescription(DiagnosticResult.Severity severity) {
|
||||||
|
return resourceBundle.getString(switch (severity) {
|
||||||
|
case GOOD -> "health.result.severityTip.good";
|
||||||
|
case INFO -> "health.result.severityTip.info";
|
||||||
|
case WARN -> "health.result.severityTip.warn";
|
||||||
|
case CRITICAL -> "health.result.severityTip.crit";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -93,22 +122,19 @@ public class ResultListCellController implements FxController {
|
|||||||
.onCondition(fixing) //
|
.onCondition(fixing) //
|
||||||
.afterStop(() -> fixView.setRotate(0)) //
|
.afterStop(() -> fixView.setRotate(0)) //
|
||||||
.build();
|
.build();
|
||||||
|
fixState.addListener(((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue == Result.FixState.FIXED || newValue == Result.FixState.FIX_FAILED) {
|
||||||
|
Tooltip.install(fixView, fixStateTip);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Tooltip.install(severityView, severityTip);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void fix() {
|
public void fix() {
|
||||||
Result r = result.get();
|
Result r = result.get();
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
fixApplier.fix(r).whenCompleteAsync(this::fixFinished, Platform::runLater);
|
fixApplier.fix(r);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,20 +178,12 @@ public class ResultListCellController implements FxController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectBinding<FontAwesome5Icon> fixGlyphProperty() {
|
public ObservableValue<FontAwesome5Icon> fixGlyphProperty() {
|
||||||
return fixGlyph;
|
return fixGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FontAwesome5Icon getFixGlyph() {
|
public FontAwesome5Icon getFixGlyph() {
|
||||||
if (fixState.getValue() == null) {
|
return fixGlyph.getValue();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return switch (fixState.getValue()) {
|
|
||||||
case NOT_FIXABLE, FIXABLE -> null;
|
|
||||||
case FIXING -> FontAwesome5Icon.SPINNER;
|
|
||||||
case FIXED -> FontAwesome5Icon.CHECK;
|
|
||||||
case FIX_FAILED -> FontAwesome5Icon.TIMES;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding fixableProperty() {
|
public BooleanBinding fixableProperty() {
|
||||||
|
|||||||
@@ -87,6 +87,13 @@ public abstract class HubKeyLoadingModule {
|
|||||||
@StringKey(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS)
|
@StringKey(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS)
|
||||||
abstract KeyLoadingStrategy bindHubKeyLoadingStrategyToHubHttps(HubKeyLoadingStrategy strategy);
|
abstract KeyLoadingStrategy bindHubKeyLoadingStrategyToHubHttps(HubKeyLoadingStrategy strategy);
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.HUB_NO_KEYCHAIN)
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Scene provideHubNoKeychainScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.HUB_NO_KEYCHAIN);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.HUB_AUTH_FLOW)
|
@FxmlScene(FxmlFile.HUB_AUTH_FLOW)
|
||||||
@KeyLoadingScoped
|
@KeyLoadingScoped
|
||||||
@@ -136,6 +143,11 @@ public abstract class HubKeyLoadingModule {
|
|||||||
return fxmlLoaders.createScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE);
|
return fxmlLoaders.createScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(NoKeychainController.class)
|
||||||
|
abstract FxController bindNoKeychainController(NoKeychainController controller);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(AuthFlowController.class)
|
@FxControllerKey(AuthFlowController.class)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.cryptomator.ui.keyloading.hub;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.nimbusds.jose.JWEObject;
|
import com.nimbusds.jose.JWEObject;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
|
import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
|
||||||
import org.cryptomator.common.settings.DeviceKey;
|
import org.cryptomator.common.settings.DeviceKey;
|
||||||
import org.cryptomator.cryptolib.api.Masterkey;
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
@@ -31,15 +33,19 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
|||||||
static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
|
static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
|
private final KeychainManager keychainManager;
|
||||||
private final Lazy<Scene> authFlowScene;
|
private final Lazy<Scene> authFlowScene;
|
||||||
|
private final Lazy<Scene> noKeychainScene;
|
||||||
private final CompletableFuture<JWEObject> result;
|
private final CompletableFuture<JWEObject> result;
|
||||||
private final DeviceKey deviceKey;
|
private final DeviceKey deviceKey;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, @Named("windowTitle") String windowTitle) {
|
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
this.keychainManager = keychainManager;
|
||||||
window.setTitle(windowTitle);
|
window.setTitle(windowTitle);
|
||||||
this.authFlowScene = authFlowScene;
|
this.authFlowScene = authFlowScene;
|
||||||
|
this.noKeychainScene = noKeychainScene;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.deviceKey = deviceKey;
|
this.deviceKey = deviceKey;
|
||||||
}
|
}
|
||||||
@@ -48,9 +54,16 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
|||||||
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
|
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
|
||||||
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
|
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
|
||||||
try {
|
try {
|
||||||
startAuthFlow();
|
if (!keychainManager.isSupported()) {
|
||||||
|
throw new NoKeychainAccessProviderException();
|
||||||
|
}
|
||||||
|
var keypair = deviceKey.get();
|
||||||
|
showWindow(authFlowScene);
|
||||||
var jwe = result.get();
|
var jwe = result.get();
|
||||||
return JWEHelper.decrypt(jwe, deviceKey.get().getPrivate());
|
return JWEHelper.decrypt(jwe, keypair.getPrivate());
|
||||||
|
} catch (NoKeychainAccessProviderException e) {
|
||||||
|
showWindow(noKeychainScene);
|
||||||
|
throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e);
|
||||||
} catch (DeviceKey.DeviceKeyRetrievalException e) {
|
} catch (DeviceKey.DeviceKeyRetrievalException e) {
|
||||||
throw new MasterkeyLoadingFailedException("Failed to load keypair", e);
|
throw new MasterkeyLoadingFailedException("Failed to load keypair", e);
|
||||||
} catch (CancellationException e) {
|
} catch (CancellationException e) {
|
||||||
@@ -63,9 +76,9 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startAuthFlow() {
|
private void showWindow(Lazy<Scene> scene) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
window.setScene(authFlowScene.get());
|
window.setScene(scene.get());
|
||||||
window.show();
|
window.show();
|
||||||
Window owner = window.getOwner();
|
Window owner = window.getOwner();
|
||||||
if (owner != null) {
|
if (owner != null) {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
public class NoKeychainController implements FxController {
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final FxApplicationWindows appWindows;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public NoKeychainController(@KeyLoading Stage window, FxApplicationWindows appWindows) {
|
||||||
|
this.window = window;
|
||||||
|
this.appWindows = appWindows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openPreferences() {
|
||||||
|
appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ public class LockForcedController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isForceSupported() {
|
public boolean isForceSupported() {
|
||||||
return false;//vault.supportsForcedUnmount(); TODO
|
return vault.supportsForcedUnmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,33 +3,17 @@ package org.cryptomator.ui.mainwindow;
|
|||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
|
||||||
import org.cryptomator.cryptofs.DirStructure;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.input.DragEvent;
|
|
||||||
import javafx.scene.input.TransferMode;
|
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
|
||||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class MainWindowController implements FxController {
|
public class MainWindowController implements FxController {
|
||||||
@@ -37,28 +21,19 @@ public class MainWindowController implements FxController {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final VaultListManager vaultListManager;
|
|
||||||
private final ReadOnlyObjectProperty<Vault> selectedVault;
|
private final ReadOnlyObjectProperty<Vault> selectedVault;
|
||||||
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
|
||||||
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
|
||||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
|
||||||
public StackPane root;
|
public StackPane root;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MainWindowController(@MainWindow Stage window, VaultListManager vaultListManager, ObjectProperty<Vault> selectedVault, WrongFileAlertComponent.Builder wrongFileAlert) {
|
public MainWindowController(@MainWindow Stage window, ObjectProperty<Vault> selectedVault) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vaultListManager = vaultListManager;
|
|
||||||
this.selectedVault = selectedVault;
|
this.selectedVault = selectedVault;
|
||||||
this.wrongFileAlert = wrongFileAlert;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
LOG.trace("init MainWindowController");
|
LOG.trace("init MainWindowController");
|
||||||
root.setOnDragEntered(this::handleDragEvent);
|
|
||||||
root.setOnDragOver(this::handleDragEvent);
|
|
||||||
root.setOnDragDropped(this::handleDragEvent);
|
|
||||||
root.setOnDragExited(this::handleDragEvent);
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
root.getStyleClass().add("os-windows");
|
root.getStyleClass().add("os-windows");
|
||||||
}
|
}
|
||||||
@@ -72,65 +47,4 @@ public class MainWindowController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDragEvent(DragEvent event) {
|
|
||||||
if (DragEvent.DRAG_ENTERED.equals(event.getEventType()) && event.getGestureSource() == null) {
|
|
||||||
draggingOver.set(true);
|
|
||||||
} else if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
|
||||||
event.acceptTransferModes(TransferMode.ANY);
|
|
||||||
draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
|
|
||||||
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
|
||||||
Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
|
|
||||||
if (vaultPaths.isEmpty()) {
|
|
||||||
wrongFileAlert.build().showWrongFileAlertWindow();
|
|
||||||
} else {
|
|
||||||
vaultPaths.forEach(this::addVault);
|
|
||||||
}
|
|
||||||
event.setDropCompleted(!vaultPaths.isEmpty());
|
|
||||||
event.consume();
|
|
||||||
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
|
||||||
draggingOver.set(false);
|
|
||||||
draggingVaultOver.set(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean containsVault(Path path) {
|
|
||||||
try {
|
|
||||||
if (path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
|
||||||
path = path.getParent();
|
|
||||||
}
|
|
||||||
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addVault(Path pathToVault) {
|
|
||||||
try {
|
|
||||||
if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
|
||||||
vaultListManager.add(pathToVault.getParent());
|
|
||||||
} else {
|
|
||||||
vaultListManager.add(pathToVault);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug("Not a vault: {}", pathToVault);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Getter/Setter */
|
|
||||||
|
|
||||||
public BooleanProperty draggingOverProperty() {
|
|
||||||
return draggingOver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDraggingOver() {
|
|
||||||
return draggingOver.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty draggingVaultOverProperty() {
|
|
||||||
return draggingVaultOver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDraggingVaultOver() {
|
|
||||||
return draggingVaultOver.get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,120 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.tobiasdiez.easybind.EasyBind;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
|
||||||
import org.cryptomator.integrations.mount.Mountpoint;
|
import org.cryptomator.integrations.mount.Mountpoint;
|
||||||
|
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||||
|
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||||
|
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.binding.StringBinding;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.input.DataFormat;
|
||||||
|
import javafx.scene.input.DragEvent;
|
||||||
|
import javafx.scene.input.TransferMode;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.net.URI;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultDetailUnlockedController implements FxController {
|
public class VaultDetailUnlockedController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VaultDetailUnlockedController.class);
|
||||||
|
private static final String ACTIVE_CLASS = "active";
|
||||||
|
|
||||||
private final ReadOnlyObjectProperty<Vault> vault;
|
private final ReadOnlyObjectProperty<Vault> vault;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
|
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
||||||
private final Stage mainWindow;
|
private final Stage mainWindow;
|
||||||
|
private final Optional<RevealPathService> revealPathService;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
||||||
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
||||||
private final ObservableValue<Mountpoint> mountPoint;
|
|
||||||
private final ObservableValue<Boolean> accessibleViaPath;
|
private final ObservableValue<Boolean> accessibleViaPath;
|
||||||
private final ObservableValue<Boolean> accessibleViaUri;
|
private final ObservableValue<Boolean> accessibleViaUri;
|
||||||
private final ObservableValue<String> mountUri;
|
private final ObservableValue<String> mountPoint;
|
||||||
|
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
||||||
|
private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty();
|
||||||
|
|
||||||
|
//FXML
|
||||||
|
public Button dropZone;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
|
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, WrongFileAlertComponent.Builder wrongFileAlert, @MainWindow Stage mainWindow, Optional<RevealPathService> revealPathService, ResourceBundle resourceBundle) {
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
|
this.wrongFileAlert = wrongFileAlert;
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
|
this.revealPathService = revealPathService;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
||||||
this.vaultStatsBuilder = vaultStatsBuilder;
|
this.vaultStatsBuilder = vaultStatsBuilder;
|
||||||
this.mountPoint = vault.flatMap(Vault::mountPointProperty);
|
var mp = vault.flatMap(Vault::mountPointProperty);
|
||||||
this.accessibleViaPath = mountPoint.map(m -> m instanceof Mountpoint.WithPath).orElse(false);
|
this.accessibleViaPath = mp.map(m -> m instanceof Mountpoint.WithPath).orElse(false);
|
||||||
this.accessibleViaUri = mountPoint.map(m -> m instanceof Mountpoint.WithUri).orElse(false);
|
this.accessibleViaUri = mp.map(m -> m instanceof Mountpoint.WithUri).orElse(false);
|
||||||
this.mountUri = mountPoint.map(Mountpoint::uri).map(URI::toASCIIString).orElse("");
|
this.mountPoint = mp.map(m -> {
|
||||||
|
if (m instanceof Mountpoint.WithPath mwp) {
|
||||||
|
return mwp.path().toString();
|
||||||
|
} else {
|
||||||
|
return m.uri().toASCIIString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize() {
|
||||||
|
dropZone.setOnDragEntered(this::handleDragEvent);
|
||||||
|
dropZone.setOnDragOver(this::handleDragEvent);
|
||||||
|
dropZone.setOnDragDropped(this::handleDragEvent);
|
||||||
|
dropZone.setOnDragExited(this::handleDragEvent);
|
||||||
|
|
||||||
|
EasyBind.includeWhen(dropZone.getStyleClass(), ACTIVE_CLASS, draggingOver);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDragEvent(DragEvent event) {
|
||||||
|
if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||||
|
event.acceptTransferModes(TransferMode.LINK);
|
||||||
|
draggingOver.set(true);
|
||||||
|
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||||
|
List<Path> ciphertextPaths = event.getDragboard().getFiles().stream().map(File::toPath).map(this::getCiphertextPath).flatMap(Optional::stream).toList();
|
||||||
|
if (ciphertextPaths.isEmpty()) {
|
||||||
|
wrongFileAlert.build().showWrongFileAlertWindow();
|
||||||
|
} else {
|
||||||
|
revealOrCopyPaths(ciphertextPaths);
|
||||||
|
}
|
||||||
|
event.setDropCompleted(!ciphertextPaths.isEmpty());
|
||||||
|
event.consume();
|
||||||
|
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
||||||
|
draggingOver.set(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
||||||
@@ -62,7 +128,9 @@ public class VaultDetailUnlockedController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void copyMountUri() {
|
public void copyMountUri() {
|
||||||
// TODO
|
ClipboardContent clipboardContent = new ClipboardContent();
|
||||||
|
clipboardContent.putString(mountPoint.getValue());
|
||||||
|
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -75,6 +143,69 @@ public class VaultDetailUnlockedController implements FxController {
|
|||||||
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
|
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void chooseFileAndReveal() {
|
||||||
|
Preconditions.checkState(accessibleViaPath.getValue());
|
||||||
|
var fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.filePickerTitle"));
|
||||||
|
fileChooser.setInitialDirectory(Path.of(mountPoint.getValue()).toFile());
|
||||||
|
var cleartextFile = fileChooser.showOpenDialog(mainWindow);
|
||||||
|
if (cleartextFile != null) {
|
||||||
|
var ciphertextPaths = getCiphertextPath(cleartextFile.toPath()).stream().toList();
|
||||||
|
revealOrCopyPaths(ciphertextPaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean startsWithVaultAccessPoint(Path path) {
|
||||||
|
return path.startsWith(Path.of(mountPoint.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Path> getCiphertextPath(Path path) {
|
||||||
|
if (!startsWithVaultAccessPoint(path)) {
|
||||||
|
LOG.debug("Path does not start with access point of selected vault: {}", path);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var accessPoint = mountPoint.getValue();
|
||||||
|
var cleartextPath = path.toString().substring(accessPoint.length());
|
||||||
|
if (!cleartextPath.startsWith("/")) {
|
||||||
|
cleartextPath = "/" + cleartextPath;
|
||||||
|
}
|
||||||
|
return Optional.of(vault.get().getCiphertextPath(cleartextPath));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Unable to get ciphertext path from path: {}", path, e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revealOrCopyPaths(List<Path> paths) {
|
||||||
|
revealPathService.ifPresentOrElse(svc -> revealPaths(svc, paths), () -> {
|
||||||
|
LOG.warn("No service provider to reveal files found.");
|
||||||
|
copyPathsToClipboard(paths);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revealPaths(RevealPathService service, List<Path> paths) {
|
||||||
|
paths.forEach(path -> {
|
||||||
|
try {
|
||||||
|
LOG.debug("Revealing {}", path);
|
||||||
|
service.reveal(path);
|
||||||
|
} catch (RevealFailedException e) {
|
||||||
|
LOG.error("Revealing ciphertext file failed.", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyPathsToClipboard(List<Path> paths) {
|
||||||
|
StringBuilder clipboardString = new StringBuilder();
|
||||||
|
paths.forEach(p -> clipboardString.append(p.toString()).append("\n"));
|
||||||
|
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, clipboardString.toString()));
|
||||||
|
ciphertextPathsCopied.setValue(true);
|
||||||
|
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
|
||||||
|
ciphertextPathsCopied.set(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|
||||||
@@ -101,13 +232,19 @@ public class VaultDetailUnlockedController implements FxController {
|
|||||||
return accessibleViaUri.getValue();
|
return accessibleViaUri.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableValue<String> mountUriProperty() {
|
public ObservableValue<String> mountPointProperty() {
|
||||||
return mountUri;
|
return mountPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMountUri() {
|
public String getMountPoint() {
|
||||||
return mountUri.getValue();
|
return mountPoint.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty ciphertextPathsCopiedProperty() {
|
||||||
|
return ciphertextPathsCopied;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCiphertextPathsCopied() {
|
||||||
|
return ciphertextPathsCopied.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,43 @@ package org.cryptomator.ui.mainwindow;
|
|||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
|
import org.cryptomator.cryptofs.DirStructure;
|
||||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
import javafx.scene.input.ContextMenuEvent;
|
import javafx.scene.input.ContextMenuEvent;
|
||||||
|
import javafx.scene.input.DragEvent;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.input.TransferMode;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
||||||
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||||
@@ -31,6 +48,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
|||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultListController implements FxController {
|
public class VaultListController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
|
||||||
|
|
||||||
private final Stage mainWindow;
|
private final Stage mainWindow;
|
||||||
private final ObservableList<Vault> vaults;
|
private final ObservableList<Vault> vaults;
|
||||||
@@ -39,17 +57,21 @@ public class VaultListController implements FxController {
|
|||||||
private final AddVaultWizardComponent.Builder addVaultWizard;
|
private final AddVaultWizardComponent.Builder addVaultWizard;
|
||||||
private final BooleanBinding emptyVaultList;
|
private final BooleanBinding emptyVaultList;
|
||||||
private final RemoveVaultComponent.Builder removeVaultDialogue;
|
private final RemoveVaultComponent.Builder removeVaultDialogue;
|
||||||
|
private final VaultListManager vaultListManager;
|
||||||
|
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||||
|
|
||||||
public ListView<Vault> vaultList;
|
public ListView<Vault> vaultList;
|
||||||
|
public StackPane root;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue) {
|
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue, VaultListManager vaultListManager) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
this.selectedVault = selectedVault;
|
this.selectedVault = selectedVault;
|
||||||
this.cellFactory = cellFactory;
|
this.cellFactory = cellFactory;
|
||||||
this.addVaultWizard = addVaultWizard;
|
this.addVaultWizard = addVaultWizard;
|
||||||
this.removeVaultDialogue = removeVaultDialogue;
|
this.removeVaultDialogue = removeVaultDialogue;
|
||||||
|
this.vaultListManager = vaultListManager;
|
||||||
|
|
||||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||||
|
|
||||||
@@ -100,6 +122,11 @@ public class VaultListController implements FxController {
|
|||||||
keyEvent.consume();
|
keyEvent.consume();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
root.setOnDragEntered(this::handleDragEvent);
|
||||||
|
root.setOnDragOver(this::handleDragEvent);
|
||||||
|
root.setOnDragDropped(this::handleDragEvent);
|
||||||
|
root.setOnDragExited(this::handleDragEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deselect(MouseEvent released) {
|
private void deselect(MouseEvent released) {
|
||||||
@@ -128,6 +155,47 @@ public class VaultListController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleDragEvent(DragEvent event) {
|
||||||
|
if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||||
|
draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
|
||||||
|
if (draggingVaultOver.get()) {
|
||||||
|
event.acceptTransferModes(TransferMode.ANY);
|
||||||
|
}
|
||||||
|
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||||
|
Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
|
||||||
|
if (!vaultPaths.isEmpty()) {
|
||||||
|
vaultPaths.forEach(this::addVault);
|
||||||
|
}
|
||||||
|
event.setDropCompleted(!vaultPaths.isEmpty());
|
||||||
|
event.consume();
|
||||||
|
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
||||||
|
draggingVaultOver.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsVault(Path path) {
|
||||||
|
try {
|
||||||
|
if (path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||||
|
path = path.getParent();
|
||||||
|
}
|
||||||
|
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addVault(Path pathToVault) {
|
||||||
|
try {
|
||||||
|
if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||||
|
vaultListManager.add(pathToVault.getParent());
|
||||||
|
} else {
|
||||||
|
vaultListManager.add(pathToVault);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.debug("Not a vault: {}", pathToVault);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Getter and Setter
|
// Getter and Setter
|
||||||
|
|
||||||
public BooleanBinding emptyVaultListProperty() {
|
public BooleanBinding emptyVaultListProperty() {
|
||||||
@@ -138,4 +206,13 @@ public class VaultListController implements FxController {
|
|||||||
return emptyVaultList.get();
|
return emptyVaultList.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty draggingVaultOverProperty() {
|
||||||
|
return draggingVaultOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDraggingVaultOver() {
|
||||||
|
return draggingVaultOver.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.ObservableUtil;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.integrations.mount.MountCapability;
|
import org.cryptomator.integrations.mount.MountCapability;
|
||||||
import org.cryptomator.integrations.mount.MountService;
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Application;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanExpression;
|
import javafx.beans.binding.BooleanExpression;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
@@ -14,39 +17,58 @@ import javafx.scene.control.ChoiceBox;
|
|||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: if WebDAV is selected under Windows, show warning that specific mount options (like selecting a directory as mount point) are _not_ supported
|
|
||||||
*/
|
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
public class VolumePreferencesController implements FxController {
|
public class VolumePreferencesController implements FxController {
|
||||||
|
|
||||||
|
private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/vault-mounting/";
|
||||||
|
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final ObservableValue<MountService> selectedMountService;
|
private final ObservableValue<MountService> selectedMountService;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
private final BooleanExpression loopbackPortSupported;
|
private final BooleanExpression loopbackPortSupported;
|
||||||
|
private final ObservableValue<Boolean> mountToDirSupported;
|
||||||
|
private final ObservableValue<Boolean> mountToDriveLetterSupported;
|
||||||
|
private final ObservableValue<Boolean> mountFlagsSupported;
|
||||||
|
private final ObservableValue<Boolean> readonlySupported;
|
||||||
|
private final Lazy<Application> application;
|
||||||
private final List<MountService> mountProviders;
|
private final List<MountService> mountProviders;
|
||||||
public ChoiceBox<MountService> volumeTypeChoiceBox;
|
public ChoiceBox<MountService> volumeTypeChoiceBox;
|
||||||
public TextField loopbackPortField;
|
public TextField loopbackPortField;
|
||||||
public Button loopbackPortApplyButton;
|
public Button loopbackPortApplyButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VolumePreferencesController(Settings settings, List<MountService> mountProviders, ObservableValue<MountService> selectedMountService) {
|
VolumePreferencesController(Settings settings, Lazy<Application> application, List<MountService> mountProviders, ResourceBundle resourceBundle) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
this.application = application;
|
||||||
this.mountProviders = mountProviders;
|
this.mountProviders = mountProviders;
|
||||||
this.selectedMountService = selectedMountService;
|
this.resourceBundle = resourceBundle;
|
||||||
|
|
||||||
|
var fallbackProvider = mountProviders.stream().findFirst().orElse(null);
|
||||||
|
this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService(), serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider);
|
||||||
this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)));
|
this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)));
|
||||||
|
this.mountToDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT) || s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR));
|
||||||
|
this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
|
||||||
|
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
|
||||||
|
this.readonlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
volumeTypeChoiceBox.getItems().add(null);
|
||||||
volumeTypeChoiceBox.getItems().addAll(mountProviders);
|
volumeTypeChoiceBox.getItems().addAll(mountProviders);
|
||||||
volumeTypeChoiceBox.setConverter(new MountServiceConverter());
|
volumeTypeChoiceBox.setConverter(new MountServiceConverter());
|
||||||
volumeTypeChoiceBox.getSelectionModel().select(selectedMountService.getValue());
|
boolean autoSelected = settings.mountService().get() == null;
|
||||||
volumeTypeChoiceBox.valueProperty().addListener((observableValue, oldProvide, newProvider) -> settings.mountService().set(newProvider.getClass().getName()));
|
volumeTypeChoiceBox.getSelectionModel().select(autoSelected ? null : selectedMountService.getValue());
|
||||||
|
volumeTypeChoiceBox.valueProperty().addListener((observableValue, oldProvider, newProvider) -> {
|
||||||
|
var toSet = Optional.ofNullable(newProvider).map(nP -> nP.getClass().getName()).orElse(null);
|
||||||
|
settings.mountService().set(toSet);
|
||||||
|
});
|
||||||
|
|
||||||
loopbackPortField.setText(String.valueOf(settings.port().get()));
|
loopbackPortField.setText(String.valueOf(settings.port().get()));
|
||||||
loopbackPortApplyButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(loopbackPortField.textProperty()));
|
loopbackPortApplyButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(loopbackPortField.textProperty()));
|
||||||
loopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, loopbackPortField.textProperty()).not());
|
loopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, loopbackPortField.textProperty()).not());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validateLoopbackPort() {
|
private boolean validateLoopbackPort() {
|
||||||
@@ -75,13 +97,49 @@ public class VolumePreferencesController implements FxController {
|
|||||||
return loopbackPortSupported.get();
|
return loopbackPortSupported.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> readonlySupportedProperty() {
|
||||||
|
return readonlySupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReadonlySupported() {
|
||||||
|
return readonlySupported.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> mountToDirSupportedProperty() {
|
||||||
|
return mountToDirSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMountToDirSupported() {
|
||||||
|
return mountToDirSupported.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> mountToDriveLetterSupportedProperty() {
|
||||||
|
return mountToDriveLetterSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMountToDriveLetterSupported() {
|
||||||
|
return mountToDriveLetterSupported.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> mountFlagsSupportedProperty() {
|
||||||
|
return mountFlagsSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMountFlagsSupported() {
|
||||||
|
return mountFlagsSupported.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
/* Helpers */
|
/* Helpers */
|
||||||
|
|
||||||
private static class MountServiceConverter extends StringConverter<MountService> {
|
private class MountServiceConverter extends StringConverter<MountService> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(MountService provider) {
|
public String toString(MountService provider) {
|
||||||
return provider== null? "None" : provider.displayName(); //TODO: adjust message
|
if (provider == null) {
|
||||||
|
return resourceBundle.getString("preferences.volume.type.automatic");
|
||||||
|
} else {
|
||||||
|
return provider.displayName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,5 +148,7 @@ public class VolumePreferencesController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void openDocs() {
|
||||||
|
application.get().getHostServices().showDocument(DOCS_MOUNTING_URL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import javax.inject.Inject;
|
|||||||
import javafx.animation.Animation;
|
import javafx.animation.Animation;
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.DoubleBinding;
|
import javafx.beans.binding.DoubleBinding;
|
||||||
import javafx.beans.binding.LongBinding;
|
import javafx.beans.binding.LongBinding;
|
||||||
|
import javafx.beans.property.LongProperty;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@@ -21,6 +23,7 @@ import javafx.scene.chart.XYChart.Series;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
@VaultStatisticsScoped
|
@VaultStatisticsScoped
|
||||||
public class VaultStatisticsController implements FxController {
|
public class VaultStatisticsController implements FxController {
|
||||||
@@ -32,6 +35,7 @@ public class VaultStatisticsController implements FxController {
|
|||||||
private final VaultStats stats;
|
private final VaultStats stats;
|
||||||
private final Series<Number, Number> readData;
|
private final Series<Number, Number> readData;
|
||||||
private final Series<Number, Number> writeData;
|
private final Series<Number, Number> writeData;
|
||||||
|
private final Series<Number, Number> accessData;
|
||||||
private final Timeline ioAnimation;
|
private final Timeline ioAnimation;
|
||||||
private final LongBinding bpsRead;
|
private final LongBinding bpsRead;
|
||||||
private final LongBinding bpsWritten;
|
private final LongBinding bpsWritten;
|
||||||
@@ -44,15 +48,20 @@ public class VaultStatisticsController implements FxController {
|
|||||||
private final LongBinding totalBytesDecrypted;
|
private final LongBinding totalBytesDecrypted;
|
||||||
private final LongBinding filesRead;
|
private final LongBinding filesRead;
|
||||||
private final LongBinding filesWritten;
|
private final LongBinding filesWritten;
|
||||||
|
private final LongBinding filesAccessed;
|
||||||
|
private final LongBinding totalFilesAccessed;
|
||||||
private final LongBinding bpsEncrypted;
|
private final LongBinding bpsEncrypted;
|
||||||
private final LongBinding bpsDecrypted;
|
private final LongBinding bpsDecrypted;
|
||||||
|
|
||||||
public AreaChart<Number, Number> readChart;
|
public AreaChart<Number, Number> readChart;
|
||||||
public AreaChart<Number, Number> writeChart;
|
public AreaChart<Number, Number> writeChart;
|
||||||
|
public AreaChart<Number, Number> accessChart;
|
||||||
public NumberAxis readChartXAxis;
|
public NumberAxis readChartXAxis;
|
||||||
public NumberAxis readChartYAxis;
|
public NumberAxis readChartYAxis;
|
||||||
public NumberAxis writeChartXAxis;
|
public NumberAxis writeChartXAxis;
|
||||||
public NumberAxis writeChartYAxis;
|
public NumberAxis writeChartYAxis;
|
||||||
|
public NumberAxis accessChartXAxis;
|
||||||
|
public NumberAxis accessChartYAxis;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultStatisticsController(VaultStatisticsComponent component, @VaultStatisticsWindow Stage window, @VaultStatisticsWindow Vault vault) {
|
public VaultStatisticsController(VaultStatisticsComponent component, @VaultStatisticsWindow Stage window, @VaultStatisticsWindow Vault vault) {
|
||||||
@@ -60,6 +69,7 @@ public class VaultStatisticsController implements FxController {
|
|||||||
this.stats = vault.getStats();
|
this.stats = vault.getStats();
|
||||||
this.readData = new Series<>();
|
this.readData = new Series<>();
|
||||||
this.writeData = new Series<>();
|
this.writeData = new Series<>();
|
||||||
|
this.accessData = new Series<>();
|
||||||
this.bpsRead = WeakBindings.bindLong(stats.bytesPerSecondReadProperty());
|
this.bpsRead = WeakBindings.bindLong(stats.bytesPerSecondReadProperty());
|
||||||
this.bpsWritten = WeakBindings.bindLong(stats.bytesPerSecondWrittenProperty());
|
this.bpsWritten = WeakBindings.bindLong(stats.bytesPerSecondWrittenProperty());
|
||||||
this.cacheHitRate = WeakBindings.bindDouble(stats.cacheHitRateProperty());
|
this.cacheHitRate = WeakBindings.bindDouble(stats.cacheHitRateProperty());
|
||||||
@@ -71,11 +81,13 @@ public class VaultStatisticsController implements FxController {
|
|||||||
this.totalBytesEncrypted = WeakBindings.bindLong(stats.totalBytesEncryptedProperty());
|
this.totalBytesEncrypted = WeakBindings.bindLong(stats.totalBytesEncryptedProperty());
|
||||||
this.filesRead = WeakBindings.bindLong(stats.filesRead());
|
this.filesRead = WeakBindings.bindLong(stats.filesRead());
|
||||||
this.filesWritten = WeakBindings.bindLong(stats.filesWritten());
|
this.filesWritten = WeakBindings.bindLong(stats.filesWritten());
|
||||||
|
this.filesAccessed = WeakBindings.bindLong(stats.filesAccessed());
|
||||||
|
this.totalFilesAccessed = WeakBindings.bindLong(stats.totalFilesAccessed());
|
||||||
this.bpsEncrypted = WeakBindings.bindLong(stats.bytesPerSecondEncryptedProperty());
|
this.bpsEncrypted = WeakBindings.bindLong(stats.bytesPerSecondEncryptedProperty());
|
||||||
this.bpsDecrypted = WeakBindings.bindLong(stats.bytesPerSecondDecryptedProperty());
|
this.bpsDecrypted = WeakBindings.bindLong(stats.bytesPerSecondDecryptedProperty());
|
||||||
|
|
||||||
this.ioAnimation = new Timeline(); //TODO Research better timer
|
this.ioAnimation = new Timeline(); //TODO Research better timer
|
||||||
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(readData, writeData)));
|
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(readData, writeData, accessData)));
|
||||||
ioAnimation.setCycleCount(Animation.INDEFINITE);
|
ioAnimation.setCycleCount(Animation.INDEFINITE);
|
||||||
ioAnimation.play();
|
ioAnimation.play();
|
||||||
|
|
||||||
@@ -89,6 +101,7 @@ public class VaultStatisticsController implements FxController {
|
|||||||
public void initialize() {
|
public void initialize() {
|
||||||
readChart.getData().addAll(readData);
|
readChart.getData().addAll(readData);
|
||||||
writeChart.getData().addAll(writeData);
|
writeChart.getData().addAll(writeData);
|
||||||
|
accessChart.getData().addAll(accessData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
|
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
|
||||||
@@ -96,16 +109,21 @@ public class VaultStatisticsController implements FxController {
|
|||||||
private long step = IO_SAMPLING_STEPS;
|
private long step = IO_SAMPLING_STEPS;
|
||||||
private final Series<Number, Number> decryptedBytesRead;
|
private final Series<Number, Number> decryptedBytesRead;
|
||||||
private final Series<Number, Number> encryptedBytesWrite;
|
private final Series<Number, Number> encryptedBytesWrite;
|
||||||
|
private final Series<Number, Number> accessedFiles;
|
||||||
private final long[] maxBuf = new long[IO_SAMPLING_STEPS];
|
private final long[] maxBuf = new long[IO_SAMPLING_STEPS];
|
||||||
|
private final long[] maxAccessBuf = new long[IO_SAMPLING_STEPS];
|
||||||
|
|
||||||
public IoSamplingAnimationHandler(Series<Number, Number> readData, Series<Number, Number> writeData) {
|
|
||||||
|
public IoSamplingAnimationHandler(Series<Number, Number> readData, Series<Number, Number> writeData, Series<Number, Number> accessData) {
|
||||||
this.decryptedBytesRead = readData;
|
this.decryptedBytesRead = readData;
|
||||||
this.encryptedBytesWrite = writeData;
|
this.encryptedBytesWrite = writeData;
|
||||||
|
this.accessedFiles = accessData;
|
||||||
|
|
||||||
// initialize data once and change value of data points later:
|
// initialize data once and change value of data points later:
|
||||||
for (int i = 0; i < IO_SAMPLING_STEPS; i++) {
|
for (int i = 0; i < IO_SAMPLING_STEPS; i++) {
|
||||||
decryptedBytesRead.getData().add(new Data<>(i, 0));
|
decryptedBytesRead.getData().add(new Data<>(i, 0));
|
||||||
encryptedBytesWrite.getData().add(new Data<>(i, 0));
|
encryptedBytesWrite.getData().add(new Data<>(i, 0));
|
||||||
|
accessedFiles.getData().add(new Data<>(i, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,17 +132,22 @@ public class VaultStatisticsController implements FxController {
|
|||||||
final long currentStep = step++;
|
final long currentStep = step++;
|
||||||
final long decBytes = stats.bytesPerSecondReadProperty().get();
|
final long decBytes = stats.bytesPerSecondReadProperty().get();
|
||||||
final long encBytes = stats.bytesPerSecondWrittenProperty().get();
|
final long encBytes = stats.bytesPerSecondWrittenProperty().get();
|
||||||
|
final long accFiles = stats.filesAccessed().get();
|
||||||
|
|
||||||
maxBuf[(int) currentStep % IO_SAMPLING_STEPS] = Math.max(decBytes, encBytes);
|
maxBuf[(int) currentStep % IO_SAMPLING_STEPS] = Math.max(decBytes, encBytes);
|
||||||
long allTimeMax = Arrays.stream(maxBuf).max().orElse(0l);
|
long allTimeMax = Arrays.stream(maxBuf).max().orElse(0L);
|
||||||
|
maxAccessBuf[(int) currentStep % IO_SAMPLING_STEPS] = accFiles;
|
||||||
|
long allTimeMaxAccessedFiles = Arrays.stream(maxAccessBuf).max().orElse(0L);
|
||||||
|
|
||||||
// remove oldest value:
|
// remove oldest value:
|
||||||
decryptedBytesRead.getData().remove(0);
|
decryptedBytesRead.getData().remove(0);
|
||||||
encryptedBytesWrite.getData().remove(0);
|
encryptedBytesWrite.getData().remove(0);
|
||||||
|
accessedFiles.getData().remove(0);
|
||||||
|
|
||||||
// add latest value:
|
// add latest value:
|
||||||
decryptedBytesRead.getData().add(new Data<>(currentStep, decBytes));
|
decryptedBytesRead.getData().add(new Data<>(currentStep, decBytes));
|
||||||
encryptedBytesWrite.getData().add(new Data<>(currentStep, encBytes));
|
encryptedBytesWrite.getData().add(new Data<>(currentStep, encBytes));
|
||||||
|
accessedFiles.getData().add(new Data<>(currentStep, accFiles));
|
||||||
|
|
||||||
// adjust ranges:
|
// adjust ranges:
|
||||||
readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||||
@@ -133,6 +156,9 @@ public class VaultStatisticsController implements FxController {
|
|||||||
writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||||
writeChartXAxis.setUpperBound(currentStep);
|
writeChartXAxis.setUpperBound(currentStep);
|
||||||
writeChartYAxis.setUpperBound(allTimeMax);
|
writeChartYAxis.setUpperBound(allTimeMax);
|
||||||
|
accessChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||||
|
accessChartXAxis.setUpperBound(currentStep);
|
||||||
|
accessChartYAxis.setUpperBound(allTimeMaxAccessedFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,4 +233,12 @@ public class VaultStatisticsController implements FxController {
|
|||||||
public LongBinding filesWrittenProperty() {return filesWritten;}
|
public LongBinding filesWrittenProperty() {return filesWritten;}
|
||||||
|
|
||||||
public long getFilesWritten() {return filesWritten.get();}
|
public long getFilesWritten() {return filesWritten.get();}
|
||||||
|
|
||||||
|
public LongBinding filesAccessedProperty() {return filesAccessed;}
|
||||||
|
|
||||||
|
public long getFilesAccessed() {return filesAccessed.get();}
|
||||||
|
|
||||||
|
public LongBinding totalFilesAccessedProperty() {return totalFilesAccessed;}
|
||||||
|
|
||||||
|
public long getTotalFilesAccessed() {return totalFilesAccessed.get();}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.traymenu;
|
package org.cryptomator.ui.traymenu;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.integrations.common.CheckAvailability;
|
import org.cryptomator.integrations.common.CheckAvailability;
|
||||||
import org.cryptomator.integrations.common.Priority;
|
import org.cryptomator.integrations.common.Priority;
|
||||||
@@ -19,6 +20,8 @@ import java.awt.PopupMenu;
|
|||||||
import java.awt.SystemTray;
|
import java.awt.SystemTray;
|
||||||
import java.awt.Toolkit;
|
import java.awt.Toolkit;
|
||||||
import java.awt.TrayIcon;
|
import java.awt.TrayIcon;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@CheckAvailability
|
@CheckAvailability
|
||||||
@@ -28,6 +31,7 @@ public class AwtTrayMenuController implements TrayMenuController {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class);
|
||||||
|
|
||||||
private final PopupMenu menu = new PopupMenu();
|
private final PopupMenu menu = new PopupMenu();
|
||||||
|
private TrayIcon trayIcon;
|
||||||
|
|
||||||
@CheckAvailability
|
@CheckAvailability
|
||||||
public static boolean isAvailable() {
|
public static boolean isAvailable() {
|
||||||
@@ -35,9 +39,9 @@ public class AwtTrayMenuController implements TrayMenuController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showTrayIcon(byte[] rawImageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
|
public void showTrayIcon(byte[] imageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
|
||||||
var image = Toolkit.getDefaultToolkit().createImage(rawImageData);
|
var image = Toolkit.getDefaultToolkit().createImage(imageData);
|
||||||
var trayIcon = new TrayIcon(image, tooltip, menu);
|
trayIcon = new TrayIcon(image, tooltip, menu);
|
||||||
|
|
||||||
trayIcon.setImageAutoSize(true);
|
trayIcon.setImageAutoSize(true);
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
@@ -52,6 +56,15 @@ public class AwtTrayMenuController implements TrayMenuController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateTrayIcon(byte[] imageData) {
|
||||||
|
if (trayIcon == null) {
|
||||||
|
throw new IllegalStateException("Failed to update the icon as it has not yet been added");
|
||||||
|
}
|
||||||
|
var image = Toolkit.getDefaultToolkit().createImage(imageData);
|
||||||
|
trayIcon.setImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateTrayMenu(List<TrayMenuItem> items) {
|
public void updateTrayMenu(List<TrayMenuItem> items) {
|
||||||
menu.removeAll();
|
menu.removeAll();
|
||||||
@@ -59,8 +72,14 @@ public class AwtTrayMenuController implements TrayMenuController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBeforeOpenMenu(Runnable runnable) {
|
public void onBeforeOpenMenu(Runnable listener) {
|
||||||
|
Preconditions.checkNotNull(this.trayIcon);
|
||||||
|
this.trayIcon.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
listener.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addChildren(Menu menu, List<TrayMenuItem> items) {
|
private void addChildren(Menu menu, List<TrayMenuItem> items) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.cryptomator.ui.traymenu;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.integrations.tray.ActionItem;
|
import org.cryptomator.integrations.tray.ActionItem;
|
||||||
import org.cryptomator.integrations.tray.SeparatorItem;
|
import org.cryptomator.integrations.tray.SeparatorItem;
|
||||||
import org.cryptomator.integrations.tray.SubMenuItem;
|
import org.cryptomator.integrations.tray.SubMenuItem;
|
||||||
@@ -32,7 +33,9 @@ public class TrayMenuBuilder {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TrayMenuBuilder.class);
|
private static final Logger LOG = LoggerFactory.getLogger(TrayMenuBuilder.class);
|
||||||
private static final String TRAY_ICON_MAC = "/img/tray_icon_mac@2x.png";
|
private static final String TRAY_ICON_MAC = "/img/tray_icon_mac@2x.png";
|
||||||
private static final String TRAY_ICON = "/img/window_icon_32.png";
|
private static final String TRAY_ICON_UNLOCKED_MAC = "/img/tray_icon_unlocked_mac@2x.png";
|
||||||
|
private static final String TRAY_ICON = "/img/tray_icon.png";
|
||||||
|
private static final String TRAY_ICON_UNLOCKED = "/img/tray_icon_unlocked.png";
|
||||||
|
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
@@ -61,12 +64,15 @@ public class TrayMenuBuilder {
|
|||||||
v.displayNameProperty().addListener(this::vaultListChanged);
|
v.displayNameProperty().addListener(this::vaultListChanged);
|
||||||
});
|
});
|
||||||
|
|
||||||
try (var image = getClass().getResourceAsStream(SystemUtils.IS_OS_MAC_OSX ? TRAY_ICON_MAC : TRAY_ICON)) {
|
try {
|
||||||
trayMenu.showTrayIcon(image.readAllBytes(), this::showMainWindow, "Cryptomator");
|
trayMenu.showTrayIcon(getAppropriateTrayIconImage(), this::showMainWindow, "Cryptomator");
|
||||||
|
trayMenu.onBeforeOpenMenu(() -> {
|
||||||
|
for (Vault vault : vaults) {
|
||||||
|
VaultListManager.redetermineVaultState(vault);
|
||||||
|
}
|
||||||
|
});
|
||||||
rebuildMenu();
|
rebuildMenu();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Failed to load embedded resource", e);
|
|
||||||
} catch (TrayMenuException e) {
|
} catch (TrayMenuException e) {
|
||||||
LOG.error("Adding tray icon failed", e);
|
LOG.error("Adding tray icon failed", e);
|
||||||
}
|
}
|
||||||
@@ -78,6 +84,7 @@ public class TrayMenuBuilder {
|
|||||||
|
|
||||||
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
|
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
assert Platform.isFxApplicationThread();
|
assert Platform.isFxApplicationThread();
|
||||||
|
trayMenu.updateTrayIcon(getAppropriateTrayIconImage());
|
||||||
rebuildMenu();
|
rebuildMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,4 +155,22 @@ public class TrayMenuBuilder {
|
|||||||
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
|
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] getAppropriateTrayIconImage() {
|
||||||
|
boolean isAnyVaultUnlocked = vaults.stream().anyMatch(Vault::isUnlocked);
|
||||||
|
|
||||||
|
String resourceName;
|
||||||
|
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||||
|
resourceName = isAnyVaultUnlocked ? TRAY_ICON_UNLOCKED_MAC : TRAY_ICON_MAC;
|
||||||
|
} else {
|
||||||
|
resourceName = isAnyVaultUnlocked ? TRAY_ICON_UNLOCKED : TRAY_ICON;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var image = getClass().getResourceAsStream(resourceName)) {
|
||||||
|
assert image != null;
|
||||||
|
return image.readAllBytes();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("Failed to load tray icon image: " + resourceName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package org.cryptomator.ui.unlock;
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
|
import org.cryptomator.common.mount.MountPointNotExistsException;
|
||||||
|
import org.cryptomator.common.mount.MountPointNotSupportedException;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.controls.FormattedLabel;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
|
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
@@ -13,11 +20,32 @@ public class UnlockInvalidMountPointController implements FxController {
|
|||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
|
private final AtomicReference<Throwable> unlockException;
|
||||||
|
private final FxApplicationWindows appWindows;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
|
public FormattedLabel dialogDescription;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault) {
|
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow AtomicReference<Throwable> unlockException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
|
this.unlockException = unlockException;
|
||||||
|
this.appWindows = appWindows;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
var e = unlockException.get();
|
||||||
|
String translationKey = "unlock.error.customPath.description.generic";
|
||||||
|
if (e instanceof MountPointNotSupportedException) {
|
||||||
|
translationKey = "unlock.error.customPath.description.notSupported";
|
||||||
|
} else if (e instanceof MountPointNotExistsException) {
|
||||||
|
translationKey = "unlock.error.customPath.description.notExists";
|
||||||
|
}
|
||||||
|
dialogDescription.setFormat(resourceBundle.getString(translationKey));
|
||||||
|
dialogDescription.setArg1(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -25,4 +53,10 @@ public class UnlockInvalidMountPointController implements FxController {
|
|||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void closeAndOpenPreferences() {
|
||||||
|
appWindows.showPreferencesWindow(SelectedPreferencesTab.VOLUME);
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,7 @@ import javafx.stage.Modality;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@Module(subcomponents = {KeyLoadingComponent.class})
|
@Module(subcomponents = {KeyLoadingComponent.class})
|
||||||
abstract class UnlockModule {
|
abstract class UnlockModule {
|
||||||
@@ -57,6 +58,13 @@ abstract class UnlockModule {
|
|||||||
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
|
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@UnlockWindow
|
||||||
|
@UnlockScoped
|
||||||
|
static AtomicReference<Throwable> unlockException() {
|
||||||
|
return new AtomicReference<>();
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.UNLOCK_SUCCESS)
|
@FxmlScene(FxmlFile.UNLOCK_SUCCESS)
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.cryptomator.ui.unlock;
|
|||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.mount.IllegalMountPointException;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
@@ -20,6 +21,7 @@ import javafx.concurrent.Task;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A multi-step task that consists of background activities as well as user interaction.
|
* A multi-step task that consists of background activities as well as user interaction.
|
||||||
@@ -38,9 +40,10 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
private final Lazy<Scene> invalidMountPointScene;
|
private final Lazy<Scene> invalidMountPointScene;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
private final KeyLoadingStrategy keyLoadingStrategy;
|
private final KeyLoadingStrategy keyLoadingStrategy;
|
||||||
|
private final AtomicReference<Throwable> unlockFailedException;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy) {
|
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow AtomicReference<Throwable> unlockFailedException) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
@@ -48,6 +51,7 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
this.invalidMountPointScene = invalidMountPointScene;
|
this.invalidMountPointScene = invalidMountPointScene;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.keyLoadingStrategy = keyLoadingStrategy;
|
this.keyLoadingStrategy = keyLoadingStrategy;
|
||||||
|
this.unlockFailedException = unlockFailedException;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -67,13 +71,15 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Throwables.propagateIfPossible(e, IOException.class);
|
Throwables.propagateIfPossible(e, IOException.class);
|
||||||
Throwables.propagateIfPossible(e, CryptoException.class);
|
Throwables.propagateIfPossible(e, CryptoException.class);
|
||||||
|
Throwables.propagateIfPossible(e, IllegalMountPointException.class);
|
||||||
Throwables.propagateIfPossible(e, MountFailedException.class);
|
Throwables.propagateIfPossible(e, MountFailedException.class);
|
||||||
throw new IllegalStateException("unexpected exception type", e);
|
throw new IllegalStateException("unexpected exception type", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showInvalidMountPointScene() {
|
private void handleIllegalMountPointError(IllegalMountPointException impe) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
unlockFailedException.set(impe);
|
||||||
window.setScene(invalidMountPointScene.get());
|
window.setScene(invalidMountPointScene.get());
|
||||||
window.show();
|
window.show();
|
||||||
});
|
});
|
||||||
@@ -107,7 +113,11 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
protected void failed() {
|
protected void failed() {
|
||||||
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
|
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
|
||||||
Throwable throwable = super.getException();
|
Throwable throwable = super.getException();
|
||||||
handleGenericError(throwable);
|
if(throwable instanceof IllegalMountPointException impe) {
|
||||||
|
handleIllegalMountPointError(impe);
|
||||||
|
} else {
|
||||||
|
handleGenericError(throwable);
|
||||||
|
}
|
||||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package org.cryptomator.ui.vaultoptions;
|
package org.cryptomator.ui.vaultoptions;
|
||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
import com.google.common.base.Strings;
|
||||||
|
import org.cryptomator.common.mount.ActualMountService;
|
||||||
import org.cryptomator.common.mount.WindowsDriveLetters;
|
import org.cryptomator.common.mount.WindowsDriveLetters;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.integrations.mount.MountCapability;
|
import org.cryptomator.integrations.mount.MountCapability;
|
||||||
import org.cryptomator.integrations.mount.MountService;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
@@ -30,16 +33,17 @@ import java.util.Set;
|
|||||||
public class MountOptionsController implements FxController {
|
public class MountOptionsController implements FxController {
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final VaultSettings vaultSettings;
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
|
private final ObservableValue<String> defaultMountFlags;
|
||||||
private final ObservableValue<Boolean> mountpointDirSupported;
|
private final ObservableValue<Boolean> mountpointDirSupported;
|
||||||
private final ObservableValue<Boolean> mountpointDriveLetterSupported;
|
private final ObservableValue<Boolean> mountpointDriveLetterSupported;
|
||||||
private final ObservableValue<Boolean> readOnlySupported;
|
private final ObservableValue<Boolean> readOnlySupported;
|
||||||
private final ObservableValue<Boolean> mountFlagsSupported;
|
private final ObservableValue<Boolean> mountFlagsSupported;
|
||||||
private final ObservableValue<Path> driveLetter;
|
|
||||||
private final ObservableValue<String> directoryPath;
|
private final ObservableValue<String> directoryPath;
|
||||||
|
private final FxApplicationWindows applicationWindows;
|
||||||
|
|
||||||
|
|
||||||
//-- FXML objects --
|
//-- FXML objects --
|
||||||
@@ -54,59 +58,75 @@ public class MountOptionsController implements FxController {
|
|||||||
public ChoiceBox<Path> driveLetterSelection;
|
public ChoiceBox<Path> driveLetterSelection;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObservableValue<MountService> mountService, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, Environment environment) {
|
MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObservableValue<ActualMountService> mountService, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, FxApplicationWindows applicationWindows) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vaultSettings = vault.getVaultSettings();
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.mountpointDirSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
|
this.defaultMountFlags = mountService.map(as -> {
|
||||||
this.mountpointDriveLetterSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
|
if (as.service().hasCapability(MountCapability.MOUNT_FLAGS)) {
|
||||||
this.mountFlagsSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
|
return as.service().getDefaultMountFlags();
|
||||||
this.readOnlySupported = mountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
|
} else {
|
||||||
this.driveLetter = vault.getVaultSettings().mountPoint().map(p -> isDriveLetter(p) ? p : null);
|
return "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.mountpointDirSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || as.service().hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
|
||||||
|
this.mountpointDriveLetterSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
|
||||||
|
this.mountFlagsSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_FLAGS));
|
||||||
|
this.readOnlySupported = mountService.map(as -> as.service().hasCapability(MountCapability.READ_ONLY));
|
||||||
this.directoryPath = vault.getVaultSettings().mountPoint().map(p -> isDriveLetter(p) ? null : p.toString());
|
this.directoryPath = vault.getVaultSettings().mountPoint().map(p -> isDriveLetter(p) ? null : p.toString());
|
||||||
|
this.applicationWindows = applicationWindows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// readonly:
|
// readonly:
|
||||||
readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode());
|
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode());
|
||||||
|
|
||||||
// custom mount flags:
|
// custom mount flags:
|
||||||
mountFlagsField.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
|
mountFlagsField.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
|
||||||
customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags());
|
customMountFlagsCheckbox.setSelected(!Strings.isNullOrEmpty(vaultSettings.mountFlags().getValue()));
|
||||||
|
toggleUseCustomMountFlags();
|
||||||
|
|
||||||
//driveLetter choice box
|
//driveLetter choice box
|
||||||
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAll());
|
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAll());
|
||||||
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
|
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
|
||||||
driveLetterSelection.setOnShowing(event -> driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle))); //TODO: does this work?
|
|
||||||
|
|
||||||
//mountPoint toggle group
|
//mountPoint toggle group
|
||||||
var mountPoint = vault.getVaultSettings().getMountPoint();
|
var mountPoint = vaultSettings.getMountPoint();
|
||||||
if (mountPoint == null) {
|
if (mountPoint == null) {
|
||||||
//prepare and select auto
|
//prepare and select auto
|
||||||
mountPointToggleGroup.selectToggle(mountPointAutoBtn);
|
mountPointToggleGroup.selectToggle(mountPointAutoBtn);
|
||||||
} else if (mountPoint.getParent() == null && isDriveLetter(mountPoint)) {
|
} else if (mountPoint.getParent() == null && isDriveLetter(mountPoint)) {
|
||||||
//prepare and select drive letter
|
//prepare and select drive letter
|
||||||
mountPointToggleGroup.selectToggle(mountPointDriveLetterBtn);
|
mountPointToggleGroup.selectToggle(mountPointDriveLetterBtn);
|
||||||
} else if (driveLetterSelection.getValue() == null) {
|
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint());
|
||||||
|
} else {
|
||||||
//prepare and select dir
|
//prepare and select dir
|
||||||
mountPointToggleGroup.selectToggle(mountPointDirBtn);
|
mountPointToggleGroup.selectToggle(mountPointDirBtn);
|
||||||
}
|
}
|
||||||
mountPointToggleGroup.selectedToggleProperty().addListener(this::selectedToggleChanged);
|
mountPointToggleGroup.selectedToggleProperty().addListener(this::selectedToggleChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void openVolumePreferences() {
|
||||||
|
applicationWindows.showPreferencesWindow(SelectedPreferencesTab.VOLUME);
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void toggleUseCustomMountFlags() {
|
public void toggleUseCustomMountFlags() {
|
||||||
if (customMountFlagsCheckbox.isSelected()) {
|
if (customMountFlagsCheckbox.isSelected()) {
|
||||||
readOnlyCheckbox.setSelected(false); // to prevent invalid states
|
readOnlyCheckbox.setSelected(false); // to prevent invalid states
|
||||||
mountFlagsField.textProperty().unbind();
|
mountFlagsField.textProperty().unbind();
|
||||||
vault.setCustomMountFlags(vault.defaultMountFlagsProperty().getValue());
|
var mountFlags = vaultSettings.mountFlags().get();
|
||||||
mountFlagsField.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags());
|
if (mountFlags == null || mountFlags.isBlank()) {
|
||||||
|
vaultSettings.mountFlags().set(defaultMountFlags.getValue());
|
||||||
|
}
|
||||||
|
mountFlagsField.textProperty().bindBidirectional(vaultSettings.mountFlags());
|
||||||
} else {
|
} else {
|
||||||
mountFlagsField.textProperty().unbindBidirectional(vault.getVaultSettings().mountFlags());
|
mountFlagsField.textProperty().unbindBidirectional(vaultSettings.mountFlags());
|
||||||
vault.setCustomMountFlags(null);
|
vaultSettings.mountFlags().set(null);
|
||||||
mountFlagsField.textProperty().bind(vault.defaultMountFlagsProperty());
|
mountFlagsField.textProperty().bind(defaultMountFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +134,7 @@ public class MountOptionsController implements FxController {
|
|||||||
public void chooseCustomMountPoint() {
|
public void chooseCustomMountPoint() {
|
||||||
try {
|
try {
|
||||||
Path chosenPath = chooseCustomMountPointInternal();
|
Path chosenPath = chooseCustomMountPointInternal();
|
||||||
vault.getVaultSettings().mountPoint().set(chosenPath);
|
vaultSettings.mountPoint().set(chosenPath);
|
||||||
} catch (NoDirSelectedException e) {
|
} catch (NoDirSelectedException e) {
|
||||||
//no-op
|
//no-op
|
||||||
}
|
}
|
||||||
@@ -131,7 +151,7 @@ public class MountOptionsController implements FxController {
|
|||||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||||
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
|
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
|
||||||
try {
|
try {
|
||||||
var mp = vault.getVaultSettings().mountPoint().get();
|
var mp = vaultSettings.mountPoint().get();
|
||||||
var initialDir = mp != null && !isDriveLetter(mp) ? mp : Path.of(System.getProperty("user.home"));
|
var initialDir = mp != null && !isDriveLetter(mp) ? mp : Path.of(System.getProperty("user.home"));
|
||||||
|
|
||||||
if (Files.isDirectory(initialDir)) {
|
if (Files.isDirectory(initialDir)) {
|
||||||
@@ -149,27 +169,30 @@ public class MountOptionsController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void selectedToggleChanged(ObservableValue<? extends Toggle> observable, Toggle oldToggle, Toggle newToggle) {
|
private void selectedToggleChanged(ObservableValue<? extends Toggle> observable, Toggle oldToggle, Toggle newToggle) {
|
||||||
Path mountPointToBe = null;
|
//Remark: the mountpoint corresponding to the newToggle must be null, otherwise it would not be new!
|
||||||
try {
|
driveLetterSelection.valueProperty().unbindBidirectional(vaultSettings.mountPoint());
|
||||||
//Remark: the mountpoint corresponding to the newToggle must be null, otherwise it would not be new!
|
if (mountPointDriveLetterBtn.equals(newToggle)) {
|
||||||
if (mountPointDriveLetterBtn.equals(newToggle)) {
|
vaultSettings.mountPoint().set(windowsDriveLetters.getFirstDesiredAvailable().orElse(windowsDriveLetters.getAll().stream().findAny().get()));
|
||||||
mountPointToBe = driveLetterSelection.getItems().get(0);
|
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint());
|
||||||
} else if (mountPointDirBtn.equals(newToggle)) {
|
} else if (mountPointDirBtn.equals(newToggle)) {
|
||||||
mountPointToBe = chooseCustomMountPointInternal();
|
try {
|
||||||
}
|
vaultSettings.mountPoint().set(chooseCustomMountPointInternal());
|
||||||
vault.getVaultSettings().mountPoint().set(mountPointToBe);
|
} catch (NoDirSelectedException e) {
|
||||||
} catch (NoDirSelectedException e) {
|
if (oldToggle != null && !mountPointDirBtn.equals(oldToggle)) {
|
||||||
if (!mountPointDirBtn.equals(oldToggle)) {
|
mountPointToggleGroup.selectToggle(oldToggle);
|
||||||
mountPointToggleGroup.selectToggle(oldToggle);
|
} else {
|
||||||
|
mountPointToggleGroup.selectToggle(mountPointAutoBtn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
vaultSettings.mountPoint().set(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isDriveLetter(Path mountPoint) {
|
private boolean isDriveLetter(Path mountPoint) {
|
||||||
if (mountPoint != null) {
|
if (mountPoint != null) {
|
||||||
var s = mountPoint.toString();
|
var s = mountPoint.toString();
|
||||||
return s.length() == 3 && mountPoint.toString().endsWith(":\\");
|
return s.length() == 3 && s.endsWith(":\\");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -237,21 +260,13 @@ public class MountOptionsController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ObservableValue<Boolean> readOnlySupportedProperty() {
|
public ObservableValue<Boolean> readOnlySupportedProperty() {
|
||||||
return mountpointDriveLetterSupported;
|
return readOnlySupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReadOnlySupported() {
|
public boolean isReadOnlySupported() {
|
||||||
return readOnlySupported.getValue();
|
return readOnlySupported.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableValue<Path> driveLetterProperty() {
|
|
||||||
return driveLetter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getDriveLetter() {
|
|
||||||
return driveLetter.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableValue<String> directoryPathProperty() {
|
public ObservableValue<String> directoryPathProperty() {
|
||||||
return directoryPath;
|
return directoryPath;
|
||||||
}
|
}
|
||||||
@@ -259,5 +274,4 @@ public class MountOptionsController implements FxController {
|
|||||||
public String getDirectoryPath() {
|
public String getDirectoryPath() {
|
||||||
return directoryPath.getValue();
|
return directoryPath.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,16 +204,6 @@
|
|||||||
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
|
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-window .drag-n-drop-indicator {
|
|
||||||
-fx-border-color: SECONDARY;
|
|
||||||
-fx-border-width: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-window .drag-n-drop-indicator .drag-n-drop-header {
|
|
||||||
-fx-background-color: SECONDARY;
|
|
||||||
-fx-padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* TabPane *
|
* TabPane *
|
||||||
@@ -884,3 +874,51 @@
|
|||||||
-fx-fill: linear-gradient(to bottom, PRIMARY, transparent);
|
-fx-fill: linear-gradient(to bottom, PRIMARY, transparent);
|
||||||
-fx-stroke: transparent;
|
-fx-stroke: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* Drag and Drop *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.drag-n-drop-border {
|
||||||
|
-fx-border-color: SECONDARY;
|
||||||
|
-fx-border-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.drag-n-drop {
|
||||||
|
-fx-background-color: CONTROL_BG_NORMAL;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 1.4em 1em 1.4em 1em;
|
||||||
|
-fx-text-fill: TEXT_FILL_MUTED;
|
||||||
|
-fx-font-size: 0.8em;
|
||||||
|
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||||
|
-fx-border-radius: 4px;
|
||||||
|
-fx-border-style: dashed inside;
|
||||||
|
-fx-border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.drag-n-drop:focused {
|
||||||
|
-fx-border-color: CONTROL_BORDER_FOCUSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.drag-n-drop:armed {
|
||||||
|
-fx-background-color: CONTROL_BG_ARMED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.drag-n-drop.active {
|
||||||
|
-fx-border-color: SECONDARY;
|
||||||
|
-fx-border-style: solid inside;
|
||||||
|
-fx-border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* Separator *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
-fx-padding: 0.5px;
|
||||||
|
-fx-background-color: CONTROL_BORDER_NORMAL;
|
||||||
|
}
|
||||||
|
|||||||
@@ -203,16 +203,6 @@
|
|||||||
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
|
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-window .drag-n-drop-indicator {
|
|
||||||
-fx-border-color: SECONDARY;
|
|
||||||
-fx-border-width: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-window .drag-n-drop-indicator .drag-n-drop-header {
|
|
||||||
-fx-background-color: SECONDARY;
|
|
||||||
-fx-padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* TabPane *
|
* TabPane *
|
||||||
@@ -883,3 +873,51 @@
|
|||||||
-fx-fill: linear-gradient(to bottom, PRIMARY, transparent);
|
-fx-fill: linear-gradient(to bottom, PRIMARY, transparent);
|
||||||
-fx-stroke: transparent;
|
-fx-stroke: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* Drag and Drop *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.drag-n-drop-border {
|
||||||
|
-fx-border-color: SECONDARY;
|
||||||
|
-fx-border-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.drag-n-drop {
|
||||||
|
-fx-background-color: CONTROL_BG_NORMAL;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 1.4em 1em 1.4em 1em;
|
||||||
|
-fx-text-fill: TEXT_FILL_MUTED;
|
||||||
|
-fx-font-size: 0.8em;
|
||||||
|
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||||
|
-fx-border-radius: 4px;
|
||||||
|
-fx-border-style: dashed inside;
|
||||||
|
-fx-border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.drag-n-drop:focused {
|
||||||
|
-fx-border-color: CONTROL_BORDER_FOCUSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.drag-n-drop:armed {
|
||||||
|
-fx-background-color: CONTROL_BG_ARMED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.drag-n-drop.active {
|
||||||
|
-fx-border-color: SECONDARY;
|
||||||
|
-fx-border-style: solid inside;
|
||||||
|
-fx-border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* Separator *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
-fx-padding: 0.5px;
|
||||||
|
-fx-background-color: CONTROL_BORDER_NORMAL;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,25 +5,59 @@
|
|||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import org.cryptomator.ui.health.CheckStateIconView?>
|
<?import org.cryptomator.ui.health.CheckStateIconView?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.control.ChoiceBox?>
|
||||||
|
<?import javafx.scene.control.ContextMenu?>
|
||||||
|
<?import javafx.scene.control.MenuItem?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.health.CheckDetailController"
|
fx:controller="org.cryptomator.ui.health.CheckDetailController"
|
||||||
spacing="6">
|
spacing="12">
|
||||||
<Label fx:id="detailHeader" styleClass="label-extra-large" text="${controller.checkName}" contentDisplay="LEFT">
|
<HBox alignment="CENTER" >
|
||||||
<graphic>
|
<VBox spacing="12">
|
||||||
<HBox alignment="CENTER" minWidth="25" maxWidth="25">
|
<Label fx:id="detailHeader" styleClass="label-extra-large" text="${controller.checkName}" contentDisplay="RIGHT">
|
||||||
<CheckStateIconView fx:id="checkStateIconView" check="${controller.check}" glyphSize="20"/>
|
<graphic>
|
||||||
</HBox>
|
<HBox alignment="CENTER" minWidth="25" maxWidth="25">
|
||||||
</graphic>
|
<CheckStateIconView check="${controller.check}" glyphSize="20"/>
|
||||||
</Label>
|
</HBox>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
<Label text="%health.check.detail.checkRunning" visible="${controller.checkRunning}" managed="${controller.checkRunning}"/>
|
||||||
|
<Label text="%health.check.detail.checkScheduled" visible="${controller.checkScheduled}" managed="${controller.checkScheduled}"/>
|
||||||
|
<Label text="%health.check.detail.checkSkipped" visible="${controller.checkSkipped}" managed="${controller.checkSkipped}"/>
|
||||||
|
<Label text="%health.check.detail.checkCancelled" visible="${controller.checkCancelled}" managed="${controller.checkCancelled}"/>
|
||||||
|
<Label text="%health.check.detail.checkFailed" visible="${controller.checkFailed}" managed="${controller.checkFailed}"/>
|
||||||
|
<Label text="%health.check.detail.checkFinished" visible="${controller.checkSucceeded && !controller.warnOrCritsExist}" managed="${controller.checkSucceeded && !controller.warnOrCritsExist}"/>
|
||||||
|
<Label text="%health.check.detail.checkFinishedAndFound" visible="${controller.checkSucceeded && controller.warnOrCritsExist}" managed="${controller.checkSucceeded && controller.warnOrCritsExist}"/>
|
||||||
|
</VBox>
|
||||||
|
<Region HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button text="%health.check.detail.fixAllSpecificBtn" contentDisplay="RIGHT" graphicTextGap="3" visible="${controller.checkFinished}" managed="${controller.checkFinished}" disable="${! controller.fixAllInfoResultsPossible}" onAction="#fixAllInfoResults">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="INFO_CIRCLE" glyphSize="12" styleClass="glyph-icon-muted"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Label text="%health.check.detail.checkRunning" visible="${controller.checkRunning}" managed="${controller.checkRunning}"/>
|
</HBox>
|
||||||
<Label text="%health.check.detail.checkScheduled" visible="${controller.checkScheduled}" managed="${controller.checkScheduled}"/>
|
<VBox spacing="6" VBox.vgrow="ALWAYS">
|
||||||
<Label text="%health.check.detail.checkSkipped" visible="${controller.checkSkipped}" managed="${controller.checkSkipped}"/>
|
<HBox alignment="CENTER_LEFT" spacing="6">
|
||||||
<Label text="%health.check.detail.checkCancelled" visible="${controller.checkCancelled}" managed="${controller.checkCancelled}"/>
|
<Label fx:id="filterLbl" text="%health.check.detail.listFilters.label">
|
||||||
<Label text="%health.check.detail.checkFailed" visible="${controller.checkFailed}" managed="${controller.checkFailed}"/>
|
<graphic>
|
||||||
<Label text="%health.check.detail.checkFinished" visible="${controller.checkSucceeded && !controller.warnOrCritsExist}" managed="${controller.checkSucceeded && !controller.warnOrCritsExist}"/>
|
<FontAwesome5IconView glyph="FUNNEL" glyphSize="${filterLbl.height}" styleClass="glyph-icon-muted"/>
|
||||||
<Label text="%health.check.detail.checkFinishedAndFound" visible="${controller.checkSucceeded && controller.warnOrCritsExist}" managed="${controller.checkSucceeded && controller.warnOrCritsExist}"/>
|
</graphic>
|
||||||
|
</Label>
|
||||||
<ListView fx:id="resultsListView" VBox.vgrow="ALWAYS" visible="${!controller.checkSkipped}" fixedCellSize="25"/>
|
<ChoiceBox fx:id="severityChoiceBox" />
|
||||||
|
<ChoiceBox fx:id="fixStateChoiceBox" />
|
||||||
|
</HBox>
|
||||||
|
<ListView fx:id="resultsListView" VBox.vgrow="ALWAYS" visible="${!controller.checkSkipped}" fixedCellSize="25">
|
||||||
|
<contextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<items>
|
||||||
|
<MenuItem text="%generic.button.copy" onAction="#copyResultDetails"/>
|
||||||
|
</items>
|
||||||
|
</ContextMenu>
|
||||||
|
</contextMenu>
|
||||||
|
</ListView>
|
||||||
|
</VBox>
|
||||||
</VBox>
|
</VBox>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.health.CheckListController"
|
fx:controller="org.cryptomator.ui.health.CheckListController"
|
||||||
prefWidth="600"
|
prefWidth="650"
|
||||||
prefHeight="400"
|
prefHeight="400"
|
||||||
spacing="12">
|
spacing="12">
|
||||||
<padding>
|
<padding>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.health.StartController"
|
fx:controller="org.cryptomator.ui.health.StartController"
|
||||||
prefWidth="600"
|
prefWidth="650"
|
||||||
prefHeight="400"
|
prefHeight="400"
|
||||||
spacing="12">
|
spacing="12">
|
||||||
<padding>
|
<padding>
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.Group?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
fx:controller="org.cryptomator.ui.keyloading.hub.NoKeychainController"
|
||||||
|
minWidth="400"
|
||||||
|
maxWidth="400"
|
||||||
|
minHeight="145"
|
||||||
|
spacing="12"
|
||||||
|
alignment="TOP_LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="12"/>
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<Group>
|
||||||
|
<StackPane>
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="6"/>
|
||||||
|
</padding>
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<VBox HBox.hgrow="ALWAYS">
|
||||||
|
<Label styleClass="label-large" text="%hub.noKeychain.message" wrapText="true" textAlignment="LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" top="6"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<FormattedLabel format="%hub.noKeychain.description" arg1="%preferences.general.keychainBackend" wrapText="true" textAlignment="LEFT"/>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="false" cancelButton="true" onAction="#cancel"/>
|
||||||
|
<Button text="%hub.noKeychain.openBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#openPreferences"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
|
||||||
<?import javafx.scene.control.SplitPane?>
|
<?import javafx.scene.control.SplitPane?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
|
||||||
<?import javafx.scene.layout.Region?>
|
|
||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
@@ -14,24 +10,10 @@
|
|||||||
styleClass="main-window">
|
styleClass="main-window">
|
||||||
<VBox minWidth="650">
|
<VBox minWidth="650">
|
||||||
<fx:include source="main_window_title.fxml" VBox.vgrow="NEVER"/>
|
<fx:include source="main_window_title.fxml" VBox.vgrow="NEVER"/>
|
||||||
<StackPane VBox.vgrow="ALWAYS">
|
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL" VBox.vgrow="ALWAYS">
|
||||||
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL">
|
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
|
||||||
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
|
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
|
||||||
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
|
</SplitPane>
|
||||||
</SplitPane>
|
|
||||||
|
|
||||||
<VBox styleClass="drag-n-drop-indicator" visible="${controller.draggingOver}" alignment="TOP_CENTER">
|
|
||||||
<HBox visible="${!controller.draggingVaultOver}" managed="${!controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
|
|
||||||
<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
|
|
||||||
<Label text="%main.dropZone.unknownDragboardContent"/>
|
|
||||||
</HBox>
|
|
||||||
<HBox visible="${controller.draggingVaultOver}" managed="${controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
|
|
||||||
<FontAwesome5IconView glyph="CHECK"/>
|
|
||||||
<Label text="%main.dropZone.dropVault"/>
|
|
||||||
</HBox>
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
|
||||||
</VBox>
|
|
||||||
</StackPane>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
<fx:include source="main_window_resize.fxml"/>
|
<fx:include source="main_window_resize.fxml"/>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
|
|||||||
@@ -6,16 +6,20 @@
|
|||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.ButtonBar?>
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
<?import javafx.scene.control.CheckBox?>
|
<?import javafx.scene.control.CheckBox?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.shape.Circle?>
|
<?import javafx.scene.shape.Circle?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.migration.MigrationStartController"
|
fx:controller="org.cryptomator.ui.migration.MigrationStartController"
|
||||||
minWidth="400"
|
prefWidth="580"
|
||||||
maxWidth="400"
|
prefHeight="350"
|
||||||
minHeight="145"
|
|
||||||
spacing="12">
|
spacing="12">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets topRightBottomLeft="12"/>
|
<Insets topRightBottomLeft="12"/>
|
||||||
@@ -23,13 +27,47 @@
|
|||||||
<children>
|
<children>
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT" VBox.vgrow="ALWAYS">
|
<HBox spacing="12" alignment="CENTER_LEFT" VBox.vgrow="ALWAYS">
|
||||||
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
<padding>
|
||||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE_IMPORT" glyphSize="24"/>
|
<Insets left="12"/>
|
||||||
|
</padding>
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="48"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE_IMPORT" glyphSize="48"/>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
|
|
||||||
<VBox spacing="6" HBox.hgrow="ALWAYS">
|
<VBox HBox.hgrow="ALWAYS" alignment="CENTER">
|
||||||
<FormattedLabel format="%migration.start.prompt" arg1="${controller.vault.displayName}" wrapText="true"/>
|
<padding>
|
||||||
<CheckBox fx:id="confirmSyncDone" text="%migration.start.confirm"/>
|
<Insets top="0" right="12" bottom="0" left="12"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="%migration.start.header" styleClass="label-extra-large"/>
|
||||||
|
<Region minHeight="15"/>
|
||||||
|
<VBox>
|
||||||
|
<FormattedLabel format="%migration.start.text" arg1="${controller.vault.displayName}" wrapText="true"/>
|
||||||
|
<GridPane alignment="CENTER_LEFT" >
|
||||||
|
<padding>
|
||||||
|
<Insets left="6"/>
|
||||||
|
</padding>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints minWidth="20" halignment="LEFT"/>
|
||||||
|
<ColumnConstraints fillWidth="true"/>
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints valignment="TOP"/>
|
||||||
|
<RowConstraints valignment="TOP"/>
|
||||||
|
<RowConstraints valignment="TOP"/>
|
||||||
|
<RowConstraints valignment="TOP"/>
|
||||||
|
</rowConstraints>
|
||||||
|
<Label text="1." GridPane.rowIndex="0" GridPane.columnIndex="0" />
|
||||||
|
<Label text="%migration.start.remarkUndone" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="1" />
|
||||||
|
<Label text="2." GridPane.rowIndex="1" GridPane.columnIndex="0" />
|
||||||
|
<Label text="%migration.start.remarkVersions" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="1" />
|
||||||
|
<Label text="3." GridPane.rowIndex="2" GridPane.columnIndex="0" />
|
||||||
|
<Label text="%migration.start.remarkCanRun" wrapText="true" GridPane.rowIndex="2" GridPane.columnIndex="1" />
|
||||||
|
<Label text="4." GridPane.rowIndex="3" GridPane.columnIndex="0" />
|
||||||
|
<Label text="%migration.start.remarkSynced" wrapText="true" GridPane.rowIndex="3" GridPane.columnIndex="1" />
|
||||||
|
</GridPane>
|
||||||
|
<Region minHeight="15"/>
|
||||||
|
<CheckBox fx:id="confirmSyncDone" text="%migration.start.confirm"/>
|
||||||
|
</VBox>
|
||||||
</VBox>
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</ImageView>
|
</ImageView>
|
||||||
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
|
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
|
||||||
<FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/>
|
<FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/>
|
||||||
<Label text="© 2016 – 2022 Skymatic GmbH"/>
|
<Label text="© 2016 – 2023 Skymatic GmbH"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
<?import org.cryptomator.ui.controls.NumericTextField?>
|
<?import org.cryptomator.ui.controls.NumericTextField?>
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.ChoiceBox?>
|
<?import javafx.scene.control.ChoiceBox?>
|
||||||
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.Separator?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.preferences.VolumePreferencesController"
|
fx:controller="org.cryptomator.ui.preferences.VolumePreferencesController"
|
||||||
@@ -18,12 +22,51 @@
|
|||||||
<HBox spacing="12" alignment="CENTER_LEFT">
|
<HBox spacing="12" alignment="CENTER_LEFT">
|
||||||
<Label text="%preferences.volume.type"/>
|
<Label text="%preferences.volume.type"/>
|
||||||
<ChoiceBox fx:id="volumeTypeChoiceBox"/>
|
<ChoiceBox fx:id="volumeTypeChoiceBox"/>
|
||||||
|
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%preferences.volume.docsTooltip" showDelay="100ms"/>
|
||||||
|
</tooltip>
|
||||||
|
</Hyperlink>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}">
|
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}" managed="${controller.loopbackPortSupported}">
|
||||||
<Label text="%preferences.volume.tcp.port"/>
|
<Label text="%preferences.volume.tcp.port"/>
|
||||||
<NumericTextField fx:id="loopbackPortField"/>
|
<NumericTextField fx:id="loopbackPortField"/>
|
||||||
<Button text="%generic.button.apply" fx:id="loopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
|
<Button text="%generic.button.apply" fx:id="loopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
<Separator orientation="HORIZONTAL"/>
|
||||||
|
|
||||||
|
<Label text="%preferences.volume.supportedFeatures"/>
|
||||||
|
<VBox spacing="12">
|
||||||
|
<Label text="%preferences.volume.feature.mountAuto">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="CHECK"/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
<Label text="%preferences.volume.feature.mountToDir" visible="${controller.mountToDirSupported}" managed="${controller.mountToDirSupported}" contentDisplay="LEFT">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="CHECK"/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
<Label text="%preferences.volume.feature.mountToDriveLetter" visible="${controller.mountToDriveLetterSupported}" managed="${controller.mountToDriveLetterSupported}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="CHECK"/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
<Label text="%preferences.volume.feature.mountFlags" visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="CHECK"/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
<Label text="%preferences.volume.feature.readOnly" visible="${controller.readonlySupported}" managed="${controller.readonlySupported}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="CHECK"/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
</VBox>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.shape.Arc?>
|
<?import javafx.scene.shape.Arc?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
<HBox xmlns="http://javafx.com/javafx"
|
<HBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.stats.VaultStatisticsController"
|
fx:controller="org.cryptomator.ui.stats.VaultStatisticsController"
|
||||||
@@ -22,18 +23,17 @@
|
|||||||
</padding>
|
</padding>
|
||||||
|
|
||||||
<!-- Caching -->
|
<!-- Caching -->
|
||||||
<VBox prefWidth="200" prefHeight="200">
|
<StackPane prefWidth="200" prefHeight="200">
|
||||||
<StackPane>
|
<Group>
|
||||||
<Group>
|
<Circle centerX="100" centerY="100" radius="100" opacity="0"/> <!-- for alignment. visible=false does not yield the desired result -->
|
||||||
<Arc styleClass="cache-arc-background" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="-270"/>
|
<Arc styleClass="cache-arc-background" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="-270"/>
|
||||||
<Arc styleClass="cache-arc-foreground" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="${controller.cacheHitDegrees}"/>
|
<Arc styleClass="cache-arc-foreground" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="${controller.cacheHitDegrees}"/>
|
||||||
</Group>
|
</Group>
|
||||||
<VBox StackPane.alignment="CENTER" alignment="CENTER">
|
<VBox StackPane.alignment="CENTER" alignment="CENTER">
|
||||||
<FormattedLabel styleClass="label-extra-large" format="\%1.0f %%" arg1="${controller.cacheHitPercentage}"/>
|
<FormattedLabel styleClass="label-extra-large" format="\%1.0f %%" arg1="${controller.cacheHitPercentage}"/>
|
||||||
<Label text="%stats.cacheHitRate"/>
|
<Label text="%stats.cacheHitRate"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
</VBox>
|
|
||||||
|
|
||||||
<!-- Read -->
|
<!-- Read -->
|
||||||
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
|
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
|
||||||
@@ -72,4 +72,22 @@
|
|||||||
<DataLabel byteFormat="%stats.encr.total.data.none" kibFormat="%stats.encr.total.data.kib" mibFormat="%stats.encr.total.data.mib" gibFormat="%stats.encr.total.data.gib" dataInBytes="${controller.totalBytesEncrypted}"/>
|
<DataLabel byteFormat="%stats.encr.total.data.none" kibFormat="%stats.encr.total.data.kib" mibFormat="%stats.encr.total.data.mib" gibFormat="%stats.encr.total.data.gib" dataInBytes="${controller.totalBytesEncrypted}"/>
|
||||||
<FormattedLabel format="%stats.write.accessCount" arg1="${controller.filesWritten}"/>
|
<FormattedLabel format="%stats.write.accessCount" arg1="${controller.filesWritten}"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
<!-- Access -->
|
||||||
|
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
|
||||||
|
<FormattedLabel styleClass="label-large" format="%stats.access.current" arg1="${controller.filesAccessed}"/>
|
||||||
|
<AreaChart fx:id="accessChart" styleClass="io-stats" createSymbols="false" animated="false">
|
||||||
|
<xAxis>
|
||||||
|
<NumberAxis fx:id="accessChartXAxis" styleClass="io-stats" autoRanging="false" forceZeroInRange="false" side="BOTTOM"/>
|
||||||
|
</xAxis>
|
||||||
|
<yAxis>
|
||||||
|
<NumberAxis fx:id="accessChartYAxis" styleClass="io-stats" autoRanging="true" forceZeroInRange="true" side="LEFT" tickUnit="Infinity"/>
|
||||||
|
</yAxis>
|
||||||
|
<cursor>
|
||||||
|
<Cursor fx:constant="DEFAULT"/>
|
||||||
|
</cursor>
|
||||||
|
</AreaChart>
|
||||||
|
<FormattedLabel format="%stats.access.total" arg1="${controller.totalFilesAccessed}"/>
|
||||||
|
<Label/> <!-- to align with other graphs -->
|
||||||
|
<Label/> <!-- to align with other graphs -->
|
||||||
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|||||||
@@ -34,16 +34,19 @@
|
|||||||
</StackPane>
|
</StackPane>
|
||||||
</Group>
|
</Group>
|
||||||
<VBox HBox.hgrow="ALWAYS">
|
<VBox HBox.hgrow="ALWAYS">
|
||||||
<Label styleClass="label-large" text="%unlock.error.message" wrapText="true" textAlignment="LEFT">
|
<Label styleClass="label-large" text="%unlock.error.customPath.message" wrapText="true" textAlignment="LEFT">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="6" top="6"/>
|
<Insets bottom="6" top="6"/>
|
||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
|
<FormattedLabel fx:id="dialogDescription" wrapText="true" textAlignment="LEFT"/>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||||
|
<Button text="%hub.noKeychain.openBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#closeAndOpenPreferences"/>
|
||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
<?import org.cryptomator.ui.controls.ThroughputLabel?>
|
<?import org.cryptomator.ui.controls.ThroughputLabel?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.Tooltip?>
|
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.Region?>
|
<?import javafx.scene.layout.Region?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnlockedController"
|
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnlockedController"
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
<FontAwesome5IconView glyph="HDD" glyphSize="24"/>
|
<FontAwesome5IconView glyph="HDD" glyphSize="24"/>
|
||||||
<VBox spacing="4" alignment="CENTER_LEFT">
|
<VBox spacing="4" alignment="CENTER_LEFT">
|
||||||
<Label text="%main.vaultDetail.revealBtn"/>
|
<Label text="%main.vaultDetail.revealBtn"/>
|
||||||
|
<Label styleClass="label-extra-small" text="${controller.mountPoint}" textOverrun="CENTER_ELLIPSIS"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
</graphic>
|
</graphic>
|
||||||
@@ -27,8 +29,8 @@
|
|||||||
<HBox spacing="12" alignment="CENTER">
|
<HBox spacing="12" alignment="CENTER">
|
||||||
<FontAwesome5IconView glyph="LINK" glyphSize="24"/>
|
<FontAwesome5IconView glyph="LINK" glyphSize="24"/>
|
||||||
<VBox spacing="4" alignment="CENTER_LEFT">
|
<VBox spacing="4" alignment="CENTER_LEFT">
|
||||||
<Label text="%generic.button.copy"/> <!-- TODO -->
|
<Label text="%main.vaultDetail.copyUri"/>
|
||||||
<Label styleClass="label-extra-small" text="${controller.mountUri}" textOverrun="CENTER_ELLIPSIS"/>
|
<Label styleClass="label-extra-small" text="${controller.mountPoint}" textOverrun="CENTER_ELLIPSIS"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
</graphic>
|
</graphic>
|
||||||
@@ -41,7 +43,28 @@
|
|||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
<HBox alignment="BOTTOM_RIGHT">
|
<HBox alignment="BOTTOM_CENTER">
|
||||||
|
<HBox visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="0"/>
|
||||||
|
</padding>
|
||||||
|
<Button fx:id="dropZone" styleClass="drag-n-drop" text="%main.vaultDetail.locateEncryptedFileBtn" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${!controller.ciphertextPathsCopied}" managed="${!controller.ciphertextPathsCopied}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="FILE_DOWNLOAD" glyphSize="15"/>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%main.vaultDetail.locateEncryptedFileBtn.tooltip"/>
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
<Button styleClass="drag-n-drop" text="%main.vaultDetail.encryptedPathsCopied" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${controller.ciphertextPathsCopied}" managed="${controller.ciphertextPathsCopied}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Region HBox.hgrow="ALWAYS"/>
|
||||||
|
|
||||||
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM">
|
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM">
|
||||||
<graphic>
|
<graphic>
|
||||||
<VBox spacing="6">
|
<VBox spacing="6">
|
||||||
|
|||||||
@@ -8,25 +8,29 @@
|
|||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.shape.Arc?>
|
<?import javafx.scene.shape.Arc?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.mainwindow.VaultListController"
|
fx:id="root"
|
||||||
minWidth="206">
|
fx:controller="org.cryptomator.ui.mainwindow.VaultListController"
|
||||||
<StackPane VBox.vgrow="ALWAYS">
|
minWidth="206">
|
||||||
<ListView fx:id="vaultList" editable="true" fixedCellSize="60">
|
<VBox>
|
||||||
<contextMenu>
|
<StackPane VBox.vgrow="ALWAYS">
|
||||||
<fx:include source="vault_list_contextmenu.fxml"/>
|
<ListView fx:id="vaultList" editable="true" fixedCellSize="60">
|
||||||
</contextMenu>
|
<contextMenu>
|
||||||
</ListView>
|
<fx:include source="vault_list_contextmenu.fxml"/>
|
||||||
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
|
</contextMenu>
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
</ListView>
|
||||||
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" wrapText="true"/>
|
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
|
||||||
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
</VBox>
|
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" wrapText="true"/>
|
||||||
</StackPane>
|
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
|
||||||
<Button styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" onAction="#didClickAddVault" alignment="BASELINE_CENTER" maxWidth="Infinity">
|
</VBox>
|
||||||
<graphic>
|
</StackPane>
|
||||||
<FontAwesome5IconView glyph="PLUS"/>
|
<Button styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" onAction="#didClickAddVault" alignment="BASELINE_CENTER" maxWidth="Infinity">
|
||||||
</graphic>
|
<graphic>
|
||||||
</Button>
|
<FontAwesome5IconView glyph="PLUS"/>
|
||||||
</VBox>
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</VBox>
|
||||||
|
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
|
||||||
|
</StackPane>
|
||||||
|
|||||||
@@ -5,12 +5,15 @@
|
|||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.CheckBox?>
|
<?import javafx.scene.control.CheckBox?>
|
||||||
<?import javafx.scene.control.ChoiceBox?>
|
<?import javafx.scene.control.ChoiceBox?>
|
||||||
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.RadioButton?>
|
<?import javafx.scene.control.RadioButton?>
|
||||||
<?import javafx.scene.control.TextField?>
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.control.ToggleGroup?>
|
<?import javafx.scene.control.ToggleGroup?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
<?import javafx.scene.text.TextFlow?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
|
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
|
||||||
@@ -22,7 +25,11 @@
|
|||||||
<Insets topRightBottomLeft="12"/>
|
<Insets topRightBottomLeft="12"/>
|
||||||
</padding>
|
</padding>
|
||||||
<children>
|
<children>
|
||||||
<Label text="Options depend on the selected volume provider in the general preferences"/>
|
<TextFlow>
|
||||||
|
<Text text="%vaultOptions.mount.info" styleClass="label"/>
|
||||||
|
<Text text=" "/>
|
||||||
|
<Hyperlink styleClass="hyperlink-underline" text="%vaultOptions.mount.linkToPreferences" onAction="#openVolumePreferences" wrapText="true"/>
|
||||||
|
</TextFlow>
|
||||||
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlySupported}" managed="${controller.readOnlySupported}"/>
|
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlySupported}" managed="${controller.readOnlySupported}"/>
|
||||||
|
|
||||||
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
|
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
|
||||||
@@ -44,22 +51,24 @@
|
|||||||
|
|
||||||
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
|
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
|
||||||
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
|
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
|
||||||
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}" value="${controller.driveLetter}"/>
|
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<HBox spacing="6" alignment="CENTER_LEFT" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
|
<VBox spacing="6" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
|
||||||
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
|
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
|
||||||
<graphic>
|
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
|
||||||
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
<graphic>
|
||||||
</graphic>
|
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
||||||
</Button>
|
</graphic>
|
||||||
</HBox>
|
</Button>
|
||||||
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false" >
|
</HBox>
|
||||||
<VBox.margin>
|
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false">
|
||||||
<Insets left="24"/>
|
<VBox.margin>
|
||||||
</VBox.margin>
|
<Insets left="24"/>
|
||||||
</TextField>
|
</VBox.margin>
|
||||||
|
</TextField>
|
||||||
|
</VBox>
|
||||||
</children>
|
</children>
|
||||||
|
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user