mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 07:54:19 +00:00
Compare commits
216
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -13,40 +13,35 @@ 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
|
||||||
@@ -60,7 +55,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 +64,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 +78,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 +93,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 +111,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 +125,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,13 +137,21 @@ 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
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
|
tag_name: ${{ inputs.ref }}
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
files: |
|
files: |
|
||||||
cryptomator_*_amd64.deb
|
cryptomator_*_amd64.deb
|
||||||
|
|||||||
@@ -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,36 +36,26 @@ 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
|
||||||
@@ -73,7 +69,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 +87,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 +115,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 +150,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 +204,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 +215,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,40 +19,37 @@ 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
|
||||||
@@ -66,7 +63,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 +81,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 +95,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 +105,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 +131,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 +156,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 }}"
|
||||||
--win-menu
|
--win-menu
|
||||||
--win-dir-chooser
|
--win-dir-chooser
|
||||||
--win-shortcut-prompt
|
--win-shortcut-prompt
|
||||||
@@ -152,7 +169,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 +178,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 +203,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 +252,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 +269,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 +283,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 +292,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 +325,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 +340,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
+2
@@ -10,4 +10,6 @@ java \
|
|||||||
-Djdk.gtk.version=2 \
|
-Djdk.gtk.version=2 \
|
||||||
-Xss2m \
|
-Xss2m \
|
||||||
-Xmx512m \
|
-Xmx512m \
|
||||||
|
--enable-preview \
|
||||||
|
--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64 \
|
||||||
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
-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
+2
@@ -9,4 +9,6 @@ java \
|
|||||||
-Dcryptomator.mountPointsDir="/Volumes" \
|
-Dcryptomator.mountPointsDir="/Volumes" \
|
||||||
-Xss20m \
|
-Xss20m \
|
||||||
-Xmx512m \
|
-Xmx512m \
|
||||||
|
--enable-preview \
|
||||||
|
--enable-native-access=org.cryptomator.jfuse.mac \
|
||||||
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
-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
+12
@@ -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"
|
||||||
|
|||||||
+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
+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^
|
||||||
|
|||||||
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
|
||||||
+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
+16
-11
@@ -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
|
||||||
@@ -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>
|
||||||
|
|||||||
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-beta1</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-beta1</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>2.0.0-beta1</cryptomator.fuse.version>
|
<cryptomator.fuse.version>2.0.0-beta4</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-beta4</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>
|
||||||
|
|||||||
@@ -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,138 @@
|
|||||||
|
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 java.util.Objects;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (userChosenMountPoint == null) {
|
||||||
|
if (service.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
|
||||||
|
// 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 {
|
||||||
|
if (canMountToParent && !canMountToDir) {
|
||||||
|
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
|
||||||
|
cleanup = () -> {
|
||||||
|
MountWithinParentUtil.cleanup(userChosenMountPoint);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
builder.setMountpoint(userChosenMountPoint);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
|
||||||
|
var configNotSupported = (!canMountToDriveLetter && mpIsDriveLetter) || (!canMountToDir && !mpIsDriveLetter) || (!canMountToParent && !mpIsDriveLetter);
|
||||||
|
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;
|
||||||
@@ -73,7 +74,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 +108,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;
|
||||||
}
|
}
|
||||||
|
|||||||
+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"), //
|
||||||
|
|||||||
@@ -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,118 @@
|
|||||||
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 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, 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.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 +126,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 +141,77 @@ 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);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revealOrCopyPaths(List<Path> paths) {
|
||||||
|
if (!revealPaths(paths)) {
|
||||||
|
LOG.warn("No service provider to reveal files found.");
|
||||||
|
copyPathsToClipboard(paths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveals the paths over the {@link RevealPathService} in the file system
|
||||||
|
*
|
||||||
|
* @param paths List of Paths to reveal
|
||||||
|
* @return true, if at least one service provider was present, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean revealPaths(List<Path> paths) {
|
||||||
|
return RevealPathService.get().findAny().map(s -> {
|
||||||
|
paths.forEach(path -> {
|
||||||
|
try {
|
||||||
|
s.reveal(path);
|
||||||
|
} catch (RevealFailedException e) {
|
||||||
|
LOG.error("Revealing ciphertext file failed.", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +238,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,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -14,39 +15,54 @@ 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 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 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, List<MountService> mountProviders, ResourceBundle resourceBundle) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
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 +91,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
|
||||||
@@ -89,6 +141,4 @@ public class VolumePreferencesController implements FxController {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,10 +1,12 @@
|
|||||||
<?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.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?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
@@ -20,10 +22,41 @@
|
|||||||
<ChoiceBox fx:id="volumeTypeChoiceBox"/>
|
<ChoiceBox fx:id="volumeTypeChoiceBox"/>
|
||||||
</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"/>
|
||||||
|
<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>
|
||||||
|
|||||||
@@ -124,12 +124,14 @@ unlock.success.description=Content in vault "%s" is now accessible over its moun
|
|||||||
unlock.success.rememberChoice=Remember my choice, don't ask again
|
unlock.success.rememberChoice=Remember my choice, don't ask again
|
||||||
unlock.success.revealBtn=Reveal Drive
|
unlock.success.revealBtn=Reveal Drive
|
||||||
## Failure
|
## Failure
|
||||||
unlock.error.message=Unable to unlock vault
|
unlock.error.customPath.message=Unable to mount vault to custom path
|
||||||
### Invalid Mount Point
|
unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point.
|
||||||
unlock.error.invalidMountPoint.notExisting=Mount point "%s" is not a directory, not empty or does not exist.
|
unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options.
|
||||||
unlock.error.invalidMountPoint.existing=Mount point "%s" already exists or parent folder is missing.
|
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %s
|
||||||
unlock.error.invalidMountPoint.driveLetterOccupied=Drive Letter "%s" is already in use.
|
|
||||||
## Hub
|
## Hub
|
||||||
|
hub.noKeychain.message=Unable to access device key
|
||||||
|
hub.noKeychain.description=In order to unlock Hub vaults, a device key is required, which is secured using a keychain. To proceed, enable “%s” and select a keychain in the preferences.
|
||||||
|
hub.noKeychain.openBtn=Open Preferences
|
||||||
### Waiting
|
### Waiting
|
||||||
hub.auth.message=Waiting for authentication…
|
hub.auth.message=Waiting for authentication…
|
||||||
hub.auth.description=You should automatically be redirected to the login page.
|
hub.auth.description=You should automatically be redirected to the login page.
|
||||||
@@ -170,8 +172,13 @@ lock.fail.description=Vault "%s" could not be locked. Ensure unsaved work is sav
|
|||||||
# Migration
|
# Migration
|
||||||
migration.title=Upgrade Vault
|
migration.title=Upgrade Vault
|
||||||
## Start
|
## Start
|
||||||
migration.start.prompt=Your vault "%s" needs to be updated to a newer format. Before proceeding, make sure there is no pending synchronization affecting this vault.
|
migration.start.header=Upgrade Vault
|
||||||
migration.start.confirm=Yes, my vault is fully synced
|
migration.start.text=In order to open your vault "%s" in this new version of Cryptomator, the vault needs to be upgraded to a newer format. Before doing this, you should know the following:
|
||||||
|
migration.start.remarkUndone=This upgrade cannot be undone.
|
||||||
|
migration.start.remarkVersions=Older versions of Cryptomator will not be able to open the upgraded vault.
|
||||||
|
migration.start.remarkCanRun=You must be sure that every device from which you access the vault can run this version of Cryptomator.
|
||||||
|
migration.start.remarkSynced=You must be sure that your vault is fully synced on this device, and on your other devices, before upgrading it.
|
||||||
|
migration.start.confirm=I have read and understood the above information
|
||||||
## Run
|
## Run
|
||||||
migration.run.enterPassword=Enter the password for "%s"
|
migration.run.enterPassword=Enter the password for "%s"
|
||||||
migration.run.startMigrationBtn=Migrate Vault
|
migration.run.startMigrationBtn=Migrate Vault
|
||||||
@@ -219,7 +226,25 @@ health.check.detail.checkFinished=The check finished successfully.
|
|||||||
health.check.detail.checkFinishedAndFound=The check finished running. Please review the results.
|
health.check.detail.checkFinishedAndFound=The check finished running. Please review the results.
|
||||||
health.check.detail.checkFailed=The check exited due to an error.
|
health.check.detail.checkFailed=The check exited due to an error.
|
||||||
health.check.detail.checkCancelled=The check was cancelled.
|
health.check.detail.checkCancelled=The check was cancelled.
|
||||||
|
health.check.detail.listFilters.label=Filter
|
||||||
|
health.check.detail.fixAllSpecificBtn=Fix all of type
|
||||||
health.check.exportBtn=Export Report
|
health.check.exportBtn=Export Report
|
||||||
|
## Result view
|
||||||
|
health.result.severityFilter.all=Severity - All
|
||||||
|
health.result.severityFilter.good=Good
|
||||||
|
health.result.severityFilter.info=Info
|
||||||
|
health.result.severityFilter.warn=Warning
|
||||||
|
health.result.severityFilter.crit=Critical
|
||||||
|
health.result.severityTip.good=Severity: Good\nNormal vault structure.
|
||||||
|
health.result.severityTip.info=Severity: Info\nVault structure intact, fix suggested.
|
||||||
|
health.result.severityTip.warn=Severity: Warning\nVault structure corrupted, fix highly advised.
|
||||||
|
health.result.severityTip.crit=Severity: Critical\nVault structure corrupted, data loss determined.
|
||||||
|
health.result.fixStateFilter.all=Fix state - All
|
||||||
|
health.result.fixStateFilter.fixable=Fixable
|
||||||
|
health.result.fixStateFilter.notFixable=Not fixable
|
||||||
|
health.result.fixStateFilter.fixing=Fixing…
|
||||||
|
health.result.fixStateFilter.fixed=Fixed
|
||||||
|
health.result.fixStateFilter.fixFailed=Fix failed
|
||||||
## Fix Application
|
## Fix Application
|
||||||
health.fix.fixBtn=Fix
|
health.fix.fixBtn=Fix
|
||||||
health.fix.successTip=Fix successful
|
health.fix.successTip=Fix successful
|
||||||
@@ -251,8 +276,15 @@ preferences.interface.showMinimizeButton=Show minimize button
|
|||||||
preferences.interface.showTrayIcon=Show tray icon (requires restart)
|
preferences.interface.showTrayIcon=Show tray icon (requires restart)
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Virtual Drive
|
preferences.volume=Virtual Drive
|
||||||
preferences.volume.type=Volume Type
|
preferences.volume.type=Volume Type (requires restart)
|
||||||
|
preferences.volume.type.automatic=Automatic
|
||||||
preferences.volume.tcp.port=TCP Port
|
preferences.volume.tcp.port=TCP Port
|
||||||
|
preferences.volume.supportedFeatures=The chosen volume type supports the following features:
|
||||||
|
preferences.volume.feature.mountAuto=Automatic mount point selection
|
||||||
|
preferences.volume.feature.mountToDir=Custom directory as mount point
|
||||||
|
preferences.volume.feature.mountToDriveLetter=Drive letter as mount point
|
||||||
|
preferences.volume.feature.mountFlags=Custom mount options
|
||||||
|
preferences.volume.feature.readOnly=Read-only mount
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=Updates
|
preferences.updates=Updates
|
||||||
preferences.updates.currentVersion=Current Version: %s
|
preferences.updates.currentVersion=Current Version: %s
|
||||||
@@ -300,15 +332,17 @@ stats.encr.total.data.mib=Data encrypted: %.1f MiB
|
|||||||
stats.encr.total.data.gib=Data encrypted: %.1f GiB
|
stats.encr.total.data.gib=Data encrypted: %.1f GiB
|
||||||
stats.write.accessCount=Total writes: %d
|
stats.write.accessCount=Total writes: %d
|
||||||
|
|
||||||
|
## Accesses
|
||||||
|
stats.access.current=Access: %d
|
||||||
|
stats.access.total=Total accesses: %d
|
||||||
|
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=Close
|
main.closeBtn.tooltip=Close
|
||||||
main.minimizeBtn.tooltip=Minimize
|
main.minimizeBtn.tooltip=Minimize
|
||||||
main.preferencesBtn.tooltip=Preferences
|
main.preferencesBtn.tooltip=Preferences
|
||||||
main.debugModeEnabled.tooltip=Debug mode is enabled
|
main.debugModeEnabled.tooltip=Debug mode is enabled
|
||||||
main.supporterCertificateMissing.tooltip=Please consider donating
|
main.supporterCertificateMissing.tooltip=Please consider donating
|
||||||
## Drag 'n' Drop
|
|
||||||
main.dropZone.dropVault=Add this vault
|
|
||||||
main.dropZone.unknownDragboardContent=If you want to add a vault, drag it to this window
|
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
|
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
|
||||||
main.vaultlist.contextMenu.remove=Remove…
|
main.vaultlist.contextMenu.remove=Remove…
|
||||||
@@ -331,6 +365,7 @@ main.vaultDetail.passwordSavedInKeychain=Password saved
|
|||||||
main.vaultDetail.unlockedStatus=UNLOCKED
|
main.vaultDetail.unlockedStatus=UNLOCKED
|
||||||
main.vaultDetail.accessLocation=Your vault's contents are accessible here:
|
main.vaultDetail.accessLocation=Your vault's contents are accessible here:
|
||||||
main.vaultDetail.revealBtn=Reveal Drive
|
main.vaultDetail.revealBtn=Reveal Drive
|
||||||
|
main.vaultDetail.copyUri=Copy URI
|
||||||
main.vaultDetail.lockBtn=Lock
|
main.vaultDetail.lockBtn=Lock
|
||||||
main.vaultDetail.bytesPerSecondRead=Read:
|
main.vaultDetail.bytesPerSecondRead=Read:
|
||||||
main.vaultDetail.bytesPerSecondWritten=Write:
|
main.vaultDetail.bytesPerSecondWritten=Write:
|
||||||
@@ -338,6 +373,10 @@ main.vaultDetail.throughput.idle=idle
|
|||||||
main.vaultDetail.throughput.kbps=%.1f kiB/s
|
main.vaultDetail.throughput.kbps=%.1f kiB/s
|
||||||
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
||||||
main.vaultDetail.stats=Vault Statistics
|
main.vaultDetail.stats=Vault Statistics
|
||||||
|
main.vaultDetail.locateEncryptedFileBtn=Locate Encrypted File
|
||||||
|
main.vaultDetail.locateEncryptedFileBtn.tooltip=Choose a file from your vault to locate its encrypted counterpart
|
||||||
|
main.vaultDetail.encryptedPathsCopied=Paths Copied to Clipboard!
|
||||||
|
main.vaultDetail.filePickerTitle=Select File Inside Vault
|
||||||
### Missing
|
### Missing
|
||||||
main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
|
main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
|
||||||
main.vaultDetail.missing.recheck=Recheck
|
main.vaultDetail.missing.recheck=Recheck
|
||||||
@@ -376,6 +415,8 @@ vaultOptions.general.startHealthCheckBtn=Start Health Check
|
|||||||
|
|
||||||
## Mount
|
## Mount
|
||||||
vaultOptions.mount=Mounting
|
vaultOptions.mount=Mounting
|
||||||
|
vaultOptions.mount.info=Options depend on the selected volume type.
|
||||||
|
vaultOptions.mount.linkToPreferences=Open virtual drive preferences
|
||||||
vaultOptions.mount.readonly=Read-only
|
vaultOptions.mount.readonly=Read-only
|
||||||
vaultOptions.mount.customMountFlags=Custom mount flags
|
vaultOptions.mount.customMountFlags=Custom mount flags
|
||||||
vaultOptions.mount.winDriveLetterOccupied=occupied
|
vaultOptions.mount.winDriveLetterOccupied=occupied
|
||||||
|
|||||||
@@ -103,10 +103,6 @@ unlock.success.description=تم فتح "%s" بنجاح! يمكنك الآن ال
|
|||||||
unlock.success.rememberChoice=تذكر اختياري ولا تظهر هذا مرة أخرى
|
unlock.success.rememberChoice=تذكر اختياري ولا تظهر هذا مرة أخرى
|
||||||
unlock.success.revealBtn=اظهار القرص
|
unlock.success.revealBtn=اظهار القرص
|
||||||
## Failure
|
## Failure
|
||||||
unlock.error.message=غير قادر على فتح الخزنة
|
|
||||||
### Invalid Mount Point
|
|
||||||
unlock.error.invalidMountPoint.notExisting=نقطة التحميل ليست مجلد فارغ أو غير موجودة: %s
|
|
||||||
unlock.error.invalidMountPoint.existing=نقطة/مجلد التحميل موجود بالفعل أو المجلد الأصل مفقود: %s
|
|
||||||
## Hub
|
## Hub
|
||||||
### Waiting
|
### Waiting
|
||||||
### Receive Key
|
### Receive Key
|
||||||
@@ -129,8 +125,13 @@ lock.fail.description=فشل عملية قفل %s". تأكد من حفظ الع
|
|||||||
# Migration
|
# Migration
|
||||||
migration.title=ترقية الحافظة
|
migration.title=ترقية الحافظة
|
||||||
## Start
|
## Start
|
||||||
migration.start.prompt=يجب تحديث المخزن الخاص بك "%s" إلى تنسيق أحدث. قبل المتابعة، يجب التأكد من عدم وجود مزامنة معلقة على هذا المخزن.
|
migration.start.header=ترقية الخزنة
|
||||||
migration.start.confirm=نعم, محفظتي متزامنة بالكامل
|
migration.start.text=من أجل فتح الخزنة الخاصة بك "%s" في هذا الإصدار الجديد من Cryptomator، يجب ترقية الخزنة إلى تنسيق جديد. وقبل القيام بذلك، ينبغي أن تعرف ما يلي:
|
||||||
|
migration.start.remarkUndone=هذه الترقية لا يمكن التراجع عنها.
|
||||||
|
migration.start.remarkVersions=لن تكون الإصدارات القديمة من Cryptomator قادرة على فتح الخزنة التي تم ترقيتها.
|
||||||
|
migration.start.remarkCanRun=يجب أن تكون متأكداً من أن كل جهاز تريد ان تستخدم الخزنة عليه يمكن تشغيل هذا الإصدار من Cryptomter.
|
||||||
|
migration.start.remarkSynced=يجب أن تكون متأكداً من أن خزنتك قد تمت مزامنتها بالكامل على هذا الجهاز، وعلى الأجهزة الأخرى الخاصة بك، قبل ترقيتها.
|
||||||
|
migration.start.confirm=لقد قرأت وفهمت المعلومات الواردة أعلاه
|
||||||
## Run
|
## Run
|
||||||
migration.run.enterPassword=أدخل كلمة المرور لـ "%s"
|
migration.run.enterPassword=أدخل كلمة المرور لـ "%s"
|
||||||
migration.run.startMigrationBtn=ترقية الحافظة
|
migration.run.startMigrationBtn=ترقية الحافظة
|
||||||
@@ -155,6 +156,7 @@ migration.impossible.moreInfo=لا يزال ممكناً فتح المخزن ب
|
|||||||
## Start Failure
|
## Start Failure
|
||||||
## Check Selection
|
## Check Selection
|
||||||
## Detail view
|
## Detail view
|
||||||
|
## Result view
|
||||||
## Fix Application
|
## Fix Application
|
||||||
|
|
||||||
# Preferences
|
# Preferences
|
||||||
@@ -171,9 +173,6 @@ preferences.interface.theme.dark=مظلم (أسود)
|
|||||||
preferences.interface.theme.light=فاتح (أبيض)
|
preferences.interface.theme.light=فاتح (أبيض)
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=القرص الإفتراضي
|
preferences.volume=القرص الإفتراضي
|
||||||
preferences.volume.type=نوع المجلد
|
|
||||||
preferences.volume.webdav.port=منفذ WebDav
|
|
||||||
preferences.volume.webdav.scheme=مخطط WebDAV
|
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=تحديثات
|
preferences.updates=تحديثات
|
||||||
preferences.updates.currentVersion=الإصدار الحالي: %s
|
preferences.updates.currentVersion=الإصدار الحالي: %s
|
||||||
@@ -200,15 +199,15 @@ stats.decr.total.data.none=تم فك تشفير البيانات:-
|
|||||||
## Write
|
## Write
|
||||||
stats.encr.total.data.none=البيانات المشفرة: -
|
stats.encr.total.data.none=البيانات المشفرة: -
|
||||||
|
|
||||||
|
## Accesses
|
||||||
|
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=إغلاق
|
main.closeBtn.tooltip=إغلاق
|
||||||
main.minimizeBtn.tooltip=تصغير
|
main.minimizeBtn.tooltip=تصغير
|
||||||
main.preferencesBtn.tooltip=تفضيلات
|
main.preferencesBtn.tooltip=تفضيلات
|
||||||
main.debugModeEnabled.tooltip=تم تمكين وضع التصحيح
|
main.debugModeEnabled.tooltip=تم تمكين وضع التصحيح
|
||||||
main.supporterCertificateMissing.tooltip=يرجى النظر في تقديم تبرعات
|
main.supporterCertificateMissing.tooltip=يرجى النظر في تقديم تبرعات
|
||||||
## Drag 'n' Drop
|
|
||||||
main.dropZone.dropVault=أضف هذا المخزن
|
|
||||||
main.dropZone.unknownDragboardContent=إذا كنت ترغب في إضافة مخزن، قم بسحبه إلى هذه النافذة
|
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.emptyList.onboardingInstruction=انقر هنا لإضافة خزنة
|
main.vaultlist.emptyList.onboardingInstruction=انقر هنا لإضافة خزنة
|
||||||
main.vaultlist.contextMenu.remove=حذف…
|
main.vaultlist.contextMenu.remove=حذف…
|
||||||
@@ -238,6 +237,10 @@ main.vaultDetail.throughput.idle=خمول
|
|||||||
main.vaultDetail.throughput.kbps=%.1f كيلوبايت/ث
|
main.vaultDetail.throughput.kbps=%.1f كيلوبايت/ث
|
||||||
main.vaultDetail.throughput.mbps=%.1f ميجابايت/ث
|
main.vaultDetail.throughput.mbps=%.1f ميجابايت/ث
|
||||||
main.vaultDetail.stats=إحصائيات الخزنة
|
main.vaultDetail.stats=إحصائيات الخزنة
|
||||||
|
main.vaultDetail.locateEncryptedFileBtn=تحديد موقع الملف المشفر
|
||||||
|
main.vaultDetail.locateEncryptedFileBtn.tooltip=اختر ملف من خزانتك لتحديد مكان نظيره المشفر
|
||||||
|
main.vaultDetail.encryptedPathsCopied=تم نسخ مسارات الملفات إلى الحافظة!
|
||||||
|
main.vaultDetail.filePickerTitle=إختر الملف من الخزنة
|
||||||
### Missing
|
### Missing
|
||||||
main.vaultDetail.missing.info=لم يتمكن Cryptomator من العثور على خزنة في هذا المسار.
|
main.vaultDetail.missing.info=لم يتمكن Cryptomator من العثور على خزنة في هذا المسار.
|
||||||
main.vaultDetail.missing.recheck=إعادة الفحص
|
main.vaultDetail.missing.recheck=إعادة الفحص
|
||||||
@@ -276,9 +279,7 @@ vaultOptions.mount.winDriveLetterOccupied=مشغول
|
|||||||
vaultOptions.mount.mountPoint=موقع التحميل الوهمي
|
vaultOptions.mount.mountPoint=موقع التحميل الوهمي
|
||||||
vaultOptions.mount.mountPoint.auto=اختيار موقع مناسب تلقائياً
|
vaultOptions.mount.mountPoint.auto=اختيار موقع مناسب تلقائياً
|
||||||
vaultOptions.mount.mountPoint.driveLetter=استخدام دالة قرص معين
|
vaultOptions.mount.mountPoint.driveLetter=استخدام دالة قرص معين
|
||||||
vaultOptions.mount.mountPoint.custom=مسار مخصص
|
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
|
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
|
||||||
vaultOptions.mount.mountPoint.directoryPickerTitle=اختر مجلد فارغ
|
|
||||||
## Master Key
|
## Master Key
|
||||||
vaultOptions.masterkey=كلمة المرور
|
vaultOptions.masterkey=كلمة المرور
|
||||||
vaultOptions.masterkey.changePasswordBtn=تغيير كلمة المرور
|
vaultOptions.masterkey.changePasswordBtn=تغيير كلمة المرور
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ addvaultwizard.new.fileAlreadyExists=Файл або тэчка з такім і
|
|||||||
addvaultwizard.new.locationDoesNotExist=Тэчка ва ўказанай сцежцы не існуе альбо ня можа адчыніцца
|
addvaultwizard.new.locationDoesNotExist=Тэчка ва ўказанай сцежцы не існуе альбо ня можа адчыніцца
|
||||||
addvaultwizard.new.locationIsNotWritable=Для гэтай сцежцы бракуе правоў на запіс
|
addvaultwizard.new.locationIsNotWritable=Для гэтай сцежцы бракуе правоў на запіс
|
||||||
addvaultwizard.new.locationIsOk=Пасуючае месца для тваёй скарбніцы
|
addvaultwizard.new.locationIsOk=Пасуючае месца для тваёй скарбніцы
|
||||||
|
addvaultwizard.new.invalidName=Некарэктная назва скрабніцы
|
||||||
|
addvaultwizard.new.validName=Карэктная назва скрабніцы
|
||||||
|
addvaultwizard.new.validCharacters.message=Нчзва скарбніцы мусіць утрымліваць наступныя знакі:
|
||||||
|
addvaultwizard.new.validCharacters.chars=Слоўныя знакі, накшталт a, ж або 수
|
||||||
|
addvaultwizard.new.validCharacters.numbers=Лічбы
|
||||||
|
addvaultwizard.new.validCharacters.dashes=Злучок (%s) або падкрэслінік (%s)
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.createVaultBtn=Стварыць скарбніцу
|
addvaultwizard.new.createVaultBtn=Стварыць скарбніцу
|
||||||
addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль?
|
addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль?
|
||||||
@@ -76,26 +82,30 @@ addvault.new.readme.accessLocation.2=Гэта месца дуступу да т
|
|||||||
addvault.new.readme.accessLocation.3=Любы дададзены сюды файл будзе зашыфраваны праз Cryptomator. Ты можаш працаваць тут як са звычайнаю тэчкаю альбо дыскам. Гэта толькі расшыфраваны агляд кантэнту, самі файлы заўжды захоўваюцца зашыфраванымі на тваім цвёрдым дыску.
|
addvault.new.readme.accessLocation.3=Любы дададзены сюды файл будзе зашыфраваны праз Cryptomator. Ты можаш працаваць тут як са звычайнаю тэчкаю альбо дыскам. Гэта толькі расшыфраваны агляд кантэнту, самі файлы заўжды захоўваюцца зашыфраванымі на тваім цвёрдым дыску.
|
||||||
addvault.new.readme.accessLocation.4=Ты можаш выдаліць гэты файл.
|
addvault.new.readme.accessLocation.4=Ты можаш выдаліць гэты файл.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.instruction=Абяры файл "vault.cryptomator" у існуючай скарбніцы. Калі існуе толькі файл "masterkey.cryptomator", абяры яго.
|
||||||
addvaultwizard.existing.chooseBtn=Абраць…
|
addvaultwizard.existing.chooseBtn=Абраць…
|
||||||
addvaultwizard.existing.filePickerTitle=Абяры файл скарбніцы
|
addvaultwizard.existing.filePickerTitle=Абяры файл скарбніцы
|
||||||
addvaultwizard.existing.filePickerMimeDesc=Скрабніца Cryptomator
|
addvaultwizard.existing.filePickerMimeDesc=Скрабніца Cryptomator
|
||||||
## Success
|
## Success
|
||||||
|
addvaultwizard.success.nextStepsInstructions=Скарбніца "%s" дададзеная.\nТабе трэба разамкнуць гэтую скрабніцу, каб атрымаць доступ да зместу альбо дадаць яго. Таксама ты можаш разамкнуць яе ў любы іншы час.
|
||||||
addvaultwizard.success.unlockNow=Разамкнуць зараз
|
addvaultwizard.success.unlockNow=Разамкнуць зараз
|
||||||
|
|
||||||
# Remove Vault
|
# Remove Vault
|
||||||
removeVault.title=Выдаліць "%s"
|
removeVault.title=Выдаліць "%s"
|
||||||
removeVault.message=Ці выдаліць скарбніцу?
|
removeVault.message=Ці выдаліць скарбніцу?
|
||||||
|
removeVault.description=Гэта прывядзе толькі да таго, што Cryptomator забудзецца на гэтую скрынку. Ты зможаш дадаць яе пазней ізноў. Аніякія зашыфраваныя файлы з тайго жорсткага дыску не выдаляцьмуцца.
|
||||||
removeVault.confirmBtn=Выдаліць скарбніцу
|
removeVault.confirmBtn=Выдаліць скарбніцу
|
||||||
|
|
||||||
# Change Password
|
# Change Password
|
||||||
changepassword.title=Змяніць пароль
|
changepassword.title=Змяніць пароль
|
||||||
changepassword.enterOldPassword=Увядзі бягучы пароль ад "%s"
|
changepassword.enterOldPassword=Увядзі бягучы пароль ад "%s"
|
||||||
changepassword.finalConfirmation=Я разумею, што калі забудуся на пароль, то згублю доступ да сваех даных
|
changepassword.finalConfirmation=Я разумею, што калі забудуся на пароль, то згублю доступ да маіх даных
|
||||||
|
|
||||||
# Forget Password
|
# Forget Password
|
||||||
forgetPassword.title=Забыўся на пароль
|
forgetPassword.title=Забыцца на пароль
|
||||||
forgetPassword.message=Забыўся на захаваны пароль?
|
forgetPassword.message=Ці забыцца на захаваны пароль?
|
||||||
forgetPassword.confirmBtn=Забыўся на пароль
|
forgetPassword.description=Гэта выдаліць захаваны пароль ад гэтай скарбніцы з твайго мэнэджара ключоў.
|
||||||
|
forgetPassword.confirmBtn=Забыцца на пароль
|
||||||
|
|
||||||
# Unlock
|
# Unlock
|
||||||
unlock.title=Разамкнуць "%s"
|
unlock.title=Разамкнуць "%s"
|
||||||
@@ -104,45 +114,66 @@ unlock.savePassword=Захоўваць пароль
|
|||||||
unlock.unlockBtn=Адамкнуць
|
unlock.unlockBtn=Адамкнуць
|
||||||
## Select
|
## Select
|
||||||
unlock.chooseMasterkey.message=Файл майстра ключоў ня знойдзены
|
unlock.chooseMasterkey.message=Файл майстра ключоў ня знойдзены
|
||||||
|
unlock.chooseMasterkey.description=Cryptomator ня змог знайсці файл masterkey у тваёй скарбніцы "%s". Калі ласка, абяры самастойна файл з ключом.
|
||||||
unlock.chooseMasterkey.filePickerTitle=Абяры файл masterkey
|
unlock.chooseMasterkey.filePickerTitle=Абяры файл masterkey
|
||||||
unlock.chooseMasterkey.filePickerMimeDesc=Майстар ключоў Cryptomator
|
unlock.chooseMasterkey.filePickerMimeDesc=Майстар ключоў Cryptomator
|
||||||
## Success
|
## Success
|
||||||
unlock.success.message=Паспяховае размыканне
|
unlock.success.message=Паспяховае размыканне
|
||||||
|
unlock.success.description=Змест скарбніцы "%s" цяпер дасяжны праз ёйны мантажны пункт.
|
||||||
unlock.success.rememberChoice=Запомні мой выбар і больш не пытай
|
unlock.success.rememberChoice=Запомні мой выбар і больш не пытай
|
||||||
unlock.success.revealBtn=Паказаць дыск
|
unlock.success.revealBtn=Паказаць дыск
|
||||||
## Failure
|
## Failure
|
||||||
unlock.error.message=Немагчыма разамкнуць скарбніцу
|
|
||||||
### Invalid Mount Point
|
|
||||||
## Hub
|
## Hub
|
||||||
### Waiting
|
### Waiting
|
||||||
|
hub.auth.message=Чаканне спраўджання…
|
||||||
|
hub.auth.description=Ты мусіш аўтаматычна перанакіравацца на старонку ўваходу.
|
||||||
|
hub.auth.loginLink=Не перанакіраваўся? Пстрыкні сюды, каб адчыніць старонку.
|
||||||
### Receive Key
|
### Receive Key
|
||||||
|
hub.receive.message=Апрацоўка адказу…
|
||||||
|
hub.receive.description=Cryptomator атрымлівае ды апрацоўвае адказ ад Hub. Калі ласка, пачакай.
|
||||||
### Register Device
|
### Register Device
|
||||||
|
hub.register.message=Патрабуецца назва прылады
|
||||||
|
hub.register.description=Здаецца, што ты ў першы раз увайшла/-оў у Hub з гэтай прылады. Каб ідэнтыфікаваць яе для спраўджання доступу, табе трэба назваць гэтую прыладу.
|
||||||
|
hub.register.nameLabel=Назва прылады
|
||||||
|
hub.register.occupiedMsg=Назва ўжо ўжытая
|
||||||
|
hub.register.registerBtn=Пацвердзіць
|
||||||
### Registration Success
|
### Registration Success
|
||||||
|
hub.registerSuccess.message=Прылада атрымала назву
|
||||||
|
hub.registerSuccess.description=Каб атрымаць доступ да скарбніцы, твая прылада мусіць быць спраўджанай уладальнікам скарбніцы.
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
|
hub.registerFailed.message=Памылка пры называнні прылады
|
||||||
|
hub.registerFailed.description=Падчас прысваення імя адбылася памылка. Па дэтальную інфармацыю звярніся да пратаколу праграмы.
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
|
hub.unauthorized.message=Адмова ў доступе
|
||||||
|
hub.unauthorized.description=Тваёй прыладзе ў дадзены момант не дазволена мець доступ да гэтай скрабніцы. Запытайся ўладальніка скрабніцы за дазволам.
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
|
hub.licenseExceeded.message=Ліцэнзія пратэрмінаваная
|
||||||
|
|
||||||
|
|
||||||
# Lock
|
# Lock
|
||||||
## Force
|
## Force
|
||||||
lock.forced.message=Замыканне не атрымалася
|
lock.forced.message=Замыканне не атрымалася
|
||||||
|
lock.forced.description=Замыканне скарбніцы "%s" было заблакаванае праз аперацыі ў апрацоўцы альбо праз адчыненыя файлы. Ты можаш прымусова замкнуць скарбніцу, але гэта можа прывесці да страты незахаваных даных.
|
||||||
lock.forced.retryBtn=Паспрабаваць ізноў
|
lock.forced.retryBtn=Паспрабаваць ізноў
|
||||||
lock.forced.forceBtn=Замкнуць прымусова
|
lock.forced.forceBtn=Замкнуць прымусова
|
||||||
## Failure
|
## Failure
|
||||||
lock.fail.message=Не атрымалася замкнуць скарбніцу
|
lock.fail.message=Не атрымалася замкнуць скарбніцу
|
||||||
|
lock.fail.description=Скарбніцу "%s" не мажліва замкнуць. Упэўніся, што ты захаваў сваю незахаваную працу дзесьці ў іншым месцы, і што скночыліся ўсе важныя працэсы чытання-пісання. Для замыкання скарбніцы забі працэс Cryptomator.
|
||||||
|
|
||||||
# Migration
|
# Migration
|
||||||
migration.title=Абнавіць скарбніцу
|
migration.title=Абнавіць скарбніцу
|
||||||
## Start
|
## Start
|
||||||
migration.start.confirm=Так, мая скарбніца цалкам сінхранавана
|
migration.start.header=Абнавіць скарбніцу
|
||||||
## Run
|
## Run
|
||||||
migration.run.enterPassword=Увядзіце пароль да "%s"
|
migration.run.enterPassword=Увядзіце пароль да "%s"
|
||||||
migration.run.startMigrationBtn=Перамясціць скарбніцу
|
migration.run.startMigrationBtn=Перамясціць скарбніцу
|
||||||
migration.run.progressHint=Гэта можа заняць пэўны час…
|
migration.run.progressHint=Гэта можа заняць пэўны час…
|
||||||
## Success
|
## Success
|
||||||
|
migration.success.nextStepsInstructions=Міграцыя скарбніцы "%s" прайшла паспяхова.\nЦяпер ты можаш разамкнуць сваю скарбніцу.
|
||||||
migration.success.unlockNow=Разамкнуць зараз
|
migration.success.unlockNow=Разамкнуць зараз
|
||||||
## Missing file system capabilities
|
## Missing file system capabilities
|
||||||
migration.error.missingFileSystemCapabilities.title=Файлавая сістэма не падтрымліваецца
|
migration.error.missingFileSystemCapabilities.title=Файлавая сістэма не падтрымліваецца
|
||||||
|
migration.error.missingFileSystemCapabilities.description=Міграцыя не пачалася, бо твая скарбніца знаходзіцца ў непрыдатнай файлавай сістэме.
|
||||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файлавая сістэма не падтрымлівае доўгія назвы файлаў.
|
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файлавая сістэма не падтрымлівае доўгія назвы файлаў.
|
||||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файлавая сістэма не падтрымлівае доўгія сцежкі.
|
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файлавая сістэма не падтрымлівае доўгія сцежкі.
|
||||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файлавая сістэма не дазваляе чытаць у ёй.
|
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файлавая сістэма не дазваляе чытаць у ёй.
|
||||||
@@ -152,6 +183,8 @@ migration.impossible.heading=Не мажліва перанесці скарбн
|
|||||||
|
|
||||||
# Health Check
|
# Health Check
|
||||||
## Start
|
## Start
|
||||||
|
health.title=Тэст на цэласнасць для "%s"
|
||||||
|
health.intro.header=Тэст на цэласнасць
|
||||||
health.intro.remarkBackup=Калі даныя пашкоджаныя, дапаможа толькі рэзервовая копія.
|
health.intro.remarkBackup=Калі даныя пашкоджаныя, дапаможа толькі рэзервовая копія.
|
||||||
## Start Failure
|
## Start Failure
|
||||||
health.fail.moreInfo=Падрабязней
|
health.fail.moreInfo=Падрабязней
|
||||||
@@ -160,18 +193,41 @@ health.checkList.selectAllButton=Вылучыць усе элементы
|
|||||||
health.checkList.deselectAllButton=Адрабіць вылучэнне ўсіх элементаў
|
health.checkList.deselectAllButton=Адрабіць вылучэнне ўсіх элементаў
|
||||||
health.check.runBatchBtn=Выканаць вылучаныя элементы
|
health.check.runBatchBtn=Выканаць вылучаныя элементы
|
||||||
## Detail view
|
## Detail view
|
||||||
|
health.check.detail.checkCancelled=Праверка была скасавана.
|
||||||
|
health.check.exportBtn=Экспартаваць справаздачу
|
||||||
|
## Result view
|
||||||
## Fix Application
|
## Fix Application
|
||||||
|
health.fix.fixBtn=Выправіць
|
||||||
|
health.fix.successTip=Паспяхова выпраўлена
|
||||||
|
health.fix.failTip=Няўдалае выпраўленне, глядзі пратакол з дэталямі
|
||||||
|
|
||||||
# Preferences
|
# Preferences
|
||||||
preferences.title=Налады
|
preferences.title=Налады
|
||||||
## General
|
## General
|
||||||
|
preferences.general=Агульныя
|
||||||
|
preferences.general.startHidden=Хаваць акно пры запуску Cryptomator
|
||||||
|
preferences.general.autoCloseVaults=Замыкаць адчыненыя скарбніцы аўтаматычна пры выхадзе з праграмы
|
||||||
|
preferences.general.debugLogging=Уключыць пратакаляванне адладкі
|
||||||
|
preferences.general.debugDirectory=Паказаць файлы пратаколу
|
||||||
|
preferences.general.autoStart=Запускаць Cryptomator падчас запуску сістэмы
|
||||||
|
preferences.general.keychainBackend=Захоўваць паролі праз
|
||||||
## Interface
|
## Interface
|
||||||
|
preferences.interface=Інтэрфэйс
|
||||||
|
preferences.interface.theme=Iнтэрфэйс
|
||||||
|
preferences.interface.theme.automatic=Аўтаматычна
|
||||||
preferences.interface.theme.dark=Цёмная
|
preferences.interface.theme.dark=Цёмная
|
||||||
preferences.interface.theme.light=Светлая
|
preferences.interface.theme.light=Светлая
|
||||||
|
preferences.interface.unlockThemes=Разблакаваць цёмную тэму
|
||||||
preferences.interface.language=Мова (спатрэбуецца перазапуск)
|
preferences.interface.language=Мова (спатрэбуецца перазапуск)
|
||||||
|
preferences.interface.language.auto=Сістэма па змаўчанні
|
||||||
|
preferences.interface.interfaceOrientation=Арыентацыя інтэрфэйсу
|
||||||
preferences.interface.interfaceOrientation.ltr=Злева ўправа
|
preferences.interface.interfaceOrientation.ltr=Злева ўправа
|
||||||
preferences.interface.interfaceOrientation.rtl=Справа ўлева
|
preferences.interface.interfaceOrientation.rtl=Справа ўлева
|
||||||
|
preferences.interface.showMinimizeButton=Паказаць кнопку згортвання
|
||||||
|
preferences.interface.showTrayIcon=Паказваць іконку на інфармацыйнай панэлі (спатрэбіцца перазапуск)
|
||||||
## Volume
|
## Volume
|
||||||
|
preferences.volume=Віртуальны дыск
|
||||||
|
preferences.volume.type.automatic=Аўтаматычна
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=Абнаўленні
|
preferences.updates=Абнаўленні
|
||||||
preferences.updates.currentVersion=Бягучая версія: %s
|
preferences.updates.currentVersion=Бягучая версія: %s
|
||||||
@@ -180,20 +236,52 @@ preferences.updates.checkNowBtn=Праверыць зараз
|
|||||||
preferences.updates.updateAvailable=Даступна абнаўленне да версіі %s
|
preferences.updates.updateAvailable=Даступна абнаўленне да версіі %s
|
||||||
## Contribution
|
## Contribution
|
||||||
preferences.contribute=Падтрымай нас
|
preferences.contribute=Падтрымай нас
|
||||||
|
preferences.contribute.getCertificate=Яшчэ ня маеш такога? Даведайся, як атрымаць.
|
||||||
#<-- Add entries for donations and code/translation/documentation contribution -->
|
#<-- Add entries for donations and code/translation/documentation contribution -->
|
||||||
|
|
||||||
## About
|
## About
|
||||||
preferences.about=Пра нас
|
preferences.about=Пра нас
|
||||||
|
|
||||||
# Vault Statistics
|
# Vault Statistics
|
||||||
|
stats.title=Статыстыкі для %s
|
||||||
## Read
|
## Read
|
||||||
|
stats.read.throughput.idle=Чытанне: -
|
||||||
|
stats.read.throughput.kibs=Чытанне: %.2f КіБ/с
|
||||||
|
stats.read.throughput.mibs=Чытанне: %.2f МіБ/с
|
||||||
|
stats.read.total.data.none=Прачытана: -
|
||||||
|
stats.read.total.data.kib=Прачытана: %.1f КіБ
|
||||||
|
stats.read.total.data.mib=Прачытана: %.1f МіБ
|
||||||
|
stats.read.total.data.gib=Прачытана: %.1f ҐіБ
|
||||||
|
stats.decr.total.data.none=Расшыфравана: -
|
||||||
|
stats.decr.total.data.kib=Расшыфравана: %.1f КіБ
|
||||||
|
stats.decr.total.data.mib=Расшыфравана: %.1f МіБ
|
||||||
|
stats.decr.total.data.gib=Расшыфравана: %.1f ҐіБ
|
||||||
|
stats.read.accessCount=Агульная колькасць чытанняў: %d
|
||||||
## Write
|
## Write
|
||||||
|
stats.write.throughput.idle=Чытанне: -
|
||||||
|
stats.write.throughput.kibs=Пісанне: %.2f КіБ/с
|
||||||
|
stats.write.throughput.mibs=Пісанне: %.2f МіБ/с
|
||||||
|
stats.write.total.data.none=Напісана: -
|
||||||
|
stats.write.total.data.kib=Запісана: %.1f КіБ
|
||||||
|
stats.write.total.data.mib=Запісана: %.1f МіБ
|
||||||
|
stats.write.total.data.gib=Запісана: %.1f ҐіБ
|
||||||
|
stats.encr.total.data.none=Зашыфравана: -
|
||||||
|
stats.encr.total.data.kib=Зашыфравана: %.1f КіБ
|
||||||
|
stats.encr.total.data.mib=Зашыфравана: %.1f МіБ
|
||||||
|
stats.encr.total.data.gib=Зашыфравана: %.1f ҐіБ
|
||||||
|
stats.write.accessCount=Агульная колькасць запісаў: %d
|
||||||
|
|
||||||
|
## Accesses
|
||||||
|
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=Зачыніць
|
main.closeBtn.tooltip=Зачыніць
|
||||||
|
main.minimizeBtn.tooltip=Згарнуць
|
||||||
main.preferencesBtn.tooltip=Налады
|
main.preferencesBtn.tooltip=Налады
|
||||||
## Drag 'n' Drop
|
main.debugModeEnabled.tooltip=Функццыя дыягназавання выключана
|
||||||
|
main.supporterCertificateMissing.tooltip=Калі ласка, падумай пра ахвяраванне
|
||||||
## Vault List
|
## Vault List
|
||||||
|
main.vaultlist.emptyList.onboardingInstruction=Пстрыкні тут, каб дадаць скарбніцу
|
||||||
main.vaultlist.contextMenu.remove=Выдаліць…
|
main.vaultlist.contextMenu.remove=Выдаліць…
|
||||||
main.vaultlist.contextMenu.lock=Замкнуць
|
main.vaultlist.contextMenu.lock=Замкнуць
|
||||||
main.vaultlist.contextMenu.unlock=Адамкнуць…
|
main.vaultlist.contextMenu.unlock=Адамкнуць…
|
||||||
@@ -207,37 +295,96 @@ main.vaultlist.addVaultBtn=Дадаць скарбніцу
|
|||||||
main.vaultDetail.lockedStatus=ЗАМКНЁНА
|
main.vaultDetail.lockedStatus=ЗАМКНЁНА
|
||||||
main.vaultDetail.unlockBtn=Адамкнуць…
|
main.vaultDetail.unlockBtn=Адамкнуць…
|
||||||
main.vaultDetail.unlockNowBtn=Разамкнуць зараз
|
main.vaultDetail.unlockNowBtn=Разамкнуць зараз
|
||||||
|
main.vaultDetail.optionsBtn=Параметры скарбніцы
|
||||||
|
main.vaultDetail.passwordSavedInKeychain=Пароль захаваны
|
||||||
### Unlocked
|
### Unlocked
|
||||||
main.vaultDetail.unlockedStatus=РАЗАМКНЁНА
|
main.vaultDetail.unlockedStatus=РАЗАМКНЁНА
|
||||||
|
main.vaultDetail.accessLocation=Змест тваёй скарбніцы даступны тут:
|
||||||
main.vaultDetail.revealBtn=Паказаць дыск
|
main.vaultDetail.revealBtn=Паказаць дыск
|
||||||
main.vaultDetail.lockBtn=Замкнуць
|
main.vaultDetail.lockBtn=Замкнуць
|
||||||
|
main.vaultDetail.bytesPerSecondRead=Чытанне:
|
||||||
|
main.vaultDetail.bytesPerSecondWritten=Пісанне:
|
||||||
|
main.vaultDetail.throughput.idle=бяздзейны
|
||||||
|
main.vaultDetail.throughput.kbps=%.1f КіБ/с
|
||||||
|
main.vaultDetail.throughput.mbps=%.1f МіБ/с
|
||||||
|
main.vaultDetail.stats=Статыстыка скарбніцы
|
||||||
### Missing
|
### Missing
|
||||||
|
main.vaultDetail.missing.info=Cryptomator ня змог знайсці скарбніцу па гэтай сцежцы.
|
||||||
|
main.vaultDetail.missing.recheck=Пераправерыць
|
||||||
|
main.vaultDetail.missing.remove=Выдаліць са спісу скарбніц…
|
||||||
|
main.vaultDetail.missing.changeLocation=Змяніць месцазнаходжанне скарбніцы…
|
||||||
### Needs Migration
|
### Needs Migration
|
||||||
main.vaultDetail.migrateButton=Абнавіць скарбніцу
|
main.vaultDetail.migrateButton=Абнавіць скарбніцу
|
||||||
|
main.vaultDetail.migratePrompt=Тваю скарбніцу трэба сканвертаваць у новы фармат, перад тым як ты зможаш атрымаць доступ да яе
|
||||||
### Error
|
### Error
|
||||||
|
main.vaultDetail.error.info=Адбылася памылка пры загрузцы скарбніцы з дыску.
|
||||||
|
main.vaultDetail.error.reload=Перазагрузіць
|
||||||
|
main.vaultDetail.error.windowTitle=Памылка загрузкі скарбніцы
|
||||||
|
|
||||||
# Wrong File Alert
|
# Wrong File Alert
|
||||||
|
wrongFileAlert.title=Як зашыфраваць файлы
|
||||||
|
wrongFileAlert.message=Ці спрабаваў ты зашыфраваць гэтыя файлы?
|
||||||
|
wrongFileAlert.instruction.0=Каб зашыфраваць файлы, зрабі наступныя крокі:
|
||||||
|
wrongFileAlert.instruction.1=1. Разамкні свая скарбніцу.
|
||||||
|
wrongFileAlert.instruction.2=2. Пстрыкні па "Паказаць"; каб адчыніць том у тваім файлавым мэнэджары.
|
||||||
|
wrongFileAlert.instruction.3=3. Дадай свае файлы ў гэты том.
|
||||||
|
wrongFileAlert.link=Каб атрымаць больш інфармацыі, наведай
|
||||||
|
|
||||||
# Vault Options
|
# Vault Options
|
||||||
## General
|
## General
|
||||||
|
vaultOptions.general=Агульныя
|
||||||
vaultOptions.general.vaultName=Назва скарбніцы
|
vaultOptions.general.vaultName=Назва скарбніцы
|
||||||
|
vaultOptions.general.autoLock.lockAfterTimePart1=Замыкаць пры бяздзейнасці праз
|
||||||
|
vaultOptions.general.autoLock.lockAfterTimePart2=хвілін(ы)
|
||||||
|
vaultOptions.general.unlockAfterStartup=Размыкаць скрабніцу пры запуску Cryptomator
|
||||||
|
vaultOptions.general.actionAfterUnlock=Пасля паспяховага размыкання
|
||||||
|
vaultOptions.general.actionAfterUnlock.ignore=Нічога не рабі
|
||||||
vaultOptions.general.actionAfterUnlock.reveal=Паказаць дыск
|
vaultOptions.general.actionAfterUnlock.reveal=Паказаць дыск
|
||||||
|
vaultOptions.general.actionAfterUnlock.ask=Запытацца
|
||||||
|
|
||||||
## Mount
|
## Mount
|
||||||
|
vaultOptions.mount=Мантажаванне
|
||||||
|
vaultOptions.mount.readonly=Толькі для чытання
|
||||||
|
vaultOptions.mount.customMountFlags=Карыстальніцкія опцыі мантажавання
|
||||||
|
vaultOptions.mount.winDriveLetterOccupied=занята
|
||||||
|
vaultOptions.mount.mountPoint=Пункт мантажавання
|
||||||
|
vaultOptions.mount.mountPoint.auto=Аўтаматычны выбар пасуючага месцазнаходжання
|
||||||
|
vaultOptions.mount.mountPoint.driveLetter=Назначыць літару для дыску
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=Абраць…
|
vaultOptions.mount.mountPoint.directoryPickerButton=Абраць…
|
||||||
## Master Key
|
## Master Key
|
||||||
|
vaultOptions.masterkey=Пароль
|
||||||
vaultOptions.masterkey.changePasswordBtn=Змяніць пароль
|
vaultOptions.masterkey.changePasswordBtn=Змяніць пароль
|
||||||
|
vaultOptions.masterkey.forgetSavedPasswordBtn=Забыцца на захаваны пароль
|
||||||
|
vaultOptions.masterkey.showRecoveryKeyBtn=Паказаць ключ аднаўлення
|
||||||
|
vaultOptions.masterkey.recoverPasswordBtn=Скінуць пароль
|
||||||
|
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
## Display Recovery Key
|
## Display Recovery Key
|
||||||
|
recoveryKey.display.title=Паказаць пароль аднаўлення
|
||||||
|
recoveryKey.create.message=Патрабуецца пароль
|
||||||
|
recoveryKey.create.description=Увядзі пароль ад "%s" каб паказаць ягоны код аднаўлення.
|
||||||
|
recoveryKey.display.description=Наступны код аднаўлення можа быць выкарастаны для аднаўлення доступу да "%s":
|
||||||
recoveryKey.display.StorageHints=Захоўвай іх у бяспечным месцы, напрыклад:\n • Выкарыстоўваючы мэнэджар пароляў\n • На USB-флэшцы\n • Раздрукаванымі на паперы
|
recoveryKey.display.StorageHints=Захоўвай іх у бяспечным месцы, напрыклад:\n • Выкарыстоўваючы мэнэджар пароляў\n • На USB-флэшцы\n • Раздрукаванымі на паперы
|
||||||
## Reset Password
|
## Reset Password
|
||||||
### Enter Recovery Key
|
### Enter Recovery Key
|
||||||
|
recoveryKey.recover.title=Скінуць пароль
|
||||||
|
recoveryKey.recover.prompt=Увядзі свой ключ аднаўлення для "%s":
|
||||||
|
recoveryKey.recover.validKey=Гэта валідны ключ аднаўлення
|
||||||
|
recoveryKey.printout.heading=Ключ аднаўлення Cryptomator\n"%s"\n
|
||||||
### Reset Password
|
### Reset Password
|
||||||
|
recoveryKey.recover.resetBtn=Скінуць
|
||||||
### Recovery Key Password Reset Success
|
### Recovery Key Password Reset Success
|
||||||
|
recoveryKey.recover.resetSuccess.message=Пароль паспяхова скінуты
|
||||||
|
recoveryKey.recover.resetSuccess.description=Ты можаш разамкнуць сваю скарбніцу з дапамогаю новага паролю.
|
||||||
|
|
||||||
# New Password
|
# New Password
|
||||||
|
newPassword.promptText=Увядзі новы пароль
|
||||||
|
newPassword.reenterPassword=Пацвердзі новы пароль
|
||||||
|
newPassword.passwordsMatch=Паролі супадаюць!
|
||||||
|
newPassword.passwordsDoNotMatch=Паролі не супадаюць
|
||||||
|
passwordStrength.messageLabel.tooShort=Выкарыстай ня менш за %d сімвалаў
|
||||||
|
passwordStrength.messageLabel.0=Вельмі слабы
|
||||||
passwordStrength.messageLabel.1=Слабы
|
passwordStrength.messageLabel.1=Слабы
|
||||||
passwordStrength.messageLabel.2=Нармалёвы
|
passwordStrength.messageLabel.2=Нармалёвы
|
||||||
passwordStrength.messageLabel.3=Моцны
|
passwordStrength.messageLabel.3=Моцны
|
||||||
@@ -246,5 +393,9 @@ passwordStrength.messageLabel.4=Вельмі моцны
|
|||||||
# Quit
|
# Quit
|
||||||
quit.title=Пакінуць праграму
|
quit.title=Пакінуць праграму
|
||||||
quit.message=Існуюць разамкнёныя скарбніцы
|
quit.message=Існуюць разамкнёныя скарбніцы
|
||||||
|
quit.description=Калі ласка, пацвердзі, што ты збіраешся выйсці. Cryptomator замкне ўсе разомкнутыя скарбніцы, каб прадухіліць страту даных.
|
||||||
|
quit.lockAndQuitBtn=Замкнуць ды вайсці
|
||||||
|
|
||||||
# Forced Quit
|
# Forced Quit
|
||||||
|
quit.forced.message=Некаторыя скарбніцы не магчыма замкнуць
|
||||||
|
quit.forced.forceAndQuitBtn=Прымусіць і выйсці
|
||||||
@@ -44,11 +44,19 @@ addvaultwizard.new.namePrompt=ভোল্ট এর নাম
|
|||||||
addvaultwizard.new.locationInstruction=ভোল্ট এর এনক্রিপ্টেড ফাইলগুলো ক্রিপ্টোমেটর কোথায় সংরক্ষণ করবে?
|
addvaultwizard.new.locationInstruction=ভোল্ট এর এনক্রিপ্টেড ফাইলগুলো ক্রিপ্টোমেটর কোথায় সংরক্ষণ করবে?
|
||||||
addvaultwizard.new.locationLabel=স্টোরেজ লোকেশন
|
addvaultwizard.new.locationLabel=স্টোরেজ লোকেশন
|
||||||
addvaultwizard.new.locationPrompt=…
|
addvaultwizard.new.locationPrompt=…
|
||||||
|
addvaultwizard.new.directoryPickerLabel=স্বনির্বাচিত লোকেশন
|
||||||
addvaultwizard.new.directoryPickerButton=নির্বাচন করুন…
|
addvaultwizard.new.directoryPickerButton=নির্বাচন করুন…
|
||||||
addvaultwizard.new.directoryPickerTitle=ডিরেক্টরি নির্বাচন
|
addvaultwizard.new.directoryPickerTitle=ডিরেক্টরি নির্বাচন
|
||||||
|
addvaultwizard.new.fileAlreadyExists=ভোল্টের নামে একটি ফাইল অথবা ডিরেক্টরি ইতিমধ্যে বিদ্যমান
|
||||||
addvaultwizard.new.locationDoesNotExist=নিদিষ্ট করা পথে একটি ডিরেক্টরি বিদ্যমান নয় অথবা প্রবেশ করা যাচ্ছে না
|
addvaultwizard.new.locationDoesNotExist=নিদিষ্ট করা পথে একটি ডিরেক্টরি বিদ্যমান নয় অথবা প্রবেশ করা যাচ্ছে না
|
||||||
addvaultwizard.new.locationIsNotWritable=নিদিষ্ট করা পথে কোনো কিছু পরিবর্তন করার অনুমতি নেই
|
addvaultwizard.new.locationIsNotWritable=নিদিষ্ট করা পথে কোনো কিছু পরিবর্তন করার অনুমতি নেই
|
||||||
addvaultwizard.new.locationIsOk=আপনার ভোল্টের জন্য উপযুক্ত ঠিকানা
|
addvaultwizard.new.locationIsOk=আপনার ভোল্টের জন্য উপযুক্ত ঠিকানা
|
||||||
|
addvaultwizard.new.invalidName=ভোল্টের নামটি বৈধ নয়
|
||||||
|
addvaultwizard.new.validName=ভোল্টের নামটি বৈধ
|
||||||
|
addvaultwizard.new.validCharacters.message=ভোল্টের নামটিতে নিম্নলিখিত অক্ষরগুলো থাকতে পারবে:
|
||||||
|
addvaultwizard.new.validCharacters.chars=শব্দের অক্ষরগুলো (যেমনঃ a, ж অথবা 수)
|
||||||
|
addvaultwizard.new.validCharacters.numbers=সংখ্যা
|
||||||
|
addvaultwizard.new.validCharacters.dashes=হাইফেন (%s) অথবা আন্ডারস্কোর (%s)
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.createVaultBtn=ভোল্ট তৈরি করুন
|
addvaultwizard.new.createVaultBtn=ভোল্ট তৈরি করুন
|
||||||
addvaultwizard.new.generateRecoveryKeyChoice=পাসওয়ার্ড ছাড়া আপনি আপনার তথ্যগুলো ব্যবহার করতে পারবেন না. তাই আপনি কি একটি পুনরুদ্ধার চাবি চান যদি আপনি পাসওয়ার্ড হারিয়ে ফেলেন?
|
addvaultwizard.new.generateRecoveryKeyChoice=পাসওয়ার্ড ছাড়া আপনি আপনার তথ্যগুলো ব্যবহার করতে পারবেন না. তাই আপনি কি একটি পুনরুদ্ধার চাবি চান যদি আপনি পাসওয়ার্ড হারিয়ে ফেলেন?
|
||||||
@@ -79,7 +87,6 @@ unlock.unlockBtn=আনলক করুন
|
|||||||
## Select
|
## Select
|
||||||
## Success
|
## Success
|
||||||
## Failure
|
## Failure
|
||||||
### Invalid Mount Point
|
|
||||||
## Hub
|
## Hub
|
||||||
### Waiting
|
### Waiting
|
||||||
### Receive Key
|
### Receive Key
|
||||||
@@ -108,6 +115,7 @@ lock.forced.retryBtn=পুনরায় চেষ্টা করুন
|
|||||||
## Start Failure
|
## Start Failure
|
||||||
## Check Selection
|
## Check Selection
|
||||||
## Detail view
|
## Detail view
|
||||||
|
## Result view
|
||||||
## Fix Application
|
## Fix Application
|
||||||
|
|
||||||
# Preferences
|
# Preferences
|
||||||
@@ -124,9 +132,11 @@ lock.forced.retryBtn=পুনরায় চেষ্টা করুন
|
|||||||
## Read
|
## Read
|
||||||
## Write
|
## Write
|
||||||
|
|
||||||
|
## Accesses
|
||||||
|
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=বন্ধ করুন
|
main.closeBtn.tooltip=বন্ধ করুন
|
||||||
## Drag 'n' Drop
|
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.contextMenu.lock=লক করুন
|
main.vaultlist.contextMenu.lock=লক করুন
|
||||||
main.vaultlist.addVaultBtn=ভোল্ট যুক্ত করুন
|
main.vaultlist.addVaultBtn=ভোল্ট যুক্ত করুন
|
||||||
|
|||||||
@@ -103,10 +103,6 @@ unlock.success.description=Uspješno ste otključali "%s"! Vaš sef je sada dost
|
|||||||
unlock.success.rememberChoice=Zapamtite izbor, ne pokazujte ovo ponovo
|
unlock.success.rememberChoice=Zapamtite izbor, ne pokazujte ovo ponovo
|
||||||
unlock.success.revealBtn=Otkrij pogon
|
unlock.success.revealBtn=Otkrij pogon
|
||||||
## Failure
|
## Failure
|
||||||
unlock.error.message=Sef nije moguće otključati
|
|
||||||
### Invalid Mount Point
|
|
||||||
unlock.error.invalidMountPoint.notExisting=Tačka postavljanja "%s" nije direktorij, nije prazna ili ne postoji.
|
|
||||||
unlock.error.invalidMountPoint.existing=Tačka povezivanja "%s" već postoji ili nadređeni folder nedostaje.
|
|
||||||
## Hub
|
## Hub
|
||||||
### Waiting
|
### Waiting
|
||||||
### Receive Key
|
### Receive Key
|
||||||
@@ -128,8 +124,7 @@ lock.fail.description=Sef "%s" nije moguće zaključati. Osigurajte da je nespre
|
|||||||
# Migration
|
# Migration
|
||||||
migration.title=Nadogradi sef
|
migration.title=Nadogradi sef
|
||||||
## Start
|
## Start
|
||||||
migration.start.prompt=Sef "%s" treba ažurirati na noviji format. Prije nastavka, provjerite da nije na čekanju sinkronizacija koja utječe na ovaj sef.
|
migration.start.header=Nadogradi sef
|
||||||
migration.start.confirm=Da, moj sef je u potpunosti sinhroniziran
|
|
||||||
## Run
|
## Run
|
||||||
migration.run.enterPassword=Unesite lozinku za "%s"
|
migration.run.enterPassword=Unesite lozinku za "%s"
|
||||||
migration.run.startMigrationBtn=Nadogradi sef
|
migration.run.startMigrationBtn=Nadogradi sef
|
||||||
@@ -154,6 +149,7 @@ migration.impossible.moreInfo=Sef se i dalje može otvoriti sa starijom verzijom
|
|||||||
## Start Failure
|
## Start Failure
|
||||||
## Check Selection
|
## Check Selection
|
||||||
## Detail view
|
## Detail view
|
||||||
|
## Result view
|
||||||
## Fix Application
|
## Fix Application
|
||||||
|
|
||||||
# Preferences
|
# Preferences
|
||||||
@@ -170,9 +166,6 @@ preferences.interface.theme.dark=Tamno
|
|||||||
preferences.interface.theme.light=Svijetlo
|
preferences.interface.theme.light=Svijetlo
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Virtuelni pogon
|
preferences.volume=Virtuelni pogon
|
||||||
preferences.volume.type=Tip volumena
|
|
||||||
preferences.volume.webdav.port=WebDAV Port
|
|
||||||
preferences.volume.webdav.scheme=WebDAV Scheme
|
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=Ažuriranja
|
preferences.updates=Ažuriranja
|
||||||
preferences.updates.currentVersion=Trenutna verzija: %s
|
preferences.updates.currentVersion=Trenutna verzija: %s
|
||||||
@@ -215,15 +208,15 @@ stats.encr.total.data.mib=Otključano podataka: %.1f MiB
|
|||||||
stats.encr.total.data.gib=Otključano podataka: %.1f GiB
|
stats.encr.total.data.gib=Otključano podataka: %.1f GiB
|
||||||
stats.write.accessCount=Ukupno upisano: %d
|
stats.write.accessCount=Ukupno upisano: %d
|
||||||
|
|
||||||
|
## Accesses
|
||||||
|
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=Zatvori
|
main.closeBtn.tooltip=Zatvori
|
||||||
main.minimizeBtn.tooltip=Minimiziraj
|
main.minimizeBtn.tooltip=Minimiziraj
|
||||||
main.preferencesBtn.tooltip=Postavke
|
main.preferencesBtn.tooltip=Postavke
|
||||||
main.debugModeEnabled.tooltip=Omogućen je mod otklanjanja pogrešaka
|
main.debugModeEnabled.tooltip=Omogućen je mod otklanjanja pogrešaka
|
||||||
main.supporterCertificateMissing.tooltip=Molimo razmislite o doniranju
|
main.supporterCertificateMissing.tooltip=Molimo razmislite o doniranju
|
||||||
## Drag 'n' Drop
|
|
||||||
main.dropZone.dropVault=Dodajte ovaj sef
|
|
||||||
main.dropZone.unknownDragboardContent=Ako želite dodati sef, povucite ga u ovaj prozor
|
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.emptyList.onboardingInstruction=Kliknite ovdje da dodate sef
|
main.vaultlist.emptyList.onboardingInstruction=Kliknite ovdje da dodate sef
|
||||||
main.vaultlist.contextMenu.remove=Ukloni…
|
main.vaultlist.contextMenu.remove=Ukloni…
|
||||||
@@ -291,9 +284,7 @@ vaultOptions.mount.winDriveLetterOccupied=zauzeto
|
|||||||
vaultOptions.mount.mountPoint=Tačka povezivanja
|
vaultOptions.mount.mountPoint=Tačka povezivanja
|
||||||
vaultOptions.mount.mountPoint.auto=Automatski odaberi pogodnu lokaciju
|
vaultOptions.mount.mountPoint.auto=Automatski odaberi pogodnu lokaciju
|
||||||
vaultOptions.mount.mountPoint.driveLetter=Koristi dodjeljeno slovo (oznaku) diska
|
vaultOptions.mount.mountPoint.driveLetter=Koristi dodjeljeno slovo (oznaku) diska
|
||||||
vaultOptions.mount.mountPoint.custom=Prilagođena adresa
|
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=Odaberi…
|
vaultOptions.mount.mountPoint.directoryPickerButton=Odaberi…
|
||||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Izaberi praznu mapu
|
|
||||||
## Master Key
|
## Master Key
|
||||||
vaultOptions.masterkey=Šifra
|
vaultOptions.masterkey=Šifra
|
||||||
vaultOptions.masterkey.changePasswordBtn=Promjeni lozinku
|
vaultOptions.masterkey.changePasswordBtn=Promjeni lozinku
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ generic.button.next=Següent
|
|||||||
generic.button.print=Imprimeix
|
generic.button.print=Imprimeix
|
||||||
|
|
||||||
# Error
|
# Error
|
||||||
error.message=Error %s
|
error.message=S'ha produït un error
|
||||||
error.description=Ui! Cryptomator no esperava que passés això. Podeu cercar alguna de les solucions existents per a aquest error. Si no ha estat reportat encara, sentiu-vos lliure de fer-ho vós mateix.
|
error.description=Ui! Cryptomator no esperava que passés això. Podeu cercar alguna de les solucions existents per a aquest error. Si no ha estat reportat encara, sentiu-vos lliure de fer-ho vós mateix.
|
||||||
error.hyperlink.lookup=Cerca aquest error
|
error.hyperlink.lookup=Cerca aquest error
|
||||||
error.hyperlink.report=Notifica un error
|
error.hyperlink.report=Notifica aquest error
|
||||||
error.technicalDetails=Detalls:
|
error.technicalDetails=Detalls:
|
||||||
|
|
||||||
# Defaults
|
# Defaults
|
||||||
@@ -116,18 +116,17 @@ unlock.unlockBtn=Desbloqueja
|
|||||||
unlock.chooseMasterkey.message=L'arxiu de contrasenyes no s'ha pogut trobar
|
unlock.chooseMasterkey.message=L'arxiu de contrasenyes no s'ha pogut trobar
|
||||||
unlock.chooseMasterkey.description=No es pot trobar el fitxer de clau mestra per aquesta bòveda a la ubicació esperada. Escull el fixer manualment.
|
unlock.chooseMasterkey.description=No es pot trobar el fitxer de clau mestra per aquesta bòveda a la ubicació esperada. Escull el fixer manualment.
|
||||||
unlock.chooseMasterkey.filePickerTitle=Seleccioneu el fitxer de Clau Mestra
|
unlock.chooseMasterkey.filePickerTitle=Seleccioneu el fitxer de Clau Mestra
|
||||||
|
unlock.chooseMasterkey.filePickerMimeDesc=La clau mestra de Cryptomator
|
||||||
## Success
|
## Success
|
||||||
unlock.success.message=Desbloqueig satisfactori
|
unlock.success.message=Desbloqueig satisfactori
|
||||||
unlock.success.description=S'ha desblocat %s correctament! Podeu accedir a la vostra caixa forta a través de la unitat virtual.
|
unlock.success.description=S'ha desblocat %s correctament! Podeu accedir a la vostra caixa forta a través de la unitat virtual.
|
||||||
unlock.success.rememberChoice=Recorda l'elecció. No ho tornis a mostrar.
|
unlock.success.rememberChoice=Recorda l'elecció. No ho tornis a mostrar.
|
||||||
unlock.success.revealBtn=Mostra la unitat
|
unlock.success.revealBtn=Mostra la unitat
|
||||||
## Failure
|
## Failure
|
||||||
unlock.error.message=No ha estat possible desblocar la caixa forta
|
|
||||||
### Invalid Mount Point
|
|
||||||
unlock.error.invalidMountPoint.notExisting=El punt de muntatge no és un directori buit, o no existeix: %s
|
|
||||||
unlock.error.invalidMountPoint.existing=El punt de muntatge o la carpeta ja existeix, o no es pot accedir al directori superior: %s
|
|
||||||
unlock.error.invalidMountPoint.driveLetterOccupied=La unitat o arxiu "%s" ja està en ús.
|
|
||||||
## Hub
|
## Hub
|
||||||
|
hub.noKeychain.message=No es pot accedir a la clau del dispositiu
|
||||||
|
hub.noKeychain.description=Per poder desblocar caixes fortes del Hub es requereix la clau d'un dispositiu, que s'emmagatzema de forma segura en un clauer. Per continuar, habiliti "%s" i seleccioni un clauer en les Preferències.
|
||||||
|
hub.noKeychain.openBtn=Obrir les Preferències
|
||||||
### Waiting
|
### Waiting
|
||||||
hub.auth.message=S’està esperant l’autenticació…
|
hub.auth.message=S’està esperant l’autenticació…
|
||||||
hub.auth.description=Hauríeu de ser redirigits a la pàgina d'accés.
|
hub.auth.description=Hauríeu de ser redirigits a la pàgina d'accés.
|
||||||
@@ -161,8 +160,8 @@ lock.fail.description=La caixa forta "%s" no s'ha pogut blocar. Assegureu-vos qu
|
|||||||
# Migration
|
# Migration
|
||||||
migration.title=Actualitza la caixa forta
|
migration.title=Actualitza la caixa forta
|
||||||
## Start
|
## Start
|
||||||
migration.start.prompt=La vostra caixa forta "%s" ha de ser actualitzada a un format més nou. Abans de continuar, assegureu-vos de que no hi haja cap sincronització pendent que puga afectar a aquesta caixa forta.
|
migration.start.header=Actualitza la caixa forta
|
||||||
migration.start.confirm=Sí, la meua caixa forta està completament sicronitzada
|
migration.start.confirm=He llegit i entès la informació anterior
|
||||||
## Run
|
## Run
|
||||||
migration.run.enterPassword=Introduïu la contrasenya per a "%s"
|
migration.run.enterPassword=Introduïu la contrasenya per a "%s"
|
||||||
migration.run.startMigrationBtn=Migrar la caixa forta
|
migration.run.startMigrationBtn=Migrar la caixa forta
|
||||||
@@ -210,7 +209,21 @@ health.check.detail.checkFinished=La prova ha finalitzat amb èxit.
|
|||||||
health.check.detail.checkFinishedAndFound=La comprovació ha finalitzat. Si us plau, comproveu-ne es resultat.
|
health.check.detail.checkFinishedAndFound=La comprovació ha finalitzat. Si us plau, comproveu-ne es resultat.
|
||||||
health.check.detail.checkFailed=La comprovació ha acabat a causa d'un error.
|
health.check.detail.checkFailed=La comprovació ha acabat a causa d'un error.
|
||||||
health.check.detail.checkCancelled=S'ha cancel·lat la prova.
|
health.check.detail.checkCancelled=S'ha cancel·lat la prova.
|
||||||
|
health.check.detail.listFilters.label=Filtre
|
||||||
|
health.check.detail.fixAllSpecificBtn=Qualsevol reparació
|
||||||
health.check.exportBtn=Exporta informe
|
health.check.exportBtn=Exporta informe
|
||||||
|
## Result view
|
||||||
|
health.result.severityFilter.all=Gravetat - Totes
|
||||||
|
health.result.severityFilter.good=Bé
|
||||||
|
health.result.severityFilter.info=Info
|
||||||
|
health.result.severityFilter.warn=Avís
|
||||||
|
health.result.severityFilter.crit=Crític
|
||||||
|
health.result.fixStateFilter.all=Estat de reparació - Tot
|
||||||
|
health.result.fixStateFilter.fixable=Es pot arreglar
|
||||||
|
health.result.fixStateFilter.notFixable=No es pot arreglar
|
||||||
|
health.result.fixStateFilter.fixing=Reparant…
|
||||||
|
health.result.fixStateFilter.fixed=Solucionat
|
||||||
|
health.result.fixStateFilter.fixFailed=Reparació fallida
|
||||||
## Fix Application
|
## Fix Application
|
||||||
health.fix.fixBtn=Corregeix
|
health.fix.fixBtn=Corregeix
|
||||||
health.fix.successTip=S'ha corregit amb èxit
|
health.fix.successTip=S'ha corregit amb èxit
|
||||||
@@ -242,9 +255,7 @@ preferences.interface.showMinimizeButton=Mostra el botó 'minimitzar'
|
|||||||
preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar)
|
preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar)
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Unitat virtual
|
preferences.volume=Unitat virtual
|
||||||
preferences.volume.type=Tipus de volum
|
preferences.volume.type.automatic=Automàtic
|
||||||
preferences.volume.webdav.port=Port WebDAV
|
|
||||||
preferences.volume.webdav.scheme=Esquema WebDAV
|
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=Actualitzacions
|
preferences.updates=Actualitzacions
|
||||||
preferences.updates.currentVersion=Versió actual: %s
|
preferences.updates.currentVersion=Versió actual: %s
|
||||||
@@ -292,15 +303,17 @@ stats.encr.total.data.mib=Dades xifrades: %.1f MiB
|
|||||||
stats.encr.total.data.gib=Dades xifrades: %.1f GiB
|
stats.encr.total.data.gib=Dades xifrades: %.1f GiB
|
||||||
stats.write.accessCount=Total escrits: %d
|
stats.write.accessCount=Total escrits: %d
|
||||||
|
|
||||||
|
## Accesses
|
||||||
|
stats.access.current=Accés: %d
|
||||||
|
stats.access.total=Total d'accessos: %d
|
||||||
|
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=Tanca
|
main.closeBtn.tooltip=Tanca
|
||||||
main.minimizeBtn.tooltip=Minimitza
|
main.minimizeBtn.tooltip=Minimitza
|
||||||
main.preferencesBtn.tooltip=Preferències
|
main.preferencesBtn.tooltip=Preferències
|
||||||
main.debugModeEnabled.tooltip=Mode depuració activat
|
main.debugModeEnabled.tooltip=Mode depuració activat
|
||||||
main.supporterCertificateMissing.tooltip=Si us plau, considereu fer una donació
|
main.supporterCertificateMissing.tooltip=Si us plau, considereu fer una donació
|
||||||
## Drag 'n' Drop
|
|
||||||
main.dropZone.dropVault=Afegeix aquesta caixa forta
|
|
||||||
main.dropZone.unknownDragboardContent=Si voleu afegir una caixa forta, arrossegueu-la a aquesta finestra
|
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.emptyList.onboardingInstruction=Feu clic aquí per afegir una caixa forta
|
main.vaultlist.emptyList.onboardingInstruction=Feu clic aquí per afegir una caixa forta
|
||||||
main.vaultlist.contextMenu.remove=Elimina…
|
main.vaultlist.contextMenu.remove=Elimina…
|
||||||
@@ -374,9 +387,7 @@ vaultOptions.mount.winDriveLetterOccupied=ocupat
|
|||||||
vaultOptions.mount.mountPoint=Punt de muntatge
|
vaultOptions.mount.mountPoint=Punt de muntatge
|
||||||
vaultOptions.mount.mountPoint.auto=Tria automàticament una ubicació adequada
|
vaultOptions.mount.mountPoint.auto=Tria automàticament una ubicació adequada
|
||||||
vaultOptions.mount.mountPoint.driveLetter=Utilitza la lletra de la unitat assignada
|
vaultOptions.mount.mountPoint.driveLetter=Utilitza la lletra de la unitat assignada
|
||||||
vaultOptions.mount.mountPoint.custom=Camí personalitzat
|
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=Trieu…
|
vaultOptions.mount.mountPoint.directoryPickerButton=Trieu…
|
||||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Esculliu un directori buit
|
|
||||||
## Master Key
|
## Master Key
|
||||||
vaultOptions.masterkey=Contrasenya
|
vaultOptions.masterkey=Contrasenya
|
||||||
vaultOptions.masterkey.changePasswordBtn=Canvi de contrasenya
|
vaultOptions.masterkey.changePasswordBtn=Canvi de contrasenya
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ addvaultwizard.new.fileAlreadyExists=Soubor nebo složka s tímto názvem trezor
|
|||||||
addvaultwizard.new.locationDoesNotExist=Složka v zadané cestě neexistuje nebo není přístupná
|
addvaultwizard.new.locationDoesNotExist=Složka v zadané cestě neexistuje nebo není přístupná
|
||||||
addvaultwizard.new.locationIsNotWritable=Není možné zapisovat na zadané cestě
|
addvaultwizard.new.locationIsNotWritable=Není možné zapisovat na zadané cestě
|
||||||
addvaultwizard.new.locationIsOk=Vhodné umístění pro váš trezor
|
addvaultwizard.new.locationIsOk=Vhodné umístění pro váš trezor
|
||||||
|
addvaultwizard.new.invalidName=Neplatný název trezoru
|
||||||
|
addvaultwizard.new.validName=Platné jméno trezoru
|
||||||
|
addvaultwizard.new.validCharacters.message=Název trezoru může obsahovat tyto znaky:
|
||||||
|
addvaultwizard.new.validCharacters.chars=Slovní znaky (např. a, ж nebo 수)
|
||||||
|
addvaultwizard.new.validCharacters.numbers=Čísla
|
||||||
|
addvaultwizard.new.validCharacters.dashes=Pomlčka (%s) nebo podtržítko (%s)
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.createVaultBtn=Vytvořit trezor
|
addvaultwizard.new.createVaultBtn=Vytvořit trezor
|
||||||
addvaultwizard.new.generateRecoveryKeyChoice=Nebudete mít přístup k datům bez hesla. Chcete recovery klíč pro případ, že ztratíte heslo?
|
addvaultwizard.new.generateRecoveryKeyChoice=Nebudete mít přístup k datům bez hesla. Chcete recovery klíč pro případ, že ztratíte heslo?
|
||||||
@@ -86,6 +92,7 @@ addvaultwizard.success.unlockNow=Odemknout nyní
|
|||||||
|
|
||||||
# Remove Vault
|
# Remove Vault
|
||||||
removeVault.title=Odstranit trezor
|
removeVault.title=Odstranit trezor
|
||||||
|
removeVault.message=Odstranit trezor?
|
||||||
removeVault.description=Toto jen umožní Cryptomatoru zapomenout tento trezor. Později jej můžete opět přidat. Žádné šifrované soubory nebudou z vašeho disku smazány.
|
removeVault.description=Toto jen umožní Cryptomatoru zapomenout tento trezor. Později jej můžete opět přidat. Žádné šifrované soubory nebudou z vašeho disku smazány.
|
||||||
removeVault.confirmBtn=Odstranit trezor
|
removeVault.confirmBtn=Odstranit trezor
|
||||||
|
|
||||||
@@ -96,6 +103,7 @@ changepassword.finalConfirmation=Chápu, že pokud zapomenu své heslo, nebudu m
|
|||||||
|
|
||||||
# Forget Password
|
# Forget Password
|
||||||
forgetPassword.title=Zapomenout heslo
|
forgetPassword.title=Zapomenout heslo
|
||||||
|
forgetPassword.message=Zapomenout uložené heslo?
|
||||||
forgetPassword.description=Toto smaže uložené heslo pro tento trezor.
|
forgetPassword.description=Toto smaže uložené heslo pro tento trezor.
|
||||||
forgetPassword.confirmBtn=Zapomenout heslo
|
forgetPassword.confirmBtn=Zapomenout heslo
|
||||||
|
|
||||||
@@ -105,28 +113,37 @@ unlock.passwordPrompt=Zadejte heslo pro "%s":
|
|||||||
unlock.savePassword=Zapamatovat heslo
|
unlock.savePassword=Zapamatovat heslo
|
||||||
unlock.unlockBtn=Odemknout
|
unlock.unlockBtn=Odemknout
|
||||||
## Select
|
## Select
|
||||||
|
unlock.chooseMasterkey.message=Masterkey soubor nebyl nalezen
|
||||||
unlock.chooseMasterkey.description=Nepodařilo se najít soubor hlavního klíče pro tento trezor v očekávaném umístění. Vyberte prosím soubor klíče ručně.
|
unlock.chooseMasterkey.description=Nepodařilo se najít soubor hlavního klíče pro tento trezor v očekávaném umístění. Vyberte prosím soubor klíče ručně.
|
||||||
unlock.chooseMasterkey.filePickerTitle=Vyberte soubor s hlavním klíčem
|
unlock.chooseMasterkey.filePickerTitle=Vyberte soubor s hlavním klíčem
|
||||||
unlock.chooseMasterkey.filePickerMimeDesc=Hlavní klíč Cryptomatoru
|
unlock.chooseMasterkey.filePickerMimeDesc=Hlavní klíč Cryptomatoru
|
||||||
## Success
|
## Success
|
||||||
|
unlock.success.message=Odemknutí bylo úspěšné
|
||||||
unlock.success.description=Trezor "%s" byl úspěšně odemčen a nyní je dostupný jako virtuální jednotka.
|
unlock.success.description=Trezor "%s" byl úspěšně odemčen a nyní je dostupný jako virtuální jednotka.
|
||||||
unlock.success.rememberChoice=Pamatovat si volbu, nezobrazovat to znovu
|
unlock.success.rememberChoice=Pamatovat si volbu, nezobrazovat to znovu
|
||||||
unlock.success.revealBtn=Zobrazit jednotku
|
unlock.success.revealBtn=Zobrazit jednotku
|
||||||
## Failure
|
## Failure
|
||||||
unlock.error.message=Nelze odemknout trezor
|
|
||||||
### Invalid Mount Point
|
|
||||||
unlock.error.invalidMountPoint.notExisting=Připojovací bod %s není složkou, není prázdný nebo neexistuje.
|
|
||||||
unlock.error.invalidMountPoint.existing=Připojovací bod %s již existuje nebo nadřazená složka chybí.
|
|
||||||
unlock.error.invalidMountPoint.driveLetterOccupied=Písmeno „%s“ už je používáno pro jiný disk.
|
|
||||||
## Hub
|
## Hub
|
||||||
|
hub.noKeychain.openBtn=Otevřít předvolby
|
||||||
### Waiting
|
### Waiting
|
||||||
|
hub.auth.message=Čekání na ověření…
|
||||||
### Receive Key
|
### Receive Key
|
||||||
### Register Device
|
### Register Device
|
||||||
|
hub.register.message=Je vyžadován název zařízení
|
||||||
|
hub.register.nameLabel=Název zařízení
|
||||||
|
hub.register.occupiedMsg=Jméno je již používáno
|
||||||
hub.register.registerBtn=Potvrdit
|
hub.register.registerBtn=Potvrdit
|
||||||
### Registration Success
|
### Registration Success
|
||||||
|
hub.registerSuccess.message=Jméno zařízení
|
||||||
|
hub.registerSuccess.description=Pro přístup k trezoru musí být vaše zařízení autorizováno majitelem trezoru.
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
|
hub.registerFailed.message=Pojmenování zařízení se nezdařilo
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
|
hub.unauthorized.message=Přístup odepřen
|
||||||
|
hub.unauthorized.description=Vaše zařízení dosud nebylo oprávněno k přístupu k tomuto trezoru. Požádejte vlastníka trezoru, aby jej autorizoval.
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
|
hub.licenseExceeded.message=Licence byla překročena
|
||||||
|
hub.licenseExceeded.description=Cryptomator Hub poskytl přístup více uživatelům, než jsou jeho licenční povolení. Obraťte se na správce Hubu pro aktualizaci licence nebo správce trezoru pro odstranění uživatelů z trezoru.
|
||||||
|
|
||||||
|
|
||||||
# Lock
|
# Lock
|
||||||
@@ -142,8 +159,13 @@ lock.fail.description=Trezor "%s" nelze uzamknout. Ujistěte se, že je neulože
|
|||||||
# Migration
|
# Migration
|
||||||
migration.title=Upgrade trezoru
|
migration.title=Upgrade trezoru
|
||||||
## Start
|
## Start
|
||||||
migration.start.prompt=Váš trezor "%s" musí být aktualizován na nový formát.\nNež začnete, ujistěte se, že není žádná probíhající či čekající synchronizace na tomto trezoru.
|
migration.start.header=Upgrade trezoru
|
||||||
migration.start.confirm=Ano, můj trezor je plně synchronizován.
|
migration.start.text=Pro otevření trezoru "%s" v této nové verzi Cryptomatoru je třeba aktualizovat trezor na novější formát. Před tím byste měli vědět následující:
|
||||||
|
migration.start.remarkUndone=Tuto aktualizaci nelze vrátit zpět.
|
||||||
|
migration.start.remarkVersions=Starší verze Cryptomator nebudou moci otevřít aktualizovaný trezor.
|
||||||
|
migration.start.remarkCanRun=Musíte si být jisti, že každé zařízení, ze kterého máte přístup k trezoru, může spustit tuto verzi Cryptomatoru.
|
||||||
|
migration.start.remarkSynced=Před aktualizací se musíte ujistit, že je váš trezor plně synchronizován na tomto zařízení a na vašich dalších zařízeních.
|
||||||
|
migration.start.confirm=Přečetl jsem si výše uvedené informace a rozumím jim
|
||||||
## Run
|
## Run
|
||||||
migration.run.enterPassword=Zadejte heslo pro "%s"
|
migration.run.enterPassword=Zadejte heslo pro "%s"
|
||||||
migration.run.startMigrationBtn=Migrovat trezor
|
migration.run.startMigrationBtn=Migrovat trezor
|
||||||
@@ -191,7 +213,15 @@ health.check.detail.checkFinished=Kontrola byla úspěšně dokončena.
|
|||||||
health.check.detail.checkFinishedAndFound=Kontrola byla dokončena. Zkontrolujte prosím výsledky.
|
health.check.detail.checkFinishedAndFound=Kontrola byla dokončena. Zkontrolujte prosím výsledky.
|
||||||
health.check.detail.checkFailed=Kontrola byla ukončena z důvodu chyby.
|
health.check.detail.checkFailed=Kontrola byla ukončena z důvodu chyby.
|
||||||
health.check.detail.checkCancelled=Kontrola byla zrušena.
|
health.check.detail.checkCancelled=Kontrola byla zrušena.
|
||||||
|
health.check.detail.listFilters.label=Filtr
|
||||||
|
health.check.detail.fixAllSpecificBtn=Opravit všechny typy
|
||||||
health.check.exportBtn=Exportovat sestavu
|
health.check.exportBtn=Exportovat sestavu
|
||||||
|
## Result view
|
||||||
|
health.result.severityFilter.all=Závažnost - vše
|
||||||
|
health.result.severityFilter.good=Dobré
|
||||||
|
health.result.severityFilter.info=Informace
|
||||||
|
health.result.severityFilter.warn=Varování
|
||||||
|
health.result.severityFilter.crit=Kritické
|
||||||
## Fix Application
|
## Fix Application
|
||||||
health.fix.fixBtn=Opravit
|
health.fix.fixBtn=Opravit
|
||||||
health.fix.successTip=Oprava byla úspěšná
|
health.fix.successTip=Oprava byla úspěšná
|
||||||
@@ -222,9 +252,7 @@ preferences.interface.showMinimizeButton=Zobrazit tlačítko minimalizovat
|
|||||||
preferences.interface.showTrayIcon=Zobrazit ikonu v liště (vyžaduje restart)
|
preferences.interface.showTrayIcon=Zobrazit ikonu v liště (vyžaduje restart)
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Virtuální jednotky
|
preferences.volume=Virtuální jednotky
|
||||||
preferences.volume.type=Typ připojení
|
preferences.volume.type.automatic=Automatické
|
||||||
preferences.volume.webdav.port=WebDAV Port
|
|
||||||
preferences.volume.webdav.scheme=WebDAV schéma
|
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=Aktualizace
|
preferences.updates=Aktualizace
|
||||||
preferences.updates.currentVersion=Aktuální verze: %s
|
preferences.updates.currentVersion=Aktuální verze: %s
|
||||||
@@ -272,15 +300,17 @@ stats.encr.total.data.mib=Zašifrováno: %.1f MiB
|
|||||||
stats.encr.total.data.gib=Zašifrováno: %.1f GiB
|
stats.encr.total.data.gib=Zašifrováno: %.1f GiB
|
||||||
stats.write.accessCount=Celkem zapsáno: %d
|
stats.write.accessCount=Celkem zapsáno: %d
|
||||||
|
|
||||||
|
## Accesses
|
||||||
|
stats.access.current=Přístup: %d
|
||||||
|
stats.access.total=Přístup celkem: %d
|
||||||
|
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=Zavřít
|
main.closeBtn.tooltip=Zavřít
|
||||||
main.minimizeBtn.tooltip=Minimalizovat
|
main.minimizeBtn.tooltip=Minimalizovat
|
||||||
main.preferencesBtn.tooltip=Nastavení
|
main.preferencesBtn.tooltip=Nastavení
|
||||||
main.debugModeEnabled.tooltip=Režim ladění je povolen
|
main.debugModeEnabled.tooltip=Režim ladění je povolen
|
||||||
main.supporterCertificateMissing.tooltip=Prosím, zvažte darování
|
main.supporterCertificateMissing.tooltip=Prosím, zvažte darování
|
||||||
## Drag 'n' Drop
|
|
||||||
main.dropZone.dropVault=Přidat tento trezor
|
|
||||||
main.dropZone.unknownDragboardContent=Pokud chcete přidat trezor, přetáhněte jej do tohoto okna
|
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.emptyList.onboardingInstruction=Klikněte zde pro přidání nového trezoru
|
main.vaultlist.emptyList.onboardingInstruction=Klikněte zde pro přidání nového trezoru
|
||||||
main.vaultlist.contextMenu.remove=Odstranit…
|
main.vaultlist.contextMenu.remove=Odstranit…
|
||||||
@@ -354,28 +384,31 @@ vaultOptions.mount.winDriveLetterOccupied=obsazeno
|
|||||||
vaultOptions.mount.mountPoint=Přípojný bod
|
vaultOptions.mount.mountPoint=Přípojný bod
|
||||||
vaultOptions.mount.mountPoint.auto=Automaticky vybrat vhodné místo
|
vaultOptions.mount.mountPoint.auto=Automaticky vybrat vhodné místo
|
||||||
vaultOptions.mount.mountPoint.driveLetter=Použít zvolenou jednotku
|
vaultOptions.mount.mountPoint.driveLetter=Použít zvolenou jednotku
|
||||||
vaultOptions.mount.mountPoint.custom=Vlastní cesta
|
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrat...
|
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrat...
|
||||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Vyberte prázdný adresář
|
|
||||||
## Master Key
|
## Master Key
|
||||||
vaultOptions.masterkey=Heslo
|
vaultOptions.masterkey=Heslo
|
||||||
vaultOptions.masterkey.changePasswordBtn=Změnit heslo
|
vaultOptions.masterkey.changePasswordBtn=Změnit heslo
|
||||||
vaultOptions.masterkey.forgetSavedPasswordBtn=Zapomenout uložené heslo
|
vaultOptions.masterkey.forgetSavedPasswordBtn=Zapomenout uložené heslo
|
||||||
vaultOptions.masterkey.recoveryKeyExplanation=Obnovovací klíč je váš jediný způsob, jak obnovit přístup k trezoru, pokud ztratíte své heslo.
|
vaultOptions.masterkey.recoveryKeyExplanation=Obnovovací klíč je váš jediný způsob, jak obnovit přístup k trezoru, pokud ztratíte své heslo.
|
||||||
vaultOptions.masterkey.showRecoveryKeyBtn=Zobrazit klíč k obnově
|
vaultOptions.masterkey.showRecoveryKeyBtn=Zobrazit klíč k obnově
|
||||||
|
vaultOptions.masterkey.recoverPasswordBtn=Resetovat heslo
|
||||||
|
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
## Display Recovery Key
|
## Display Recovery Key
|
||||||
|
recoveryKey.display.title=Zobrazit obnovovací klíč
|
||||||
|
recoveryKey.create.message=Vyžadováno heslo
|
||||||
recoveryKey.create.description=Zadejte své heslo pro zobrazení obnovovacího klíče pro "%s":
|
recoveryKey.create.description=Zadejte své heslo pro zobrazení obnovovacího klíče pro "%s":
|
||||||
recoveryKey.display.description=Následující obnovovací klíč může být použit k obnovení přístupu k "%s":
|
recoveryKey.display.description=Následující obnovovací klíč může být použit k obnovení přístupu k "%s":
|
||||||
recoveryKey.display.StorageHints=Uchovejte ho někde velmi bezpečně, např.\n • Uložit pomocí správce hesel\n • Uložit na USB flash disk\n • Vytisknout na papír
|
recoveryKey.display.StorageHints=Uchovejte ho někde velmi bezpečně, např.\n • Uložit pomocí správce hesel\n • Uložit na USB flash disk\n • Vytisknout na papír
|
||||||
## Reset Password
|
## Reset Password
|
||||||
### Enter Recovery Key
|
### Enter Recovery Key
|
||||||
|
recoveryKey.recover.title=Resetovat heslo
|
||||||
recoveryKey.recover.prompt=Zadejte váš obnovovací klíč pro "%s":
|
recoveryKey.recover.prompt=Zadejte váš obnovovací klíč pro "%s":
|
||||||
recoveryKey.recover.validKey=Toto je platný obnovovací klíč
|
recoveryKey.recover.validKey=Toto je platný obnovovací klíč
|
||||||
recoveryKey.printout.heading=Obnovovací klíč Cryptomator\n"%s"\n
|
recoveryKey.printout.heading=Obnovovací klíč Cryptomator\n"%s"\n
|
||||||
### Reset Password
|
### Reset Password
|
||||||
|
recoveryKey.recover.resetBtn=Resetovat
|
||||||
### Recovery Key Password Reset Success
|
### Recovery Key Password Reset Success
|
||||||
|
|
||||||
# New Password
|
# New Password
|
||||||
@@ -391,6 +424,7 @@ passwordStrength.messageLabel.3=silné
|
|||||||
passwordStrength.messageLabel.4=velmi silné
|
passwordStrength.messageLabel.4=velmi silné
|
||||||
|
|
||||||
# Quit
|
# Quit
|
||||||
|
quit.title=Ukončit aplikaci
|
||||||
quit.lockAndQuitBtn=Uzamknout a zavřít
|
quit.lockAndQuitBtn=Uzamknout a zavřít
|
||||||
|
|
||||||
# Forced Quit
|
# Forced Quit
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user