Compare commits

...
Author SHA1 Message Date
Armin Schrenk c615ad2cd7 Merge branch 'develop' into feature/exe-cache-msis 2024-02-28 13:25:25 +01:00
Armin Schrenk 414465371b [ci skip] refactor win-exe workflow
* only publish, if msi and exe succeed
* make stable release jobs dependent of publish
2024-02-28 13:19:54 +01:00
Armin Schrenk 91a4db388f cache also cryptomator and winfsp msi 2024-02-28 12:51:30 +01:00
Armin Schrenk feed72a729 [ci skip] fix flathub workflow 2024-02-27 22:52:56 +01:00
Armin Schrenk bca20a7a66 [ci skip] seperate av whitelisting into own workflow to be able to upload assets on failure 2024-02-27 18:55:14 +01:00
Armin Schrenk a38377baaa [ci skip] Merge branch 'main' into develop 2024-02-27 12:53:30 +01:00
Armin Schrenk 61a45fc738 Merge branch 'release/1.12.3' 2024-02-27 12:52:00 +01:00
Armin Schrenk 3a65b5551f adjust release check to changed metainfo file
references fcb940e29c
2024-02-27 12:48:36 +01:00
Armin Schrenk 94ed5c18b8 damnn merge from develop to release 2024-02-27 12:41:06 +01:00
Armin Schrenk a559483d86 finalize 1.12.3 2024-02-27 12:38:52 +01:00
Armin Schrenk 3acdef1dd0 Merge branch 'develop' into release/1.12.3 2024-02-27 12:37:13 +01:00
Armin Schrenk def6f8ab95 [ci skip] clean up 2024-02-27 12:35:55 +01:00
Armin Schrenk 11ba1f3ddc revert 1ab73be1f4 and 2d968eac8c 2024-02-27 12:32:37 +01:00
CryptobotandGitHub 3228f2cf5f New Crowdin updates (#3343)
New translations strings.properties

Chinese Simplified; Dutch; French; Japanese; Portuguese; Russian; Slovak; Swedish; 

[ci skip]
2024-02-27 10:23:56 +01:00
Armin Schrenk 9c4f7ad79d [ci skip] remove unknown env/style combination from appstream metadata 2024-02-26 17:41:18 +01:00
Armin Schrenk 3bd57d162b [ci skip] update appstream metadata 2024-02-26 17:37:04 +01:00
dependabot[bot]andGitHub 5ea73a5a8d Bump the java-production-dependencies group with 1 update (#3347) 2024-02-26 09:50:42 +00:00
Armin Schrenk 38670838c7 [ci skip] Drop yq usage 2024-02-26 10:15:25 +01:00
Armin Schrenk 3e0b84dbce [ci skip] fix pr body description & reduce log noise 2024-02-23 17:39:38 +01:00
Armin Schrenk 2302db6206 [ci skip] fix create pr command in flathub job 2024-02-23 17:27:48 +01:00
Armin Schrenk a94bf99660 [ci skip] automatically set up tracking branch on push 2024-02-23 17:13:11 +01:00
Armin Schrenk 0c5ce353b1 [ci skip] use github.actor 2024-02-23 17:05:26 +01:00
Armin Schrenk b3ce777a42 [ci skip] configure git in flathub job 2024-02-23 16:29:18 +01:00
Armin Schrenk 3085df3397 [ci skip] further fixes:
* remove yq after update
* set token for git
2024-02-23 16:14:21 +01:00
Armin Schrenk fb740b605f add missing quote char [ci skip] 2024-02-23 15:57:06 +01:00
Armin Schrenk 6a74d9f3b2 use same varaible in get-version job 2024-02-23 15:53:22 +01:00
Armin Schrenk 1c7dffb63f Add ci job to create flathub PR on release [ci skip] 2024-02-23 15:44:48 +01:00
Jan-Peter Klein a213f073b1 enhance screen selection by adding primary fallback 2024-02-23 10:51:23 +01:00
Armin Schrenk 1ab73be1f4 fixes #3344 2024-02-22 16:11:54 +01:00
Armin Schrenk 8412871090 apply proper formatting and state to error dialog 2024-02-22 12:23:43 +01:00
Armin Schrenk fa63f3ca67 Reset develop to SNAPSHOT version 2024-02-22 12:21:00 +01:00
Armin Schrenk dbc0f52481 fix not-uploading artifacts on release 2024-02-21 22:04:35 +01:00
Armin Schrenk b8cd1caeac also upload asc file as artifact for mac build 2024-02-21 13:09:14 +01:00
Armin Schrenk 43c25b6d97 fix ci
references 84732337ca
2024-02-21 12:53:01 +01:00
Armin Schrenk e44bc09074 Merge branch 'develop' into release/1.12.3 2024-02-21 12:20:36 +01:00
CryptobotandGitHub 7b02f78ef5 New Crowdin updates (#3342)
New translations strings.properties

Arabic; Bashkir; Belarusian; Bengali; Bosnian; Bulgarian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Croatian; Czech; Danish; Dutch; Filipino; Finnish; French; Galician; German; Greek; Hebrew; Hindi; Hungarian; Indonesian; Italian; Japanese; Korean; Latvian; Macedonian; Marathi; Norwegian Bokmal; Norwegian Nynorsk; Persian; Polish; Portuguese; Portuguese, Brazilian; Punjabi; Romanian; Russian; Serbian (Cyrillic); Serbian (Latin); Sinhala; Slovak; Slovenian; Spanish; Swahili, Tanzania; Swedish; Tamil; Telugu; Thai; Turkish; Ukrainian; Vietnamese; 

[ci skip]
2024-02-21 12:13:24 +01:00
Armin Schrenk f7e8a4d1e6 prepare 1.12.3 2024-02-20 18:13:01 +01:00
Armin Schrenk 664375d692 fix wrong method name 2024-02-20 18:06:55 +01:00
Armin SchrenkandGitHub 2d968eac8c Merge pull request #3336 from cryptomator/feature/3272-vaultid-as-mountpoint
Feature: For FUSE-Ts default mountpoint use vault id as mountpoint
2024-02-20 17:34:09 +01:00
Armin Schrenk 542d2fcfe1 only create winget PR for stable versions [ci skip] 2024-02-19 16:42:42 +01:00
mindmonkandGitHub 144e929896 Merge pull request #3335 from cryptomator/feature/center-unlock-dialog-on-mainstage
Center Unlock Success Dialog on Main Application Window
2024-02-19 15:05:28 +01:00
Armin Schrenk 803748f78d update code-sign-action [ci skip] 2024-02-19 14:46:58 +01:00
Armin Schrenk 3410e7243a Update minimal required macOS version for dmg
due to javafx update in 7d281e2878
2024-02-19 13:02:05 +01:00
Armin Schrenk 84732337ca removed GTK-2 support
due to javafx update in 7d281e2878
2024-02-19 12:17:11 +01:00
dependabot[bot]andGitHub c527808710 Bump the java-production-dependencies group with 4 updates (#3340) 2024-02-19 09:35:25 +00:00
Armin Schrenk 99c89dbf39 fix possible null comparsion 2024-02-19 10:22:22 +01:00
Jan-Peter Klein 829b64cd3d dialog centers on main app screen if mainWindow is hidden 2024-02-19 08:55:16 +01:00
Jan-Peter Klein f4007267fb simplified the code and removed the function 2024-02-16 17:31:20 +01:00
Armin Schrenk c82a0bfaf3 cleanup 2024-02-16 17:09:05 +01:00
Armin SchrenkandGitHub 251ad65344 Merge pull request #3327 from cryptomator/feature/3233-load-presets-background
Feature: Load LocationPresets in background and show indicator in UI
2024-02-16 16:21:01 +01:00
Jan-Peter Klein 155ba4607b introduced functionality to center the unlock screen within the main application window 2024-02-16 15:24:52 +01:00
Armin Schrenk 62f6865d3e cleanup created directories after mount
fails silently
2024-02-14 10:30:51 +01:00
Armin Schrenk ffb3290248 Merge branch 'develop' into feature/3272-vaultid-as-mountpoint 2024-02-14 10:29:30 +01:00
Armin Schrenk a013ae3d91 [ci skip] change mountPointDir for macOS-Dev-run profile
aligns production build
2024-02-14 10:29:14 +01:00
Armin Schrenk 19a954e677 use correct mountProvider 2024-02-14 10:05:56 +01:00
Armin Schrenk f262980acc closes #3272 2024-02-13 16:37:46 +01:00
Armin Schrenk 3b3aa18c92 fixes #3233
this time with gc prevention of a binding
2024-02-13 00:55:07 +01:00
Armin Schrenk c7b8bc89c2 also don't bother upload to av scan, if not published 2024-02-12 13:18:17 +01:00
Armin Schrenk 5fc981abd3 prevent automatic overwrite of already released files 2024-02-12 13:16:45 +01:00
Armin Schrenk 36ec1a5ebc update windows update url [ci skip] 2024-02-09 18:37:19 +01:00
Armin Schrenk be4aad4168 set default value for Windows debug flag 2024-02-09 17:03:22 +01:00
Armin Schrenk fcb940e29c update release section of appstream metainfo file [ci skip] 2024-02-09 14:52:10 +01:00
Armin Schrenk 4f3ca2a6c4 fix appimage build script [ci skip] 2024-02-09 12:56:33 +01:00
Armin Schrenk 27cd34bee0 Merge branch 'main' into develop [ci skip] 2024-02-09 12:53:16 +01:00
Armin Schrenk c07e51be51 Merge branch 'release/1.12.2' 2024-02-09 12:51:57 +01:00
Armin Schrenk 0421879b39 finalize 1.12.2 2024-02-09 12:51:19 +01:00
Armin Schrenk 2d627717a0 prepare 1.12.2 2024-02-09 10:59:33 +01:00
Armin Schrenk dc0e88a694 fixes #3319 2024-02-09 10:55:27 +01:00
Armin Schrenk a5e3630375 also specify version for winget release [ci skip] 2024-02-08 17:47:50 +01:00
Armin Schrenk 17335e8f70 shorten workflow name [ci skip] 2024-02-08 17:39:38 +01:00
Armin Schrenk 42dd2fba48 github cli needs special token in CI env [ci skip] 2024-02-08 17:37:50 +01:00
Armin Schrenk e1cca6427c Add ci job for releasing to winget 2024-02-08 17:34:02 +01:00
Armin Schrenk 42d3dbaa23 always use latest version of winfsp-uninstaller 2024-02-08 17:10:40 +01:00
Armin Schrenk 185d67c492 Merge branch 'main' into develop [ci skip] 2024-02-07 11:43:59 +01:00
Armin Schrenk a8af3c8b40 Merge branch 'release/1.12.1' 2024-02-07 11:42:58 +01:00
Armin Schrenk 038a7fac62 finalize 1.12.1 2024-02-07 11:42:37 +01:00
Armin Schrenk 48408fa40d prepare 1.12.1 2024-02-07 11:37:44 +01:00
Armin Schrenk 18a417667e Fixes #3315 2024-02-07 11:33:45 +01:00
JaniruTEC aab07b13e3 Changed version specifier for dependency-check
See: https://github.com/cryptomator/cryptofs/pull/202#discussion_r1453615249
2024-02-06 22:28:16 +01:00
Armin Schrenk 869e40e351 use latest coffeelibs-jdk release 2024-02-06 15:34:09 +01:00
Armin Schrenk bf91e3f15c Merge branch 'main' into develop [ci skip] 2024-02-06 14:29:19 +01:00
Armin Schrenk 17544828e4 Merge branch 'release/1.12.0' 2024-02-06 14:28:08 +01:00
Armin Schrenk cfef3139b1 finalize 1.12.0 2024-02-06 14:20:50 +01:00
Armin Schrenk b01ba27b06 Partially reverting 24293b316f 2024-02-06 14:16:03 +01:00
Armin Schrenk 5a33e66a7d prepare 1.12.0 2024-02-06 11:31:42 +01:00
Armin Schrenk 3c4e35406e Bump dependencies 2024-02-06 11:27:02 +01:00
CryptobotandGitHub d3275fa4e7 New Crowdin updates (#3256)
New translations strings.properties

Arabic; Bashkir; Belarusian; Bulgarian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Czech; Danish; Dutch; Filipino; French; German; Greek; Hebrew; Hungarian; Italian; Japanese; Korean; Latvian; Norwegian Bokmal; Polish; Portuguese; Portuguese, Brazilian; Punjabi; Romanian; Russian; Slovak; Spanish; Swahili, Tanzania; Swedish; Turkish; Vietnamese;

[ci skip]
2024-02-06 11:21:07 +01:00
dependabot[bot]andGitHub a30db34c19 Bump the java-production-dependencies group with 6 updates (#3295) 2024-02-06 10:18:33 +00:00
dependabot[bot]andGitHub 509100e7ae Bump the java-test-dependencies group with 2 updates (#3312) 2024-02-06 10:17:11 +00:00
dependabot[bot]andGitHub 024c34172b Bump the github-actions group with 5 updates (#3308) 2024-02-06 10:16:24 +00:00
dependabot[bot]andGitHub 638570562b Bump the maven-build-plugins group with 3 updates (#3300) 2024-02-06 10:15:06 +00:00
Sebastian StenzelandGitHub ee5a5c6563 Merge pull request #3291 from cryptomator/feature/legacy-device-migration
Hub 1.2.x → Hub 1.3.x Legacy Device Migration
2024-02-02 14:59:30 +01:00
Armin Schrenk 85c52c60c5 Revert "update javafx to 21.0.2"
This reverts commit 93184abd3c.
2024-02-02 10:45:34 +01:00
Armin Schrenk 93184abd3c update javafx to 21.0.2 2024-01-30 12:55:33 +01:00
Armin Schrenk 8062392ccd update jdk to 21.0.2+13 2024-01-30 12:17:45 +01:00
Jan-Peter Klein d49c02c7de optimize share vault fxml 2024-01-29 17:37:55 +01:00
Sebastian Stenzel d66cfe0e7c adjust to new migration API 2024-01-26 16:48:08 +01:00
mindmonkandGitHub fa26fa9dee Merge pull request #3289 from cryptomator/feature/share-vault
Feature: Introduce 'Share Vault' Functionality
2024-01-26 16:08:06 +01:00
Jan-Peter Klein 52fc94ca01 fixed a spelling error 2024-01-26 16:03:51 +01:00
Jan-Peter Klein e9a412ae16 improved layout and added styleClass for add-box 2024-01-26 15:42:25 +01:00
Jan-Peter Klein 8104c35d76 more specific translation keys and consistent spacing 2024-01-23 19:09:49 +01:00
Jan-Peter Klein baa2e05e69 improved wording and layout 2024-01-23 14:00:20 +01:00
Armin SchrenkandGitHub f50b204cef Merge pull request #3288 from cryptomator/feature/3246-split-device-registration
Feature: Split device registration into "normal" and "legacy" flow
2024-01-22 14:56:45 +01:00
lectricalandJaniruTEC ed902bc59f Hello 2024 2024-01-20 16:22:27 +01:00
Sebastian Stenzel 693299a5d7 first draft of legacy device migration
actual migration still missing due to API discussion
2024-01-20 13:28:56 +01:00
Sebastian Stenzel def64aa2ac added tests 2024-01-20 13:25:57 +01:00
Sebastian Stenzel c5bb8a131d dedup 2024-01-20 13:15:33 +01:00
Sebastian Stenzel 2e443c72a9 add new API encryptVaultKey(vaultKey, userKey)
and `decodeECPublicKey(byte[])`
2024-01-20 13:12:59 +01:00
Sebastian Stenzel e9ee17493b refactor API, expose decodeECPrivateKey(byte[]) 2024-01-20 13:01:48 +01:00
Sebastian Stenzel 35eb548d8e replaced Guava's BaseEncoding with Base64 2024-01-20 12:58:44 +01:00
Sebastian Stenzel dc5d6e734e deduplicate code 2024-01-19 17:55:36 +01:00
Sebastian Stenzel 729f38866f use string templates for URI construction 2024-01-19 17:40:39 +01:00
Jan-Peter Klein 4de8434f1b Merge branch 'develop' into feature/share-vault 2024-01-19 12:31:37 +01:00
Jan-Peter Klein cd40731b5f implemented open hub feature
optimized fxml files and img
2024-01-19 12:30:56 +01:00
Armin SchrenkandGitHub 24293b316f Merge pull request #3236 from cryptomator/feature/3233-locationpresets-background
Feature: Load LocationPresets in background and show indicator in UI
2024-01-19 10:51:46 +01:00
Armin Schrenk 062c674ef1 Always hide spinner, as soon as locationPresetLoading is finished 2024-01-19 10:44:06 +01:00
Armin SchrenkandGitHub 830bb5776f Use naming convention for comparator 2024-01-19 10:04:15 +01:00
Armin Schrenk bfdb23785e Merge branch 'develop' into feature/3233-locationpresets-background 2024-01-18 15:57:45 +01:00
Armin Schrenk 248d655afb ditch custom in-place sorting and rely on javafx methods 2024-01-18 15:45:45 +01:00
Armin Schrenk 995c0a4b92 remove unnecessary TODO 2024-01-17 23:30:41 +01:00
Armin Schrenk 42425afe56 closes #3246
splitting registerSuccess into two scenes, one for hub 1.3.x and one for legacy
2024-01-17 16:52:35 +01:00
Sebastian Stenzel a1b712495f switch back to UI thread later
see https://github.com/cryptomator/cryptomator/pull/3287#discussion_r1453196792
2024-01-17 13:35:23 +01:00
Sebastian Stenzel 2225476bf8 reordered methods
[ci skip]
2024-01-17 13:33:38 +01:00
Sebastian StenzelandGitHub 325057c548 Merge pull request #3287 from cryptomator/feature/refactored-hub-unlock
Refactored Hub Unlock
2024-01-17 13:32:09 +01:00
JaniruTECandGitHub b1a5eed2aa Merge pull request #3286 from cryptomator/refactoring/centralize-dependency-check
Externalize dependency-check
2024-01-16 19:54:32 +01:00
Jan-Peter Klein 4ca190cfb9 implemented a 'Share Vault' button in vault_detail_locked and added a new window for vault sharing options 2024-01-16 17:21:07 +01:00
GiwrgosTsifoutisandGitHub 828fd321cc Fixed "Locked Vault Options after unlocking vault #3249" (#3267)
Fixes #3249 

Bound the UIs disabled state to the vault's lock state without the need of a map
2024-01-16 12:32:36 +01:00
Armin Schrenk 2e32f39986 [ci skip] fix missing translation key
(cherry picked from commit daa026b285)
2024-01-16 11:38:54 +01:00
Armin Schrenk daa026b285 [ci skip] fix missing translation key 2024-01-16 11:38:33 +01:00
Sebastian Stenzel 87084e8c2a TODO now obsolete
as we will now re-attempt unlock when returning from device registration

[ci skip]
2024-01-16 04:14:06 +01:00
Sebastian Stenzel 8e52058373 re-attempt receiving key after registering device 2024-01-16 03:58:30 +01:00
Armin Schrenk 8bf0acf35f build release artifacts with javafx 21.0.1
references 7d281e2878
2024-01-15 16:35:34 +01:00
JaniruTECandGitHub e0d3a3d9c7 Externalized dependency-check 2024-01-15 16:12:27 +01:00
Sebastian Stenzel b59ce75ecd add step 0: check API level 2024-01-14 15:02:41 +01:00
Sebastian Stenzel 748f895b98 change unlock request order 2024-01-14 15:01:51 +01:00
Sebastian Stenzel 6d974c7fcf fix incorrect vaultId 2024-01-14 14:58:58 +01:00
Sebastian Stenzel 0ed73e8b41 use string templates for building /api/* URIs 2024-01-14 14:02:35 +01:00
Sebastian Stenzel 538b4ecd0b added javadoc 2024-01-14 13:59:23 +01:00
Armin Schrenk 8f32b46b30 set no jlink compression for build artifacts
references 00fc2a9837
2024-01-12 13:18:05 +01:00
Armin Schrenk 4de25afde0 wrap locations in scrollPane 2024-01-11 23:48:26 +01:00
Armin Schrenk 2e6c228f3f Merge branch 'develop' into feature/3233-locationpresets-background 2024-01-11 22:59:41 +01:00
Armin Schrenk 00fc2a9837 remove deprecate compress options and use default ones
see also https://bugs.openjdk.org/browse/JDK-8301124?jql=text%20~%20%22compress%22
2024-01-11 22:59:08 +01:00
Armin Schrenk 22e80e7dac update local appimage buildscript to download jmods 2024-01-11 22:56:41 +01:00
Armin Schrenk 517e05c378 use stable version of winfsp-uninstaller 2024-01-11 22:35:51 +01:00
Sebastian StenzelandGitHub 5759b7e596 Merge pull request #3210 from cryptomator/feature/3203-device-already-registered
Feature: Adjust Hub workflow, if device is already registerd
2024-01-11 12:05:25 +01:00
Sebastian Stenzel bfb26f6171 renamed field 2024-01-11 11:46:06 +01:00
Sebastian Stenzel f36e2bb4b7 reduced visibility 2024-01-11 11:45:57 +01:00
Sebastian Stenzel b85e455ff5 renamed field 2024-01-11 11:42:08 +01:00
Sebastian Stenzel a902400522 get rid of registerException
use separate views, no need to pass state between views
2024-01-11 11:23:54 +01:00
Sebastian Stenzel a6d6474294 Merge branch 'develop' into feature/3203-device-already-registered 2024-01-11 11:02:45 +01:00
Sebastian Stenzel 06859fe36b remove newline
[ci skip]
2024-01-11 10:27:42 +01:00
Sebastian StenzelandGitHub 7d7b7c1dc9 Merge pull request #3260 from cryptomator/feature/annihilate-FUPFMS
annihilate FUPFMS
2024-01-11 10:25:21 +01:00
Sebastian Stenzel 706d2b51be remove added newline
[ci skip]
2024-01-11 10:24:28 +01:00
Armin SchrenkandGitHub 92bf136a8b Merge pull request #3278 from mooons/issue-3277
Bump README's build dependency JDK version from 19 to 21
2024-01-09 12:03:21 +01:00
mooons f00f028d8b Bump README's build dependency JDK version from 19 to 21. Fix #3277 2024-01-09 01:57:59 -08:00
dependabot[bot]andGitHub 325092efb0 Bump the java-test-dependencies group with 1 update (#3238) 2023-12-26 04:32:24 +00:00
dependabot[bot]andGitHub aaf8036713 Bump the maven-build-plugins group with 3 updates (#3264) 2023-12-25 19:40:21 +00:00
Sebastian Stenzel c86ee679a9 renamed fuse-specific exception to be more generic 2023-12-21 10:05:22 +01:00
Sebastian Stenzel a0fcb63a1a replaced FUPFMS
with more generic set of used mount services
2023-12-21 09:40:43 +01:00
Armin SchrenkandGitHub 55ba255651 Update dependency-check.yml
to not run into 403 due to rate limit
2023-12-18 10:49:47 +01:00
dependabot[bot]andGitHub 47df4213c3 Bump the java-production-dependencies group with 5 updates (#3255) 2023-12-18 09:40:34 +00:00
Armin Schrenk 6929760979 adjust dependency check plugin 2023-12-13 16:47:30 +01:00
Armin Schrenk 720fbd0e6b use separate cache for dependency-cache data 2023-12-13 16:47:08 +01:00
mindmonkandGitHub 923af4bc83 Merge pull request #3199 from cryptomator/feature/vault-volume-type
Feature: Customizable Volume Type Options for Each Vault
2023-12-13 16:16:08 +01:00
Jan-Peter Klein e9c464ba8f sonar cloud issues solved 2023-12-13 15:56:39 +01:00
Jan-Peter Klein 7b0f616747 Merge branch 'develop' into feature/vault-volume-type 2023-12-13 15:50:55 +01:00
Jan-Peter Klein 65788f3c1f pr mentioned changes 2023-12-13 15:50:37 +01:00
Jan-Peter Klein f55fbf0f64 code cleanup and optimized a description text 2023-12-13 09:25:50 +01:00
Armin Schrenk 851838e3a2 remove run of dependency-chec workflow for release branches (already done in check release workflow) 2023-12-12 14:58:48 +01:00
Armin Schrenk e194f9b205 update dependency check and refactor to own workflow 2023-12-12 14:56:48 +01:00
Jan-Peter Klein 2f8df88ad5 Merge branch 'develop' into feature/vault-volume-type
# Conflicts:
#	src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java
2023-12-12 12:48:59 +01:00
Jan-Peter Klein 55247d9f8a moved provideDefaultMountService to MountModule and renamed problematicFuseMountServices to CONFLICTING_MOUNT_SERVICES 2023-12-12 12:38:33 +01:00
dependabot[bot]andGitHub 48f273b755 Bump ch.qos.logback:logback-core from 1.4.13 to 1.4.14 (#3251) 2023-12-12 08:48:54 +00:00
Jan-Peter Klein 4fc31cb13f refactored 'loopbackPortSupported' from 'BooleanExpression' to 'ObservableValue<Boolean>' in VolumePreferencesController and MountOptionsController 2023-12-11 15:16:41 +01:00
Jan-Peter Klein a795b82a42 vault options ui change 2023-12-11 14:49:54 +01:00
Jan-Peter Klein 1e28a04a7b renamed string properties 2023-12-11 10:16:26 +01:00
Jan-Peter Klein b88eef4591 renamed string property 2023-12-08 12:03:08 +01:00
Jan-Peter Klein eef5edf775 code cleanup 2023-12-08 11:14:54 +01:00
Armin Schrenk bdb1d605eb Add cloud vendor to example of involved software 2023-12-07 18:43:10 +01:00
Armin Schrenk dfe4b74f3d clean code:
* remove unused methods
* combine or move methods
2023-12-07 17:48:58 +01:00
Jan-Peter Klein 985a3c30de changed string properties for volume type labels 2023-12-07 13:56:04 +01:00
Jan-Peter Klein 1b5125dfed refactored code, global default volume type settings 2023-12-07 10:48:33 +01:00
Jan-Peter Klein da240ce8b9 optimized error message 2023-12-07 08:40:39 +01:00
Jan-Peter Klein 52affb891e refactored mount method 2023-12-06 16:41:36 +01:00
Jan-Peter Klein fac72ca24a undo remove settings port 2023-12-06 09:44:05 +01:00
Jan-Peter Klein 5cbed502ed undo VaultListManager changes 2023-12-06 08:49:21 +01:00
Jan-Peter Klein dce4c60881 removed pr unrelated changes and code cleanup 2023-12-06 08:40:43 +01:00
Jan-Peter Klein 63bf0315c7 created global mountService 2023-12-05 16:21:12 +01:00
Armin Schrenk 8b543c48ee [ci skip] Merge branch 'main' into develop 2023-12-05 11:11:31 +01:00
dependabot[bot]andGitHub a07dea7ca8 Bump the github-actions group with 2 updates (#3235) 2023-12-05 08:39:49 +00:00
Jan-Peter Klein c369487e8e Merge branch 'develop' into feature/vault-volume-type 2023-12-01 09:13:38 +01:00
Jan-Peter Klein 98590ecec5 refactoring 2023-12-01 09:11:43 +01:00
Armin SchrenkandGitHub 316f3975ed add missing import 2023-11-30 18:24:52 +01:00
Armin SchrenkandGitHub 71bf7e0913 Simplify handling of window hiding events 2023-11-30 18:22:07 +01:00
dependabot[bot]andGitHub c7839e2c46 Bump the java-production-dependencies group with 2 updates (#3234) 2023-11-30 14:26:07 +00:00
Armin Schrenk 89b8bc4148 adjust ui and add localization 2023-11-30 11:44:41 +01:00
Armin Schrenk 43ad1c05c0 cancel background task, if window is closed 2023-11-30 11:18:33 +01:00
Armin Schrenk 1343099be6 [ci skip] correct dependabot config 2023-11-30 10:03:29 +01:00
Armin Schrenk b895ac69fb fixes #3233 2023-11-29 18:45:52 +01:00
dependabot[bot]andGitHub 7d281e2878 Bump the javafx group with 5 updates (#3229) 2023-11-29 15:08:31 +00:00
Jan-Peter Klein 6a704ca0ad Merge branch 'develop' into feature/vault-volume-type 2023-11-27 16:24:35 +01:00
Jan-Peter Klein 38c102a64b refactored code by removing unnecessary observables 2023-11-27 12:46:29 +01:00
Armin Schrenk b632c84bb6 more renaming 2023-11-24 11:23:22 +01:00
Jan-Peter Klein 1052e4c3d2 fix gui issues in vault mount settings 2023-11-23 10:55:17 +01:00
44f776050f Apply suggestions from code review
Co-authored-by: Tobias Hagemann <tobias.hagemann@skymatic.de>
2023-11-22 21:36:15 +01:00
Armin Schrenk baa29dbd73 remove unused translation key 2023-11-17 16:33:53 +01:00
Armin Schrenk 1caf391936 use correct word for button label 2023-11-17 16:24:23 +01:00
Armin Schrenk 82caf76e12 clean up 2023-11-17 15:51:20 +01:00
Armin Schrenk de27bbf96e always cancle unlock result when closing the unlock window 2023-11-17 15:50:39 +01:00
Armin Schrenk 1199ef40dd on 409 repsonse of registerDevice-request, show setupFailed with adjusted message 2023-11-17 15:47:08 +01:00
Jan-Peter Klein e7e88f13e3 improved code quality 2023-11-17 14:51:13 +01:00
Armin Schrenk 41c22b7840 Rename leftovers 2023-11-17 13:30:40 +01:00
Jan-Peter Klein 7bc1d52bf8 set deprecated since of settingsjson port to 1.12.0, changed button text and added tooltip to 'show vault options' button 2023-11-12 13:49:47 +01:00
Jan-Peter Klein ef9cdd8e92 Merge branch 'develop' into feature/vault-volume-type
# Conflicts:
#	src/main/resources/i18n/strings.properties
2023-11-08 11:45:03 +01:00
Jan-Peter Klein 2f657c7c14 migrate port to vault settings 2023-11-07 12:42:07 +01:00
Jan-Peter Klein ab9278a9fc Merge branch 'develop' into feature/vault-volume-type 2023-11-03 10:48:00 +01:00
Jan-Peter Klein 49769f986a remove port from settings 2023-11-03 10:47:43 +01:00
Jan-Peter Klein b68b895bba removed tcp port from volume preferences controller 2023-11-02 16:53:29 +01:00
Jan-Peter Klein 77f9e6c411 some ui and wording changes 2023-11-02 13:47:58 +01:00
Jan-Peter Klein 31fcf294e9 fix misbehaivour 2023-11-01 14:59:25 +01:00
Jan-Peter Klein a03f00ee9a set up default mount service as pre fallbackProvider 2023-10-31 20:29:54 +01:00
Jan-Peter Klein 80af32bd82 introduced FuseRestartRequiredException and UnlockFuseRestartRequired window 2023-10-30 17:05:09 +01:00
Jan-Peter Klein b7e4c0fe31 set default mount service and port while adding a vault 2023-10-30 15:27:11 +01:00
Jan-Peter Klein 76d499596f Merge branch 'develop' into feature/vault-volume-type 2023-10-30 14:33:32 +01:00
Jan-Peter Klein 1818344c49 different labeling for volume type option in general volume preferences and vault options 2023-10-30 14:33:01 +01:00
Jan-Peter Klein e79c975d7b prevent mounting via MountFailedException when fuseRestartRequired 2023-10-27 12:39:25 +02:00
Jan-Peter Klein c85823fe04 code cleanup 2023-10-26 12:18:40 +02:00
Jan-Peter Klein 8f46aec851 created MountServiceConverter class 2023-10-26 10:35:16 +02:00
Jan-Peter Klein cd6d1eeab3 moved fupfms to VaultModule 2023-10-26 10:25:11 +02:00
Jan-Peter Klein 52b74a74fe display specific mount options according to selected volume type in vault mounting options 2023-10-25 14:02:29 +02:00
Jan-Peter Klein 368f0630c1 some sonar cloud fixes 2023-10-25 12:44:22 +02:00
Jan-Peter Klein 040bc4c5df fix sonar cloud issues 2023-10-24 14:26:06 +02:00
Jan-Peter Klein 07906fbea5 modified mounter to utilize the mount service settings specified in the vault settings 2023-10-24 14:04:53 +02:00
Jan-Peter Klein 44f67b7c89 Merge branch 'develop' into feature/vault-volume-type 2023-10-20 15:58:52 +02:00
Jan-Peter Klein 44ec19122a added port and mountService to vaultSettings 2023-09-12 02:19:08 +02:00
Jan-Peter Klein 8ba2540b35 implemented volume type selection in 'vault options mount' 2023-09-11 14:23:31 +02:00
149 changed files with 3514 additions and 996 deletions
+1
View File
@@ -26,6 +26,7 @@ body:
Examples:
- Operating System: Windows 10
- Cryptomator: 1.5.16
- OneDrive: 23.226
- LibreOffice: 7.1.4
value: |
- Operating System:
+1 -1
View File
@@ -6,7 +6,7 @@ updates:
interval: "weekly"
day: "monday"
time: "06:00"
timezone: "UTC"
timezone: "Etc/UTC"
groups:
java-test-dependencies:
patterns:
+11 -15
View File
@@ -11,7 +11,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '21.0.1+12'
JAVA_VERSION: '21.0.2+13'
jobs:
get-version:
@@ -29,16 +29,16 @@ jobs:
include:
- os: ubuntu-latest
appimage-suffix: x86_64
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
openjfx-sha: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
openjfx-sha: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
- os: [self-hosted, Linux, ARM64]
appimage-suffix: aarch64
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
openjfx-sha: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
openjfx-sha: '871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -74,6 +74,7 @@ jobs:
cp LICENSE.txt target
cp target/cryptomator-*.jar target/mods
- name: Run jlink
#Remark: no compression is applied for improved build compression later (here appimage)
run: >
${JAVA_HOME}/bin/jlink
--verbose
@@ -84,12 +85,7 @@ jobs:
--no-header-files
--no-man-pages
--strip-debug
--compress=1
- 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 }}
--compress zip-0
- name: Run jpackage
run: >
${JAVA_HOME}/bin/jpackage
@@ -102,7 +98,7 @@ jobs:
--dest appdir
--name Cryptomator
--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 }}"
--java-options "--enable-preview"
--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.zsync
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: appimage-${{ matrix.appimage-suffix }}
path: |
@@ -172,7 +168,7 @@ jobs:
cryptomator-*.asc
if-no-files-found: error
- 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
with:
fail_on_unmatched_files: true
+40
View File
@@ -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 }}
+2 -2
View File
@@ -19,13 +19,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Cache SonarCloud packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
outputs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
steps:
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
java-version: ${{ env.JDK_VERSION }}
distribution: ${{ env.JDK_VENDOR }}
@@ -32,7 +32,7 @@ jobs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
jdk-version: ${{ steps.get-data.outputs.jdk-version}}
steps:
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
java-version: 21
distribution: ${{ env.JDK_VENDOR }}
+10 -11
View File
@@ -17,13 +17,13 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '21.0.1+12'
JAVA_VERSION: '21.0.2+13'
COFFEELIBS_JDK: 21
COFFEELIBS_JDK_VERSION: '21.0.1+12-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
COFFEELIBS_JDK_VERSION: '21.0.2+13-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: '871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
jobs:
build:
@@ -44,9 +44,9 @@ jobs:
run: |
sudo add-apt-repository ppa:coffeelibs/openjdk
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
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -128,7 +128,7 @@ jobs:
run: |
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: linux-deb-package
path: |
@@ -142,10 +142,9 @@ jobs:
- name: Publish on PPA
if: inputs.dput
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
# If ref is a tag, also upload to GitHub Releases:
- name: Publish Debian package on GitHub Releases
if: startsWith(github.ref, 'refs/tags/')
if: startsWith(github.ref, 'refs/tags/') && inputs.dput
env:
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
run: |
+17
View File
@@ -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 }}
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Get download count of latest releases
id: get-stats
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
const query = `query($owner:String!, $name:String!) {
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Query Discussion Data
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
id: query-data
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
+88
View File
@@ -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
+3 -3
View File
@@ -39,7 +39,7 @@ jobs:
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -50,7 +50,7 @@ jobs:
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
SEM_VER_STR="${{ inputs.version }}"
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
@@ -73,4 +73,4 @@ jobs:
- name: Validate Version
uses: skymatic/semver-validation-action@v2
with:
version: ${{ steps.versions.outputs.semVerStr }}
version: ${{ steps.versions.outputs.semVerStr }}
+14 -11
View File
@@ -16,7 +16,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '21.0.1+12'
JAVA_VERSION: '21.0.2+13'
jobs:
get-version:
@@ -37,19 +37,19 @@ jobs:
output-suffix: x64
xcode-path: '/Applications/Xcode_13.2.1.app'
fuse-lib: macFUSE
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-x64_bin-jmods.zip'
openjfx-sha: '55b8ff7453d59c89ae129f6c9c5ad7b09a5d359568811b376ac1766c14d6a17c'
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-x64_bin-jmods.zip'
openjfx-sha: 'bd6abab20da73d5a968dcf2fd915d81b5fb919340e3bb84979ee9a888a829939'
- os: [self-hosted, macOS, ARM64]
architecture: aarch64
output-suffix: arm64
xcode-path: '/Applications/Xcode_13.2.1.app'
fuse-lib: FUSE-T
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip'
openjfx-sha: 'c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf'
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-aarch64_bin-jmods.zip'
openjfx-sha: '7afaa1c57a6cc3c384d636e597b9a5364693e2db4aaec0a6e63d2fa964400b58'
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -85,6 +85,7 @@ jobs:
cp LICENSE.txt target
cp target/cryptomator-*.jar target/mods
- name: Run jlink
#Remark: no compression is applied for improved build compression later (here dmg)
run: >
${JAVA_HOME}/bin/jlink
--verbose
@@ -95,7 +96,7 @@ jobs:
--no-header-files
--no-man-pages
--strip-debug
--compress=1
--compress zip-0
- name: Run jpackage
run: >
${JAVA_HOME}/bin/jpackage
@@ -108,7 +109,7 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2023 Skymatic GmbH"
--copyright "(C) 2016 - 2024 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
@@ -249,13 +250,15 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: dmg-${{ matrix.output-suffix }}
path: Cryptomator-*.dmg
path: |
Cryptomator-*.dmg
Cryptomator-*.asc
if-no-files-found: error
- 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
with:
fail_on_unmatched_files: true
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
days-before-stale: 14
days-before-close: 0
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
+28 -3
View File
@@ -10,12 +10,22 @@ defaults:
run:
shell: bash
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: 21
jobs:
release-check-precondition:
check-preconditions:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest
steps:
- 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
name: Validate POM version
run: |
@@ -34,7 +44,22 @@ jobs:
fi
- name: Validate release in org.cryptomator.Cryptomator.metainfo.xml file
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"
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 }}
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
days-before-stale: 365
days-before-close: 90
+67 -67
View File
@@ -11,15 +11,16 @@ on:
isDebug:
description: 'Build debug version with console output'
type: boolean
default: false
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '21.0.1+12'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
JAVA_VERSION: '21.0.2+13'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a'
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:
run:
@@ -41,7 +42,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -79,6 +80,7 @@ jobs:
cp LICENSE.txt target
cp target/cryptomator-*.jar target/mods
- name: Run jlink
#Remark: no compression is applied for improved build compression later (here msi)
run: >
${JAVA_HOME}/bin/jlink
--verbose
@@ -89,7 +91,7 @@ jobs:
--no-header-files
--no-man-pages
--strip-debug
--compress=1
--compress zip-0
- name: Change win-console flag if debug is active
if: ${{ inputs.isDebug }}
run: echo "WIN_CONSOLE_FLAG=--win-console" >> $GITHUB_ENV
@@ -105,7 +107,7 @@ jobs:
--dest appdir
--name Cryptomator
--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 }}"
--java-options "--enable-preview"
--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"
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
- name: Codesign
uses: skymatic/code-sign-action@v2
uses: skymatic/code-sign-action@v3
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
@@ -213,12 +215,12 @@ jobs:
--dest installer
--name Cryptomator
--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}}"
--win-menu
--win-dir-chooser
--win-shortcut-prompt
--win-update-url "https:\\cryptomator.org"
--win-update-url "https:\\cryptomator.org\downloads"
--win-menu-group Cryptomator
--resource-dir dist/win/resources
--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_WIXHELPER_DIR: ${{ github.workspace }}\appdir
- name: Codesign MSI
uses: skymatic/code-sign-action@v2
uses: skymatic/code-sign-action@v3
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
@@ -245,22 +247,13 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: msi
path: |
Cryptomator-*.msi
Cryptomator-*.asc
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:
name: Build .exe installer
@@ -269,13 +262,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Download .msi
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: msi
path: dist/win/bundle/resources
- name: Strip version info from msi file name
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:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -308,7 +301,7 @@ jobs:
-out dist/win/bundle/
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
-dBundleVendor="Skymatic GmbH"
-dBundleCopyright="(C) 2016 - 2023 Skymatic GmbH"
-dBundleCopyright="(C) 2016 - 2024 Skymatic GmbH"
-dAboutUrl="https://cryptomator.org"
-dHelpUrl="https://cryptomator.org/contact"
-dUpdateUrl="https://cryptomator.org/downloads/"
@@ -324,7 +317,7 @@ jobs:
-ib installer/unsigned/Cryptomator-Installer.exe
-o tmp/engine.exe
- name: Codesign burn engine
uses: skymatic/code-sign-action@v2
uses: skymatic/code-sign-action@v3
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
@@ -338,7 +331,7 @@ jobs:
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
-o installer/Cryptomator-Installer.exe
- name: Codesign EXE
uses: skymatic/code-sign-action@v2
uses: skymatic/code-sign-action@v3
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
@@ -356,59 +349,66 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: exe
path: |
Cryptomator-*.exe
Cryptomator-*.asc
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
if: startsWith(github.ref, 'refs/tags/')
id: publish
uses: softprops/action-gh-release@v1
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
# do not change ordering of filelist, required for correct job output
files: |
Cryptomator-*.exe
Cryptomator-*.asc
*.msi
*.exe
*.asc
allowlist:
name: Anti Virus Allowlisting
if: startsWith(github.ref, 'refs/tags/')
allowlist-msi:
uses: ./.github/workflows/av-whitelist.yml
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
needs: [build-msi, build-exe]
steps:
- name: Download .msi
uses: actions/download-artifact@v3
with:
name: msi
path: msi
- name: Download .exe
uses: actions/download-artifact@v3
with:
name: exe
path: exe
- name: Collect files
run: |
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/
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "MSI of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/winget.yml| release to winget>."
SLACK_FOOTER: false
MSG_MINIMAL: true
+27
View File
@@ -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
View File
@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -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=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
<method v="2">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -86,7 +86,7 @@ For more information on the security details visit [cryptomator.org](https://doc
### Dependencies
* JDK 19 (e.g. temurin)
* JDK 21 (e.g. temurin, zulu)
* Maven 3
### Run Maven
+36 -7
View File
@@ -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
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 unzip >/dev/null 2>&1 || { echo >&2 "unzip not found."; exit 1; }
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
SEMVER_STR=${VERSION}
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}
# compile
@@ -20,20 +23,47 @@ mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
cp ../../../LICENSE.txt ../../../target
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
${JAVA_HOME}/bin/jlink \
--verbose \
--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 \
--strip-native-commands \
--no-header-files \
--no-man-pages \
--strip-debug \
--compress=1
--compress zip-0
# create app dir
envsubst '${SEMVER_STR} ${REVISION_NUM}' < ../launcher-gtk2.properties > launcher-gtk2.properties
${JAVA_HOME}/bin/jpackage \
--verbose \
--type app-image \
@@ -46,7 +76,7 @@ ${JAVA_HOME}/bin/jpackage \
--vendor "Skymatic GmbH" \
--java-options "--enable-preview" \
--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 "-Xmx256m" \
--app-version "${VERSION}.${REVISION_NO}" \
@@ -61,7 +91,6 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.showTrayIcon=true" \
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \
--add-launcher cryptomator-gtk2=launcher-gtk2.properties \
--resource-dir ../resources
# transform AppDir
@@ -97,5 +126,5 @@ chmod +x /tmp/appimagetool.AppImage
echo ""
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage"
echo ""
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
echo ""
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir runtime squashfs-root openjfx-jmods; rm /tmp/appimagetool.AppImage openjfx-jmods.zip"
echo ""
+110 -42
View File
@@ -5,26 +5,27 @@
<metadata_license>FSFAP</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<name>Cryptomator</name>
<summary>Multi-platform client-side encryption tool optimized for cloud storages</summary>
<summary>Encryption made easy and optimized for the cloud</summary>
<description>
<p>
Cryptomator provides transparent, client-side encryption for your cloud. Protect your documents from unauthorized
access. Cryptomator is free and open source software, so you can rest assured there are no backdoors.
Cryptomator provides easy-to-use, transparent, client-side encryption for your cloud.
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>
Cryptomator encrypts file contents and names using AES. Your passphrase is protected against bruteforcing attempts
using scrypt. Directory structures get obfuscated. The only thing which cannot be encrypted without breaking your
cloud synchronization is the modification date of your files.
Cryptomator encrypts file contents and names using the widespread industry standard AES.
Your passphrase is protected against brute forcing attempts using scrypt.
Additionally, directory structures get obfuscated.
For more info about the Cryptomator encryption scheme, check out the online documentation.
</p>
<p>
Cryptomator is a free and open source software licensed under the GPLv3. This allows anyone to check our code. It
is impossible to introduce backdoors for third parties. Also we cannot hide vulnerabilities. And the best thing
is: There is no need to trust us, as you can control us!
</p>
<p>
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.
Cryptomator is a free and open-source software licensed under the GPLv3.
This allows anyone to check our code.
Thus, it is impossible to introduce backdoors for third parties or to hide vulnerabilities, so you do not need to trust Cryptomator.
Also, vendor lock-ins are impossible.
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.
</p>
</description>
@@ -42,7 +43,7 @@
</provides>
<screenshots>
<screenshot>
<screenshot type="default">
<caption>Light theme</caption>
<image>https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png</image>
</screenshot>
@@ -56,41 +57,108 @@
<url type="bugtracker">https://github.com/cryptomator/cryptomator/issues/</url>
<url type="donation">https://cryptomator.org/donate</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>
<developer_name>Skymatic GmbH</developer_name>
<developer id="de.skymatic">
<name>Skymatic GmbH</name>
</developer>
<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_rating>
<releases>
<release date="2023-12-05" version="1.11.1"/>
<release date="2023-11-08" version="1.11.0"/>
<release date="2023-09-20" version="1.10.1"/>
<release date="2023-09-11" version="1.10.0"/>
<release date="2023-08-11" version="1.9.4"/>
<release date="2023-08-07" version="1.9.3"/>
<release date="2023-07-24" version="1.9.2"/>
<release date="2023-06-07" version="1.9.1"/>
<release date="2023-05-30" version="1.9.0"/>
<release date="2023-04-25" version="1.8.0"/>
<release date="2023-04-07" version="1.7.5"/>
<release date="2023-04-05" version="1.7.4"/>
<release date="2023-03-15" version="1.7.3"/>
<release date="2023-03-07" version="1.7.2"/>
<release date="2023-03-03" version="1.7.1"/>
<release date="2023-03-01" version="1.7.0"/>
<release date="2022-12-14" version="1.6.17"/>
<release date="2022-12-06" version="1.6.16"/>
<release date="2022-10-06" version="1.6.15"/>
<release date="2022-08-31" version="1.6.14"/>
<release date="2022-07-27" version="1.6.12"/>
<release date="2022-07-26" version="1.6.11"/>
<release date="2022-05-03" version="1.6.10"/>
<release date="2022-04-27" version="1.6.9"/>
<release date="2022-03-30" version="1.6.8"/>
<release date="2021-12-16" version="1.6.5"/>
<release date="2024-02-27" version="1.12.3">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.3</url>
</release>
<release date="2024-02-09" version="1.12.2">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.2</url>
</release>
<release date="2024-02-07" version="1.12.1">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.1</url>
</release>
<release date="2024-02-06" version="1.12.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.0</url>
</release>
<release date="2023-12-05" version="1.11.1">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.11.1</url>
</release>
<release date="2023-11-08" version="1.11.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.11.0</url>
</release>
<release date="2023-09-20" version="1.10.1">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.10.1</url>
</release>
<release date="2023-09-11" version="1.10.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.10.0</url>
</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>
</component>
+1 -1
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils
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
Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git
+2 -2
View File
@@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator <info@cryptomator.org>
Source: https://cryptomator.org
Files: *
Copyright: 2016-2023 Skymatic GmbH
Copyright: 2016-2024 Skymatic GmbH
License: GPL-3+
Files: debian/org.cryptomator.Cryptomator.appdata.xml
Copyright: 2016-2023 Skymatic GmbH
Copyright: 2016-2024 Skymatic GmbH
License: FSFAP
License: GPL-3+
+3 -2
View File
@@ -24,6 +24,7 @@ override_dh_auto_clean:
override_dh_auto_build:
mkdir resources
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 \
--output runtime \
--module-path "${JMODS_PATH}" \
@@ -32,7 +33,7 @@ override_dh_auto_build:
--no-header-files \
--no-man-pages \
--strip-debug \
--compress=2
--compress zip-0
$(JAVA_HOME)/bin/jpackage \
--type app-image \
--runtime-image runtime \
@@ -44,7 +45,7 @@ override_dh_auto_build:
--vendor "Skymatic GmbH" \
--java-options "--enable-preview" \
--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 "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
-14
View File
@@ -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
+3 -3
View File
@@ -21,7 +21,7 @@ rm -rf runtime dmg *.app *.dmg
# set variables
APP_NAME="Cryptomator"
VENDOR="Skymatic GmbH"
COPYRIGHT_YEARS="2016 - 2023"
COPYRIGHT_YEARS="2016 - 2024"
PACKAGE_IDENTIFIER="org.cryptomator"
MAIN_JAR_GLOB="cryptomator-*.jar"
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
@@ -35,7 +35,7 @@ if [ "$(machine)" = "arm64e" ]; then
else
ARCH="x64"
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
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-man-pages \
--strip-debug \
--compress=1
--compress zip-0
# create app dir
${JAVA_HOME}/bin/jpackage \
+1 -1
View File
@@ -17,7 +17,7 @@
\f1\b0 \
\
\f0\b \'a9 2016 \'96 2023 Skymatic GmbH
\f0\b \'a9 2016 \'96 2024 Skymatic GmbH
\f1\b0 \
\
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\
+1 -1
View File
@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<string>11</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleAllowMixedLocalizations</key>
+5 -5
View File
@@ -51,9 +51,9 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
}
## 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"
$jfxJmodsChecksum = '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
$jfxJmodsChecksum = 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a'
$jfxJmodsZip = '.\resources\jfxJmods.zip'
if( !(Test-Path -Path $jfxJmodsZip) ) {
Write-Output "Downloading ${jmodsUrl}..."
@@ -69,7 +69,7 @@ Expand-Archive -Path $jfxJmodsZip -Force -DestinationPath ".\resources\"
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods"
Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
## create custom runtime
& "$Env:JAVA_HOME\bin\jlink" `
--verbose `
--output runtime `
@@ -79,7 +79,7 @@ Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\ja
--no-header-files `
--no-man-pages `
--strip-debug `
--compress=1
--compress "zip-0" #do not compress to have improved msi compression
$appPath = ".\$AppName"
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
# 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}..."
Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default
+3
View File
@@ -31,6 +31,7 @@
</BootstrapperApplicationRef>
<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"
SourceFile="resources\winfsp-uninstaller.exe"
DisplayName="Removing outdated WinFsp Driver"
@@ -51,11 +52,13 @@ Do you want to continue?&quot;" RepairArgument="-q" UninstallArgument="-s" />
</ExePackage>
<!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/msipackage.html-->
<MsiPackage
Cache="yes"
SourceFile="resources\Cryptomator.msi"
CacheId="cryptomator-bundle-cryptomator"
DisplayInternalUI="no"
Visible="no"/>
<MsiPackage
Cache="yes"
SourceFile="resources\winfsp.msi"
CacheId="cryptomator-bundle-winfsp"
Visible="yes"
+1 -1
View File
@@ -10,7 +10,7 @@
\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
\par
\b\'a9 2016 \'96 2023 Skymatic GmbH \b0\par
\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par
\par
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par
\par
+1 -1
View File
@@ -10,7 +10,7 @@
\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
\par
\b\'a9 2016 \'96 2023 Skymatic GmbH \b0\par
\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par
\par
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par
\par
+24 -22
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.11.1</version>
<version>1.13.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -33,44 +33,44 @@
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.6.8</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.3.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.4</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.4.0-beta2</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>4.0.0-beta5</cryptomator.fuse.version>
<cryptomator.cryptofs.version>2.6.9</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.3.1</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.5</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.3</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.4.4</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>4.0.0</cryptomator.fuse.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 -->
<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>
<guava.version>32.1.3-jre</guava.version>
<jackson.version>2.16.0</jackson.version>
<javafx.version>20.0.2</javafx.version>
<guava.version>33.0.0-jre</guava.version>
<jackson.version>2.16.1</jackson.version>
<javafx.version>21.0.1</javafx.version>
<jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.37.1</nimbus-jose.version>
<logback.version>1.4.12</logback.version>
<slf4j.version>2.0.9</slf4j.version>
<nimbus-jose.version>9.37.3</nimbus-jose.version>
<logback.version>1.5.0</logback.version>
<slf4j.version>2.0.12</slf4j.version>
<tinyoauth2.version>0.8.0</tinyoauth2.version>
<zxcvbn.version>1.8.2</zxcvbn.version>
<!-- test dependencies -->
<junit.jupiter.version>5.10.1</junit.jupiter.version>
<mockito.version>5.7.0</mockito.version>
<junit.jupiter.version>5.10.2</junit.jupiter.version>
<mockito.version>5.10.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
<!-- build-time dependencies -->
<jetbrains.annotations.version>24.1.0</jetbrains.annotations.version>
<dependency-check.version>9.0.1</dependency-check.version>
<dependency-check.version>9.0.9</dependency-check.version>
<jacoco.version>0.8.11</jacoco.version>
<license-generator.version>2.3.0</license-generator.version>
<license-generator.version>2.4.0</license-generator.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-dependency.version>3.6.1</mvn-dependency.version>
<mvn-surefire.version>3.2.2</mvn-surefire.version>
<mvn-surefire.version>3.2.5</mvn-surefire.version>
<mvn-jar.version>3.3.0</mvn-jar.version>
</properties>
@@ -460,17 +460,19 @@
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<configuration>
<cveValidForHours>24</cveValidForHours>
<nvdValidForHours>24</nvdValidForHours>
<failBuildOnCVSS>0</failBuildOnCVSS>
<skipTestScope>true</skipTestScope>
<detail>true</detail>
<suppressionFile>suppression.xml</suppressionFile>
<nvdApiKey>${env.NVD_API_KEY}</nvdApiKey>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>validate</phase>
</execution>
</executions>
</plugin>
@@ -5,10 +5,8 @@
*******************************************************************************/
package org.cryptomator.common;
import com.tobiasdiez.easybind.EasyBind;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.keychain.KeychainModule;
import org.cryptomator.common.mount.MountModule;
import org.cryptomator.common.settings.Settings;
@@ -22,8 +20,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Comparator;
@@ -136,13 +132,4 @@ public abstract class CommonsModule {
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 org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.mount.Mount;
import org.cryptomator.integrations.mount.MountService;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Module
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
@Singleton
static List<MountService> provideSupportedMountServices() {
@@ -27,46 +24,18 @@ public class MountModule {
@Provides
@Singleton
@Named("FUPFMS")
static AtomicReference<MountService> provideFirstUsedProblematicFuseMountService() {
return new AtomicReference<>(null);
static ObservableValue<MountService> provideDefaultMountService(List<MountService> mountProviders, Settings settings) {
var fallbackProvider = mountProviders.stream().findFirst().get(); //there should always be a mount provider, at least webDAV
return ObservableUtil.mapWithDefault(settings.mountService, //
serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), //
fallbackProvider);
}
@Provides
@Singleton
static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls, @Named("FUPFMS") AtomicReference<MountService> fupfms) {
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
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;
@Named("usedMountServices")
static Set<MountService> provideSetOfUsedMountServices() {
return ConcurrentHashMap.newKeySet();
}
//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.MountFailedException;
import org.cryptomator.integrations.mount.MountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.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_TO_EXISTING_DIR;
@@ -24,24 +30,41 @@ import static org.cryptomator.integrations.mount.MountCapability.UNMOUNT_FORCED;
@Singleton
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 Settings settings;
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
public Mounter(Settings settings, Environment env, WindowsDriveLetters driveLetters, ObservableValue<ActualMountService> mountServiceObservable) {
this.settings = settings;
public Mounter(Environment env, //
Settings settings, //
WindowsDriveLetters driveLetters, //
List<MountService> mountProviders, //
@Named("usedMountServices") Set<MountService> usedMountServices, //
ObservableValue<MountService> defaultMountService) {
this.env = env;
this.settings = settings;
this.driveLetters = driveLetters;
this.mountServiceObservable = mountServiceObservable;
this.mountProviders = mountProviders;
this.usedMountServices = usedMountServices;
this.defaultMountService = defaultMountService;
}
private class SettledMounter {
private MountService service;
private MountBuilder builder;
private VaultSettings vaultSettings;
private final MountService service;
private final MountBuilder builder;
private final VaultSettings vaultSettings;
public SettledMounter(MountService service, MountBuilder builder, VaultSettings vaultSettings) {
this.service = service;
@@ -53,8 +76,13 @@ public class Mounter {
for (var capability : service.capabilities()) {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
case LOOPBACK_PORT ->
builder.setLoopbackPort(settings.port.get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
case LOOPBACK_PORT -> {
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 READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get());
case MOUNT_FLAGS -> {
@@ -103,9 +131,7 @@ public class Mounter {
}
} else if (canMountToParent && !canMountToDir) {
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
cleanup = () -> {
MountWithinParentUtil.cleanup(userChosenMountPoint);
};
cleanup = () -> MountWithinParentUtil.cleanup(userChosenMountPoint);
}
try {
builder.setMountpoint(userChosenMountPoint);
@@ -131,13 +157,26 @@ public class Mounter {
}
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 internal = new SettledMounter(mountService, builder, vaultSettings);
var internal = new SettledMounter(mountService, builder, vaultSettings); // FIXME: no need for an inner class
var cleanup = internal.prepare();
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) {
}
@@ -8,7 +8,6 @@ package org.cryptomator.common.settings;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.annotations.VisibleForTesting;
import javafx.beans.Observable;
@@ -40,6 +39,7 @@ public class VaultSettings {
static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
static final int DEFAULT_PORT = 42427;
private static final Random RNG = new Random();
@@ -56,6 +56,8 @@ public class VaultSettings {
public final IntegerProperty autoLockIdleSeconds;
public final ObjectProperty<Path> mountPoint;
public final StringExpression mountName;
public final StringProperty mountService;
public final IntegerProperty port;
VaultSettings(VaultSettingsJson json) {
this.id = json.id;
@@ -70,6 +72,8 @@ public class VaultSettings {
this.autoLockWhenIdle = new SimpleBooleanProperty(this, "autoLockWhenIdle", json.autoLockWhenIdle);
this.autoLockIdleSeconds = new SimpleIntegerProperty(this, "autoLockIdleSeconds", json.autoLockIdleSeconds);
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
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
final String name;
@@ -95,7 +99,7 @@ public class VaultSettings {
}
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() {
@@ -124,6 +128,8 @@ public class VaultSettings {
json.autoLockWhenIdle = autoLockWhenIdle.get();
json.autoLockIdleSeconds = autoLockIdleSeconds.get();
json.mountPoint = mountPoint.map(Path::toString).getValue();
json.mountService = mountService.get();
json.port = port.get();
return json;
}
@@ -45,6 +45,12 @@ class VaultSettingsJson {
@JsonProperty("autoLockIdleSeconds")
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
@JsonProperty("mountService")
String mountService;
@JsonProperty("port")
int port = VaultSettings.DEFAULT_PORT;
@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
String winDriveLetter;
@@ -11,7 +11,6 @@ package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.mount.WindowsDriveLetters;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
@@ -73,7 +72,13 @@ public class Vault {
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@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.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem;
@@ -13,31 +13,37 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
@AddVaultWizardScoped
public class CreateNewVaultLocationController implements FxController {
@@ -49,19 +55,23 @@ public class CreateNewVaultLocationController implements FxController {
private final Stage window;
private final Lazy<Scene> chooseNameScene;
private final Lazy<Scene> chooseExpertSettingsScene;
private final List<RadioButton> locationPresetBtns;
private final ObjectProperty<Path> vaultPath;
private final StringProperty vaultName;
private final ExecutorService backgroundExecutor;
private final ResourceBundle resourceBundle;
private final ObservableValue<VaultPathStatus> vaultPathStatus;
private final ObservableValue<Boolean> validVaultPath;
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;
//FXML
public ToggleGroup locationPresetsToggler;
public VBox radioButtonVBox;
public HBox customLocationRadioBtn;
public RadioButton customRadioButton;
public Label locationStatusLabel;
public FontAwesome5IconView goodLocation;
@@ -73,25 +83,20 @@ public class CreateNewVaultLocationController implements FxController {
@FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS) Lazy<Scene> chooseExpertSettingsScene, //
ObjectProperty<Path> vaultPath, //
@Named("vaultName") StringProperty vaultName, //
ResourceBundle resourceBundle) {
ExecutorService backgroundExecutor, ResourceBundle resourceBundle) {
this.window = window;
this.chooseNameScene = chooseNameScene;
this.chooseExpertSettingsScene = chooseExpertSettingsScene;
this.vaultPath = vaultPath;
this.vaultName = vaultName;
this.backgroundExecutor = backgroundExecutor;
this.resourceBundle = resourceBundle;
this.vaultPathStatus = ObservableUtil.mapWithDefault(vaultPath, this::validatePath, new VaultPathStatus(false, "error.message"));
this.validVaultPath = ObservableUtil.mapWithDefault(vaultPathStatus, VaultPathStatus::valid, false);
this.vaultPathStatus.addListener(this::updateStatusLabel);
this.usePresetPath = new SimpleBooleanProperty();
this.locationPresetBtns = LocationPresetsProvider.loadAll(LocationPresetsProvider.class) //
.flatMap(LocationPresetsProvider::getLocations) //
.sorted(Comparator.comparing(LocationPreset::name)) //
.map(preset -> { //
var btn = new RadioButton(preset.name());
btn.setUserData(preset.path());
return btn;
}).toList();
this.radioButtons = FXCollections.observableArrayList();
this.sortedRadioButtons = radioButtons.sorted(this::compareLocationPresets);
}
private VaultPathStatus validatePath(Path p) throws NullPointerException {
@@ -137,12 +142,45 @@ public class CreateNewVaultLocationController implements FxController {
@FXML
public void initialize() {
radioButtonVBox.getChildren().addAll(1, locationPresetBtns); //first item is the list header
locationPresetsToggler.getToggles().addAll(locationPresetBtns);
var task = backgroundExecutor.submit(this::loadLocationPresets);
window.addEventHandler(WindowEvent.WINDOW_HIDING, _ -> task.cancel(true));
locationPresetsToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
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) {
var storagePath = Optional.ofNullable((Path) newValue.getUserData()).orElse(customVaultPath);
vaultPath.set(storagePath.resolve(vaultName.get()));
@@ -197,7 +235,15 @@ public class CreateNewVaultLocationController implements FxController {
}
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() {
@@ -21,9 +21,11 @@ public enum FxmlFile {
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
HUB_RECEIVE_KEY("/fxml/hub_receive_key.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_DEVICE_ALREADY_EXISTS("/fxml/hub_register_device_already_exists.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_REQUIRE_ACCOUNT_INIT("/fxml/hub_require_account_init.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), //
@@ -43,8 +45,10 @@ public enum FxmlFile {
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
SHARE_VAULT("/fxml/share_vault.fxml"), //
UPDATE_REMINDER("/fxml/update_reminder.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_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), //
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
@@ -47,6 +47,7 @@ public enum FontAwesome5Icon {
QUESTION_CIRCLE("\uf059"), //
REDO("\uF01E"), //
SEARCH("\uF002"), //
SHARE("\uF064"), //
SPINNER("\uF110"), //
STETHOSCOPE("\uF0f1"), //
SYNC("\uF021"), //
@@ -75,6 +75,7 @@ public class ErrorController implements FxController {
private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull();
private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty();
private final BooleanProperty askedForLookupDatabasePermission = new SimpleBooleanProperty();
private final boolean formerSceneWasResizable;
@Inject
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.environment = environment;
this.executorService = executorService;
this.formerSceneWasResizable = window.isResizable();
}
@FXML
public void back() {
if (previousScene != null) {
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.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.sharevault.ShareVaultComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
@@ -22,7 +23,17 @@ import javafx.scene.image.Image;
import java.io.IOException;
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 {
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.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.sharevault.ShareVaultComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.unlock.UnlockWorkflow;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
@@ -51,6 +52,7 @@ public class FxApplicationWindows {
private final ErrorComponent.Factory errorWindowFactory;
private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow;
private final FilteredList<Window> visibleWindows;
@Inject
@@ -64,6 +66,7 @@ public class FxApplicationWindows {
LockComponent.Factory lockWorkflowFactory, //
ErrorComponent.Factory errorWindowFactory, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, //
ExecutorService executor) {
this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration;
@@ -76,6 +79,7 @@ public class FxApplicationWindows {
this.errorWindowFactory = errorWindowFactory;
this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow;
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);
}
public void showShareVaultWindow(Vault vault) {
CompletableFuture.runAsync(() -> shareVaultWindow.create(vault).showShareVaultWindow(), Platform::runLater);
}
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
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;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.URI;
@@ -19,6 +19,19 @@ public class HubConfig {
@Deprecated // use apiBaseUrl + "/devices/"
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() {
if (apiBaseUrl != null) {
// make sure to end on "/":
@@ -33,4 +46,17 @@ public class HubConfig {
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,12 @@ public abstract class HubKeyLoadingModule {
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
@FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS)
@@ -135,10 +141,17 @@ public abstract class HubKeyLoadingModule {
}
@Provides
@FxmlScene(FxmlFile.HUB_SETUP_DEVICE)
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE)
@KeyLoadingScoped
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
@@ -185,6 +198,11 @@ public abstract class HubKeyLoadingModule {
@FxControllerKey(LegacyRegisterDeviceController.class)
abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller);
@Binds
@IntoMap
@FxControllerKey(LegacyRegisterSuccessController.class)
abstract FxController bindLegacyRegisterSuccessController(LegacyRegisterSuccessController controller);
@Binds
@IntoMap
@FxControllerKey(RegisterSuccessController.class)
@@ -1,7 +1,6 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
@@ -44,6 +43,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
this.window = window;
this.keychainManager = keychainManager;
window.setTitle(windowTitle);
window.setOnCloseRequest(_ -> result.cancel(true));
this.authFlowScene = authFlowScene;
this.noKeychainScene = noKeychainScene;
this.result = result;
@@ -1,7 +1,6 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
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.jwk.Curve;
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.MasterkeyLoadingFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
@@ -37,26 +37,16 @@ class JWEHelper {
private static final String JWE_PAYLOAD_KEY_FIELD = "key";
private static final String EC_ALG = "EC";
private JWEHelper(){}
private JWEHelper() {}
public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) {
try {
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);
}
return encryptKey(userKey, deviceKey);
}
public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException {
try {
jwe.decrypt(new PasswordBasedDecrypter(setupCode));
return decodeUserKey(jwe);
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, JWEHelper::decodeECPrivateKey);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
@@ -65,17 +55,23 @@ class JWEHelper {
public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException {
try {
jwe.decrypt(new ECDHDecrypter(deviceKey));
return decodeUserKey(jwe);
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, JWEHelper::decodeECPrivateKey);
} catch (JOSEException 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 {
var keySpec = readKey(decryptedJwe, JWE_PAYLOAD_KEY_FIELD, PKCS8EncodedKeySpec::new);
var factory = KeyFactory.getInstance(EC_ALG);
var privateKey = factory.generatePrivate(keySpec);
KeyFactory factory = KeyFactory.getInstance(EC_ALG);
var privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
if (privateKey instanceof ECPrivateKey ecPrivateKey) {
return ecPrivateKey;
} else {
@@ -84,8 +80,49 @@ class JWEHelper {
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(EC_ALG + " not supported");
} catch (InvalidKeySpecException e) {
LOG.warn("Unexpected JWE payload: {}", decryptedJwe.getPayload());
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
throw new KeyDecodeFailedException(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];
try {
if (fields.get(keyField) instanceof String key) {
keyBytes = BaseEncoding.base64().decode(key);
keyBytes = Base64.getDecoder().decode(key);
return rawKeyFactory.apply(keyBytes);
} else {
throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField);
}
} catch (IllegalArgumentException e) {
} catch (IllegalArgumentException | KeyDecodeFailedException e) {
LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
} finally {
@@ -127,4 +164,11 @@ class JWEHelper {
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;
@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.hubConfig = hubConfig;
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.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
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.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,7 +32,6 @@ import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -48,29 +47,29 @@ public class ReceiveKeyController implements FxController {
private final Stage window;
private final HubConfig hubConfig;
private final String vaultId;
private final String deviceId;
private final String bearerToken;
private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> setupDeviceScene;
private final Lazy<Scene> registerDeviceScene;
private final Lazy<Scene> legacyRegisterDeviceScene;
private final Lazy<Scene> unauthorizedScene;
private final Lazy<Scene> accountInitializationScene;
private final URI vaultBaseUri;
private final Lazy<Scene> invalidLicenseScene;
private final HttpClient httpClient;
@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_REQUIRE_ACCOUNT_INIT) Lazy<Scene> accountInitializationScene, @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.hubConfig = hubConfig;
this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs)
this.deviceId = deviceId;
this.bearerToken = Objects.requireNonNull(tokenRef.get());
this.result = result;
this.setupDeviceScene = setupDeviceScene;
this.registerDeviceScene = registerDeviceScene;
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
this.unauthorizedScene = unauthorizedScene;
this.accountInitializationScene = accountInitializationScene;
this.vaultBaseUri = getVaultBaseUri(vault);
this.invalidLicenseScene = invalidLicenseScene;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
@@ -78,70 +77,76 @@ public class ReceiveKeyController implements FxController {
@FXML
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() {
var accessTokenUri = appendPath(vaultBaseUri, "/access-token");
var request = HttpRequest.newBuilder(accessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
private void requestApiConfig() {
var configUri = hubConfig.URIs.API."config";
var request = HttpRequest.newBuilder(configUri) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
.thenAcceptAsync(this::receivedVaultMasterkey, Platform::runLater) //
.thenAcceptAsync(this::receivedApiConfig, Platform::runLater) //
.exceptionally(this::retrievalFailed);
}
/**
* STEP 1 (Response): GET vault key for this user
* STEP 0 (Response): GET /api/config
*
* @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());
switch (response.statusCode()) {
case 200 -> requestUserKey(response.body());
case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
case 449 -> accountInitializationRequired();
case 404 -> requestLegacyAccessToken();
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
Preconditions.checkState(response.statusCode() == 200, "Unexpected response " + response.statusCode());
try {
var config = JSON.reader().readValue(response.body(), ConfigDto.class);
if (config.apiLevel >= 1) {
requestDeviceData();
} 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) {
var deviceTokenUri = URI.create(hubConfig.getApiBaseUrl() + "/devices/" + deviceId);
var request = HttpRequest.newBuilder(deviceTokenUri) //
private void requestDeviceData() {
var deviceUri = hubConfig.URIs.API."devices/\{deviceId}";
var request = HttpRequest.newBuilder(deviceUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
.thenAcceptAsync(response -> receivedUserKey(encryptedVaultKey, response), Platform::runLater) //
.thenAcceptAsync(this::receivedDeviceData) //
.exceptionally(this::retrievalFailed);
}
/**
* STEP 2 (Response): GET user key for this device
* STEP 1 (Response): GET user key for this device
*
* @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());
try {
switch (response.statusCode()) {
case 200 -> {
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());
}
} catch (IOException e) {
@@ -149,18 +154,49 @@ public class ReceiveKeyController implements FxController {
}
}
private void needsDeviceSetup() {
window.setScene(setupDeviceScene.get());
private void needsDeviceRegistration() {
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 {
var vaultKeyJwe = JWEObject.parse(encryptedVaultKey);
var userKeyJwe = JWEObject.parse(encryptedUserKey);
result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe));
window.close();
} catch (ParseException e) {
throw new IOException("Failed to parse JWE", e);
retrievalFailed(e);
}
}
@@ -169,7 +205,7 @@ public class ReceiveKeyController implements FxController {
*/
@Deprecated
private void requestLegacyAccessToken() {
var legacyAccessTokenUri = appendPath(vaultBaseUri, "/keys/" + deviceId);
var legacyAccessTokenUri = hubConfig.URIs.API."vaults/\{vaultId}/keys/\{deviceId}";
var request = HttpRequest.newBuilder(legacyAccessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
@@ -251,19 +287,15 @@ public class ReceiveKeyController implements FxController {
}
}
private static URI getVaultBaseUri(Vault vault) {
try {
var url = vault.getVaultConfigCache().get().getKeyId();
assert url.getScheme().startsWith(SCHEME_PREFIX);
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);
}
private static String extractVaultId(URI vaultKeyUri) {
assert vaultKeyUri.getScheme().startsWith(SCHEME_PREFIX);
var path = vaultKeyUri.getPath();
return path.substring(path.lastIndexOf('/') + 1);
}
@JsonIgnoreProperties(ignoreUnknown = true)
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.WindowEvent;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.ECPublicKey;
import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@KeyLoadingScoped
public class RegisterDeviceController implements FxController {
@@ -57,12 +63,12 @@ public class RegisterDeviceController implements FxController {
private final String bearerToken;
private final Lazy<Scene> registerSuccessScene;
private final Lazy<Scene> registerFailedScene;
private final Lazy<Scene> deviceAlreadyExistsScene;
private final String deviceId;
private final P384KeyPair deviceKeyPair;
private final CompletableFuture<ReceivedKey> result;
private final HttpClient httpClient;
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false);
private final BooleanProperty workInProgress = new SimpleBooleanProperty(false);
public TextField setupCodeField;
@@ -70,7 +76,7 @@ public class RegisterDeviceController implements FxController {
public Button registerBtn;
@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.hubConfig = hubConfig;
this.deviceId = deviceId;
@@ -79,13 +85,13 @@ public class RegisterDeviceController implements FxController {
this.bearerToken = Objects.requireNonNull(bearerToken.get());
this.registerSuccessScene = registerSuccessScene;
this.registerFailedScene = registerFailedScene;
this.deviceAlreadyExistsScene = deviceAlreadyExistsScene;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
}
public void initialize() {
deviceNameField.setText(determineHostname());
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
deviceNameField.disableProperty().bind(workInProgress);
setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false));
setupCodeField.disableProperty().bind(workInProgress);
@@ -108,9 +114,8 @@ public class RegisterDeviceController implements FxController {
public void register() {
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() //
.timeout(REQ_TIMEOUT) //
.header("Authorization", "Bearer " + bearerToken) //
@@ -126,17 +131,19 @@ public class RegisterDeviceController implements FxController {
}
}).thenApply(user -> {
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());
return JWEHelper.encryptUserKey(userKey, deviceKeyPair.getPublic());
} catch (ParseException e) {
} catch (ParseException | JWEHelper.KeyDecodeFailedException e) {
throw new RuntimeException("Server answered with unparsable user key", e);
}
}).thenCompose(jwe -> {
var now = Instant.now().toString();
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now);
var json = toJson(dto);
var deviceUri = apiRootUrl.resolve("devices/" + deviceId);
var deviceUri = hubConfig.URIs.API."devices/\{deviceId}";
var putDeviceReq = HttpRequest.newBuilder(deviceUri) //
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
.timeout(REQ_TIMEOUT) //
@@ -146,7 +153,7 @@ public class RegisterDeviceController implements FxController {
return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding());
}).whenCompleteAsync((response, throwable) -> {
if (response != null) {
this.handleResponse(response);
this.handleRegisterDeviceResponse(response);
} else {
this.setupFailed(throwable);
}
@@ -154,6 +161,46 @@ public class RegisterDeviceController implements FxController {
}, 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) {
try {
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) {
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
window.setScene(registerSuccessScene.get());
} else if (response.statusCode() == 409) {
deviceNameAlreadyExists.set(true);
setupFailed(new DeviceAlreadyExistsException());
} else {
setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode()));
}
@@ -184,10 +231,13 @@ public class RegisterDeviceController implements FxController {
private void setupFailed(Throwable cause) {
switch (cause) {
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 -> {
LOG.warn("Device setup failed.", cause);
window.setScene(registerFailedScene.get());
result.completeExceptionally(cause);
}
}
}
@@ -202,15 +252,6 @@ public class RegisterDeviceController implements FxController {
}
//--- Getters & Setters
public BooleanProperty deviceNameAlreadyExistsProperty() {
return deviceNameAlreadyExists;
}
public boolean getDeviceNameAlreadyExists() {
return deviceNameAlreadyExists.get();
}
public BooleanProperty invalidSetupCodeProperty() {
return invalidSetupCode;
}
@@ -1,6 +1,5 @@
package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.KeyLoading;
@@ -22,8 +21,8 @@ public class RegisterFailedController implements FxController {
@FXML
public void close() {
result.cancel(true);
window.close();
}
}
@@ -1,24 +1,43 @@
package org.cryptomator.ui.keyloading.hub;
import dagger.Lazy;
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 javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.util.concurrent.CompletableFuture;
public class RegisterSuccessController implements FxController {
private final Stage window;
private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> receiveKeyScene;
private final ReceiveKeyController receiveKeyController;
@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.result = result;
this.receiveKeyScene = receiveKeyScene;
this.receiveKeyController = receiveKeyController;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
@FXML
public void close() {
window.close();
public void complete() {
window.setScene(receiveKeyScene.get());
receiveKeyController.receiveKey();
}
private void windowClosed(WindowEvent windowEvent) {
result.cancel(true);
}
}
@@ -44,6 +44,11 @@ public class VaultDetailLockedController implements FxController {
appWindows.startUnlockWorkflow(vault.get(), mainWindow);
}
@FXML
public void share() {
appWindows.showShareVaultWindow(vault.get());
}
@FXML
public void showVaultOptions() {
vaultOptionsWindow.create(vault.get()).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
@@ -2,17 +2,14 @@ package org.cryptomator.ui.preferences;
import dagger.Lazy;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.mount.MountModule;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.mount.MountCapability;
import org.cryptomator.integrations.mount.MountService;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
@@ -21,24 +18,22 @@ import javafx.util.StringConverter;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
@PreferencesScoped
public class VolumePreferencesController implements FxController {
private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
private static final int MIN_PORT = 1024;
private static final int MAX_PORT = 65535;
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
private final Settings settings;
private final ObservableValue<MountService> selectedMountService;
private final ResourceBundle resourceBundle;
private final BooleanExpression loopbackPortSupported;
private final ObservableValue<Boolean> loopbackPortSupported;
private final ObservableValue<Boolean> mountToDirSupported;
private final ObservableValue<Boolean> mountToDriveLetterSupported;
private final ObservableValue<Boolean> mountFlagsSupported;
private final ObservableValue<Boolean> readonlySupported;
private final ObservableValue<Boolean> fuseRestartRequired;
private final Lazy<Application> application;
private final List<MountService> mountProviders;
public ChoiceBox<MountService> volumeTypeChoiceBox;
@@ -46,7 +41,10 @@ public class VolumePreferencesController implements FxController {
public Button loopbackPortApplyButton;
@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.application = application;
this.mountProviders = mountProviders;
@@ -54,17 +52,11 @@ public class VolumePreferencesController implements FxController {
var fallbackProvider = mountProviders.stream().findFirst().orElse(null);
this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService, serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider);
this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)));
this.loopbackPortSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT));
this.mountToDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT) || s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR));
this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
this.readonlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
this.fuseRestartRequired = selectedMountService.map(s -> {//
return firstUsedProblematicFuseMountService.get() != null //
&& MountModule.isProblematicFuseService(s) //
&& !firstUsedProblematicFuseMountService.get().equals(s);
});
}
public void initialize() {
@@ -101,12 +93,12 @@ public class VolumePreferencesController implements FxController {
/* Property Getters */
public BooleanExpression loopbackPortSupportedProperty() {
public ObservableValue<Boolean> loopbackPortSupportedProperty() {
return loopbackPortSupported;
}
public boolean isLoopbackPortSupported() {
return loopbackPortSupported.get();
return loopbackPortSupported.getValue();
}
public ObservableValue<Boolean> readonlySupportedProperty() {
@@ -141,14 +133,6 @@ public class VolumePreferencesController implements FxController {
return mountFlagsSupported.getValue();
}
public ObservableValue<Boolean> fuseRestartRequiredProperty() {
return fuseRestartRequired;
}
public boolean getFuseRestartRequired() {
return fuseRestartRequired.getValue();
}
/* Helpers */
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
@TrayMenuScoped
static Optional<TrayMenuController> provideSupportedKeychainAccessProviders() {
static Optional<TrayMenuController> provideFirstSupportedTrayMenuController() {
return TrayMenuController.get();
}
@@ -19,16 +19,8 @@ import java.util.concurrent.Future;
@Subcomponent(modules = {UnlockModule.class})
public interface UnlockComponent {
ExecutorService defaultExecutorService();
UnlockWorkflow unlockWorkflow();
default Future<Boolean> startUnlockWorkflow() {
UnlockWorkflow workflow = unlockWorkflow();
defaultExecutorService().submit(workflow);
return workflow;
}
@Subcomponent.Factory
interface Factory {
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);
}
@Provides
@FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART)
@UnlockScoped
static Scene provideRestartRequiredScene(@UnlockWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.UNLOCK_REQUIRES_RESTART);
}
// ------------------
@Binds
@@ -93,4 +100,9 @@ abstract class UnlockModule {
@FxControllerKey(UnlockInvalidMountPointController.class)
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;
import com.google.common.base.Throwables;
import dagger.Lazy;
import org.cryptomator.common.mount.ConflictingMountServiceException;
import org.cryptomator.common.mount.IllegalMountPointException;
import org.cryptomator.common.vaults.Vault;
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.VaultService;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,7 +20,9 @@ import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.concurrent.Task;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.stage.Screen;
import javafx.stage.Stage;
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.
*/
@UnlockScoped
public class UnlockWorkflow extends Task<Boolean> {
public class UnlockWorkflow extends Task<Void> {
private static final Logger LOG = LoggerFactory.getLogger(UnlockWorkflow.class);
private final Stage mainWindow;
private final Stage window;
private final Vault vault;
private final VaultService vaultService;
private final Lazy<Scene> successScene;
private final Lazy<Scene> invalidMountPointScene;
private final Lazy<Scene> restartRequiredScene;
private final FxApplicationWindows appWindows;
private final KeyLoadingStrategy keyLoadingStrategy;
private final ObjectProperty<IllegalMountPointException> illegalMountPointException;
@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.vault = vault;
this.vaultService = vaultService;
this.successScene = successScene;
this.invalidMountPointScene = invalidMountPointScene;
this.restartRequiredScene = restartRequiredScene;
this.appWindows = appWindows;
this.keyLoadingStrategy = keyLoadingStrategy;
this.illegalMountPointException = illegalMountPointException;
}
@Override
protected Boolean 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 {
protected Void call() throws InterruptedException, IOException, CryptoException, MountFailedException {
try {
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) {
Throwables.propagateIfPossible(e, IOException.class);
Throwables.propagateIfPossible(e, CryptoException.class);
Throwables.propagateIfPossible(e, IllegalMountPointException.class);
Throwables.propagateIfPossible(e, MountFailedException.class);
throw new IllegalStateException("unexpected exception type", e);
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) {
LOG.error("Unlock failed for technical reasons.", e);
appWindows.showErrorWindow(e, window, null);
@@ -98,6 +113,19 @@ public class UnlockWorkflow extends Task<Boolean> {
case ASK -> Platform.runLater(() -> {
window.setScene(successScene.get());
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 -> {
Platform.runLater(window::close);
@@ -113,10 +141,10 @@ public class UnlockWorkflow extends Task<Boolean> {
protected void failed() {
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
Throwable throwable = super.getException();
if(throwable instanceof IllegalMountPointException impe) {
handleIllegalMountPointError(impe);
} else {
handleGenericError(throwable);
switch (throwable) {
case IllegalMountPointException e -> handleIllegalMountPointError(e);
case ConflictingMountServiceException _ -> handleConflictingMountServiceException();
default -> handleGenericError(throwable);
}
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
}
@@ -1,18 +1,25 @@
package org.cryptomator.ui.vaultoptions;
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.settings.VaultSettings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.mount.MountCapability;
import org.cryptomator.integrations.mount.MountService;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.preferences.VolumePreferencesController;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.RadioButton;
@@ -26,6 +33,8 @@ import java.io.File;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
@@ -36,14 +45,21 @@ public class MountOptionsController implements FxController {
private final VaultSettings vaultSettings;
private final WindowsDriveLetters windowsDriveLetters;
private final ResourceBundle resourceBundle;
private final Lazy<Application> application;
private final ObservableValue<String> defaultMountFlags;
private final ObservableValue<Boolean> mountpointDirSupported;
private final ObservableValue<Boolean> mountpointDriveLetterSupported;
private final ObservableValue<Boolean> readOnlySupported;
private final ObservableValue<Boolean> mountFlagsSupported;
private final ObservableValue<Boolean> defaultMountServiceSelected;
private final ObservableValue<String> directoryPath;
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 --
@@ -56,30 +72,58 @@ public class MountOptionsController implements FxController {
public RadioButton mountPointDirBtn;
public TextField directoryPathField;
public ChoiceBox<Path> driveLetterSelection;
public ChoiceBox<MountService> vaultVolumeTypeChoiceBox;
public TextField vaultLoopbackPortField;
public Button vaultLoopbackPortApplyButton;
@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.vaultSettings = vault.getVaultSettings();
this.windowsDriveLetters = windowsDriveLetters;
this.resourceBundle = resourceBundle;
this.defaultMountFlags = mountService.map(as -> {
if (as.service().hasCapability(MountCapability.MOUNT_FLAGS)) {
return as.service().getDefaultMountFlags();
this.applicationWindows = applicationWindows;
this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString());
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 {
return "";
}
});
this.mountpointDirSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || as.service().hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
this.mountpointDriveLetterSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.mountFlagsSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_FLAGS));
this.readOnlySupported = mountService.map(as -> as.service().hasCapability(MountCapability.READ_ONLY));
this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString());
this.applicationWindows = applicationWindows;
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
this.defaultMountServiceSelected = ObservableUtil.mapWithDefault(vaultSettings.mountService, _ -> false, true);
this.readOnlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
this.mountpointDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
this.mountpointDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
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
public void initialize() {
defaultMountService.addListener((_, _, _) -> vaultVolumeTypeChoiceBox.setConverter(new MountServiceConverter()));
// readonly:
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode);
@@ -106,6 +150,20 @@ public class MountOptionsController implements FxController {
mountPointToggleGroup.selectToggle(mountPointDirBtn);
}
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
@@ -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
private static class NoDirSelectedException extends Exception {}
//@formatter:on
@@ -243,6 +321,14 @@ public class MountOptionsController implements FxController {
return mountFlagsSupported.getValue();
}
public ObservableValue<Boolean> defaultMountServiceSelectedProperty() {
return defaultMountServiceSelected;
}
public boolean isDefaultMountServiceSelected() {
return defaultMountServiceSelected.getValue();
}
public ObservableValue<Boolean> mountpointDirSupportedProperty() {
return mountpointDirSupported;
}
@@ -274,4 +360,37 @@ public class MountOptionsController implements FxController {
public String getDirectoryPath() {
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;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
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))){
tabPane.getTabs().remove(hubTab);
}
vault.stateProperty().addListener(observable -> {
tabPane.setDisable(vault.getState().equals(VaultState.Value.UNLOCKED));
});
}
private void selectChosenTab() {
+13
View File
@@ -936,3 +936,16 @@
-fx-padding: 0.5px;
-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;
}
+13
View File
@@ -935,3 +935,16 @@
-fx-padding: 0.5px;
-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"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.HBox?>
@@ -29,18 +31,26 @@
<children>
<Region VBox.vgrow="ALWAYS"/>
<VBox fx:id="radioButtonVBox" spacing="6">
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
<!-- PLACEHOLDER, more radio buttons are added programmatically via controller -->
<HBox spacing="12" alignment="CENTER_LEFT">
<RadioButton fx:id="customRadioButton" toggleGroup="${locationPresetsToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN"/>
</graphic>
</Button>
</HBox>
</VBox>
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
<ScrollPane hbarPolicy="NEVER">
<VBox fx:id="radioButtonVBox" spacing="6">
<!-- PLACEHOLDER, more radio buttons are added programmatically via controller -->
<HBox fx:id="customLocationRadioBtn" spacing="12" alignment="CENTER_LEFT">
<RadioButton fx:id="customRadioButton" toggleGroup="${locationPresetsToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN"/>
</graphic>
</Button>
</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"/>
+75 -64
View File
@@ -9,86 +9,97 @@
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?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?>
<VBox xmlns:fx="http://javafx.com/fxml"
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.error.ErrorController"
minWidth="450"
minHeight="450"
prefWidth="450"
prefHeight="450"
spacing="18"
alignment="TOP_CENTER">
spacing="12">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>
<children>
<HBox spacing="12" VBox.vgrow="NEVER">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
<FormattedLabel styleClass="label-extra-large" format="%error.message" arg1="${controller.errorCode}"/>
<VBox visible="${!controller.askedForLookupDatabasePermission}" managed="${!controller.askedForLookupDatabasePermission}">
<Label text="%error.lookupPermissionMessage" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<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>
</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>
<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">
<FormattedLabel styleClass="label-extra-large" format="%error.message" arg1="${controller.errorCode}">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</FormattedLabel>
<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">
<buttons>
<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"/>
</buttons>
</ButtonBar>
</children>
</VBox>
</VBox>
</HBox>
@@ -41,7 +41,7 @@
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%hub.register.description" wrapText="true"/>
<Label text="%hub.register.legacy.description" wrapText="true"/>
<HBox spacing="6" alignment="CENTER_LEFT">
<padding>
<Insets top="12"/>
@@ -50,7 +50,7 @@
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
</HBox>
<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>
<Insets top="6"/>
</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>
@@ -57,15 +57,6 @@
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
</HBox>
<HBox alignment="TOP_RIGHT">
<Label text="%hub.register.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" managed="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
<padding>
<Insets top="6"/>
</padding>
<graphic>
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
</graphic>
</Label>
<Label text="%hub.register.invalidAccountKeyLabel" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.invalidSetupCode}" managed="${controller.invalidSetupCode}" graphicTextGap="6">
<padding>
<Insets top="6"/>
@@ -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"/>
</padding>
</Label>
<Label text="%hub.registerFailed.description" wrapText="true"/>
<Label text="%hub.registerFailed.description.generic" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
@@ -41,9 +41,9 @@
<Label text="%hub.registerSuccess.description" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
<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 -->
</buttons>
</ButtonBar>
@@ -22,7 +22,7 @@
</ImageView>
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
<FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/>
<Label text="© 2016 2023 Skymatic GmbH"/>
<Label text="© 2016 2024 Skymatic GmbH"/>
</VBox>
</HBox>
@@ -32,8 +32,6 @@
</Hyperlink>
</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}">
<Label text="%preferences.volume.tcp.port"/>
<NumericTextField fx:id="loopbackPortField"/>
+111
View File
@@ -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"/>
</graphic>
</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>
<FontAwesome5IconView glyph="LOCK"/>
</graphic>
</Hyperlink>
<Button text="%main.vaultDetail.share" minWidth="120" onAction="#share">
<graphic>
<FontAwesome5IconView glyph="SHARE" glyphSize="15"/>
</graphic>
</Button>
<Region VBox.vgrow="ALWAYS"/>
<HBox alignment="BOTTOM_RIGHT">
<Button text="%main.vaultDetail.optionsBtn" minWidth="120" onAction="#showVaultOptions">
<graphic>
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.NumericTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
@@ -10,9 +11,9 @@
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.TextFlow?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
@@ -24,11 +25,35 @@
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<TextFlow>
<Label text="%vaultOptions.mount.info"/>
<Label text=" "/>
<Hyperlink styleClass="hyperlink-underline" text="%vaultOptions.mount.linkToPreferences" onAction="#openVolumePreferences" wrapText="true"/>
</TextFlow>
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="%vaultOptions.mount.volume.type"/>
<ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}">
<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}"/>
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
+45 -12
View File
@@ -48,6 +48,7 @@ addvaultwizard.new.nameInstruction=Choose a name for the vault
addvaultwizard.new.namePrompt=Vault Name
### Location
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.locationPrompt=
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.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.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.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.
@@ -155,17 +159,22 @@ hub.receive.message=Processing response…
hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait.
### Register Device
hub.register.message=New Device
hub.register.description=This is the first Hub access from this device. Please authorize it using your Account Key.
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.invalidAccountKeyLabel=Invalid Account Key
hub.register.occupiedMsg=Name already in use
hub.register.registerBtn=Confirm
hub.register.registerBtn=Register
### Register Device Legacy
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
hub.registerSuccess.message=Device named
hub.registerSuccess.description=To access the vault, your device needs to be authorized by the vault owner.
hub.registerSuccess.message=Device registered
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
hub.registerFailed.message=Device naming failed
hub.registerFailed.description=An error was thrown in the naming process. For more details, look into the application log.
hub.registerFailed.message=Device registration failed
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
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.
@@ -295,11 +304,11 @@ preferences.interface.showMinimizeButton=Show minimize button
preferences.interface.showTrayIcon=Show tray icon (requires restart)
## Volume
preferences.volume=Virtual Drive
preferences.volume.type=Volume Type
preferences.volume.type=Default Volume Type
preferences.volume.type.automatic=Automatic
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.tcp.port=TCP Port
preferences.volume.tcp.port=Default TCP Port
preferences.volume.supportedFeatures=The chosen volume type supports the following features:
preferences.volume.feature.mountAuto=Automatic mount point selection
preferences.volume.feature.mountToDir=Custom directory as mount point
@@ -384,6 +393,7 @@ main.vaultDetail.unlockBtn=Unlock…
main.vaultDetail.unlockNowBtn=Unlock Now
main.vaultDetail.optionsBtn=Vault Options
main.vaultDetail.passwordSavedInKeychain=Password saved
main.vaultDetail.share=Share…
### Unlocked
main.vaultDetail.unlockedStatus=UNLOCKED
main.vaultDetail.accessLocation=Your vault's contents are accessible here:
@@ -438,8 +448,7 @@ vaultOptions.general.startHealthCheckBtn=Start Health Check
## Mount
vaultOptions.mount=Mounting
vaultOptions.mount.info=Options depend on the selected volume type.
vaultOptions.mount.linkToPreferences=Open virtual drive preferences
vaultOptions.mount.info=Open virtual drive preferences to change default settings.
vaultOptions.mount.readonly=Read-only
vaultOptions.mount.customMountFlags=Custom mount flags
vaultOptions.mount.winDriveLetterOccupied=occupied
@@ -449,6 +458,10 @@ vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
vaultOptions.mount.mountPoint.custom=Use chosen directory
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
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
vaultOptions.masterkey=Password
vaultOptions.masterkey.changePasswordBtn=Change Password
@@ -518,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.notNow=Not Now
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
+11 -9
View File
@@ -154,14 +154,10 @@ hub.receive.message=معالجة الاستجابة…
hub.receive.description=Cryptomator يتلقى ويعالج الاستجابة من المركز. الرجاء الانتظار.
### Register Device
hub.register.nameLabel=اسم الجهاز
hub.register.occupiedMsg=الاسم مستخدم مسبقاً
hub.register.registerBtn=تأكيد
### Register Device Legacy
### Registration Success
hub.registerSuccess.message=تم تسمية الجهاز
hub.registerSuccess.description=للدخول إلى الخزينة، يحتاج جهازك إلى إذن من مالك الخزينة.
hub.registerSuccess.unlockBtn=افتح
### Registration Failed
hub.registerFailed.message=أخفق في تسمية الجهاز
hub.registerFailed.description=خطأ في عملية التسمية. لمزيد من التفاصيل، انظر إلى سجل التطبيق.
### Unauthorized
hub.unauthorized.message=تم رفض الوصول
hub.unauthorized.description=لم يتم بعد منح الإذن لجهازك بالوصول إلى هذا المخزن. اطلب من مالك المخزن أن يأذن بذلك.
@@ -287,11 +283,9 @@ 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=يدعم نوع وحدة تخزين المختار الميزات التالية:
## Updates
preferences.updates=تحديثات
@@ -402,21 +396,27 @@ vaultOptions.mount.mountPoint=موقع التحميل الوهمي
vaultOptions.mount.mountPoint.auto=اختيار موقع مناسب تلقائياً
vaultOptions.mount.mountPoint.driveLetter=استخدام دالة قرص معين
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
vaultOptions.mount.volumeType.default=الافتراضي (%s)
vaultOptions.mount.volume.tcp.port=منفذ TCP
## Master Key
vaultOptions.masterkey=كلمة المرور
vaultOptions.masterkey.changePasswordBtn=تغيير كلمة المرور
vaultOptions.masterkey.forgetSavedPasswordBtn=نسيان كلمة المرور المحفوظة
vaultOptions.masterkey.recoveryKeyExplanation=مفتاح الاسترداد هو وسيلتك الوحيدة لاستعادة الوصول إلى مخزنك إذا فقدت كلمة المرور.
vaultOptions.masterkey.showRecoveryKeyBtn=عرض مفتاح الاسترداد
vaultOptions.masterkey.recoverPasswordBtn=إعادة تعيين كلمة المرور
## Hub
vaultOptions.hub=الإسترداد
# Recovery Key
## Display Recovery Key
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.correctKey=هذا مفتاح استرداد صالح
recoveryKey.printout.heading=مفتاح استرداد Cryptomator\n"%s"\n
### Reset Password
@@ -441,4 +441,6 @@ quit.lockAndQuitBtn=قفل و خروج
# 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
+5 -11
View File
@@ -151,14 +151,10 @@ hub.receive.description=Cryptomator атрымлівае ды апрацоўва
### Register Device
hub.register.message=Новая прылада
hub.register.nameLabel=Назва прылады
hub.register.occupiedMsg=Назва ўжо ўжытая
hub.register.registerBtn=Пацвердзіць
### Register Device Legacy
### Registration Success
hub.registerSuccess.message=Прылада атрымала назву
hub.registerSuccess.description=Каб атрымаць доступ да скарбніцы, твая прылада мусіць быць спраўджанай уладальнікам скарбніцы.
hub.registerSuccess.unlockBtn=Адамкнуць
### Registration Failed
hub.registerFailed.message=Памылка пры называнні прылады
hub.registerFailed.description=Падчас прысваення імя адбылася памылка. Па дэтальную інфармацыю звярніся да пратаколу праграмы.
### Unauthorized
hub.unauthorized.message=Адмова ў доступе
hub.unauthorized.description=Тваёй прыладзе ў дадзены момант не дазволена мець доступ да гэтай скрабніцы. Запытайся ўладальніка скрабніцы за дазволам.
@@ -284,11 +280,9 @@ 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=Карыстальніцкая тэчка як пункт мантажавання
@@ -427,8 +421,6 @@ vaultOptions.general.startHealthCheckBtn=Пачаць тэст на цэласн
## Mount
vaultOptions.mount=Мантажаванне
vaultOptions.mount.info=Опцыі залежаць ад абранага тыпу тому.
vaultOptions.mount.linkToPreferences=Адчыніць налады віртуальнага дыску
vaultOptions.mount.readonly=Толькі для чытання
vaultOptions.mount.customMountFlags=Карыстальніцкія опцыі мантажавання
vaultOptions.mount.winDriveLetterOccupied=занята
@@ -504,4 +496,6 @@ updateReminder.title=Праверка абнаўленняў
updateReminder.message=Ці праверыць абнаўленні?
updateReminder.notNow=Не цяпер
updateReminder.yesOnce=Так, аднойчы
updateReminder.yesAutomatically=Так, аўтаматычна
updateReminder.yesAutomatically=Так, аўтаматычна
# Share Vault
+15 -14
View File
@@ -141,6 +141,7 @@ unlock.error.customPath.description.hideawayNotDir=Временният, скр
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=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките.
@@ -154,23 +155,19 @@ hub.receive.message=Обработване на отговора…
hub.receive.description=Криптоматор получава и обработва отговора от Hub. Изчакайте.
### Register Device
hub.register.message=Ново устройство
hub.register.description=За пръв път посещавате Hub от това устройство. Удостоверете го, като използвате ключа на профила.
hub.register.nameLabel=Име на устройството
hub.register.invalidAccountKeyLabel=Неприемлив ключ на профила
hub.register.occupiedMsg=Това име вече е заето
hub.register.registerBtn=Потвърждаване
hub.register.registerBtn=Регистриране
### Register Device Legacy
### Registration Success
hub.registerSuccess.message=Устройството е именувано
hub.registerSuccess.description=За да получи достъп до хранилището, устройството трябва да бъде упълномощено от собственика на хранилището.
hub.registerSuccess.unlockBtn=Отключване
### Registration Failed
hub.registerFailed.message=Грешка при именуване на устройството
hub.registerFailed.description=В процеса на именуване е допусната грешка. За повече подробности разгледайте дневника на приложението.
### Unauthorized
hub.unauthorized.message=Отказан достъп
hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика.
### Requires Account Initialization
hub.requireAccountInit.message=Необходимо действие
hub.requireAccountInit.description.0=За да продължите завършене необходимите стъпки в
hub.requireAccountInit.message=Необходимо е действие
hub.requireAccountInit.description.0=За да продължите завършете необходимите стъпки в
hub.requireAccountInit.description.1=профила в Hub
hub.requireAccountInit.description.2=.
### License Exceeded
@@ -294,11 +291,11 @@ preferences.interface.showMinimizeButton=Бутон за скриване
preferences.interface.showTrayIcon=Икона в областта за известия (изисква рестарт)
## Volume
preferences.volume=Виртуален диск
preferences.volume.type=Вид на тома
preferences.volume.type=Подразбиран вид на тома
preferences.volume.type.automatic=Автоматично
preferences.volume.docsTooltip=Вижте документацията относно видовете томове.
preferences.volume.fuseRestartRequired=За да бъдат приложени промените, Криптоматор трябва да бъде рестартиран.
preferences.volume.tcp.port=Порт на TCP
preferences.volume.tcp.port=Подразбиран порт на TCP
preferences.volume.supportedFeatures=Избрания вид на тома има следните възможности:
preferences.volume.feature.mountAuto=Автоматичен избор на точка за монтиране
preferences.volume.feature.mountToDir=Папка по избор като точка за монтиране
@@ -437,8 +434,7 @@ vaultOptions.general.startHealthCheckBtn=Проверка на състояни
## Mount
vaultOptions.mount=Монтиране
vaultOptions.mount.info=Възможностите зависят от вида на избрания том.
vaultOptions.mount.linkToPreferences=Настройки на виртуалния диск
vaultOptions.mount.info=Отворете настройките на виртуалния диск, за да промените стандартните настройки.
vaultOptions.mount.readonly=Само за четене
vaultOptions.mount.customMountFlags=Флагове на монтиране
vaultOptions.mount.winDriveLetterOccupied=използвана
@@ -448,6 +444,9 @@ 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=Промяна на парола
@@ -517,4 +516,6 @@ updateReminder.message=Проверяване за обновяване?
updateReminder.description=Получавайте нови възможности на приложението, поправки на дефекти и подобрения на сигурността. Препоръчваме ви автоматичното обновяване.
updateReminder.notNow=Не сега
updateReminder.yesOnce=Да, веднъж
updateReminder.yesAutomatically=Да, автоматично
updateReminder.yesAutomatically=Да, автоматично
# Share Vault
@@ -89,8 +89,9 @@ unlock.unlockBtn=আনলক করুন
### Waiting
### Receive Key
### Register Device
hub.register.registerBtn=নিশ্চিত করুন
### Register Device Legacy
### Registration Success
hub.registerSuccess.unlockBtn=আনলক করুন
### Registration Failed
### Unauthorized
### Requires Account Initialization
@@ -172,4 +173,6 @@ vaultOptions.mount.mountPoint.directoryPickerButton=নির্বাচন ক
# Forced Quit
# Update Reminder
# Update Reminder
# Share Vault
@@ -105,8 +105,9 @@ unlock.success.revealBtn=Otkrij pogon
### Waiting
### Receive Key
### Register Device
hub.register.registerBtn=Potvrdi
### Register Device Legacy
### Registration Success
hub.registerSuccess.unlockBtn=Otključaj
### Registration Failed
### Unauthorized
### Requires Account Initialization
@@ -314,4 +315,6 @@ quit.lockAndQuitBtn=Zaključaj i zatvori
# Forced Quit
# Update Reminder
# Update Reminder
# Share Vault
+5 -11
View File
@@ -155,14 +155,10 @@ hub.receive.description=Cryptomator està rebent i processant la resposta del Hu
### Register Device
hub.register.message=Nou dispositiu
hub.register.nameLabel=Nom del dispositiu
hub.register.occupiedMsg=El nom ja està en ús
hub.register.registerBtn=Confirma
### Register Device Legacy
### Registration Success
hub.registerSuccess.message=Unitat anomenada
hub.registerSuccess.description=Per a accedir a la caixa forta, el vostre dispositiu ha de ser autoritzat pel propietari de la caixa.
hub.registerSuccess.unlockBtn=Desbloqueja
### Registration Failed
hub.registerFailed.message=Error al nombrar el dispositiu
hub.registerFailed.description=S'ha produït un error en el procés de nomenament. Per més detalls vegeu el registre de l'aplicació.
### Unauthorized
hub.unauthorized.message=Accés denegat
hub.unauthorized.description=El vostre dispositiu no ha estat encara autoritzat a accedir a aquesta caixa forta. Demaneu autorització al propietari.
@@ -288,11 +284,9 @@ preferences.interface.showMinimizeButton=Mostra el botó 'minimitzar'
preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar)
## Volume
preferences.volume=Unitat virtual
preferences.volume.type=Tipus de volum
preferences.volume.type.automatic=Automàtic
preferences.volume.docsTooltip=Obre la documentació per aprendre més sobre els diferents tipus de volums.
preferences.volume.fuseRestartRequired=Per aplicar els canvis Cryptomator necessita reiniciar-se.
preferences.volume.tcp.port=Port TCP
preferences.volume.supportedFeatures=El tipus de volum escollit suporta les següents característiques:
preferences.volume.feature.mountAuto=Selecció automàtica del punt de muntatge
preferences.volume.feature.mountToDir=Directori personalitzat com a punt de muntatge
@@ -431,8 +425,6 @@ vaultOptions.general.startHealthCheckBtn=Inicia la comprovació
## Mount
vaultOptions.mount=Muntatge
vaultOptions.mount.info=Les opcions depenen del tipus de volum escollit.
vaultOptions.mount.linkToPreferences=Obre les preferències del disc virtual
vaultOptions.mount.readonly=Només lectura
vaultOptions.mount.customMountFlags=Senyaladors de muntatge personalitzats
vaultOptions.mount.winDriveLetterOccupied=ocupat
@@ -511,4 +503,6 @@ updateReminder.message=Comprova si hi ha actualitzacions?
updateReminder.description=Per estar actualitzat amb funcions noves, correccions d'errors i millores de seguretat recomanem comprovar les actualitzacions automàticament.
updateReminder.notNow=Ara no
updateReminder.yesOnce=Sí, una vegada
updateReminder.yesAutomatically=Sí, automàticament
updateReminder.yesAutomatically=Sí, automàticament
# Share Vault
+20 -7
View File
@@ -23,6 +23,7 @@ error.technicalDetails=Podrobnosti:
error.existingSolutionDescription=Cryptomator neočekával, že se tak stane. Našli jsme však existující řešení pro tuto chybu. Podívejte se prosím na následující odkaz.
error.hyperlink.solution=Podívejte se na řešení
error.lookupPermissionMessage=Cryptomator může najít řešení tohoto problému online. To odešle žádost do naší databáze problémů z vaší IP adresy.
error.dismiss=Zrušit
# Defaults
defaults.vault.vaultName=Trezor
@@ -39,6 +40,7 @@ traymenu.vault.reveal=Zobrazit
# Add Vault Wizard
addvaultwizard.title=Přidat trezor
## New
addvaultwizard.new.title=Přidat nový trezor
### Name
addvaultwizard.new.nameInstruction=Zvolte jméno trezoru
addvaultwizard.new.namePrompt=Název trezoru
@@ -88,6 +90,7 @@ addvault.new.readme.accessLocation.2=Toto je místo přístupu vašeho trezoru.
addvault.new.readme.accessLocation.3=Všechny soubory přidané do tohoto svazku budou šifrovány Cryptomatorem. Můžete na něm pracovat na jiném disku/složce. Toto je pouze dešifrovaný pohled na jeho obsah, vaše soubory zůstávají na pevném disku stále zašifrovány.
addvault.new.readme.accessLocation.4=Tento soubor můžete odstranit.
## Existing
addvaultwizard.existing.title=Přidat existující trezor
addvaultwizard.existing.instruction=Vyberte soubor "vault.cryptomator" z existujícího trezoru. Pokud existuje pouze soubor s názvem "masterkey.cryptomator", vyberte jej.
addvaultwizard.existing.chooseBtn=Vybrat...
addvaultwizard.existing.filePickerTitle=Vyberte soubor trezoru
@@ -142,18 +145,18 @@ hub.auth.loginLink=Nebyli jste přesměrováni? Klikněte zde pro otevření.
### Receive Key
hub.receive.message=Zpracovávání odezvy…
### Register Device
hub.register.message=Nové zařízení
hub.register.nameLabel=Název zařízení
hub.register.occupiedMsg=Jméno je již používáno
hub.register.registerBtn=Potvrdit
hub.register.registerBtn=Registrovat
### Register Device Legacy
### Registration Success
hub.registerSuccess.message=Jméno zařízení
hub.registerSuccess.description=Pro přístup k trezoru musí být vaše zařízení autorizováno majitelem trezoru.
hub.registerSuccess.unlockBtn=Odemknout
### Registration Failed
hub.registerFailed.message=Pojmenování zařízení se nezdařilo
### Unauthorized
hub.unauthorized.message=Přístup odepřen
hub.unauthorized.description=Vaše zařízení dosud nebylo oprávněno k přístupu k tomuto trezoru. Požádejte vlastníka trezoru, aby jej autorizoval.
### Requires Account Initialization
hub.requireAccountInit.message=Požadována akce
### License Exceeded
hub.invalidLicense.message=Licence Hubu je neplatná
@@ -268,8 +271,8 @@ preferences.interface.showMinimizeButton=Zobrazit tlačítko minimalizovat
preferences.interface.showTrayIcon=Zobrazit ikonu v liště (vyžaduje restart)
## Volume
preferences.volume=Virtuální jednotky
preferences.volume.type=Výchozí typ jednotky
preferences.volume.type.automatic=Automatické
preferences.volume.tcp.port=TCP port
preferences.volume.feature.mountFlags=Vlastní možnosti připojení disku
preferences.volume.feature.readOnly=Připojení disku pouze pro čtení
## Updates
@@ -339,6 +342,8 @@ main.vaultlist.contextMenu.unlockNow=Odemknout nyní
main.vaultlist.contextMenu.vaultoptions=Zobrazit možnosti trezoru
main.vaultlist.contextMenu.reveal=Zobrazit jednotku
main.vaultlist.addVaultBtn=Přidat
main.vaultlist.addVaultBtn.menuItemNew=Nový trezor...
main.vaultlist.addVaultBtn.menuItemExisting=Existující trezor...
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=Děkujeme, že jste si vybrali Cryptomator pro ochranu vašich souborů. Pokud potřebujete pomoc, podívejte se na naše návody:
@@ -408,6 +413,8 @@ vaultOptions.mount.mountPoint.auto=Automaticky vybrat vhodné místo
vaultOptions.mount.mountPoint.driveLetter=Použít zvolenou jednotku
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrat...
vaultOptions.mount.mountPoint.directoryPickerTitle=Vyberte složku
vaultOptions.mount.volume.tcp.port=TCP port
vaultOptions.mount.volume.type=Typ jednotky
## Master Key
vaultOptions.masterkey=Heslo
vaultOptions.masterkey.changePasswordBtn=Změnit heslo
@@ -438,6 +445,10 @@ recoveryKey.recover.resetBtn=Resetovat
recoveryKey.recover.resetSuccess.message=Resetování hesla bylo úspěšné
# Convert Vault
convertVault.title=Převést trezor
convertVault.convert.convertBtn.before=Převést
convertVault.convert.convertBtn.processing=Převádění…
convertVault.success.message=Konverze byla úspěšná
# New Password
newPassword.promptText=Zadejte nové heslo
@@ -466,4 +477,6 @@ updateReminder.message=Zkontrolovat aktualizace?
updateReminder.description=Získejte aktuální informace o nových funkcích, opravách chyb a vylepšeních zabezpečení. Doporučujeme automaticky kontrolovat aktualizace.
updateReminder.notNow=Nyní ne
updateReminder.yesOnce=Ano, jednou
updateReminder.yesAutomatically=Ano, automaticky
updateReminder.yesAutomatically=Ano, automaticky
# Share Vault
+5 -11
View File
@@ -155,14 +155,10 @@ hub.receive.description=Cryptomator modtager og behandler svaret fra hubben. Ven
### Register Device
hub.register.message=Ny Enhed
hub.register.nameLabel=Enheds-navn
hub.register.occupiedMsg=Navnet er allerede i brug
hub.register.registerBtn=Bekræft
### Register Device Legacy
### Registration Success
hub.registerSuccess.message=Enhed navngivet
hub.registerSuccess.description=For at tilgå boksen, skal din enhed godkendes af boks-ejeren.
hub.registerSuccess.unlockBtn=Lås op
### Registration Failed
hub.registerFailed.message=Navngivning af enhed mislykkedes
hub.registerFailed.description=Der opstod en fejl i navngivnings-processen. Kig i applikations-loggen for flere detaljer.
### Unauthorized
hub.unauthorized.message=Adgang nægtet
hub.unauthorized.description=Din enhed er endnu ikke blevet godkendt til at få adgang til denne boks. Spørg boks-ejeren om godkendelse.
@@ -288,11 +284,9 @@ preferences.interface.showMinimizeButton=Vis knap til minimering
preferences.interface.showTrayIcon=Vis ikon i system-bakken (kræver genstart)
## Volume
preferences.volume=Virtuelt drev
preferences.volume.type=Drev Type
preferences.volume.type.automatic=Automatisk
preferences.volume.docsTooltip=Åbn dokumentationen for at lære mere om de forskellige typer drev.
preferences.volume.fuseRestartRequired=For at anvende ændringerne skal Cryptomator genstartes.
preferences.volume.tcp.port=TCP port
preferences.volume.supportedFeatures=Den valgte type drev understøtter følgende funktioner:
preferences.volume.feature.mountAuto=Automatisk valg af monteringspunkt
preferences.volume.feature.mountToDir=Brugerdefineret mappe som monteringspunkt
@@ -431,8 +425,6 @@ vaultOptions.general.startHealthCheckBtn=Start sunhedstjek
## Mount
vaultOptions.mount=Montering
vaultOptions.mount.info=Valgmulighederne afhænger af den valgte type drev.
vaultOptions.mount.linkToPreferences=Åbn virtuelle drev præferencer
vaultOptions.mount.readonly=Skrivebeskyttet
vaultOptions.mount.customMountFlags=Brugerdefinerede monterings-flag
vaultOptions.mount.winDriveLetterOccupied=optaget
@@ -511,4 +503,6 @@ updateReminder.message=Tjek for opdateringer?
updateReminder.description=Hold dig opdateret med nye funktioner, fejlrettelser og sikkerhedsforbedringer. Vi anbefaler automatisk at søge efter opdateringer.
updateReminder.notNow=Ikke nu
updateReminder.yesOnce=Ja, én gang
updateReminder.yesAutomatically=Ja, automatisk
updateReminder.yesAutomatically=Ja, automatisk
# Share Vault

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