mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 16:04:28 +00:00
Compare commits
268
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c615ad2cd7 | ||
|
|
414465371b | ||
|
|
91a4db388f | ||
|
|
feed72a729 | ||
|
|
bca20a7a66 | ||
|
|
a38377baaa | ||
|
|
61a45fc738 | ||
|
|
3a65b5551f | ||
|
|
94ed5c18b8 | ||
|
|
a559483d86 | ||
|
|
3acdef1dd0 | ||
|
|
def6f8ab95 | ||
|
|
11ba1f3ddc | ||
|
|
3228f2cf5f | ||
|
|
9c4f7ad79d | ||
|
|
3bd57d162b | ||
|
|
5ea73a5a8d | ||
|
|
38670838c7 | ||
|
|
3e0b84dbce | ||
|
|
2302db6206 | ||
|
|
a94bf99660 | ||
|
|
0c5ce353b1 | ||
|
|
b3ce777a42 | ||
|
|
3085df3397 | ||
|
|
fb740b605f | ||
|
|
6a74d9f3b2 | ||
|
|
1c7dffb63f | ||
|
|
a213f073b1 | ||
|
|
1ab73be1f4 | ||
|
|
8412871090 | ||
|
|
fa63f3ca67 | ||
|
|
dbc0f52481 | ||
|
|
b8cd1caeac | ||
|
|
43c25b6d97 | ||
|
|
e44bc09074 | ||
|
|
7b02f78ef5 | ||
|
|
f7e8a4d1e6 | ||
|
|
664375d692 | ||
|
|
2d968eac8c | ||
|
|
542d2fcfe1 | ||
|
|
144e929896 | ||
|
|
803748f78d | ||
|
|
3410e7243a | ||
|
|
84732337ca | ||
|
|
c527808710 | ||
|
|
99c89dbf39 | ||
|
|
829b64cd3d | ||
|
|
f4007267fb | ||
|
|
c82a0bfaf3 | ||
|
|
251ad65344 | ||
|
|
155ba4607b | ||
|
|
62f6865d3e | ||
|
|
ffb3290248 | ||
|
|
a013ae3d91 | ||
|
|
19a954e677 | ||
|
|
f262980acc | ||
|
|
3b3aa18c92 | ||
|
|
c7b8bc89c2 | ||
|
|
5fc981abd3 | ||
|
|
36ec1a5ebc | ||
|
|
be4aad4168 | ||
|
|
fcb940e29c | ||
|
|
4f3ca2a6c4 | ||
|
|
27cd34bee0 | ||
|
|
c07e51be51 | ||
|
|
0421879b39 | ||
|
|
2d627717a0 | ||
|
|
dc0e88a694 | ||
|
|
a5e3630375 | ||
|
|
17335e8f70 | ||
|
|
42dd2fba48 | ||
|
|
e1cca6427c | ||
|
|
42d3dbaa23 | ||
|
|
185d67c492 | ||
|
|
a8af3c8b40 | ||
|
|
038a7fac62 | ||
|
|
48408fa40d | ||
|
|
18a417667e | ||
|
|
aab07b13e3 | ||
|
|
869e40e351 | ||
|
|
bf91e3f15c | ||
|
|
17544828e4 | ||
|
|
cfef3139b1 | ||
|
|
b01ba27b06 | ||
|
|
5a33e66a7d | ||
|
|
3c4e35406e | ||
|
|
d3275fa4e7 | ||
|
|
a30db34c19 | ||
|
|
509100e7ae | ||
|
|
024c34172b | ||
|
|
638570562b | ||
|
|
ee5a5c6563 | ||
|
|
85c52c60c5 | ||
|
|
93184abd3c | ||
|
|
8062392ccd | ||
|
|
d49c02c7de | ||
|
|
d66cfe0e7c | ||
|
|
fa26fa9dee | ||
|
|
52fc94ca01 | ||
|
|
e9a412ae16 | ||
|
|
8104c35d76 | ||
|
|
baa2e05e69 | ||
|
|
f50b204cef | ||
|
|
ed902bc59f | ||
|
|
693299a5d7 | ||
|
|
def64aa2ac | ||
|
|
c5bb8a131d | ||
|
|
2e443c72a9 | ||
|
|
e9ee17493b | ||
|
|
35eb548d8e | ||
|
|
dc5d6e734e | ||
|
|
729f38866f | ||
|
|
4de8434f1b | ||
|
|
cd40731b5f | ||
|
|
24293b316f | ||
|
|
062c674ef1 | ||
|
|
830bb5776f | ||
|
|
bfdb23785e | ||
|
|
248d655afb | ||
|
|
995c0a4b92 | ||
|
|
42425afe56 | ||
|
|
a1b712495f | ||
|
|
2225476bf8 | ||
|
|
325057c548 | ||
|
|
b1a5eed2aa | ||
|
|
4ca190cfb9 | ||
|
|
828fd321cc | ||
|
|
2e32f39986 | ||
|
|
daa026b285 | ||
|
|
87084e8c2a | ||
|
|
8e52058373 | ||
|
|
8bf0acf35f | ||
|
|
e0d3a3d9c7 | ||
|
|
b59ce75ecd | ||
|
|
748f895b98 | ||
|
|
6d974c7fcf | ||
|
|
0ed73e8b41 | ||
|
|
538b4ecd0b | ||
|
|
8f32b46b30 | ||
|
|
4de25afde0 | ||
|
|
2e6c228f3f | ||
|
|
00fc2a9837 | ||
|
|
22e80e7dac | ||
|
|
517e05c378 | ||
|
|
5759b7e596 | ||
|
|
bfb26f6171 | ||
|
|
f36e2bb4b7 | ||
|
|
b85e455ff5 | ||
|
|
a902400522 | ||
|
|
a6d6474294 | ||
|
|
06859fe36b | ||
|
|
7d7b7c1dc9 | ||
|
|
706d2b51be | ||
|
|
92bf136a8b | ||
|
|
f00f028d8b | ||
|
|
325092efb0 | ||
|
|
aaf8036713 | ||
|
|
c86ee679a9 | ||
|
|
a0fcb63a1a | ||
|
|
55ba255651 | ||
|
|
47df4213c3 | ||
|
|
6929760979 | ||
|
|
720fbd0e6b | ||
|
|
923af4bc83 | ||
|
|
e9c464ba8f | ||
|
|
7b0f616747 | ||
|
|
65788f3c1f | ||
|
|
f55fbf0f64 | ||
|
|
851838e3a2 | ||
|
|
e194f9b205 | ||
|
|
2f8df88ad5 | ||
|
|
55247d9f8a | ||
|
|
48f273b755 | ||
|
|
4fc31cb13f | ||
|
|
a795b82a42 | ||
|
|
1e28a04a7b | ||
|
|
b88eef4591 | ||
|
|
eef5edf775 | ||
|
|
bdb1d605eb | ||
|
|
dfe4b74f3d | ||
|
|
985a3c30de | ||
|
|
1b5125dfed | ||
|
|
da240ce8b9 | ||
|
|
52affb891e | ||
|
|
fac72ca24a | ||
|
|
5cbed502ed | ||
|
|
dce4c60881 | ||
|
|
63bf0315c7 | ||
|
|
8b543c48ee | ||
|
|
087a5326df | ||
|
|
a4545352d8 | ||
|
|
a07dea7ca8 | ||
|
|
c369487e8e | ||
|
|
98590ecec5 | ||
|
|
316f3975ed | ||
|
|
71bf7e0913 | ||
|
|
c7839e2c46 | ||
|
|
89b8bc4148 | ||
|
|
43ad1c05c0 | ||
|
|
1343099be6 | ||
|
|
b895ac69fb | ||
|
|
7d281e2878 | ||
|
|
688450bf5a | ||
|
|
e994133177 | ||
|
|
68713ef9a7 | ||
|
|
dbacbc8874 | ||
|
|
8382299a05 | ||
|
|
93b09cf449 | ||
|
|
dac3311b81 | ||
|
|
5b1ca7a533 | ||
|
|
e940c29110 | ||
|
|
6a704ca0ad | ||
|
|
38c102a64b | ||
|
|
b632c84bb6 | ||
|
|
6bb5ed1d73 | ||
|
|
1052e4c3d2 | ||
|
|
44f776050f | ||
|
|
baa29dbd73 | ||
|
|
1caf391936 | ||
|
|
82caf76e12 | ||
|
|
de27bbf96e | ||
|
|
1199ef40dd | ||
|
|
e7e88f13e3 | ||
|
|
41c22b7840 | ||
|
|
19cc4e8a37 | ||
|
|
7bc1d52bf8 | ||
|
|
fd84ff09e1 | ||
|
|
3dce175f94 | ||
|
|
0aaa3263cf | ||
|
|
c4dcbd8c44 | ||
|
|
ef9cdd8e92 | ||
|
|
09ced50590 | ||
|
|
b2ab1a30a5 | ||
|
|
9eff3916b3 | ||
|
|
98472d1952 | ||
|
|
1ceafb69a5 | ||
|
|
f3f4b6576c | ||
|
|
e699d7dafc | ||
|
|
5eb6d186c0 | ||
|
|
edc9409e56 | ||
|
|
d015719fae | ||
|
|
afc69e447e | ||
|
|
54fbbd734f | ||
|
|
2f657c7c14 | ||
|
|
d29872e69e | ||
|
|
ab9278a9fc | ||
|
|
49769f986a | ||
|
|
b68b895bba | ||
|
|
99fb9972f9 | ||
|
|
f2e7d0fae2 | ||
|
|
77f9e6c411 | ||
|
|
31fcf294e9 | ||
|
|
a03f00ee9a | ||
|
|
80af32bd82 | ||
|
|
b7e4c0fe31 | ||
|
|
76d499596f | ||
|
|
1818344c49 | ||
|
|
e79c975d7b | ||
|
|
c85823fe04 | ||
|
|
8f46aec851 | ||
|
|
cd6d1eeab3 | ||
|
|
52b74a74fe | ||
|
|
368f0630c1 | ||
|
|
040bc4c5df | ||
|
|
07906fbea5 | ||
|
|
44f67b7c89 | ||
|
|
44ec19122a | ||
|
|
8ba2540b35 |
@@ -26,6 +26,7 @@ body:
|
|||||||
Examples:
|
Examples:
|
||||||
- Operating System: Windows 10
|
- Operating System: Windows 10
|
||||||
- Cryptomator: 1.5.16
|
- Cryptomator: 1.5.16
|
||||||
|
- OneDrive: 23.226
|
||||||
- LibreOffice: 7.1.4
|
- LibreOffice: 7.1.4
|
||||||
value: |
|
value: |
|
||||||
- Operating System:
|
- Operating System:
|
||||||
|
|||||||
+29
-2
@@ -6,11 +6,38 @@ updates:
|
|||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
day: "monday"
|
day: "monday"
|
||||||
time: "06:00"
|
time: "06:00"
|
||||||
timezone: "UTC"
|
timezone: "Etc/UTC"
|
||||||
groups:
|
groups:
|
||||||
maven-dependencies:
|
java-test-dependencies:
|
||||||
|
patterns:
|
||||||
|
- "org.junit.jupiter:*"
|
||||||
|
- "org.mockito:*"
|
||||||
|
- "org.hamcrest:*"
|
||||||
|
- "com.google.jimfs:jimfs"
|
||||||
|
maven-build-plugins:
|
||||||
|
patterns:
|
||||||
|
- "org.apache.maven.plugins:*"
|
||||||
|
- "org.jacoco:jacoco-maven-plugin"
|
||||||
|
- "org.owasp:dependency-check-maven"
|
||||||
|
- "me.fabriciorby:maven-surefire-junit5-tree-reporter"
|
||||||
|
- "org.codehaus.mojo:license-maven-plugin"
|
||||||
|
javafx:
|
||||||
|
patterns:
|
||||||
|
- "org.openjfx:*"
|
||||||
|
java-production-dependencies:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
|
exclude-patterns:
|
||||||
|
- "org.openjfx:*"
|
||||||
|
- "org.apache.maven.plugins:*"
|
||||||
|
- "org.jacoco:jacoco-maven-plugin"
|
||||||
|
- "org.owasp:dependency-check-maven"
|
||||||
|
- "me.fabriciorby:maven-surefire-junit5-tree-reporter"
|
||||||
|
- "org.codehaus.mojo:license-maven-plugin"
|
||||||
|
- "org.junit.jupiter:*"
|
||||||
|
- "org.mockito:*"
|
||||||
|
- "org.hamcrest:*"
|
||||||
|
- "com.google.jimfs:jimfs"
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/" # even for `.github/workflows`
|
directory: "/" # even for `.github/workflows`
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'zulu'
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_VERSION: '21.0.1+12'
|
JAVA_VERSION: '21.0.2+13'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get-version:
|
get-version:
|
||||||
@@ -29,16 +29,16 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
appimage-suffix: x86_64
|
appimage-suffix: x86_64
|
||||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
|
||||||
openjfx-sha: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
|
openjfx-sha: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
|
||||||
- os: [self-hosted, Linux, ARM64]
|
- os: [self-hosted, Linux, ARM64]
|
||||||
appimage-suffix: aarch64
|
appimage-suffix: aarch64
|
||||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
|
||||||
openjfx-sha: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
|
openjfx-sha: '871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -68,12 +68,13 @@ jobs:
|
|||||||
- name: Set version
|
- name: Set version
|
||||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
run: mvn -B clean package -Plinux -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
cp target/cryptomator-*.jar target/mods
|
cp target/cryptomator-*.jar target/mods
|
||||||
- name: Run jlink
|
- name: Run jlink
|
||||||
|
#Remark: no compression is applied for improved build compression later (here appimage)
|
||||||
run: >
|
run: >
|
||||||
${JAVA_HOME}/bin/jlink
|
${JAVA_HOME}/bin/jlink
|
||||||
--verbose
|
--verbose
|
||||||
@@ -84,12 +85,7 @@ jobs:
|
|||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
--strip-debug
|
--strip-debug
|
||||||
--compress=1
|
--compress zip-0
|
||||||
- name: Prepare additional launcher
|
|
||||||
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
|
||||||
env:
|
|
||||||
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
|
|
||||||
REVISION_NUM: ${{ needs.get-version.outputs.revNum }}
|
|
||||||
- name: Run jpackage
|
- name: Run jpackage
|
||||||
run: >
|
run: >
|
||||||
${JAVA_HOME}/bin/jpackage
|
${JAVA_HOME}/bin/jpackage
|
||||||
@@ -102,7 +98,7 @@ jobs:
|
|||||||
--dest appdir
|
--dest appdir
|
||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2023 Skymatic GmbH"
|
--copyright "(C) 2016 - 2024 Skymatic GmbH"
|
||||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
--java-options "--enable-preview"
|
--java-options "--enable-preview"
|
||||||
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
|
||||||
@@ -163,7 +159,7 @@ jobs:
|
|||||||
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
|
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
|
||||||
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
|
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: appimage-${{ matrix.appimage-suffix }}
|
name: appimage-${{ matrix.appimage-suffix }}
|
||||||
path: |
|
path: |
|
||||||
@@ -172,7 +168,7 @@ jobs:
|
|||||||
cryptomator-*.asc
|
cryptomator-*.asc
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Publish AppImage on GitHub Releases
|
- name: Publish AppImage on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
name: AntiVirus Whitelisting
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
url:
|
||||||
|
description: "Url to the file to upload"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
url:
|
||||||
|
description: "Url to the file to upload"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
allowlist:
|
||||||
|
name: Anti Virus Allowlisting
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download file
|
||||||
|
run: |
|
||||||
|
curl --remote-name ${{ inputs.url }} -L
|
||||||
|
- name: Upload to Kaspersky
|
||||||
|
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
||||||
|
with:
|
||||||
|
protocol: ftps
|
||||||
|
server: allowlist.kaspersky-labs.com
|
||||||
|
port: 990
|
||||||
|
username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
|
||||||
|
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
|
||||||
|
- name: Upload to Avast
|
||||||
|
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
||||||
|
with:
|
||||||
|
protocol: ftp
|
||||||
|
server: whitelisting.avast.com
|
||||||
|
port: 21
|
||||||
|
username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
|
||||||
|
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
|
||||||
@@ -19,13 +19,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Cache SonarCloud packages
|
- name: Cache SonarCloud packages
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.sonar/cache
|
path: ~/.sonar/cache
|
||||||
key: ${{ runner.os }}-sonar
|
key: ${{ runner.os }}-sonar
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
mvn -B verify
|
mvn -B verify
|
||||||
jacoco:report
|
jacoco:report
|
||||||
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
|
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
|
||||||
-Pcoverage,dependency-check
|
-Pcoverage
|
||||||
-Dsonar.projectKey=cryptomator_cryptomator
|
-Dsonar.projectKey=cryptomator_cryptomator
|
||||||
-Dsonar.organization=cryptomator
|
-Dsonar.organization=cryptomator
|
||||||
-Dsonar.host.url=https://sonarcloud.io
|
-Dsonar.host.url=https://sonarcloud.io
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
|
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: ${{ env.JDK_VERSION }}
|
java-version: ${{ env.JDK_VERSION }}
|
||||||
distribution: ${{ env.JDK_VENDOR }}
|
distribution: ${{ env.JDK_VENDOR }}
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
|
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
|
||||||
jdk-version: ${{ steps.get-data.outputs.jdk-version}}
|
jdk-version: ${{ steps.get-data.outputs.jdk-version}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 21
|
||||||
distribution: ${{ env.JDK_VENDOR }}
|
distribution: ${{ env.JDK_VENDOR }}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'zulu'
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_VERSION: '21.0.1+12'
|
JAVA_VERSION: '21.0.2+13'
|
||||||
COFFEELIBS_JDK: 21
|
COFFEELIBS_JDK: 21
|
||||||
COFFEELIBS_JDK_VERSION: '21.0.1+12-0ppa1'
|
COFFEELIBS_JDK_VERSION: '21.0.2+13-0ppa1'
|
||||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
|
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
|
||||||
OPENJFX_JMODS_AMD64_HASH: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
|
OPENJFX_JMODS_AMD64_HASH: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
|
||||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
|
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
|
||||||
OPENJFX_JMODS_AARCH64_HASH: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
|
OPENJFX_JMODS_AARCH64_HASH: '871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -44,16 +44,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo add-apt-repository ppa:coffeelibs/openjdk
|
sudo add-apt-repository ppa:coffeelibs/openjdk
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }} libgtk2.0-0
|
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }}
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
run: mvn -B clean package -Plinux -DskipTests
|
||||||
- name: Download OpenJFX jmods
|
- name: Download OpenJFX jmods
|
||||||
id: download-jmods
|
id: download-jmods
|
||||||
run: |
|
run: |
|
||||||
@@ -128,7 +128,7 @@ jobs:
|
|||||||
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
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux-deb-package
|
name: linux-deb-package
|
||||||
path: |
|
path: |
|
||||||
@@ -142,10 +142,9 @@ jobs:
|
|||||||
- name: Publish on PPA
|
- name: Publish on PPA
|
||||||
if: 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:
|
# If ref is a tag, also upload to GitHub Releases:
|
||||||
- name: Publish Debian package on GitHub Releases
|
- name: Publish Debian package on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/') && inputs.dput
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
name: OWASP Maven Dependency Check
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 8 * * 0'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-dependencies:
|
||||||
|
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@v1
|
||||||
|
with:
|
||||||
|
runner-os: 'ubuntu-latest'
|
||||||
|
java-distribution: 'temurin'
|
||||||
|
java-version: 21
|
||||||
|
secrets:
|
||||||
|
nvd-api-key: ${{ secrets.NVD_API_KEY }}
|
||||||
|
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Get download count of latest releases
|
- name: Get download count of latest releases
|
||||||
id: get-stats
|
id: get-stats
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const query = `query($owner:String!, $name:String!) {
|
const query = `query($owner:String!, $name:String!) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Query Discussion Data
|
- name: Query Discussion Data
|
||||||
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
|
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
|
||||||
id: query-data
|
id: query-data
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
|
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
name: Create PR for flathub
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Release tag'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
get-version:
|
||||||
|
uses: ./.github/workflows/get-version.yml
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.tag }}
|
||||||
|
tarball:
|
||||||
|
name: Determines tarball url and compute checksum
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-version]
|
||||||
|
if: github.event_name == 'workflow_dispatch' || needs.get-version.outputs.versionType == 'stable'
|
||||||
|
outputs:
|
||||||
|
url: ${{ steps.url.outputs.url}}
|
||||||
|
sha512: ${{ steps.sha512.outputs.sha512}}
|
||||||
|
steps:
|
||||||
|
- name: Determine tarball url
|
||||||
|
id: url
|
||||||
|
run: |
|
||||||
|
URL="";
|
||||||
|
if [[ -n "${{ inputs.tag }}" ]]; then
|
||||||
|
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ inputs.tag }}.tar.gz"
|
||||||
|
else
|
||||||
|
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz"
|
||||||
|
fi
|
||||||
|
echo "url=${URL}" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Download source tarball and compute checksum
|
||||||
|
id: sha512
|
||||||
|
run: |
|
||||||
|
curl --silent --fail-with-body -L -H "Accept: application/vnd.github+json" ${{ steps.url.outputs.url }} --output cryptomator.tar.gz
|
||||||
|
TARBALL_SHA512=$(sha512sum cryptomator.tar.gz | cut -d ' ' -f1)
|
||||||
|
echo "sha512=${TARBALL_SHA512}" >> "$GITHUB_OUTPUT"
|
||||||
|
flathub:
|
||||||
|
name: Create PR for flathub
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [tarball, get-version]
|
||||||
|
env:
|
||||||
|
FLATHUB_PR_URL: tbd
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'flathub/org.cryptomator.Cryptomator'
|
||||||
|
token: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||||
|
- name: Checkout release branch
|
||||||
|
run: |
|
||||||
|
git checkout -b release/${{ needs.get-version.outputs.semVerStr }}
|
||||||
|
- name: Update build file
|
||||||
|
run: |
|
||||||
|
sed -i -e 's/VERSION: [0-9]\+\.[0-9]\+\.[0-9]\+.*/VERSION: ${{ needs.get-version.outputs.semVerStr }}/g' org.cryptomator.Cryptomator.yaml
|
||||||
|
sed -i -e 's/sha512: [0-9A-Za-z_\+-]\{128\} #CRYPTOMATOR/sha512: ${{ needs.tarball.outputs.sha512 }} #CRYPTOMATOR/g' org.cryptomator.Cryptomator.yaml
|
||||||
|
sed -i -e 's;url: https://github.com/cryptomator/cryptomator/archive/refs/tags/[^[:blank:]]\+;url: ${{ needs.tarball.outputs.url }};g' org.cryptomator.Cryptomator.yaml
|
||||||
|
- name: Commit and push
|
||||||
|
run: |
|
||||||
|
git config user.name "${{ github.actor }}"
|
||||||
|
git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
|
||||||
|
git config push.autoSetupRemote true
|
||||||
|
git stage .
|
||||||
|
git commit -m "Prepare release ${{needs.get-version.outputs.semVerStr}}"
|
||||||
|
git push
|
||||||
|
- name: Create pull request
|
||||||
|
run: |
|
||||||
|
echo "> [!IMPORTANT]\n> Todos:\n> - [ ] Update maven dependencies\n> - [ ] Check for JDK update\n> - [ ] Check for JFX update" > pr_body.md
|
||||||
|
PR_URL=$(gh pr create --title "Release ${{ needs.get-version.outputs.semVerStr }}" --body-file pr_body.md)
|
||||||
|
echo "FLATHUB_PR_URL=$PR_URL" >> "$GITHUB_ENV"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||||
|
- name: Slack Notification
|
||||||
|
uses: rtCamp/action-slack-notify@v2
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
|
SLACK_ICON: false
|
||||||
|
SLACK_ICON_EMOJI: ':bot:'
|
||||||
|
SLACK_CHANNEL: 'cryptomator-desktop'
|
||||||
|
SLACK_TITLE: "Flathub release PR created for ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} created."
|
||||||
|
SLACK_MESSAGE: "See <${{ env.FLATHUB_PR_URL }}|PR> on how to proceed.>."
|
||||||
|
SLACK_FOOTER: false
|
||||||
|
MSG_MINIMAL: true
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
SEM_VER_STR=${GITHUB_REF##*/}
|
||||||
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
SEM_VER_STR="${{ inputs.version }}"
|
||||||
else
|
else
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||||
fi
|
fi
|
||||||
@@ -73,4 +73,4 @@ jobs:
|
|||||||
- name: Validate Version
|
- name: Validate Version
|
||||||
uses: skymatic/semver-validation-action@v2
|
uses: skymatic/semver-validation-action@v2
|
||||||
with:
|
with:
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
version: ${{ steps.versions.outputs.semVerStr }}
|
||||||
@@ -16,7 +16,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'zulu'
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_VERSION: '21.0.1+12'
|
JAVA_VERSION: '21.0.2+13'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get-version:
|
get-version:
|
||||||
@@ -37,19 +37,19 @@ jobs:
|
|||||||
output-suffix: x64
|
output-suffix: x64
|
||||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
fuse-lib: macFUSE
|
fuse-lib: macFUSE
|
||||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-x64_bin-jmods.zip'
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-x64_bin-jmods.zip'
|
||||||
openjfx-sha: '55b8ff7453d59c89ae129f6c9c5ad7b09a5d359568811b376ac1766c14d6a17c'
|
openjfx-sha: 'bd6abab20da73d5a968dcf2fd915d81b5fb919340e3bb84979ee9a888a829939'
|
||||||
- os: [self-hosted, macOS, ARM64]
|
- os: [self-hosted, macOS, ARM64]
|
||||||
architecture: aarch64
|
architecture: aarch64
|
||||||
output-suffix: arm64
|
output-suffix: arm64
|
||||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
fuse-lib: FUSE-T
|
fuse-lib: FUSE-T
|
||||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip'
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-aarch64_bin-jmods.zip'
|
||||||
openjfx-sha: 'c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf'
|
openjfx-sha: '7afaa1c57a6cc3c384d636e597b9a5364693e2db4aaec0a6e63d2fa964400b58'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -79,12 +79,13 @@ jobs:
|
|||||||
- name: Set version
|
- name: Set version
|
||||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,mac -DskipTests
|
run: mvn -B clean package -Pmac -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
cp target/cryptomator-*.jar target/mods
|
cp target/cryptomator-*.jar target/mods
|
||||||
- name: Run jlink
|
- name: Run jlink
|
||||||
|
#Remark: no compression is applied for improved build compression later (here dmg)
|
||||||
run: >
|
run: >
|
||||||
${JAVA_HOME}/bin/jlink
|
${JAVA_HOME}/bin/jlink
|
||||||
--verbose
|
--verbose
|
||||||
@@ -95,7 +96,7 @@ jobs:
|
|||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
--strip-debug
|
--strip-debug
|
||||||
--compress=1
|
--compress zip-0
|
||||||
- name: Run jpackage
|
- name: Run jpackage
|
||||||
run: >
|
run: >
|
||||||
${JAVA_HOME}/bin/jpackage
|
${JAVA_HOME}/bin/jpackage
|
||||||
@@ -108,7 +109,7 @@ jobs:
|
|||||||
--dest appdir
|
--dest appdir
|
||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2023 Skymatic GmbH"
|
--copyright "(C) 2016 - 2024 Skymatic GmbH"
|
||||||
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||||
--java-options "--enable-preview"
|
--java-options "--enable-preview"
|
||||||
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
|
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
|
||||||
@@ -249,13 +250,15 @@ jobs:
|
|||||||
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dmg-${{ matrix.output-suffix }}
|
name: dmg-${{ matrix.output-suffix }}
|
||||||
path: Cryptomator-*.dmg
|
path: |
|
||||||
|
Cryptomator-*.dmg
|
||||||
|
Cryptomator-*.asc
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Publish dmg on GitHub Releases
|
- name: Publish dmg on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
days-before-stale: 14
|
days-before-stale: 14
|
||||||
days-before-close: 0
|
days-before-close: 0
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ jobs:
|
|||||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
run: xvfb-run mvn -B clean install jacoco:report -Pcoverage,dependency-check
|
run: xvfb-run mvn -B clean install jacoco:report -Pcoverage
|
||||||
@@ -10,12 +10,22 @@ defaults:
|
|||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
JAVA_DIST: 'zulu'
|
||||||
|
JAVA_VERSION: 21
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-check-precondition:
|
check-preconditions:
|
||||||
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
cache: 'maven'
|
||||||
- id: validate-pom-version
|
- id: validate-pom-version
|
||||||
name: Validate POM version
|
name: Validate POM version
|
||||||
run: |
|
run: |
|
||||||
@@ -34,7 +44,22 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- name: Validate release in org.cryptomator.Cryptomator.metainfo.xml file
|
- name: Validate release in org.cryptomator.Cryptomator.metainfo.xml file
|
||||||
run: |
|
run: |
|
||||||
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\"/>" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
|
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\">" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
|
||||||
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
|
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
- name: Cache NVD DB
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.m2/repository/org/owasp/dependency-check-data/
|
||||||
|
key: dependency-check-${{ github.run_id }}
|
||||||
|
restore-keys: |
|
||||||
|
dependency-check
|
||||||
|
env:
|
||||||
|
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 5
|
||||||
|
- name: Run org.owasp:dependency-check plugin
|
||||||
|
id: dependency-check
|
||||||
|
continue-on-error: true
|
||||||
|
run: mvn -B verify -Pdependency-check -DskipTests
|
||||||
|
env:
|
||||||
|
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
|
||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
days-before-stale: 365
|
days-before-stale: 365
|
||||||
days-before-close: 90
|
days-before-close: 90
|
||||||
|
|||||||
@@ -11,15 +11,16 @@ on:
|
|||||||
isDebug:
|
isDebug:
|
||||||
description: 'Build debug version with console output'
|
description: 'Build debug version with console output'
|
||||||
type: boolean
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'zulu'
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_VERSION: '21.0.1+12'
|
JAVA_VERSION: '21.0.2+13'
|
||||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip'
|
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_windows-x64_bin-jmods.zip'
|
||||||
OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
|
OPENJFX_JMODS_AMD64_HASH: 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a'
|
||||||
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
|
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
|
||||||
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe'
|
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -41,7 +42,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -73,12 +74,13 @@ jobs:
|
|||||||
- name: Set version
|
- name: Set version
|
||||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,win -DskipTests
|
run: mvn -B clean package -Pwin -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
cp target/cryptomator-*.jar target/mods
|
cp target/cryptomator-*.jar target/mods
|
||||||
- name: Run jlink
|
- name: Run jlink
|
||||||
|
#Remark: no compression is applied for improved build compression later (here msi)
|
||||||
run: >
|
run: >
|
||||||
${JAVA_HOME}/bin/jlink
|
${JAVA_HOME}/bin/jlink
|
||||||
--verbose
|
--verbose
|
||||||
@@ -89,7 +91,7 @@ jobs:
|
|||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
--strip-debug
|
--strip-debug
|
||||||
--compress=1
|
--compress zip-0
|
||||||
- name: Change win-console flag if debug is active
|
- name: Change win-console flag if debug is active
|
||||||
if: ${{ inputs.isDebug }}
|
if: ${{ inputs.isDebug }}
|
||||||
run: echo "WIN_CONSOLE_FLAG=--win-console" >> $GITHUB_ENV
|
run: echo "WIN_CONSOLE_FLAG=--win-console" >> $GITHUB_ENV
|
||||||
@@ -105,7 +107,7 @@ jobs:
|
|||||||
--dest appdir
|
--dest appdir
|
||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2023 Skymatic GmbH"
|
--copyright "(C) 2016 - 2024 Skymatic GmbH"
|
||||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
--java-options "--enable-preview"
|
--java-options "--enable-preview"
|
||||||
--java-options "--enable-native-access=org.cryptomator.jfuse.win"
|
--java-options "--enable-native-access=org.cryptomator.jfuse.win"
|
||||||
@@ -169,7 +171,7 @@ jobs:
|
|||||||
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
|
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
|
||||||
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
|
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
|
||||||
- name: Codesign
|
- name: Codesign
|
||||||
uses: skymatic/code-sign-action@v2
|
uses: skymatic/code-sign-action@v3
|
||||||
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 }}
|
||||||
@@ -213,12 +215,12 @@ jobs:
|
|||||||
--dest installer
|
--dest installer
|
||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2023 Skymatic GmbH"
|
--copyright "(C) 2016 - 2024 Skymatic GmbH"
|
||||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}"
|
||||||
--win-menu
|
--win-menu
|
||||||
--win-dir-chooser
|
--win-dir-chooser
|
||||||
--win-shortcut-prompt
|
--win-shortcut-prompt
|
||||||
--win-update-url "https:\\cryptomator.org"
|
--win-update-url "https:\\cryptomator.org\downloads"
|
||||||
--win-menu-group Cryptomator
|
--win-menu-group Cryptomator
|
||||||
--resource-dir dist/win/resources
|
--resource-dir dist/win/resources
|
||||||
--license-file dist/win/resources/license.rtf
|
--license-file dist/win/resources/license.rtf
|
||||||
@@ -227,7 +229,7 @@ jobs:
|
|||||||
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
||||||
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
|
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
|
||||||
- name: Codesign MSI
|
- name: Codesign MSI
|
||||||
uses: skymatic/code-sign-action@v2
|
uses: skymatic/code-sign-action@v3
|
||||||
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 }}
|
||||||
@@ -245,22 +247,13 @@ jobs:
|
|||||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: msi
|
name: msi
|
||||||
path: |
|
path: |
|
||||||
Cryptomator-*.msi
|
Cryptomator-*.msi
|
||||||
Cryptomator-*.asc
|
Cryptomator-*.asc
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Publish .msi on GitHub Releases
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
fail_on_unmatched_files: true
|
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
|
||||||
files: |
|
|
||||||
*.msi
|
|
||||||
*.asc
|
|
||||||
|
|
||||||
build-exe:
|
build-exe:
|
||||||
name: Build .exe installer
|
name: Build .exe installer
|
||||||
@@ -269,13 +262,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Download .msi
|
- name: Download .msi
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: msi
|
name: msi
|
||||||
path: dist/win/bundle/resources
|
path: dist/win/bundle/resources
|
||||||
- name: Strip version info from msi file name
|
- name: Strip version info from msi file name
|
||||||
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
|
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -308,7 +301,7 @@ jobs:
|
|||||||
-out dist/win/bundle/
|
-out dist/win/bundle/
|
||||||
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
-dBundleVendor="Skymatic GmbH"
|
-dBundleVendor="Skymatic GmbH"
|
||||||
-dBundleCopyright="(C) 2016 - 2023 Skymatic GmbH"
|
-dBundleCopyright="(C) 2016 - 2024 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/"
|
||||||
@@ -324,7 +317,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@v2
|
uses: skymatic/code-sign-action@v3
|
||||||
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 }}
|
||||||
@@ -338,7 +331,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@v2
|
uses: skymatic/code-sign-action@v3
|
||||||
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 }}
|
||||||
@@ -356,59 +349,66 @@ jobs:
|
|||||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: exe
|
name: exe
|
||||||
path: |
|
path: |
|
||||||
Cryptomator-*.exe
|
Cryptomator-*.exe
|
||||||
Cryptomator-*.asc
|
Cryptomator-*.asc
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish installers to the github release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build-msi, build-exe]
|
||||||
|
outputs:
|
||||||
|
download-url-msi: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }}
|
||||||
|
download-url-exe: ${{ fromJSON(steps.publish.outputs.assets)[1].browser_download_url }}
|
||||||
|
steps:
|
||||||
|
- name: Download installers
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
merge-multiple: true
|
||||||
- name: Publish .msi on GitHub Releases
|
- name: Publish .msi on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
id: publish
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
|
# do not change ordering of filelist, required for correct job output
|
||||||
files: |
|
files: |
|
||||||
Cryptomator-*.exe
|
*.msi
|
||||||
Cryptomator-*.asc
|
*.exe
|
||||||
|
*.asc
|
||||||
|
|
||||||
allowlist:
|
allowlist-msi:
|
||||||
name: Anti Virus Allowlisting
|
uses: ./.github/workflows/av-whitelist.yml
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
needs: [publish]
|
||||||
|
with:
|
||||||
|
url: ${{ needs.publish.outputs.download-url-msi }}
|
||||||
|
|
||||||
|
allowlist-exe:
|
||||||
|
uses: ./.github/workflows/av-whitelist.yml
|
||||||
|
needs: [publish]
|
||||||
|
with:
|
||||||
|
url: ${{ needs.publish.outputs.download-url-exe }}
|
||||||
|
|
||||||
|
notify-winget:
|
||||||
|
name: Notify for winget-release
|
||||||
|
if: needs.get-version.outputs.versionType == 'stable'
|
||||||
|
needs: [publish, get-version]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-msi, build-exe]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download .msi
|
- name: Slack Notification
|
||||||
uses: actions/download-artifact@v3
|
uses: rtCamp/action-slack-notify@v2
|
||||||
with:
|
env:
|
||||||
name: msi
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
path: msi
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
- name: Download .exe
|
SLACK_ICON: false
|
||||||
uses: actions/download-artifact@v3
|
SLACK_ICON_EMOJI: ':bot:'
|
||||||
with:
|
SLACK_CHANNEL: 'cryptomator-desktop'
|
||||||
name: exe
|
SLACK_TITLE: "MSI of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
|
||||||
path: exe
|
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/winget.yml| release to winget>."
|
||||||
- name: Collect files
|
SLACK_FOOTER: false
|
||||||
run: |
|
MSG_MINIMAL: true
|
||||||
mkdir files
|
|
||||||
cp msi/*.msi files
|
|
||||||
cp exe/*.exe files
|
|
||||||
- name: Upload to Kaspersky
|
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
|
||||||
with:
|
|
||||||
protocol: ftps
|
|
||||||
server: allowlist.kaspersky-labs.com
|
|
||||||
port: 990
|
|
||||||
username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
|
|
||||||
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
|
|
||||||
local-dir: files/
|
|
||||||
- name: Upload to Avast
|
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
|
||||||
with:
|
|
||||||
protocol: ftp
|
|
||||||
server: whitelisting.avast.com
|
|
||||||
port: 21
|
|
||||||
username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
|
|
||||||
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
|
|
||||||
local-dir: files/
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: Publish MSI to winget-pkgs
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Release tag'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
winget:
|
||||||
|
name: Publish winget package
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Sync winget-pkgs fork
|
||||||
|
run: |
|
||||||
|
gh repo sync cryptomator/winget-pkgs -b master --force
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||||
|
- name: Submit package
|
||||||
|
uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
|
with:
|
||||||
|
identifier: Cryptomator.Cryptomator
|
||||||
|
version: ${{ inputs.tag }}
|
||||||
|
release-tag: ${{ inputs.tag }}
|
||||||
|
installers-regex: '\.msi$'
|
||||||
|
token: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
</envs>
|
</envs>
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/Cryptomator-Dev" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
|
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/Cryptomator-Dev" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins" -Dcryptomator.mountPointsDir="@{userhome}/Library/Application Support/Cryptomator-Dev/mnt" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ Cryptomator is provided free of charge as an open-source project despite the hig
|
|||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<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="28"></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>
|
<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>
|
||||||
|
<td><a href="https://ente.io/"><img src="https://cryptomator.org/img/sponsors/ente.svg" alt="Ente" height="58"></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -85,7 +86,7 @@ For more information on the security details visit [cryptomator.org](https://doc
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* JDK 19 (e.g. temurin)
|
* JDK 21 (e.g. temurin, zulu)
|
||||||
* Maven 3
|
* Maven 3
|
||||||
|
|
||||||
### Run Maven
|
### Run Maven
|
||||||
|
|||||||
Vendored
+36
-7
@@ -8,11 +8,14 @@ REVISION_NO=`git rev-list --count HEAD`
|
|||||||
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
|
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
|
||||||
command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; }
|
command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; }
|
||||||
command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
|
command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
|
||||||
|
command -v unzip >/dev/null 2>&1 || { echo >&2 "unzip not found."; exit 1; }
|
||||||
|
|
||||||
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
|
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||||
SEMVER_STR=${VERSION}
|
SEMVER_STR=${VERSION}
|
||||||
MACHINE_TYPE=$(uname -m)
|
MACHINE_TYPE=$(uname -m)
|
||||||
|
|
||||||
|
if [[ ! "${MACHINE_TYPE}" =~ x86_64|aarch64 ]]; then echo "Platform ${MACHINE_TYPE} not supported"; exit 1; fi
|
||||||
|
|
||||||
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
||||||
|
|
||||||
# compile
|
# compile
|
||||||
@@ -20,20 +23,47 @@ mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
|
|||||||
cp ../../../LICENSE.txt ../../../target
|
cp ../../../LICENSE.txt ../../../target
|
||||||
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
||||||
|
|
||||||
|
|
||||||
|
# download javaFX jmods
|
||||||
|
OPENJFX_URL='https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
|
||||||
|
OPENJFX_SHA='7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
|
||||||
|
OPENJFX_URL_aarch64='https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
|
||||||
|
OPENJFX_SHA_aarch64='871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
|
||||||
|
|
||||||
|
if [[ "${MACHINE_TYPE}" = "aarch64" ]]; then
|
||||||
|
OPENJFX_URL="${OPENJFX_URL_aarch64}";
|
||||||
|
OPENJFX_SHA="${OPENJFX_SHA_aarch64}";
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -L ${OPENJFX_URL} -o openjfx-jmods.zip
|
||||||
|
echo "${OPENJFX_SHA} openjfx-jmods.zip" | shasum -a256 --check
|
||||||
|
mkdir -p openjfx-jmods
|
||||||
|
unzip -o -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
||||||
|
JMOD_VERSION=$(jmod describe ./openjfx-jmods/javafx.base.jmod | head -1)
|
||||||
|
JMOD_VERSION=${JMOD_VERSION#*@}
|
||||||
|
JMOD_VERSION=${JMOD_VERSION%%.*}
|
||||||
|
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout -B -f ../../../pom.xml)
|
||||||
|
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
||||||
|
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
||||||
|
if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then
|
||||||
|
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
# add runtime
|
# add runtime
|
||||||
${JAVA_HOME}/bin/jlink \
|
${JAVA_HOME}/bin/jlink \
|
||||||
--verbose \
|
--verbose \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JAVA_HOME}/jmods" \
|
--module-path "${JAVA_HOME}/jmods:openjfx-jmods" \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
--strip-debug \
|
--strip-debug \
|
||||||
--compress=1
|
--compress zip-0
|
||||||
|
|
||||||
# create app dir
|
# create app dir
|
||||||
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 \
|
||||||
@@ -46,7 +76,7 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--vendor "Skymatic GmbH" \
|
--vendor "Skymatic GmbH" \
|
||||||
--java-options "--enable-preview" \
|
--java-options "--enable-preview" \
|
||||||
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
|
||||||
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
|
--copyright "(C) 2016 - 2024 Skymatic GmbH" \
|
||||||
--java-options "-Xss5m" \
|
--java-options "-Xss5m" \
|
||||||
--java-options "-Xmx256m" \
|
--java-options "-Xmx256m" \
|
||||||
--app-version "${VERSION}.${REVISION_NO}" \
|
--app-version "${VERSION}.${REVISION_NO}" \
|
||||||
@@ -61,7 +91,6 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--java-options "-Dcryptomator.showTrayIcon=true" \
|
--java-options "-Dcryptomator.showTrayIcon=true" \
|
||||||
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \
|
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \
|
||||||
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \
|
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \
|
||||||
--add-launcher cryptomator-gtk2=launcher-gtk2.properties \
|
|
||||||
--resource-dir ../resources
|
--resource-dir ../resources
|
||||||
|
|
||||||
# transform AppDir
|
# transform AppDir
|
||||||
@@ -97,5 +126,5 @@ chmod +x /tmp/appimagetool.AppImage
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage"
|
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage"
|
||||||
echo ""
|
echo ""
|
||||||
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
|
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir runtime squashfs-root openjfx-jmods; rm /tmp/appimagetool.AppImage openjfx-jmods.zip"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
+110
-40
@@ -5,26 +5,27 @@
|
|||||||
<metadata_license>FSFAP</metadata_license>
|
<metadata_license>FSFAP</metadata_license>
|
||||||
<project_license>GPL-3.0-or-later</project_license>
|
<project_license>GPL-3.0-or-later</project_license>
|
||||||
<name>Cryptomator</name>
|
<name>Cryptomator</name>
|
||||||
<summary>Multi-platform client-side encryption tool optimized for cloud storages</summary>
|
<summary>Encryption made easy and optimized for the cloud</summary>
|
||||||
|
|
||||||
<description>
|
<description>
|
||||||
<p>
|
<p>
|
||||||
Cryptomator provides transparent, client-side encryption for your cloud. Protect your documents from unauthorized
|
Cryptomator provides easy-to-use, transparent, client-side encryption for your cloud.
|
||||||
access. Cryptomator is free and open source software, so you can rest assured there are no backdoors.
|
It protects your documents from unauthorized access and prying eyes, while you will still be able to view and edit your documents locally.
|
||||||
|
By not requiring any registration or account and performing all encryption locally, it gives you back control over your data and ensures your privacy.
|
||||||
|
Cryptomator is offered for all major platforms (including Android and iOS).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Cryptomator encrypts file contents and names using AES. Your passphrase is protected against bruteforcing attempts
|
Cryptomator encrypts file contents and names using the widespread industry standard AES.
|
||||||
using scrypt. Directory structures get obfuscated. The only thing which cannot be encrypted without breaking your
|
Your passphrase is protected against brute forcing attempts using scrypt.
|
||||||
cloud synchronization is the modification date of your files.
|
Additionally, directory structures get obfuscated.
|
||||||
|
For more info about the Cryptomator encryption scheme, check out the online documentation.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Cryptomator is a free and open source software licensed under the GPLv3. This allows anyone to check our code. It
|
Cryptomator is a free and open-source software licensed under the GPLv3.
|
||||||
is impossible to introduce backdoors for third parties. Also we cannot hide vulnerabilities. And the best thing
|
This allows anyone to check our code.
|
||||||
is: There is no need to trust us, as you can control us!
|
Thus, it is impossible to introduce backdoors for third parties or to hide vulnerabilities, so you do not need to trust Cryptomator.
|
||||||
</p>
|
Also, vendor lock-ins are impossible.
|
||||||
<p>
|
Even if we decided to stop development: The source code is already cloned by hundreds of other developers and development can be picked up by others.
|
||||||
Vendor lock-ins are impossible. Even if we decided to stop development: The source code is already cloned by
|
|
||||||
hundreds of other developers. As you don't need an account, you will never stand in front of locked doors.
|
|
||||||
</p>
|
</p>
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
</provides>
|
</provides>
|
||||||
|
|
||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot>
|
<screenshot type="default">
|
||||||
<caption>Light theme</caption>
|
<caption>Light theme</caption>
|
||||||
<image>https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png</image>
|
<image>https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
@@ -56,39 +57,108 @@
|
|||||||
<url type="bugtracker">https://github.com/cryptomator/cryptomator/issues/</url>
|
<url type="bugtracker">https://github.com/cryptomator/cryptomator/issues/</url>
|
||||||
<url type="donation">https://cryptomator.org/donate</url>
|
<url type="donation">https://cryptomator.org/donate</url>
|
||||||
<url type="faq">https://community.cryptomator.org/c/kb/faq</url>
|
<url type="faq">https://community.cryptomator.org/c/kb/faq</url>
|
||||||
<url type="help">https://community.cryptomator.org/</url>
|
<url type="help">https://docs.cryptomator.org/</url>
|
||||||
<url type="translate">https://translate.cryptomator.org</url>
|
<url type="translate">https://translate.cryptomator.org</url>
|
||||||
|
|
||||||
<developer_name>Skymatic GmbH</developer_name>
|
<developer id="de.skymatic">
|
||||||
|
<name>Skymatic GmbH</name>
|
||||||
|
</developer>
|
||||||
|
|
||||||
|
|
||||||
<content_rating type="oars-1.1">
|
<content_rating type="oars-1.1">
|
||||||
<content_attribute id="social-info">mild</content_attribute> <!-- update checker connects to https://api.cryptomator.org/updates/latestVersion.json -->
|
<content_attribute id="social-info">mild</content_attribute> <!-- update checker connects to https://api.cryptomator.org/updates/latestVersion.json -->
|
||||||
</content_rating>
|
</content_rating>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
<release date="2023-09-20" version="1.10.1"/>
|
<release date="2024-02-27" version="1.12.3">
|
||||||
<release date="2023-09-11" version="1.10.0"/>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.3</url>
|
||||||
<release date="2023-08-11" version="1.9.4"/>
|
</release>
|
||||||
<release date="2023-08-07" version="1.9.3"/>
|
<release date="2024-02-09" version="1.12.2">
|
||||||
<release date="2023-07-24" version="1.9.2"/>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.2</url>
|
||||||
<release date="2023-06-07" version="1.9.1"/>
|
</release>
|
||||||
<release date="2023-05-30" version="1.9.0"/>
|
<release date="2024-02-07" version="1.12.1">
|
||||||
<release date="2023-04-25" version="1.8.0"/>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.1</url>
|
||||||
<release date="2023-04-07" version="1.7.5"/>
|
</release>
|
||||||
<release date="2023-04-05" version="1.7.4"/>
|
<release date="2024-02-06" version="1.12.0">
|
||||||
<release date="2023-03-15" version="1.7.3"/>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.0</url>
|
||||||
<release date="2023-03-07" version="1.7.2"/>
|
</release>
|
||||||
<release date="2023-03-03" version="1.7.1"/>
|
<release date="2023-12-05" version="1.11.1">
|
||||||
<release date="2023-03-01" version="1.7.0"/>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.11.1</url>
|
||||||
<release date="2022-12-14" version="1.6.17"/>
|
</release>
|
||||||
<release date="2022-12-06" version="1.6.16"/>
|
<release date="2023-11-08" version="1.11.0">
|
||||||
<release date="2022-10-06" version="1.6.15"/>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.11.0</url>
|
||||||
<release date="2022-08-31" version="1.6.14"/>
|
</release>
|
||||||
<release date="2022-07-27" version="1.6.12"/>
|
<release date="2023-09-20" version="1.10.1">
|
||||||
<release date="2022-07-26" version="1.6.11"/>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.10.1</url>
|
||||||
<release date="2022-05-03" version="1.6.10"/>
|
</release>
|
||||||
<release date="2022-04-27" version="1.6.9"/>
|
<release date="2023-09-11" version="1.10.0">
|
||||||
<release date="2022-03-30" version="1.6.8"/>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.10.0</url>
|
||||||
<release date="2021-12-16" version="1.6.5"/>
|
</release>
|
||||||
|
<release date="2023-08-11" version="1.9.4">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.4</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-08-07" version="1.9.3">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.3</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-07-24" version="1.9.2">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.2</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-06-07" version="1.9.1">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.1</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-05-30" version="1.9.0">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.0</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-04-25" version="1.8.0">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.8.0</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-04-07" version="1.7.5">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.5</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-04-05" version="1.7.4">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.4</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-03-15" version="1.7.3">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.3</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-03-07" version="1.7.2">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.2</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-03-03" version="1.7.1">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.1</url>
|
||||||
|
</release>
|
||||||
|
<release date="2023-03-01" version="1.7.0">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.0</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-12-14" version="1.6.17">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.17</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-12-06" version="1.6.16">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.16</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-10-06" version="1.6.15">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.15</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-08-31" version="1.6.14">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.14</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-07-27" version="1.6.12">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.12</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-07-26" version="1.6.11">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.11</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-05-03" version="1.6.10">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.10</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-04-27" version="1.6.9">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.9</url>
|
||||||
|
</release>
|
||||||
|
<release date="2022-03-30" version="1.6.8">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.8</url>
|
||||||
|
</release>
|
||||||
|
<release date="2021-12-16" version="1.6.5">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.5</url>
|
||||||
|
</release>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
Vendored
+1
-1
@@ -2,7 +2,7 @@ Source: cryptomator
|
|||||||
Maintainer: Cryptobot <releases@cryptomator.org>
|
Maintainer: Cryptobot <releases@cryptomator.org>
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-21 (= 21.0.1+12-0ppa1), libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
|
Build-Depends: debhelper (>=10), coffeelibs-jdk-21 (>= 21.0.2+12-0ppa1), 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
|
||||||
|
|||||||
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-2023 Skymatic GmbH
|
Copyright: 2016-2024 Skymatic GmbH
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: debian/org.cryptomator.Cryptomator.appdata.xml
|
Files: debian/org.cryptomator.Cryptomator.appdata.xml
|
||||||
Copyright: 2016-2023 Skymatic GmbH
|
Copyright: 2016-2024 Skymatic GmbH
|
||||||
License: FSFAP
|
License: FSFAP
|
||||||
|
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|||||||
Vendored
+3
-2
@@ -24,6 +24,7 @@ override_dh_auto_clean:
|
|||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
mkdir resources
|
mkdir resources
|
||||||
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
||||||
|
# Remark: no compression is applied for improved build compression later (here deb)
|
||||||
$(JAVA_HOME)/bin/jlink \
|
$(JAVA_HOME)/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JMODS_PATH}" \
|
--module-path "${JMODS_PATH}" \
|
||||||
@@ -32,7 +33,7 @@ override_dh_auto_build:
|
|||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
--strip-debug \
|
--strip-debug \
|
||||||
--compress=2
|
--compress zip-0
|
||||||
$(JAVA_HOME)/bin/jpackage \
|
$(JAVA_HOME)/bin/jpackage \
|
||||||
--type app-image \
|
--type app-image \
|
||||||
--runtime-image runtime \
|
--runtime-image runtime \
|
||||||
@@ -44,7 +45,7 @@ override_dh_auto_build:
|
|||||||
--vendor "Skymatic GmbH" \
|
--vendor "Skymatic GmbH" \
|
||||||
--java-options "--enable-preview" \
|
--java-options "--enable-preview" \
|
||||||
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
|
||||||
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
|
--copyright "(C) 2016 - 2024 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\"" \
|
||||||
|
|||||||
Vendored
-14
@@ -1,14 +0,0 @@
|
|||||||
java-options=-Xss5m \
|
|
||||||
-Xmx256m \
|
|
||||||
--enable-preview \
|
|
||||||
--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64 \
|
|
||||||
-Dfile.encoding=\"utf-8\" \
|
|
||||||
-Dcryptomator.appVersion=\"${SEMVER_STR}\" \
|
|
||||||
-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\" \
|
|
||||||
-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\" \
|
|
||||||
-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\" \
|
|
||||||
-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\" \
|
|
||||||
-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\" \
|
|
||||||
-Dcryptomator.showTrayIcon=false \
|
|
||||||
-Dcryptomator.buildNumber=\"appimage-${REVISION_NUM}\" \
|
|
||||||
-Djdk.gtk.version=2
|
|
||||||
Vendored
+3
-3
@@ -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 - 2023"
|
COPYRIGHT_YEARS="2016 - 2024"
|
||||||
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"
|
||||||
@@ -35,7 +35,7 @@ if [ "$(machine)" = "arm64e" ]; then
|
|||||||
else
|
else
|
||||||
ARCH="x64"
|
ARCH="x64"
|
||||||
fi
|
fi
|
||||||
OPENJFX_JMODS="https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-${ARCH}_bin-jmods.zip"
|
OPENJFX_JMODS="https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-${ARCH}_bin-jmods.zip"
|
||||||
|
|
||||||
# check preconditions
|
# check preconditions
|
||||||
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
|
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
|
||||||
@@ -76,7 +76,7 @@ ${JAVA_HOME}/bin/jlink \
|
|||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
--strip-debug \
|
--strip-debug \
|
||||||
--compress=1
|
--compress zip-0
|
||||||
|
|
||||||
# create app dir
|
# create app dir
|
||||||
${JAVA_HOME}/bin/jpackage \
|
${JAVA_HOME}/bin/jpackage \
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
\f1\b0 \
|
\f1\b0 \
|
||||||
\
|
\
|
||||||
|
|
||||||
\f0\b \'a9 2016 \'96 2023 Skymatic GmbH
|
\f0\b \'a9 2016 \'96 2024 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
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>10.13.0</string>
|
<string>11</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>English</string>
|
<string>English</string>
|
||||||
<key>CFBundleAllowMixedLocalizations</key>
|
<key>CFBundleAllowMixedLocalizations</key>
|
||||||
|
|||||||
Vendored
+5
-5
@@ -51,9 +51,9 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
## download jfx jmods
|
## download jfx jmods
|
||||||
$jmodsVersion='20.0.2'
|
$jmodsVersion='21.0.1'
|
||||||
$jmodsUrl = "https://download2.gluonhq.com/openjfx/${jmodsVersion}/openjfx-${jmodsVersion}_windows-x64_bin-jmods.zip"
|
$jmodsUrl = "https://download2.gluonhq.com/openjfx/${jmodsVersion}/openjfx-${jmodsVersion}_windows-x64_bin-jmods.zip"
|
||||||
$jfxJmodsChecksum = '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
|
$jfxJmodsChecksum = 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a'
|
||||||
$jfxJmodsZip = '.\resources\jfxJmods.zip'
|
$jfxJmodsZip = '.\resources\jfxJmods.zip'
|
||||||
if( !(Test-Path -Path $jfxJmodsZip) ) {
|
if( !(Test-Path -Path $jfxJmodsZip) ) {
|
||||||
Write-Output "Downloading ${jmodsUrl}..."
|
Write-Output "Downloading ${jmodsUrl}..."
|
||||||
@@ -69,7 +69,7 @@ Expand-Archive -Path $jfxJmodsZip -Force -DestinationPath ".\resources\"
|
|||||||
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods"
|
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods"
|
||||||
Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
|
Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
|
||||||
|
|
||||||
|
## create custom runtime
|
||||||
& "$Env:JAVA_HOME\bin\jlink" `
|
& "$Env:JAVA_HOME\bin\jlink" `
|
||||||
--verbose `
|
--verbose `
|
||||||
--output runtime `
|
--output runtime `
|
||||||
@@ -79,7 +79,7 @@ Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\ja
|
|||||||
--no-header-files `
|
--no-header-files `
|
||||||
--no-man-pages `
|
--no-man-pages `
|
||||||
--strip-debug `
|
--strip-debug `
|
||||||
--compress=1
|
--compress "zip-0" #do not compress to have improved msi compression
|
||||||
|
|
||||||
$appPath = ".\$AppName"
|
$appPath = ".\$AppName"
|
||||||
if ($clean -and (Test-Path -Path $appPath)) {
|
if ($clean -and (Test-Path -Path $appPath)) {
|
||||||
@@ -181,7 +181,7 @@ Write-Output "Downloading ${winfspMsiUrl}..."
|
|||||||
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
|
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
|
||||||
|
|
||||||
# download legacy-winfsp uninstaller
|
# download legacy-winfsp uninstaller
|
||||||
$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe'
|
$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
|
||||||
Write-Output "Downloading ${winfspUninstaller}..."
|
Write-Output "Downloading ${winfspUninstaller}..."
|
||||||
Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default
|
Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default
|
||||||
|
|
||||||
|
|||||||
Vendored
+3
@@ -31,6 +31,7 @@
|
|||||||
</BootstrapperApplicationRef>
|
</BootstrapperApplicationRef>
|
||||||
|
|
||||||
<Chain>
|
<Chain>
|
||||||
|
<!-- WixBundleActions are defined in the BOOTSTRAPPER_ACTION enum, see https://github.com/wixtoolset/wix3/blob/473562c0c3904216f2b6963c0ad244a8cc41e9ee/src/burn/inc/IBootstrapperEngine.h#L15 -->
|
||||||
<ExePackage Cache="yes" PerMachine="yes" Permanent="no"
|
<ExePackage Cache="yes" PerMachine="yes" Permanent="no"
|
||||||
SourceFile="resources\winfsp-uninstaller.exe"
|
SourceFile="resources\winfsp-uninstaller.exe"
|
||||||
DisplayName="Removing outdated WinFsp Driver"
|
DisplayName="Removing outdated WinFsp Driver"
|
||||||
@@ -51,11 +52,13 @@ Do you want to continue?"" RepairArgument="-q" UninstallArgument="-s" />
|
|||||||
</ExePackage>
|
</ExePackage>
|
||||||
<!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/msipackage.html-->
|
<!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/msipackage.html-->
|
||||||
<MsiPackage
|
<MsiPackage
|
||||||
|
Cache="yes"
|
||||||
SourceFile="resources\Cryptomator.msi"
|
SourceFile="resources\Cryptomator.msi"
|
||||||
CacheId="cryptomator-bundle-cryptomator"
|
CacheId="cryptomator-bundle-cryptomator"
|
||||||
DisplayInternalUI="no"
|
DisplayInternalUI="no"
|
||||||
Visible="no"/>
|
Visible="no"/>
|
||||||
<MsiPackage
|
<MsiPackage
|
||||||
|
Cache="yes"
|
||||||
SourceFile="resources\winfsp.msi"
|
SourceFile="resources\winfsp.msi"
|
||||||
CacheId="cryptomator-bundle-winfsp"
|
CacheId="cryptomator-bundle-winfsp"
|
||||||
Visible="yes"
|
Visible="yes"
|
||||||
|
|||||||
+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 2023 Skymatic GmbH \b0\par
|
\b\'a9 2016 \'96 2024 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
|
||||||
|
|||||||
+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 2023 Skymatic GmbH \b0\par
|
\b\'a9 2016 \'96 2024 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
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptomator</artifactId>
|
<artifactId>cryptomator</artifactId>
|
||||||
<version>1.11.0</version>
|
<version>1.13.0-SNAPSHOT</version>
|
||||||
<name>Cryptomator Desktop App</name>
|
<name>Cryptomator Desktop App</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
@@ -33,44 +33,44 @@
|
|||||||
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
|
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
|
||||||
|
|
||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptofs.version>2.6.7</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>2.6.9</cryptomator.cryptofs.version>
|
||||||
<cryptomator.integrations.version>1.3.0</cryptomator.integrations.version>
|
<cryptomator.integrations.version>1.3.1</cryptomator.integrations.version>
|
||||||
<cryptomator.integrations.win.version>1.2.4</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.2.5</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>1.2.2</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.2.3</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>1.4.0-beta2</cryptomator.integrations.linux.version>
|
<cryptomator.integrations.linux.version>1.4.4</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>4.0.0-beta4</cryptomator.fuse.version>
|
<cryptomator.fuse.version>4.0.0</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
|
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>2.0.5</cryptomator.webdav.version>
|
<cryptomator.webdav.version>2.0.6</cryptomator.webdav.version>
|
||||||
|
|
||||||
<!-- 3rd party dependencies -->
|
<!-- 3rd party dependencies -->
|
||||||
<commons-lang3.version>3.13.0</commons-lang3.version>
|
<commons-lang3.version>3.14.0</commons-lang3.version>
|
||||||
<dagger.version>2.48.1</dagger.version>
|
<dagger.version>2.50</dagger.version>
|
||||||
<easybind.version>2.2</easybind.version>
|
<easybind.version>2.2</easybind.version>
|
||||||
<guava.version>32.1.3-jre</guava.version>
|
<guava.version>33.0.0-jre</guava.version>
|
||||||
<jackson.version>2.15.3</jackson.version>
|
<jackson.version>2.16.1</jackson.version>
|
||||||
<javafx.version>20.0.2</javafx.version>
|
<javafx.version>21.0.1</javafx.version>
|
||||||
<jwt.version>4.4.0</jwt.version>
|
<jwt.version>4.4.0</jwt.version>
|
||||||
<nimbus-jose.version>9.37</nimbus-jose.version>
|
<nimbus-jose.version>9.37.3</nimbus-jose.version>
|
||||||
<logback.version>1.4.11</logback.version>
|
<logback.version>1.5.0</logback.version>
|
||||||
<slf4j.version>2.0.9</slf4j.version>
|
<slf4j.version>2.0.12</slf4j.version>
|
||||||
<tinyoauth2.version>0.8.0</tinyoauth2.version>
|
<tinyoauth2.version>0.8.0</tinyoauth2.version>
|
||||||
<zxcvbn.version>1.8.2</zxcvbn.version>
|
<zxcvbn.version>1.8.2</zxcvbn.version>
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
<junit.jupiter.version>5.10.0</junit.jupiter.version>
|
<junit.jupiter.version>5.10.2</junit.jupiter.version>
|
||||||
<mockito.version>5.6.0</mockito.version>
|
<mockito.version>5.10.0</mockito.version>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
|
|
||||||
<!-- build-time dependencies -->
|
<!-- build-time dependencies -->
|
||||||
<jetbrains.annotations.version>24.0.1</jetbrains.annotations.version>
|
<jetbrains.annotations.version>24.1.0</jetbrains.annotations.version>
|
||||||
<dependency-check.version>8.4.0</dependency-check.version>
|
<dependency-check.version>9.0.9</dependency-check.version>
|
||||||
<jacoco.version>0.8.11</jacoco.version>
|
<jacoco.version>0.8.11</jacoco.version>
|
||||||
<license-generator.version>2.2.0</license-generator.version>
|
<license-generator.version>2.4.0</license-generator.version>
|
||||||
<junit-tree-reporter.version>1.2.1</junit-tree-reporter.version>
|
<junit-tree-reporter.version>1.2.1</junit-tree-reporter.version>
|
||||||
<mvn-compiler.version>3.11.0</mvn-compiler.version>
|
<mvn-compiler.version>3.12.1</mvn-compiler.version>
|
||||||
<mvn-resources.version>3.3.1</mvn-resources.version>
|
<mvn-resources.version>3.3.1</mvn-resources.version>
|
||||||
<mvn-dependency.version>3.6.0</mvn-dependency.version>
|
<mvn-dependency.version>3.6.1</mvn-dependency.version>
|
||||||
<mvn-surefire.version>3.1.2</mvn-surefire.version>
|
<mvn-surefire.version>3.2.5</mvn-surefire.version>
|
||||||
<mvn-jar.version>3.3.0</mvn-jar.version>
|
<mvn-jar.version>3.3.0</mvn-jar.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -460,17 +460,19 @@
|
|||||||
<groupId>org.owasp</groupId>
|
<groupId>org.owasp</groupId>
|
||||||
<artifactId>dependency-check-maven</artifactId>
|
<artifactId>dependency-check-maven</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<cveValidForHours>24</cveValidForHours>
|
<nvdValidForHours>24</nvdValidForHours>
|
||||||
<failBuildOnCVSS>0</failBuildOnCVSS>
|
<failBuildOnCVSS>0</failBuildOnCVSS>
|
||||||
<skipTestScope>true</skipTestScope>
|
<skipTestScope>true</skipTestScope>
|
||||||
<detail>true</detail>
|
<detail>true</detail>
|
||||||
<suppressionFile>suppression.xml</suppressionFile>
|
<suppressionFile>suppression.xml</suppressionFile>
|
||||||
|
<nvdApiKey>${env.NVD_API_KEY}</nvdApiKey>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>check</goal>
|
<goal>check</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<phase>validate</phase>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@@ -5,10 +5,8 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.keychain.KeychainModule;
|
import org.cryptomator.common.keychain.KeychainModule;
|
||||||
import org.cryptomator.common.mount.MountModule;
|
import org.cryptomator.common.mount.MountModule;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
@@ -22,8 +20,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -136,13 +132,4 @@ public abstract class CommonsModule {
|
|||||||
LOG.error("Uncaught exception in " + thread.getName(), throwable);
|
LOG.error("Uncaught exception in " + thread.getName(), throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
static ObservableValue<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
|
||||||
return settings.port.map(port -> {
|
|
||||||
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
|
||||||
return InetSocketAddress.createUnresolved(host, settings.port.intValue());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package org.cryptomator.common.mount;
|
|
||||||
|
|
||||||
import org.cryptomator.integrations.mount.MountService;
|
|
||||||
|
|
||||||
public record ActualMountService(MountService service, boolean isDesired) {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.mount.MountFailedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown by {@link Mounter} to indicate that the selected mount service can not be used
|
||||||
|
* due to incompatibilities with a different mount service that is already in use.
|
||||||
|
*/
|
||||||
|
public class ConflictingMountServiceException extends MountFailedException {
|
||||||
|
|
||||||
|
public ConflictingMountServiceException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,21 +4,18 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import org.cryptomator.common.ObservableUtil;
|
import org.cryptomator.common.ObservableUtil;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.integrations.mount.Mount;
|
|
||||||
import org.cryptomator.integrations.mount.MountService;
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class MountModule {
|
public class MountModule {
|
||||||
|
|
||||||
private static final AtomicReference<MountService> formerSelectedMountService = new AtomicReference<>(null);
|
|
||||||
private static final List<String> problematicFuseMountServices = List.of("org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", "org.cryptomator.frontend.fuse.mount.FuseTMountProvider");
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static List<MountService> provideSupportedMountServices() {
|
static List<MountService> provideSupportedMountServices() {
|
||||||
@@ -27,46 +24,18 @@ public class MountModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Named("FUPFMS")
|
static ObservableValue<MountService> provideDefaultMountService(List<MountService> mountProviders, Settings settings) {
|
||||||
static AtomicReference<MountService> provideFirstUsedProblematicFuseMountService() {
|
var fallbackProvider = mountProviders.stream().findFirst().get(); //there should always be a mount provider, at least webDAV
|
||||||
return new AtomicReference<>(null);
|
return ObservableUtil.mapWithDefault(settings.mountService, //
|
||||||
|
serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), //
|
||||||
|
fallbackProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls, @Named("FUPFMS") AtomicReference<MountService> fupfms) {
|
@Named("usedMountServices")
|
||||||
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
|
static Set<MountService> provideSetOfUsedMountServices() {
|
||||||
|
return ConcurrentHashMap.newKeySet();
|
||||||
var observableMountService = ObservableUtil.mapWithDefault(settings.mountService, //
|
|
||||||
desiredServiceImpl -> { //
|
|
||||||
var serviceFromSettings = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); //
|
|
||||||
var targetedService = serviceFromSettings.orElse(fallbackProvider);
|
|
||||||
return applyWorkaroundForProblematicFuse(targetedService, serviceFromSettings.isPresent(), fupfms);
|
|
||||||
}, //
|
|
||||||
() -> { //
|
|
||||||
return applyWorkaroundForProblematicFuse(fallbackProvider, true, fupfms);
|
|
||||||
});
|
|
||||||
return observableMountService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//see https://github.com/cryptomator/cryptomator/issues/2786
|
}
|
||||||
private synchronized static ActualMountService applyWorkaroundForProblematicFuse(MountService targetedService, boolean isDesired, AtomicReference<MountService> firstUsedProblematicFuseMountService) {
|
|
||||||
//set the first used problematic fuse service if applicable
|
|
||||||
var targetIsProblematicFuse = isProblematicFuseService(targetedService);
|
|
||||||
if (targetIsProblematicFuse && firstUsedProblematicFuseMountService.get() == null) {
|
|
||||||
firstUsedProblematicFuseMountService.set(targetedService);
|
|
||||||
}
|
|
||||||
|
|
||||||
//do not use the targeted mount service and fallback to former one, if the service is problematic _and_ not the first problematic one used.
|
|
||||||
if (targetIsProblematicFuse && !firstUsedProblematicFuseMountService.get().equals(targetedService)) {
|
|
||||||
return new ActualMountService(formerSelectedMountService.get(), false);
|
|
||||||
} else {
|
|
||||||
formerSelectedMountService.set(targetedService);
|
|
||||||
return new ActualMountService(targetedService, isDesired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isProblematicFuseService(MountService service) {
|
|
||||||
return problematicFuseMountServices.contains(service.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,13 +7,19 @@ import org.cryptomator.integrations.mount.Mount;
|
|||||||
import org.cryptomator.integrations.mount.MountBuilder;
|
import org.cryptomator.integrations.mount.MountBuilder;
|
||||||
import org.cryptomator.integrations.mount.MountFailedException;
|
import org.cryptomator.integrations.mount.MountFailedException;
|
||||||
import org.cryptomator.integrations.mount.MountService;
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
|
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_EXISTING_DIR;
|
||||||
@@ -24,24 +30,41 @@ import static org.cryptomator.integrations.mount.MountCapability.UNMOUNT_FORCED;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class Mounter {
|
public class Mounter {
|
||||||
|
|
||||||
private final Settings settings;
|
private static final Logger LOG = LoggerFactory.getLogger(Mounter.class);
|
||||||
|
|
||||||
|
// mount providers (key) can not be used if any of the conflicting mount providers (values) are already in use
|
||||||
|
private static final Map<String, Set<String>> CONFLICTING_MOUNT_SERVICES = Map.of(
|
||||||
|
"org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", Set.of("org.cryptomator.frontend.fuse.mount.FuseTMountProvider"),
|
||||||
|
"org.cryptomator.frontend.fuse.mount.FuseTMountProvider", Set.of("org.cryptomator.frontend.fuse.mount.MacFuseMountProvider")
|
||||||
|
);
|
||||||
|
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
|
private final Settings settings;
|
||||||
private final WindowsDriveLetters driveLetters;
|
private final WindowsDriveLetters driveLetters;
|
||||||
private final ObservableValue<ActualMountService> mountServiceObservable;
|
private final List<MountService> mountProviders;
|
||||||
|
private final Set<MountService> usedMountServices;
|
||||||
|
private final ObservableValue<MountService> defaultMountService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Mounter(Settings settings, Environment env, WindowsDriveLetters driveLetters, ObservableValue<ActualMountService> mountServiceObservable) {
|
public Mounter(Environment env, //
|
||||||
this.settings = settings;
|
Settings settings, //
|
||||||
|
WindowsDriveLetters driveLetters, //
|
||||||
|
List<MountService> mountProviders, //
|
||||||
|
@Named("usedMountServices") Set<MountService> usedMountServices, //
|
||||||
|
ObservableValue<MountService> defaultMountService) {
|
||||||
this.env = env;
|
this.env = env;
|
||||||
|
this.settings = settings;
|
||||||
this.driveLetters = driveLetters;
|
this.driveLetters = driveLetters;
|
||||||
this.mountServiceObservable = mountServiceObservable;
|
this.mountProviders = mountProviders;
|
||||||
|
this.usedMountServices = usedMountServices;
|
||||||
|
this.defaultMountService = defaultMountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SettledMounter {
|
private class SettledMounter {
|
||||||
|
|
||||||
private MountService service;
|
private final MountService service;
|
||||||
private MountBuilder builder;
|
private final MountBuilder builder;
|
||||||
private VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
|
|
||||||
public SettledMounter(MountService service, MountBuilder builder, VaultSettings vaultSettings) {
|
public SettledMounter(MountService service, MountBuilder builder, VaultSettings vaultSettings) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
@@ -53,8 +76,13 @@ public class Mounter {
|
|||||||
for (var capability : service.capabilities()) {
|
for (var capability : service.capabilities()) {
|
||||||
switch (capability) {
|
switch (capability) {
|
||||||
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
|
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
|
||||||
case LOOPBACK_PORT ->
|
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)
|
if (vaultSettings.mountService.getValue() == null) {
|
||||||
|
builder.setLoopbackPort(settings.port.get());
|
||||||
|
} else {
|
||||||
|
builder.setLoopbackPort(vaultSettings.port.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
|
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
|
||||||
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get());
|
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get());
|
||||||
case MOUNT_FLAGS -> {
|
case MOUNT_FLAGS -> {
|
||||||
@@ -103,9 +131,7 @@ public class Mounter {
|
|||||||
}
|
}
|
||||||
} else if (canMountToParent && !canMountToDir) {
|
} else if (canMountToParent && !canMountToDir) {
|
||||||
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
|
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
|
||||||
cleanup = () -> {
|
cleanup = () -> MountWithinParentUtil.cleanup(userChosenMountPoint);
|
||||||
MountWithinParentUtil.cleanup(userChosenMountPoint);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
builder.setMountpoint(userChosenMountPoint);
|
builder.setMountpoint(userChosenMountPoint);
|
||||||
@@ -131,13 +157,26 @@ public class Mounter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MountHandle mount(VaultSettings vaultSettings, Path cryptoFsRoot) throws IOException, MountFailedException {
|
public MountHandle mount(VaultSettings vaultSettings, Path cryptoFsRoot) throws IOException, MountFailedException {
|
||||||
var mountService = this.mountServiceObservable.getValue().service();
|
var mountService = mountProviders.stream().filter(s -> s.getClass().getName().equals(vaultSettings.mountService.getValue())).findFirst().orElse(defaultMountService.getValue());
|
||||||
|
|
||||||
|
if (isConflictingMountService(mountService)) {
|
||||||
|
var msg = STR."\{mountService.getClass()} unavailable due to conflict with either of \{CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName())}";
|
||||||
|
throw new ConflictingMountServiceException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
usedMountServices.add(mountService);
|
||||||
|
|
||||||
var builder = mountService.forFileSystem(cryptoFsRoot);
|
var builder = mountService.forFileSystem(cryptoFsRoot);
|
||||||
var internal = new SettledMounter(mountService, builder, vaultSettings);
|
var internal = new SettledMounter(mountService, builder, vaultSettings); // FIXME: no need for an inner class
|
||||||
var cleanup = internal.prepare();
|
var cleanup = internal.prepare();
|
||||||
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);
|
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isConflictingMountService(MountService service) {
|
||||||
|
var conflictingServices = CONFLICTING_MOUNT_SERVICES.getOrDefault(service.getClass().getName(), Set.of());
|
||||||
|
return usedMountServices.stream().map(MountService::getClass).map(Class::getName).anyMatch(conflictingServices::contains);
|
||||||
|
}
|
||||||
|
|
||||||
public record MountHandle(Mount mountObj, boolean supportsUnmountForced, Runnable specialCleanup) {
|
public record MountHandle(Mount mountObj, boolean supportsUnmountForced, Runnable specialCleanup) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ package org.cryptomator.common.settings;
|
|||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
@@ -40,6 +39,7 @@ public class VaultSettings {
|
|||||||
static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
||||||
static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
|
static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
|
||||||
static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
|
static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
|
||||||
|
static final int DEFAULT_PORT = 42427;
|
||||||
|
|
||||||
private static final Random RNG = new Random();
|
private static final Random RNG = new Random();
|
||||||
|
|
||||||
@@ -56,6 +56,8 @@ public class VaultSettings {
|
|||||||
public final IntegerProperty autoLockIdleSeconds;
|
public final IntegerProperty autoLockIdleSeconds;
|
||||||
public final ObjectProperty<Path> mountPoint;
|
public final ObjectProperty<Path> mountPoint;
|
||||||
public final StringExpression mountName;
|
public final StringExpression mountName;
|
||||||
|
public final StringProperty mountService;
|
||||||
|
public final IntegerProperty port;
|
||||||
|
|
||||||
VaultSettings(VaultSettingsJson json) {
|
VaultSettings(VaultSettingsJson json) {
|
||||||
this.id = json.id;
|
this.id = json.id;
|
||||||
@@ -70,6 +72,8 @@ public class VaultSettings {
|
|||||||
this.autoLockWhenIdle = new SimpleBooleanProperty(this, "autoLockWhenIdle", json.autoLockWhenIdle);
|
this.autoLockWhenIdle = new SimpleBooleanProperty(this, "autoLockWhenIdle", json.autoLockWhenIdle);
|
||||||
this.autoLockIdleSeconds = new SimpleIntegerProperty(this, "autoLockIdleSeconds", json.autoLockIdleSeconds);
|
this.autoLockIdleSeconds = new SimpleIntegerProperty(this, "autoLockIdleSeconds", json.autoLockIdleSeconds);
|
||||||
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
|
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
|
||||||
|
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
|
||||||
|
this.port = new SimpleIntegerProperty(this, "port", json.port);
|
||||||
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
|
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
|
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
|
||||||
final String name;
|
final String name;
|
||||||
@@ -95,7 +99,7 @@ public class VaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Observable[] observables() {
|
Observable[] observables() {
|
||||||
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode};
|
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultSettings withRandomId() {
|
public static VaultSettings withRandomId() {
|
||||||
@@ -124,6 +128,8 @@ public class VaultSettings {
|
|||||||
json.autoLockWhenIdle = autoLockWhenIdle.get();
|
json.autoLockWhenIdle = autoLockWhenIdle.get();
|
||||||
json.autoLockIdleSeconds = autoLockIdleSeconds.get();
|
json.autoLockIdleSeconds = autoLockIdleSeconds.get();
|
||||||
json.mountPoint = mountPoint.map(Path::toString).getValue();
|
json.mountPoint = mountPoint.map(Path::toString).getValue();
|
||||||
|
json.mountService = mountService.get();
|
||||||
|
json.port = port.get();
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ class VaultSettingsJson {
|
|||||||
@JsonProperty("autoLockIdleSeconds")
|
@JsonProperty("autoLockIdleSeconds")
|
||||||
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
|
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
|
||||||
|
|
||||||
|
@JsonProperty("mountService")
|
||||||
|
String mountService;
|
||||||
|
|
||||||
|
@JsonProperty("port")
|
||||||
|
int port = VaultSettings.DEFAULT_PORT;
|
||||||
|
|
||||||
@Deprecated(since = "1.7.0")
|
@Deprecated(since = "1.7.0")
|
||||||
@JsonProperty(value = "winDriveLetter", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
|
@JsonProperty(value = "winDriveLetter", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
|
||||||
String winDriveLetter;
|
String winDriveLetter;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ package org.cryptomator.common.vaults;
|
|||||||
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.mount.Mounter;
|
import org.cryptomator.common.mount.Mounter;
|
||||||
import org.cryptomator.common.mount.WindowsDriveLetters;
|
|
||||||
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;
|
||||||
@@ -73,7 +72,13 @@ public class Vault {
|
|||||||
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats, WindowsDriveLetters windowsDriveLetters, Mounter mounter) {
|
Vault(VaultSettings vaultSettings, //
|
||||||
|
VaultConfigCache configCache, //
|
||||||
|
AtomicReference<CryptoFileSystem> cryptoFileSystem, //
|
||||||
|
VaultState state, //
|
||||||
|
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
|
||||||
|
VaultStats stats, //
|
||||||
|
Mounter mounter) {
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.configCache = configCache;
|
this.configCache = configCache;
|
||||||
this.cryptoFileSystem = cryptoFileSystem;
|
this.cryptoFileSystem = cryptoFileSystem;
|
||||||
|
|||||||
+61
-15
@@ -13,31 +13,37 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.RadioButton;
|
import javafx.scene.control.RadioButton;
|
||||||
import javafx.scene.control.Toggle;
|
import javafx.scene.control.Toggle;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.DirectoryChooser;
|
import javafx.stage.DirectoryChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
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.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
public class CreateNewVaultLocationController implements FxController {
|
public class CreateNewVaultLocationController implements FxController {
|
||||||
@@ -49,19 +55,23 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> chooseNameScene;
|
private final Lazy<Scene> chooseNameScene;
|
||||||
private final Lazy<Scene> chooseExpertSettingsScene;
|
private final Lazy<Scene> chooseExpertSettingsScene;
|
||||||
private final List<RadioButton> locationPresetBtns;
|
|
||||||
private final ObjectProperty<Path> vaultPath;
|
private final ObjectProperty<Path> vaultPath;
|
||||||
private final StringProperty vaultName;
|
private final StringProperty vaultName;
|
||||||
|
private final ExecutorService backgroundExecutor;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final ObservableValue<VaultPathStatus> vaultPathStatus;
|
private final ObservableValue<VaultPathStatus> vaultPathStatus;
|
||||||
private final ObservableValue<Boolean> validVaultPath;
|
private final ObservableValue<Boolean> validVaultPath;
|
||||||
private final BooleanProperty usePresetPath;
|
private final BooleanProperty usePresetPath;
|
||||||
|
private final BooleanProperty loadingPresetLocations = new SimpleBooleanProperty(false);
|
||||||
|
private final ObservableList<Node> radioButtons;
|
||||||
|
private final ObservableList<Node> sortedRadioButtons;
|
||||||
|
|
||||||
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
||||||
|
|
||||||
//FXML
|
//FXML
|
||||||
public ToggleGroup locationPresetsToggler;
|
public ToggleGroup locationPresetsToggler;
|
||||||
public VBox radioButtonVBox;
|
public VBox radioButtonVBox;
|
||||||
|
public HBox customLocationRadioBtn;
|
||||||
public RadioButton customRadioButton;
|
public RadioButton customRadioButton;
|
||||||
public Label locationStatusLabel;
|
public Label locationStatusLabel;
|
||||||
public FontAwesome5IconView goodLocation;
|
public FontAwesome5IconView goodLocation;
|
||||||
@@ -73,25 +83,20 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS) Lazy<Scene> chooseExpertSettingsScene, //
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS) Lazy<Scene> chooseExpertSettingsScene, //
|
||||||
ObjectProperty<Path> vaultPath, //
|
ObjectProperty<Path> vaultPath, //
|
||||||
@Named("vaultName") StringProperty vaultName, //
|
@Named("vaultName") StringProperty vaultName, //
|
||||||
ResourceBundle resourceBundle) {
|
ExecutorService backgroundExecutor, ResourceBundle resourceBundle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.chooseNameScene = chooseNameScene;
|
this.chooseNameScene = chooseNameScene;
|
||||||
this.chooseExpertSettingsScene = chooseExpertSettingsScene;
|
this.chooseExpertSettingsScene = chooseExpertSettingsScene;
|
||||||
this.vaultPath = vaultPath;
|
this.vaultPath = vaultPath;
|
||||||
this.vaultName = vaultName;
|
this.vaultName = vaultName;
|
||||||
|
this.backgroundExecutor = backgroundExecutor;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.vaultPathStatus = ObservableUtil.mapWithDefault(vaultPath, this::validatePath, new VaultPathStatus(false, "error.message"));
|
this.vaultPathStatus = ObservableUtil.mapWithDefault(vaultPath, this::validatePath, new VaultPathStatus(false, "error.message"));
|
||||||
this.validVaultPath = ObservableUtil.mapWithDefault(vaultPathStatus, VaultPathStatus::valid, false);
|
this.validVaultPath = ObservableUtil.mapWithDefault(vaultPathStatus, VaultPathStatus::valid, false);
|
||||||
this.vaultPathStatus.addListener(this::updateStatusLabel);
|
this.vaultPathStatus.addListener(this::updateStatusLabel);
|
||||||
this.usePresetPath = new SimpleBooleanProperty();
|
this.usePresetPath = new SimpleBooleanProperty();
|
||||||
this.locationPresetBtns = LocationPresetsProvider.loadAll(LocationPresetsProvider.class) //
|
this.radioButtons = FXCollections.observableArrayList();
|
||||||
.flatMap(LocationPresetsProvider::getLocations) //
|
this.sortedRadioButtons = radioButtons.sorted(this::compareLocationPresets);
|
||||||
.sorted(Comparator.comparing(LocationPreset::name)) //
|
|
||||||
.map(preset -> { //
|
|
||||||
var btn = new RadioButton(preset.name());
|
|
||||||
btn.setUserData(preset.path());
|
|
||||||
return btn;
|
|
||||||
}).toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private VaultPathStatus validatePath(Path p) throws NullPointerException {
|
private VaultPathStatus validatePath(Path p) throws NullPointerException {
|
||||||
@@ -137,12 +142,45 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
radioButtonVBox.getChildren().addAll(1, locationPresetBtns); //first item is the list header
|
var task = backgroundExecutor.submit(this::loadLocationPresets);
|
||||||
locationPresetsToggler.getToggles().addAll(locationPresetBtns);
|
window.addEventHandler(WindowEvent.WINDOW_HIDING, _ -> task.cancel(true));
|
||||||
locationPresetsToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
locationPresetsToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
||||||
usePresetPath.bind(locationPresetsToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
usePresetPath.bind(locationPresetsToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
||||||
|
radioButtons.add(customLocationRadioBtn);
|
||||||
|
Bindings.bindContent(radioButtonVBox.getChildren(), sortedRadioButtons); //to prevent garbage collection of the binding, we bind explicitly to the sorted list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadLocationPresets() {
|
||||||
|
Platform.runLater(() -> loadingPresetLocations.set(true));
|
||||||
|
try {
|
||||||
|
LocationPresetsProvider.loadAll(LocationPresetsProvider.class) //
|
||||||
|
.flatMap(LocationPresetsProvider::getLocations) //we do not use sorted(), because it evaluates the stream elements, blocking until all elements are gathered
|
||||||
|
.forEach(this::createRadioButtonFor);
|
||||||
|
} finally {
|
||||||
|
Platform.runLater(() -> loadingPresetLocations.set(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRadioButtonFor(LocationPreset preset) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
var btn = new RadioButton(preset.name());
|
||||||
|
btn.setUserData(preset.path());
|
||||||
|
radioButtons.add(btn);
|
||||||
|
locationPresetsToggler.getToggles().add(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private int compareLocationPresets(Node left, Node right) {
|
||||||
|
if (customLocationRadioBtn.getId().equals(left.getId())) {
|
||||||
|
return 1;
|
||||||
|
} else if (customLocationRadioBtn.getId().equals(right.getId())) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return ((RadioButton) left).getText().compareToIgnoreCase(((RadioButton) right).getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||||
var storagePath = Optional.ofNullable((Path) newValue.getUserData()).orElse(customVaultPath);
|
var storagePath = Optional.ofNullable((Path) newValue.getUserData()).orElse(customVaultPath);
|
||||||
vaultPath.set(storagePath.resolve(vaultName.get()));
|
vaultPath.set(storagePath.resolve(vaultName.get()));
|
||||||
@@ -197,7 +235,15 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidVaultPath() {
|
public boolean isValidVaultPath() {
|
||||||
return validVaultPath.getValue();
|
return Boolean.TRUE.equals(validVaultPath.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoadingPresetLocations() {
|
||||||
|
return loadingPresetLocations.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty loadingPresetLocationsProperty() {
|
||||||
|
return loadingPresetLocations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty usePresetPathProperty() {
|
public BooleanProperty usePresetPathProperty() {
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ public enum FxmlFile {
|
|||||||
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
|
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
|
||||||
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
|
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
|
||||||
HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), //
|
HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), //
|
||||||
|
HUB_LEGACY_REGISTER_SUCCESS("/fxml/hub_legacy_register_success.fxml"), //
|
||||||
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
|
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
|
||||||
|
HUB_REGISTER_DEVICE_ALREADY_EXISTS("/fxml/hub_register_device_already_exists.fxml"), //
|
||||||
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), //
|
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), //
|
||||||
HUB_SETUP_DEVICE("/fxml/hub_setup_device.fxml"), //
|
HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), //
|
||||||
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
|
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
|
||||||
|
HUB_REQUIRE_ACCOUNT_INIT("/fxml/hub_require_account_init.fxml"), //
|
||||||
LOCK_FORCED("/fxml/lock_forced.fxml"), //
|
LOCK_FORCED("/fxml/lock_forced.fxml"), //
|
||||||
LOCK_FAILED("/fxml/lock_failed.fxml"), //
|
LOCK_FAILED("/fxml/lock_failed.fxml"), //
|
||||||
MAIN_WINDOW("/fxml/main_window.fxml"), //
|
MAIN_WINDOW("/fxml/main_window.fxml"), //
|
||||||
@@ -42,8 +45,10 @@ public enum FxmlFile {
|
|||||||
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
|
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
|
||||||
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
||||||
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
||||||
|
SHARE_VAULT("/fxml/share_vault.fxml"), //
|
||||||
UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
|
UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
|
||||||
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
|
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
|
||||||
|
UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), //
|
||||||
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
|
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
|
||||||
UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), //
|
UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), //
|
||||||
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
|
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
|
||||||
|
|||||||
@@ -47,12 +47,14 @@ public enum FontAwesome5Icon {
|
|||||||
QUESTION_CIRCLE("\uf059"), //
|
QUESTION_CIRCLE("\uf059"), //
|
||||||
REDO("\uF01E"), //
|
REDO("\uF01E"), //
|
||||||
SEARCH("\uF002"), //
|
SEARCH("\uF002"), //
|
||||||
|
SHARE("\uF064"), //
|
||||||
SPINNER("\uF110"), //
|
SPINNER("\uF110"), //
|
||||||
STETHOSCOPE("\uF0f1"), //
|
STETHOSCOPE("\uF0f1"), //
|
||||||
SYNC("\uF021"), //
|
SYNC("\uF021"), //
|
||||||
TIMES("\uF00D"), //
|
TIMES("\uF00D"), //
|
||||||
TRASH("\uF1F8"), //
|
TRASH("\uF1F8"), //
|
||||||
UNLINK("\uf127"), //
|
UNLINK("\uf127"), //
|
||||||
|
USER_COG("\uf4fe"), //
|
||||||
WRENCH("\uF0AD"), //
|
WRENCH("\uF0AD"), //
|
||||||
WINDOW_MINIMIZE("\uF2D1"), //
|
WINDOW_MINIMIZE("\uF2D1"), //
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public class ErrorController implements FxController {
|
|||||||
private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull();
|
private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull();
|
||||||
private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty();
|
private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty();
|
||||||
private final BooleanProperty askedForLookupDatabasePermission = new SimpleBooleanProperty();
|
private final BooleanProperty askedForLookupDatabasePermission = new SimpleBooleanProperty();
|
||||||
|
private final boolean formerSceneWasResizable;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment, ExecutorService executorService) {
|
ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment, ExecutorService executorService) {
|
||||||
@@ -85,12 +86,14 @@ public class ErrorController implements FxController {
|
|||||||
this.window = window;
|
this.window = window;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
|
this.formerSceneWasResizable = window.isResizable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void back() {
|
public void back() {
|
||||||
if (previousScene != null) {
|
if (previousScene != null) {
|
||||||
window.setScene(previousScene);
|
window.setScene(previousScene);
|
||||||
|
window.setResizable(formerSceneWasResizable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.cryptomator.ui.lock.LockComponent;
|
|||||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||||
import org.cryptomator.ui.quit.QuitComponent;
|
import org.cryptomator.ui.quit.QuitComponent;
|
||||||
|
import org.cryptomator.ui.sharevault.ShareVaultComponent;
|
||||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||||
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
||||||
@@ -22,7 +23,17 @@ import javafx.scene.image.Image;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, VaultOptionsComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class, HealthCheckComponent.class, UpdateReminderComponent.class})
|
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, //
|
||||||
|
MainWindowComponent.class, //
|
||||||
|
PreferencesComponent.class, //
|
||||||
|
VaultOptionsComponent.class, //
|
||||||
|
UnlockComponent.class, //
|
||||||
|
LockComponent.class, //
|
||||||
|
QuitComponent.class, //
|
||||||
|
ErrorComponent.class, //
|
||||||
|
HealthCheckComponent.class, //
|
||||||
|
UpdateReminderComponent.class, //
|
||||||
|
ShareVaultComponent.class})
|
||||||
abstract class FxApplicationModule {
|
abstract class FxApplicationModule {
|
||||||
|
|
||||||
private static Image createImageFromResource(String resourceName) throws IOException {
|
private static Image createImageFromResource(String resourceName) throws IOException {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
|||||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
import org.cryptomator.ui.quit.QuitComponent;
|
import org.cryptomator.ui.quit.QuitComponent;
|
||||||
|
import org.cryptomator.ui.sharevault.ShareVaultComponent;
|
||||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||||
import org.cryptomator.ui.unlock.UnlockWorkflow;
|
import org.cryptomator.ui.unlock.UnlockWorkflow;
|
||||||
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
||||||
@@ -51,6 +52,7 @@ public class FxApplicationWindows {
|
|||||||
private final ErrorComponent.Factory errorWindowFactory;
|
private final ErrorComponent.Factory errorWindowFactory;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||||
|
private final ShareVaultComponent.Factory shareVaultWindow;
|
||||||
private final FilteredList<Window> visibleWindows;
|
private final FilteredList<Window> visibleWindows;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -64,6 +66,7 @@ public class FxApplicationWindows {
|
|||||||
LockComponent.Factory lockWorkflowFactory, //
|
LockComponent.Factory lockWorkflowFactory, //
|
||||||
ErrorComponent.Factory errorWindowFactory, //
|
ErrorComponent.Factory errorWindowFactory, //
|
||||||
VaultOptionsComponent.Factory vaultOptionsWindow, //
|
VaultOptionsComponent.Factory vaultOptionsWindow, //
|
||||||
|
ShareVaultComponent.Factory shareVaultWindow, //
|
||||||
ExecutorService executor) {
|
ExecutorService executor) {
|
||||||
this.primaryStage = primaryStage;
|
this.primaryStage = primaryStage;
|
||||||
this.trayIntegration = trayIntegration;
|
this.trayIntegration = trayIntegration;
|
||||||
@@ -76,6 +79,7 @@ public class FxApplicationWindows {
|
|||||||
this.errorWindowFactory = errorWindowFactory;
|
this.errorWindowFactory = errorWindowFactory;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.vaultOptionsWindow = vaultOptionsWindow;
|
this.vaultOptionsWindow = vaultOptionsWindow;
|
||||||
|
this.shareVaultWindow = shareVaultWindow;
|
||||||
this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
|
this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +126,10 @@ public class FxApplicationWindows {
|
|||||||
return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
|
return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showShareVaultWindow(Vault vault) {
|
||||||
|
CompletableFuture.runAsync(() -> shareVaultWindow.create(vault).showShareVaultWindow(), Platform::runLater);
|
||||||
|
}
|
||||||
|
|
||||||
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
|
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
|
||||||
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
|
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown, when Hub registerDevice-Request returns with 409
|
||||||
|
*/
|
||||||
|
class DeviceAlreadyExistsException extends MasterkeyLoadingFailedException {
|
||||||
|
public DeviceAlreadyExistsException() {
|
||||||
|
super("Device already registered on this Hub instance");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -19,13 +19,44 @@ public class HubConfig {
|
|||||||
@Deprecated // use apiBaseUrl + "/devices/"
|
@Deprecated // use apiBaseUrl + "/devices/"
|
||||||
public String devicesResourceUrl;
|
public String devicesResourceUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of String template processors to construct URIs related to this Hub instance.
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public final URIProcessors URIs = new URIProcessors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URI pointing to the <code>/api/</code> base resource.
|
||||||
|
*
|
||||||
|
* @return <code>/api/</code> URI
|
||||||
|
* @apiNote URI is guaranteed to end on <code>/</code>
|
||||||
|
* @see #URIs
|
||||||
|
*/
|
||||||
public URI getApiBaseUrl() {
|
public URI getApiBaseUrl() {
|
||||||
if (apiBaseUrl != null) {
|
if (apiBaseUrl != null) {
|
||||||
return URI.create(apiBaseUrl);
|
// make sure to end on "/":
|
||||||
} else {
|
return URI.create(apiBaseUrl + "/").normalize();
|
||||||
// legacy approach
|
} else { // legacy approach
|
||||||
assert devicesResourceUrl != null;
|
assert devicesResourceUrl != null;
|
||||||
|
// make sure to end on "/":
|
||||||
return URI.create(devicesResourceUrl + "/..").normalize();
|
return URI.create(devicesResourceUrl + "/..").normalize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public URI getWebappBaseUrl() {
|
||||||
|
return getApiBaseUrl().resolve("../app/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class URIProcessors {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves paths relative to the <code>/api/</code> endpoint of this Hub instance.
|
||||||
|
*/
|
||||||
|
public final StringTemplate.Processor<URI, RuntimeException> API = template -> {
|
||||||
|
var path = template.interpolate();
|
||||||
|
var relPath = path.startsWith("/") ? path.substring(1) : path;
|
||||||
|
return getApiBaseUrl().resolve(relPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,13 @@ public abstract class HubKeyLoadingModule {
|
|||||||
return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE);
|
return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_SUCCESS)
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Scene provideHubLegacyRegisterSuccessScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS)
|
@FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS)
|
||||||
@KeyLoadingScoped
|
@KeyLoadingScoped
|
||||||
@@ -134,10 +141,17 @@ public abstract class HubKeyLoadingModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.HUB_SETUP_DEVICE)
|
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE)
|
||||||
@KeyLoadingScoped
|
@KeyLoadingScoped
|
||||||
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.HUB_SETUP_DEVICE);
|
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS)
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Scene provideHubRegisterDeviceAlreadyExistsScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -147,6 +161,13 @@ public abstract class HubKeyLoadingModule {
|
|||||||
return fxmlLoaders.createScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE);
|
return fxmlLoaders.createScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT)
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Scene provideRequireAccountInitScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT);
|
||||||
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(NoKeychainController.class)
|
@FxControllerKey(NoKeychainController.class)
|
||||||
@@ -177,6 +198,11 @@ public abstract class HubKeyLoadingModule {
|
|||||||
@FxControllerKey(LegacyRegisterDeviceController.class)
|
@FxControllerKey(LegacyRegisterDeviceController.class)
|
||||||
abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller);
|
abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(LegacyRegisterSuccessController.class)
|
||||||
|
abstract FxController bindLegacyRegisterSuccessController(LegacyRegisterSuccessController controller);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(RegisterSuccessController.class)
|
@FxControllerKey(RegisterSuccessController.class)
|
||||||
@@ -191,4 +217,9 @@ public abstract class HubKeyLoadingModule {
|
|||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(UnauthorizedDeviceController.class)
|
@FxControllerKey(UnauthorizedDeviceController.class)
|
||||||
abstract FxController bindUnauthorizedDeviceController(UnauthorizedDeviceController controller);
|
abstract FxController bindUnauthorizedDeviceController(UnauthorizedDeviceController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(RequireAccountInitController.class)
|
||||||
|
abstract FxController bindRequireAccountInitController(RequireAccountInitController controller);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.nimbusds.jose.JWEObject;
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
|
import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
|
||||||
@@ -44,6 +43,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
|||||||
this.window = window;
|
this.window = window;
|
||||||
this.keychainManager = keychainManager;
|
this.keychainManager = keychainManager;
|
||||||
window.setTitle(windowTitle);
|
window.setTitle(windowTitle);
|
||||||
|
window.setOnCloseRequest(_ -> result.cancel(true));
|
||||||
this.authFlowScene = authFlowScene;
|
this.authFlowScene = authFlowScene;
|
||||||
this.noKeychainScene = noKeychainScene;
|
this.noKeychainScene = noKeychainScene;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.io.BaseEncoding;
|
|
||||||
import com.nimbusds.jose.EncryptionMethod;
|
import com.nimbusds.jose.EncryptionMethod;
|
||||||
import com.nimbusds.jose.JOSEException;
|
import com.nimbusds.jose.JOSEException;
|
||||||
import com.nimbusds.jose.JWEAlgorithm;
|
import com.nimbusds.jose.JWEAlgorithm;
|
||||||
@@ -13,19 +12,20 @@ import com.nimbusds.jose.crypto.ECDHEncrypter;
|
|||||||
import com.nimbusds.jose.crypto.PasswordBasedDecrypter;
|
import com.nimbusds.jose.crypto.PasswordBasedDecrypter;
|
||||||
import com.nimbusds.jose.jwk.Curve;
|
import com.nimbusds.jose.jwk.Curve;
|
||||||
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
|
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
|
||||||
import com.nimbusds.jose.jwk.gen.JWKGenerator;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.Masterkey;
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.interfaces.ECPrivateKey;
|
import java.security.interfaces.ECPrivateKey;
|
||||||
import java.security.interfaces.ECPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -37,26 +37,16 @@ class JWEHelper {
|
|||||||
private static final String JWE_PAYLOAD_KEY_FIELD = "key";
|
private static final String JWE_PAYLOAD_KEY_FIELD = "key";
|
||||||
private static final String EC_ALG = "EC";
|
private static final String EC_ALG = "EC";
|
||||||
|
|
||||||
private JWEHelper(){}
|
private JWEHelper() {}
|
||||||
|
|
||||||
public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) {
|
public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) {
|
||||||
try {
|
return encryptKey(userKey, deviceKey);
|
||||||
var encodedUserKey = Base64.getEncoder().encodeToString(userKey.getEncoded());
|
|
||||||
var keyGen = new ECKeyGenerator(Curve.P_384);
|
|
||||||
var ephemeralKeyPair = keyGen.generate();
|
|
||||||
var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build();
|
|
||||||
var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedUserKey));
|
|
||||||
var jwe = new JWEObject(header, payload);
|
|
||||||
jwe.encrypt(new ECDHEncrypter(deviceKey));
|
|
||||||
return jwe;
|
|
||||||
} catch (JOSEException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException {
|
public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException {
|
||||||
try {
|
try {
|
||||||
jwe.decrypt(new PasswordBasedDecrypter(setupCode));
|
jwe.decrypt(new PasswordBasedDecrypter(setupCode));
|
||||||
return decodeUserKey(jwe);
|
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, JWEHelper::decodeECPrivateKey);
|
||||||
} catch (JOSEException e) {
|
} catch (JOSEException e) {
|
||||||
throw new InvalidJweKeyException(e);
|
throw new InvalidJweKeyException(e);
|
||||||
}
|
}
|
||||||
@@ -65,17 +55,23 @@ class JWEHelper {
|
|||||||
public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException {
|
public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException {
|
||||||
try {
|
try {
|
||||||
jwe.decrypt(new ECDHDecrypter(deviceKey));
|
jwe.decrypt(new ECDHDecrypter(deviceKey));
|
||||||
return decodeUserKey(jwe);
|
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, JWEHelper::decodeECPrivateKey);
|
||||||
} catch (JOSEException e) {
|
} catch (JOSEException e) {
|
||||||
throw new InvalidJweKeyException(e);
|
throw new InvalidJweKeyException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ECPrivateKey decodeUserKey(JWEObject decryptedJwe) {
|
/**
|
||||||
|
* Attempts to decode a DER-encoded EC private key.
|
||||||
|
*
|
||||||
|
* @param encoded DER-encoded EC private key
|
||||||
|
* @return the decoded key
|
||||||
|
* @throws KeyDecodeFailedException On malformed input
|
||||||
|
*/
|
||||||
|
public static ECPrivateKey decodeECPrivateKey(byte[] encoded) throws KeyDecodeFailedException {
|
||||||
try {
|
try {
|
||||||
var keySpec = readKey(decryptedJwe, JWE_PAYLOAD_KEY_FIELD, PKCS8EncodedKeySpec::new);
|
KeyFactory factory = KeyFactory.getInstance(EC_ALG);
|
||||||
var factory = KeyFactory.getInstance(EC_ALG);
|
var privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
|
||||||
var privateKey = factory.generatePrivate(keySpec);
|
|
||||||
if (privateKey instanceof ECPrivateKey ecPrivateKey) {
|
if (privateKey instanceof ECPrivateKey ecPrivateKey) {
|
||||||
return ecPrivateKey;
|
return ecPrivateKey;
|
||||||
} else {
|
} else {
|
||||||
@@ -84,8 +80,49 @@ class JWEHelper {
|
|||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new IllegalStateException(EC_ALG + " not supported");
|
throw new IllegalStateException(EC_ALG + " not supported");
|
||||||
} catch (InvalidKeySpecException e) {
|
} catch (InvalidKeySpecException e) {
|
||||||
LOG.warn("Unexpected JWE payload: {}", decryptedJwe.getPayload());
|
throw new KeyDecodeFailedException(e);
|
||||||
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to decode a DER-encoded EC public key.
|
||||||
|
*
|
||||||
|
* @param encoded DER-encoded EC public key
|
||||||
|
* @return the decoded key
|
||||||
|
* @throws KeyDecodeFailedException On malformed input
|
||||||
|
*/
|
||||||
|
public static ECPublicKey decodeECPublicKey(byte[] encoded) throws KeyDecodeFailedException {
|
||||||
|
try {
|
||||||
|
KeyFactory factory = KeyFactory.getInstance(EC_ALG);
|
||||||
|
var publicKey = factory.generatePublic(new X509EncodedKeySpec(encoded));
|
||||||
|
if (publicKey instanceof ECPublicKey ecPublicKey) {
|
||||||
|
return ecPublicKey;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(EC_ALG + " key factory not generating ECPublicKeys");
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException(EC_ALG + " not supported");
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
throw new KeyDecodeFailedException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JWEObject encryptVaultKey(Masterkey vaultKey, ECPublicKey userKey) {
|
||||||
|
return encryptKey(vaultKey, userKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JWEObject encryptKey(Key key, ECPublicKey userKey) {
|
||||||
|
try {
|
||||||
|
var encodedVaultKey = Base64.getEncoder().encodeToString(key.getEncoded());
|
||||||
|
var keyGen = new ECKeyGenerator(Curve.P_384);
|
||||||
|
var ephemeralKeyPair = keyGen.generate();
|
||||||
|
var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build();
|
||||||
|
var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedVaultKey));
|
||||||
|
var jwe = new JWEObject(header, payload);
|
||||||
|
jwe.encrypt(new ECDHEncrypter(userKey));
|
||||||
|
return jwe;
|
||||||
|
} catch (JOSEException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +145,12 @@ class JWEHelper {
|
|||||||
var keyBytes = new byte[0];
|
var keyBytes = new byte[0];
|
||||||
try {
|
try {
|
||||||
if (fields.get(keyField) instanceof String key) {
|
if (fields.get(keyField) instanceof String key) {
|
||||||
keyBytes = BaseEncoding.base64().decode(key);
|
keyBytes = Base64.getDecoder().decode(key);
|
||||||
return rawKeyFactory.apply(keyBytes);
|
return rawKeyFactory.apply(keyBytes);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField);
|
throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField);
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException | KeyDecodeFailedException e) {
|
||||||
LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
|
LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
|
||||||
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
|
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -127,4 +164,11 @@ class JWEHelper {
|
|||||||
super("Invalid key", cause);
|
super("Invalid key", cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class KeyDecodeFailedException extends CryptoException {
|
||||||
|
|
||||||
|
public KeyDecodeFailedException(Throwable cause) {
|
||||||
|
super("Malformed key", cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class LegacyRegisterDeviceController implements FxController {
|
|||||||
public Button registerBtn;
|
public Button registerBtn;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LegacyRegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
|
public LegacyRegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.hubConfig = hubConfig;
|
this.hubConfig = hubConfig;
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
@KeyLoadingScoped
|
||||||
|
public class LegacyRegisterSuccessController implements FxController {
|
||||||
|
private final Stage window;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LegacyRegisterSuccessController(@KeyLoading Stage window) {
|
||||||
|
this.window = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package org.cryptomator.ui.keyloading.hub;
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
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.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
@@ -11,7 +12,6 @@ import org.cryptomator.ui.common.FxmlFile;
|
|||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@ import java.net.http.HttpResponse;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -48,27 +47,29 @@ public class ReceiveKeyController implements FxController {
|
|||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final HubConfig hubConfig;
|
private final HubConfig hubConfig;
|
||||||
|
private final String vaultId;
|
||||||
private final String deviceId;
|
private final String deviceId;
|
||||||
private final String bearerToken;
|
private final String bearerToken;
|
||||||
private final CompletableFuture<ReceivedKey> result;
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
private final Lazy<Scene> setupDeviceScene;
|
private final Lazy<Scene> registerDeviceScene;
|
||||||
private final Lazy<Scene> legacyRegisterDeviceScene;
|
private final Lazy<Scene> legacyRegisterDeviceScene;
|
||||||
private final Lazy<Scene> unauthorizedScene;
|
private final Lazy<Scene> unauthorizedScene;
|
||||||
private final URI vaultBaseUri;
|
private final Lazy<Scene> accountInitializationScene;
|
||||||
private final Lazy<Scene> invalidLicenseScene;
|
private final Lazy<Scene> invalidLicenseScene;
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) Lazy<Scene> setupDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
|
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy<Scene> accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.hubConfig = hubConfig;
|
this.hubConfig = hubConfig;
|
||||||
|
this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs)
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
this.bearerToken = Objects.requireNonNull(tokenRef.get());
|
this.bearerToken = Objects.requireNonNull(tokenRef.get());
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.setupDeviceScene = setupDeviceScene;
|
this.registerDeviceScene = registerDeviceScene;
|
||||||
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
|
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
|
||||||
this.unauthorizedScene = unauthorizedScene;
|
this.unauthorizedScene = unauthorizedScene;
|
||||||
this.vaultBaseUri = getVaultBaseUri(vault);
|
this.accountInitializationScene = accountInitializationScene;
|
||||||
this.invalidLicenseScene = invalidLicenseScene;
|
this.invalidLicenseScene = invalidLicenseScene;
|
||||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
||||||
@@ -76,69 +77,76 @@ public class ReceiveKeyController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
requestVaultMasterkey();
|
receiveKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receiveKey() {
|
||||||
|
requestApiConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* STEP 1 (Request): GET vault key for this user
|
* STEP 0 (Request): GET /api/config
|
||||||
*/
|
*/
|
||||||
private void requestVaultMasterkey() {
|
private void requestApiConfig() {
|
||||||
var accessTokenUri = appendPath(vaultBaseUri, "/access-token");
|
var configUri = hubConfig.URIs.API."config";
|
||||||
var request = HttpRequest.newBuilder(accessTokenUri) //
|
var request = HttpRequest.newBuilder(configUri) //
|
||||||
.header("Authorization", "Bearer " + bearerToken) //
|
|
||||||
.GET() //
|
.GET() //
|
||||||
.timeout(REQ_TIMEOUT) //
|
.timeout(REQ_TIMEOUT) //
|
||||||
.build();
|
.build();
|
||||||
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
|
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
|
||||||
.thenAcceptAsync(this::receivedVaultMasterkey, Platform::runLater) //
|
.thenAcceptAsync(this::receivedApiConfig, Platform::runLater) //
|
||||||
.exceptionally(this::retrievalFailed);
|
.exceptionally(this::retrievalFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* STEP 1 (Response): GET vault key for this user
|
* STEP 0 (Response): GET /api/config
|
||||||
*
|
*
|
||||||
* @param response Response
|
* @param response Response
|
||||||
*/
|
*/
|
||||||
private void receivedVaultMasterkey(HttpResponse<String> response) {
|
private void receivedApiConfig(HttpResponse<String> response) {
|
||||||
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
|
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
|
||||||
switch (response.statusCode()) {
|
Preconditions.checkState(response.statusCode() == 200, "Unexpected response " + response.statusCode());
|
||||||
case 200 -> requestUserKey(response.body());
|
try {
|
||||||
case 402 -> licenseExceeded();
|
var config = JSON.reader().readValue(response.body(), ConfigDto.class);
|
||||||
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
|
if (config.apiLevel >= 1) {
|
||||||
case 404 -> requestLegacyAccessToken();
|
requestDeviceData();
|
||||||
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
|
} else {
|
||||||
|
requestLegacyAccessToken();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* STEP 2 (Request): GET user key for this device
|
* STEP 1 (Request): GET user key for this device
|
||||||
*/
|
*/
|
||||||
private void requestUserKey(String encryptedVaultKey) {
|
private void requestDeviceData() {
|
||||||
var deviceTokenUri = URI.create(hubConfig.getApiBaseUrl() + "/devices/" + deviceId);
|
var deviceUri = hubConfig.URIs.API."devices/\{deviceId}";
|
||||||
var request = HttpRequest.newBuilder(deviceTokenUri) //
|
var request = HttpRequest.newBuilder(deviceUri) //
|
||||||
.header("Authorization", "Bearer " + bearerToken) //
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
.GET() //
|
.GET() //
|
||||||
.timeout(REQ_TIMEOUT) //
|
.timeout(REQ_TIMEOUT) //
|
||||||
.build();
|
.build();
|
||||||
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
|
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
|
||||||
.thenAcceptAsync(response -> receivedUserKey(encryptedVaultKey, response), Platform::runLater) //
|
.thenAcceptAsync(this::receivedDeviceData) //
|
||||||
.exceptionally(this::retrievalFailed);
|
.exceptionally(this::retrievalFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* STEP 2 (Response): GET user key for this device
|
* STEP 1 (Response): GET user key for this device
|
||||||
*
|
*
|
||||||
* @param response Response
|
* @param response Response
|
||||||
*/
|
*/
|
||||||
private void receivedUserKey(String encryptedVaultKey, HttpResponse<String> response) {
|
private void receivedDeviceData(HttpResponse<String> response) {
|
||||||
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
|
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
|
||||||
try {
|
try {
|
||||||
switch (response.statusCode()) {
|
switch (response.statusCode()) {
|
||||||
case 200 -> {
|
case 200 -> {
|
||||||
var device = JSON.reader().readValue(response.body(), DeviceDto.class);
|
var device = JSON.reader().readValue(response.body(), DeviceDto.class);
|
||||||
receivedBothEncryptedKeys(encryptedVaultKey, device.userPrivateKey);
|
requestVaultMasterkey(device.userPrivateKey);
|
||||||
}
|
}
|
||||||
case 404 -> needsDeviceSetup(); // TODO: using the setup code, we can theoretically immediately unlock
|
case 404 -> Platform.runLater(this::needsDeviceRegistration);
|
||||||
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
|
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -146,18 +154,49 @@ public class ReceiveKeyController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void needsDeviceSetup() {
|
private void needsDeviceRegistration() {
|
||||||
window.setScene(setupDeviceScene.get());
|
window.setScene(registerDeviceScene.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) throws IOException {
|
/**
|
||||||
|
* STEP 2 (Request): GET vault key for this user
|
||||||
|
*/
|
||||||
|
private void requestVaultMasterkey(String encryptedUserKey) {
|
||||||
|
var vaultKeyUri = hubConfig.URIs.API."vaults/\{vaultId}/access-token";
|
||||||
|
var request = HttpRequest.newBuilder(vaultKeyUri) //
|
||||||
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
|
.GET() //
|
||||||
|
.timeout(REQ_TIMEOUT) //
|
||||||
|
.build();
|
||||||
|
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
|
||||||
|
.thenAcceptAsync(response -> receivedVaultMasterkey(encryptedUserKey, response), Platform::runLater) //
|
||||||
|
.exceptionally(this::retrievalFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STEP 2 (Response): GET vault key for this user
|
||||||
|
*
|
||||||
|
* @param response Response
|
||||||
|
*/
|
||||||
|
private void receivedVaultMasterkey(String encryptedUserKey, HttpResponse<String> response) {
|
||||||
|
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
|
||||||
|
switch (response.statusCode()) {
|
||||||
|
case 200 -> receivedBothEncryptedKeys(response.body(), encryptedUserKey);
|
||||||
|
case 402 -> licenseExceeded();
|
||||||
|
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
|
||||||
|
case 449 -> accountInitializationRequired();
|
||||||
|
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) {
|
||||||
try {
|
try {
|
||||||
var vaultKeyJwe = JWEObject.parse(encryptedVaultKey);
|
var vaultKeyJwe = JWEObject.parse(encryptedVaultKey);
|
||||||
var userKeyJwe = JWEObject.parse(encryptedUserKey);
|
var userKeyJwe = JWEObject.parse(encryptedUserKey);
|
||||||
result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe));
|
result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe));
|
||||||
window.close();
|
window.close();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
throw new IOException("Failed to parse JWE", e);
|
retrievalFailed(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +205,7 @@ public class ReceiveKeyController implements FxController {
|
|||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
private void requestLegacyAccessToken() {
|
private void requestLegacyAccessToken() {
|
||||||
var legacyAccessTokenUri = appendPath(vaultBaseUri, "/keys/" + deviceId);
|
var legacyAccessTokenUri = hubConfig.URIs.API."vaults/\{vaultId}/keys/\{deviceId}";
|
||||||
var request = HttpRequest.newBuilder(legacyAccessTokenUri) //
|
var request = HttpRequest.newBuilder(legacyAccessTokenUri) //
|
||||||
.header("Authorization", "Bearer " + bearerToken) //
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
.GET() //
|
.GET() //
|
||||||
@@ -221,6 +260,10 @@ public class ReceiveKeyController implements FxController {
|
|||||||
window.setScene(unauthorizedScene.get());
|
window.setScene(unauthorizedScene.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void accountInitializationRequired() {
|
||||||
|
window.setScene(accountInitializationScene.get());
|
||||||
|
}
|
||||||
|
|
||||||
private Void retrievalFailed(Throwable cause) {
|
private Void retrievalFailed(Throwable cause) {
|
||||||
result.completeExceptionally(cause);
|
result.completeExceptionally(cause);
|
||||||
return null;
|
return null;
|
||||||
@@ -244,19 +287,15 @@ public class ReceiveKeyController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static URI getVaultBaseUri(Vault vault) {
|
private static String extractVaultId(URI vaultKeyUri) {
|
||||||
try {
|
assert vaultKeyUri.getScheme().startsWith(SCHEME_PREFIX);
|
||||||
var url = vault.getVaultConfigCache().get().getKeyId();
|
var path = vaultKeyUri.getPath();
|
||||||
assert url.getScheme().startsWith(SCHEME_PREFIX);
|
return path.substring(path.lastIndexOf('/') + 1);
|
||||||
var correctedScheme = url.getScheme().substring(SCHEME_PREFIX.length());
|
|
||||||
return new URI(correctedScheme, url.getSchemeSpecificPart(), url.getFragment());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new IllegalStateException("URI constructed from params known to be valid", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
|
private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
private record ConfigDto(@JsonProperty(value = "apiLevel") int apiLevel) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,19 +31,25 @@ import javafx.scene.control.TextField;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.WindowEvent;
|
import javafx.stage.WindowEvent;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.interfaces.ECPublicKey;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@KeyLoadingScoped
|
@KeyLoadingScoped
|
||||||
public class RegisterDeviceController implements FxController {
|
public class RegisterDeviceController implements FxController {
|
||||||
@@ -57,12 +63,12 @@ public class RegisterDeviceController implements FxController {
|
|||||||
private final String bearerToken;
|
private final String bearerToken;
|
||||||
private final Lazy<Scene> registerSuccessScene;
|
private final Lazy<Scene> registerSuccessScene;
|
||||||
private final Lazy<Scene> registerFailedScene;
|
private final Lazy<Scene> registerFailedScene;
|
||||||
|
private final Lazy<Scene> deviceAlreadyExistsScene;
|
||||||
private final String deviceId;
|
private final String deviceId;
|
||||||
private final P384KeyPair deviceKeyPair;
|
private final P384KeyPair deviceKeyPair;
|
||||||
private final CompletableFuture<ReceivedKey> result;
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
|
|
||||||
private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false);
|
private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false);
|
||||||
private final BooleanProperty workInProgress = new SimpleBooleanProperty(false);
|
private final BooleanProperty workInProgress = new SimpleBooleanProperty(false);
|
||||||
public TextField setupCodeField;
|
public TextField setupCodeField;
|
||||||
@@ -70,7 +76,7 @@ public class RegisterDeviceController implements FxController {
|
|||||||
public Button registerBtn;
|
public Button registerBtn;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
|
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS) Lazy<Scene> deviceAlreadyExistsScene) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.hubConfig = hubConfig;
|
this.hubConfig = hubConfig;
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
@@ -79,13 +85,13 @@ public class RegisterDeviceController implements FxController {
|
|||||||
this.bearerToken = Objects.requireNonNull(bearerToken.get());
|
this.bearerToken = Objects.requireNonNull(bearerToken.get());
|
||||||
this.registerSuccessScene = registerSuccessScene;
|
this.registerSuccessScene = registerSuccessScene;
|
||||||
this.registerFailedScene = registerFailedScene;
|
this.registerFailedScene = registerFailedScene;
|
||||||
|
this.deviceAlreadyExistsScene = deviceAlreadyExistsScene;
|
||||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
deviceNameField.setText(determineHostname());
|
deviceNameField.setText(determineHostname());
|
||||||
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
|
|
||||||
deviceNameField.disableProperty().bind(workInProgress);
|
deviceNameField.disableProperty().bind(workInProgress);
|
||||||
setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false));
|
setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false));
|
||||||
setupCodeField.disableProperty().bind(workInProgress);
|
setupCodeField.disableProperty().bind(workInProgress);
|
||||||
@@ -108,9 +114,8 @@ public class RegisterDeviceController implements FxController {
|
|||||||
public void register() {
|
public void register() {
|
||||||
workInProgress.set(true);
|
workInProgress.set(true);
|
||||||
|
|
||||||
var apiRootUrl = hubConfig.getApiBaseUrl();
|
|
||||||
|
|
||||||
var userReq = HttpRequest.newBuilder(apiRootUrl.resolve("users/me")) //
|
var userReq = HttpRequest.newBuilder(hubConfig.URIs.API."users/me") //
|
||||||
.GET() //
|
.GET() //
|
||||||
.timeout(REQ_TIMEOUT) //
|
.timeout(REQ_TIMEOUT) //
|
||||||
.header("Authorization", "Bearer " + bearerToken) //
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
@@ -126,17 +131,19 @@ public class RegisterDeviceController implements FxController {
|
|||||||
}
|
}
|
||||||
}).thenApply(user -> {
|
}).thenApply(user -> {
|
||||||
try {
|
try {
|
||||||
assert user.privateKey != null; // api/vaults/{v}/user-tokens/me would have returned 403, if user wasn't fully set up yet
|
assert user.privateKey != null && user.publicKey != null; // api/vaults/{v}/user-tokens/me would have returned 403, if user wasn't fully set up yet
|
||||||
|
var userPublicKey = JWEHelper.decodeECPublicKey(Base64.getDecoder().decode(user.publicKey));
|
||||||
|
migrateLegacyDevices(userPublicKey); // TODO: remove eventually, when most users have migrated to Hub 1.3.x or newer
|
||||||
var userKey = JWEHelper.decryptUserKey(JWEObject.parse(user.privateKey), setupCodeField.getText());
|
var userKey = JWEHelper.decryptUserKey(JWEObject.parse(user.privateKey), setupCodeField.getText());
|
||||||
return JWEHelper.encryptUserKey(userKey, deviceKeyPair.getPublic());
|
return JWEHelper.encryptUserKey(userKey, deviceKeyPair.getPublic());
|
||||||
} catch (ParseException e) {
|
} catch (ParseException | JWEHelper.KeyDecodeFailedException e) {
|
||||||
throw new RuntimeException("Server answered with unparsable user key", e);
|
throw new RuntimeException("Server answered with unparsable user key", e);
|
||||||
}
|
}
|
||||||
}).thenCompose(jwe -> {
|
}).thenCompose(jwe -> {
|
||||||
var now = Instant.now().toString();
|
var now = Instant.now().toString();
|
||||||
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now);
|
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now);
|
||||||
var json = toJson(dto);
|
var json = toJson(dto);
|
||||||
var deviceUri = apiRootUrl.resolve("devices/" + deviceId);
|
var deviceUri = hubConfig.URIs.API."devices/\{deviceId}";
|
||||||
var putDeviceReq = HttpRequest.newBuilder(deviceUri) //
|
var putDeviceReq = HttpRequest.newBuilder(deviceUri) //
|
||||||
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
|
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
|
||||||
.timeout(REQ_TIMEOUT) //
|
.timeout(REQ_TIMEOUT) //
|
||||||
@@ -146,7 +153,7 @@ public class RegisterDeviceController implements FxController {
|
|||||||
return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding());
|
return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding());
|
||||||
}).whenCompleteAsync((response, throwable) -> {
|
}).whenCompleteAsync((response, throwable) -> {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
this.handleResponse(response);
|
this.handleRegisterDeviceResponse(response);
|
||||||
} else {
|
} else {
|
||||||
this.setupFailed(throwable);
|
this.setupFailed(throwable);
|
||||||
}
|
}
|
||||||
@@ -154,6 +161,46 @@ public class RegisterDeviceController implements FxController {
|
|||||||
}, Platform::runLater);
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void migrateLegacyDevices(ECPublicKey userPublicKey) {
|
||||||
|
try {
|
||||||
|
// GET legacy access tokens
|
||||||
|
var getUri = hubConfig.URIs.API."devices/\{deviceId}/legacy-access-tokens";
|
||||||
|
var getReq = HttpRequest.newBuilder(getUri).GET().timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build();
|
||||||
|
var getRes = httpClient.send(getReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||||
|
if (getRes.statusCode() != 200) {
|
||||||
|
LOG.debug("GET {} resulted in status code {}. Skipping migration.", getUri, getRes.statusCode());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, String> legacyAccessTokens = JSON.readerForMapOf(String.class).readValue(getRes.body());
|
||||||
|
if (legacyAccessTokens.isEmpty()) {
|
||||||
|
return; // no migration required
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST new access tokens
|
||||||
|
Map<String, String> newAccessTokens = legacyAccessTokens.entrySet().stream().<Map.Entry<String, String>>mapMulti((entry, consumer) -> {
|
||||||
|
try (var vaultKey = JWEHelper.decryptVaultKey(JWEObject.parse(entry.getValue()), deviceKeyPair.getPrivate())) {
|
||||||
|
var newAccessToken = JWEHelper.encryptVaultKey(vaultKey, userPublicKey).serialize();
|
||||||
|
consumer.accept(Map.entry(entry.getKey(), newAccessToken));
|
||||||
|
} catch (ParseException | JWEHelper.InvalidJweKeyException e) {
|
||||||
|
LOG.warn("Failed to decrypt legacy access token for vault {}. Skipping migration.", entry.getKey());
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
var postUri = hubConfig.URIs.API."users/me/access-tokens";
|
||||||
|
var postBody = JSON.writer().writeValueAsString(newAccessTokens);
|
||||||
|
var postReq = HttpRequest.newBuilder(postUri).POST(HttpRequest.BodyPublishers.ofString(postBody)).timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build();
|
||||||
|
var postRes = httpClient.send(postReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||||
|
if (postRes.statusCode() != 200) {
|
||||||
|
throw new IOException(STR."Unexpected response from POST \{postUri}: \{postRes.statusCode()}");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// log and ignore: this is merely a best-effort attempt of migrating legacy devices. Failure is uncritical as this is merely a convenience feature.
|
||||||
|
LOG.error("Legacy Device Migration failed.", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new UncheckedIOException(new InterruptedIOException("Legacy Device Migration interrupted"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private UserDto fromJson(String json) {
|
private UserDto fromJson(String json) {
|
||||||
try {
|
try {
|
||||||
return JSON.reader().readValue(json, UserDto.class);
|
return JSON.reader().readValue(json, UserDto.class);
|
||||||
@@ -170,12 +217,12 @@ public class RegisterDeviceController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleResponse(HttpResponse<Void> response) {
|
private void handleRegisterDeviceResponse(HttpResponse<Void> response) {
|
||||||
if (response.statusCode() == 201) {
|
if (response.statusCode() == 201) {
|
||||||
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
|
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
|
||||||
window.setScene(registerSuccessScene.get());
|
window.setScene(registerSuccessScene.get());
|
||||||
} else if (response.statusCode() == 409) {
|
} else if (response.statusCode() == 409) {
|
||||||
deviceNameAlreadyExists.set(true);
|
setupFailed(new DeviceAlreadyExistsException());
|
||||||
} else {
|
} else {
|
||||||
setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode()));
|
setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode()));
|
||||||
}
|
}
|
||||||
@@ -184,10 +231,13 @@ public class RegisterDeviceController implements FxController {
|
|||||||
private void setupFailed(Throwable cause) {
|
private void setupFailed(Throwable cause) {
|
||||||
switch (cause) {
|
switch (cause) {
|
||||||
case CompletionException e when e.getCause() instanceof JWEHelper.InvalidJweKeyException -> invalidSetupCode.set(true);
|
case CompletionException e when e.getCause() instanceof JWEHelper.InvalidJweKeyException -> invalidSetupCode.set(true);
|
||||||
|
case DeviceAlreadyExistsException e -> {
|
||||||
|
LOG.debug("Device already registered in hub instance {} for different user", hubConfig.authSuccessUrl);
|
||||||
|
window.setScene(deviceAlreadyExistsScene.get());
|
||||||
|
}
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Device setup failed.", cause);
|
LOG.warn("Device setup failed.", cause);
|
||||||
window.setScene(registerFailedScene.get());
|
window.setScene(registerFailedScene.get());
|
||||||
result.completeExceptionally(cause);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,15 +252,6 @@ public class RegisterDeviceController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//--- Getters & Setters
|
//--- Getters & Setters
|
||||||
|
|
||||||
public BooleanProperty deviceNameAlreadyExistsProperty() {
|
|
||||||
return deviceNameAlreadyExists;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getDeviceNameAlreadyExists() {
|
|
||||||
return deviceNameAlreadyExists.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty invalidSetupCodeProperty() {
|
public BooleanProperty invalidSetupCodeProperty() {
|
||||||
return invalidSetupCode;
|
return invalidSetupCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
import com.nimbusds.jose.JWEObject;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
|
||||||
@@ -22,8 +21,8 @@ public class RegisterFailedController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void close() {
|
public void close() {
|
||||||
|
result.cancel(true);
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public class RegisterSuccessController implements FxController {
|
public class RegisterSuccessController implements FxController {
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
|
private final Lazy<Scene> receiveKeyScene;
|
||||||
|
private final ReceiveKeyController receiveKeyController;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RegisterSuccessController(@KeyLoading Stage window) {
|
public RegisterSuccessController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene, ReceiveKeyController receiveKeyController) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
this.result = result;
|
||||||
|
this.receiveKeyScene = receiveKeyScene;
|
||||||
|
this.receiveKeyController = receiveKeyController;
|
||||||
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void close() {
|
public void complete() {
|
||||||
window.close();
|
window.setScene(receiveKeyScene.get());
|
||||||
|
receiveKeyController.receiveKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void windowClosed(WindowEvent windowEvent) {
|
||||||
|
result.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@KeyLoadingScoped
|
||||||
|
public class RequireAccountInitController implements FxController {
|
||||||
|
|
||||||
|
private final Application application;
|
||||||
|
private final HubConfig hubConfig;
|
||||||
|
private final Stage window;
|
||||||
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RequireAccountInitController(Application application, HubConfig hubConfig, @KeyLoading Stage window, CompletableFuture<ReceivedKey> result) {
|
||||||
|
this.application = application;
|
||||||
|
this.hubConfig = hubConfig;
|
||||||
|
this.window = window;
|
||||||
|
this.result = result;
|
||||||
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void completeSetup() {
|
||||||
|
application.getHostServices().showDocument(hubConfig.getWebappBaseUrl().resolve("profile").toString());
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void windowClosed(WindowEvent windowEvent) {
|
||||||
|
result.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,11 @@ public class VaultDetailLockedController implements FxController {
|
|||||||
appWindows.startUnlockWorkflow(vault.get(), mainWindow);
|
appWindows.startUnlockWorkflow(vault.get(), mainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void share() {
|
||||||
|
appWindows.showShareVaultWindow(vault.get());
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void showVaultOptions() {
|
public void showVaultOptions() {
|
||||||
vaultOptionsWindow.create(vault.get()).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
vaultOptionsWindow.create(vault.get()).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
|||||||
import org.cryptomator.cryptofs.DirStructure;
|
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.fxapp.FxApplicationWindows;
|
||||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -37,6 +38,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -64,6 +66,7 @@ public class VaultListController implements FxController {
|
|||||||
private final VaultListManager vaultListManager;
|
private final VaultListManager vaultListManager;
|
||||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
private final FxApplicationWindows appWindows;
|
||||||
|
|
||||||
public ListView<Vault> vaultList;
|
public ListView<Vault> vaultList;
|
||||||
public StackPane root;
|
public StackPane root;
|
||||||
@@ -79,7 +82,8 @@ public class VaultListController implements FxController {
|
|||||||
AddVaultWizardComponent.Builder addVaultWizard, //
|
AddVaultWizardComponent.Builder addVaultWizard, //
|
||||||
RemoveVaultComponent.Builder removeVaultDialogue, //
|
RemoveVaultComponent.Builder removeVaultDialogue, //
|
||||||
VaultListManager vaultListManager, //
|
VaultListManager vaultListManager, //
|
||||||
ResourceBundle resourceBundle) {
|
ResourceBundle resourceBundle, //
|
||||||
|
FxApplicationWindows appWindows) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
this.selectedVault = selectedVault;
|
this.selectedVault = selectedVault;
|
||||||
@@ -88,6 +92,7 @@ public class VaultListController implements FxController {
|
|||||||
this.removeVaultDialogue = removeVaultDialogue;
|
this.removeVaultDialogue = removeVaultDialogue;
|
||||||
this.vaultListManager = vaultListManager;
|
this.vaultListManager = vaultListManager;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.appWindows = appWindows;
|
||||||
|
|
||||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||||
|
|
||||||
@@ -108,6 +113,15 @@ public class VaultListController implements FxController {
|
|||||||
});
|
});
|
||||||
vaultList.addEventFilter(MouseEvent.MOUSE_RELEASED, this::deselect);
|
vaultList.addEventFilter(MouseEvent.MOUSE_RELEASED, this::deselect);
|
||||||
|
|
||||||
|
//unlock vault on double click
|
||||||
|
vaultList.addEventFilter(MouseEvent.MOUSE_CLICKED, click -> {
|
||||||
|
if (click.getClickCount() >= 2) {
|
||||||
|
Optional.ofNullable(selectedVault.get())
|
||||||
|
.filter(Vault::isLocked)
|
||||||
|
.ifPresent(vault -> appWindows.startUnlockWorkflow(vault, mainWindow));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//don't show context menu when no vault selected
|
//don't show context menu when no vault selected
|
||||||
vaultList.addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, request -> {
|
vaultList.addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, request -> {
|
||||||
if (selectedVault.get() == null) {
|
if (selectedVault.get() == null) {
|
||||||
|
|||||||
@@ -2,17 +2,14 @@ package org.cryptomator.ui.preferences;
|
|||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.ObservableUtil;
|
import org.cryptomator.common.ObservableUtil;
|
||||||
import org.cryptomator.common.mount.MountModule;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.integrations.mount.MountCapability;
|
import org.cryptomator.integrations.mount.MountCapability;
|
||||||
import org.cryptomator.integrations.mount.MountService;
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanExpression;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ChoiceBox;
|
import javafx.scene.control.ChoiceBox;
|
||||||
@@ -21,24 +18,22 @@ import javafx.util.StringConverter;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
public class VolumePreferencesController implements FxController {
|
public class VolumePreferencesController implements FxController {
|
||||||
|
|
||||||
private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
|
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
|
||||||
private static final int MIN_PORT = 1024;
|
public static final int MIN_PORT = 1024;
|
||||||
private static final int MAX_PORT = 65535;
|
public static final int MAX_PORT = 65535;
|
||||||
|
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final ObservableValue<MountService> selectedMountService;
|
private final ObservableValue<MountService> selectedMountService;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final BooleanExpression loopbackPortSupported;
|
private final ObservableValue<Boolean> loopbackPortSupported;
|
||||||
private final ObservableValue<Boolean> mountToDirSupported;
|
private final ObservableValue<Boolean> mountToDirSupported;
|
||||||
private final ObservableValue<Boolean> mountToDriveLetterSupported;
|
private final ObservableValue<Boolean> mountToDriveLetterSupported;
|
||||||
private final ObservableValue<Boolean> mountFlagsSupported;
|
private final ObservableValue<Boolean> mountFlagsSupported;
|
||||||
private final ObservableValue<Boolean> readonlySupported;
|
private final ObservableValue<Boolean> readonlySupported;
|
||||||
private final ObservableValue<Boolean> fuseRestartRequired;
|
|
||||||
private final Lazy<Application> application;
|
private final Lazy<Application> application;
|
||||||
private final List<MountService> mountProviders;
|
private final List<MountService> mountProviders;
|
||||||
public ChoiceBox<MountService> volumeTypeChoiceBox;
|
public ChoiceBox<MountService> volumeTypeChoiceBox;
|
||||||
@@ -46,7 +41,10 @@ public class VolumePreferencesController implements FxController {
|
|||||||
public Button loopbackPortApplyButton;
|
public Button loopbackPortApplyButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VolumePreferencesController(Settings settings, Lazy<Application> application, List<MountService> mountProviders, @Named("FUPFMS") AtomicReference<MountService> firstUsedProblematicFuseMountService, ResourceBundle resourceBundle) {
|
VolumePreferencesController(Settings settings, //
|
||||||
|
Lazy<Application> application, //
|
||||||
|
List<MountService> mountProviders, //
|
||||||
|
ResourceBundle resourceBundle) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.mountProviders = mountProviders;
|
this.mountProviders = mountProviders;
|
||||||
@@ -54,17 +52,11 @@ public class VolumePreferencesController implements FxController {
|
|||||||
|
|
||||||
var fallbackProvider = mountProviders.stream().findFirst().orElse(null);
|
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.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 = 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.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.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
|
||||||
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
|
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
|
||||||
this.readonlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
|
this.readonlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
|
||||||
this.fuseRestartRequired = selectedMountService.map(s -> {//
|
|
||||||
return firstUsedProblematicFuseMountService.get() != null //
|
|
||||||
&& MountModule.isProblematicFuseService(s) //
|
|
||||||
&& !firstUsedProblematicFuseMountService.get().equals(s);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
@@ -101,12 +93,12 @@ public class VolumePreferencesController implements FxController {
|
|||||||
|
|
||||||
/* Property Getters */
|
/* Property Getters */
|
||||||
|
|
||||||
public BooleanExpression loopbackPortSupportedProperty() {
|
public ObservableValue<Boolean> loopbackPortSupportedProperty() {
|
||||||
return loopbackPortSupported;
|
return loopbackPortSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoopbackPortSupported() {
|
public boolean isLoopbackPortSupported() {
|
||||||
return loopbackPortSupported.get();
|
return loopbackPortSupported.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableValue<Boolean> readonlySupportedProperty() {
|
public ObservableValue<Boolean> readonlySupportedProperty() {
|
||||||
@@ -141,14 +133,6 @@ public class VolumePreferencesController implements FxController {
|
|||||||
return mountFlagsSupported.getValue();
|
return mountFlagsSupported.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableValue<Boolean> fuseRestartRequiredProperty() {
|
|
||||||
return fuseRestartRequired;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getFuseRestartRequired() {
|
|
||||||
return fuseRestartRequired.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helpers */
|
/* Helpers */
|
||||||
|
|
||||||
private class MountServiceConverter extends StringConverter<MountService> {
|
private class MountServiceConverter extends StringConverter<MountService> {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.cryptomator.ui.sharevault;
|
||||||
|
|
||||||
|
import dagger.BindsInstance;
|
||||||
|
import dagger.Lazy;
|
||||||
|
import dagger.Subcomponent;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
@ShareVaultScoped
|
||||||
|
@Subcomponent(modules = {ShareVaultModule.class})
|
||||||
|
public interface ShareVaultComponent {
|
||||||
|
|
||||||
|
@ShareVaultWindow
|
||||||
|
Stage window();
|
||||||
|
|
||||||
|
@FxmlScene(FxmlFile.SHARE_VAULT)
|
||||||
|
Lazy<Scene> scene();
|
||||||
|
|
||||||
|
default void showShareVaultWindow(){
|
||||||
|
Stage stage = window();
|
||||||
|
stage.setScene(scene().get());
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subcomponent.Factory
|
||||||
|
interface Factory {
|
||||||
|
ShareVaultComponent create(@BindsInstance @ShareVaultWindow Vault vault);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package org.cryptomator.ui.sharevault;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
@ShareVaultScoped
|
||||||
|
public class ShareVaultController implements FxController {
|
||||||
|
|
||||||
|
private static final String SCHEME_PREFIX = "hub+";
|
||||||
|
private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/";
|
||||||
|
private static final String BEST_PRACTICES_URL = "https://docs.cryptomator.org/en/latest/security/best-practices/#sharing-of-vaults";
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final Lazy<Application> application;
|
||||||
|
private final Vault vault;
|
||||||
|
private final Boolean hubVault;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ShareVaultController(@ShareVaultWindow Stage window, //
|
||||||
|
Lazy<Application> application, //
|
||||||
|
@ShareVaultWindow Vault vault) {
|
||||||
|
this.window = window;
|
||||||
|
this.application = application;
|
||||||
|
this.vault = vault;
|
||||||
|
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
|
||||||
|
this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void visitHub() {
|
||||||
|
application.get().getHostServices().showDocument(VISIT_HUB_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void openHub() {
|
||||||
|
application.get().getHostServices().showDocument(getHubUri(vault).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void visitBestPractices() {
|
||||||
|
application.get().getHostServices().showDocument(BEST_PRACTICES_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static URI getHubUri(Vault vault) {
|
||||||
|
try {
|
||||||
|
var keyID = new URI(vault.getVaultConfigCache().get().getKeyId().toString());
|
||||||
|
assert keyID.getScheme().startsWith(SCHEME_PREFIX);
|
||||||
|
return new URI(keyID.getScheme().substring(SCHEME_PREFIX.length()) + "://" + keyID.getHost() + "/app/vaults");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IllegalStateException("URI constructed from params known to be valid", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHubVault() {
|
||||||
|
return hubVault;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.cryptomator.ui.sharevault;
|
||||||
|
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.common.StageFactory;
|
||||||
|
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
abstract class ShareVaultModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ShareVaultWindow
|
||||||
|
@ShareVaultScoped
|
||||||
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ShareVaultWindow
|
||||||
|
@ShareVaultScoped
|
||||||
|
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
|
||||||
|
Stage stage = factory.create();
|
||||||
|
stage.setResizable(false);
|
||||||
|
stage.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
stage.setTitle(resourceBundle.getString("shareVault.title"));
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.SHARE_VAULT)
|
||||||
|
@ShareVaultScoped
|
||||||
|
static Scene provideShareVaultScene(@ShareVaultWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.SHARE_VAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(ShareVaultController.class)
|
||||||
|
abstract FxController bindShareVaultController(ShareVaultController controller);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.cryptomator.ui.sharevault;
|
||||||
|
|
||||||
|
import javax.inject.Scope;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@Scope
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@interface ShareVaultScoped {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.cryptomator.ui.sharevault;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@interface ShareVaultWindow {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ public class TrayMenuModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@TrayMenuScoped
|
@TrayMenuScoped
|
||||||
static Optional<TrayMenuController> provideSupportedKeychainAccessProviders() {
|
static Optional<TrayMenuController> provideFirstSupportedTrayMenuController() {
|
||||||
return TrayMenuController.get();
|
return TrayMenuController.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,16 +19,8 @@ import java.util.concurrent.Future;
|
|||||||
@Subcomponent(modules = {UnlockModule.class})
|
@Subcomponent(modules = {UnlockModule.class})
|
||||||
public interface UnlockComponent {
|
public interface UnlockComponent {
|
||||||
|
|
||||||
ExecutorService defaultExecutorService();
|
|
||||||
|
|
||||||
UnlockWorkflow unlockWorkflow();
|
UnlockWorkflow unlockWorkflow();
|
||||||
|
|
||||||
default Future<Boolean> startUnlockWorkflow() {
|
|
||||||
UnlockWorkflow workflow = unlockWorkflow();
|
|
||||||
defaultExecutorService().submit(workflow);
|
|
||||||
return workflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subcomponent.Factory
|
@Subcomponent.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
UnlockComponent create(@BindsInstance @UnlockWindow Vault vault, @BindsInstance @Named("unlockWindowOwner") @Nullable Stage owner);
|
UnlockComponent create(@BindsInstance @UnlockWindow Vault vault, @BindsInstance @Named("unlockWindowOwner") @Nullable Stage owner);
|
||||||
|
|||||||
@@ -81,6 +81,13 @@ abstract class UnlockModule {
|
|||||||
return fxmlLoaders.createScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT);
|
return fxmlLoaders.createScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART)
|
||||||
|
@UnlockScoped
|
||||||
|
static Scene provideRestartRequiredScene(@UnlockWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.UNLOCK_REQUIRES_RESTART);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@@ -93,4 +100,9 @@ abstract class UnlockModule {
|
|||||||
@FxControllerKey(UnlockInvalidMountPointController.class)
|
@FxControllerKey(UnlockInvalidMountPointController.class)
|
||||||
abstract FxController bindUnlockInvalidMountPointController(UnlockInvalidMountPointController controller);
|
abstract FxController bindUnlockInvalidMountPointController(UnlockInvalidMountPointController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(UnlockRequiresRestartController.class)
|
||||||
|
abstract FxController bindUnlockRequiresRestartController(UnlockRequiresRestartController controller);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
|
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
@UnlockScoped
|
||||||
|
public class UnlockRequiresRestartController implements FxController {
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
private final FxApplicationWindows appWindows;
|
||||||
|
private final Vault vault;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UnlockRequiresRestartController(@UnlockWindow Stage window, //
|
||||||
|
ResourceBundle resourceBundle, //
|
||||||
|
FxApplicationWindows appWindows, //
|
||||||
|
@UnlockWindow Vault vault) {
|
||||||
|
this.window = window;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.appWindows = appWindows;
|
||||||
|
this.vault = vault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize() {
|
||||||
|
window.setTitle(String.format(resourceBundle.getString("unlock.error.title"), vault.getDisplayName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void closeAndOpenVaultOptions() {
|
||||||
|
appWindows.showVaultOptionsWindow(vault, SelectedVaultOptionsTab.MOUNT);
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.cryptomator.ui.unlock;
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.mount.ConflictingMountServiceException;
|
||||||
import org.cryptomator.common.mount.IllegalMountPointException;
|
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;
|
||||||
@@ -11,6 +11,7 @@ import org.cryptomator.ui.common.FxmlFile;
|
|||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
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.fxapp.PrimaryStage;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -19,7 +20,9 @@ import javax.inject.Inject;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.geometry.Rectangle2D;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Screen;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -29,51 +32,56 @@ import java.io.IOException;
|
|||||||
* This class runs the unlock process and controls when to display which UI.
|
* This class runs the unlock process and controls when to display which UI.
|
||||||
*/
|
*/
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
public class UnlockWorkflow extends Task<Boolean> {
|
public class UnlockWorkflow extends Task<Void> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockWorkflow.class);
|
private static final Logger LOG = LoggerFactory.getLogger(UnlockWorkflow.class);
|
||||||
|
|
||||||
|
private final Stage mainWindow;
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
private final Lazy<Scene> successScene;
|
private final Lazy<Scene> successScene;
|
||||||
private final Lazy<Scene> invalidMountPointScene;
|
private final Lazy<Scene> invalidMountPointScene;
|
||||||
|
private final Lazy<Scene> restartRequiredScene;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
private final KeyLoadingStrategy keyLoadingStrategy;
|
private final KeyLoadingStrategy keyLoadingStrategy;
|
||||||
private final ObjectProperty<IllegalMountPointException> illegalMountPointException;
|
private final ObjectProperty<IllegalMountPointException> illegalMountPointException;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException) {
|
UnlockWorkflow(@PrimaryStage Stage mainWindow, //
|
||||||
|
@UnlockWindow Stage window, //
|
||||||
|
@UnlockWindow Vault vault, //
|
||||||
|
VaultService vaultService, //
|
||||||
|
@FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, //
|
||||||
|
@FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, //
|
||||||
|
@FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART) Lazy<Scene> restartRequiredScene, //
|
||||||
|
FxApplicationWindows appWindows, //
|
||||||
|
@UnlockWindow KeyLoadingStrategy keyLoadingStrategy, //
|
||||||
|
@UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
this.invalidMountPointScene = invalidMountPointScene;
|
this.invalidMountPointScene = invalidMountPointScene;
|
||||||
|
this.restartRequiredScene = restartRequiredScene;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.keyLoadingStrategy = keyLoadingStrategy;
|
this.keyLoadingStrategy = keyLoadingStrategy;
|
||||||
this.illegalMountPointException = illegalMountPointException;
|
this.illegalMountPointException = illegalMountPointException;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean call() throws InterruptedException, IOException, CryptoException, MountFailedException {
|
protected Void call() throws InterruptedException, IOException, CryptoException, MountFailedException {
|
||||||
try {
|
|
||||||
attemptUnlock();
|
|
||||||
return true;
|
|
||||||
} catch (UnlockCancelledException e) {
|
|
||||||
cancel(false); // set Tasks state to cancelled
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void attemptUnlock() throws IOException, CryptoException, MountFailedException {
|
|
||||||
try {
|
try {
|
||||||
keyLoadingStrategy.use(vault::unlock);
|
keyLoadingStrategy.use(vault::unlock);
|
||||||
|
return null;
|
||||||
|
} catch (UnlockCancelledException e) {
|
||||||
|
cancel(false); // set Tasks state to cancelled
|
||||||
|
return null;
|
||||||
|
} catch (IOException | RuntimeException | MountFailedException e) {
|
||||||
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Throwables.propagateIfPossible(e, IOException.class);
|
throw new IllegalStateException("Unexpected exception type", e);
|
||||||
Throwables.propagateIfPossible(e, CryptoException.class);
|
|
||||||
Throwables.propagateIfPossible(e, IllegalMountPointException.class);
|
|
||||||
Throwables.propagateIfPossible(e, MountFailedException.class);
|
|
||||||
throw new IllegalStateException("unexpected exception type", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +93,13 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleConflictingMountServiceException() {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
window.setScene(restartRequiredScene.get());
|
||||||
|
window.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void handleGenericError(Throwable e) {
|
private void handleGenericError(Throwable e) {
|
||||||
LOG.error("Unlock failed for technical reasons.", e);
|
LOG.error("Unlock failed for technical reasons.", e);
|
||||||
appWindows.showErrorWindow(e, window, null);
|
appWindows.showErrorWindow(e, window, null);
|
||||||
@@ -98,6 +113,19 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
case ASK -> Platform.runLater(() -> {
|
case ASK -> Platform.runLater(() -> {
|
||||||
window.setScene(successScene.get());
|
window.setScene(successScene.get());
|
||||||
window.show();
|
window.show();
|
||||||
|
double x = mainWindow.getX() + (mainWindow.getWidth() - window.getWidth()) / 2;
|
||||||
|
double y = mainWindow.getY() + (mainWindow.getHeight() - window.getHeight()) / 2;
|
||||||
|
if(!mainWindow.isShowing()) {
|
||||||
|
Screen screen = Screen.getScreensForRectangle(mainWindow.getX(), mainWindow.getY(), mainWindow.getWidth(), mainWindow.getHeight())
|
||||||
|
.stream()
|
||||||
|
.findFirst()
|
||||||
|
.orElse(Screen.getPrimary());
|
||||||
|
Rectangle2D bounds = screen.getVisualBounds();
|
||||||
|
x = bounds.getMinX() + (bounds.getWidth() - window.getWidth()) / 2;
|
||||||
|
y = bounds.getMinY() + (bounds.getHeight() - window.getHeight()) / 2;
|
||||||
|
}
|
||||||
|
window.setX(x);
|
||||||
|
window.setY(y);
|
||||||
});
|
});
|
||||||
case REVEAL -> {
|
case REVEAL -> {
|
||||||
Platform.runLater(window::close);
|
Platform.runLater(window::close);
|
||||||
@@ -113,10 +141,10 @@ 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();
|
||||||
if(throwable instanceof IllegalMountPointException impe) {
|
switch (throwable) {
|
||||||
handleIllegalMountPointError(impe);
|
case IllegalMountPointException e -> handleIllegalMountPointError(e);
|
||||||
} else {
|
case ConflictingMountServiceException _ -> handleConflictingMountServiceException();
|
||||||
handleGenericError(throwable);
|
default -> handleGenericError(throwable);
|
||||||
}
|
}
|
||||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
package org.cryptomator.ui.vaultoptions;
|
package org.cryptomator.ui.vaultoptions;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.cryptomator.common.mount.ActualMountService;
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.ObservableUtil;
|
||||||
|
import org.cryptomator.common.mount.Mounter;
|
||||||
import org.cryptomator.common.mount.WindowsDriveLetters;
|
import org.cryptomator.common.mount.WindowsDriveLetters;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
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.fxapp.FxApplicationWindows;
|
||||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
|
import org.cryptomator.ui.preferences.VolumePreferencesController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
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.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ChoiceBox;
|
import javafx.scene.control.ChoiceBox;
|
||||||
import javafx.scene.control.RadioButton;
|
import javafx.scene.control.RadioButton;
|
||||||
@@ -26,6 +33,8 @@ import java.io.File;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -36,14 +45,21 @@ public class MountOptionsController implements FxController {
|
|||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
private final Lazy<Application> application;
|
||||||
|
|
||||||
private final ObservableValue<String> defaultMountFlags;
|
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<Boolean> defaultMountServiceSelected;
|
||||||
private final ObservableValue<String> directoryPath;
|
private final ObservableValue<String> directoryPath;
|
||||||
private final FxApplicationWindows applicationWindows;
|
private final FxApplicationWindows applicationWindows;
|
||||||
|
private final List<MountService> mountProviders;
|
||||||
|
private final ObservableValue<MountService> defaultMountService;
|
||||||
|
private final ObservableValue<MountService> selectedMountService;
|
||||||
|
private final ObservableValue<Boolean> selectedMountServiceRequiresRestart;
|
||||||
|
private final ObservableValue<Boolean> loopbackPortChangeable;
|
||||||
|
|
||||||
|
|
||||||
//-- FXML objects --
|
//-- FXML objects --
|
||||||
@@ -56,30 +72,58 @@ public class MountOptionsController implements FxController {
|
|||||||
public RadioButton mountPointDirBtn;
|
public RadioButton mountPointDirBtn;
|
||||||
public TextField directoryPathField;
|
public TextField directoryPathField;
|
||||||
public ChoiceBox<Path> driveLetterSelection;
|
public ChoiceBox<Path> driveLetterSelection;
|
||||||
|
public ChoiceBox<MountService> vaultVolumeTypeChoiceBox;
|
||||||
|
public TextField vaultLoopbackPortField;
|
||||||
|
public Button vaultLoopbackPortApplyButton;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObservableValue<ActualMountService> mountService, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, FxApplicationWindows applicationWindows) {
|
MountOptionsController(@VaultOptionsWindow Stage window, //
|
||||||
|
@VaultOptionsWindow Vault vault, //
|
||||||
|
WindowsDriveLetters windowsDriveLetters, //
|
||||||
|
ResourceBundle resourceBundle, //
|
||||||
|
FxApplicationWindows applicationWindows, //
|
||||||
|
Lazy<Application> application, //
|
||||||
|
List<MountService> mountProviders, //
|
||||||
|
Mounter mounter, //
|
||||||
|
ObservableValue<MountService> defaultMountService) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vaultSettings = vault.getVaultSettings();
|
this.vaultSettings = vault.getVaultSettings();
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.defaultMountFlags = mountService.map(as -> {
|
this.applicationWindows = applicationWindows;
|
||||||
if (as.service().hasCapability(MountCapability.MOUNT_FLAGS)) {
|
this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString());
|
||||||
return as.service().getDefaultMountFlags();
|
this.application = application;
|
||||||
|
this.mountProviders = mountProviders;
|
||||||
|
this.defaultMountService = defaultMountService;
|
||||||
|
this.selectedMountService = Bindings.createObjectBinding(this::reselectMountService, defaultMountService, vaultSettings.mountService);
|
||||||
|
this.selectedMountServiceRequiresRestart = selectedMountService.map(mounter::isConflictingMountService);
|
||||||
|
|
||||||
|
this.defaultMountFlags = selectedMountService.map(s -> {
|
||||||
|
if (s.hasCapability(MountCapability.MOUNT_FLAGS)) {
|
||||||
|
return s.getDefaultMountFlags();
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.mountpointDirSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || as.service().hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
|
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
|
||||||
this.mountpointDriveLetterSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
|
this.defaultMountServiceSelected = ObservableUtil.mapWithDefault(vaultSettings.mountService, _ -> false, true);
|
||||||
this.mountFlagsSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_FLAGS));
|
this.readOnlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
|
||||||
this.readOnlySupported = mountService.map(as -> as.service().hasCapability(MountCapability.READ_ONLY));
|
this.mountpointDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
|
||||||
this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString());
|
this.mountpointDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
|
||||||
this.applicationWindows = applicationWindows;
|
this.loopbackPortChangeable = selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT) && vaultSettings.mountService.getValue() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MountService reselectMountService() {
|
||||||
|
var desired = vaultSettings.mountService.getValue();
|
||||||
|
var defaultMS = defaultMountService.getValue();
|
||||||
|
return mountProviders.stream().filter(s -> s.getClass().getName().equals(desired)).findFirst().orElse(defaultMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
defaultMountService.addListener((_, _, _) -> vaultVolumeTypeChoiceBox.setConverter(new MountServiceConverter()));
|
||||||
|
|
||||||
// readonly:
|
// readonly:
|
||||||
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode);
|
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode);
|
||||||
|
|
||||||
@@ -106,6 +150,20 @@ public class MountOptionsController implements FxController {
|
|||||||
mountPointToggleGroup.selectToggle(mountPointDirBtn);
|
mountPointToggleGroup.selectToggle(mountPointDirBtn);
|
||||||
}
|
}
|
||||||
mountPointToggleGroup.selectedToggleProperty().addListener(this::selectedToggleChanged);
|
mountPointToggleGroup.selectedToggleProperty().addListener(this::selectedToggleChanged);
|
||||||
|
|
||||||
|
vaultVolumeTypeChoiceBox.getItems().add(null);
|
||||||
|
vaultVolumeTypeChoiceBox.getItems().addAll(mountProviders);
|
||||||
|
vaultVolumeTypeChoiceBox.setConverter(new MountServiceConverter());
|
||||||
|
vaultVolumeTypeChoiceBox.getSelectionModel().select(isDefaultMountServiceSelected() ? null : selectedMountService.getValue());
|
||||||
|
vaultVolumeTypeChoiceBox.valueProperty().addListener((_, _, newProvider) -> {
|
||||||
|
var toSet = Optional.ofNullable(newProvider).map(nP -> nP.getClass().getName()).orElse(null);
|
||||||
|
vaultSettings.mountService.set(toSet);
|
||||||
|
});
|
||||||
|
|
||||||
|
vaultLoopbackPortField.setText(String.valueOf(vaultSettings.port.get()));
|
||||||
|
vaultLoopbackPortApplyButton.visibleProperty().bind(vaultSettings.port.asString().isNotEqualTo(vaultLoopbackPortField.textProperty()));
|
||||||
|
vaultLoopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, vaultLoopbackPortField.textProperty()).not());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -229,6 +287,26 @@ public class MountOptionsController implements FxController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void openDocs() {
|
||||||
|
application.get().getHostServices().showDocument(VolumePreferencesController.DOCS_MOUNTING_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateLoopbackPort() {
|
||||||
|
try {
|
||||||
|
int port = Integer.parseInt(vaultLoopbackPortField.getText());
|
||||||
|
return port == 0 // choose port automatically
|
||||||
|
|| port >= VolumePreferencesController.MIN_PORT && port <= VolumePreferencesController.MAX_PORT; // port within range
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doChangeLoopbackPort() {
|
||||||
|
if (validateLoopbackPort()) {
|
||||||
|
vaultSettings.port.set(Integer.parseInt(vaultLoopbackPortField.getText()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
private static class NoDirSelectedException extends Exception {}
|
private static class NoDirSelectedException extends Exception {}
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
@@ -243,6 +321,14 @@ public class MountOptionsController implements FxController {
|
|||||||
return mountFlagsSupported.getValue();
|
return mountFlagsSupported.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> defaultMountServiceSelectedProperty() {
|
||||||
|
return defaultMountServiceSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefaultMountServiceSelected() {
|
||||||
|
return defaultMountServiceSelected.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableValue<Boolean> mountpointDirSupportedProperty() {
|
public ObservableValue<Boolean> mountpointDirSupportedProperty() {
|
||||||
return mountpointDirSupported;
|
return mountpointDirSupported;
|
||||||
}
|
}
|
||||||
@@ -274,4 +360,37 @@ public class MountOptionsController implements FxController {
|
|||||||
public String getDirectoryPath() {
|
public String getDirectoryPath() {
|
||||||
return directoryPath.getValue();
|
return directoryPath.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> selectedMountServiceRequiresRestartProperty() {
|
||||||
|
return selectedMountServiceRequiresRestart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSelectedMountServiceRequiresRestart() {
|
||||||
|
return selectedMountServiceRequiresRestart.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> loopbackPortChangeableProperty() {
|
||||||
|
return loopbackPortChangeable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoopbackPortChangeable() {
|
||||||
|
return loopbackPortChangeable.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MountServiceConverter extends StringConverter<MountService> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(MountService provider) {
|
||||||
|
if (provider == null) {
|
||||||
|
return String.format(resourceBundle.getString("vaultOptions.mount.volumeType.default"), defaultMountService.getValue().displayName());
|
||||||
|
} else {
|
||||||
|
return provider.displayName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MountService fromString(String string) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.cryptomator.ui.vaultoptions;
|
package org.cryptomator.ui.vaultoptions;
|
||||||
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||||
@@ -48,6 +49,10 @@ public class VaultOptionsController implements FxController {
|
|||||||
if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
|
if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
|
||||||
tabPane.getTabs().remove(hubTab);
|
tabPane.getTabs().remove(hubTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vault.stateProperty().addListener(observable -> {
|
||||||
|
tabPane.setDisable(vault.getState().equals(VaultState.Value.UNLOCKED));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectChosenTab() {
|
private void selectChosenTab() {
|
||||||
|
|||||||
@@ -936,3 +936,16 @@
|
|||||||
-fx-padding: 0.5px;
|
-fx-padding: 0.5px;
|
||||||
-fx-background-color: CONTROL_BORDER_NORMAL;
|
-fx-background-color: CONTROL_BORDER_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* Ad box *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.ad-box {
|
||||||
|
-fx-padding: 12px;
|
||||||
|
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||||
|
-fx-background-insets: 0, 1px;
|
||||||
|
-fx-background-radius: 4px;
|
||||||
|
}
|
||||||
@@ -935,3 +935,16 @@
|
|||||||
-fx-padding: 0.5px;
|
-fx-padding: 0.5px;
|
||||||
-fx-background-color: CONTROL_BORDER_NORMAL;
|
-fx-background-color: CONTROL_BORDER_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* Ad box *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.ad-box {
|
||||||
|
-fx-padding: 12px;
|
||||||
|
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||||
|
-fx-background-insets: 0, 1px;
|
||||||
|
-fx-background-radius: 4px;
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
<?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.FontAwesome5IconView?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.ButtonBar?>
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.RadioButton?>
|
<?import javafx.scene.control.RadioButton?>
|
||||||
|
<?import javafx.scene.control.ScrollPane?>
|
||||||
<?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?>
|
||||||
@@ -29,18 +31,26 @@
|
|||||||
<children>
|
<children>
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
<VBox fx:id="radioButtonVBox" spacing="6">
|
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
|
||||||
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
|
<ScrollPane hbarPolicy="NEVER">
|
||||||
<!-- PLACEHOLDER, more radio buttons are added programmatically via controller -->
|
<VBox fx:id="radioButtonVBox" spacing="6">
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT">
|
<!-- PLACEHOLDER, more radio buttons are added programmatically via controller -->
|
||||||
<RadioButton fx:id="customRadioButton" toggleGroup="${locationPresetsToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
|
<HBox fx:id="customLocationRadioBtn" spacing="12" alignment="CENTER_LEFT">
|
||||||
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
|
<RadioButton fx:id="customRadioButton" toggleGroup="${locationPresetsToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
|
||||||
<graphic>
|
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
|
||||||
<FontAwesome5IconView glyph="FOLDER_OPEN"/>
|
<graphic>
|
||||||
</graphic>
|
<FontAwesome5IconView glyph="FOLDER_OPEN"/>
|
||||||
</Button>
|
</graphic>
|
||||||
</HBox>
|
</Button>
|
||||||
</VBox>
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
</ScrollPane>
|
||||||
|
<Region prefHeight="2"/>
|
||||||
|
<Label wrapText="true" text="%addvaultwizard.new.locationLoading" visible="${controller.loadingPresetLocations}" managed="${controller.loadingPresetLocations}" graphicTextGap="8">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5Spinner/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
|
||||||
<Region prefHeight="12" VBox.vgrow="NEVER"/>
|
<Region prefHeight="12" VBox.vgrow="NEVER"/>
|
||||||
|
|
||||||
|
|||||||
@@ -9,86 +9,97 @@
|
|||||||
<?import javafx.scene.control.Hyperlink?>
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.TextArea?>
|
<?import javafx.scene.control.TextArea?>
|
||||||
|
<?import javafx.scene.Group?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.Region?>
|
<?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?>
|
||||||
<?import javafx.scene.shape.Circle?>
|
<?import javafx.scene.shape.Circle?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.error.ErrorController"
|
fx:controller="org.cryptomator.ui.error.ErrorController"
|
||||||
|
minWidth="450"
|
||||||
|
minHeight="450"
|
||||||
prefWidth="450"
|
prefWidth="450"
|
||||||
prefHeight="450"
|
prefHeight="450"
|
||||||
spacing="18"
|
spacing="12">
|
||||||
alignment="TOP_CENTER">
|
|
||||||
<padding>
|
<padding>
|
||||||
<Insets topRightBottomLeft="24"/>
|
<Insets topRightBottomLeft="24"/>
|
||||||
</padding>
|
</padding>
|
||||||
<children>
|
<Group>
|
||||||
<HBox spacing="12" VBox.vgrow="NEVER">
|
<StackPane>
|
||||||
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
<padding>
|
||||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
<Insets topRightBottomLeft="6"/>
|
||||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
|
</padding>
|
||||||
</StackPane>
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
<VBox spacing="6" HBox.hgrow="ALWAYS">
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
|
||||||
<FormattedLabel styleClass="label-extra-large" format="%error.message" arg1="${controller.errorCode}"/>
|
</StackPane>
|
||||||
<VBox visible="${!controller.askedForLookupDatabasePermission}" managed="${!controller.askedForLookupDatabasePermission}">
|
</Group>
|
||||||
<Label text="%error.lookupPermissionMessage" wrapText="true"/>
|
<VBox HBox.hgrow="ALWAYS">
|
||||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
<FormattedLabel styleClass="label-extra-large" format="%error.message" arg1="${controller.errorCode}">
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+NY">
|
<padding>
|
||||||
<buttons>
|
<Insets bottom="6" top="6"/>
|
||||||
<Button text="%error.dismiss" ButtonBar.buttonData="NO" onAction="#dismiss"/>
|
</padding>
|
||||||
<Button text="%error.lookUpSolution" ButtonBar.buttonData="YES" defaultButton="true" onAction="#lookUpSolution"/>
|
</FormattedLabel>
|
||||||
</buttons>
|
|
||||||
</ButtonBar>
|
|
||||||
</VBox>
|
|
||||||
<VBox visible="${controller.askedForLookupDatabasePermission}" managed="${controller.askedForLookupDatabasePermission}">
|
|
||||||
<FontAwesome5Spinner glyphSize="24" visible="${controller.isLoadingHttpResponse}" managed="${controller.isLoadingHttpResponse}"/>
|
|
||||||
<VBox visible="${!controller.isLoadingHttpResponse}" managed="${!controller.isLoadingHttpResponse}">
|
|
||||||
<Label text="%error.existingSolutionDescription" wrapText="true" visible="${controller.errorSolutionFound}" managed="${controller.errorSolutionFound}"/>
|
|
||||||
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.solution" onAction="#showSolution" contentDisplay="LEFT" visible="${controller.errorSolutionFound}" managed="${controller.errorSolutionFound}">
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
|
|
||||||
</graphic>
|
|
||||||
</Hyperlink>
|
|
||||||
<Label text="%error.description" wrapText="true" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}"/>
|
|
||||||
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.lookup" onAction="#searchError" contentDisplay="LEFT" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}">
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
|
|
||||||
</graphic>
|
|
||||||
</Hyperlink>
|
|
||||||
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.report" onAction="#reportError" contentDisplay="LEFT" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}">
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
|
|
||||||
</graphic>
|
|
||||||
</Hyperlink>
|
|
||||||
</VBox>
|
|
||||||
</VBox>
|
|
||||||
</VBox>
|
|
||||||
</HBox>
|
|
||||||
<VBox spacing="6" VBox.vgrow="ALWAYS">
|
|
||||||
<HBox>
|
|
||||||
<Label text="%error.technicalDetails"/>
|
|
||||||
<Region HBox.hgrow="ALWAYS"/>
|
|
||||||
<Hyperlink styleClass="hyperlink-underline" text="%generic.button.copy" onAction="#copyDetails" contentDisplay="LEFT" visible="${!controller.copiedDetails}" managed="${!controller.copiedDetails}">
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="CLIPBOARD" glyphSize="12"/>
|
|
||||||
</graphic>
|
|
||||||
</Hyperlink>
|
|
||||||
<Hyperlink styleClass="hyperlink-underline" text="%generic.button.copied" onAction="#copyDetails" contentDisplay="LEFT" visible="${controller.copiedDetails}" managed="${controller.copiedDetails}">
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="CHECK" glyphSize="12"/>
|
|
||||||
</graphic>
|
|
||||||
</Hyperlink>
|
|
||||||
</HBox>
|
|
||||||
<TextArea VBox.vgrow="ALWAYS" text="${controller.detailText}" prefRowCount="5" editable="false"/>
|
|
||||||
</VBox>
|
|
||||||
|
|
||||||
|
<VBox visible="${!controller.askedForLookupDatabasePermission}" managed="${!controller.askedForLookupDatabasePermission}">
|
||||||
|
<Label text="%error.lookupPermissionMessage" wrapText="true"/>
|
||||||
|
<Region minHeight="12"/>
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="+NY">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%error.dismiss" ButtonBar.buttonData="NO" onAction="#dismiss"/>
|
||||||
|
<Button text="%error.lookUpSolution" ButtonBar.buttonData="YES" defaultButton="true" onAction="#lookUpSolution"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
<VBox visible="${controller.askedForLookupDatabasePermission}" managed="${controller.askedForLookupDatabasePermission}">
|
||||||
|
<FontAwesome5Spinner glyphSize="24" visible="${controller.isLoadingHttpResponse}" managed="${controller.isLoadingHttpResponse}"/>
|
||||||
|
<VBox visible="${!controller.isLoadingHttpResponse}" managed="${!controller.isLoadingHttpResponse}">
|
||||||
|
<Label text="%error.existingSolutionDescription" wrapText="true" visible="${controller.errorSolutionFound}" managed="${controller.errorSolutionFound}"/>
|
||||||
|
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.solution" onAction="#showSolution" contentDisplay="LEFT" visible="${controller.errorSolutionFound}" managed="${controller.errorSolutionFound}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
|
||||||
|
</graphic>
|
||||||
|
</Hyperlink>
|
||||||
|
<Label text="%error.description" wrapText="true" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}"/>
|
||||||
|
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.lookup" onAction="#searchError" contentDisplay="LEFT" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
|
||||||
|
</graphic>
|
||||||
|
</Hyperlink>
|
||||||
|
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.report" onAction="#reportError" contentDisplay="LEFT" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
|
||||||
|
</graphic>
|
||||||
|
</Hyperlink>
|
||||||
|
</VBox>
|
||||||
|
</VBox>
|
||||||
|
<Region minHeight="12"/>
|
||||||
|
<HBox>
|
||||||
|
<padding>
|
||||||
|
<Insets top="6" bottom="6"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="%error.technicalDetails"/>
|
||||||
|
<Region HBox.hgrow="ALWAYS"/>
|
||||||
|
<Hyperlink styleClass="hyperlink-underline" text="%generic.button.copy" onAction="#copyDetails" contentDisplay="LEFT" visible="${!controller.copiedDetails}" managed="${!controller.copiedDetails}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="CLIPBOARD" glyphSize="12"/>
|
||||||
|
</graphic>
|
||||||
|
</Hyperlink>
|
||||||
|
<Hyperlink styleClass="hyperlink-underline" text="%generic.button.copied" onAction="#copyDetails" contentDisplay="LEFT" visible="${controller.copiedDetails}" managed="${controller.copiedDetails}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="CHECK" glyphSize="12"/>
|
||||||
|
</graphic>
|
||||||
|
</Hyperlink>
|
||||||
|
</HBox>
|
||||||
|
<TextArea VBox.vgrow="ALWAYS" text="${controller.detailText}" prefRowCount="5" editable="false"/>
|
||||||
|
|
||||||
|
<Region minHeight="18"/>
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="B+C">
|
<ButtonBar buttonMinWidth="120" buttonOrder="B+C">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back" visible="${controller.previousScenePresent}"/>
|
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back" visible="${controller.previousScenePresent}"/>
|
||||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#close"/>
|
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#close"/>
|
||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
</children>
|
</VBox>
|
||||||
</VBox>
|
</HBox>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<Insets bottom="6" top="6"/>
|
<Insets bottom="6" top="6"/>
|
||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</Label>
|
||||||
<Label text="%hub.register.description" wrapText="true"/>
|
<Label text="%hub.register.legacy.description" wrapText="true"/>
|
||||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets top="12"/>
|
<Insets top="12"/>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
|
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
<HBox alignment="TOP_RIGHT">
|
<HBox alignment="TOP_RIGHT">
|
||||||
<Label text="%hub.register.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
|
<Label text="%hub.register.legacy.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets top="6"/>
|
<Insets top="6"/>
|
||||||
</padding>
|
</padding>
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.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?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
fx:controller="org.cryptomator.ui.keyloading.hub.LegacyRegisterSuccessController"
|
||||||
|
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="CHECK" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
</Group>
|
||||||
|
<VBox HBox.hgrow="ALWAYS">
|
||||||
|
<Label styleClass="label-large" text="%hub.registerSuccess.message" wrapText="true" textAlignment="LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" top="6"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<Label text="%hub.registerSuccess.legacy.description" wrapText="true"/>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#close"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
+2
-11
@@ -46,7 +46,7 @@
|
|||||||
<padding>
|
<padding>
|
||||||
<Insets top="12"/>
|
<Insets top="12"/>
|
||||||
</padding>
|
</padding>
|
||||||
<Label text="%hub.register.setupCodeLabel" labelFor="$setupCodeField"/>
|
<Label text="Account Key" labelFor="$setupCodeField"/>
|
||||||
<TextField fx:id="setupCodeField" HBox.hgrow="ALWAYS"/>
|
<TextField fx:id="setupCodeField" HBox.hgrow="ALWAYS"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
@@ -57,16 +57,7 @@
|
|||||||
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
|
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
<HBox alignment="TOP_RIGHT">
|
<HBox alignment="TOP_RIGHT">
|
||||||
<Label text="%hub.register.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" managed="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
|
<Label text="%hub.register.invalidAccountKeyLabel" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.invalidSetupCode}" managed="${controller.invalidSetupCode}" graphicTextGap="6">
|
||||||
<padding>
|
|
||||||
<Insets top="6"/>
|
|
||||||
</padding>
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
|
||||||
</graphic>
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<Label text="%hub.register.invalidSetupCode" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.invalidSetupCode}" managed="${controller.invalidSetupCode}" graphicTextGap="6">
|
|
||||||
<padding>
|
<padding>
|
||||||
<Insets top="6"/>
|
<Insets top="6"/>
|
||||||
</padding>
|
</padding>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.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?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
fx:controller="org.cryptomator.ui.keyloading.hub.RegisterFailedController"
|
||||||
|
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.registerFailed.message" wrapText="true" textAlignment="LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" top="6"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<Label text="%hub.registerFailed.description.deviceAlreadyExists" wrapText="true"/>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#close"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<Insets bottom="6" top="6"/>
|
<Insets bottom="6" top="6"/>
|
||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</Label>
|
||||||
<Label text="%hub.registerFailed.description" wrapText="true"/>
|
<Label text="%hub.registerFailed.description.generic" wrapText="true"/>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||||
|
|||||||
@@ -41,9 +41,9 @@
|
|||||||
<Label text="%hub.registerSuccess.description" wrapText="true"/>
|
<Label text="%hub.registerSuccess.description" wrapText="true"/>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#close"/>
|
<Button text="%hub.registerSuccess.unlockBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#complete"/>
|
||||||
<!-- TODO: add request access button -->
|
<!-- TODO: add request access button -->
|
||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.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 javafx.scene.text.TextFlow?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
fx:controller="org.cryptomator.ui.keyloading.hub.RequireAccountInitController"
|
||||||
|
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="USER_COG" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
</Group>
|
||||||
|
<VBox HBox.hgrow="ALWAYS">
|
||||||
|
<Label styleClass="label-large" text="%hub.requireAccountInit.message" wrapText="true" textAlignment="LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" top="6"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<TextFlow styleClass="text-flow">
|
||||||
|
<Text text="%hub.requireAccountInit.description.0"/>
|
||||||
|
<Text text=" "/>
|
||||||
|
<Hyperlink styleClass="hyperlink-underline" text="%hub.requireAccountInit.description.1" onAction="#completeSetup"/>
|
||||||
|
<Text text="%hub.requireAccountInit.description.2"/>
|
||||||
|
</TextFlow>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#close"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</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 – 2023 Skymatic GmbH"/>
|
<Label text="© 2016 – 2024 Skymatic GmbH"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,6 @@
|
|||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<Label styleClass="label-red" text="%preferences.volume.fuseRestartRequired" visible="${controller.fuseRestartRequired}" managed="${controller.fuseRestartRequired}"/>
|
|
||||||
|
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}" managed="${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"/>
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.image.Image?>
|
||||||
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?import javafx.scene.Group?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
<?import javafx.scene.text.TextFlow?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
fx:controller="org.cryptomator.ui.sharevault.ShareVaultController"
|
||||||
|
prefWidth="540"
|
||||||
|
spacing="12">
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="12"/>
|
||||||
|
</padding>
|
||||||
|
<Group>
|
||||||
|
<StackPane>
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="6"/>
|
||||||
|
</padding>
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="INFO" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
</Group>
|
||||||
|
<VBox>
|
||||||
|
<VBox HBox.hgrow="ALWAYS" visible="${controller.hubVault}" managed="${controller.hubVault}">
|
||||||
|
<Label text="%shareVault.hub.message" styleClass="label-large" wrapText="true">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" top="6"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<Label text="%shareVault.hub.description" wrapText="true"/>
|
||||||
|
<VBox>
|
||||||
|
<padding>
|
||||||
|
<Insets left="6"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="%shareVault.hub.instruction.1" wrapText="true"/>
|
||||||
|
<Label text="%shareVault.hub.instruction.2" wrapText="true"/>
|
||||||
|
</VBox>
|
||||||
|
</VBox>
|
||||||
|
<VBox HBox.hgrow="ALWAYS" visible="${!controller.hubVault}" managed="${!controller.hubVault}">
|
||||||
|
<Label text="%shareVault.message" styleClass="label-large" wrapText="true">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" top="6"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<Label text="%shareVault.description" wrapText="true"/>
|
||||||
|
<VBox>
|
||||||
|
<padding>
|
||||||
|
<Insets left="6"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="%shareVault.instruction.1" wrapText="true"/>
|
||||||
|
<Label text="%shareVault.instruction.2" wrapText="true"/>
|
||||||
|
</VBox>
|
||||||
|
<Region minHeight="6"/>
|
||||||
|
<TextFlow styleClass="text-flow">
|
||||||
|
<Text text="%shareVault.remarkBestPractices"/>
|
||||||
|
<Text text=" "/>
|
||||||
|
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#visitBestPractices">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%shareVault.docsTooltip" showDelay="100ms"/>
|
||||||
|
</tooltip>
|
||||||
|
</Hyperlink>
|
||||||
|
</TextFlow>
|
||||||
|
<Region minHeight="12"/>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="6" styleClass="ad-box">
|
||||||
|
<VBox spacing="6" alignment="CENTER_LEFT">
|
||||||
|
<ImageView HBox.hgrow="ALWAYS" fitWidth="180" preserveRatio="true" cache="true">
|
||||||
|
<Image url="@../img/hub_logo.png"/>
|
||||||
|
</ImageView>
|
||||||
|
<Label text="%shareVault.hubAd.description" style="-fx-font-weight: bold;" wrapText="true"/>
|
||||||
|
<VBox spacing="6" alignment="CENTER_LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets left="6"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="%shareVault.hubAd.keyManagement" wrapText="true"/>
|
||||||
|
<Label text="%shareVault.hubAd.authentication" wrapText="true"/>
|
||||||
|
<Label text="%shareVault.hubAd.encryption" wrapText="true"/>
|
||||||
|
</VBox>
|
||||||
|
</VBox>
|
||||||
|
<Region HBox.hgrow="ALWAYS"/>
|
||||||
|
<ImageView HBox.hgrow="ALWAYS" fitWidth="180" preserveRatio="true" cache="true">
|
||||||
|
<Image url="@../img/group-magic.png"/>
|
||||||
|
</ImageView>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#close"/>
|
||||||
|
<Button text="%shareVault.hub.openHub" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#openHub" visible="${controller.hubVault}" managed="${controller.hubVault}"/>
|
||||||
|
<Button text="%shareVault.visitHub" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#visitHub" visible="${!controller.hubVault}" managed="${!controller.hubVault}"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?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?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
fx:controller="org.cryptomator.ui.unlock.UnlockRequiresRestartController"
|
||||||
|
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-red" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="TIMES" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
</Group>
|
||||||
|
<VBox HBox.hgrow="ALWAYS">
|
||||||
|
<Label styleClass="label-large" text="%unlock.error.restartRequired.message" wrapText="true" textAlignment="LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" top="6"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Label text="%unlock.error.restartRequired.description" 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" cancelButton="true" onAction="#close"/>
|
||||||
|
<Button text="%main.vaultlist.contextMenu.vaultoptions" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#closeAndOpenVaultOptions">
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%main.vaultlist.contextMenu.vaultoptions"/>
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
@@ -24,14 +24,17 @@
|
|||||||
<FontAwesome5IconView glyph="KEY" glyphSize="15"/>
|
<FontAwesome5IconView glyph="KEY" glyphSize="15"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
<Hyperlink text="%main.vaultDetail.passwordSavedInKeychain" visible="${controller.passwordSaved}" onAction="#showKeyVaultOptions">
|
<Hyperlink text="%main.vaultDetail.passwordSavedInKeychain" visible="${controller.passwordSaved}" managed="${controller.passwordSaved}" onAction="#showKeyVaultOptions">
|
||||||
<graphic>
|
<graphic>
|
||||||
<FontAwesome5IconView glyph="LOCK"/>
|
<FontAwesome5IconView glyph="LOCK"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
|
<Button text="%main.vaultDetail.share" minWidth="120" onAction="#share">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="SHARE" glyphSize="15"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
<HBox alignment="BOTTOM_RIGHT">
|
<HBox alignment="BOTTOM_RIGHT">
|
||||||
<Button text="%main.vaultDetail.optionsBtn" minWidth="120" onAction="#showVaultOptions">
|
<Button text="%main.vaultDetail.optionsBtn" minWidth="120" onAction="#showVaultOptions">
|
||||||
<graphic>
|
<graphic>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?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.FontAwesome5IconView?>
|
||||||
|
<?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.CheckBox?>
|
<?import javafx.scene.control.CheckBox?>
|
||||||
@@ -10,9 +11,9 @@
|
|||||||
<?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.control.Tooltip?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?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"
|
||||||
@@ -24,11 +25,35 @@
|
|||||||
<Insets topRightBottomLeft="12"/>
|
<Insets topRightBottomLeft="12"/>
|
||||||
</padding>
|
</padding>
|
||||||
<children>
|
<children>
|
||||||
<TextFlow>
|
<HBox spacing="12" alignment="CENTER_LEFT">
|
||||||
<Label text="%vaultOptions.mount.info"/>
|
<Label text="%vaultOptions.mount.volume.type"/>
|
||||||
<Label text=" "/>
|
<ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/>
|
||||||
<Hyperlink styleClass="hyperlink-underline" text="%vaultOptions.mount.linkToPreferences" onAction="#openVolumePreferences" wrapText="true"/>
|
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}">
|
||||||
</TextFlow>
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="COGS" styleClass="glyph-icon-muted"/>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%vaultOptions.mount.info" showDelay="100ms"/>
|
||||||
|
</tooltip>
|
||||||
|
</Hyperlink>
|
||||||
|
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" visible="${!controller.defaultMountServiceSelected}" managed="${!controller.defaultMountServiceSelected}">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%preferences.volume.docsTooltip" showDelay="100ms"/>
|
||||||
|
</tooltip>
|
||||||
|
</Hyperlink>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Label styleClass="label-red" text="%vaultOptions.mount.volumeType.restartRequired" visible="${controller.selectedMountServiceRequiresRestart}" managed="${controller.selectedMountServiceRequiresRestart}"/>
|
||||||
|
|
||||||
|
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortChangeable}" managed="${controller.loopbackPortChangeable}">
|
||||||
|
<Label text="%vaultOptions.mount.volume.tcp.port"/>
|
||||||
|
<NumericTextField fx:id="vaultLoopbackPortField"/>
|
||||||
|
<Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
<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}">
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ addvaultwizard.new.nameInstruction=Choose a name for the vault
|
|||||||
addvaultwizard.new.namePrompt=Vault Name
|
addvaultwizard.new.namePrompt=Vault Name
|
||||||
### Location
|
### Location
|
||||||
addvaultwizard.new.locationInstruction=Where should Cryptomator store the encrypted files of your vault?
|
addvaultwizard.new.locationInstruction=Where should Cryptomator store the encrypted files of your vault?
|
||||||
|
addvaultwizard.new.locationLoading=Checking local filesystem for default cloud storage directories…
|
||||||
addvaultwizard.new.locationLabel=Storage location
|
addvaultwizard.new.locationLabel=Storage location
|
||||||
addvaultwizard.new.locationPrompt=…
|
addvaultwizard.new.locationPrompt=…
|
||||||
addvaultwizard.new.directoryPickerLabel=Custom location
|
addvaultwizard.new.directoryPickerLabel=Custom location
|
||||||
@@ -142,6 +143,9 @@ unlock.error.customPath.description.hideawayNotDir=The temporary, hidden file "%
|
|||||||
unlock.error.customPath.description.couldNotBeCleaned=Your vault could not be mounted to the path "%s". Please try again or choose a different path.
|
unlock.error.customPath.description.couldNotBeCleaned=Your vault could not be mounted to the path "%s". Please try again or choose a different path.
|
||||||
unlock.error.customPath.description.notEmptyDir=The custom mount path "%s" is not an empty folder. Please choose an empty folder and try again.
|
unlock.error.customPath.description.notEmptyDir=The custom mount path "%s" is not an empty folder. Please choose an empty folder and try again.
|
||||||
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %2$s
|
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %2$s
|
||||||
|
unlock.error.restartRequired.message=Unable to unlock vault
|
||||||
|
unlock.error.restartRequired.description=Change the volume type in vault options or restart Cryptomator.
|
||||||
|
unlock.error.title=Unlock "%s" failed
|
||||||
## Hub
|
## Hub
|
||||||
hub.noKeychain.message=Unable to access device key
|
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.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.
|
||||||
@@ -155,21 +159,30 @@ hub.receive.message=Processing response…
|
|||||||
hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait.
|
hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait.
|
||||||
### Register Device
|
### Register Device
|
||||||
hub.register.message=New Device
|
hub.register.message=New Device
|
||||||
hub.register.description=This is the first Hub access from this device. Please authorize it using your setup code.
|
hub.register.description=This is the first Hub access from this device. Please register it using your Account Key.
|
||||||
hub.register.nameLabel=Device Name
|
hub.register.nameLabel=Device Name
|
||||||
hub.register.setupCodeLabel=Setup Code
|
hub.register.invalidAccountKeyLabel=Invalid Account Key
|
||||||
hub.register.invalidSetupCode=Invalid Setup Code
|
hub.register.registerBtn=Register
|
||||||
hub.register.occupiedMsg=Name already in use
|
### Register Device Legacy
|
||||||
hub.register.registerBtn=Confirm
|
hub.register.legacy.occupiedMsg=Name already in use
|
||||||
|
hub.register.legacy.description=This is the first Hub access from this device. Please register it.
|
||||||
### Registration Success
|
### Registration Success
|
||||||
hub.registerSuccess.message=Device named
|
hub.registerSuccess.message=Device registered
|
||||||
hub.registerSuccess.description=To access the vault, your device needs to be authorized by the vault owner.
|
hub.registerSuccess.description=Your device is successfully registered. You can now continue to unlock the vault.
|
||||||
|
hub.registerSuccess.unlockBtn=Unlock
|
||||||
|
hub.registerSuccess.legacy.description=To access the vault, your device needs to be additionally authorized by the vault owner.
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
hub.registerFailed.message=Device naming failed
|
hub.registerFailed.message=Device registration failed
|
||||||
hub.registerFailed.description=An error was thrown in the naming process. For more details, look into the application log.
|
hub.registerFailed.description.generic=An error was thrown in the registration process. For more details, look into the application log.
|
||||||
|
hub.registerFailed.description.deviceAlreadyExists=This device is already registered for a different user. Try to change the user account or use a different device.
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
hub.unauthorized.message=Access denied
|
hub.unauthorized.message=Access denied
|
||||||
hub.unauthorized.description=Your device has not yet been authorized to access this vault. Ask the vault owner to authorize it.
|
hub.unauthorized.description=Your device has not yet been authorized to access this vault. Ask the vault owner to authorize it.
|
||||||
|
### Requires Account Initialization
|
||||||
|
hub.requireAccountInit.message=Action required
|
||||||
|
hub.requireAccountInit.description.0=To proceed, please complete the steps required in your
|
||||||
|
hub.requireAccountInit.description.1=Hub user profile
|
||||||
|
hub.requireAccountInit.description.2=.
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
hub.invalidLicense.message=Hub License invalid
|
hub.invalidLicense.message=Hub License invalid
|
||||||
hub.invalidLicense.description=Your Cryptomator Hub instance has an invalid license. Please inform a Hub administrator to upgrade or renew the license.
|
hub.invalidLicense.description=Your Cryptomator Hub instance has an invalid license. Please inform a Hub administrator to upgrade or renew the license.
|
||||||
@@ -291,11 +304,11 @@ 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=Default Volume Type
|
||||||
preferences.volume.type.automatic=Automatic
|
preferences.volume.type.automatic=Automatic
|
||||||
preferences.volume.docsTooltip=Open the documentation to learn more about the different volume types.
|
preferences.volume.docsTooltip=Open the documentation to learn more about the different volume types.
|
||||||
preferences.volume.fuseRestartRequired=To apply the changes, Cryptomator needs to be restarted.
|
preferences.volume.fuseRestartRequired=To apply the changes, Cryptomator needs to be restarted.
|
||||||
preferences.volume.tcp.port=TCP Port
|
preferences.volume.tcp.port=Default TCP Port
|
||||||
preferences.volume.supportedFeatures=The chosen volume type supports the following features:
|
preferences.volume.supportedFeatures=The chosen volume type supports the following features:
|
||||||
preferences.volume.feature.mountAuto=Automatic mount point selection
|
preferences.volume.feature.mountAuto=Automatic mount point selection
|
||||||
preferences.volume.feature.mountToDir=Custom directory as mount point
|
preferences.volume.feature.mountToDir=Custom directory as mount point
|
||||||
@@ -380,6 +393,7 @@ main.vaultDetail.unlockBtn=Unlock…
|
|||||||
main.vaultDetail.unlockNowBtn=Unlock Now
|
main.vaultDetail.unlockNowBtn=Unlock Now
|
||||||
main.vaultDetail.optionsBtn=Vault Options
|
main.vaultDetail.optionsBtn=Vault Options
|
||||||
main.vaultDetail.passwordSavedInKeychain=Password saved
|
main.vaultDetail.passwordSavedInKeychain=Password saved
|
||||||
|
main.vaultDetail.share=Share…
|
||||||
### Unlocked
|
### Unlocked
|
||||||
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:
|
||||||
@@ -434,8 +448,7 @@ 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.info=Open virtual drive preferences to change default settings.
|
||||||
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
|
||||||
@@ -445,6 +458,10 @@ vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
|
|||||||
vaultOptions.mount.mountPoint.custom=Use chosen directory
|
vaultOptions.mount.mountPoint.custom=Use chosen directory
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
|
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
|
||||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Pick a directory
|
vaultOptions.mount.mountPoint.directoryPickerTitle=Pick a directory
|
||||||
|
vaultOptions.mount.volumeType.default=Default (%s)
|
||||||
|
vaultOptions.mount.volumeType.restartRequired=To use this volume type, Cryptomator needs to be restarted.
|
||||||
|
vaultOptions.mount.volume.tcp.port=TCP Port
|
||||||
|
vaultOptions.mount.volume.type=Volume Type
|
||||||
## Master Key
|
## Master Key
|
||||||
vaultOptions.masterkey=Password
|
vaultOptions.masterkey=Password
|
||||||
vaultOptions.masterkey.changePasswordBtn=Change Password
|
vaultOptions.masterkey.changePasswordBtn=Change Password
|
||||||
@@ -514,4 +531,24 @@ updateReminder.message=Check for Updates?
|
|||||||
updateReminder.description=Stay updated with new features, bug fixes, and security improvements. We recommend to automatically check for updates.
|
updateReminder.description=Stay updated with new features, bug fixes, and security improvements. We recommend to automatically check for updates.
|
||||||
updateReminder.notNow=Not Now
|
updateReminder.notNow=Not Now
|
||||||
updateReminder.yesOnce=Yes, Once
|
updateReminder.yesOnce=Yes, Once
|
||||||
updateReminder.yesAutomatically=Yes, Automatically
|
updateReminder.yesAutomatically=Yes, Automatically
|
||||||
|
|
||||||
|
# Share Vault
|
||||||
|
shareVault.title=Share Vault
|
||||||
|
shareVault.message=Would you like to share your vault with others?
|
||||||
|
shareVault.description=Always be careful when sharing your vault with other people. In short, follow these steps:
|
||||||
|
shareVault.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
|
||||||
|
shareVault.instruction.2=2. Share the vault password in a secure way.
|
||||||
|
shareVault.remarkBestPractices=For more information, check out the best practices suggestions in our docs.
|
||||||
|
shareVault.docsTooltip=Open the documentation to learn more about sharing of vaults.
|
||||||
|
shareVault.hubAd.description=The secure way to work in teams
|
||||||
|
shareVault.hubAd.keyManagement=• Zero-knowledge key management
|
||||||
|
shareVault.hubAd.authentication=• Strong authentication
|
||||||
|
shareVault.hubAd.encryption=• End-to-end encryption
|
||||||
|
shareVault.visitHub=Visit Cryptomator Hub
|
||||||
|
|
||||||
|
shareVault.hub.message=How to share a Hub vault
|
||||||
|
shareVault.hub.description=In order to share the vault content with another team member, you have to perform two steps:
|
||||||
|
shareVault.hub.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
|
||||||
|
shareVault.hub.instruction.2=2. Grant access to team member in Cryptomator Hub.
|
||||||
|
shareVault.hub.openHub=Open Cryptomator Hub
|
||||||
@@ -154,17 +154,14 @@ hub.receive.message=معالجة الاستجابة…
|
|||||||
hub.receive.description=Cryptomator يتلقى ويعالج الاستجابة من المركز. الرجاء الانتظار.
|
hub.receive.description=Cryptomator يتلقى ويعالج الاستجابة من المركز. الرجاء الانتظار.
|
||||||
### Register Device
|
### Register Device
|
||||||
hub.register.nameLabel=اسم الجهاز
|
hub.register.nameLabel=اسم الجهاز
|
||||||
hub.register.occupiedMsg=الاسم مستخدم مسبقاً
|
### Register Device Legacy
|
||||||
hub.register.registerBtn=تأكيد
|
|
||||||
### Registration Success
|
### Registration Success
|
||||||
hub.registerSuccess.message=تم تسمية الجهاز
|
hub.registerSuccess.unlockBtn=افتح
|
||||||
hub.registerSuccess.description=للدخول إلى الخزينة، يحتاج جهازك إلى إذن من مالك الخزينة.
|
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
hub.registerFailed.message=أخفق في تسمية الجهاز
|
|
||||||
hub.registerFailed.description=خطأ في عملية التسمية. لمزيد من التفاصيل، انظر إلى سجل التطبيق.
|
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
hub.unauthorized.message=تم رفض الوصول
|
hub.unauthorized.message=تم رفض الوصول
|
||||||
hub.unauthorized.description=لم يتم بعد منح الإذن لجهازك بالوصول إلى هذا المخزن. اطلب من مالك المخزن أن يأذن بذلك.
|
hub.unauthorized.description=لم يتم بعد منح الإذن لجهازك بالوصول إلى هذا المخزن. اطلب من مالك المخزن أن يأذن بذلك.
|
||||||
|
### Requires Account Initialization
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
hub.invalidLicense.message=ترخيص المركز غير صالح
|
hub.invalidLicense.message=ترخيص المركز غير صالح
|
||||||
hub.invalidLicense.description=نموذج المركز Cryptomator الخاص بك لديه ترخيص غير صالح. الرجاء إبلاغ مسؤول مركز لترقية أو تجديد الترخيص.
|
hub.invalidLicense.description=نموذج المركز Cryptomator الخاص بك لديه ترخيص غير صالح. الرجاء إبلاغ مسؤول مركز لترقية أو تجديد الترخيص.
|
||||||
@@ -286,11 +283,9 @@ preferences.interface.showMinimizeButton=إظهار زر التصغير
|
|||||||
preferences.interface.showTrayIcon=إظهار أيقونة اللوحة (يتطلب إعادة تشغيل)
|
preferences.interface.showTrayIcon=إظهار أيقونة اللوحة (يتطلب إعادة تشغيل)
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=القرص الإفتراضي
|
preferences.volume=القرص الإفتراضي
|
||||||
preferences.volume.type=نوع وحدة التخزين
|
|
||||||
preferences.volume.type.automatic=تلقائي
|
preferences.volume.type.automatic=تلقائي
|
||||||
preferences.volume.docsTooltip=افتح الوثائق لمعرفة المزيد عن مختلف أنواع وحدة التخزين.
|
preferences.volume.docsTooltip=افتح الوثائق لمعرفة المزيد عن مختلف أنواع وحدة التخزين.
|
||||||
preferences.volume.fuseRestartRequired=لتطبيق التغييرات، يحتاج Cryptomator إلى إعادة التشغيل.
|
preferences.volume.fuseRestartRequired=لتطبيق التغييرات، يحتاج Cryptomator إلى إعادة التشغيل.
|
||||||
preferences.volume.tcp.port=منفذ TCP
|
|
||||||
preferences.volume.supportedFeatures=يدعم نوع وحدة تخزين المختار الميزات التالية:
|
preferences.volume.supportedFeatures=يدعم نوع وحدة تخزين المختار الميزات التالية:
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=تحديثات
|
preferences.updates=تحديثات
|
||||||
@@ -401,21 +396,27 @@ vaultOptions.mount.mountPoint=موقع التحميل الوهمي
|
|||||||
vaultOptions.mount.mountPoint.auto=اختيار موقع مناسب تلقائياً
|
vaultOptions.mount.mountPoint.auto=اختيار موقع مناسب تلقائياً
|
||||||
vaultOptions.mount.mountPoint.driveLetter=استخدام دالة قرص معين
|
vaultOptions.mount.mountPoint.driveLetter=استخدام دالة قرص معين
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
|
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
|
||||||
|
vaultOptions.mount.volumeType.default=الافتراضي (%s)
|
||||||
|
vaultOptions.mount.volume.tcp.port=منفذ TCP
|
||||||
## Master Key
|
## Master Key
|
||||||
vaultOptions.masterkey=كلمة المرور
|
vaultOptions.masterkey=كلمة المرور
|
||||||
vaultOptions.masterkey.changePasswordBtn=تغيير كلمة المرور
|
vaultOptions.masterkey.changePasswordBtn=تغيير كلمة المرور
|
||||||
vaultOptions.masterkey.forgetSavedPasswordBtn=نسيان كلمة المرور المحفوظة
|
vaultOptions.masterkey.forgetSavedPasswordBtn=نسيان كلمة المرور المحفوظة
|
||||||
vaultOptions.masterkey.recoveryKeyExplanation=مفتاح الاسترداد هو وسيلتك الوحيدة لاستعادة الوصول إلى مخزنك إذا فقدت كلمة المرور.
|
vaultOptions.masterkey.recoveryKeyExplanation=مفتاح الاسترداد هو وسيلتك الوحيدة لاستعادة الوصول إلى مخزنك إذا فقدت كلمة المرور.
|
||||||
vaultOptions.masterkey.showRecoveryKeyBtn=عرض مفتاح الاسترداد
|
vaultOptions.masterkey.showRecoveryKeyBtn=عرض مفتاح الاسترداد
|
||||||
|
vaultOptions.masterkey.recoverPasswordBtn=إعادة تعيين كلمة المرور
|
||||||
## Hub
|
## Hub
|
||||||
|
vaultOptions.hub=الإسترداد
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
## Display Recovery Key
|
## Display Recovery Key
|
||||||
|
recoveryKey.create.message=مطلوب كلمة المرور
|
||||||
recoveryKey.create.description=أدخل كلمة المرور الخاصة بك لإظهار مفتاح الاسترداد ل "%s":
|
recoveryKey.create.description=أدخل كلمة المرور الخاصة بك لإظهار مفتاح الاسترداد ل "%s":
|
||||||
recoveryKey.display.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.correctKey=هذا مفتاح استرداد صالح
|
recoveryKey.recover.correctKey=هذا مفتاح استرداد صالح
|
||||||
recoveryKey.printout.heading=مفتاح استرداد Cryptomator\n"%s"\n
|
recoveryKey.printout.heading=مفتاح استرداد Cryptomator\n"%s"\n
|
||||||
### Reset Password
|
### Reset Password
|
||||||
@@ -440,4 +441,6 @@ quit.lockAndQuitBtn=قفل و خروج
|
|||||||
|
|
||||||
# Forced Quit
|
# Forced Quit
|
||||||
|
|
||||||
# Update Reminder
|
# Update Reminder
|
||||||
|
|
||||||
|
# Share Vault
|
||||||
|
|||||||
@@ -0,0 +1,521 @@
|
|||||||
|
# Locale Specific CSS files such as CJK, RTL,...
|
||||||
|
|
||||||
|
# Generics
|
||||||
|
## Button
|
||||||
|
generic.button.apply=Ҡуллан
|
||||||
|
generic.button.back=Артҡа
|
||||||
|
generic.button.cancel=Кире ал
|
||||||
|
generic.button.change=Үҙгәрт
|
||||||
|
generic.button.choose=Һайла…
|
||||||
|
generic.button.close=Яп
|
||||||
|
generic.button.copy=Күсермәһен ал
|
||||||
|
generic.button.copied=Күсермә алынды!
|
||||||
|
generic.button.done=Тамам
|
||||||
|
generic.button.next=Киләһе
|
||||||
|
generic.button.print=Баҫтыр
|
||||||
|
|
||||||
|
# Error
|
||||||
|
error.message=Хата килеп сыҡты
|
||||||
|
error.description=Cryptomator өсөн көтөлмәгән хәл. Был хатаның булған төҙәтеү варианттарын ҡарай алаһығыҙ. Әлегә тиклем белдерелмәгән булһа, был хаҡта хәбәр итә алаһығыҙ.
|
||||||
|
error.hyperlink.lookup=Был хатаны тикшерегеҙ
|
||||||
|
error.hyperlink.report=Был хатаны белдерегеҙ
|
||||||
|
error.technicalDetails=Тулыраҡ мәғлүмәт:
|
||||||
|
error.existingSolutionDescription=Cryptomator бының булырын көтмәгән. Әммә беҙ был хатаны хәл итер юл таптыҡ. Түбәндәге һылтанманы ҡарап сығығыҙ.
|
||||||
|
error.hyperlink.solution=Хәл итеү варианттарын ҡарарға
|
||||||
|
error.lookupPermissionMessage=Cryptomator был проблеманы онлайн хәл итә ала. Был ваҡытта IP-адресығыҙҙан беҙҙең проблемалар базаһына һоратыу ебәреләсәк.
|
||||||
|
error.dismiss=Кире ҡаҡ
|
||||||
|
error.lookUpSolution=Хәл итеү варианттарын ҡара
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
defaults.vault.vaultName=Һаҡлағыс
|
||||||
|
|
||||||
|
# Tray Menu
|
||||||
|
traymenu.showMainWindow=Күрһәт
|
||||||
|
traymenu.showPreferencesWindow=Көйләүҙәр
|
||||||
|
traymenu.lockAllVaults=Барыһын да биклә
|
||||||
|
traymenu.quitApplication=Сыҡ
|
||||||
|
traymenu.vault.unlock=Биген ас
|
||||||
|
traymenu.vault.lock=Биклә
|
||||||
|
traymenu.vault.reveal=Күрһәт
|
||||||
|
|
||||||
|
# Add Vault Wizard
|
||||||
|
addvaultwizard.title=Һаҡлағыс өҫтәү
|
||||||
|
## New
|
||||||
|
addvaultwizard.new.title=Яңы һаҡлағыс өҫтәү
|
||||||
|
### Name
|
||||||
|
addvaultwizard.new.nameInstruction=Һаҡлағыс өсөн исем һайлағыҙ
|
||||||
|
addvaultwizard.new.namePrompt=Һаҡлағыс исеме
|
||||||
|
### Location
|
||||||
|
addvaultwizard.new.locationInstruction=Cryptomator һаҡлағысығыҙҙың шифрланған файлдарын ҡайҙа һаҡларға тейеш?
|
||||||
|
addvaultwizard.new.locationLabel=Һаҡлау урыны
|
||||||
|
addvaultwizard.new.locationPrompt=…
|
||||||
|
addvaultwizard.new.directoryPickerLabel=Башҡа урын
|
||||||
|
addvaultwizard.new.directoryPickerButton=Һайла…
|
||||||
|
addvaultwizard.new.directoryPickerTitle=Каталог һайлау
|
||||||
|
addvaultwizard.new.fileAlreadyExists=Һаҡлағыс исеме менән бер файл йәки каталог бар
|
||||||
|
addvaultwizard.new.locationDoesNotExist=Билдәләнгән юлда каталог юҡ йәки уға инеп булмай
|
||||||
|
addvaultwizard.new.locationIsNotWritable=Билдәләнгән юлда яҙыу мөмкинлеге юҡ
|
||||||
|
addvaultwizard.new.locationIsOk=Һаҡлағыс өсөн яраҡлы урын
|
||||||
|
addvaultwizard.new.invalidName=Яраҡһыҙ һаҡлағыс исеме
|
||||||
|
addvaultwizard.new.validName=Яраҡлы һаҡлағыс исеме
|
||||||
|
addvaultwizard.new.validCharacters.message=Һаҡлағыс исемендә түбәндәге билдәләр була ала:
|
||||||
|
addvaultwizard.new.validCharacters.chars=Хәрефтәр (мәҫ. a, ж or 수)
|
||||||
|
addvaultwizard.new.validCharacters.numbers=Һандар
|
||||||
|
addvaultwizard.new.validCharacters.dashes=Дефис (%s) йәки аҫҡы һыҙыҡ (%s)
|
||||||
|
### Expert Settings
|
||||||
|
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Эксперт көйләүҙәре ғәмәлдә
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=36 менән 220 араһында ҡиммәт яҙығыҙ (ғәҙәттә 220)
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Тулыраҡ мәғлүмәт алыр өсөн документтарҙы ҡарағыҙ.
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.title=Шифрланған файл исемдәренең максимум оҙонлоғо
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Яраҡлы
|
||||||
|
### Password
|
||||||
|
addvaultwizard.new.createVaultBtn=Һаҡлағыс яһа
|
||||||
|
addvaultwizard.new.generateRecoveryKeyChoice=Серһүҙһеҙ мәғлүмәттәрегеҙгә инеп булмаясаҡ. Серһүҙ юғалтҡан осраҡ өсөн тергеҙеү асҡысы кәрәкме?
|
||||||
|
addvaultwizard.new.generateRecoveryKeyChoice.yes=Эйе, аҙаҡтан терһәк тешләүҙән һаҡ булыу яҡшыраҡ
|
||||||
|
addvaultwizard.new.generateRecoveryKeyChoice.no=Юҡ, рәхмәт, мин серһүҙемде юғалтмаясаҡмын
|
||||||
|
### Information
|
||||||
|
addvault.new.readme.storageLocation.fileName=МӨҺИМ.rtf
|
||||||
|
addvault.new.readme.storageLocation.1=⚠️ ҺАҠЛАҒЫС ФАЙЛДАРЫ ⚠️
|
||||||
|
addvault.new.readme.storageLocation.2=Был һаҡлағысығыҙҙы һаҡлау урыны.
|
||||||
|
addvault.new.readme.storageLocation.3=ЯРАМАЙ
|
||||||
|
addvault.new.readme.storageLocation.4=• был каталогтағы файлдарҙы үҙгәртеү
|
||||||
|
addvault.new.readme.storageLocation.5=• шифрлау өсөн ниндәй ҙә булһа файлды ошо каталогка һалыу
|
||||||
|
addvault.new.readme.storageLocation.6=Әгәр файлдарҙы шифрларға һәм һаҡлағыстың эстәлеген ҡарарға теләһәгеҙ, түбәндәгеләрҙе эшләгеҙ:
|
||||||
|
addvault.new.readme.storageLocation.7=1. Был һаҡлағысты Cryptomator-ға өҫтәгеҙ.
|
||||||
|
addvault.new.readme.storageLocation.8=2. Cryptomator-ҙа һаҡлағысты асығыҙ.
|
||||||
|
addvault.new.readme.storageLocation.9=3. "Күрһәт" төймәһенә баҫып, инеү урынын асығыҙ.
|
||||||
|
addvault.new.readme.storageLocation.10=Әгәр ярҙам кәрәк булһа, документтарҙы ҡарағыҙ: %s
|
||||||
|
addvault.new.readme.accessLocation.fileName=РӘХИМ ИТЕГЕҘ.rtf
|
||||||
|
addvault.new.readme.accessLocation.1=🔐️ ШИФРЛАНҒАН ТОМ 🔐️
|
||||||
|
addvault.new.readme.accessLocation.2=Был һаҡлағысығыҙға инеү урыны.
|
||||||
|
addvault.new.readme.accessLocation.3=Был томға өҫтәлгән файлдар Cryptomator тарафынан шифрлана. Уның менән башҡа диск/каталог кеүек эшләй алаһығыҙ. Был уның эстәлегенең дешифрланған күренеше генә, һеҙҙең файлдар ҡаты дискығыҙҙа һәр ваҡыт шифрланған булып ҡала.
|
||||||
|
addvault.new.readme.accessLocation.4=Был файлды юйырға була.
|
||||||
|
## Existing
|
||||||
|
addvaultwizard.existing.title=Булған һаҡлағыс өҫтәү
|
||||||
|
addvaultwizard.existing.instruction=Һаҡлағысығыҙҙа "vault.cryptomator" файлын һайлағыҙ. Әгәр "masterkey.cryptomator" исемле файл ғына булһа, уны һайлағыҙ.
|
||||||
|
addvaultwizard.existing.chooseBtn=Һайла…
|
||||||
|
addvaultwizard.existing.filePickerTitle=Һаҡлағыс файлын һайлау
|
||||||
|
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Һаҡлағысы
|
||||||
|
## Success
|
||||||
|
addvaultwizard.success.nextStepsInstructions=\ "%s" һаҡлағысы өҫтәлде.\nЭстәлеккә инеү йәки өҫтәү өсөн был һаҡлағысты асыр кәрәк. Уны аҙаҡтан да асырға була.
|
||||||
|
addvaultwizard.success.unlockNow=Хәҙер бикте ас
|
||||||
|
|
||||||
|
# Remove Vault
|
||||||
|
removeVault.title="%s" һаҡлағысын алып ташла
|
||||||
|
removeVault.message=Һаҡлағысты алып ташларғамы?
|
||||||
|
removeVault.description=Cryptomator был һаҡлағысты ғына онотасаҡ. Һеҙ уны яңынан өҫтәй алаһығыҙ. Ҡаты дисктан шифрланған файлдар юйылмаясаҡ.
|
||||||
|
removeVault.confirmBtn=Һаҡлағысты алып ташла
|
||||||
|
|
||||||
|
# Change Password
|
||||||
|
changepassword.title=Серһүҙҙе үҙгәртеү
|
||||||
|
changepassword.enterOldPassword="%s" өсөн ағымдағы серһүҙҙе яҙығыҙ
|
||||||
|
changepassword.finalConfirmation=Серһүҙемде онотһам, мәғлүмәттәремә инә алмаясағымды аңлайым
|
||||||
|
|
||||||
|
# Forget Password
|
||||||
|
forgetPassword.title=Серһүҙҙе онот
|
||||||
|
forgetPassword.message=Һаҡланған серһүҙҙе оноторғамы?
|
||||||
|
forgetPassword.description=Был һаҡлағыстың һеҙҙең система асҡыс сылбырында һаҡланған серһүҙен юясаҡ.
|
||||||
|
forgetPassword.confirmBtn=Серһүҙҙе онот
|
||||||
|
|
||||||
|
# Unlock
|
||||||
|
unlock.title="%s" биген асыу
|
||||||
|
unlock.passwordPrompt="%s" серһүҙен яҙығыҙ:
|
||||||
|
unlock.savePassword=Серһүҙҙе һаҡла
|
||||||
|
unlock.unlockBtn=Биген ас
|
||||||
|
## Select
|
||||||
|
unlock.chooseMasterkey.message=Masterkey файлы табылманы
|
||||||
|
unlock.chooseMasterkey.description=Cryptomator "%s" һаҡлағысының masterkey файлын таба алманы. Асҡыс файлын ҡулдан һайлағыҙ.
|
||||||
|
unlock.chooseMasterkey.filePickerTitle=Masterkey файлын һайлағыҙ
|
||||||
|
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
|
||||||
|
## Success
|
||||||
|
unlock.success.message=Бикте сисеү уңышлы тамамланды
|
||||||
|
unlock.success.description="%s" һаҡлағысының эстәлеге хәҙер уның тоташтырыу нөктәһе аша асыҡ.
|
||||||
|
unlock.success.rememberChoice=Һайлауымды иҫтә тот, ҡабат һорама
|
||||||
|
unlock.success.revealBtn=Дискты күрһәт
|
||||||
|
## Failure
|
||||||
|
unlock.error.customPath.message=Һаҡлағысты күрһәтелгән юлға тоташтырып булманы
|
||||||
|
unlock.error.customPath.description.notSupported=Махсус юлды артабан да ҡулланырға теләһәгеҙ, көйләүҙәргә барып уны терәкләгән том төрөн һайлағыҙ. Юғиһә, һаҡлағыс көйләүҙәренә күсерегеҙ һәм терәкләнгән тоташтырыу урыны һайлағыҙ.
|
||||||
|
unlock.error.customPath.description.notExists=Махсус тоташтырыу юлы юҡ. Уны урындағы файл системаһында яһағыҙ йәки һаҡлағыс параметрҙарында үҙгәртегеҙ.
|
||||||
|
unlock.error.customPath.description.inUse="%s" диск хәрефе йәки махсус тоташтырыу юлы ҡулланыла инде.
|
||||||
|
unlock.error.customPath.description.hideawayNotDir=Асыу өсөн ҡулланылған ваҡытлыса, йәшерен "%3$s" файлын алып ташлау мөмкин түгел. Файлды тикшереп, һуңынан ҡул менән юйығыҙ.
|
||||||
|
unlock.error.customPath.description.couldNotBeCleaned=Һаҡлағысығыҙ "%s" юлына тоташтырылманы. Тағы бер тапҡыр ҡабатлап ҡарағыҙ йәки икенсе юл һайлағыҙ.
|
||||||
|
unlock.error.customPath.description.notEmptyDir="%s" махсус тоташтырыу урыны буш каталог түгел. Зинһар, буш каталог һайлағыҙ һәм яңынан ҡабатлағыҙ.
|
||||||
|
unlock.error.customPath.description.generic=Һеҙ был һаҡлағыс өсөн махсус тоташтырыу урыны һайланығыҙ, ләкин уны ҡулланыу киләһе хәбәр менән уңышһыҙ тамамланды: %2$s
|
||||||
|
unlock.error.title="%s" биге асылманы
|
||||||
|
## Hub
|
||||||
|
hub.noKeychain.message=Йыһаз асҡысына инеү рөхсәте юҡ
|
||||||
|
hub.noKeychain.description=Хаб һаҡлағыстарын асыу өсөн йыһаз асҡысы кәрәк, ул иһә асҡыс сылбырында һаҡлана. Артабан “%s” мөмкинлеге бирегеҙ һәм көйләүҙәрҙә асҡыс сылбырын һайлағыҙ.
|
||||||
|
hub.noKeychain.openBtn=Көйләүҙәрҙе ас
|
||||||
|
### Waiting
|
||||||
|
hub.auth.message=Аутентиклау көтөлә…
|
||||||
|
hub.auth.description=Автоматик рәүештә инеү битенә йүнәлтелергә тейешһегеҙ.
|
||||||
|
hub.auth.loginLink=Йүнәлтелмәнегеҙме? Асыу өсөн ошонда баҫығыҙ.
|
||||||
|
### Receive Key
|
||||||
|
hub.receive.message=Яуапты эшкәртеү…
|
||||||
|
hub.receive.description=Cryptomator хабтан килгән яуапты ҡабул итә һәм эшкәртә. Зинһар, көтөгөҙ.
|
||||||
|
### Register Device
|
||||||
|
hub.register.message=Яңы йыһаз
|
||||||
|
hub.register.nameLabel=Йыһаз исеме
|
||||||
|
hub.register.invalidAccountKeyLabel=Хаталы иҫәп яҙмаһы асҡысы
|
||||||
|
hub.register.registerBtn=Теркәл
|
||||||
|
### Register Device Legacy
|
||||||
|
### Registration Success
|
||||||
|
hub.registerSuccess.unlockBtn=Биген ас
|
||||||
|
### Registration Failed
|
||||||
|
### Unauthorized
|
||||||
|
hub.unauthorized.message=Инеү кире ҡағылды
|
||||||
|
hub.unauthorized.description=Һеҙҙең йыһаз әлегә был һаҡлағысҡа инеү хоҡуғына эйә түгел. Һаҡлағыс хужаһынан рөхсәт һорағыҙ.
|
||||||
|
### Requires Account Initialization
|
||||||
|
hub.requireAccountInit.message=Эш-хәрәкәт кәрәкле
|
||||||
|
hub.requireAccountInit.description.0=Дауам итер өсөн, кәрәкле аҙымдар тамамланырға тейеш урын:
|
||||||
|
hub.requireAccountInit.description.1=Хаб ҡулланыусы профиле
|
||||||
|
hub.requireAccountInit.description.2=.
|
||||||
|
### License Exceeded
|
||||||
|
hub.invalidLicense.message=Хаб рөхсәтнамәһе яраҡһыҙ
|
||||||
|
hub.invalidLicense.description=Һеҙҙең Cryptomator хабығыҙ ғәмәлдән тыш рөхсәтнамәгә эйә. Рөхсәтнамәне яңыртыу йәки оҙайтыу өсөн Хаб администраторына хәбәр итегеҙ.
|
||||||
|
|
||||||
|
# Lock
|
||||||
|
## Force
|
||||||
|
lock.forced.message=Бикләп булманы
|
||||||
|
lock.forced.description=Көтөп торған ғәмәлдәр йәки асыҡ файлдар "%s" бикләүҙе тотҡарланы. Был һаҡлағысты көсләп бикләп була, ләкин инеү-сығыуҙы өҙөү һаҡланмаған мәғлүмәттәрҙе юғалтыуға килтереүе ихтимал.
|
||||||
|
lock.forced.retryBtn=Ҡабатла
|
||||||
|
lock.forced.forceBtn=Көсләп биклә
|
||||||
|
## Failure
|
||||||
|
lock.fail.message=Һаҡлағысты бикләп булманы
|
||||||
|
lock.fail.description="%s" һаҡлағысын бикләп булмай. Һаҡланмаған эштең һаҡланыуын һәм мөһим уҡыу/яҙыу ғәмәлдәренең тамамланыуын тикшерегеҙ. һаҡлағысты ябыр өсөн Cryptomator процесын туҡтатығыҙ.
|
||||||
|
|
||||||
|
# Migration
|
||||||
|
migration.title=Һаҡлағысты яңыртыу
|
||||||
|
## Start
|
||||||
|
migration.start.header=Һаҡлағысты яңыртыу
|
||||||
|
migration.start.text="%s" һаҡлағысын Cryptomator-ҙың яңы версияһында асыу өсөн һаҡлағысты яңы форматҡа күсерергә кәрәк. Быны эшләр алдынан түбәндәгеләрҙе белер кәрәк:
|
||||||
|
migration.start.remarkUndone=Был яңыртыуҙы кире алып булмай.
|
||||||
|
migration.start.remarkVersions=Cryptomator-ҙың иҫке версиялары яңыртылған һаҡлағысты аса алмай.
|
||||||
|
migration.start.remarkCanRun=Һаҡлағысты ҡулланған һәр йыһаздың Cryptomator-ҙың был версияһы менән эшләй аласағын тикшереү кәрәк.
|
||||||
|
migration.start.remarkSynced=Яңыртыуҙан алда һаҡлағыстың был һәм башҡа йыһыздарҙа тулыһынса синхронлаштырылыуына тикшерер кәрәк.
|
||||||
|
migration.start.confirm=Юғарыла килтерелгән мәғлүмәтте уҡыным һәм аңланым
|
||||||
|
## Run
|
||||||
|
migration.run.enterPassword="%s" серһүҙен яҙығыҙ
|
||||||
|
migration.run.startMigrationBtn=Һаҡлағысты күсер
|
||||||
|
migration.run.progressHint=Был бер ни тиклем ваҡыт ала…
|
||||||
|
## Success
|
||||||
|
migration.success.nextStepsInstructions="%s" уңышлы күсерелде.\nХәҙер һаҡлағысығыҙҙы аса алаһығыҙ.
|
||||||
|
migration.success.unlockNow=Хәҙер бикте ас
|
||||||
|
## Missing file system capabilities
|
||||||
|
migration.error.missingFileSystemCapabilities.title=Терәкләнмәгән файл системаһы
|
||||||
|
migration.error.missingFileSystemCapabilities.description=Күсеү башланманы, сөнки һаҡлағысығыҙ яраҡһыҙ файл системаһында урынлашҡан.
|
||||||
|
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файл системаһы оҙон файл исемдәрен терәкләмәй.
|
||||||
|
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файл системаһы оҙон юлдарҙы терәкләмәй.
|
||||||
|
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файл системаһы уҡылыуға рөхсәт бирмәй.
|
||||||
|
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Файл системаһы яҙыуға рөхсәт бирмәй.
|
||||||
|
## Impossible
|
||||||
|
migration.impossible.heading=Һаҡлағысты күсереп булманы
|
||||||
|
migration.impossible.reason=Һаҡлағыс автоматик рәүештә күсерелә алмай, сөнки уның һаҡлау урыны йәки инеү урыны яраҡһыҙ.
|
||||||
|
migration.impossible.moreInfo=Һаҡлағысты элекке версия менән асырға мөмкин. Ҡулдан күсереү буйынса инструкциялар өсөн, сайтҡа инегеҙ:
|
||||||
|
|
||||||
|
# Health Check
|
||||||
|
## Start
|
||||||
|
health.title="%s" хәлен тикшереү
|
||||||
|
health.intro.header=Хәлде тикшереү
|
||||||
|
health.intro.text=Хәлде тикшереү — көмбәҙҙең эске төҙөлөшөндәге проблемаларҙы асыҡлау һәм хәл итеү өсөн тикшереүҙәр тупланмаһы. Зинһар, иҫтә тотоғоҙ:
|
||||||
|
health.intro.remarkSync=Бөтә йыһаздарҙың тулыһынса синхронлаштырылғанын ҡарағыҙ, был күпселек проблемаларҙы хәл итә.
|
||||||
|
health.intro.remarkFix=Бөтә проблемаларҙы ла хәл итеү мөмкин түгел.
|
||||||
|
health.intro.remarkBackup=Мәғлүмәттәр боҙолған осраҡта һаҡлыҡ файлдары ғына ярҙам итә ала.
|
||||||
|
health.intro.affirmation=Юғарыла килтерелгән мәғлүмәтте уҡыным һәм аңланым
|
||||||
|
## Start Failure
|
||||||
|
health.fail.header=Һаҡлағыс конфигурацияһын тейәүҙәге хата
|
||||||
|
health.fail.ioError=Конфигурация файлын асыу һәм уҡыу ваҡытында хата килеп сыҡты.
|
||||||
|
health.fail.parseError=Һаҡлағыс конфигурацияһын тағатҡанда хата барлыҡҡа килде.
|
||||||
|
health.fail.moreInfo=Тулыраҡ мәғлүмәт
|
||||||
|
## Check Selection
|
||||||
|
health.checkList.description=Һул яҡтағы исемлектән һайлағыҙ йәки аҫтағы төймәләрҙе ҡулланығыҙ.
|
||||||
|
health.checkList.selectAllButton=Бөтәһен дә һайла
|
||||||
|
health.checkList.deselectAllButton=Береһен дә һайлама
|
||||||
|
health.check.runBatchBtn=Һайланған тикшереүҙәрҙе башлат
|
||||||
|
## Detail view
|
||||||
|
health.check.detail.noSelectedCheck=Һөҙөмтәләр өсөн һул яҡтағы исемлектә тамамланған хәл тикшереүен һайлағыҙ.
|
||||||
|
health.check.detail.checkScheduled=Тикшереү планлаштырылған.
|
||||||
|
health.check.detail.checkRunning=Әлеге ваҡытта тикшереү бара…
|
||||||
|
health.check.detail.checkSkipped=Тикшереү үткәреү өсөн һайланмаған.
|
||||||
|
health.check.detail.checkFinished=Тикшереү уңышлы тамамланды.
|
||||||
|
health.check.detail.checkFinishedAndFound=Тикшереү тамамланды. Һөҙөмтәләрҙе ҡарап сығығыҙ.
|
||||||
|
health.check.detail.checkFailed=Тикшереү хата арҡаһында тамамланманы.
|
||||||
|
health.check.detail.checkCancelled=Тикшереү өҙөлдө.
|
||||||
|
health.check.detail.listFilters.label=Фильтр
|
||||||
|
health.check.detail.fixAllSpecificBtn=Бөтә төрҙәрҙе төҙәтеү
|
||||||
|
health.check.exportBtn=Һөҙөмтәләрҙе экспортла
|
||||||
|
## Result view
|
||||||
|
health.result.severityFilter.all=Ауырлыҡ кимәле — Бөтәһе лә
|
||||||
|
health.result.severityFilter.good=Яҡшы
|
||||||
|
health.result.severityFilter.info=Мәғлүмәт
|
||||||
|
health.result.severityFilter.warn=Иҫкәртеү
|
||||||
|
health.result.severityFilter.crit=Критик
|
||||||
|
health.result.severityTip.good=Ауырлыҡ кимәле: Яҡшы\nһаҡлағыстың ғәҙәти структураһы.
|
||||||
|
health.result.severityTip.info=Ауырлыҡ кимәле: Мәғлүмәт\nһаҡлағыс структураһы боҙолмаған, төҙәтеү тәҡдим ителә.
|
||||||
|
health.result.severityTip.warn=Ауырлыҡ кимәле: Иҫкәртеү\nһаҡлағыс структураһы боҙолған, төҙәтеү бик ныҡ кәңәш ителә.
|
||||||
|
health.result.severityTip.crit=Ауырлыҡ кимәле: Критик\nһаҡлағыс структураһы боҙоҡ, мәғлүмәт юғалтыуҙары бар.
|
||||||
|
health.result.fixStateFilter.all=Төҙәтеү торошо — Барыһы ла
|
||||||
|
health.result.fixStateFilter.fixable=Төҙәтеп булған
|
||||||
|
health.result.fixStateFilter.notFixable=Төҙәтелерлек түгел
|
||||||
|
health.result.fixStateFilter.fixing=Төҙәтеү…
|
||||||
|
health.result.fixStateFilter.fixed=Төҙәтелде
|
||||||
|
health.result.fixStateFilter.fixFailed=Төҙәтеп булманы
|
||||||
|
## Fix Application
|
||||||
|
health.fix.fixBtn=Төҙәт
|
||||||
|
health.fix.successTip=Төҙәтелде
|
||||||
|
health.fix.failTip=Төҙәтеп булманы, тулыраҡ мәғлүмәт өсөн журналды ҡарағыҙ
|
||||||
|
|
||||||
|
# Preferences
|
||||||
|
preferences.title=Көйләүҙәр
|
||||||
|
## General
|
||||||
|
preferences.general=Дөйөм
|
||||||
|
preferences.general.startHidden=Cryptomator башланған саҡта тәҙрәне йәшерергә
|
||||||
|
preferences.general.autoCloseVaults=Ҡушымтанан сыҡҡан ваҡытта һаҡлағыстарҙы автоматик рәүештә бикләргә
|
||||||
|
preferences.general.debugLogging=Төҙөкләндереү журналын асырға
|
||||||
|
preferences.general.debugDirectory=Журнал файлдарын күрһәт
|
||||||
|
preferences.general.autoStart=Система стартында Cryptomator-ҙы эшләтеп ебәрергә
|
||||||
|
preferences.general.keychainBackend=Серһүҙ һаҡлау өсөн
|
||||||
|
## Interface
|
||||||
|
preferences.interface=Интерфейс
|
||||||
|
preferences.interface.theme=Күренеш
|
||||||
|
preferences.interface.theme.automatic=Автоматик
|
||||||
|
preferences.interface.theme.dark=Ҡараңғы
|
||||||
|
preferences.interface.theme.light=Яҡты
|
||||||
|
preferences.interface.unlockThemes=Ҡараңғы режимды ас
|
||||||
|
preferences.interface.language=Тел (яңынан башлатыу кәрәк)
|
||||||
|
preferences.interface.language.auto=Ғәҙәттәге система көйләүҙәре
|
||||||
|
preferences.interface.interfaceOrientation=Интерфейс йүнәлеше
|
||||||
|
preferences.interface.interfaceOrientation.ltr=Һулдан уңға
|
||||||
|
preferences.interface.interfaceOrientation.rtl=Уңдан һулға
|
||||||
|
preferences.interface.showMinimizeButton=Бәләкәйләтеү төймәһен күрһәтергә
|
||||||
|
preferences.interface.showTrayIcon=Панелдә билдәне күрһәтергә (яңынан башлатыу кәрәк)
|
||||||
|
## Volume
|
||||||
|
preferences.volume=Виртуаль диск
|
||||||
|
preferences.volume.type=Ғәҙәттәге том төрө
|
||||||
|
preferences.volume.type.automatic=Автоматик
|
||||||
|
preferences.volume.docsTooltip=Төрлө том төрҙәре тураһында күберәк белер өсөн, документтарҙы ҡарағыҙ.
|
||||||
|
preferences.volume.fuseRestartRequired=Үҙгәрештәрҙе ҡулланыу өсөн Cryptomator-ҙы яңынан асыу кәрәк.
|
||||||
|
preferences.volume.tcp.port=Ғәҙәттәге TCP порты
|
||||||
|
preferences.volume.supportedFeatures=Һайланған том төрө түбәндәге үҙенсәлектәрҙе терәкләй:
|
||||||
|
preferences.volume.feature.mountAuto=Тоташтырыу нөктәһен автоматик рәүештә һайлау
|
||||||
|
preferences.volume.feature.mountToDir=Тоташтырыу нөктәһе өсөн махсус каталог һайлау
|
||||||
|
preferences.volume.feature.mountToDriveLetter=Тоташтырыу нөктәһе ите диск хәрефен ҡулланыу
|
||||||
|
preferences.volume.feature.mountFlags=Махсус тоташтырыу варианттары
|
||||||
|
preferences.volume.feature.readOnly=Уҡыу ғына режимында тоташтырыу
|
||||||
|
## Updates
|
||||||
|
preferences.updates=Яңыртыуҙар
|
||||||
|
preferences.updates.currentVersion=Ағымдағы версия: %s
|
||||||
|
preferences.updates.autoUpdateCheck=Яңыртыуҙарҙы автоматик рәүештә тикшереү
|
||||||
|
preferences.updates.checkNowBtn=Хәҙер тикшер
|
||||||
|
preferences.updates.updateAvailable=%s версияһына тиклем яңыртыу бар.
|
||||||
|
## Contribution
|
||||||
|
preferences.contribute=Ярҙам
|
||||||
|
preferences.contribute.registeredFor=%s өсөн теркәлгән ярҙамсы сертификаты
|
||||||
|
preferences.contribute.noCertificate=Cryptomator-ҡа ярҙам итегеҙ һәм ярҙамсы сертификаты алығыҙ. Был рөхсәтнамә асҡысы кеүек, әммә ирекле программа ҡулланған бик шәп кешеләр өсөн. ;-)
|
||||||
|
preferences.contribute.getCertificate=Юҡ мы әллә? Уны нисек алырға кәрәклеген ҡарағыҙ.
|
||||||
|
preferences.contribute.promptText=Ярҙамсы сертификатын бында йәбештерегеҙ
|
||||||
|
#<-- Add entries for donations and code/translation/documentation contribution -->
|
||||||
|
|
||||||
|
## About
|
||||||
|
preferences.about=Тураһында
|
||||||
|
|
||||||
|
# Vault Statistics
|
||||||
|
stats.title=%s статистикаһы
|
||||||
|
stats.cacheHitRate=Кэшҡа тип килеү йышлығы
|
||||||
|
## 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
|
||||||
|
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
|
||||||
|
stats.access.current=Инеү: %d
|
||||||
|
stats.access.total=Барлыҡ инеү: %d
|
||||||
|
|
||||||
|
|
||||||
|
# Main Window
|
||||||
|
main.closeBtn.tooltip=Яп
|
||||||
|
main.minimizeBtn.tooltip=Бәләкәйләт
|
||||||
|
main.preferencesBtn.tooltip=Көйләүҙәр
|
||||||
|
main.debugModeEnabled.tooltip=Төҙөкләндереү режимы асыҡ
|
||||||
|
main.supporterCertificateMissing.tooltip=Зинһар, иғәнә биреү тураһында уйлағыҙ
|
||||||
|
## Vault List
|
||||||
|
main.vaultlist.emptyList.onboardingInstruction=Һаҡлағыс өҫтәү өсөн бында баҫығыҙ
|
||||||
|
main.vaultlist.contextMenu.remove=Алып ташлау…
|
||||||
|
main.vaultlist.contextMenu.lock=Биклә
|
||||||
|
main.vaultlist.contextMenu.unlock=Бикте асыу…
|
||||||
|
main.vaultlist.contextMenu.unlockNow=Хәҙер бикте ас
|
||||||
|
main.vaultlist.contextMenu.vaultoptions=Һаҡлағыс варианттарын күрһәт
|
||||||
|
main.vaultlist.contextMenu.reveal=Дискты күрһәт
|
||||||
|
main.vaultlist.addVaultBtn=Өҫтәү
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=Яңы Һаҡлағыс...
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=Булған һаҡлағыс...
|
||||||
|
## Vault Detail
|
||||||
|
### Welcome
|
||||||
|
main.vaultDetail.welcomeOnboarding=Файлдарығыҙҙы һаҡлау өсөн Cryptomator-ҙы һайлағанығыҙ өсөн рәхмәт. Әгәр һеҙгә ярҙам кәрәк булһа, башлау буйынса белешмәләребеҙҙе ҡарағыҙ:
|
||||||
|
### Locked
|
||||||
|
main.vaultDetail.lockedStatus=БИКЛЕ
|
||||||
|
main.vaultDetail.unlockBtn=Биген ас…
|
||||||
|
main.vaultDetail.unlockNowBtn=Хәҙер бикте ас
|
||||||
|
main.vaultDetail.optionsBtn=Һаҡлағыс варианттары
|
||||||
|
main.vaultDetail.passwordSavedInKeychain=Серһүҙ һаҡланды
|
||||||
|
### Unlocked
|
||||||
|
main.vaultDetail.unlockedStatus=АСЫҠ
|
||||||
|
main.vaultDetail.accessLocation=Һаҡлағыс эстәлеген бында ҡарарға була:
|
||||||
|
main.vaultDetail.revealBtn=Дискты күрһәт
|
||||||
|
main.vaultDetail.copyUri=URI күсермәһен ал
|
||||||
|
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=Һаҡлағыс статистикаһы
|
||||||
|
main.vaultDetail.locateEncryptedFileBtn=Шифрланған файлды тап
|
||||||
|
main.vaultDetail.locateEncryptedFileBtn.tooltip=Шифрланған аналогын табыр өсөн һаҡлағыстан файл һайлағыҙ
|
||||||
|
main.vaultDetail.encryptedPathsCopied=Юлдарҙын Clipboard-ҡа күсермәһе алынды!
|
||||||
|
main.vaultDetail.filePickerTitle=Һаҡлағыс эсендә файл һайлау
|
||||||
|
### Missing
|
||||||
|
main.vaultDetail.missing.info=Cryptomator был юлдан һаҡлағыс таба алманы.
|
||||||
|
main.vaultDetail.missing.recheck=Яңынан тикшер
|
||||||
|
main.vaultDetail.missing.remove=Һаҡлағыс исемлегенән алып ташла…
|
||||||
|
main.vaultDetail.missing.changeLocation=Һаҡлағыс урынын үҙгәрт…
|
||||||
|
### Needs Migration
|
||||||
|
main.vaultDetail.migrateButton=Һаҡлағысты яңыртыу
|
||||||
|
main.vaultDetail.migratePrompt=Һаҡлағысҡа инер алдынан уны яңы форматҡа тиклем яңыртыу кәрәк
|
||||||
|
### Error
|
||||||
|
main.vaultDetail.error.info=Һаҡлағысты дисктан тейәүҙә хата килеп сыҡты.
|
||||||
|
main.vaultDetail.error.reload=Яңынан тейә
|
||||||
|
main.vaultDetail.error.windowTitle=Һаҡлағыс тейәүҙә хата
|
||||||
|
|
||||||
|
# Wrong File Alert
|
||||||
|
wrongFileAlert.title=Файлдарҙы нисек шифрларға
|
||||||
|
wrongFileAlert.message=Был файлдарҙы шифрларға тырыштығыҙмы?
|
||||||
|
wrongFileAlert.description=Бының өсөн Cryptomator һеҙҙең система файл менеджерында том яһай.
|
||||||
|
wrongFileAlert.instruction.0=Файлдарҙы шифрлау өсөн ошо аҙымдарҙы үтәгеҙ:
|
||||||
|
wrongFileAlert.instruction.1=1. Һаҡлағысығыҙҙы асығыҙ.
|
||||||
|
wrongFileAlert.instruction.2=2. Файл менеджерығыҙҙа томды асыу өсөн «Күрһәт» төймәһенә баҫығыҙ.
|
||||||
|
wrongFileAlert.instruction.3=3. Файлдарығыҙҙы ошо томға өҫтәгеҙ.
|
||||||
|
wrongFileAlert.link=Өҫтәмә ярҙам өсөн, ҡарағыҙ:
|
||||||
|
|
||||||
|
# Vault Options
|
||||||
|
## General
|
||||||
|
vaultOptions.general=Дөйөм
|
||||||
|
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.ask=Һорарға
|
||||||
|
vaultOptions.general.startHealthCheckBtn=Хәл тикшереүен башлатырға
|
||||||
|
|
||||||
|
## Mount
|
||||||
|
vaultOptions.mount=Тоташтырыу
|
||||||
|
vaultOptions.mount.info=Ғәҙәттәге көйләүҙәрҙе үҙгәртеү өсөн виртуаль диск көйләүҙәрен асығыҙ.
|
||||||
|
vaultOptions.mount.readonly=Уҡыу ғына
|
||||||
|
vaultOptions.mount.customMountFlags=Махсус тоташтырыу флагтары
|
||||||
|
vaultOptions.mount.winDriveLetterOccupied=биләнгән
|
||||||
|
vaultOptions.mount.mountPoint=Тоташтырыу нөктәһе
|
||||||
|
vaultOptions.mount.mountPoint.auto=Автоматик рәүештә уңай урын һайларға
|
||||||
|
vaultOptions.mount.mountPoint.driveLetter=Тәғәйенләнгән диск хәрефен ҡулланырға
|
||||||
|
vaultOptions.mount.mountPoint.custom=Һайланған каталог ҡулланырға
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerButton=Һайла…
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerTitle=Каталог һайлау
|
||||||
|
vaultOptions.mount.volumeType.default=Ғәҙәттәгеләр (%s)
|
||||||
|
vaultOptions.mount.volume.tcp.port=TCP порты
|
||||||
|
vaultOptions.mount.volume.type=Том төрө
|
||||||
|
## Master Key
|
||||||
|
vaultOptions.masterkey=Серһүҙ
|
||||||
|
vaultOptions.masterkey.changePasswordBtn=Серһүҙҙе үҙгәртеү
|
||||||
|
vaultOptions.masterkey.forgetSavedPasswordBtn=Һаҡланған серһүҙҙе онот
|
||||||
|
vaultOptions.masterkey.recoveryKeyExplanation=Серһүҙ юғалғанда һаҡлағысҡа инеү мөмкинлеген ҡайтарыу асҡысы менән генә тергеҙергә мөмкин.
|
||||||
|
vaultOptions.masterkey.showRecoveryKeyBtn=Ҡайтарыу асҡысын күрһәт
|
||||||
|
vaultOptions.masterkey.recoverPasswordBtn=Серһүҙҙе яңырт
|
||||||
|
## Hub
|
||||||
|
vaultOptions.hub=Ҡайтарыу
|
||||||
|
vaultOptions.hub.convertInfo=Ашығыс осраҡта, был хаб һаҡлағысын серһүҙ нигеҙле һаҡлағысҡа әйләндереү өсөн тергеҙеү асҡысын ҡуллана алаһығыҙ.
|
||||||
|
vaultOptions.hub.convertBtn=Серһүҙ нигеҙле һаҡлағысҡа күс
|
||||||
|
|
||||||
|
# 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 • Ҡағыҙҙа баҫтырығыҙ
|
||||||
|
## Reset Password
|
||||||
|
### Enter Recovery Key
|
||||||
|
recoveryKey.recover.title=Серһүҙҙе яңырт
|
||||||
|
recoveryKey.recover.prompt="%s" өсөн ҡайтарыу асҡысын яҙығыҙ:
|
||||||
|
recoveryKey.recover.correctKey=Был ҡайтарыу асҡысы дөрөҫ
|
||||||
|
recoveryKey.recover.wrongKey=Был ҡайтарыу асҡысы башҡа һаҡлағысҡа ҡарай
|
||||||
|
recoveryKey.recover.invalidKey=Был ҡайтарыу асҡысы дөрөҫ түгел
|
||||||
|
recoveryKey.printout.heading=Cryptomator ҡайтарыу асҡысы\n"%s"\n
|
||||||
|
### Reset Password
|
||||||
|
recoveryKey.recover.resetBtn=Яңынан башла
|
||||||
|
### Recovery Key Password Reset Success
|
||||||
|
recoveryKey.recover.resetSuccess.message=Серһүҙ яңыртыу уңышлы тамамланды
|
||||||
|
recoveryKey.recover.resetSuccess.description=Яңы серһүҙ менән һаҡлағысты аса алаһығыҙ.
|
||||||
|
|
||||||
|
# Convert Vault
|
||||||
|
convertVault.title=Һаҡлағысты үҙгәртеү
|
||||||
|
convertVault.convert.convertBtn.before=Үҙгәрт
|
||||||
|
convertVault.convert.convertBtn.processing=Үҙгәртеү…
|
||||||
|
convertVault.success.message=Үҙгәртеү уңышлы тамамланды
|
||||||
|
convertVault.hubToPassword.success.description=Хәҙер һеҙ һайланған серһүҙ менән, хабҡа инмәйенсә, һаҡлағысты аса алаһығыҙ.
|
||||||
|
|
||||||
|
# New Password
|
||||||
|
newPassword.promptText=Яңы серһүҙ яҙығыҙ
|
||||||
|
newPassword.reenterPassword=Яңы серһүҙҙе раҫлағыҙ
|
||||||
|
newPassword.passwordsMatch=Серһүҙҙәр тап килә!
|
||||||
|
newPassword.passwordsDoNotMatch=Серһүҙҙәр тап килмәй
|
||||||
|
passwordStrength.messageLabel.tooShort=Кәмендә %d билдә ҡулланығыҙ
|
||||||
|
passwordStrength.messageLabel.0=Бик көсһөҙ
|
||||||
|
passwordStrength.messageLabel.1=Көсһөҙ
|
||||||
|
passwordStrength.messageLabel.2=Уртаса
|
||||||
|
passwordStrength.messageLabel.3=Көслө
|
||||||
|
passwordStrength.messageLabel.4=Бик көслө
|
||||||
|
|
||||||
|
# Quit
|
||||||
|
quit.title=Ҡушымтанан сығыу
|
||||||
|
quit.message=Асыҡ һаҡлағыстар бар
|
||||||
|
quit.description=Сығырға теләүегеҙҙе раҫлағыҙ. Cryptomator мәғлүмәтте юғалтыуҙан һаҡлар өсөн бөтә һаҡлағыстарҙы бикләп ҡуя.
|
||||||
|
quit.lockAndQuitBtn=Биклә һәм сыҡ
|
||||||
|
|
||||||
|
# Forced Quit
|
||||||
|
quit.forced.message=Ҡайһы бер һаҡлағыстарҙы бикләп булмай
|
||||||
|
quit.forced.description=Көтөп торған ғәмәлдәр йәки асыҡ файлдар һаҡлағыстарҙы бикләүҙе тотҡарланы. Был һаҡлағыстарҙы көсләп бикләп була, ләкин инеү-сығыуҙы өҙөү һаҡланмаған мәғлүмәттәрҙе юғалтыуға килтереүе ихтимал.
|
||||||
|
quit.forced.forceAndQuitBtn=Көсләп биклә һәм сыҡ
|
||||||
|
|
||||||
|
# Update Reminder
|
||||||
|
updateReminder.title=Яңыртыуҙарҙы тикшереү
|
||||||
|
updateReminder.message=Яңыртыуҙарҙы тикшерергәме?
|
||||||
|
updateReminder.description=Яңы үҙенсәлектәр, хаталарҙы төҙәтеү, хәүефһеҙлекте яҡшыртыу менән яңыртылып торорға. Яңыртыуҙарҙы автоматик рәүештә тикшереүҙе кәңәш итәбеҙ.
|
||||||
|
updateReminder.notNow=Хәҙер түгел
|
||||||
|
updateReminder.yesOnce=Эйе, бер тапҡыр
|
||||||
|
updateReminder.yesAutomatically=Эйе, автоматик рәүештә
|
||||||
|
|
||||||
|
# Share Vault
|
||||||
@@ -22,6 +22,9 @@ error.hyperlink.report=Паведаміць пра гэтую памылку
|
|||||||
error.technicalDetails=Падрабязнасці:
|
error.technicalDetails=Падрабязнасці:
|
||||||
error.existingSolutionDescription=Cryptomator не чакаў такога, але мы знайшлі, як можна выправіць гэтую хібу. Калі ласка, скарыстайся спасылкаю.
|
error.existingSolutionDescription=Cryptomator не чакаў такога, але мы знайшлі, як можна выправіць гэтую хібу. Калі ласка, скарыстайся спасылкаю.
|
||||||
error.hyperlink.solution=Паглядзець на рашэнне
|
error.hyperlink.solution=Паглядзець на рашэнне
|
||||||
|
error.lookupPermissionMessage=Cryptomator можа шукаць рашэнне праблемаў онлайн, дасылаючы запыт да нашай база даных хібаў з твайго IP-адрасу.
|
||||||
|
error.dismiss=Адхіліць
|
||||||
|
error.lookUpSolution=Пашукаць вырашэнне
|
||||||
|
|
||||||
# Defaults
|
# Defaults
|
||||||
defaults.vault.vaultName=Скарбніца
|
defaults.vault.vaultName=Скарбніца
|
||||||
@@ -38,6 +41,7 @@ traymenu.vault.reveal=Паказаць
|
|||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Дадаць скарбніцу
|
addvaultwizard.title=Дадаць скарбніцу
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=Дадаць новую скарбніцу
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Абраць назву для скарбніцы
|
addvaultwizard.new.nameInstruction=Абраць назву для скарбніцы
|
||||||
addvaultwizard.new.namePrompt=Назва скарбніцы
|
addvaultwizard.new.namePrompt=Назва скарбніцы
|
||||||
@@ -59,6 +63,11 @@ addvaultwizard.new.validCharacters.chars=Слоўныя знакі, накшта
|
|||||||
addvaultwizard.new.validCharacters.numbers=Лічбы
|
addvaultwizard.new.validCharacters.numbers=Лічбы
|
||||||
addvaultwizard.new.validCharacters.dashes=Злучок (%s) або падкрэслінік (%s)
|
addvaultwizard.new.validCharacters.dashes=Злучок (%s) або падкрэслінік (%s)
|
||||||
### Expert Settings
|
### Expert Settings
|
||||||
|
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Актываваць экспэртныя функцыі
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Увядзі значэнне ад 36 да 220 (прадвызначана 220)
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Адкрый дакумэнтацыю, каб атрымаць больш інфармацыі.
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.title=Максымальная даўжыня назваў зашыфраваных файлаў
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Дзейсны
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.createVaultBtn=Стварыць скарбніцу
|
addvaultwizard.new.createVaultBtn=Стварыць скарбніцу
|
||||||
addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль?
|
addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль?
|
||||||
@@ -82,6 +91,7 @@ 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.title=Дадаць існуючую скрабніцу
|
||||||
addvaultwizard.existing.instruction=Абяры файл "vault.cryptomator" у існуючай скарбніцы. Калі існуе толькі файл "masterkey.cryptomator", абяры яго.
|
addvaultwizard.existing.instruction=Абяры файл "vault.cryptomator" у існуючай скарбніцы. Калі існуе толькі файл "masterkey.cryptomator", абяры яго.
|
||||||
addvaultwizard.existing.chooseBtn=Абраць…
|
addvaultwizard.existing.chooseBtn=Абраць…
|
||||||
addvaultwizard.existing.filePickerTitle=Абяры файл скарбніцы
|
addvaultwizard.existing.filePickerTitle=Абяры файл скарбніцы
|
||||||
@@ -126,6 +136,7 @@ unlock.success.revealBtn=Паказаць дыск
|
|||||||
unlock.error.customPath.message=Не магчыма змантажаваць скарбніцу да карыстальніцкай сцежкі
|
unlock.error.customPath.message=Не магчыма змантажаваць скарбніцу да карыстальніцкай сцежкі
|
||||||
unlock.error.customPath.description.notSupported=Калі ты надалей жадаеш карыстацца адмысловаю сцежкаю, калі ласка, пайдзі ў налады ды абары там тып тому, які падтрымлівае яе. У іншым выпадку пайдзі ў опцыі скарбніцы ды абяры там пункт мантажавання, які падтрымліваецца.
|
unlock.error.customPath.description.notSupported=Калі ты надалей жадаеш карыстацца адмысловаю сцежкаю, калі ласка, пайдзі ў налады ды абары там тып тому, які падтрымлівае яе. У іншым выпадку пайдзі ў опцыі скарбніцы ды абяры там пункт мантажавання, які падтрымліваецца.
|
||||||
unlock.error.customPath.description.notExists=Адмысловая сцежка мантажавання не існуе. Ствары яе ў сваёй файлавай сістэме, альбо змяні яе ў опцыях скарбніцы.
|
unlock.error.customPath.description.notExists=Адмысловая сцежка мантажавання не існуе. Ствары яе ў сваёй файлавай сістэме, альбо змяні яе ў опцыях скарбніцы.
|
||||||
|
unlock.error.customPath.description.inUse=Дыскавая літара або карыстальніцкая мантажная сьцежка "%s" ужо выкарыстоўваецца.
|
||||||
## Hub
|
## Hub
|
||||||
hub.noKeychain.message=Няма доступу да ключа прылады
|
hub.noKeychain.message=Няма доступу да ключа прылады
|
||||||
hub.noKeychain.description=Каб разамкнуць скарбніцы Hub, патрэбны ключ прылады, які захаваны ў звязку ключоў. Каб працягнуць, уключы "%s" ды абяры звязак ключоў у наладах.
|
hub.noKeychain.description=Каб разамкнуць скарбніцы Hub, патрэбны ключ прылады, які захаваны ў звязку ключоў. Каб працягнуць, уключы "%s" ды абяры звязак ключоў у наладах.
|
||||||
@@ -138,18 +149,16 @@ hub.auth.loginLink=Не перанакіраваўся? Пстрыкні сюд
|
|||||||
hub.receive.message=Апрацоўка адказу…
|
hub.receive.message=Апрацоўка адказу…
|
||||||
hub.receive.description=Cryptomator атрымлівае ды апрацоўвае адказ ад Hub. Калі ласка, пачакай.
|
hub.receive.description=Cryptomator атрымлівае ды апрацоўвае адказ ад Hub. Калі ласка, пачакай.
|
||||||
### Register Device
|
### Register Device
|
||||||
|
hub.register.message=Новая прылада
|
||||||
hub.register.nameLabel=Назва прылады
|
hub.register.nameLabel=Назва прылады
|
||||||
hub.register.occupiedMsg=Назва ўжо ўжытая
|
### Register Device Legacy
|
||||||
hub.register.registerBtn=Пацвердзіць
|
|
||||||
### Registration Success
|
### Registration Success
|
||||||
hub.registerSuccess.message=Прылада атрымала назву
|
hub.registerSuccess.unlockBtn=Адамкнуць
|
||||||
hub.registerSuccess.description=Каб атрымаць доступ да скарбніцы, твая прылада мусіць быць спраўджанай уладальнікам скарбніцы.
|
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
hub.registerFailed.message=Памылка пры называнні прылады
|
|
||||||
hub.registerFailed.description=Падчас прысваення імя адбылася памылка. Па дэтальную інфармацыю звярніся да пратаколу праграмы.
|
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
hub.unauthorized.message=Адмова ў доступе
|
hub.unauthorized.message=Адмова ў доступе
|
||||||
hub.unauthorized.description=Тваёй прыладзе ў дадзены момант не дазволена мець доступ да гэтай скрабніцы. Запытайся ўладальніка скрабніцы за дазволам.
|
hub.unauthorized.description=Тваёй прыладзе ў дадзены момант не дазволена мець доступ да гэтай скрабніцы. Запытайся ўладальніка скрабніцы за дазволам.
|
||||||
|
### Requires Account Initialization
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
hub.invalidLicense.message=Несапраўдная ліцэнзія Hub
|
hub.invalidLicense.message=Несапраўдная ліцэнзія Hub
|
||||||
hub.invalidLicense.description=Твая інстанцыя Cryptomator Hub мае некарэктную ліцэнзію. Калі ласка, паведамі адміністратару Hub пра гэта, каб абнавіць альбо аднавіць ліцэнзію.
|
hub.invalidLicense.description=Твая інстанцыя Cryptomator Hub мае некарэктную ліцэнзію. Калі ласка, паведамі адміністратару Hub пра гэта, каб абнавіць альбо аднавіць ліцэнзію.
|
||||||
@@ -271,11 +280,9 @@ preferences.interface.showMinimizeButton=Паказаць кнопку згор
|
|||||||
preferences.interface.showTrayIcon=Паказваць іконку на інфармацыйнай панэлі (спатрэбіцца перазапуск)
|
preferences.interface.showTrayIcon=Паказваць іконку на інфармацыйнай панэлі (спатрэбіцца перазапуск)
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Віртуальны дыск
|
preferences.volume=Віртуальны дыск
|
||||||
preferences.volume.type=Тып тому
|
|
||||||
preferences.volume.type.automatic=Аўтаматычна
|
preferences.volume.type.automatic=Аўтаматычна
|
||||||
preferences.volume.docsTooltip=Адчыні дакумэнтацыю, каб даведацца больш пра розныя тыпы тому.
|
preferences.volume.docsTooltip=Адчыні дакумэнтацыю, каб даведацца больш пра розныя тыпы тому.
|
||||||
preferences.volume.fuseRestartRequired=Каб дастасаваць зьмены, Cryptomator патрэбна перазапусьціць.
|
preferences.volume.fuseRestartRequired=Каб дастасаваць зьмены, Cryptomator патрэбна перазапусьціць.
|
||||||
preferences.volume.tcp.port=Порт TCP
|
|
||||||
preferences.volume.supportedFeatures=Абраны тып тому падтрымлівае наступныя функцыі:
|
preferences.volume.supportedFeatures=Абраны тып тому падтрымлівае наступныя функцыі:
|
||||||
preferences.volume.feature.mountAuto=Аўтаматычны выбар пункту мантажавання
|
preferences.volume.feature.mountAuto=Аўтаматычны выбар пункту мантажавання
|
||||||
preferences.volume.feature.mountToDir=Карыстальніцкая тэчка як пункт мантажавання
|
preferences.volume.feature.mountToDir=Карыстальніцкая тэчка як пункт мантажавання
|
||||||
@@ -349,6 +356,8 @@ main.vaultlist.contextMenu.unlockNow=Разамкнуць зараз
|
|||||||
main.vaultlist.contextMenu.vaultoptions=Паказаць параметры скарбніцы
|
main.vaultlist.contextMenu.vaultoptions=Паказаць параметры скарбніцы
|
||||||
main.vaultlist.contextMenu.reveal=Паказаць дыск
|
main.vaultlist.contextMenu.reveal=Паказаць дыск
|
||||||
main.vaultlist.addVaultBtn=Дадаць
|
main.vaultlist.addVaultBtn=Дадаць
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=Новая скарбніца...
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=Існуючая скрабніца...
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Дзякуй, што ты абраў Cryptomator для абароны тваіх файлаў. Калі табе патрэбна дапамога, калі ласка, паглядзі нашы інструкцыі:
|
main.vaultDetail.welcomeOnboarding=Дзякуй, што ты абраў Cryptomator для абароны тваіх файлаў. Калі табе патрэбна дапамога, калі ласка, паглядзі нашы інструкцыі:
|
||||||
@@ -412,8 +421,6 @@ vaultOptions.general.startHealthCheckBtn=Пачаць тэст на цэласн
|
|||||||
|
|
||||||
## Mount
|
## Mount
|
||||||
vaultOptions.mount=Мантажаванне
|
vaultOptions.mount=Мантажаванне
|
||||||
vaultOptions.mount.info=Опцыі залежаць ад абранага тыпу тому.
|
|
||||||
vaultOptions.mount.linkToPreferences=Адчыніць налады віртуальнага дыску
|
|
||||||
vaultOptions.mount.readonly=Толькі для чытання
|
vaultOptions.mount.readonly=Толькі для чытання
|
||||||
vaultOptions.mount.customMountFlags=Карыстальніцкія опцыі мантажавання
|
vaultOptions.mount.customMountFlags=Карыстальніцкія опцыі мантажавання
|
||||||
vaultOptions.mount.winDriveLetterOccupied=занята
|
vaultOptions.mount.winDriveLetterOccupied=занята
|
||||||
@@ -432,6 +439,7 @@ vaultOptions.masterkey.showRecoveryKeyBtn=Паказаць ключ аднаўл
|
|||||||
vaultOptions.masterkey.recoverPasswordBtn=Скінуць пароль
|
vaultOptions.masterkey.recoverPasswordBtn=Скінуць пароль
|
||||||
## Hub
|
## Hub
|
||||||
vaultOptions.hub=Аднаўленне
|
vaultOptions.hub=Аднаўленне
|
||||||
|
vaultOptions.hub.convertBtn=Сканвэртаваць у скарбніцу з паролем
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
## Display Recovery Key
|
## Display Recovery Key
|
||||||
@@ -483,4 +491,11 @@ quit.forced.message=Некаторыя скарбніцы не магчыма з
|
|||||||
quit.forced.description=Замыканне скарбніц было заблакавана праз дзеючыя аперацыі альбо праз адчыненыя файлы. Ты можаш прымусова замкнуць скарбніцы, але гэта можа прывесці да страты незахаваных даных.
|
quit.forced.description=Замыканне скарбніц было заблакавана праз дзеючыя аперацыі альбо праз адчыненыя файлы. Ты можаш прымусова замкнуць скарбніцы, але гэта можа прывесці да страты незахаваных даных.
|
||||||
quit.forced.forceAndQuitBtn=Прымусіць і выйсці
|
quit.forced.forceAndQuitBtn=Прымусіць і выйсці
|
||||||
|
|
||||||
# Update Reminder
|
# Update Reminder
|
||||||
|
updateReminder.title=Праверка абнаўленняў
|
||||||
|
updateReminder.message=Ці праверыць абнаўленні?
|
||||||
|
updateReminder.notNow=Не цяпер
|
||||||
|
updateReminder.yesOnce=Так, аднойчы
|
||||||
|
updateReminder.yesAutomatically=Так, аўтаматычна
|
||||||
|
|
||||||
|
# Share Vault
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ unlock.error.customPath.description.hideawayNotDir=Временният, скр
|
|||||||
unlock.error.customPath.description.couldNotBeCleaned=Хранилището не може да бъде монтирано на „%s“. Опитайте отново или изберете друг път.
|
unlock.error.customPath.description.couldNotBeCleaned=Хранилището не може да бъде монтирано на „%s“. Опитайте отново или изберете друг път.
|
||||||
unlock.error.customPath.description.notEmptyDir=Потребителският път на монтиране „%s“ не е празна папка. Изберете празна папка и опитайте отново.
|
unlock.error.customPath.description.notEmptyDir=Потребителският път на монтиране „%s“ не е празна папка. Изберете празна папка и опитайте отново.
|
||||||
unlock.error.customPath.description.generic=Избрали сте потребителски път за монтиране на това хранилище, но при използването му възникна следната грешка: %2$s
|
unlock.error.customPath.description.generic=Избрали сте потребителски път за монтиране на това хранилище, но при използването му възникна следната грешка: %2$s
|
||||||
|
unlock.error.title=Неуспешно отключване на „%s“
|
||||||
## Hub
|
## Hub
|
||||||
hub.noKeychain.message=Няма достъп до ключа на устройството
|
hub.noKeychain.message=Няма достъп до ключа на устройството
|
||||||
hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките.
|
hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките.
|
||||||
@@ -154,21 +155,21 @@ hub.receive.message=Обработване на отговора…
|
|||||||
hub.receive.description=Криптоматор получава и обработва отговора от Hub. Изчакайте.
|
hub.receive.description=Криптоматор получава и обработва отговора от Hub. Изчакайте.
|
||||||
### Register Device
|
### Register Device
|
||||||
hub.register.message=Ново устройство
|
hub.register.message=Ново устройство
|
||||||
hub.register.description=За пръв път посещавате Hub от това устройство. Удостоверете го, като използвате кода за настройка.
|
|
||||||
hub.register.nameLabel=Име на устройството
|
hub.register.nameLabel=Име на устройството
|
||||||
hub.register.setupCodeLabel=Код за настройка
|
hub.register.invalidAccountKeyLabel=Неприемлив ключ на профила
|
||||||
hub.register.invalidSetupCode=Неприемлив код за настройка
|
hub.register.registerBtn=Регистриране
|
||||||
hub.register.occupiedMsg=Това име вече е заето
|
### Register Device Legacy
|
||||||
hub.register.registerBtn=Потвърждаване
|
|
||||||
### Registration Success
|
### Registration Success
|
||||||
hub.registerSuccess.message=Устройството е именувано
|
hub.registerSuccess.unlockBtn=Отключване
|
||||||
hub.registerSuccess.description=За да получи достъп до хранилището, устройството трябва да бъде упълномощено от собственика на хранилището.
|
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
hub.registerFailed.message=Грешка при именуване на устройството
|
|
||||||
hub.registerFailed.description=В процеса на именуване е допусната грешка. За повече подробности разгледайте дневника на приложението.
|
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
hub.unauthorized.message=Отказан достъп
|
hub.unauthorized.message=Отказан достъп
|
||||||
hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика.
|
hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика.
|
||||||
|
### Requires Account Initialization
|
||||||
|
hub.requireAccountInit.message=Необходимо е действие
|
||||||
|
hub.requireAccountInit.description.0=За да продължите завършете необходимите стъпки в
|
||||||
|
hub.requireAccountInit.description.1=профила в Hub
|
||||||
|
hub.requireAccountInit.description.2=.
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
hub.invalidLicense.message=Лиценза за Hub е недействителен
|
hub.invalidLicense.message=Лиценза за Hub е недействителен
|
||||||
hub.invalidLicense.description=Лиценза на екземпляра на Концентратора на Криптоматор който вие използвате е лиценз. Информирайте администратора на Концентратора, за да поднови или надгради лиценза.
|
hub.invalidLicense.description=Лиценза на екземпляра на Концентратора на Криптоматор който вие използвате е лиценз. Информирайте администратора на Концентратора, за да поднови или надгради лиценза.
|
||||||
@@ -290,11 +291,11 @@ preferences.interface.showMinimizeButton=Бутон за скриване
|
|||||||
preferences.interface.showTrayIcon=Икона в областта за известия (изисква рестарт)
|
preferences.interface.showTrayIcon=Икона в областта за известия (изисква рестарт)
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Виртуален диск
|
preferences.volume=Виртуален диск
|
||||||
preferences.volume.type=Вид на тома
|
preferences.volume.type=Подразбиран вид на тома
|
||||||
preferences.volume.type.automatic=Автоматично
|
preferences.volume.type.automatic=Автоматично
|
||||||
preferences.volume.docsTooltip=Вижте документацията относно видовете томове.
|
preferences.volume.docsTooltip=Вижте документацията относно видовете томове.
|
||||||
preferences.volume.fuseRestartRequired=За да бъдат приложени промените, Криптоматор трябва да бъде рестартиран.
|
preferences.volume.fuseRestartRequired=За да бъдат приложени промените, Криптоматор трябва да бъде рестартиран.
|
||||||
preferences.volume.tcp.port=Порт на TCP
|
preferences.volume.tcp.port=Подразбиран порт на TCP
|
||||||
preferences.volume.supportedFeatures=Избрания вид на тома има следните възможности:
|
preferences.volume.supportedFeatures=Избрания вид на тома има следните възможности:
|
||||||
preferences.volume.feature.mountAuto=Автоматичен избор на точка за монтиране
|
preferences.volume.feature.mountAuto=Автоматичен избор на точка за монтиране
|
||||||
preferences.volume.feature.mountToDir=Папка по избор като точка за монтиране
|
preferences.volume.feature.mountToDir=Папка по избор като точка за монтиране
|
||||||
@@ -433,8 +434,7 @@ vaultOptions.general.startHealthCheckBtn=Проверка на състояни
|
|||||||
|
|
||||||
## Mount
|
## Mount
|
||||||
vaultOptions.mount=Монтиране
|
vaultOptions.mount=Монтиране
|
||||||
vaultOptions.mount.info=Възможностите зависят от вида на избрания том.
|
vaultOptions.mount.info=Отворете настройките на виртуалния диск, за да промените стандартните настройки.
|
||||||
vaultOptions.mount.linkToPreferences=Настройки на виртуалния диск
|
|
||||||
vaultOptions.mount.readonly=Само за четене
|
vaultOptions.mount.readonly=Само за четене
|
||||||
vaultOptions.mount.customMountFlags=Флагове на монтиране
|
vaultOptions.mount.customMountFlags=Флагове на монтиране
|
||||||
vaultOptions.mount.winDriveLetterOccupied=използвана
|
vaultOptions.mount.winDriveLetterOccupied=използвана
|
||||||
@@ -444,6 +444,9 @@ vaultOptions.mount.mountPoint.driveLetter=Използване на опреде
|
|||||||
vaultOptions.mount.mountPoint.custom=Използване на избрана папка
|
vaultOptions.mount.mountPoint.custom=Използване на избрана папка
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=Избиране…
|
vaultOptions.mount.mountPoint.directoryPickerButton=Избиране…
|
||||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Избиране на папка
|
vaultOptions.mount.mountPoint.directoryPickerTitle=Избиране на папка
|
||||||
|
vaultOptions.mount.volumeType.default=По подразбиране (%s)
|
||||||
|
vaultOptions.mount.volume.tcp.port=Порт на TCP
|
||||||
|
vaultOptions.mount.volume.type=Вид на тома
|
||||||
## Master Key
|
## Master Key
|
||||||
vaultOptions.masterkey=Парола
|
vaultOptions.masterkey=Парола
|
||||||
vaultOptions.masterkey.changePasswordBtn=Промяна на парола
|
vaultOptions.masterkey.changePasswordBtn=Промяна на парола
|
||||||
@@ -513,4 +516,6 @@ updateReminder.message=Проверяване за обновяване?
|
|||||||
updateReminder.description=Получавайте нови възможности на приложението, поправки на дефекти и подобрения на сигурността. Препоръчваме ви автоматичното обновяване.
|
updateReminder.description=Получавайте нови възможности на приложението, поправки на дефекти и подобрения на сигурността. Препоръчваме ви автоматичното обновяване.
|
||||||
updateReminder.notNow=Не сега
|
updateReminder.notNow=Не сега
|
||||||
updateReminder.yesOnce=Да, веднъж
|
updateReminder.yesOnce=Да, веднъж
|
||||||
updateReminder.yesAutomatically=Да, автоматично
|
updateReminder.yesAutomatically=Да, автоматично
|
||||||
|
|
||||||
|
# Share Vault
|
||||||
|
|||||||
@@ -89,10 +89,12 @@ unlock.unlockBtn=আনলক করুন
|
|||||||
### Waiting
|
### Waiting
|
||||||
### Receive Key
|
### Receive Key
|
||||||
### Register Device
|
### Register Device
|
||||||
hub.register.registerBtn=নিশ্চিত করুন
|
### Register Device Legacy
|
||||||
### Registration Success
|
### Registration Success
|
||||||
|
hub.registerSuccess.unlockBtn=আনলক করুন
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
|
### Requires Account Initialization
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
|
|
||||||
# Lock
|
# Lock
|
||||||
@@ -171,4 +173,6 @@ vaultOptions.mount.mountPoint.directoryPickerButton=নির্বাচন ক
|
|||||||
|
|
||||||
# Forced Quit
|
# Forced Quit
|
||||||
|
|
||||||
# Update Reminder
|
# Update Reminder
|
||||||
|
|
||||||
|
# Share Vault
|
||||||
|
|||||||
@@ -105,10 +105,12 @@ unlock.success.revealBtn=Otkrij pogon
|
|||||||
### Waiting
|
### Waiting
|
||||||
### Receive Key
|
### Receive Key
|
||||||
### Register Device
|
### Register Device
|
||||||
hub.register.registerBtn=Potvrdi
|
### Register Device Legacy
|
||||||
### Registration Success
|
### Registration Success
|
||||||
|
hub.registerSuccess.unlockBtn=Otključaj
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
|
### Requires Account Initialization
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
|
|
||||||
# Lock
|
# Lock
|
||||||
@@ -313,4 +315,6 @@ quit.lockAndQuitBtn=Zaključaj i zatvori
|
|||||||
|
|
||||||
# Forced Quit
|
# Forced Quit
|
||||||
|
|
||||||
# Update Reminder
|
# Update Reminder
|
||||||
|
|
||||||
|
# Share Vault
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user