Compare commits

...
216 Commits
Author SHA1 Message Date
Armin Schrenk ef190db739 Merge branch 'develop' into release/1.7.0 2023-01-27 11:36:28 +01:00
CryptobotandGitHub b378354bb9 New Crowdin updates (#2562)
New translations strings.properties

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

[ci skip]
2023-01-27 11:35:50 +01:00
Armin Schrenk 822e863e27 partially revert 220782b84f 2023-01-26 16:29:43 +01:00
Armin Schrenk 40fbe4f59d remove more libffi workaround leftovers 2023-01-26 16:05:53 +01:00
Tobias Hagemann bf779b5b41 updated dmg background 2023-01-26 15:57:10 +01:00
Tobias Hagemann ce5dbd14d8 reverted be7ac50928 2023-01-26 15:57:10 +01:00
Armin Schrenk 48744018a2 Merge branch 'release/1.7.0' into develop 2023-01-26 15:56:48 +01:00
Armin Schrenk bd9f79cb56 add cryptomator.mountPointsDir property to macOS installer scripts 2023-01-26 15:54:31 +01:00
Armin Schrenk f4c4f72539 remove libjffi stuff from appimage 2023-01-26 14:31:23 +01:00
Armin Schrenk 74718754ad fix merge 2023-01-26 13:48:30 +01:00
Armin Schrenk eb595379d6 Merge branch 'develop' into release/1.7.0
# Conflicts:
#	dist/linux/debian/control
#	dist/linux/debian/cryptomator.sh
2023-01-26 13:47:46 +01:00
Armin Schrenk 220782b84f reduce jars neede to stay on classpath due to missing module info 2023-01-26 13:28:55 +01:00
Armin SchrenkandGitHub 1c104737c1 Merge pull request #2639 from cryptomator/feature/mount-provider
Refactoring: Use mount service
2023-01-26 13:20:11 +01:00
Armin Schrenk 7c8a519919 bump webdav-nio-adapter 2023-01-26 13:10:32 +01:00
Armin Schrenk ae1fa5851f remove traces of ActualMountService from volume preferences controller 2023-01-26 13:02:21 +01:00
Armin Schrenk 86ccb1a58f changing volume provider requires restart due to macFUSE/FUSE-T lib loaded in same JVM 2023-01-26 12:05:34 +01:00
Armin Schrenk c3fb58f7d0 bump fuse-nio-adapter 2023-01-25 16:49:00 +01:00
Armin Schrenk b4c384acc9 use speciaized translation key 2023-01-25 12:44:09 +01:00
Armin Schrenk 8580e97791 cleanup 2023-01-23 14:39:46 +01:00
Armin Schrenk 5d348ee229 Bump WebDAV dependency 2023-01-23 14:39:21 +01:00
Armin Schrenk dda0ff545c add point of automatic mount point selection 2023-01-23 13:47:00 +01:00
Armin Schrenk 708b5938ac Add info about the dependency of volume type and mountOptions 2023-01-23 13:11:01 +01:00
Armin SchrenkandGitHub a3cb8cd2f1 Merge pull request #2641 from purejava/jdk.security.auth
Add jdk.security.auth module as it's required by dbus-java-core

Closes #2640
2023-01-23 11:10:59 +01:00
Armin Schrenk 3587d53d15 bump integrations-mac, fixes NoSuchServiceException for RevealPathService on macOS 2023-01-23 11:09:15 +01:00
Tobias HagemannandGitHub 2f5e2af8d9 Merge pull request #2638 from cryptomator/feature/traymenu-onbeforeopenmenu-macos-fix
Fixed vault list refresh when tray icon is clicked on macOS
2023-01-23 10:24:57 +01:00
Ralph PlawetzkiandGitHub 8139cb8c38 Add jdk.security.auth module to rules 2023-01-22 20:05:13 +01:00
Armin Schrenk 09d5f71335 do not reset mountflags to default ones when window opens 2023-01-22 19:24:35 +01:00
Ralph Plawetzki 3a638735b7 Add jdk.security.auth module to appimage.yml 2023-01-22 18:11:50 +01:00
Ralph Plawetzki f1f5f7e02f Add jdk.security.auth module as it's required by dbus-java-core 2023-01-22 17:59:20 +01:00
Tobias Hagemann 94fe8ca449 updated string
[ci skip]
2023-01-20 17:18:05 +01:00
Tobias Hagemann 1251e1b873 updated separator styling 2023-01-20 17:14:02 +01:00
Armin Schrenk 50405c0bd5 remove debug message 2023-01-20 17:07:19 +01:00
Armin Schrenk 774d24625c change styling of volume preferences 2023-01-20 17:07:04 +01:00
Tobias Hagemann ba4902a5bc Fixed vault list refresh when tray icon is clicked on macOS 2023-01-20 16:45:35 +01:00
Armin Schrenk 90408504e2 Merge branch 'develop' into feature/mount-provider
# Conflicts:
#	pom.xml
#	src/main/java/org/cryptomator/common/vaults/Vault.java
#	src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
2023-01-20 15:27:48 +01:00
Armin Schrenk c8c44e0554 bump fuse-nio-adapter dependency 2023-01-20 15:01:31 +01:00
Armin Schrenk 7afab6fe78 add auto option to volume type selection 2023-01-20 12:29:17 +01:00
Armin Schrenk ad321dfe55 migrate 1.6.x volumeimpl to hardcoded mountservices 2023-01-20 11:49:15 +01:00
d6388d6205 Added drop zone for revealing encrypted files or folders (#2592)
* Added drop zone for revealing encrypted files or folders

* Split god drop zone into distinct drop zones in vault list and vault detail unlocked

* Prevent cursor from changing if content doesn't match during drag over

[ci skip]

* Removed unused methods / css classes

[ci skip]

* Updated method name

* Refactor Vault::getCiphertextPath to only accept strings starting with "/"

* bump cryptofs

* ensure that path cleartext path always starts with "/"

* added file chooser for revealing encrypted items

* Trying to fix path parsing again

* Updated drag & drop design for buttons

* use RevealPathService to reveal files in file manager

* fix compilation error

* Copy paths to clipboard if no revealService is present

* reintegrate wongFileAlert

* Only accept TrasnferMode.LINK

* updated drag-n-drop button styling

* added tooltip

* updated string

* cleanup

Co-authored-by: Armin Schrenk <armin.schrenk@skymatic.de>
2023-01-20 10:46:00 +01:00
Armin SchrenkandGitHub b8326907bf Merge pull request #2624 from purejava/update-appimage-build-script
Update build.sh to .github/workflows/appimage.yml
2023-01-19 13:37:10 +01:00
Armin Schrenk 5beeeae27f Sign integrations.dll during build 2023-01-16 18:47:41 +01:00
Armin Schrenk fb1f3c4757 fix bug using wrong mount flags 2023-01-16 14:14:03 +01:00
Armin Schrenk 5e32cbcdd0 show supported mount features in preferences/volume 2023-01-13 18:11:36 +01:00
Armin Schrenk 08d741ecf5 address TODOs 2023-01-13 16:24:15 +01:00
Armin Schrenk 23060a8497 reinitate workaround for MOUNT_WITHIN_PARENT 2023-01-13 15:59:44 +01:00
Armin Schrenk 47a32893f0 Refactor mounter class 2023-01-13 13:46:41 +01:00
Armin Schrenk 0f7ba4e00e remove updating driveletter strings 2023-01-13 12:53:41 +01:00
Armin Schrenk 0d75b999a3 cleanup 2023-01-13 12:50:38 +01:00
Armin Schrenk b6d97528d2 fix driveletter selection 2023-01-13 12:49:20 +01:00
Armin Schrenk 94c53989b1 MountOptions window: Reset to auto mount, if no dir selected 2023-01-13 11:27:30 +01:00
Armin Schrenk a72ea1b9f5 move mount prepartions to own class 2023-01-13 11:20:23 +01:00
Armin Schrenk 6395f17736 Handle illegelArgumentException when setting custom mountpoint 2023-01-12 17:21:54 +01:00
Armin Schrenk 5c61944757 fix minor ui bugs 2023-01-12 12:53:58 +01:00
Armin Schrenk c641695a40 migrate 1.6.x vault settings on-the-fly 2023-01-12 12:11:03 +01:00
Armin Schrenk d93ef2e905 Reinitiate force lock 2023-01-11 16:40:14 +01:00
Armin Schrenk 8b9377cf4e only fix custom mount flags, if explicitly desired. Otherwise use the ones provided by mount service 2023-01-11 16:35:12 +01:00
Armin Schrenk 991b03e000 use function paramter, not instance variable 2023-01-11 16:21:49 +01:00
Armin Schrenk 73d8c4098e resolve TODOs 2023-01-11 16:18:05 +01:00
Armin Schrenk 6a52d15510 Show mountpath in reveal button 2023-01-11 16:01:59 +01:00
Armin Schrenk d07b1b96dd Use first available mount provider if none is selected or selected is not present 2023-01-11 16:01:51 +01:00
Armin Schrenk 24d472ed8b Merge branch 'develop' into feature/mount-provider
# Conflicts:
#	dist/linux/appimage/build.sh
#	dist/linux/debian/rules
2023-01-11 16:01:27 +01:00
Armin Schrenk 6fe2d73c52 copy uri to clipboard when using fallback mounter 2023-01-11 15:59:39 +01:00
Armin Schrenk 523e5d7904 bump webdav- and dokany-adapter 2023-01-11 15:59:17 +01:00
Tobias Hagemann 304064d9ab updated dependency-check version 2023-01-09 11:59:51 +01:00
Tobias Hagemann 0d49c60d4d reverted 1a6258eb17 2023-01-09 11:32:11 +01:00
Ralph Plawetzki 0e0e00f116 Remove stuff not needed when run locally
From: changes requested for PR #2624
2023-01-08 14:10:38 +01:00
Ralph Plawetzki 77c0a3e265 Update build.sh to .github/workflows/appimage.yml 2023-01-07 15:31:32 +01:00
Tobias Hagemann 1807888860 updated sponsors
[ci skip]
2023-01-05 11:16:22 +01:00
Tobias Hagemann be7ac50928 added new app icon for macos, fixes #2517 2023-01-04 16:32:40 +01:00
Julian Raufelder 9182c415eb Hello 2023 🎉 2023-01-04 13:35:58 +01:00
Armin Schrenk 1a6258eb17 revert eea568ca4e and e2898f0c8b 2023-01-04 12:52:16 +01:00
Armin SchrenkandGitHub 9c0c6a91fb Merge pull request #2580 from jacob-willden/develop
Add more info to Migration Screen
2023-01-03 16:47:18 +01:00
Armin Schrenk 53436520c9 fix using the wrong property 2023-01-03 12:38:18 +01:00
Armin Schrenk fd5aeaf90a Merge branch 'develop' into feature/mount-setting-per-vault 2023-01-02 13:47:30 +01:00
Armin Schrenk f3fcfc913b Remove unused class 2023-01-02 12:10:46 +01:00
Armin SchrenkandGitHub 0b0870e313 Merge pull request #2575 from VsevolodGolovanov/issue_2557_unlocked_status_indicator_in_tray_icon
Unlocked status indicator in tray icon 

Closes #2557
2022-12-30 11:40:51 +01:00
Armin SchrenkandGitHub 6704bb1f8c Merge pull request #2612 from cryptomator/feature/2597-no-keychain-dialog
Feature: Show dialog for hub vaults, if keychain is disabled'

Closes #2597
2022-12-30 11:32:40 +01:00
Armin Schrenk 48d1938b99 Refine dialog text 2022-12-30 11:19:37 +01:00
Armin Schrenk 6199277002 Add Button to open preferences 2022-12-29 18:26:13 +01:00
Armin Schrenk 9fefe8e462 Show info dialog when keychain is deactivated 2022-12-29 18:20:46 +01:00
Armin Schrenk 5fcd53be18 load device key before showing UI to prevent uncatched failure of UI loading 2022-12-29 18:17:47 +01:00
Jacob Willden 7147636428 Switch term for new vault migration dialog 2022-12-21 18:56:17 -07:00
Jacob Willden 690bd833e8 Add more info to Migration Screen 2022-12-16 13:28:57 -07:00
1e12590681 Update src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java
Co-authored-by: Tobias Hagemann <tobias.hagemann@gmail.com>
2022-12-15 13:34:52 +04:00
Tobias Hagemann eea568ca4e GCM is explicitly ready! 2022-12-14 22:27:34 +01:00
Armin Schrenk 8009205da7 Merge branch 'main' into develop
# Conflicts:
#	pom.xml
2022-12-14 20:07:24 +01:00
Armin Schrenk c0f73a2802 Merge branch 'hotfix/1.6.17' 2022-12-14 20:06:35 +01:00
Armin Schrenk 8e902877a3 Update dependency-check plugin and exclude false positive
(cherry picked from commit ebcd0adf78)
2022-12-14 20:06:23 +01:00
Armin Schrenk fd76c89393 update metainfo 2022-12-14 20:02:12 +01:00
Armin Schrenk 975baf594c Fixes #2576 2022-12-14 19:58:22 +01:00
Vsevolod Golovanov 5a99c7bb61 Unlocked status indicator in tray icon #2557
Now tracking unlockedness and updating the icon accordingly.
2022-12-14 21:18:37 +04:00
Tobias Hagemann e2898f0c8b GCM is ready! 2022-12-14 17:36:06 +01:00
Tobias Hagemann cf2b236df5 added tray icons for unlocked state 2022-12-14 17:28:59 +01:00
Tobias Hagemann 819fa803f3 cleaned up tray icons 2022-12-14 16:47:07 +01:00
Armin Schrenk 980e2b184b use correct tag name for gh release in debian workflow 2022-12-13 12:48:29 +01:00
Sebastian Stenzel ebad677e84 fix path to downloaded jmod files 2022-12-12 09:14:24 +01:00
Sebastian Stenzel 4fda4a2b2e fix makefile 2022-12-12 09:09:25 +01:00
Sebastian Stenzel ffaf7ad360 adjust package dependencies to new mount api and jfuse 2022-12-12 09:03:56 +01:00
Sebastian Stenzel 76689705f5 remove LD_PRELOAD workaround, which should no longer be required with jfuse 2022-12-12 08:54:18 +01:00
Armin Schrenk 1a358f06e8 revert 0725a63b09 and fix workflow
(cherry picked from commit 9dfd8adcce)
2022-12-09 17:01:42 +01:00
Armin Schrenk db4d845a5b adjust matching rules for version type detection
(cherry picked from commit cd358916260e713d4119c20b1bcb5b448a017365)
(cherry picked from commit 433eecc6a8)
2022-12-09 17:01:33 +01:00
Armin Schrenk 0d6d7b0dd6 directly use inputs object 2022-12-09 16:56:45 +01:00
Armin Schrenk 9dfd8adcce revert 0725a63b09 and fix workflow 2022-12-09 16:56:02 +01:00
Armin Schrenk 433eecc6a8 adjust matching rules for version type detection
(cherry picked from commit cd358916260e713d4119c20b1bcb5b448a017365)
2022-12-09 16:50:39 +01:00
Armin Schrenk 36ed4554b2 Fixes #2570
Dabian build: Make libffi7 a runtime dependency and preload it.
2022-12-09 15:12:04 +01:00
Armin Schrenk c2dac30f2d Merge branch 'develop' into release/1.7.0 2022-12-08 17:50:55 +01:00
Armin Schrenk 63a89b5bcf adjust post-release and win-exe workflow 2022-12-08 17:50:31 +01:00
Armin Schrenk 12b53acd22 Merge branch 'develop' into release/1.7.0 2022-12-08 17:37:29 +01:00
Armin Schrenk 9105d1af16 Merge branch 'feature/mount-provider' into release/1.7.0
# Conflicts:
#	dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml
2022-12-08 17:02:44 +01:00
Armin Schrenk de46b91420 use directly inputs object 2022-12-08 13:59:55 +01:00
Armin Schrenk 67b40649bc fix version number in debian workflow 2022-12-08 13:56:42 +01:00
Armin Schrenk c41af77108 remove unused translation keys 2022-12-08 10:50:04 +01:00
Armin Schrenk ba66d4bfce bump fuse-nio-adapter 2022-12-07 17:39:27 +01:00
Armin Schrenk c45a00377d Merge branch 'develop' into feature/mount-provider
# Conflicts:
#	.github/workflows/appimage.yml
#	.github/workflows/mac-dmg.yml
#	.github/workflows/win-exe.yml
#	pom.xml
#	src/main/java/org/cryptomator/common/vaults/WebDavVolume.java
#	src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java
2022-12-07 17:01:56 +01:00
Armin Schrenk f9d588320c add slack notification to release PPA 2022-12-07 14:53:41 +01:00
Armin Schrenk c61087e697 create and upload source signature after release publish 2022-12-07 14:52:33 +01:00
Armin Schrenk 0725a63b09 release winget manually 2022-12-07 14:29:00 +01:00
Armin Schrenk ebcd0adf78 Update dependency-check plugin and exclude false positive 2022-12-07 14:17:42 +01:00
Armin Schrenk 0e7ec8eb37 bump replace deprecated syntax and dependencies in win-exe build 2022-12-07 12:24:42 +01:00
Armin Schrenk 0e9b115647 debian build: use correct package names 2022-12-07 11:43:59 +01:00
Armin Schrenk 5ba137dda5 Add additional build dependencies to debian build 2022-12-07 11:11:31 +01:00
Sebastian Stenzel db928ba034 try fixing debian build 2022-12-06 20:49:09 +01:00
Sebastian Stenzel 2cc01be236 simplify jmod compatibility check 2022-12-06 20:44:51 +01:00
Sebastian Stenzel ba2a4955d0 fix expected orig.tar.gz name 2022-12-06 20:27:19 +01:00
Sebastian Stenzel d1971e3c2a yml validation error 2022-12-06 20:18:55 +01:00
Sebastian Stenzel 5665984a9b trigger Debian build and provide version info by hand 2022-12-06 20:13:34 +01:00
Armin Schrenk 1f2225991a Merge branch 'main' into develop 2022-12-06 15:46:19 +01:00
Armin Schrenk 1f0fe161fd Merge branch 'release/1.6.16' 2022-12-06 15:45:34 +01:00
Armin Schrenk 4ff8bbaa62 Merge branch 'develop' into release/1.6.16 2022-12-06 15:44:18 +01:00
Armin Schrenk e188169916 add missing javafx getter in resultListCell 2022-12-06 15:44:02 +01:00
Armin Schrenk 1b9480293a Bump cryptofs 2022-12-06 15:43:25 +01:00
Armin Schrenk d8b71db8e5 bump metainfo.xml 2022-12-06 15:27:17 +01:00
Armin Schrenk 4bd99d2b66 preparing 1.6.16 2022-12-06 15:24:52 +01:00
CryptobotandGitHub 61027852e1 New Crowdin updates (#2474)
New translations strings.properties

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

[ci skip]
2022-12-06 15:20:47 +01:00
Armin Schrenk 512500aaef bump cryptofs version 2022-12-06 11:03:29 +01:00
Armin SchrenkandGitHub f9d28f20b3 Update README.md
* update number of commits
* remove "start" section
2022-12-06 10:59:18 +01:00
Armin SchrenkandGitHub 7f79c554f7 Merge pull request #2553 from cryptomator/feature/health-check-fix-batch
Improve Health Check (Fix-all-button, result filtering and more)
2022-12-06 10:47:11 +01:00
Armin Schrenk 3cbe4aa7eb use functional style 2022-12-06 10:39:38 +01:00
Armin Schrenk 06922717c4 adjust spacing and window size to fit (english) labels 2022-12-05 23:32:49 +01:00
Armin SchrenkandGitHub 32399c4a09 Merge pull request #2546 from cryptomator/feature/javafx-jmods
Use OpenJFX jmods in jlink
2022-12-05 23:18:52 +01:00
Armin Schrenk 00a3692dcf final adjustments 2022-12-05 23:06:03 +01:00
Armin Schrenk 83b198d491 only run jfx guard on one mac system 2022-12-05 22:38:37 +01:00
Armin Schrenk 8086ec1c10 improve check on debian to not return non-zero code 2022-12-05 22:37:21 +01:00
Armin Schrenk b2b49f4d2f use correct pwsh syntax on all publish workflows 2022-12-05 16:49:19 +01:00
Armin Schrenk d6e14dab9a version defined in javafx.properties is also the complete version 2022-12-05 16:43:58 +01:00
Armin Schrenk 497ddcfc9c shoulda used quotes 2022-12-05 16:37:48 +01:00
Armin Schrenk 169039903d Use correct cmdlet 2022-12-05 16:31:09 +01:00
Armin Schrenk 3641b34c2d add guard to ensure pom defined jfx version matches the jdk bundled/jmod one 2022-12-05 16:10:39 +01:00
Armin SchrenkandGitHub 42400d1f0c Merge pull request #2233 from cryptomator/feature/stats-counter-for-metadata
Add to statistics window a counter for metadata

Closes #1511
2022-12-02 13:09:25 +01:00
Armin SchrenkandGitHub 8f7f8ffb11 Merge branch 'develop' into feature/stats-counter-for-metadata 2022-12-02 12:19:53 +01:00
Armin Schrenk 3016d35c9b closes #2488 2022-12-02 11:22:50 +01:00
Armin Schrenk f21b8a0f7a Merge branch 'develop' into feature/health-check-fix-batch
# Conflicts:
#	pom.xml
2022-12-02 11:08:58 +01:00
Armin Schrenk 0c1bbc5bfd remove labels and change alignment 2022-12-02 10:53:30 +01:00
Armin Schrenk d932091021 add translations for fixState filter 2022-12-01 22:20:31 +01:00
Armin Schrenk 216abf224e add severity and its list filter translatable 2022-12-01 17:37:26 +01:00
Armin Schrenk d5523f07bd adjust translations and translation keys 2022-12-01 13:32:39 +01:00
Armin Schrenk e097bea1f5 adjust stats alignment 2022-12-01 13:28:36 +01:00
Armin Schrenk 920866b1a1 Merge branch 'develop' into feature/stats-counter-for-metadata
# Conflicts:
#	pom.xml
2022-12-01 12:44:09 +01:00
Armin SchrenkandGitHub c41ffe2568 Merge pull request #2489 from ledwardchow/fix/2383-tray-icon-refresh
Refresh vault list when tray icon is clicked
2022-11-30 16:37:19 +01:00
Armin Schrenk 792fe1eafe use jfx19 API 2022-11-30 16:23:44 +01:00
Armin Schrenk 348bc2b2ec add translation to filter labels 2022-11-30 13:53:29 +01:00
Armin Schrenk 7bac78bc5d Center Filters and let result list grow to bottom 2022-11-30 13:50:31 +01:00
Armin Schrenk 65e1993b43 Add tooltip to result severity icon 2022-11-30 13:35:06 +01:00
Armin Schrenk b98eb8c656 Using Zulu JDK, codesign addiitonal files on macOS 2022-11-30 12:48:18 +01:00
Sebastian StenzelandGitHub 33ebc17619 Merge branch 'develop' into fix/2383-tray-icon-refresh 2022-11-30 11:46:36 +01:00
Sebastian Stenzel ba7029968d try fixing dpkg-shlibdeps: error: cannot find library libgtk-x11-2.0.so.0 ... 2022-11-30 11:16:38 +01:00
Sebastian Stenzel a3fc68ab6f jlink javafx.* modules from arch-dependent jmod path 2022-11-30 11:16:38 +01:00
Sebastian Stenzel abf944a130 add required openjfx jmods to debian source package 2022-11-30 11:16:38 +01:00
Sebastian Stenzel 99bc4dbdc5 use openjfx jmods instead of jars 2022-11-30 11:16:38 +01:00
Armin SchrenkandGitHub 019292f1db Merge pull request #2521 from cryptomator/feature/mac-screenshot-fix
Fixes wrong screenshot shown in Open Existing Vault

Fixes #2512
2022-11-29 17:19:31 +01:00
Armin Schrenk 2f9818aade use new JavaFX 19 API 2022-11-29 17:07:43 +01:00
Armin Schrenk 9cec45dc1f Add context menu to copy single result info 2022-11-29 14:18:43 +01:00
Armin Schrenk 5f55530b4a Add filters to results list 2022-11-29 13:49:11 +01:00
Armin Schrenk 96de2556a9 apply correct style class on check icon 2022-11-28 15:00:16 +01:00
Armin Schrenk db63a82d76 bump version of skymatic actions 2022-11-28 13:46:02 +01:00
Armin Schrenk 11aea06ecf replace deprecated set-output command in ci 2022-11-28 13:36:15 +01:00
Armin SchrenkandGitHub 932cfd2200 Merge pull request #2541 from cryptomator/feature/reusable-workflow-version
Use reusable workflow to get version information
2022-11-28 13:17:23 +01:00
Armin Schrenk edaa2b7391 Revert partially 9984b2af9b 2022-11-25 18:07:07 +01:00
Armin Schrenk 9984b2af9b Fix errors 2022-11-25 17:56:24 +01:00
Armin Schrenk 34986f5919 migrate other build worfklows to use get-version.yml 2022-11-25 17:46:24 +01:00
Armin Schrenk 23d9521724 Remove resolved TODO (see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_call) 2022-11-25 17:29:36 +01:00
Armin Schrenk 4dc1d59305 Implement fix all button 2022-11-25 17:17:55 +01:00
Armin Schrenk 74e3c28441 bump cryptofs 2022-11-25 16:54:21 +01:00
Sebastian Stenzel 6e526ba796 draft release 2022-11-25 14:47:07 +01:00
Sebastian Stenzel b978044383 Merge branch 'feature/mount-provider' into release/1.7.0 2022-11-25 14:44:41 +01:00
Sebastian Stenzel 6325c6f450 fixed java options required for panama 2022-11-25 14:42:18 +01:00
Armin Schrenk dd79b46d72 add --enable-preview flag to java runtime options 2022-11-25 12:21:37 +01:00
Armin Schrenk d896fe21b5 adapt result fixing to new api 2022-11-24 10:13:37 +01:00
Armin Schrenk 6983d9d72d Remove cryptolib dependency, bump cryptofs 2022-11-24 10:12:59 +01:00
Armin Schrenk faf39c2507 fix wrong name 2022-11-22 15:01:47 +01:00
Armin Schrenk 083d0cfbc4 migrate windows build to use get-version workflow 2022-11-22 14:51:12 +01:00
Armin Schrenk 9ca8072ab6 Rename workflow and add type output 2022-11-22 14:45:49 +01:00
Armin Schrenk 3223ea2e5d Refactor version parsing and validation to reusable workflow 2022-11-22 11:18:05 +01:00
Armin Schrenk 83af5e796f publish only production versions to winget 2022-11-21 22:10:06 +01:00
Armin SchrenkandGitHub 9d9f118a32 Merge pull request #2537 from cryptomator/feature/generalize-win-installer
Windows Installer: Parameterize Hardcoded Values
2022-11-21 13:55:34 +01:00
Armin Schrenk 1b0eb34734 set loopback alias via parameter in ps1 script 2022-11-21 13:46:12 +01:00
Armin Schrenk 79952418f4 only use lower characters in loopback alias 2022-11-21 13:26:14 +01:00
Armin Schrenk 65e26971ff make loopback alias depend on appName 2022-11-21 13:12:32 +01:00
Armin Schrenk 13debaafbe set loopback alias in Cryptomator app during build time 2022-11-18 13:34:38 +01:00
Armin Schrenk 5729e2885d fix typo [ci skip] 2022-11-18 12:42:11 +01:00
Armin Schrenk 043184cf53 set loopback device alias during build
instead of hardcode
2022-11-18 12:36:10 +01:00
Armin Schrenk 38062f9a8d Move Cryptomator installer defaults to actual installer file 2022-11-18 11:32:39 +01:00
Armin Schrenk 14d0d95f23 add variable to skip legacy check in installer 2022-11-18 11:20:51 +01:00
Edward Chow 759c21e5cf fix preconditions import 2022-11-13 11:43:05 +11:00
Edward Chow d8ed9f854a update pom.xml for integrations-api 1.2.0-beta1 2022-11-13 11:42:41 +11:00
Edward Chow 1414ab4681 use preconditions instead of try...catch 2022-11-12 22:26:08 +11:00
Edward Chow 1486826883 remove unused imports 2022-11-12 22:19:22 +11:00
Tobias Hagemann dda7255d8e deduplicated code 2022-11-10 18:36:14 +01:00
Tobias Hagemann 6caf132cfb fixes #2512 2022-11-10 18:18:34 +01:00
Edward Chow dc310c009f rename to onBeforeOpenMenu and init local reference in showTrayIcon() 2022-11-01 22:37:11 +11:00
Edward Chow 445be3b668 remove straggling lines of awt code 2022-10-16 22:36:25 +11:00
Edward Chow f852ae0392 remove AWT code and relocate to TrayMenuController 2022-10-16 22:26:30 +11:00
Edward Chow e5e606f3ae refresh vault list on tray icon click (#2383) 2022-10-16 17:42:07 +11:00
Martin Beyer e3c609351f Add third stats graph, displaying general file(attribute) accesses 2022-05-05 00:09:39 +02:00
Martin Beyer 01412070ad Merge remote-tracking branch 'origin/develop' into feature/stats-counter-for-metadata 2022-05-04 15:41:33 +02:00
Martin Beyer 4944a634fb Add counter for file access 2022-04-14 15:47:41 +02:00
149 changed files with 3964 additions and 1421 deletions
+27 -36
View File
@@ -13,40 +13,35 @@ env:
JAVA_VERSION: 19
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build:
name: Build AppImage
runs-on: ubuntu-latest
needs: [get-version]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
java-package: 'jdk+fx'
cache: 'maven'
- id: versions
name: Apply version information
- name: Ensure major jfx version in pom equals in jdk
shell: pwsh
run: |
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
mvn versions:set -DnewVersion=${SEM_VER_STR}
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
mvn versions:set -DnewVersion=${SEM_VER_STR}
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
REVCOUNT=`git rev-list --count HEAD`
echo "::set-output name=semVerStr::${SEM_VER_STR}"
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
echo "::set-output name=revNum::${REVCOUNT}"
- name: Validate Version
uses: skymatic/semver-validation-action@v1
with:
version: ${{ steps.versions.outputs.semVerStr }}
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
exit 1
}
- name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
run: mvn -B clean package -Pdependency-check,linux -DskipTests
- name: Patch target dir
@@ -60,7 +55,7 @@ jobs:
--verbose
--output runtime
--module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr
--strip-native-commands
--no-header-files
--no-man-pages
@@ -69,8 +64,8 @@ jobs:
- name: Prepare additional launcher
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
env:
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
REVISION_NUM: ${{ needs.get-version.outputs.revNum }}
- name: Run jpackage
run: >
${JAVA_HOME}/bin/jpackage
@@ -83,11 +78,13 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2022 Skymatic GmbH"
--app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
--copyright "(C) 2016 - 2023 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"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
@@ -96,7 +93,7 @@ jobs:
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=false"
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\""
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
--resource-dir dist/linux/resources
- name: Patch Cryptomator.AppDir
@@ -114,12 +111,6 @@ jobs:
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
- name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27
run: |
JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'`
${JAVA_HOME}/bin/jar -xf lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/
mv jni/x86_64-Linux/* lib/app/libjffi.so
working-directory: Cryptomator.AppDir
- name: Download AppImageKit
run: |
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage
@@ -134,7 +125,7 @@ jobs:
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Build AppImage
run: >
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ steps.versions.outputs.semVerStr }}-x86_64.AppImage
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-x86_64.AppImage
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
- name: Create detached GPG signatures
-12
View File
@@ -42,15 +42,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Sign source tarball with key 615D449FE6E6A235
if: startsWith(github.ref, 'refs/tags/')
run: |
git archive --prefix="cryptomator-${{ github.ref_name }}/" -o "cryptomator-${{ github.ref_name }}.tar.gz" ${{ github.ref }}
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Draft a release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
@@ -59,9 +50,6 @@ jobs:
discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
generate_release_notes: true
files: |
cryptomator-*.tar.gz.asc
fail_on_unmatched_files: true
body: |-
:construction: Work in Progress
+68 -35
View File
@@ -1,21 +1,27 @@
name: Build Debian Package
on:
release:
types: [published]
workflow_dispatch:
inputs:
ref:
description: 'GitHub Ref (e.g. refs/tags/1.6.16)'
required: true
semver:
description: 'SemVer String (e.g. 1.7.0-beta1)'
required: true
ppaver:
description: 'Base PPA Version String (e.g. 1.6.16+1.7.0~beta1) without -0ppa1'
required: true
dput:
description: 'Upload to PPA'
required: true
default: false
type: boolean
version:
description: 'Version'
required: false
env:
JAVA_VERSION: 19
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/19/openjfx-19_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/19/openjfx-19_linux-aarch64_bin-jmods.zip'
jobs:
build:
@@ -24,50 +30,69 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- id: versions
name: Get version information
run: |
SEM_VER_STR="${{ inputs.semver }}"
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
REVCOUNT=`git rev-list --count HEAD`
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
- name: Install build tools
run: |
sudo add-apt-repository ppa:coffeelibs/openjdk
sudo apt-get update
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19 libgtk2.0-0
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- id: versions
name: Apply version information
run: |
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
mvn versions:set -DnewVersion=${SEM_VER_STR}
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
mvn versions:set -DnewVersion=${SEM_VER_STR}
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
REVCOUNT=`git rev-list --count HEAD`
echo "::set-output name=semVerStr::${SEM_VER_STR}"
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
echo "::set-output name=revNum::${REVCOUNT}"
echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}"
- name: Validate Version
uses: skymatic/semver-validation-action@v1
with:
version: ${{ steps.versions.outputs.semVerStr }}
- name: Run maven
run: mvn -B clean package -Pdependency-check,linux -DskipTests
- name: Create orig.tar.gz with common/ libs/ mods/
- name: Download OpenJFX jmods
id: download-jmods
run: |
curl -L ${{ env.OPENJFX_JMODS_AMD64 }} -o openjfx-amd64.zip
mkdir -p jmods/amd64
unzip -j openjfx-amd64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/amd64
curl -L ${{ env.OPENJFX_JMODS_AARCH64 }} -o openjfx-aarch64.zip
mkdir -p jmods/aarch64
unzip -j openjfx-aarch64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/aarch64
- name: Ensure major jfx version in pom and in jmods is the same
run: |
JMOD_VERSION_AMD64=$(jmod describe jmods/amd64/javafx.base.jmod | head -1)
JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64#*@}
JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64%%.*}
JMOD_VERSION_AARCH64=$(jmod describe jmods/aarch64/javafx.base.jmod | head -1)
JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64#*@}
JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION_AMD64})"
exit 1
fi
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AARCH64 ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != aarch64 jmod version (${JMOD_VERSION_AARCH64})"
exit 1
fi
- name: Create orig.tar.gz with common/ libs/ mods/ jmods/
run: |
mkdir pkgdir
cp -r target/libs pkgdir
cp -r target/mods pkgdir
cp -r jmods pkgdir
cp -r dist/linux/common/ pkgdir
cp target/cryptomator-*.jar pkgdir/mods
tar -cJf cryptomator_${{ steps.versions.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir .
tar -cJf cryptomator_${{ inputs.ppaver }}.orig.tar.xz -C pkgdir .
- name: Patch and rename pkgdir
run: |
cp -r dist/linux/debian/ pkgdir
@@ -75,12 +100,12 @@ jobs:
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
find . -name "*.jar" >> pkgdir/debian/source/include-binaries
mv pkgdir cryptomator_${{ steps.versions.outputs.ppaVerStr }}
mv pkgdir cryptomator_${{ inputs.ppaver }}
env:
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
VERSION_NUM: ${{ steps.versions.outputs.semVerNum }}
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
PPA_VERSION: ${{ steps.versions.outputs.ppaVerStr }}-0ppa1
PPA_VERSION: ${{ inputs.ppaver }}-0ppa1
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -95,7 +120,7 @@ jobs:
env:
DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback
DEBSIGN_KEYID: 615D449FE6E6A235
working-directory: cryptomator_${{ steps.versions.outputs.ppaVerStr }}
working-directory: cryptomator_${{ inputs.ppaver }}
- name: Create detached GPG signatures
run: |
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
@@ -112,13 +137,21 @@ jobs:
cryptomator_*_amd64.deb
cryptomator_*.asc
- name: Publish on PPA
if: startsWith(github.ref, 'refs/tags/') || inputs.dput
if: inputs.dput
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
# If ref is a tag, also upload to GitHub Releases:
- name: Determine tag name
if: startsWith(inputs.ref, 'refs/tags/')
run: |
REF=${{ inputs.ref }}
echo "TAG_NAME=${REF##*/}" >> $GITHUB_ENV
- name: Publish Debian package on GitHub Releases
if: startsWith(github.ref, 'refs/tags/')
if: startsWith(inputs.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
fail_on_unmatched_files: true
tag_name: ${{ inputs.ref }}
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
cryptomator_*_amd64.deb
+1 -1
View File
@@ -48,7 +48,7 @@ jobs:
jq -c 'select(.filename|endswith(".dmg")) | select(.filename|endswith("-arm64.dmg")|not) | {name: "github.releases.downloads", tags: ["file=dmg", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
RESULT=$(jq -s -c "." output.json)
echo "::set-output name=result::${RESULT}"
echo "result=${RESULT}" >> $GITHUB_OUTPUT
env:
INTERVAL: 900
JSON_DATA: ${{ steps.get-stats.outputs.result }}
+77
View File
@@ -0,0 +1,77 @@
name: Parse and Validate a version string or tag
on:
workflow_call:
inputs:
version:
description: "A specific version to use"
required: false
type: string
outputs:
semVerStr:
description: "The full version string."
value: ${{ jobs.determine-version.outputs.semVerStr}}
semVerNum:
description: "The numerical part of the version string"
value: ${{ jobs.determine-version.outputs.semVerNum}}
revNum:
description: "The revision number"
value: ${{ jobs.determine-version.outputs.revNum}}
versionType:
description: "Type of the version. Values are [stable, alpha, beta, rc, unknown]"
value: ${{ jobs.determine-version.outputs.type }}
env:
JAVA_VERSION: 19
JAVA_DIST: 'temurin'
JAVA_CACHE: 'maven'
jobs:
determine-version:
name: 'Determines the version following semver'
runs-on: ubuntu-latest
outputs:
semVerNum: ${{ steps.versions.outputs.semVerNum }}
semVerStr: ${{ steps.versions.outputs.semVerStr }}
revNum: ${{ steps.versions.outputs.revNum }}
type: ${{ steps.versions.outputs.type}}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: ${{ env.JAVA_CACHE }}
- id: versions
name: Get version information
run: |
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
REVCOUNT=`git rev-list --count HEAD`
TYPE="unknown"
if [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then
TYPE="stable"
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-alpha[1-9]+$ ]]; then
TYPE="alpha"
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-beta[1-9]+$ ]]; then
TYPE="beta"
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-rc[1-9]$ ]]; then
TYPE="rc"
fi
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
echo "type=${TYPE}" >> $GITHUB_OUTPUT
- name: Validate Version
uses: skymatic/semver-validation-action@v2
with:
version: ${{ steps.versions.outputs.semVerStr }}
+34 -31
View File
@@ -13,9 +13,15 @@ env:
JAVA_VERSION: 19
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build:
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
runs-on: ${{ matrix.os }}
needs: [get-version]
strategy:
fail-fast: false
matrix:
@@ -30,36 +36,26 @@ jobs:
xcode-path: '/Applications/Xcode_13.2.1.app'
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
java-package: 'jdk+fx'
architecture: ${{ matrix.architecture }}
cache: 'maven'
- id: versions
name: Apply version information
- name: Ensure major jfx version in pom equals in jdk
if: ${{ !contains(matrix.os, 'self-hosted') }}
shell: pwsh
run: |
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
mvn versions:set -DnewVersion=${SEM_VER_STR}
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
mvn versions:set -DnewVersion=${SEM_VER_STR}
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
REVCOUNT=`git rev-list --count HEAD`
echo "::set-output name=semVerStr::${SEM_VER_STR}"
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
echo "::set-output name=revNum::${REVCOUNT}"
- name: Validate Version
uses: skymatic/semver-validation-action@v1
with:
version: ${{ steps.versions.outputs.semVerStr }}
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
exit 1
}
- name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
run: mvn -B clean package -Pdependency-check,mac -DskipTests
- name: Patch target dir
@@ -73,7 +69,7 @@ jobs:
--verbose
--output runtime
--module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--strip-native-commands
--no-header-files
--no-man-pages
@@ -91,22 +87,25 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2022 Skymatic GmbH"
--app-version "${{ steps.versions.outputs.semVerNum }}"
--copyright "(C) 2016 - 2023 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\""
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
--mac-package-identifier org.cryptomator
--resource-dir dist/mac/resources
- name: Patch Cryptomator.app
@@ -116,8 +115,8 @@ jobs:
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
env:
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
REVISION_NO: ${{ steps.versions.outputs.revNum }}
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
- name: Generate license for dmg
run: >
mvn -B license:add-third-party
@@ -151,6 +150,10 @@ jobs:
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
- name: Codesign
run: |
echo "Codesigning jdk files..."
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name 'jspawnhelper' -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
echo "Codesigning jar contents..."
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
@@ -201,7 +204,7 @@ jobs:
--icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
env:
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
- name: Notarize .dmg
if: startsWith(github.ref, 'refs/tags/')
uses: cocoalibs/xcode-notarization-action@v1
@@ -212,7 +215,7 @@ jobs:
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
xcode-path: ${{ matrix.xcode-path }}
- name: Add possible alpha/beta tags to installer name
run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
+39
View File
@@ -0,0 +1,39 @@
name: Post Release Publish Tasks
on:
release:
types: [published]
jobs:
get-version:
runs-on: ubuntu-latest
steps:
- name: Download source tarball
run: |
curl -L -H "Accept: application/vnd.github+json" ${{ github.event.release.tarball_url }} --output cryptomator-${{ github.event.release.tag_name }}.tar.gz
- name: Sign source tarball with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Publish asc on GitHub Releases
uses: softprops/action-gh-release@v1
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
cryptomator-*.tar.gz.asc
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|build deb Package>."
SLACK_FOOTER: false
MSG_MINIMAL: true
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
fi
if [[ ${SEM_VER_STR} == `mvn help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then
echo "::set-output name=semVerStr::${SEM_VER_STR}"
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
else
echo "Version not set in POM"
exit 1
+61 -47
View File
@@ -19,40 +19,37 @@ defaults:
shell: bash
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build-msi:
name: Build .msi Installer
runs-on: windows-latest
needs: [get-version]
env:
LOOPBACK_ALIAS: 'cryptomator-vault'
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
java-package: 'jdk+fx'
cache: ${{ env.JAVA_CACHE }}
- id: versions
name: Apply version information
- name: Ensure major jfx version in pom equals in jdk
shell: pwsh
run: |
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
mvn versions:set -DnewVersion=${SEM_VER_STR}
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
mvn versions:set -DnewVersion=${SEM_VER_STR}
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
REVCOUNT=`git rev-list --count HEAD`
echo "::set-output name=semVerStr::${SEM_VER_STR}"
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
echo "::set-output name=revNum::${REVCOUNT}"
- name: Validate Version
uses: skymatic/semver-validation-action@v1
with:
version: ${{ steps.versions.outputs.semVerStr }}
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
exit 1
}
- name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
run: mvn -B clean package -Pdependency-check,win -DskipTests
- name: Patch target dir
@@ -66,7 +63,7 @@ jobs:
--verbose
--output runtime
--module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--strip-native-commands
--no-header-files
--no-man-pages
@@ -84,11 +81,13 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2022 Skymatic GmbH"
--app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
--copyright "(C) 2016 - 2023 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"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
@@ -96,8 +95,9 @@ jobs:
--java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
--java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\""
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"~/AppData/Roaming/Cryptomator/keychain.json\""
--resource-dir dist/win/resources
@@ -105,11 +105,24 @@ jobs:
- name: Patch Application Directory
run: |
cp dist/win/contrib/* appdir/Cryptomator
- name: Set LOOPBACK_ALIAS in patchWebDAV.bat
shell: pwsh
run: |
$patchScript = "appdir\Cryptomator\patchWebDAV.bat"
try {
(Get-Content $patchScript ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"${{ env.LOOPBACK_ALIAS}}`"" | Set-Content $patchScript
} catch {
Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
exit 1
}
- name: Fix permissions
run: attrib -r appdir/Cryptomator/Cryptomator.exe
shell: pwsh
- name: Extract integrations DLL for code signing
shell: pwsh
run: gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --extract integrations.dll }
- name: Codesign
uses: skymatic/code-sign-action@v1
uses: skymatic/code-sign-action@v2
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
@@ -118,6 +131,10 @@ jobs:
timestampUrl: 'http://timestamp.digicert.com'
folder: appdir/Cryptomator
recursive: true
- name: Repack signed DLL into jar
shell: pwsh
run: |
gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --update integrations.dll; Remove-Item integrations.dll}
- name: Generate license for MSI
run: >
mvn -B license:add-third-party
@@ -139,8 +156,8 @@ jobs:
--dest installer
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2022 Skymatic GmbH"
--app-version "${{ steps.versions.outputs.semVerNum }}"
--copyright "(C) 2016 - 2023 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--win-menu
--win-dir-chooser
--win-shortcut-prompt
@@ -152,7 +169,7 @@ jobs:
env:
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
- name: Codesign MSI
uses: skymatic/code-sign-action@v1
uses: skymatic/code-sign-action@v2
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
@@ -161,7 +178,7 @@ jobs:
timestampUrl: 'http://timestamp.digicert.com'
folder: installer
- name: Add possible alpha/beta tags to installer name
run: mv installer/Cryptomator-*.msi Cryptomator-${{ steps.versions.outputs.semVerStr }}-x64.msi
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.msi
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -186,23 +203,20 @@ jobs:
files: |
*.msi
*.asc
outputs:
semVerNum: ${{ steps.versions.outputs.semVerNum }}
semVerStr: ${{ steps.versions.outputs.semVerStr }}
revNum: ${{ steps.versions.outputs.revNum }}
call-winget-flow:
needs: [build-msi]
if: github.event.action == 'published'
needs: [get-version, build-msi]
if: github.event.action == 'published' && needs.get-version.outputs.versionType == 'stable'
uses: ./.github/workflows/winget.yml
with:
releaseTag: ${{ github.event.release.tag_name }}
secrets: inherit
build-exe:
name: Build .exe installer
runs-on: windows-latest
needs: [build-msi]
needs: [get-version, build-msi]
steps:
- uses: actions/checkout@v3
- name: Download .msi
@@ -238,9 +252,9 @@ jobs:
"${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs
-ext WixBalExtension
-out dist/win/bundle/
-dBundleVersion="${{ needs.build-msi.outputs.semVerNum }}.${{ needs.build-msi.outputs.revNum }}"
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
-dBundleVendor="Skymatic GmbH"
-dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH"
-dBundleCopyright="(C) 2016 - 2023 Skymatic GmbH"
-dAboutUrl="https://cryptomator.org"
-dHelpUrl="https://cryptomator.org/contact"
-dUpdateUrl="https://cryptomator.org/downloads/"
@@ -255,7 +269,7 @@ jobs:
-ib installer/unsigned/Cryptomator-Installer.exe
-o tmp/engine.exe
- name: Codesign burn engine
uses: skymatic/code-sign-action@v1
uses: skymatic/code-sign-action@v2
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
@@ -269,7 +283,7 @@ jobs:
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
-o installer/Cryptomator-Installer.exe
- name: Codesign EXE
uses: skymatic/code-sign-action@v1
uses: skymatic/code-sign-action@v2
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
@@ -278,7 +292,7 @@ jobs:
timestampUrl: 'http://timestamp.digicert.com'
folder: installer
- name: Add possible alpha/beta tags to installer name
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.exe
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -311,12 +325,12 @@ jobs:
needs: [build-msi, build-exe]
steps:
- name: Download .msi
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: msi
path: msi
- name: Download .exe
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: exe
path: exe
@@ -326,7 +340,7 @@ jobs:
cp msi/*.msi files
cp exe/*.exe files
- name: Upload to Kaspersky
uses: SamKirkland/FTP-Deploy-Action@4.3.0
uses: SamKirkland/FTP-Deploy-Action@4.3.3
with:
protocol: ftps
server: allowlist.kaspersky-labs.com
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
name: Publish on winget repo
runs-on: windows-latest
steps:
- name: Get download url for msi artifacts
- name: Get download url for release assets
id: get-release-assets
uses: actions/github-script@v6
with:
+3 -6
View File
@@ -33,6 +33,7 @@ Cryptomator is provided free of charge as an open-source project despite the hig
<tr>
<td><a href="https://mowcapital.com/"><img src="https://cryptomator.org/img/sponsors/mowcapital.svg" alt="Mow Capital" height="40"></a></td>
<td><a href="https://www.easeus.com/"><img src="https://cryptomator.org/img/sponsors/easeus.png" alt="EaseUS" height="40"></a></td>
<td><a href="https://www.hassmann-it-forensik.de/"><img src="https://cryptomator.org/img/sponsors/hassmannitforensik.png" alt="Hassmann IT-Forensik" height="40"></a></td>
</tr>
</tbody>
</table>
@@ -61,7 +62,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
- File names get encrypted
- Folder structure gets obfuscated
- Use as many vaults in your Dropbox as you want, each having individual passwords
- Three thousand commits for the security of your data!! :tada:
- Four thousand commits for the security of your data!! :tada:
### Privacy
@@ -85,7 +86,7 @@ For more information on the security details visit [cryptomator.org](https://doc
### Dependencies
* JDK 17 (e.g. temurin)
* JDK 19 (e.g. temurin)
* Maven 3
### Run Maven
@@ -99,10 +100,6 @@ mvn clean install
This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages.
### Start Cryptomator
If you unzip the buildkit for your OS, you will find a launcher script with some basic settings. You might want to adjust these to your needs. To start Cryptomator, simply execute the launcher script from a terminal, e.g. `launcher-linux.sh`, if you're on a Linux system.
## License
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
+21 -14
View File
@@ -11,15 +11,20 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
SEMVER_STR=${VERSION}
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
# compile
mvn -B -f ../../../pom.xml clean package -DskipTests -Plinux
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
cp ../../../LICENSE.txt ../../../target
cp ../launcher.sh ../../../target
cp ../../../target/cryptomator-*.jar ../../../target/mods
# add runtime
${JAVA_HOME}/bin/jlink \
--verbose \
--output runtime \
--module-path "${JAVA_HOME}/jmods" \
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
--strip-native-commands \
--no-header-files \
--no-man-pages \
@@ -27,7 +32,7 @@ ${JAVA_HOME}/bin/jlink \
--compress=1
# create app dir
envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
envsubst '${SEMVER_STR} ${REVISION_NUM}' < ../launcher-gtk2.properties > launcher-gtk2.properties
${JAVA_HOME}/bin/jpackage \
--verbose \
--type app-image \
@@ -35,10 +40,12 @@ ${JAVA_HOME}/bin/jpackage \
--input ../../../target/libs \
--module-path ../../../target/mods \
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator \
--dest . \
--dest appdir \
--name Cryptomator \
--vendor "Skymatic GmbH" \
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
--java-options "--enable-preview" \
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" \
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--app-version "${VERSION}.${REVISION_NO}" \
@@ -46,6 +53,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.showTrayIcon=false" \
@@ -54,9 +62,8 @@ ${JAVA_HOME}/bin/jpackage \
--resource-dir ../resources
# transform AppDir
mv Cryptomator Cryptomator.AppDir
mv appdir/Cryptomator Cryptomator.AppDir
cp -r resources/AppDir/* Cryptomator.AppDir/
chmod +x Cryptomator.AppDir/lib/runtime/bin/java
envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png
cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png
@@ -70,12 +77,6 @@ ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryp
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
# extract jffi
JFFI_NATIVE_JAR=`ls Cryptomator.AppDir/lib/app | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'`
${JAVA_HOME}/bin/jar -xf Cryptomator.AppDir/lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/
mv jni/x86_64-Linux/* Cryptomator.AppDir/lib/app/libjffi.so
rm -r jni/x86_64-Linux
# load AppImageTool
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /tmp/appimagetool.AppImage
chmod +x /tmp/appimagetool.AppImage
@@ -83,5 +84,11 @@ chmod +x /tmp/appimagetool.AppImage
# create AppImage
/tmp/appimagetool.AppImage \
Cryptomator.AppDir \
cryptomator-SNAPSHOT-x86_64.AppImage \
cryptomator-${SEMVER_STR}-x86_64.AppImage \
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
echo ""
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-x86_64.AppImage"
echo ""
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
echo ""
@@ -15,9 +15,6 @@ elif command -v pacman &> /dev/null; then # don't forget arch
GTK3_PRESENT=`pacman -Qi gtk3 &> /dev/null; echo $?`
fi
# workaround for https://github.com/cryptomator/cryptomator-linux/issues/27
export LD_PRELOAD=lib/app/libjffi.so
if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then
bin/Cryptomator-gtk2 $@
else
@@ -66,6 +66,8 @@
</content_rating>
<releases>
<release date="2022-12-14" version="1.6.17"/>
<release date="2022-12-06" version="1.6.16"/>
<release date="2022-10-06" version="1.6.15"/>
<release date="2022-08-31" version="1.6.14"/>
<release date="2022-07-27" version="1.6.12"/>
+2 -2
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils
Priority: optional
Build-Depends: debhelper (>=10), coffeelibs-jdk-19
Build-Depends: debhelper (>=10), coffeelibs-jdk-19, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
Standards-Version: 4.5.0
Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git
@@ -12,7 +12,7 @@ Package: cryptomator
Architecture: any
Section: utils
Priority: optional
Depends: ${shlibs:Depends}, ${misc:Depends}, libfuse2, xdg-utils, libjffi-jni
Depends: ${shlibs:Depends}, ${misc:Depends}, libfuse3-3
Recommends: gvfs-backends, gvfs-fuse, gnome-keyring
XB-AppName: Cryptomator
XB-Category: Utility;Security;FileTools;
+2 -2
View File
@@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator <info@cryptomator.org>
Source: https://cryptomator.org
Files: *
Copyright: 2016-2022 Skymatic GmbH
Copyright: 2016-2023 Skymatic GmbH
License: GPL-3+
Files: debian/org.cryptomator.Cryptomator.appdata.xml
Copyright: 2016-2022 Skymatic GmbH
Copyright: 2016-2023 Skymatic GmbH
License: FSFAP
License: GPL-3+
-1
View File
@@ -1,5 +1,4 @@
cryptomator usr/lib
debian/cryptomator.sh usr/lib/cryptomator/bin
common/org.cryptomator.Cryptomator.desktop usr/share/applications
common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps
common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps
+1 -1
View File
@@ -1 +1 @@
usr/lib/cryptomator/bin/cryptomator.sh usr/bin/cryptomator
usr/lib/cryptomator/bin/cryptomator usr/bin/cryptomator
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh
# fix for https://github.com/cryptomator/cryptomator/issues/1370
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/jni/libjffi-1.2.so
/usr/lib/cryptomator/bin/cryptomator $@
+11 -9
View File
@@ -5,6 +5,12 @@
#export DH_VERBOSE=1
JAVA_HOME = /usr/lib/jvm/java-19-coffeelibs
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
ifeq ($(DEB_BUILD_ARCH),amd64)
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
else ifeq ($(DEB_BUILD_ARCH),arm64)
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
endif
%:
dh $@
@@ -20,7 +26,8 @@ override_dh_auto_build:
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
$(JAVA_HOME)/bin/jlink \
--output runtime \
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
--module-path "${JMODS_PATH}" \
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
--strip-native-commands \
--no-header-files \
--no-man-pages \
@@ -35,7 +42,9 @@ override_dh_auto_build:
--dest . \
--name cryptomator \
--vendor "Skymatic GmbH" \
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
--java-options "--enable-preview" \
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" \
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
@@ -51,10 +60,3 @@ override_dh_auto_build:
--app-version "${VERSION_NUM}.${REVISION_NUM}" \
--resource-dir resources \
--verbose
override_dh_fixperms:
dh_fixperms
chmod +x debian/cryptomator/usr/lib/cryptomator/bin/cryptomator.sh
# override_dh_strip:
# no-op
+2
View File
@@ -1,5 +1,7 @@
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\" \
+2
View File
@@ -10,4 +10,6 @@ java \
-Djdk.gtk.version=2 \
-Xss2m \
-Xmx512m \
--enable-preview \
--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64 \
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
+4 -1
View File
@@ -21,7 +21,7 @@ rm -rf runtime dmg *.app *.dmg
# set variables
APP_NAME="Cryptomator"
VENDOR="Skymatic GmbH"
COPYRIGHT_YEARS="2016 - 2022"
COPYRIGHT_YEARS="2016 - 2023"
PACKAGE_IDENTIFIER="org.cryptomator"
MAIN_JAR_GLOB="cryptomator-*.jar"
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
@@ -65,6 +65,8 @@ ${JAVA_HOME}/bin/jpackage \
--vendor "${VENDOR}" \
--copyright "(C) ${COPYRIGHT_YEARS} ${VENDOR}" \
--app-version "${VERSION_NO}" \
--java-options "--enable-preview" \
--java-options "--enable-native-access=org.cryptomator.jfuse.mac" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
@@ -77,6 +79,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/${APP_NAME}/ipc.socket\"" \
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/${APP_NAME}/key.p12\"" \
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/${APP_NAME}\"" \
--java-options "-Dcryptomator.showTrayIcon=true" \
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
--mac-package-identifier ${PACKAGE_IDENTIFIER} \
Binary file not shown.
+1 -1
View File
@@ -17,7 +17,7 @@
\f1\b0 \
\
\f0\b \'a9 2016 \'96 2022 Skymatic GmbH
\f0\b \'a9 2016 \'96 2023 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.\
+2
View File
@@ -9,4 +9,6 @@ java \
-Dcryptomator.mountPointsDir="/Volumes" \
-Xss20m \
-Xmx512m \
--enable-preview \
--enable-native-access=org.cryptomator.jfuse.mac \
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
+3 -1
View File
@@ -9,8 +9,9 @@ SET ABOUT_URL="https://cryptomator.org"
SET UPDATE_URL="https://cryptomator.org/downloads/"
SET HELP_URL="https://cryptomator.org/contact/"
SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
SET LOOPBACK_ALIAS="cryptomator-vault"
powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1^
powershell -NoLogo -ExecutionPolicy Unrestricted -Command .\build.ps1^
-AppName %APPNAME%^
-MainJarGlob "%MAIN_JAR_GLOB%"^
-ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^
@@ -20,4 +21,5 @@ powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1^
-AboutUrl "%ABOUT_URL%"^
-HelpUrl "%HELP_URL%"^
-UpdateUrl "%UPDATE_URL%"^
-LoopbackAlias "%LOOPBACK_ALIAS%"^
-Clean 1
+12
View File
@@ -8,6 +8,7 @@ Param(
[Parameter(Mandatory, HelpMessage="Please provide a help url")][string] $HelpUrl,
[Parameter(Mandatory, HelpMessage="Please provide an update url")][string] $UpdateUrl,
[Parameter(Mandatory, HelpMessage="Please provide an about url")][string] $AboutUrl,
[Parameter(Mandatory, HelpMessage="Please provide an alias for localhost")][string] $LoopbackAlias,
[bool] $clean
)
@@ -74,6 +75,8 @@ if ($clean -and (Test-Path -Path $appPath)) {
--name $AppName `
--vendor $Vendor `
--copyright $copyright `
--java-options "--enable-preview" `
--java-options "--enable-native-access=org.cryptomator.jfuse.win" `
--java-options "-Xss5m" `
--java-options "-Xmx256m" `
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
@@ -85,6 +88,7 @@ if ($clean -and (Test-Path -Path $appPath)) {
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" `
--java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" `
--java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" `
--java-options "-Dcryptomator.loopbackAlias=`"$LoopbackAlias`"" `
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" `
--java-options "-Dcryptomator.integrationsWin.keychainPaths=`"~/AppData/Roaming/$AppName/keychain.json`"" `
--java-options "-Dcryptomator.showTrayIcon=true" `
@@ -105,6 +109,14 @@ if ($clean -and (Test-Path -Path $appPath)) {
# patch app dir
Copy-Item "contrib\*" -Destination "$AppName"
attrib -r "$AppName\$AppName.exe"
# patch batch script to set hostfile
$webDAVPatcher = "$AppName\patchWebDAV.bat"
try {
(Get-Content $webDAVPatcher ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"$LoopbackAlias`"" | Set-Content $webDAVPatcher
} catch {
Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
exit 1
}
# create .msi
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
+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 2022 Skymatic GmbH \b0\par
\b\'a9 2016 \'96 2023 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
@@ -1,6 +1,6 @@
@echo off
:: Default values for Cryptomator builds
SET LOOPBACK_ALIAS="cryptomator-vault"
::REPLACE ME
cd %~dp0
powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1^
+2
View File
@@ -9,4 +9,6 @@ java ^
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
-Xss20m ^
-Xmx512m ^
--enable-preview `
--enable-native-access=org.cryptomator.jfuse.win `
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
+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 2022 Skymatic GmbH \b0\par
\b\'a9 2016 \'96 2023 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
+16 -11
View File
@@ -23,6 +23,11 @@
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
<?endif?>
<!-- Cryptomator defaults -->
<?define IconFileEncryptedData= "Cryptomator-Vault.ico" ?>
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
<?define CloseApplicationTarget= "cryptomator.exe" ?>
<?include $(var.JpConfigDir)/overrides.wxi ?>
<Product
@@ -65,16 +70,16 @@
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
<?endif?>
<!-- Looking for legacy Cryptomator versions-->
<Property Id="OLDEXEINSTALLER">
<RegistrySearch Id="InnoSetupInstallation" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Cryptomator_is1" Type="raw" Name="DisplayName" />
</Property>
<!-- Block installation if innosetup entry of Cryptomator is found -->
<!-- TODO: localize -->
<Condition Message="A lower version of [ProductName] is already installed. Uninstall it first and then start the setup again. Setup will now exit.">
<![CDATA[Installed OR NOT OLDEXEINSTALLER]]>
</Condition>
<?ifndef SkipCryptomatorLegacyCheck ?>
<!-- Block installation if innosetup entry of Cryptomator is found -->
<Property Id="OLDEXEINSTALLER">
<RegistrySearch Id="InnoSetupInstallation" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Cryptomator_is1" Type="raw" Name="DisplayName" />
</Property>
<!-- TODO: localize -->
<Condition Message="A lower version of [ProductName] is already installed. Uninstall it first and then start the setup again. Setup will now exit.">
<![CDATA[Installed OR NOT OLDEXEINSTALLER]]>
</Condition>
<?endif?>
<!-- Cryptomator uses UNIX Sockets, which are supported starting with Windows 10 v1803-->
<Property Id="WINDOWSBUILDNUMBER" Secure="yes">
<RegistrySearch Id="BuildNumberSearch" Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion" Name="CurrentBuildNumber" Type="raw" />
@@ -86,7 +91,7 @@
<!-- Non-Opening ProgID -->
<DirectoryRef Id="INSTALLDIR">
<Component Win64="yes" Id="nonStartingProgID" >
<File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\$(var.IconFileC9rC9s)" Name="$(var.IconFileC9rC9s)"></File>
<File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\$(var.IconFileEncryptedData)" Name="$(var.IconFileEncryptedData)"></File>
<ProgId Id="$(var.JpAppName).Encrypted.1" Description="$(var.JpAppName) Encrypted Data" Icon="IconFileForEncryptedData" IconIndex="0">
<Extension Id="c9r" Advertise="no" ContentType="$(var.ProgIdContentType)">
<MIME ContentType="$(var.ProgIdContentType)" Default="yes"></MIME>
+17 -6
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Stub by design -->
<!--
<!-- jPackage Section
overrides.wxi is a placeholder to set/alter WiX variables referenced from default
main.wxs file.
@@ -30,10 +30,21 @@ Should be defined to enable upgrades and undefined to disable upgrades.
Default value is `yes`.
-->
<!-- Non-opening ProgID settings-->
<?define IconFileC9rC9s= "Cryptomator-Vault.ico" ?>
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
<!-- Cryptomator Section
<!-- Close Application util -->
<?define CloseApplicationTarget= "cryptomator.exe" ?>
Non-opening ProgID settings:
- IconFileEncryptedData
Full file name of icon file used for encrypted data files. Default is "Cryptomator-Vault.ico"
- ProgIdContentType
Media Type of the encrypted data files. Default is "application/vnd.cryptomator.encrypted"
Close Application settings:
- CloseApplicationTarget
Full name of executable to be checkd in the close application util. Default is "cryptomator.exe"
Legacy Installation settings:
- SkipCryptomatorLegacyCheck
Should be defined to disable checking for the inno setup installation of Cryptomator and undefined, to enable it.
-->
<Include/>
+16 -14
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.7.0</version>
<version>1.7.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -24,19 +24,19 @@
<project.jdk.version>19</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now -->
<!-- Check progress on https://github.com/swiesend/secret-service/issues/31 to remove hypfvieh, swiesend and jnr -->
<nonModularGroupIds>com.github.jnr,org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
<!-- Once hypfvieh, swiesend, purejava and integrations-linux have module-info, remove them-->
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
<!-- cryptomator dependencies -->
<cryptomator.cryptolib.version>2.1.0-rc1</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>2.4.5</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.2.0-beta2</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.1.2</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.1.2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>2.0.0-beta1</cryptomator.fuse.version>
<cryptomator.dokany.version>2.0.0-beta1</cryptomator.dokany.version>
<cryptomator.webdav.version>2.0.0-beta1</cryptomator.webdav.version>
<cryptomator.cryptolib.version>2.1.1</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>2.6.1</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.2.0-beta4</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.0-beta1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.0-beta2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.2.0-beta1</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>2.0.0-beta4</cryptomator.fuse.version>
<cryptomator.dokany.version>2.0.0-beta2</cryptomator.dokany.version>
<cryptomator.webdav.version>2.0.0-beta4</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<commons-lang3.version>3.12.0</commons-lang3.version>
@@ -59,13 +59,14 @@
<!-- build-time dependencies -->
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
<dependency-check.version>7.2.1</dependency-check.version>
<dependency-check.version>7.4.4</dependency-check.version>
<jacoco.version>0.8.8</jacoco.version>
</properties>
<dependencies>
<!-- Cryptomator Libs -->
<dependency>
<!-- needed due to https://github.com/cryptomator/cryptolib/issues/34-->
<groupId>org.cryptomator</groupId>
<artifactId>cryptolib</artifactId>
<version>${cryptomator.cryptolib.version}</version>
@@ -373,6 +374,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<!-- sort jars into two buckets (classpath and modulepath). exclude openjfx, which gets jlinked separately -->
<execution>
<id>copy-mods</id>
<phase>prepare-package</phase>
@@ -382,7 +384,7 @@
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/mods</outputDirectory>
<excludeGroupIds>${nonModularGroupIds}</excludeGroupIds>
<excludeGroupIds>org.openjfx,${nonModularGroupIds}</excludeGroupIds>
</configuration>
</execution>
<execution>
@@ -26,6 +26,7 @@ public class Environment {
private static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
private static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
private static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
private static final String LOOPBACK_ALIAS_PROP_NAME = "cryptomator.loopbackAlias";
private static final String MOUNTPOINT_DIR_PROP_NAME = "cryptomator.mountPointsDir";
private static final String MIN_PW_LENGTH_PROP_NAME = "cryptomator.minPwLength";
private static final String APP_VERSION_PROP_NAME = "cryptomator.appVersion";
@@ -45,6 +46,7 @@ public class Environment {
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
@@ -90,6 +92,10 @@ public class Environment {
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir);
}
public Optional<String> getLoopbackAlias() {
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
}
public Optional<Path> getPluginDir() {
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir);
}
@@ -112,22 +118,13 @@ public class Environment {
}
public int getMinPwLength() {
return getInt(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
}
public boolean showTrayIcon() {
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
}
private int getInt(String propertyName, int defaultValue) {
String value = System.getProperty(propertyName);
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) { // includes "null" values
return defaultValue;
}
}
private Optional<Path> getPath(String propertyName) {
String value = System.getProperty(propertyName);
return Optional.ofNullable(value).map(Paths::get);
@@ -0,0 +1,18 @@
package org.cryptomator.common;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import java.util.function.Function;
public class ObservableUtil {
public static <T, U> ObservableValue<U> mapWithDefault(ObservableValue<T> observable, Function<? super T, ? extends U> mapper, U defaultValue) {
return Bindings.createObjectBinding(() -> {
if (observable.getValue() == null) {
return defaultValue;
} else {
return mapper.apply(observable.getValue());
}
}, observable);
}
}
@@ -0,0 +1,6 @@
package org.cryptomator.common.mount;
import org.cryptomator.integrations.mount.MountService;
public record ActualMountService(MountService service, boolean isDesired) {
}
@@ -0,0 +1,9 @@
package org.cryptomator.common.mount;
public class IllegalMountPointException extends IllegalArgumentException {
public IllegalMountPointException(String msg) {
super(msg);
}
}
@@ -6,6 +6,7 @@ import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.mount.MountService;
import javax.inject.Singleton;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import java.util.List;
@@ -18,13 +19,33 @@ public class MountModule {
return MountService.get().toList();
}
//currently not used, because macFUSE and FUSE-T cannot be used in the same JVM
/*
@Provides
@Singleton
static ObservableValue<MountService> provideMountService(Settings settings, List<MountService> serviceImpls) {
return settings.mountService().map(desiredServiceImpl -> {
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
return serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny().orElse(fallbackProvider);
});
static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls) {
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
return ObservableUtil.mapWithDefault(settings.mountService(), //
desiredServiceImpl -> { //
var desiredService = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); //
return new ActualMountService(desiredService.orElse(fallbackProvider), desiredService.isPresent()); //
}, //
new ActualMountService(fallbackProvider, true));
}
*/
@Provides
@Singleton
static ActualMountService provideActualMountService(Settings settings, List<MountService> serviceImpls) {
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
var desiredService = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(settings.mountService().getValue())).findFirst(); //
return new ActualMountService(desiredService.orElse(fallbackProvider), desiredService.isPresent()); //
}
@Provides
@Singleton
static ObservableValue<ActualMountService> provideMountService(ActualMountService service) {
return new SimpleObjectProperty<>(service);
}
}
@@ -0,0 +1,8 @@
package org.cryptomator.common.mount;
public class MountPointNotExistsException extends IllegalMountPointException {
public MountPointNotExistsException(String msg) {
super(msg);
}
}
@@ -0,0 +1,8 @@
package org.cryptomator.common.mount;
public class MountPointNotSupportedException extends IllegalMountPointException {
public MountPointNotSupportedException(String msg) {
super(msg);
}
}
@@ -0,0 +1,8 @@
package org.cryptomator.common.mount;
public class MountPointPreparationException extends RuntimeException {
public MountPointPreparationException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,110 @@
package org.cryptomator.common.mount;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
public final class MountWithinParentUtil {
private static final Logger LOG = LoggerFactory.getLogger(Mounter.class);
private static final String HIDEAWAY_PREFIX = ".~$";
private static final String HIDEAWAY_SUFFIX = ".tmp";
private static final String WIN_HIDDEN_ATTR = "dos:hidden";
private MountWithinParentUtil() {}
static void prepareParentNoMountPoint(Path mountPoint) throws MountPointPreparationException {
Path hideaway = getHideaway(mountPoint);
var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS);
var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
//TODO: possible improvement by just deleting an _empty_ hideaway
if (mpExists && hideExists) { //both resources exist (whatever type)
throw new MountPointPreparationException(new FileAlreadyExistsException(hideaway.toString()));
} else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
throw new MountPointPreparationException(new NoSuchFileException(mountPoint.toString()));
} else if (!mpExists) { //only hideaway exists
checkIsDirectory(hideaway);
LOG.info("Mountpoint {} seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
try {
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
}
} catch (IOException e) {
throw new MountPointPreparationException(e);
}
} else { //only mountpoint exists
try {
checkIsDirectory(mountPoint);
checkIsEmpty(mountPoint);
Files.move(mountPoint, hideaway);
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
}
} catch (IOException e) {
throw new MountPointPreparationException(e);
}
}
}
static void cleanup(Path mountPoint) {
Path hideaway = getHideaway(mountPoint);
try {
waitForMountpointRestoration(mountPoint);
Files.move(hideaway, mountPoint);
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(mountPoint, WIN_HIDDEN_ATTR, false);
}
} catch (IOException e) {
LOG.error("Unable to restore hidden directory to mountpoint {}.", mountPoint, e);
}
}
//on Windows removing the mountpoint takes some time, so we poll for at most 3 seconds
private static void waitForMountpointRestoration(Path mountPoint) throws FileAlreadyExistsException {
int attempts = 0;
while (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) {
attempts++;
if (attempts >= 5) {
throw new FileAlreadyExistsException("Timeout waiting for mountpoint cleanup for " + mountPoint + " .");
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new FileAlreadyExistsException("Interrupted before mountpoint " + mountPoint + " was cleared");
}
}
}
private static void checkIsDirectory(Path toCheck) throws MountPointPreparationException {
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
throw new MountPointPreparationException(new NotDirectoryException(toCheck.toString()));
}
}
private static void checkIsEmpty(Path toCheck) throws MountPointPreparationException, IOException {
try (var dirStream = Files.list(toCheck)) {
if (dirStream.findFirst().isPresent()) {
throw new MountPointPreparationException(new DirectoryNotEmptyException(toCheck.toString()));
}
}
}
//visible for testing
static Path getHideaway(Path mountPoint) {
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
}
}
@@ -0,0 +1,138 @@
package org.cryptomator.common.mount;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.integrations.mount.Mount;
import org.cryptomator.integrations.mount.MountBuilder;
import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.MountService;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_WITHIN_EXISTING_PARENT;
import static org.cryptomator.integrations.mount.MountCapability.UNMOUNT_FORCED;
@Singleton
public class Mounter {
private final Settings settings;
private final Environment env;
private final WindowsDriveLetters driveLetters;
private final ObservableValue<ActualMountService> mountServiceObservable;
@Inject
public Mounter(Settings settings, Environment env, WindowsDriveLetters driveLetters, ObservableValue<ActualMountService> mountServiceObservable) {
this.settings = settings;
this.env = env;
this.driveLetters = driveLetters;
this.mountServiceObservable = mountServiceObservable;
}
private class SettledMounter {
private MountService service;
private MountBuilder builder;
private VaultSettings vaultSettings;
public SettledMounter(MountService service, MountBuilder builder, VaultSettings vaultSettings) {
this.service = service;
this.builder = builder;
this.vaultSettings = vaultSettings;
}
Runnable prepare() throws IOException {
for (var capability : service.capabilities()) {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
case LOOPBACK_PORT ->
builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
case MOUNT_FLAGS -> {
var mountFlags = vaultSettings.mountFlags().get();
if( mountFlags == null || mountFlags.isBlank()) {
builder.setMountFlags(service.getDefaultMountFlags());
} else {
builder.setMountFlags(mountFlags);
}
}
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
}
}
return prepareMountPoint();
}
private Runnable prepareMountPoint() throws IOException {
Runnable cleanup = () -> {};
var userChosenMountPoint = vaultSettings.getMountPoint();
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
var canMountToDriveLetter = service.hasCapability(MOUNT_AS_DRIVE_LETTER);
var canMountToParent = service.hasCapability(MOUNT_WITHIN_EXISTING_PARENT);
var canMountToDir = service.hasCapability(MOUNT_TO_EXISTING_DIR);
if (userChosenMountPoint == null) {
if (service.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
// no need to set a mount point
} else if (canMountToDriveLetter) {
builder.setMountpoint(driveLetters.getFirstDesiredAvailable().orElseThrow()); //TODO: catch exception and translate
} else if (canMountToParent) {
Files.createDirectories(defaultMountPointBase);
builder.setMountpoint(defaultMountPointBase);
} else if (canMountToDir) {
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
Files.createDirectories(mountPoint);
builder.setMountpoint(mountPoint);
}
} else {
if (canMountToParent && !canMountToDir) {
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
cleanup = () -> {
MountWithinParentUtil.cleanup(userChosenMountPoint);
};
}
try {
builder.setMountpoint(userChosenMountPoint);
} catch (IllegalArgumentException e) {
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
var configNotSupported = (!canMountToDriveLetter && mpIsDriveLetter) || (!canMountToDir && !mpIsDriveLetter) || (!canMountToParent && !mpIsDriveLetter);
if (configNotSupported) {
throw new MountPointNotSupportedException(e.getMessage());
} else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) {
//mountpoint must exist
throw new MountPointNotExistsException(e.getMessage());
} else {
//TODO: add specific exception for !canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)
throw new IllegalMountPointException(e.getMessage());
}
}
}
return cleanup;
}
}
public MountHandle mount(VaultSettings vaultSettings, Path cryptoFsRoot) throws IOException, MountFailedException {
var mountService = this.mountServiceObservable.getValue().service();
var builder = mountService.forFileSystem(cryptoFsRoot);
var internal = new SettledMounter(mountService, builder, vaultSettings);
var cleanup = internal.prepare();
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);
}
public record MountHandle(Mount mountObj, boolean supportsUnmountForced, Runnable specialCleanup) {
}
}
@@ -76,6 +76,11 @@ public class DeviceKey {
private P384KeyPair createAndStoreNewKeyPair(char[] passphrase, Path p12File) throws IOException {
var keyPair = P384KeyPair.generate();
var tmpFile = p12File.resolveSibling(p12File.getFileName().toString() + ".tmp");
if(Files.exists(tmpFile)) {
LOG.debug("Leftover from devicekey creation detected. Deleting {}", tmpFile);
Files.delete(tmpFile);
}
LOG.debug("Store new device key to {}", p12File);
keyPair.store(p12File, passphrase);
return keyPair;
@@ -9,6 +9,7 @@ import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -73,7 +74,9 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
@Override
public Settings read(JsonReader in) throws IOException {
Settings settings = new Settings(env);
//1.6.x legacy
String volumeImpl = null;
//legacy end
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
@@ -105,18 +108,49 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
settings.mountService().set(in.nextString());
}
}
//1.6.x legacy
case "preferredVolumeImpl" -> volumeImpl = in.nextString();
//legacy end
default -> {
LOG.warn("Unsupported vault setting found in JSON: {}", name);
in.skipValue();
}
}
}
in.endObject();
//1.6.x legacy
if (volumeImpl != null) {
settings.mountService().set(convertLegacyVolumeImplToMountService(volumeImpl));
}
//legacy end
return settings;
}
private String convertLegacyVolumeImplToMountService(String volumeImpl) {
if (volumeImpl.equals("Dokany")) {
return "org.cryptomator.frontend.dokany.mount.DokanyMountProvider";
} else if (volumeImpl.equals("FUSE")) {
if(SystemUtils.IS_OS_WINDOWS) {
return "org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider";
} else if (SystemUtils.IS_OS_MAC) {
return "org.cryptomator.frontend.fuse.mount.MacFuseMountProvider";
} else {
return "org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider";
}
} else {
if(SystemUtils.IS_OS_WINDOWS) {
return "org.cryptomator.frontend.webdav.mount.WindowsMounter";
} else if (SystemUtils.IS_OS_MAC) {
return "org.cryptomator.frontend.webdav.mount.MacAppleScriptMounter";
} else {
return "org.cryptomator.frontend.webdav.mount.LinuxGioMounter";
}
}
}
private UiTheme parseUiTheme(String uiThemeName) {
try {
return UiTheme.valueOf(uiThemeName.toUpperCase());
@@ -45,7 +45,7 @@ public class VaultSettings {
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS); //TODO: remove empty default mount flags and let this property be null if not used
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
@@ -38,8 +38,6 @@ class VaultSettingsJsonAdapter {
out.endObject();
}
//TODO: usesCustomMountPath, customMountPath and winDriveLetter removed
// -> migration required
public VaultSettings read(JsonReader in) throws IOException {
String id = null;
String path = null;
@@ -55,6 +53,12 @@ class VaultSettingsJsonAdapter {
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
//legacy from 1.6.x
boolean useCustomMountPath = false;
String customMountPath = "";
String winDriveLetter = "";
//legacy end
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
@@ -78,6 +82,11 @@ class VaultSettingsJsonAdapter {
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
//legacy from 1.6.x
case "winDriveLetter" -> winDriveLetter = in.nextString();
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
//legacy end
default -> {
LOG.warn("Unsupported vault setting found in JSON: {}", name);
in.skipValue();
@@ -102,6 +111,13 @@ class VaultSettingsJsonAdapter {
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
vaultSettings.mountPoint().set(mountPoint);
//legacy from 1.6.x
if(useCustomMountPath && !customMountPath.isBlank()) {
vaultSettings.mountPoint().set(parseMountPoint(customMountPath));
} else if(!winDriveLetter.isBlank() ) {
vaultSettings.mountPoint().set(parseMountPoint(winDriveLetter+":\\"));
}
//legacy end
return vaultSettings;
}
@@ -1,18 +0,0 @@
package org.cryptomator.common.settings;
public enum VolumeImpl {
WEBDAV("WebDAV"),
FUSE("FUSE"),
DOKANY("Dokany");
private String displayName;
VolumeImpl(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
@@ -8,12 +8,10 @@
*******************************************************************************/
package org.cryptomator.common.vaults;
import com.google.common.base.Strings;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
import org.cryptomator.common.Environment;
import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.mount.WindowsDriveLetters;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
@@ -23,11 +21,7 @@ import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.integrations.mount.Mount;
import org.cryptomator.integrations.mount.MountBuilder;
import org.cryptomator.integrations.mount.MountCapability;
import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.MountService;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException;
import org.slf4j.Logger;
@@ -44,9 +38,7 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
@@ -54,11 +46,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_WITHIN_EXISTING_PARENT;
@PerVault
public class Vault {
@@ -66,14 +53,10 @@ public class Vault {
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
private final Environment env;
private final Settings settings;
private final VaultSettings vaultSettings;
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
private final VaultState state;
private final ObjectProperty<Exception> lastKnownException;
private final ObservableValue<MountService> mountService;
private final ObservableValue<String> defaultMountFlags;
private final VaultConfigCache configCache;
private final VaultStats stats;
private final StringBinding displayablePath;
@@ -84,22 +67,18 @@ public class Vault {
private final BooleanBinding needsMigration;
private final BooleanBinding unknownError;
private final ObjectBinding<Mountpoint> mountPoint;
private final WindowsDriveLetters windowsDriveLetters;
private final Mounter mounter;
private final BooleanProperty showingStats;
private AtomicReference<MountHandle> mountHandle = new AtomicReference<>(null);
private AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@Inject
Vault(Environment env, Settings settings, VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, ObservableValue<MountService> mountService, VaultStats stats, WindowsDriveLetters windowsDriveLetters) {
this.env = env;
this.settings = settings;
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats, WindowsDriveLetters windowsDriveLetters, Mounter mounter) {
this.vaultSettings = vaultSettings;
this.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem;
this.state = state;
this.lastKnownException = lastKnownException;
this.mountService = mountService;
this.defaultMountFlags = mountService.map(MountService::getDefaultMountFlags);
this.stats = stats;
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
@@ -109,7 +88,7 @@ public class Vault {
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
this.windowsDriveLetters = windowsDriveLetters;
this.mounter = mounter;
this.showingStats = new SimpleBooleanProperty(false);
}
@@ -159,45 +138,6 @@ public class Vault {
}
}
private MountBuilder prepareMount(Path cryptoRoot) throws IOException {
var mountProvider = mountService.getValue();
var builder = mountProvider.forFileSystem(cryptoRoot);
for (var capability : mountProvider.capabilities()) {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("crypto");
case LOOPBACK_PORT -> builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings?
case LOOPBACK_HOST_NAME -> builder.setLoopbackHostName("cryptomator-vault"); //TODO: Read from system property
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
case MOUNT_FLAGS -> builder.setMountFlags(defaultMountFlags.getValue()); // TODO use custom mount flags (pre-populated with default mount flags)
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
}
}
var userChosenMountPoint = vaultSettings.getMountPoint();
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
if (userChosenMountPoint == null) {
if (mountProvider.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
// no need to set a mount point
} else if (mountProvider.hasCapability(MOUNT_AS_DRIVE_LETTER)) {
builder.setMountpoint(windowsDriveLetters.getFirstDesiredAvailable().orElseThrow());
} else if (mountProvider.hasCapability(MOUNT_WITHIN_EXISTING_PARENT)) {
Files.createDirectories(defaultMountPointBase);
builder.setMountpoint(defaultMountPointBase);
} else if (mountProvider.hasCapability(MOUNT_TO_EXISTING_DIR) ) {
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
Files.createDirectories(mountPoint);
builder.setMountpoint(mountPoint);
}
} else if (mountProvider.hasCapability(MOUNT_TO_EXISTING_DIR) || mountProvider.hasCapability(MOUNT_WITHIN_EXISTING_PARENT) || mountProvider.hasCapability(MOUNT_AS_DRIVE_LETTER)) {
// TODO: move the mount point away in case of MOUNT_WITHIN_EXISTING_PARENT?
builder.setMountpoint(userChosenMountPoint);
}
return builder;
}
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, MountFailedException {
if (cryptoFileSystem.get() != null) {
throw new IllegalStateException("Already unlocked.");
@@ -207,8 +147,7 @@ public class Vault {
try {
cryptoFileSystem.set(fs);
var rootPath = fs.getRootDirectories().iterator().next();
var supportsForcedUnmount = mountService.getValue().hasCapability(MountCapability.UNMOUNT_FORCED);
var mountHandle = new MountHandle(prepareMount(rootPath).mount(), supportsForcedUnmount);
var mountHandle = mounter.mount(vaultSettings, rootPath);
success = this.mountHandle.compareAndSet(null, mountHandle);
} finally {
if (!success) {
@@ -217,7 +156,6 @@ public class Vault {
}
}
public synchronized void lock(boolean forced) throws UnmountFailedException, IOException {
var mountHandle = this.mountHandle.get();
if (mountHandle == null) {
@@ -225,14 +163,15 @@ public class Vault {
return;
}
if (forced && mountHandle.supportsUnmountForced) {
mountHandle.mount.unmountForced();
if (forced && mountHandle.supportsUnmountForced()) {
mountHandle.mountObj().unmountForced();
} else {
mountHandle.mount.unmount();
mountHandle.mountObj().unmount();
}
try {
mountHandle.mount.close();
mountHandle.mountObj().close();
mountHandle.specialCleanup().run();
} finally {
destroyCryptoFileSystem();
}
@@ -327,7 +266,7 @@ public class Vault {
public Mountpoint getMountPoint() {
var handle = mountHandle.get();
return handle == null ? null : handle.mount.getMountpoint();
return handle == null ? null : handle.mountObj().getMountpoint();
}
public StringBinding displayablePathProperty() {
@@ -375,35 +314,26 @@ public class Vault {
return vaultSettings.path().getValue();
}
public boolean isHavingCustomMountFlags() {
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
}
public ObservableValue<String> defaultMountFlagsProperty() {
return defaultMountFlags;
}
public String getDefaultMountFlags() {
return defaultMountFlags.getValue();
}
public String getEffectiveMountFlags() {
String mountFlags = vaultSettings.mountFlags().get();
if (Strings.isNullOrEmpty(mountFlags)) {
return ""; //TODO: should the provider provide dem defaults??
} else {
return mountFlags;
/**
* Gets from the cleartext path its ciphertext counterpart.
* The cleartext path has to start from the vault root (by starting with "/").
*
* @return Local os path to the ciphertext resource
* @throws IOException if an I/O error occurs
*/
public Path getCiphertextPath(String cleartextPath) throws IOException {
if (!cleartextPath.startsWith("/")) {
throw new IllegalArgumentException("Input path must be absolute from vault root by starting with \"/\".");
}
var fs = cryptoFileSystem.get();
var cryptoPath = fs.getPath(cleartextPath);
return fs.getCiphertextPath(cryptoPath);
}
public VaultConfigCache getVaultConfigCache() {
return configCache;
}
public void setCustomMountFlags(String mountFlags) {
vaultSettings.mountFlags().set(mountFlags);
}
public String getId() {
return vaultSettings.getId();
}
@@ -426,15 +356,12 @@ public class Vault {
}
}
/* TODO: reactivate/ needed at all?
public boolean supportsForcedUnmount() {
return volume.supportsForcedUnmount();
var mh = mountHandle.get();
if (mh == null) {
throw new IllegalStateException("Vault is not mounted");
}
return mountHandle.get().supportsUnmountForced();
}
*/
private record MountHandle(Mount mount, boolean supportsUnmountForced) {
}
}
@@ -41,6 +41,8 @@ public class VaultStats {
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
private final LongProperty filesRead = new SimpleLongProperty();
private final LongProperty filesWritten = new SimpleLongProperty();
private final LongProperty filesAccessed = new SimpleLongProperty();
private final LongProperty totalFilesAccessed = new SimpleLongProperty();
private final ObjectProperty<Instant> lastActivity = new SimpleObjectProperty<>();
@Inject
@@ -82,6 +84,8 @@ public class VaultStats {
var oldAccessCount = filesRead.get() + filesWritten.get();
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
filesAccessed.set(stats.map(CryptoFileSystemStats::pollAmountOfAccesses).orElse(0L));
totalFilesAccessed.set(stats.map(CryptoFileSystemStats::pollTotalAmountOfAccesses).orElse(0L));
var newAccessCount = filesRead.get() + filesWritten.get();
// check for any I/O activity
@@ -188,6 +192,19 @@ public class VaultStats {
public long getFilesWritten() {return filesWritten.get();}
public LongProperty filesAccessed() {
return filesAccessed;}
public long getFilesAccessed() {return filesAccessed.get();}
public LongProperty totalFilesAccessed(){
return totalFilesAccessed;
}
public long getTotalFilesAccessed(){
return totalFilesAccessed.get();
}
public ObjectProperty<Instant> lastActivityProperty() {
return lastActivity;
}
@@ -2,19 +2,20 @@ package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.fxapp.FxApplicationStyle;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.image.Image;
@@ -23,6 +24,7 @@ import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;
import java.util.ResourceBundle;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
@@ -40,12 +42,10 @@ public class ChooseExistingVaultController implements FxController {
private final ObjectProperty<Vault> vault;
private final VaultListManager vaultListManager;
private final ResourceBundle resourceBundle;
private final Settings settings;
private Image screenshot;
private final ObservableValue<Image> screenshot;
@Inject
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, Settings settings) {
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, FxApplicationStyle applicationStyle) {
this.window = window;
this.welcomeScene = welcomeScene;
this.successScene = successScene;
@@ -54,16 +54,20 @@ public class ChooseExistingVaultController implements FxController {
this.vault = vault;
this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle;
this.settings = settings;
this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot);
}
@FXML
public void initialize() {
private Image selectScreenshot(Theme theme) {
String imageResourcePath;
if (SystemUtils.IS_OS_MAC) {
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-mac"+(UiTheme.LIGHT == settings.theme().get()? "":"-dark")+".png").toString());
imageResourcePath = switch (theme) {
case LIGHT -> "/img/select-masterkey-mac.png";
case DARK -> "/img/select-masterkey-mac-dark.png";
};
} else {
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-win.png").toString());
imageResourcePath = "/img/select-masterkey-win.png";
}
return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString()));
}
@FXML
@@ -92,8 +96,13 @@ public class ChooseExistingVaultController implements FxController {
/* Getter */
public Image getScreenshot() {
public ObservableValue<Image> screenshotProperty() {
return screenshot;
}
public Image getScreenshot() {
return screenshot.getValue();
}
}
@@ -176,7 +176,7 @@ public class CreateNewVaultPasswordController implements FxController {
// 2. initialize vault:
try {
MasterkeyLoader loader = ignored -> masterkey.copy();
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).withKeyLoader(loader).build();
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_GCM).withKeyLoader(loader).build();
CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID);
// 3. write vault-internal readme file:
@@ -13,6 +13,7 @@ public enum FxmlFile {
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
HEALTH_START("/fxml/health_start.fxml"), //
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
HUB_NO_KEYCHAIN("/fxml/hub_no_keychain.fxml"), //
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
HUB_LICENSE_EXCEEDED("/fxml/hub_license_exceeded.fxml"), //
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
@@ -25,8 +25,10 @@ public enum FontAwesome5Icon {
EYE_SLASH("\uF070"), //
FAST_FORWARD("\uF050"), //
FILE("\uF15B"), //
FILE_DOWNLOAD("\uF56D"), //
FILE_IMPORT("\uF56F"), //
FOLDER_OPEN("\uF07C"), //
FUNNEL("\uF0B0"), //
HAND_HOLDING_HEART("\uF4BE"), //
HEART("\uF004"), //
HDD("\uF0A0"), //
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import java.util.Optional;
@@ -24,9 +26,10 @@ public class FxApplicationStyle {
private final Optional<UiAppearanceProvider> appearanceProvider;
private final LicenseHolder licenseHolder;
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
private final ObjectProperty<Theme> appliedTheme = new SimpleObjectProperty<>(Theme.LIGHT);
@Inject
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder){
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder) {
this.settings = settings;
this.appearanceProvider = appearanceProvider;
this.licenseHolder = licenseHolder;
@@ -91,6 +94,7 @@ public class FxApplicationStyle {
} else {
Application.setUserAgentStylesheet(stylesheet.toString());
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
appliedTheme.set(Theme.LIGHT);
}
}
@@ -103,6 +107,11 @@ public class FxApplicationStyle {
} else {
Application.setUserAgentStylesheet(stylesheet.toString());
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
appliedTheme.set(Theme.DARK);
}
}
public ObjectProperty<Theme> appliedThemeProperty() {
return appliedTheme;
}
}
@@ -8,15 +8,34 @@ import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.util.StringConverter;
import java.util.Arrays;
import java.util.ResourceBundle;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static org.cryptomator.cryptofs.health.api.DiagnosticResult.Severity;
import static org.cryptomator.ui.health.Result.FixState.FIXABLE;
import static org.cryptomator.ui.health.Result.FixState.FIXED;
import static org.cryptomator.ui.health.Result.FixState.FIXING;
import static org.cryptomator.ui.health.Result.FixState.FIX_FAILED;
import static org.cryptomator.ui.health.Result.FixState.NOT_FIXABLE;
@HealthCheckScoped
public class CheckDetailController implements FxController {
@@ -35,30 +54,48 @@ public class CheckDetailController implements FxController {
private final Binding<Number> countOfCritSeverity;
private final Binding<Boolean> warnOrCritsExist;
private final ResultListCellFactory resultListCellFactory;
private final ResultFixApplier resultFixApplier;
private final ResourceBundle resourceBundle;
private final BooleanProperty fixAllInfoResultsExecuted;
private final BooleanBinding fixAllInfoResultsPossible;
private final ObjectProperty<Predicate<Result>> resultsFilter;
public ListView<Result> resultsListView;
public ChoiceBox<DiagnosticResult.Severity> severityChoiceBox;
public ChoiceBox<Result.FixState> fixStateChoiceBox;
private Subscription resultSubscription;
@Inject
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory) {
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory, ResultFixApplier resultFixApplier, ResourceBundle resourceBundle) {
this.resultListCellFactory = resultListCellFactory;
this.resultFixApplier = resultFixApplier;
this.resourceBundle = resourceBundle;
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
this.check = selectedTask;
this.checkState = selectedTask.flatMap(Check::stateProperty);
this.checkName = selectedTask.map(Check::getName).orElse("");
this.checkRunning = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.RUNNING::equals).orElse(false));
this.checkScheduled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false));
this.checkSkipped =BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SKIPPED::equals).orElse(false));
this.checkSkipped = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SKIPPED::equals).orElse(false));
this.checkSucceeded = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false));
this.checkFailed = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.ERROR::equals).orElse(false));
this.checkCancelled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.CANCELLED::equals).orElse(false));
this.checkFinished = checkSucceeded.or(checkFailed).or(checkCancelled);
this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN));
this.countOfCritSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.CRITICAL));
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0) );
this.countOfWarnSeverity = results.reduce(countSeverity(Severity.WARN));
this.countOfCritSeverity = results.reduce(countSeverity(Severity.CRITICAL));
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0));
this.fixAllInfoResultsExecuted = new SimpleBooleanProperty(false);
this.fixAllInfoResultsPossible = Bindings.createBooleanBinding(() -> results.stream().anyMatch(this::isFixableInfoResult), results) //
.and(fixAllInfoResultsExecuted.not());
this.resultsFilter = new SimpleObjectProperty<>(r -> true);
selectedTask.addListener(this::selectedTaskChanged);
}
private boolean isFixableInfoResult(Result r) {
return r.diagnosis().getSeverity() == Severity.INFO && r.getState() == FIXABLE;
}
private void selectedTaskChanged(ObservableValue<? extends Check> observable, Check oldValue, Check newValue) {
if (resultSubscription != null) {
resultSubscription.unsubscribe();
@@ -66,6 +103,8 @@ public class CheckDetailController implements FxController {
if (newValue != null) {
resultSubscription = EasyBind.bindContent(results, newValue.getResults());
}
severityChoiceBox.setValue(null);
fixStateChoiceBox.setValue(null);
}
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
@@ -74,8 +113,110 @@ public class CheckDetailController implements FxController {
@FXML
public void initialize() {
resultsListView.setItems(results);
resultsListView.setItems(results.filtered(resultsFilter));
resultsListView.setCellFactory(resultListCellFactory);
severityChoiceBox.getItems().add(null);
severityChoiceBox.getItems().addAll(Arrays.stream(DiagnosticResult.Severity.values()).toList());
severityChoiceBox.setConverter(new SeverityStringifier());
severityChoiceBox.setValue(null);
fixStateChoiceBox.getItems().add(null);
fixStateChoiceBox.getItems().addAll(Arrays.stream(Result.FixState.values()).toList());
fixStateChoiceBox.setConverter(new FixStateStringifier());
fixStateChoiceBox.setValue(null);
resultsFilter.bind(Bindings.createObjectBinding(() -> this::filterResults, severityChoiceBox.valueProperty(), fixStateChoiceBox.valueProperty()));
}
private boolean filterResults(Result r) {
var desiredFixState = fixStateChoiceBox.getValue();
var desiredSeverity = severityChoiceBox.getValue();
return (desiredFixState == null || r.getState() == desiredFixState) && (desiredSeverity == null || r.diagnosis().getSeverity() == desiredSeverity);
}
@FXML
public void fixAllInfoResults() {
fixAllInfoResultsExecuted.setValue(true);
results.stream().filter(this::isFixableInfoResult).forEach(resultFixApplier::fix);
}
@FXML
public void copyResultDetails() {
var result = resultsListView.getSelectionModel().getSelectedItem();
if (result != null) {
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(result.diagnosis().toString());
Clipboard.getSystemClipboard().setContent(clipboardContent);
}
}
/* -- Internal classes -- */
class SeverityStringifier extends StringConverter<Severity> {
@Override
public String toString(Severity object) {
if (object == null) {
return resourceBundle.getString("health.result.severityFilter.all");
}
return switch (object) {
case GOOD -> resourceBundle.getString("health.result.severityFilter.good");
case INFO -> resourceBundle.getString("health.result.severityFilter.info");
case WARN -> resourceBundle.getString("health.result.severityFilter.warn");
case CRITICAL -> resourceBundle.getString("health.result.severityFilter.crit");
};
}
@Override
public Severity fromString(String string) {
if (resourceBundle.getString("health.result.severityFilter.good").equals(string)) {
return Severity.GOOD;
} else if (resourceBundle.getString("health.result.severityFilter.info").equals(string)) {
return Severity.INFO;
} else if (resourceBundle.getString("health.result.severityFilter.warn").equals(string)) {
return Severity.WARN;
} else if (resourceBundle.getString("health.result.severityFilter.crit").equals(string)) {
return Severity.CRITICAL;
} else {
return null;
}
}
}
class FixStateStringifier extends StringConverter<Result.FixState> {
@Override
public String toString(Result.FixState object) {
if (object == null) {
return resourceBundle.getString("health.result.fixStateFilter.all");
}
return switch (object) {
case FIXABLE -> resourceBundle.getString("health.result.fixStateFilter.fixable");
case NOT_FIXABLE -> resourceBundle.getString("health.result.fixStateFilter.notFixable");
case FIXING -> resourceBundle.getString("health.result.fixStateFilter.fixing");
case FIXED -> resourceBundle.getString("health.result.fixStateFilter.fixed");
case FIX_FAILED -> resourceBundle.getString("health.result.fixStateFilter.fixFailed");
};
}
@Override
public Result.FixState fromString(String string) {
if (resourceBundle.getString("health.result.fixStateFilter.fixable").equals(string)) {
return FIXABLE;
} else if (resourceBundle.getString("health.result.fixStateFilter.notFixable").equals(string)) {
return NOT_FIXABLE;
} else if (resourceBundle.getString("health.result.fixStateFilter.fixing").equals(string)) {
return FIXING;
} else if (resourceBundle.getString("health.result.fixStateFilter.fixed").equals(string)) {
return FIXED;
} else if (resourceBundle.getString("health.result.fixStateFilter.fixFailed").equals(string)) {
return FIX_FAILED;
} else {
return null;
}
}
}
/* Getter/Setter */
@@ -175,4 +316,12 @@ public class CheckDetailController implements FxController {
public Check getCheck() {
return check.get();
}
public ObservableValue<Boolean> fixAllInfoResultsPossibleProperty() {
return fixAllInfoResultsPossible;
}
public boolean getFixAllInfoResultsPossible() {
return fixAllInfoResultsPossible.getValue();
}
}
@@ -70,7 +70,7 @@ public class CheckExecutor {
try (var masterkeyClone = masterkey.copy(); //
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
c.getHealthCheck().check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis)));
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis, vaultPath, vaultConfig, masterkeyClone, cryptor)));
highestResultSeverity = Comparators.max(highestResultSeverity, diagnosis.getSeverity());
});
}
@@ -31,7 +31,7 @@ public class CheckStateIconView extends FontAwesome5IconView {
this.severity = EasyBind.wrapNullable(check).mapObservable(Check::highestResultSeverityProperty).asOrdinary();
this.glyph.bind(Bindings.createObjectBinding(this::glyphForState, state, severity));
this.subscriptions = List.of( //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-muted", Bindings.equal(state, Check.CheckState.SKIPPED).or(Bindings.equal(state, Check.CheckState.CANCELLED))), //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-muted", Bindings.equal(state, Check.CheckState.SKIPPED).or(Bindings.equal(state, Check.CheckState.CANCELLED)).or(Bindings.equal(severity, DiagnosticResult.Severity.INFO))), //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN).or(Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL))), //
EasyBind.includeWhen(getStyleClass(), "glyph-icon-red", Bindings.equal(state, Check.CheckState.ERROR)) //
@@ -1,10 +1,14 @@
package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.Masterkey;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.nio.file.Path;
record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
@@ -16,8 +20,8 @@ record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
FIX_FAILED
}
public static Result create(DiagnosticResult diagnosis) {
FixState initialState = diagnosis.getSeverity() == DiagnosticResult.Severity.WARN ? FixState.FIXABLE : FixState.NOT_FIXABLE;
public static Result create(DiagnosticResult diagnosis, Path vaultPath, VaultConfig config, Masterkey masterkey, Cryptor cryptor) {
FixState initialState = diagnosis.getFix(vaultPath, config, masterkey, cryptor).map( _f -> FixState.FIXABLE).orElse(FixState.NOT_FIXABLE);
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
}
@@ -23,6 +23,8 @@ import java.util.concurrent.atomic.AtomicReference;
@HealthCheckScoped
class ResultFixApplier {
private static final Logger LOG = LoggerFactory.getLogger(ResultFixApplier.class);
private final Path vaultPath;
private final SecureRandom csprng;
private final Masterkey masterkey;
@@ -40,25 +42,34 @@ class ResultFixApplier {
public CompletionStage<Void> fix(Result result) {
Preconditions.checkArgument(result.getState() == Result.FixState.FIXABLE);
result.setState(Result.FixState.FIXING);
return CompletableFuture.runAsync(() -> fix(result.diagnosis()), sequentialExecutor)
return CompletableFuture.runAsync(() -> result.setState(Result.FixState.FIXING), Platform::runLater) //
.thenRunAsync(() -> fix(result.diagnosis()), sequentialExecutor) //
.whenCompleteAsync((unused, throwable) -> {
var fixed = throwable == null ? Result.FixState.FIXED : Result.FixState.FIX_FAILED;
result.setState(fixed);
final Result.FixState s;
if (throwable == null) {
LOG.debug("Fix for {} applied successful.", result.diagnosis().getClass().getName());
s = Result.FixState.FIXED;
} else {
LOG.error("Failed to apply fix for {}", result.diagnosis().getClass().getName(), throwable);
s = Result.FixState.FIX_FAILED;
}
result.setState(s);
}, Platform::runLater);
}
public void fix(DiagnosticResult diagnosis) {
Preconditions.checkArgument(diagnosis.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
private void fix(DiagnosticResult diagnosis) {
try (var masterkeyClone = masterkey.copy(); //
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
diagnosis.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
diagnosis.getFix(vaultPath, vaultConfig, masterkeyClone, cryptor) //
.orElseThrow(() -> new IllegalStateException("No fix for diagnosis " + diagnosis.getClass().getName() + " implemented.")) //
.apply();
} catch (Exception e) {
throw new FixFailedException(e);
}
}
public static class FixFailedException extends CompletionException {
private FixFailedException(Throwable cause) {
super(cause);
}
@@ -12,7 +12,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
@@ -34,7 +33,6 @@ public class ResultListCellController implements FxController {
private static final FontAwesome5Icon WARN_ICON = FontAwesome5Icon.EXCLAMATION_TRIANGLE;
private static final FontAwesome5Icon CRIT_ICON = FontAwesome5Icon.TIMES;
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
private final ObjectProperty<Result> result;
private final ObservableValue<DiagnosticResult.Severity> severity;
@@ -42,17 +40,17 @@ public class ResultListCellController implements FxController {
private final ResultFixApplier fixApplier;
private final ObservableValue<Result.FixState> fixState;
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
private final BooleanBinding fixable;
private final BooleanBinding fixing;
private final BooleanBinding fixed;
private final BooleanBinding fixFailed;
private final BooleanBinding fixRunningOrDone;
private final ObservableValue<FontAwesome5Icon> fixGlyph;
private final List<Subscription> subscriptions;
private final Tooltip fixSuccess;
private final Tooltip fixFail;
private final Tooltip fixStateTip;
private final Tooltip severityTip;
private AutoAnimator fixRunningRotator;
private final ResourceBundle resourceBundle;
/* FXML */
public FontAwesome5IconView severityView;
@@ -60,23 +58,54 @@ public class ResultListCellController implements FxController {
@Inject
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
this.result = new SimpleObjectProperty<>(null);
this.severity = result.map(Result::diagnosis).map(DiagnosticResult::getSeverity);
this.description = result.map(Result::getDescription).orElse("");
this.fixApplier = fixApplier;
this.fixState = result.flatMap(Result::fixState);
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
this.fixGlyph = Bindings.createObjectBinding(this::getFixGlyph, fixState);
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
this.fixFailed = Bindings.createBooleanBinding(this::isFixFailed, fixState);
this.fixRunningOrDone = fixing.or(fixed).or(fixFailed);
this.fixGlyph = fixState.map(this::getFixGlyph);
this.subscriptions = new ArrayList<>();
this.fixSuccess = new Tooltip(resourceBundle.getString("health.fix.successTip"));
this.fixFail = new Tooltip(resourceBundle.getString("health.fix.failTip"));
fixSuccess.setShowDelay(Duration.millis(100));
fixFail.setShowDelay(Duration.millis(100));
this.fixStateTip = new Tooltip();
fixStateTip.textProperty().bind(fixState.map(this::getFixStateDescription));
fixStateTip.setShowDelay(Duration.millis(100));
this.severityTip = new Tooltip();
severityTip.textProperty().bind(severity.map(this::getSeverityDescription));
severityTip.setShowDelay(Duration.millis(150));
}
public FontAwesome5Icon getFixGlyph(Result.FixState state) {
return switch (state) {
case FIXING -> FontAwesome5Icon.SPINNER;
case FIXED -> FontAwesome5Icon.CHECK;
case FIX_FAILED -> FontAwesome5Icon.TIMES;
default -> null;
};
}
private String getFixStateDescription(Result.FixState fixState) {
return switch (fixState) {
case FIXED -> resourceBundle.getString("health.fix.successTip");
case FIX_FAILED -> resourceBundle.getString("health.fix.failTip");
default -> "";
};
}
private String getSeverityDescription(DiagnosticResult.Severity severity) {
return resourceBundle.getString(switch (severity) {
case GOOD -> "health.result.severityTip.good";
case INFO -> "health.result.severityTip.info";
case WARN -> "health.result.severityTip.warn";
case CRITICAL -> "health.result.severityTip.crit";
});
}
@FXML
@@ -93,22 +122,19 @@ public class ResultListCellController implements FxController {
.onCondition(fixing) //
.afterStop(() -> fixView.setRotate(0)) //
.build();
fixState.addListener(((observable, oldValue, newValue) -> {
if (newValue == Result.FixState.FIXED || newValue == Result.FixState.FIX_FAILED) {
Tooltip.install(fixView, fixStateTip);
}
}));
Tooltip.install(severityView, severityTip);
}
@FXML
public void fix() {
Result r = result.get();
if (r != null) {
fixApplier.fix(r).whenCompleteAsync(this::fixFinished, Platform::runLater);
}
}
private void fixFinished(Void unused, Throwable exception) {
if (exception != null) {
LOG.error("Failed to apply fix", exception);
Tooltip.install(fixView, fixFail);
} else {
Tooltip.install(fixView, fixSuccess);
fixApplier.fix(r);
}
}
@@ -152,20 +178,12 @@ public class ResultListCellController implements FxController {
};
}
public ObjectBinding<FontAwesome5Icon> fixGlyphProperty() {
public ObservableValue<FontAwesome5Icon> fixGlyphProperty() {
return fixGlyph;
}
public FontAwesome5Icon getFixGlyph() {
if (fixState.getValue() == null) {
return null;
}
return switch (fixState.getValue()) {
case NOT_FIXABLE, FIXABLE -> null;
case FIXING -> FontAwesome5Icon.SPINNER;
case FIXED -> FontAwesome5Icon.CHECK;
case FIX_FAILED -> FontAwesome5Icon.TIMES;
};
return fixGlyph.getValue();
}
public BooleanBinding fixableProperty() {
@@ -87,6 +87,13 @@ public abstract class HubKeyLoadingModule {
@StringKey(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS)
abstract KeyLoadingStrategy bindHubKeyLoadingStrategyToHubHttps(HubKeyLoadingStrategy strategy);
@Provides
@FxmlScene(FxmlFile.HUB_NO_KEYCHAIN)
@KeyLoadingScoped
static Scene provideHubNoKeychainScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_NO_KEYCHAIN);
}
@Provides
@FxmlScene(FxmlFile.HUB_AUTH_FLOW)
@KeyLoadingScoped
@@ -136,6 +143,11 @@ public abstract class HubKeyLoadingModule {
return fxmlLoaders.createScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE);
}
@Binds
@IntoMap
@FxControllerKey(NoKeychainController.class)
abstract FxController bindNoKeychainController(NoKeychainController controller);
@Binds
@IntoMap
@FxControllerKey(AuthFlowController.class)
@@ -3,6 +3,8 @@ 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;
import org.cryptomator.common.settings.DeviceKey;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
@@ -31,15 +33,19 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
private final Stage window;
private final KeychainManager keychainManager;
private final Lazy<Scene> authFlowScene;
private final Lazy<Scene> noKeychainScene;
private final CompletableFuture<JWEObject> result;
private final DeviceKey deviceKey;
@Inject
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, @Named("windowTitle") String windowTitle) {
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
this.window = window;
this.keychainManager = keychainManager;
window.setTitle(windowTitle);
this.authFlowScene = authFlowScene;
this.noKeychainScene = noKeychainScene;
this.result = result;
this.deviceKey = deviceKey;
}
@@ -48,9 +54,16 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
try {
startAuthFlow();
if (!keychainManager.isSupported()) {
throw new NoKeychainAccessProviderException();
}
var keypair = deviceKey.get();
showWindow(authFlowScene);
var jwe = result.get();
return JWEHelper.decrypt(jwe, deviceKey.get().getPrivate());
return JWEHelper.decrypt(jwe, keypair.getPrivate());
} catch (NoKeychainAccessProviderException e) {
showWindow(noKeychainScene);
throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e);
} catch (DeviceKey.DeviceKeyRetrievalException e) {
throw new MasterkeyLoadingFailedException("Failed to load keypair", e);
} catch (CancellationException e) {
@@ -63,9 +76,9 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
}
}
private void startAuthFlow() {
private void showWindow(Lazy<Scene> scene) {
Platform.runLater(() -> {
window.setScene(authFlowScene.get());
window.setScene(scene.get());
window.show();
Window owner = window.getOwner();
if (owner != null) {
@@ -0,0 +1,31 @@
package org.cryptomator.ui.keyloading.hub;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import javax.inject.Inject;
import javafx.stage.Stage;
public class NoKeychainController implements FxController {
private final Stage window;
private final FxApplicationWindows appWindows;
@Inject
public NoKeychainController(@KeyLoading Stage window, FxApplicationWindows appWindows) {
this.window = window;
this.appWindows = appWindows;
}
public void cancel() {
window.close();
}
public void openPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
window.close();
}
}
@@ -53,7 +53,7 @@ public class LockForcedController implements FxController {
}
public boolean isForceSupported() {
return false;//vault.supportsForcedUnmount(); TODO
return vault.supportsForcedUnmount();
}
}
@@ -3,33 +3,17 @@ package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.DirStructure;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.input.DragEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Set;
import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
@MainWindowScoped
public class MainWindowController implements FxController {
@@ -37,28 +21,19 @@ public class MainWindowController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
private final Stage window;
private final VaultListManager vaultListManager;
private final ReadOnlyObjectProperty<Vault> selectedVault;
private final WrongFileAlertComponent.Builder wrongFileAlert;
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
public StackPane root;
@Inject
public MainWindowController(@MainWindow Stage window, VaultListManager vaultListManager, ObjectProperty<Vault> selectedVault, WrongFileAlertComponent.Builder wrongFileAlert) {
public MainWindowController(@MainWindow Stage window, ObjectProperty<Vault> selectedVault) {
this.window = window;
this.vaultListManager = vaultListManager;
this.selectedVault = selectedVault;
this.wrongFileAlert = wrongFileAlert;
}
@FXML
public void initialize() {
LOG.trace("init MainWindowController");
root.setOnDragEntered(this::handleDragEvent);
root.setOnDragOver(this::handleDragEvent);
root.setOnDragDropped(this::handleDragEvent);
root.setOnDragExited(this::handleDragEvent);
if (SystemUtils.IS_OS_WINDOWS) {
root.getStyleClass().add("os-windows");
}
@@ -72,65 +47,4 @@ public class MainWindowController implements FxController {
}
}
private void handleDragEvent(DragEvent event) {
if (DragEvent.DRAG_ENTERED.equals(event.getEventType()) && event.getGestureSource() == null) {
draggingOver.set(true);
} else if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
event.acceptTransferModes(TransferMode.ANY);
draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
if (vaultPaths.isEmpty()) {
wrongFileAlert.build().showWrongFileAlertWindow();
} else {
vaultPaths.forEach(this::addVault);
}
event.setDropCompleted(!vaultPaths.isEmpty());
event.consume();
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
draggingOver.set(false);
draggingVaultOver.set(false);
}
}
private boolean containsVault(Path path) {
try {
if (path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
path = path.getParent();
}
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
} catch (IOException e) {
return false;
}
}
private void addVault(Path pathToVault) {
try {
if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
vaultListManager.add(pathToVault.getParent());
} else {
vaultListManager.add(pathToVault);
}
} catch (IOException e) {
LOG.debug("Not a vault: {}", pathToVault);
}
}
/* Getter/Setter */
public BooleanProperty draggingOverProperty() {
return draggingOver;
}
public boolean isDraggingOver() {
return draggingOver.get();
}
public BooleanProperty draggingVaultOverProperty() {
return draggingVaultOver;
}
public boolean isDraggingVaultOver() {
return draggingVaultOver.get();
}
}
@@ -1,54 +1,118 @@
package org.cryptomator.ui.mainwindow;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.revealpath.RevealFailedException;
import org.cryptomator.integrations.revealpath.RevealPathService;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.DragEvent;
import javafx.scene.input.TransferMode;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.net.URI;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@MainWindowScoped
public class VaultDetailUnlockedController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(VaultDetailUnlockedController.class);
private static final String ACTIVE_CLASS = "active";
private final ReadOnlyObjectProperty<Vault> vault;
private final FxApplicationWindows appWindows;
private final VaultService vaultService;
private final WrongFileAlertComponent.Builder wrongFileAlert;
private final Stage mainWindow;
private final ResourceBundle resourceBundle;
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
private final ObservableValue<Mountpoint> mountPoint;
private final ObservableValue<Boolean> accessibleViaPath;
private final ObservableValue<Boolean> accessibleViaUri;
private final ObservableValue<String> mountUri;
private final ObservableValue<String> mountPoint;
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty();
//FXML
public Button dropZone;
@Inject
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, WrongFileAlertComponent.Builder wrongFileAlert, @MainWindow Stage mainWindow, ResourceBundle resourceBundle) {
this.vault = vault;
this.appWindows = appWindows;
this.vaultService = vaultService;
this.wrongFileAlert = wrongFileAlert;
this.mainWindow = mainWindow;
this.resourceBundle = resourceBundle;
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
this.vaultStatsBuilder = vaultStatsBuilder;
this.mountPoint = vault.flatMap(Vault::mountPointProperty);
this.accessibleViaPath = mountPoint.map(m -> m instanceof Mountpoint.WithPath).orElse(false);
this.accessibleViaUri = mountPoint.map(m -> m instanceof Mountpoint.WithUri).orElse(false);
this.mountUri = mountPoint.map(Mountpoint::uri).map(URI::toASCIIString).orElse("");
var mp = vault.flatMap(Vault::mountPointProperty);
this.accessibleViaPath = mp.map(m -> m instanceof Mountpoint.WithPath).orElse(false);
this.accessibleViaUri = mp.map(m -> m instanceof Mountpoint.WithUri).orElse(false);
this.mountPoint = mp.map(m -> {
if (m instanceof Mountpoint.WithPath mwp) {
return mwp.path().toString();
} else {
return m.uri().toASCIIString();
}
});
}
public void initialize() {
dropZone.setOnDragEntered(this::handleDragEvent);
dropZone.setOnDragOver(this::handleDragEvent);
dropZone.setOnDragDropped(this::handleDragEvent);
dropZone.setOnDragExited(this::handleDragEvent);
EasyBind.includeWhen(dropZone.getStyleClass(), ACTIVE_CLASS, draggingOver);
}
private void handleDragEvent(DragEvent event) {
if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
event.acceptTransferModes(TransferMode.LINK);
draggingOver.set(true);
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
List<Path> ciphertextPaths = event.getDragboard().getFiles().stream().map(File::toPath).map(this::getCiphertextPath).flatMap(Optional::stream).toList();
if (ciphertextPaths.isEmpty()) {
wrongFileAlert.build().showWrongFileAlertWindow();
} else {
revealOrCopyPaths(ciphertextPaths);
}
event.setDropCompleted(!ciphertextPaths.isEmpty());
event.consume();
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
draggingOver.set(false);
}
}
private VaultStatisticsComponent buildVaultStats(Vault vault) {
@@ -62,7 +126,9 @@ public class VaultDetailUnlockedController implements FxController {
@FXML
public void copyMountUri() {
// TODO
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(mountPoint.getValue());
Clipboard.getSystemClipboard().setContent(clipboardContent);
}
@FXML
@@ -75,6 +141,77 @@ public class VaultDetailUnlockedController implements FxController {
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
}
@FXML
public void chooseFileAndReveal() {
Preconditions.checkState(accessibleViaPath.getValue());
var fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.filePickerTitle"));
fileChooser.setInitialDirectory(Path.of(mountPoint.getValue()).toFile());
var cleartextFile = fileChooser.showOpenDialog(mainWindow);
if (cleartextFile != null) {
var ciphertextPaths = getCiphertextPath(cleartextFile.toPath()).stream().toList();
revealOrCopyPaths(ciphertextPaths);
}
}
private boolean startsWithVaultAccessPoint(Path path) {
return path.startsWith(Path.of(mountPoint.getValue()));
}
private Optional<Path> getCiphertextPath(Path path) {
if (!startsWithVaultAccessPoint(path)) {
LOG.debug("Path does not start with access point of selected vault: {}", path);
return Optional.empty();
}
try {
var accessPoint = mountPoint.getValue();
var cleartextPath = path.toString().substring(accessPoint.length());
if (!cleartextPath.startsWith("/")) {
cleartextPath = "/" + cleartextPath;
}
return Optional.of(vault.get().getCiphertextPath(cleartextPath));
} catch (IOException e) {
LOG.warn("Unable to get ciphertext path from path: {}", path);
return Optional.empty();
}
}
private void revealOrCopyPaths(List<Path> paths) {
if (!revealPaths(paths)) {
LOG.warn("No service provider to reveal files found.");
copyPathsToClipboard(paths);
}
}
/**
* Reveals the paths over the {@link RevealPathService} in the file system
*
* @param paths List of Paths to reveal
* @return true, if at least one service provider was present, false otherwise
*/
private boolean revealPaths(List<Path> paths) {
return RevealPathService.get().findAny().map(s -> {
paths.forEach(path -> {
try {
s.reveal(path);
} catch (RevealFailedException e) {
LOG.error("Revealing ciphertext file failed.", e);
}
});
return true;
}).orElse(false);
}
private void copyPathsToClipboard(List<Path> paths) {
StringBuilder clipboardString = new StringBuilder();
paths.forEach(p -> clipboardString.append(p.toString()).append("\n"));
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, clipboardString.toString()));
ciphertextPathsCopied.setValue(true);
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
ciphertextPathsCopied.set(false);
});
}
/* Getter/Setter */
public ReadOnlyObjectProperty<Vault> vaultProperty() {
@@ -101,13 +238,19 @@ public class VaultDetailUnlockedController implements FxController {
return accessibleViaUri.getValue();
}
public ObservableValue<String> mountUriProperty() {
return mountUri;
public ObservableValue<String> mountPointProperty() {
return mountPoint;
}
public String getMountUri() {
return mountUri.getValue();
public String getMountPoint() {
return mountPoint.getValue();
}
public BooleanProperty ciphertextPathsCopiedProperty() {
return ciphertextPathsCopied;
}
public boolean isCiphertextPathsCopied() {
return ciphertextPathsCopied.get();
}
}
@@ -3,26 +3,43 @@ package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.DirStructure;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
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.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
@@ -31,6 +48,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
@MainWindowScoped
public class VaultListController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
private final Stage mainWindow;
private final ObservableList<Vault> vaults;
@@ -39,17 +57,21 @@ public class VaultListController implements FxController {
private final AddVaultWizardComponent.Builder addVaultWizard;
private final BooleanBinding emptyVaultList;
private final RemoveVaultComponent.Builder removeVaultDialogue;
private final VaultListManager vaultListManager;
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
public ListView<Vault> vaultList;
public StackPane root;
@Inject
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue) {
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue, VaultListManager vaultListManager) {
this.mainWindow = mainWindow;
this.vaults = vaults;
this.selectedVault = selectedVault;
this.cellFactory = cellFactory;
this.addVaultWizard = addVaultWizard;
this.removeVaultDialogue = removeVaultDialogue;
this.vaultListManager = vaultListManager;
this.emptyVaultList = Bindings.isEmpty(vaults);
@@ -100,6 +122,11 @@ public class VaultListController implements FxController {
keyEvent.consume();
}
});
root.setOnDragEntered(this::handleDragEvent);
root.setOnDragOver(this::handleDragEvent);
root.setOnDragDropped(this::handleDragEvent);
root.setOnDragExited(this::handleDragEvent);
}
private void deselect(MouseEvent released) {
@@ -128,6 +155,47 @@ public class VaultListController implements FxController {
}
}
private void handleDragEvent(DragEvent event) {
if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
if (draggingVaultOver.get()) {
event.acceptTransferModes(TransferMode.ANY);
}
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
if (!vaultPaths.isEmpty()) {
vaultPaths.forEach(this::addVault);
}
event.setDropCompleted(!vaultPaths.isEmpty());
event.consume();
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
draggingVaultOver.set(false);
}
}
private boolean containsVault(Path path) {
try {
if (path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
path = path.getParent();
}
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
} catch (IOException e) {
return false;
}
}
private void addVault(Path pathToVault) {
try {
if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
vaultListManager.add(pathToVault.getParent());
} else {
vaultListManager.add(pathToVault);
}
} catch (IOException e) {
LOG.debug("Not a vault: {}", pathToVault);
}
}
// Getter and Setter
public BooleanBinding emptyVaultListProperty() {
@@ -138,4 +206,13 @@ public class VaultListController implements FxController {
return emptyVaultList.get();
}
public BooleanProperty draggingVaultOverProperty() {
return draggingVaultOver;
}
public boolean isDraggingVaultOver() {
return draggingVaultOver.get();
}
}
@@ -1,5 +1,6 @@
package org.cryptomator.ui.preferences;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.mount.MountCapability;
import org.cryptomator.integrations.mount.MountService;
@@ -14,39 +15,54 @@ import javafx.scene.control.ChoiceBox;
import javafx.scene.control.TextField;
import javafx.util.StringConverter;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
/**
* TODO: if WebDAV is selected under Windows, show warning that specific mount options (like selecting a directory as mount point) are _not_ supported
*/
@PreferencesScoped
public class VolumePreferencesController implements FxController {
private final Settings settings;
private final ObservableValue<MountService> selectedMountService;
private final ResourceBundle resourceBundle;
private final BooleanExpression loopbackPortSupported;
private final ObservableValue<Boolean> mountToDirSupported;
private final ObservableValue<Boolean> mountToDriveLetterSupported;
private final ObservableValue<Boolean> mountFlagsSupported;
private final ObservableValue<Boolean> readonlySupported;
private final List<MountService> mountProviders;
public ChoiceBox<MountService> volumeTypeChoiceBox;
public TextField loopbackPortField;
public Button loopbackPortApplyButton;
@Inject
VolumePreferencesController(Settings settings, List<MountService> mountProviders, ObservableValue<MountService> selectedMountService) {
VolumePreferencesController(Settings settings, List<MountService> mountProviders, ResourceBundle resourceBundle) {
this.settings = settings;
this.mountProviders = mountProviders;
this.selectedMountService = selectedMountService;
this.resourceBundle = resourceBundle;
var fallbackProvider = mountProviders.stream().findFirst().orElse(null);
this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService(), serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider);
this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)));
this.mountToDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT) || s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR));
this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
this.readonlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
}
public void initialize() {
volumeTypeChoiceBox.getItems().add(null);
volumeTypeChoiceBox.getItems().addAll(mountProviders);
volumeTypeChoiceBox.setConverter(new MountServiceConverter());
volumeTypeChoiceBox.getSelectionModel().select(selectedMountService.getValue());
volumeTypeChoiceBox.valueProperty().addListener((observableValue, oldProvide, newProvider) -> settings.mountService().set(newProvider.getClass().getName()));
boolean autoSelected = settings.mountService().get() == null;
volumeTypeChoiceBox.getSelectionModel().select(autoSelected ? null : selectedMountService.getValue());
volumeTypeChoiceBox.valueProperty().addListener((observableValue, oldProvider, newProvider) -> {
var toSet = Optional.ofNullable(newProvider).map(nP -> nP.getClass().getName()).orElse(null);
settings.mountService().set(toSet);
});
loopbackPortField.setText(String.valueOf(settings.port().get()));
loopbackPortApplyButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(loopbackPortField.textProperty()));
loopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, loopbackPortField.textProperty()).not());
}
private boolean validateLoopbackPort() {
@@ -75,13 +91,49 @@ public class VolumePreferencesController implements FxController {
return loopbackPortSupported.get();
}
public ObservableValue<Boolean> readonlySupportedProperty() {
return readonlySupported;
}
public boolean isReadonlySupported() {
return readonlySupported.getValue();
}
public ObservableValue<Boolean> mountToDirSupportedProperty() {
return mountToDirSupported;
}
public boolean isMountToDirSupported() {
return mountToDirSupported.getValue();
}
public ObservableValue<Boolean> mountToDriveLetterSupportedProperty() {
return mountToDriveLetterSupported;
}
public boolean isMountToDriveLetterSupported() {
return mountToDriveLetterSupported.getValue();
}
public ObservableValue<Boolean> mountFlagsSupportedProperty() {
return mountFlagsSupported;
}
public boolean isMountFlagsSupported() {
return mountFlagsSupported.getValue();
}
/* Helpers */
private static class MountServiceConverter extends StringConverter<MountService> {
private class MountServiceConverter extends StringConverter<MountService> {
@Override
public String toString(MountService provider) {
return provider== null? "None" : provider.displayName(); //TODO: adjust message
if (provider == null) {
return resourceBundle.getString("preferences.volume.type.automatic");
} else {
return provider.displayName();
}
}
@Override
@@ -89,6 +141,4 @@ public class VolumePreferencesController implements FxController {
throw new UnsupportedOperationException();
}
}
}
@@ -9,8 +9,10 @@ import javax.inject.Inject;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.LongBinding;
import javafx.beans.property.LongProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
@@ -21,6 +23,7 @@ import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Arrays;
import java.util.concurrent.Callable;
@VaultStatisticsScoped
public class VaultStatisticsController implements FxController {
@@ -32,6 +35,7 @@ public class VaultStatisticsController implements FxController {
private final VaultStats stats;
private final Series<Number, Number> readData;
private final Series<Number, Number> writeData;
private final Series<Number, Number> accessData;
private final Timeline ioAnimation;
private final LongBinding bpsRead;
private final LongBinding bpsWritten;
@@ -44,15 +48,20 @@ public class VaultStatisticsController implements FxController {
private final LongBinding totalBytesDecrypted;
private final LongBinding filesRead;
private final LongBinding filesWritten;
private final LongBinding filesAccessed;
private final LongBinding totalFilesAccessed;
private final LongBinding bpsEncrypted;
private final LongBinding bpsDecrypted;
public AreaChart<Number, Number> readChart;
public AreaChart<Number, Number> writeChart;
public AreaChart<Number, Number> accessChart;
public NumberAxis readChartXAxis;
public NumberAxis readChartYAxis;
public NumberAxis writeChartXAxis;
public NumberAxis writeChartYAxis;
public NumberAxis accessChartXAxis;
public NumberAxis accessChartYAxis;
@Inject
public VaultStatisticsController(VaultStatisticsComponent component, @VaultStatisticsWindow Stage window, @VaultStatisticsWindow Vault vault) {
@@ -60,6 +69,7 @@ public class VaultStatisticsController implements FxController {
this.stats = vault.getStats();
this.readData = new Series<>();
this.writeData = new Series<>();
this.accessData = new Series<>();
this.bpsRead = WeakBindings.bindLong(stats.bytesPerSecondReadProperty());
this.bpsWritten = WeakBindings.bindLong(stats.bytesPerSecondWrittenProperty());
this.cacheHitRate = WeakBindings.bindDouble(stats.cacheHitRateProperty());
@@ -71,11 +81,13 @@ public class VaultStatisticsController implements FxController {
this.totalBytesEncrypted = WeakBindings.bindLong(stats.totalBytesEncryptedProperty());
this.filesRead = WeakBindings.bindLong(stats.filesRead());
this.filesWritten = WeakBindings.bindLong(stats.filesWritten());
this.filesAccessed = WeakBindings.bindLong(stats.filesAccessed());
this.totalFilesAccessed = WeakBindings.bindLong(stats.totalFilesAccessed());
this.bpsEncrypted = WeakBindings.bindLong(stats.bytesPerSecondEncryptedProperty());
this.bpsDecrypted = WeakBindings.bindLong(stats.bytesPerSecondDecryptedProperty());
this.ioAnimation = new Timeline(); //TODO Research better timer
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(readData, writeData)));
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(readData, writeData, accessData)));
ioAnimation.setCycleCount(Animation.INDEFINITE);
ioAnimation.play();
@@ -89,6 +101,7 @@ public class VaultStatisticsController implements FxController {
public void initialize() {
readChart.getData().addAll(readData);
writeChart.getData().addAll(writeData);
accessChart.getData().addAll(accessData);
}
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
@@ -96,16 +109,21 @@ public class VaultStatisticsController implements FxController {
private long step = IO_SAMPLING_STEPS;
private final Series<Number, Number> decryptedBytesRead;
private final Series<Number, Number> encryptedBytesWrite;
private final Series<Number, Number> accessedFiles;
private final long[] maxBuf = new long[IO_SAMPLING_STEPS];
private final long[] maxAccessBuf = new long[IO_SAMPLING_STEPS];
public IoSamplingAnimationHandler(Series<Number, Number> readData, Series<Number, Number> writeData) {
public IoSamplingAnimationHandler(Series<Number, Number> readData, Series<Number, Number> writeData, Series<Number, Number> accessData) {
this.decryptedBytesRead = readData;
this.encryptedBytesWrite = writeData;
this.accessedFiles = accessData;
// initialize data once and change value of data points later:
for (int i = 0; i < IO_SAMPLING_STEPS; i++) {
decryptedBytesRead.getData().add(new Data<>(i, 0));
encryptedBytesWrite.getData().add(new Data<>(i, 0));
accessedFiles.getData().add(new Data<>(i, 0));
}
}
@@ -114,17 +132,22 @@ public class VaultStatisticsController implements FxController {
final long currentStep = step++;
final long decBytes = stats.bytesPerSecondReadProperty().get();
final long encBytes = stats.bytesPerSecondWrittenProperty().get();
final long accFiles = stats.filesAccessed().get();
maxBuf[(int) currentStep % IO_SAMPLING_STEPS] = Math.max(decBytes, encBytes);
long allTimeMax = Arrays.stream(maxBuf).max().orElse(0l);
long allTimeMax = Arrays.stream(maxBuf).max().orElse(0L);
maxAccessBuf[(int) currentStep % IO_SAMPLING_STEPS] = accFiles;
long allTimeMaxAccessedFiles = Arrays.stream(maxAccessBuf).max().orElse(0L);
// remove oldest value:
decryptedBytesRead.getData().remove(0);
encryptedBytesWrite.getData().remove(0);
accessedFiles.getData().remove(0);
// add latest value:
decryptedBytesRead.getData().add(new Data<>(currentStep, decBytes));
encryptedBytesWrite.getData().add(new Data<>(currentStep, encBytes));
accessedFiles.getData().add(new Data<>(currentStep, accFiles));
// adjust ranges:
readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
@@ -133,6 +156,9 @@ public class VaultStatisticsController implements FxController {
writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
writeChartXAxis.setUpperBound(currentStep);
writeChartYAxis.setUpperBound(allTimeMax);
accessChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
accessChartXAxis.setUpperBound(currentStep);
accessChartYAxis.setUpperBound(allTimeMaxAccessedFiles);
}
}
@@ -207,4 +233,12 @@ public class VaultStatisticsController implements FxController {
public LongBinding filesWrittenProperty() {return filesWritten;}
public long getFilesWritten() {return filesWritten.get();}
public LongBinding filesAccessedProperty() {return filesAccessed;}
public long getFilesAccessed() {return filesAccessed.get();}
public LongBinding totalFilesAccessedProperty() {return totalFilesAccessed;}
public long getTotalFilesAccessed() {return totalFilesAccessed.get();}
}
@@ -1,5 +1,6 @@
package org.cryptomator.ui.traymenu;
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.Priority;
@@ -19,6 +20,8 @@ import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
@CheckAvailability
@@ -28,6 +31,7 @@ public class AwtTrayMenuController implements TrayMenuController {
private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class);
private final PopupMenu menu = new PopupMenu();
private TrayIcon trayIcon;
@CheckAvailability
public static boolean isAvailable() {
@@ -35,9 +39,9 @@ public class AwtTrayMenuController implements TrayMenuController {
}
@Override
public void showTrayIcon(byte[] rawImageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
var image = Toolkit.getDefaultToolkit().createImage(rawImageData);
var trayIcon = new TrayIcon(image, tooltip, menu);
public void showTrayIcon(byte[] imageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
var image = Toolkit.getDefaultToolkit().createImage(imageData);
trayIcon = new TrayIcon(image, tooltip, menu);
trayIcon.setImageAutoSize(true);
if (SystemUtils.IS_OS_WINDOWS) {
@@ -52,6 +56,15 @@ public class AwtTrayMenuController implements TrayMenuController {
}
}
@Override
public void updateTrayIcon(byte[] imageData) {
if (trayIcon == null) {
throw new IllegalStateException("Failed to update the icon as it has not yet been added");
}
var image = Toolkit.getDefaultToolkit().createImage(imageData);
trayIcon.setImage(image);
}
@Override
public void updateTrayMenu(List<TrayMenuItem> items) {
menu.removeAll();
@@ -59,8 +72,14 @@ public class AwtTrayMenuController implements TrayMenuController {
}
@Override
public void onBeforeOpenMenu(Runnable runnable) {
public void onBeforeOpenMenu(Runnable listener) {
Preconditions.checkNotNull(this.trayIcon);
this.trayIcon.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
listener.run();
}
});
}
private void addChildren(Menu menu, List<TrayMenuItem> items) {
@@ -3,6 +3,7 @@ package org.cryptomator.ui.traymenu;
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.integrations.tray.ActionItem;
import org.cryptomator.integrations.tray.SeparatorItem;
import org.cryptomator.integrations.tray.SubMenuItem;
@@ -32,7 +33,9 @@ public class TrayMenuBuilder {
private static final Logger LOG = LoggerFactory.getLogger(TrayMenuBuilder.class);
private static final String TRAY_ICON_MAC = "/img/tray_icon_mac@2x.png";
private static final String TRAY_ICON = "/img/window_icon_32.png";
private static final String TRAY_ICON_UNLOCKED_MAC = "/img/tray_icon_unlocked_mac@2x.png";
private static final String TRAY_ICON = "/img/tray_icon.png";
private static final String TRAY_ICON_UNLOCKED = "/img/tray_icon_unlocked.png";
private final ResourceBundle resourceBundle;
private final VaultService vaultService;
@@ -61,12 +64,15 @@ public class TrayMenuBuilder {
v.displayNameProperty().addListener(this::vaultListChanged);
});
try (var image = getClass().getResourceAsStream(SystemUtils.IS_OS_MAC_OSX ? TRAY_ICON_MAC : TRAY_ICON)) {
trayMenu.showTrayIcon(image.readAllBytes(), this::showMainWindow, "Cryptomator");
try {
trayMenu.showTrayIcon(getAppropriateTrayIconImage(), this::showMainWindow, "Cryptomator");
trayMenu.onBeforeOpenMenu(() -> {
for (Vault vault : vaults) {
VaultListManager.redetermineVaultState(vault);
}
});
rebuildMenu();
initialized = true;
} catch (IOException e) {
throw new UncheckedIOException("Failed to load embedded resource", e);
} catch (TrayMenuException e) {
LOG.error("Adding tray icon failed", e);
}
@@ -78,6 +84,7 @@ public class TrayMenuBuilder {
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
assert Platform.isFxApplicationThread();
trayMenu.updateTrayIcon(getAppropriateTrayIconImage());
rebuildMenu();
}
@@ -148,4 +155,22 @@ public class TrayMenuBuilder {
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
private byte[] getAppropriateTrayIconImage() {
boolean isAnyVaultUnlocked = vaults.stream().anyMatch(Vault::isUnlocked);
String resourceName;
if (SystemUtils.IS_OS_MAC_OSX) {
resourceName = isAnyVaultUnlocked ? TRAY_ICON_UNLOCKED_MAC : TRAY_ICON_MAC;
} else {
resourceName = isAnyVaultUnlocked ? TRAY_ICON_UNLOCKED : TRAY_ICON;
}
try (var image = getClass().getResourceAsStream(resourceName)) {
assert image != null;
return image.readAllBytes();
} catch (IOException e) {
throw new UncheckedIOException("Failed to load tray icon image: " + resourceName, e);
}
}
}
@@ -1,11 +1,18 @@
package org.cryptomator.ui.unlock;
import org.cryptomator.common.mount.MountPointNotExistsException;
import org.cryptomator.common.mount.MountPointNotSupportedException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FormattedLabel;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
@UnlockScoped
@@ -13,11 +20,32 @@ public class UnlockInvalidMountPointController implements FxController {
private final Stage window;
private final Vault vault;
private final AtomicReference<Throwable> unlockException;
private final FxApplicationWindows appWindows;
private final ResourceBundle resourceBundle;
public FormattedLabel dialogDescription;
@Inject
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault) {
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow AtomicReference<Throwable> unlockException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
this.window = window;
this.vault = vault;
this.unlockException = unlockException;
this.appWindows = appWindows;
this.resourceBundle = resourceBundle;
}
@FXML
public void initialize() {
var e = unlockException.get();
String translationKey = "unlock.error.customPath.description.generic";
if (e instanceof MountPointNotSupportedException) {
translationKey = "unlock.error.customPath.description.notSupported";
} else if (e instanceof MountPointNotExistsException) {
translationKey = "unlock.error.customPath.description.notExists";
}
dialogDescription.setFormat(resourceBundle.getString(translationKey));
dialogDescription.setArg1(e.getMessage());
}
@FXML
@@ -25,4 +53,10 @@ public class UnlockInvalidMountPointController implements FxController {
window.close();
}
@FXML
public void closeAndOpenPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.VOLUME);
window.close();
}
}
@@ -23,6 +23,7 @@ import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
@Module(subcomponents = {KeyLoadingComponent.class})
abstract class UnlockModule {
@@ -57,6 +58,13 @@ abstract class UnlockModule {
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
}
@Provides
@UnlockWindow
@UnlockScoped
static AtomicReference<Throwable> unlockException() {
return new AtomicReference<>();
}
@Provides
@FxmlScene(FxmlFile.UNLOCK_SUCCESS)
@UnlockScoped
@@ -2,6 +2,7 @@ package org.cryptomator.ui.unlock;
import com.google.common.base.Throwables;
import dagger.Lazy;
import org.cryptomator.common.mount.IllegalMountPointException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.cryptolib.api.CryptoException;
@@ -20,6 +21,7 @@ import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
/**
* A multi-step task that consists of background activities as well as user interaction.
@@ -38,9 +40,10 @@ public class UnlockWorkflow extends Task<Boolean> {
private final Lazy<Scene> invalidMountPointScene;
private final FxApplicationWindows appWindows;
private final KeyLoadingStrategy keyLoadingStrategy;
private final AtomicReference<Throwable> unlockFailedException;
@Inject
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy) {
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow AtomicReference<Throwable> unlockFailedException) {
this.window = window;
this.vault = vault;
this.vaultService = vaultService;
@@ -48,6 +51,7 @@ public class UnlockWorkflow extends Task<Boolean> {
this.invalidMountPointScene = invalidMountPointScene;
this.appWindows = appWindows;
this.keyLoadingStrategy = keyLoadingStrategy;
this.unlockFailedException = unlockFailedException;
}
@Override
@@ -67,13 +71,15 @@ public class UnlockWorkflow extends Task<Boolean> {
} 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);
}
}
private void showInvalidMountPointScene() {
private void handleIllegalMountPointError(IllegalMountPointException impe) {
Platform.runLater(() -> {
unlockFailedException.set(impe);
window.setScene(invalidMountPointScene.get());
window.show();
});
@@ -107,7 +113,11 @@ public class UnlockWorkflow extends Task<Boolean> {
protected void failed() {
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
Throwable throwable = super.getException();
handleGenericError(throwable);
if(throwable instanceof IllegalMountPointException impe) {
handleIllegalMountPointError(impe);
} else {
handleGenericError(throwable);
}
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
}
@@ -1,11 +1,14 @@
package org.cryptomator.ui.vaultoptions;
import org.cryptomator.common.Environment;
import com.google.common.base.Strings;
import org.cryptomator.common.mount.ActualMountService;
import org.cryptomator.common.mount.WindowsDriveLetters;
import org.cryptomator.common.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 javax.inject.Inject;
import javafx.beans.value.ObservableValue;
@@ -30,16 +33,17 @@ import java.util.Set;
public class MountOptionsController implements FxController {
private final Stage window;
private final Vault vault;
private final VaultSettings vaultSettings;
private final WindowsDriveLetters windowsDriveLetters;
private final ResourceBundle resourceBundle;
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<Path> driveLetter;
private final ObservableValue<String> directoryPath;
private final FxApplicationWindows applicationWindows;
//-- FXML objects --
@@ -54,59 +58,75 @@ public class MountOptionsController implements FxController {
public ChoiceBox<Path> driveLetterSelection;
@Inject
MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObservableValue<MountService> mountService, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, Environment environment) {
MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObservableValue<ActualMountService> mountService, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, FxApplicationWindows applicationWindows) {
this.window = window;
this.vault = vault;
this.vaultSettings = vault.getVaultSettings();
this.windowsDriveLetters = windowsDriveLetters;
this.resourceBundle = resourceBundle;
this.mountpointDirSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
this.mountpointDriveLetterSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.mountFlagsSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
this.readOnlySupported = mountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
this.driveLetter = vault.getVaultSettings().mountPoint().map(p -> isDriveLetter(p) ? p : null);
this.defaultMountFlags = mountService.map(as -> {
if (as.service().hasCapability(MountCapability.MOUNT_FLAGS)) {
return as.service().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;
}
@FXML
public void initialize() {
// readonly:
readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode());
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode());
// custom mount flags:
mountFlagsField.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags());
customMountFlagsCheckbox.setSelected(!Strings.isNullOrEmpty(vaultSettings.mountFlags().getValue()));
toggleUseCustomMountFlags();
//driveLetter choice box
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAll());
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
driveLetterSelection.setOnShowing(event -> driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle))); //TODO: does this work?
//mountPoint toggle group
var mountPoint = vault.getVaultSettings().getMountPoint();
var mountPoint = vaultSettings.getMountPoint();
if (mountPoint == null) {
//prepare and select auto
mountPointToggleGroup.selectToggle(mountPointAutoBtn);
} else if (mountPoint.getParent() == null && isDriveLetter(mountPoint)) {
//prepare and select drive letter
mountPointToggleGroup.selectToggle(mountPointDriveLetterBtn);
} else if (driveLetterSelection.getValue() == null) {
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint());
} else {
//prepare and select dir
mountPointToggleGroup.selectToggle(mountPointDirBtn);
}
mountPointToggleGroup.selectedToggleProperty().addListener(this::selectedToggleChanged);
}
@FXML
public void openVolumePreferences() {
applicationWindows.showPreferencesWindow(SelectedPreferencesTab.VOLUME);
}
@FXML
public void toggleUseCustomMountFlags() {
if (customMountFlagsCheckbox.isSelected()) {
readOnlyCheckbox.setSelected(false); // to prevent invalid states
mountFlagsField.textProperty().unbind();
vault.setCustomMountFlags(vault.defaultMountFlagsProperty().getValue());
mountFlagsField.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags());
var mountFlags = vaultSettings.mountFlags().get();
if (mountFlags == null || mountFlags.isBlank()) {
vaultSettings.mountFlags().set(defaultMountFlags.getValue());
}
mountFlagsField.textProperty().bindBidirectional(vaultSettings.mountFlags());
} else {
mountFlagsField.textProperty().unbindBidirectional(vault.getVaultSettings().mountFlags());
vault.setCustomMountFlags(null);
mountFlagsField.textProperty().bind(vault.defaultMountFlagsProperty());
mountFlagsField.textProperty().unbindBidirectional(vaultSettings.mountFlags());
vaultSettings.mountFlags().set(null);
mountFlagsField.textProperty().bind(defaultMountFlags);
}
}
@@ -114,7 +134,7 @@ public class MountOptionsController implements FxController {
public void chooseCustomMountPoint() {
try {
Path chosenPath = chooseCustomMountPointInternal();
vault.getVaultSettings().mountPoint().set(chosenPath);
vaultSettings.mountPoint().set(chosenPath);
} catch (NoDirSelectedException e) {
//no-op
}
@@ -131,7 +151,7 @@ public class MountOptionsController implements FxController {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
try {
var mp = vault.getVaultSettings().mountPoint().get();
var mp = vaultSettings.mountPoint().get();
var initialDir = mp != null && !isDriveLetter(mp) ? mp : Path.of(System.getProperty("user.home"));
if (Files.isDirectory(initialDir)) {
@@ -149,27 +169,30 @@ public class MountOptionsController implements FxController {
}
private void selectedToggleChanged(ObservableValue<? extends Toggle> observable, Toggle oldToggle, Toggle newToggle) {
Path mountPointToBe = null;
try {
//Remark: the mountpoint corresponding to the newToggle must be null, otherwise it would not be new!
if (mountPointDriveLetterBtn.equals(newToggle)) {
mountPointToBe = driveLetterSelection.getItems().get(0);
} else if (mountPointDirBtn.equals(newToggle)) {
mountPointToBe = chooseCustomMountPointInternal();
}
vault.getVaultSettings().mountPoint().set(mountPointToBe);
} catch (NoDirSelectedException e) {
if (!mountPointDirBtn.equals(oldToggle)) {
mountPointToggleGroup.selectToggle(oldToggle);
//Remark: the mountpoint corresponding to the newToggle must be null, otherwise it would not be new!
driveLetterSelection.valueProperty().unbindBidirectional(vaultSettings.mountPoint());
if (mountPointDriveLetterBtn.equals(newToggle)) {
vaultSettings.mountPoint().set(windowsDriveLetters.getFirstDesiredAvailable().orElse(windowsDriveLetters.getAll().stream().findAny().get()));
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint());
} else if (mountPointDirBtn.equals(newToggle)) {
try {
vaultSettings.mountPoint().set(chooseCustomMountPointInternal());
} catch (NoDirSelectedException e) {
if (oldToggle != null && !mountPointDirBtn.equals(oldToggle)) {
mountPointToggleGroup.selectToggle(oldToggle);
} else {
mountPointToggleGroup.selectToggle(mountPointAutoBtn);
}
}
} else {
vaultSettings.mountPoint().set(null);
}
}
private boolean isDriveLetter(Path mountPoint) {
if (mountPoint != null) {
var s = mountPoint.toString();
return s.length() == 3 && mountPoint.toString().endsWith(":\\");
return s.length() == 3 && s.endsWith(":\\");
}
return false;
}
@@ -237,21 +260,13 @@ public class MountOptionsController implements FxController {
}
public ObservableValue<Boolean> readOnlySupportedProperty() {
return mountpointDriveLetterSupported;
return readOnlySupported;
}
public boolean isReadOnlySupported() {
return readOnlySupported.getValue();
}
public ObservableValue<Path> driveLetterProperty() {
return driveLetter;
}
public Path getDriveLetter() {
return driveLetter.getValue();
}
public ObservableValue<String> directoryPathProperty() {
return directoryPath;
}
@@ -259,5 +274,4 @@ public class MountOptionsController implements FxController {
public String getDirectoryPath() {
return directoryPath.getValue();
}
}
+48 -10
View File
@@ -204,16 +204,6 @@
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
}
.main-window .drag-n-drop-indicator {
-fx-border-color: SECONDARY;
-fx-border-width: 3px;
}
.main-window .drag-n-drop-indicator .drag-n-drop-header {
-fx-background-color: SECONDARY;
-fx-padding: 3px;
}
/*******************************************************************************
* *
* TabPane *
@@ -884,3 +874,51 @@
-fx-fill: linear-gradient(to bottom, PRIMARY, transparent);
-fx-stroke: transparent;
}
/*******************************************************************************
* *
* Drag and Drop *
* *
******************************************************************************/
.drag-n-drop-border {
-fx-border-color: SECONDARY;
-fx-border-width: 3px;
}
.button.drag-n-drop {
-fx-background-color: CONTROL_BG_NORMAL;
-fx-background-insets: 0;
-fx-padding: 1.4em 1em 1.4em 1em;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-font-size: 0.8em;
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-radius: 4px;
-fx-border-style: dashed inside;
-fx-border-width: 1px;
}
.button.drag-n-drop:focused {
-fx-border-color: CONTROL_BORDER_FOCUSED;
}
.button.drag-n-drop:armed {
-fx-background-color: CONTROL_BG_ARMED;
}
.button.drag-n-drop.active {
-fx-border-color: SECONDARY;
-fx-border-style: solid inside;
-fx-border-width: 1px;
}
/*******************************************************************************
* *
* Separator *
* *
******************************************************************************/
.separator {
-fx-padding: 0.5px;
-fx-background-color: CONTROL_BORDER_NORMAL;
}
+48 -10
View File
@@ -203,16 +203,6 @@
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
}
.main-window .drag-n-drop-indicator {
-fx-border-color: SECONDARY;
-fx-border-width: 3px;
}
.main-window .drag-n-drop-indicator .drag-n-drop-header {
-fx-background-color: SECONDARY;
-fx-padding: 3px;
}
/*******************************************************************************
* *
* TabPane *
@@ -883,3 +873,51 @@
-fx-fill: linear-gradient(to bottom, PRIMARY, transparent);
-fx-stroke: transparent;
}
/*******************************************************************************
* *
* Drag and Drop *
* *
******************************************************************************/
.drag-n-drop-border {
-fx-border-color: SECONDARY;
-fx-border-width: 3px;
}
.button.drag-n-drop {
-fx-background-color: CONTROL_BG_NORMAL;
-fx-background-insets: 0;
-fx-padding: 1.4em 1em 1.4em 1em;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-font-size: 0.8em;
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-radius: 4px;
-fx-border-style: dashed inside;
-fx-border-width: 1px;
}
.button.drag-n-drop:focused {
-fx-border-color: CONTROL_BORDER_FOCUSED;
}
.button.drag-n-drop:armed {
-fx-background-color: CONTROL_BG_ARMED;
}
.button.drag-n-drop.active {
-fx-border-color: SECONDARY;
-fx-border-style: solid inside;
-fx-border-width: 1px;
}
/*******************************************************************************
* *
* Separator *
* *
******************************************************************************/
.separator {
-fx-padding: 0.5px;
-fx-background-color: CONTROL_BORDER_NORMAL;
}
@@ -5,25 +5,59 @@
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.health.CheckStateIconView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.MenuItem?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.health.CheckDetailController"
spacing="6">
<Label fx:id="detailHeader" styleClass="label-extra-large" text="${controller.checkName}" contentDisplay="LEFT">
<graphic>
<HBox alignment="CENTER" minWidth="25" maxWidth="25">
<CheckStateIconView fx:id="checkStateIconView" check="${controller.check}" glyphSize="20"/>
</HBox>
</graphic>
</Label>
spacing="12">
<HBox alignment="CENTER" >
<VBox spacing="12">
<Label fx:id="detailHeader" styleClass="label-extra-large" text="${controller.checkName}" contentDisplay="RIGHT">
<graphic>
<HBox alignment="CENTER" minWidth="25" maxWidth="25">
<CheckStateIconView check="${controller.check}" glyphSize="20"/>
</HBox>
</graphic>
</Label>
<Label text="%health.check.detail.checkRunning" visible="${controller.checkRunning}" managed="${controller.checkRunning}"/>
<Label text="%health.check.detail.checkScheduled" visible="${controller.checkScheduled}" managed="${controller.checkScheduled}"/>
<Label text="%health.check.detail.checkSkipped" visible="${controller.checkSkipped}" managed="${controller.checkSkipped}"/>
<Label text="%health.check.detail.checkCancelled" visible="${controller.checkCancelled}" managed="${controller.checkCancelled}"/>
<Label text="%health.check.detail.checkFailed" visible="${controller.checkFailed}" managed="${controller.checkFailed}"/>
<Label text="%health.check.detail.checkFinished" visible="${controller.checkSucceeded &amp;&amp; !controller.warnOrCritsExist}" managed="${controller.checkSucceeded &amp;&amp; !controller.warnOrCritsExist}"/>
<Label text="%health.check.detail.checkFinishedAndFound" visible="${controller.checkSucceeded &amp;&amp; controller.warnOrCritsExist}" managed="${controller.checkSucceeded &amp;&amp; controller.warnOrCritsExist}"/>
</VBox>
<Region HBox.hgrow="ALWAYS"/>
<Button text="%health.check.detail.fixAllSpecificBtn" contentDisplay="RIGHT" graphicTextGap="3" visible="${controller.checkFinished}" managed="${controller.checkFinished}" disable="${! controller.fixAllInfoResultsPossible}" onAction="#fixAllInfoResults">
<graphic>
<FontAwesome5IconView glyph="INFO_CIRCLE" glyphSize="12" styleClass="glyph-icon-muted"/>
</graphic>
</Button>
<Label text="%health.check.detail.checkRunning" visible="${controller.checkRunning}" managed="${controller.checkRunning}"/>
<Label text="%health.check.detail.checkScheduled" visible="${controller.checkScheduled}" managed="${controller.checkScheduled}"/>
<Label text="%health.check.detail.checkSkipped" visible="${controller.checkSkipped}" managed="${controller.checkSkipped}"/>
<Label text="%health.check.detail.checkCancelled" visible="${controller.checkCancelled}" managed="${controller.checkCancelled}"/>
<Label text="%health.check.detail.checkFailed" visible="${controller.checkFailed}" managed="${controller.checkFailed}"/>
<Label text="%health.check.detail.checkFinished" visible="${controller.checkSucceeded &amp;&amp; !controller.warnOrCritsExist}" managed="${controller.checkSucceeded &amp;&amp; !controller.warnOrCritsExist}"/>
<Label text="%health.check.detail.checkFinishedAndFound" visible="${controller.checkSucceeded &amp;&amp; controller.warnOrCritsExist}" managed="${controller.checkSucceeded &amp;&amp; controller.warnOrCritsExist}"/>
<ListView fx:id="resultsListView" VBox.vgrow="ALWAYS" visible="${!controller.checkSkipped}" fixedCellSize="25"/>
</HBox>
<VBox spacing="6" VBox.vgrow="ALWAYS">
<HBox alignment="CENTER_LEFT" spacing="6">
<Label fx:id="filterLbl" text="%health.check.detail.listFilters.label">
<graphic>
<FontAwesome5IconView glyph="FUNNEL" glyphSize="${filterLbl.height}" styleClass="glyph-icon-muted"/>
</graphic>
</Label>
<ChoiceBox fx:id="severityChoiceBox" />
<ChoiceBox fx:id="fixStateChoiceBox" />
</HBox>
<ListView fx:id="resultsListView" VBox.vgrow="ALWAYS" visible="${!controller.checkSkipped}" fixedCellSize="25">
<contextMenu>
<ContextMenu>
<items>
<MenuItem text="%generic.button.copy" onAction="#copyResultDetails"/>
</items>
</ContextMenu>
</contextMenu>
</ListView>
</VBox>
</VBox>
@@ -12,7 +12,7 @@
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.health.CheckListController"
prefWidth="600"
prefWidth="650"
prefHeight="400"
spacing="12">
<padding>
+1 -1
View File
@@ -16,7 +16,7 @@
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.health.StartController"
prefWidth="600"
prefWidth="650"
prefHeight="400"
spacing="12">
<padding>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.hub.NoKeychainController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%hub.noKeychain.message" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<FormattedLabel format="%hub.noKeychain.description" arg1="%preferences.general.keychainBackend" wrapText="true" textAlignment="LEFT"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="false" cancelButton="true" onAction="#cancel"/>
<Button text="%hub.noKeychain.openBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#openPreferences"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>
+4 -22
View File
@@ -1,10 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<StackPane xmlns:fx="http://javafx.com/fxml"
@@ -14,24 +10,10 @@
styleClass="main-window">
<VBox minWidth="650">
<fx:include source="main_window_title.fxml" VBox.vgrow="NEVER"/>
<StackPane VBox.vgrow="ALWAYS">
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL">
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
</SplitPane>
<VBox styleClass="drag-n-drop-indicator" visible="${controller.draggingOver}" alignment="TOP_CENTER">
<HBox visible="${!controller.draggingVaultOver}" managed="${!controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
<Label text="%main.dropZone.unknownDragboardContent"/>
</HBox>
<HBox visible="${controller.draggingVaultOver}" managed="${controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
<FontAwesome5IconView glyph="CHECK"/>
<Label text="%main.dropZone.dropVault"/>
</HBox>
<Region VBox.vgrow="ALWAYS"/>
</VBox>
</StackPane>
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL" VBox.vgrow="ALWAYS">
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
</SplitPane>
</VBox>
<fx:include source="main_window_resize.fxml"/>
</StackPane>
+46 -8
View File
@@ -6,16 +6,20 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.migration.MigrationStartController"
minWidth="400"
maxWidth="400"
minHeight="145"
prefWidth="580"
prefHeight="350"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
@@ -23,13 +27,47 @@
<children>
<HBox spacing="12" alignment="CENTER_LEFT" VBox.vgrow="ALWAYS">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE_IMPORT" glyphSize="24"/>
<padding>
<Insets left="12"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="48"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE_IMPORT" glyphSize="48"/>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
<FormattedLabel format="%migration.start.prompt" arg1="${controller.vault.displayName}" wrapText="true"/>
<CheckBox fx:id="confirmSyncDone" text="%migration.start.confirm"/>
<VBox HBox.hgrow="ALWAYS" alignment="CENTER">
<padding>
<Insets top="0" right="12" bottom="0" left="12"/>
</padding>
<Label text="%migration.start.header" styleClass="label-extra-large"/>
<Region minHeight="15"/>
<VBox>
<FormattedLabel format="%migration.start.text" arg1="${controller.vault.displayName}" wrapText="true"/>
<GridPane alignment="CENTER_LEFT" >
<padding>
<Insets left="6"/>
</padding>
<columnConstraints>
<ColumnConstraints minWidth="20" halignment="LEFT"/>
<ColumnConstraints fillWidth="true"/>
</columnConstraints>
<rowConstraints>
<RowConstraints valignment="TOP"/>
<RowConstraints valignment="TOP"/>
<RowConstraints valignment="TOP"/>
<RowConstraints valignment="TOP"/>
</rowConstraints>
<Label text="1." GridPane.rowIndex="0" GridPane.columnIndex="0" />
<Label text="%migration.start.remarkUndone" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="1" />
<Label text="2." GridPane.rowIndex="1" GridPane.columnIndex="0" />
<Label text="%migration.start.remarkVersions" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="1" />
<Label text="3." GridPane.rowIndex="2" GridPane.columnIndex="0" />
<Label text="%migration.start.remarkCanRun" wrapText="true" GridPane.rowIndex="2" GridPane.columnIndex="1" />
<Label text="4." GridPane.rowIndex="3" GridPane.columnIndex="0" />
<Label text="%migration.start.remarkSynced" wrapText="true" GridPane.rowIndex="3" GridPane.columnIndex="1" />
</GridPane>
<Region minHeight="15"/>
<CheckBox fx:id="confirmSyncDone" text="%migration.start.confirm"/>
</VBox>
</VBox>
</HBox>
@@ -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 2022 Skymatic GmbH"/>
<Label text="© 2016 2023 Skymatic GmbH"/>
</VBox>
</HBox>
@@ -1,10 +1,12 @@
<?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.ChoiceBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
@@ -20,10 +22,41 @@
<ChoiceBox fx:id="volumeTypeChoiceBox"/>
</HBox>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}">
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}" managed="${controller.loopbackPortSupported}">
<Label text="%preferences.volume.tcp.port"/>
<NumericTextField fx:id="loopbackPortField"/>
<Button text="%generic.button.apply" fx:id="loopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox>
<Separator orientation="HORIZONTAL"/>
<Label text="%preferences.volume.supportedFeatures"/>
<VBox spacing="12">
<Label text="%preferences.volume.feature.mountAuto">
<graphic>
<FontAwesome5IconView glyph="CHECK"/>
</graphic>
</Label>
<Label text="%preferences.volume.feature.mountToDir" visible="${controller.mountToDirSupported}" managed="${controller.mountToDirSupported}" contentDisplay="LEFT">
<graphic>
<FontAwesome5IconView glyph="CHECK"/>
</graphic>
</Label>
<Label text="%preferences.volume.feature.mountToDriveLetter" visible="${controller.mountToDriveLetterSupported}" managed="${controller.mountToDriveLetterSupported}">
<graphic>
<FontAwesome5IconView glyph="CHECK"/>
</graphic>
</Label>
<Label text="%preferences.volume.feature.mountFlags" visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
<graphic>
<FontAwesome5IconView glyph="CHECK"/>
</graphic>
</Label>
<Label text="%preferences.volume.feature.readOnly" visible="${controller.readonlySupported}" managed="${controller.readonlySupported}">
<graphic>
<FontAwesome5IconView glyph="CHECK"/>
</graphic>
</Label>
</VBox>
</children>
</VBox>
+30 -12
View File
@@ -13,6 +13,7 @@
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Arc?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.stats.VaultStatisticsController"
@@ -22,18 +23,17 @@
</padding>
<!-- Caching -->
<VBox prefWidth="200" prefHeight="200">
<StackPane>
<Group>
<Arc styleClass="cache-arc-background" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="-270"/>
<Arc styleClass="cache-arc-foreground" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="${controller.cacheHitDegrees}"/>
</Group>
<VBox StackPane.alignment="CENTER" alignment="CENTER">
<FormattedLabel styleClass="label-extra-large" format="\%1.0f %%" arg1="${controller.cacheHitPercentage}"/>
<Label text="%stats.cacheHitRate"/>
</VBox>
</StackPane>
</VBox>
<StackPane prefWidth="200" prefHeight="200">
<Group>
<Circle centerX="100" centerY="100" radius="100" opacity="0"/> <!-- for alignment. visible=false does not yield the desired result -->
<Arc styleClass="cache-arc-background" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="-270"/>
<Arc styleClass="cache-arc-foreground" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="${controller.cacheHitDegrees}"/>
</Group>
<VBox StackPane.alignment="CENTER" alignment="CENTER">
<FormattedLabel styleClass="label-extra-large" format="\%1.0f %%" arg1="${controller.cacheHitPercentage}"/>
<Label text="%stats.cacheHitRate"/>
</VBox>
</StackPane>
<!-- Read -->
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
@@ -72,4 +72,22 @@
<DataLabel byteFormat="%stats.encr.total.data.none" kibFormat="%stats.encr.total.data.kib" mibFormat="%stats.encr.total.data.mib" gibFormat="%stats.encr.total.data.gib" dataInBytes="${controller.totalBytesEncrypted}"/>
<FormattedLabel format="%stats.write.accessCount" arg1="${controller.filesWritten}"/>
</VBox>
<!-- Access -->
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
<FormattedLabel styleClass="label-large" format="%stats.access.current" arg1="${controller.filesAccessed}"/>
<AreaChart fx:id="accessChart" styleClass="io-stats" createSymbols="false" animated="false">
<xAxis>
<NumberAxis fx:id="accessChartXAxis" styleClass="io-stats" autoRanging="false" forceZeroInRange="false" side="BOTTOM"/>
</xAxis>
<yAxis>
<NumberAxis fx:id="accessChartYAxis" styleClass="io-stats" autoRanging="true" forceZeroInRange="true" side="LEFT" tickUnit="Infinity"/>
</yAxis>
<cursor>
<Cursor fx:constant="DEFAULT"/>
</cursor>
</AreaChart>
<FormattedLabel format="%stats.access.total" arg1="${controller.totalFilesAccessed}"/>
<Label/> <!-- to align with other graphs -->
<Label/> <!-- to align with other graphs -->
</VBox>
</HBox>
@@ -34,16 +34,19 @@
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%unlock.error.message" wrapText="true" textAlignment="LEFT">
<Label styleClass="label-large" text="%unlock.error.customPath.message" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<FormattedLabel fx:id="dialogDescription" wrapText="true" textAlignment="LEFT"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%hub.noKeychain.openBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#closeAndOpenPreferences"/>
</buttons>
</ButtonBar>
</VBox>
@@ -2,10 +2,11 @@
<?import org.cryptomator.ui.controls.ThroughputLabel?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.geometry.Insets?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnlockedController"
@@ -18,6 +19,7 @@
<FontAwesome5IconView glyph="HDD" glyphSize="24"/>
<VBox spacing="4" alignment="CENTER_LEFT">
<Label text="%main.vaultDetail.revealBtn"/>
<Label styleClass="label-extra-small" text="${controller.mountPoint}" textOverrun="CENTER_ELLIPSIS"/>
</VBox>
</HBox>
</graphic>
@@ -27,8 +29,8 @@
<HBox spacing="12" alignment="CENTER">
<FontAwesome5IconView glyph="LINK" glyphSize="24"/>
<VBox spacing="4" alignment="CENTER_LEFT">
<Label text="%generic.button.copy"/> <!-- TODO -->
<Label styleClass="label-extra-small" text="${controller.mountUri}" textOverrun="CENTER_ELLIPSIS"/>
<Label text="%main.vaultDetail.copyUri"/>
<Label styleClass="label-extra-small" text="${controller.mountPoint}" textOverrun="CENTER_ELLIPSIS"/>
</VBox>
</HBox>
</graphic>
@@ -41,7 +43,28 @@
<Region VBox.vgrow="ALWAYS"/>
<HBox alignment="BOTTOM_RIGHT">
<HBox alignment="BOTTOM_CENTER">
<HBox visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
<padding>
<Insets topRightBottomLeft="0"/>
</padding>
<Button fx:id="dropZone" styleClass="drag-n-drop" text="%main.vaultDetail.locateEncryptedFileBtn" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${!controller.ciphertextPathsCopied}" managed="${!controller.ciphertextPathsCopied}">
<graphic>
<FontAwesome5IconView glyph="FILE_DOWNLOAD" glyphSize="15"/>
</graphic>
<tooltip>
<Tooltip text="%main.vaultDetail.locateEncryptedFileBtn.tooltip"/>
</tooltip>
</Button>
<Button styleClass="drag-n-drop" text="%main.vaultDetail.encryptedPathsCopied" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${controller.ciphertextPathsCopied}" managed="${controller.ciphertextPathsCopied}">
<graphic>
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
</graphic>
</Button>
</HBox>
<Region HBox.hgrow="ALWAYS"/>
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM">
<graphic>
<VBox spacing="6">
+26 -22
View File
@@ -8,25 +8,29 @@
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Arc?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.VaultListController"
minWidth="206">
<StackPane VBox.vgrow="ALWAYS">
<ListView fx:id="vaultList" editable="true" fixedCellSize="60">
<contextMenu>
<fx:include source="vault_list_contextmenu.fxml"/>
</contextMenu>
</ListView>
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
<Region VBox.vgrow="ALWAYS"/>
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" wrapText="true"/>
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
</VBox>
</StackPane>
<Button styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" onAction="#didClickAddVault" alignment="BASELINE_CENTER" maxWidth="Infinity">
<graphic>
<FontAwesome5IconView glyph="PLUS"/>
</graphic>
</Button>
</VBox>
<StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="root"
fx:controller="org.cryptomator.ui.mainwindow.VaultListController"
minWidth="206">
<VBox>
<StackPane VBox.vgrow="ALWAYS">
<ListView fx:id="vaultList" editable="true" fixedCellSize="60">
<contextMenu>
<fx:include source="vault_list_contextmenu.fxml"/>
</contextMenu>
</ListView>
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
<Region VBox.vgrow="ALWAYS"/>
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" wrapText="true"/>
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
</VBox>
</StackPane>
<Button styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" onAction="#didClickAddVault" alignment="BASELINE_CENTER" maxWidth="Infinity">
<graphic>
<FontAwesome5IconView glyph="PLUS"/>
</graphic>
</Button>
</VBox>
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
</StackPane>
@@ -5,12 +5,15 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.text.TextFlow?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
@@ -22,7 +25,11 @@
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Label text="Options depend on the selected volume provider in the general preferences"/>
<TextFlow>
<Text text="%vaultOptions.mount.info"/>
<Text text=" "/>
<Hyperlink styleClass="hyperlink-underline" text="%vaultOptions.mount.linkToPreferences" onAction="#openVolumePreferences" wrapText="true"/>
</TextFlow>
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlySupported}" managed="${controller.readOnlySupported}"/>
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
@@ -44,22 +51,24 @@
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}" value="${controller.driveLetter}"/>
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}"/>
</HBox>
<HBox spacing="6" alignment="CENTER_LEFT" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
</graphic>
</Button>
</HBox>
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false" >
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
<VBox spacing="6" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
<HBox spacing="6" alignment="CENTER_LEFT">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
</graphic>
</Button>
</HBox>
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false">
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
</VBox>
</children>
</VBox>
+52 -11
View File
@@ -124,12 +124,14 @@ unlock.success.description=Content in vault "%s" is now accessible over its moun
unlock.success.rememberChoice=Remember my choice, don't ask again
unlock.success.revealBtn=Reveal Drive
## Failure
unlock.error.message=Unable to unlock vault
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Mount point "%s" is not a directory, not empty or does not exist.
unlock.error.invalidMountPoint.existing=Mount point "%s" already exists or parent folder is missing.
unlock.error.invalidMountPoint.driveLetterOccupied=Drive Letter "%s" is already in use.
unlock.error.customPath.message=Unable to mount vault to custom path
unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point.
unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options.
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %s
## Hub
hub.noKeychain.message=Unable to access device key
hub.noKeychain.description=In order to unlock Hub vaults, a device key is required, which is secured using a keychain. To proceed, enable “%s” and select a keychain in the preferences.
hub.noKeychain.openBtn=Open Preferences
### Waiting
hub.auth.message=Waiting for authentication…
hub.auth.description=You should automatically be redirected to the login page.
@@ -170,8 +172,13 @@ lock.fail.description=Vault "%s" could not be locked. Ensure unsaved work is sav
# Migration
migration.title=Upgrade Vault
## Start
migration.start.prompt=Your vault "%s" needs to be updated to a newer format. Before proceeding, make sure there is no pending synchronization affecting this vault.
migration.start.confirm=Yes, my vault is fully synced
migration.start.header=Upgrade Vault
migration.start.text=In order to open your vault "%s" in this new version of Cryptomator, the vault needs to be upgraded to a newer format. Before doing this, you should know the following:
migration.start.remarkUndone=This upgrade cannot be undone.
migration.start.remarkVersions=Older versions of Cryptomator will not be able to open the upgraded vault.
migration.start.remarkCanRun=You must be sure that every device from which you access the vault can run this version of Cryptomator.
migration.start.remarkSynced=You must be sure that your vault is fully synced on this device, and on your other devices, before upgrading it.
migration.start.confirm=I have read and understood the above information
## Run
migration.run.enterPassword=Enter the password for "%s"
migration.run.startMigrationBtn=Migrate Vault
@@ -219,7 +226,25 @@ health.check.detail.checkFinished=The check finished successfully.
health.check.detail.checkFinishedAndFound=The check finished running. Please review the results.
health.check.detail.checkFailed=The check exited due to an error.
health.check.detail.checkCancelled=The check was cancelled.
health.check.detail.listFilters.label=Filter
health.check.detail.fixAllSpecificBtn=Fix all of type
health.check.exportBtn=Export Report
## Result view
health.result.severityFilter.all=Severity - All
health.result.severityFilter.good=Good
health.result.severityFilter.info=Info
health.result.severityFilter.warn=Warning
health.result.severityFilter.crit=Critical
health.result.severityTip.good=Severity: Good\nNormal vault structure.
health.result.severityTip.info=Severity: Info\nVault structure intact, fix suggested.
health.result.severityTip.warn=Severity: Warning\nVault structure corrupted, fix highly advised.
health.result.severityTip.crit=Severity: Critical\nVault structure corrupted, data loss determined.
health.result.fixStateFilter.all=Fix state - All
health.result.fixStateFilter.fixable=Fixable
health.result.fixStateFilter.notFixable=Not fixable
health.result.fixStateFilter.fixing=Fixing…
health.result.fixStateFilter.fixed=Fixed
health.result.fixStateFilter.fixFailed=Fix failed
## Fix Application
health.fix.fixBtn=Fix
health.fix.successTip=Fix successful
@@ -251,8 +276,15 @@ 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=Volume Type (requires restart)
preferences.volume.type.automatic=Automatic
preferences.volume.tcp.port=TCP Port
preferences.volume.supportedFeatures=The chosen volume type supports the following features:
preferences.volume.feature.mountAuto=Automatic mount point selection
preferences.volume.feature.mountToDir=Custom directory as mount point
preferences.volume.feature.mountToDriveLetter=Drive letter as mount point
preferences.volume.feature.mountFlags=Custom mount options
preferences.volume.feature.readOnly=Read-only mount
## Updates
preferences.updates=Updates
preferences.updates.currentVersion=Current Version: %s
@@ -300,15 +332,17 @@ stats.encr.total.data.mib=Data encrypted: %.1f MiB
stats.encr.total.data.gib=Data encrypted: %.1f GiB
stats.write.accessCount=Total writes: %d
## Accesses
stats.access.current=Access: %d
stats.access.total=Total accesses: %d
# Main Window
main.closeBtn.tooltip=Close
main.minimizeBtn.tooltip=Minimize
main.preferencesBtn.tooltip=Preferences
main.debugModeEnabled.tooltip=Debug mode is enabled
main.supporterCertificateMissing.tooltip=Please consider donating
## Drag 'n' Drop
main.dropZone.dropVault=Add this vault
main.dropZone.unknownDragboardContent=If you want to add a vault, drag it to this window
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
main.vaultlist.contextMenu.remove=Remove…
@@ -331,6 +365,7 @@ main.vaultDetail.passwordSavedInKeychain=Password saved
main.vaultDetail.unlockedStatus=UNLOCKED
main.vaultDetail.accessLocation=Your vault's contents are accessible here:
main.vaultDetail.revealBtn=Reveal Drive
main.vaultDetail.copyUri=Copy URI
main.vaultDetail.lockBtn=Lock
main.vaultDetail.bytesPerSecondRead=Read:
main.vaultDetail.bytesPerSecondWritten=Write:
@@ -338,6 +373,10 @@ main.vaultDetail.throughput.idle=idle
main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Vault Statistics
main.vaultDetail.locateEncryptedFileBtn=Locate Encrypted File
main.vaultDetail.locateEncryptedFileBtn.tooltip=Choose a file from your vault to locate its encrypted counterpart
main.vaultDetail.encryptedPathsCopied=Paths Copied to Clipboard!
main.vaultDetail.filePickerTitle=Select File Inside Vault
### Missing
main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
main.vaultDetail.missing.recheck=Recheck
@@ -376,6 +415,8 @@ 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.readonly=Read-only
vaultOptions.mount.customMountFlags=Custom mount flags
vaultOptions.mount.winDriveLetterOccupied=occupied
+15 -14
View File
@@ -103,10 +103,6 @@ unlock.success.description=تم فتح "%s" بنجاح! يمكنك الآن ال
unlock.success.rememberChoice=تذكر اختياري ولا تظهر هذا مرة أخرى
unlock.success.revealBtn=اظهار القرص
## Failure
unlock.error.message=غير قادر على فتح الخزنة
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=نقطة التحميل ليست مجلد فارغ أو غير موجودة: %s
unlock.error.invalidMountPoint.existing=نقطة/مجلد التحميل موجود بالفعل أو المجلد الأصل مفقود: %s
## Hub
### Waiting
### Receive Key
@@ -129,8 +125,13 @@ lock.fail.description=فشل عملية قفل %s". تأكد من حفظ الع
# Migration
migration.title=ترقية الحافظة
## Start
migration.start.prompt=يجب تحديث المخزن الخاص بك "%s" إلى تنسيق أحدث. قبل المتابعة، يجب التأكد من عدم وجود مزامنة معلقة على هذا المخزن.
migration.start.confirm=نعم, محفظتي متزامنة بالكامل
migration.start.header=ترقية الخزنة
migration.start.text=من أجل فتح الخزنة الخاصة بك "%s" في هذا الإصدار الجديد من Cryptomator، يجب ترقية الخزنة إلى تنسيق جديد. وقبل القيام بذلك، ينبغي أن تعرف ما يلي:
migration.start.remarkUndone=هذه الترقية لا يمكن التراجع عنها.
migration.start.remarkVersions=لن تكون الإصدارات القديمة من Cryptomator قادرة على فتح الخزنة التي تم ترقيتها.
migration.start.remarkCanRun=يجب أن تكون متأكداً من أن كل جهاز تريد ان تستخدم الخزنة عليه يمكن تشغيل هذا الإصدار من Cryptomter.
migration.start.remarkSynced=يجب أن تكون متأكداً من أن خزنتك قد تمت مزامنتها بالكامل على هذا الجهاز، وعلى الأجهزة الأخرى الخاصة بك، قبل ترقيتها.
migration.start.confirm=لقد قرأت وفهمت المعلومات الواردة أعلاه
## Run
migration.run.enterPassword=أدخل كلمة المرور لـ "%s"
migration.run.startMigrationBtn=ترقية الحافظة
@@ -155,6 +156,7 @@ migration.impossible.moreInfo=لا يزال ممكناً فتح المخزن ب
## Start Failure
## Check Selection
## Detail view
## Result view
## Fix Application
# Preferences
@@ -171,9 +173,6 @@ preferences.interface.theme.dark=مظلم (أسود)
preferences.interface.theme.light=فاتح (أبيض)
## Volume
preferences.volume=القرص الإفتراضي
preferences.volume.type=‮نوع المجلد
preferences.volume.webdav.port=منفذ WebDav
preferences.volume.webdav.scheme=مخطط WebDAV
## Updates
preferences.updates=تحديثات
preferences.updates.currentVersion=الإصدار الحالي: %s
@@ -200,15 +199,15 @@ stats.decr.total.data.none=تم فك تشفير البيانات:-
## Write
stats.encr.total.data.none=البيانات المشفرة: -
## Accesses
# Main Window
main.closeBtn.tooltip=إغلاق
main.minimizeBtn.tooltip=تصغير
main.preferencesBtn.tooltip=تفضيلات
main.debugModeEnabled.tooltip=تم تمكين وضع التصحيح
main.supporterCertificateMissing.tooltip=يرجى النظر في تقديم تبرعات
## Drag 'n' Drop
main.dropZone.dropVault=أضف هذا المخزن
main.dropZone.unknownDragboardContent=إذا كنت ترغب في إضافة مخزن، قم بسحبه إلى هذه النافذة
## Vault List
main.vaultlist.emptyList.onboardingInstruction=انقر هنا لإضافة خزنة
main.vaultlist.contextMenu.remove=حذف…
@@ -238,6 +237,10 @@ 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=تم نسخ مسارات الملفات إلى الحافظة!
main.vaultDetail.filePickerTitle=إختر الملف من الخزنة
### Missing
main.vaultDetail.missing.info=لم يتمكن Cryptomator من العثور على خزنة في هذا المسار.
main.vaultDetail.missing.recheck=إعادة الفحص
@@ -276,9 +279,7 @@ 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=اختر مجلد فارغ
## Master Key
vaultOptions.masterkey=كلمة المرور
vaultOptions.masterkey.changePasswordBtn=تغيير كلمة المرور
+160 -9
View File
@@ -53,6 +53,12 @@ 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, ж або 수
addvaultwizard.new.validCharacters.numbers=Лічбы
addvaultwizard.new.validCharacters.dashes=Злучок (%s) або падкрэслінік (%s)
### Password
addvaultwizard.new.createVaultBtn=Стварыць скарбніцу
addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль?
@@ -76,26 +82,30 @@ addvault.new.readme.accessLocation.2=Гэта месца дуступу да т
addvault.new.readme.accessLocation.3=Любы дададзены сюды файл будзе зашыфраваны праз Cryptomator. Ты можаш працаваць тут як са звычайнаю тэчкаю альбо дыскам. Гэта толькі расшыфраваны агляд кантэнту, самі файлы заўжды захоўваюцца зашыфраванымі на тваім цвёрдым дыску.
addvault.new.readme.accessLocation.4=Ты можаш выдаліць гэты файл.
## Existing
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=Я разумею, што калі забудуся на пароль, то згублю доступ да сваех даных
changepassword.finalConfirmation=Я разумею, што калі забудуся на пароль, то згублю доступ да маіх даных
# Forget Password
forgetPassword.title=Забыўся на пароль
forgetPassword.message=Забыўся на захаваны пароль?
forgetPassword.confirmBtn=Забыўся на пароль
forgetPassword.title=Забыцца на пароль
forgetPassword.message=Ці забыцца на захаваны пароль?
forgetPassword.description=Гэта выдаліць захаваны пароль ад гэтай скарбніцы з твайго мэнэджара ключоў.
forgetPassword.confirmBtn=Забыцца на пароль
# Unlock
unlock.title=Разамкнуць "%s"
@@ -104,45 +114,66 @@ unlock.savePassword=Захоўваць пароль
unlock.unlockBtn=Адамкнуць
## Select
unlock.chooseMasterkey.message=Файл майстра ключоў ня знойдзены
unlock.chooseMasterkey.description=Cryptomator ня змог знайсці файл masterkey у тваёй скарбніцы "%s". Калі ласка, абяры самастойна файл з ключом.
unlock.chooseMasterkey.filePickerTitle=Абяры файл masterkey
unlock.chooseMasterkey.filePickerMimeDesc=Майстар ключоў Cryptomator
## Success
unlock.success.message=Паспяховае размыканне
unlock.success.description=Змест скарбніцы "%s" цяпер дасяжны праз ёйны мантажны пункт.
unlock.success.rememberChoice=Запомні мой выбар і больш не пытай
unlock.success.revealBtn=Паказаць дыск
## Failure
unlock.error.message=Немагчыма разамкнуць скарбніцу
### Invalid Mount Point
## Hub
### Waiting
hub.auth.message=Чаканне спраўджання…
hub.auth.description=Ты мусіш аўтаматычна перанакіравацца на старонку ўваходу.
hub.auth.loginLink=Не перанакіраваўся? Пстрыкні сюды, каб адчыніць старонку.
### Receive Key
hub.receive.message=Апрацоўка адказу…
hub.receive.description=Cryptomator атрымлівае ды апрацоўвае адказ ад Hub. Калі ласка, пачакай.
### Register Device
hub.register.message=Патрабуецца назва прылады
hub.register.description=Здаецца, што ты ў першы раз увайшла/-оў у Hub з гэтай прылады. Каб ідэнтыфікаваць яе для спраўджання доступу, табе трэба назваць гэтую прыладу.
hub.register.nameLabel=Назва прылады
hub.register.occupiedMsg=Назва ўжо ўжытая
hub.register.registerBtn=Пацвердзіць
### Registration Success
hub.registerSuccess.message=Прылада атрымала назву
hub.registerSuccess.description=Каб атрымаць доступ да скарбніцы, твая прылада мусіць быць спраўджанай уладальнікам скарбніцы.
### Registration Failed
hub.registerFailed.message=Памылка пры называнні прылады
hub.registerFailed.description=Падчас прысваення імя адбылася памылка. Па дэтальную інфармацыю звярніся да пратаколу праграмы.
### Unauthorized
hub.unauthorized.message=Адмова ў доступе
hub.unauthorized.description=Тваёй прыладзе ў дадзены момант не дазволена мець доступ да гэтай скрабніцы. Запытайся ўладальніка скрабніцы за дазволам.
### License Exceeded
hub.licenseExceeded.message=Ліцэнзія пратэрмінаваная
# 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.confirm=Так, мая скарбніца цалкам сінхранавана
migration.start.header=Абнавіць скарбніцу
## 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=Файлавая сістэма не дазваляе чытаць у ёй.
@@ -152,6 +183,8 @@ migration.impossible.heading=Не мажліва перанесці скарбн
# Health Check
## Start
health.title=Тэст на цэласнасць для "%s"
health.intro.header=Тэст на цэласнасць
health.intro.remarkBackup=Калі даныя пашкоджаныя, дапаможа толькі рэзервовая копія.
## Start Failure
health.fail.moreInfo=Падрабязней
@@ -160,18 +193,41 @@ health.checkList.selectAllButton=Вылучыць усе элементы
health.checkList.deselectAllButton=Адрабіць вылучэнне ўсіх элементаў
health.check.runBatchBtn=Выканаць вылучаныя элементы
## Detail view
health.check.detail.checkCancelled=Праверка была скасавана.
health.check.exportBtn=Экспартаваць справаздачу
## Result view
## 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=Iнтэрфэйс
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.automatic=Аўтаматычна
## Updates
preferences.updates=Абнаўленні
preferences.updates.currentVersion=Бягучая версія: %s
@@ -180,20 +236,52 @@ preferences.updates.checkNowBtn=Праверыць зараз
preferences.updates.updateAvailable=Даступна абнаўленне да версіі %s
## Contribution
preferences.contribute=Падтрымай нас
preferences.contribute.getCertificate=Яшчэ ня маеш такога? Даведайся, як атрымаць.
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
preferences.about=Пра нас
# Vault Statistics
stats.title=Статыстыкі для %s
## 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
# Main Window
main.closeBtn.tooltip=Зачыніць
main.minimizeBtn.tooltip=Згарнуць
main.preferencesBtn.tooltip=Налады
## Drag 'n' Drop
main.debugModeEnabled.tooltip=Функццыя дыягназавання выключана
main.supporterCertificateMissing.tooltip=Калі ласка, падумай пра ахвяраванне
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Пстрыкні тут, каб дадаць скарбніцу
main.vaultlist.contextMenu.remove=Выдаліць…
main.vaultlist.contextMenu.lock=Замкнуць
main.vaultlist.contextMenu.unlock=Адамкнуць…
@@ -207,37 +295,96 @@ main.vaultlist.addVaultBtn=Дадаць скарбніцу
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.lockBtn=Замкнуць
main.vaultDetail.bytesPerSecondRead=Чытанне:
main.vaultDetail.bytesPerSecondWritten=Пісанне:
main.vaultDetail.throughput.idle=бяздзейны
main.vaultDetail.throughput.kbps=%.1f КіБ/с
main.vaultDetail.throughput.mbps=%.1f МіБ/с
main.vaultDetail.stats=Статыстыка скарбніцы
### Missing
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.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=Запытацца
## Mount
vaultOptions.mount=Мантажаванне
vaultOptions.mount.readonly=Толькі для чытання
vaultOptions.mount.customMountFlags=Карыстальніцкія опцыі мантажавання
vaultOptions.mount.winDriveLetterOccupied=занята
vaultOptions.mount.mountPoint=Пункт мантажавання
vaultOptions.mount.mountPoint.auto=Аўтаматычны выбар пасуючага месцазнаходжання
vaultOptions.mount.mountPoint.driveLetter=Назначыць літару для дыску
vaultOptions.mount.mountPoint.directoryPickerButton=Абраць…
## Master Key
vaultOptions.masterkey=Пароль
vaultOptions.masterkey.changePasswordBtn=Змяніць пароль
vaultOptions.masterkey.forgetSavedPasswordBtn=Забыцца на захаваны пароль
vaultOptions.masterkey.showRecoveryKeyBtn=Паказаць ключ аднаўлення
vaultOptions.masterkey.recoverPasswordBtn=Скінуць пароль
# 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.validKey=Гэта валідны ключ аднаўлення
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=Ты можаш разамкнуць сваю скарбніцу з дапамогаю новага паролю.
# 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=Моцны
@@ -246,5 +393,9 @@ passwordStrength.messageLabel.4=Вельмі моцны
# Quit
quit.title=Пакінуць праграму
quit.message=Існуюць разамкнёныя скарбніцы
quit.description=Калі ласка, пацвердзі, што ты збіраешся выйсці. Cryptomator замкне ўсе разомкнутыя скарбніцы, каб прадухіліць страту даных.
quit.lockAndQuitBtn=Замкнуць ды вайсці
# Forced Quit
# Forced Quit
quit.forced.message=Некаторыя скарбніцы не магчыма замкнуць
quit.forced.forceAndQuitBtn=Прымусіць і выйсці
+12 -2
View File
@@ -44,11 +44,19 @@ addvaultwizard.new.namePrompt=ভোল্ট এর নাম
addvaultwizard.new.locationInstruction=ভোল্ট এর এনক্রিপ্টেড ফাইলগুলো ক্রিপ্টোমেটর কোথায় সংরক্ষণ করবে?
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, ж অথবা 수)
addvaultwizard.new.validCharacters.numbers=সংখ্যা
addvaultwizard.new.validCharacters.dashes=হাইফেন (%s) অথবা আন্ডারস্কোর (%s)
### Password
addvaultwizard.new.createVaultBtn=ভোল্ট তৈরি করুন
addvaultwizard.new.generateRecoveryKeyChoice=পাসওয়ার্ড ছাড়া আপনি আপনার তথ্যগুলো ব্যবহার করতে পারবেন না. তাই আপনি কি একটি পুনরুদ্ধার চাবি চান যদি আপনি পাসওয়ার্ড হারিয়ে ফেলেন?
@@ -79,7 +87,6 @@ unlock.unlockBtn=আনলক করুন
## Select
## Success
## Failure
### Invalid Mount Point
## Hub
### Waiting
### Receive Key
@@ -108,6 +115,7 @@ lock.forced.retryBtn=পুনরায় চেষ্টা করুন
## Start Failure
## Check Selection
## Detail view
## Result view
## Fix Application
# Preferences
@@ -124,9 +132,11 @@ lock.forced.retryBtn=পুনরায় চেষ্টা করুন
## Read
## Write
## Accesses
# Main Window
main.closeBtn.tooltip=বন্ধ করুন
## Drag 'n' Drop
## Vault List
main.vaultlist.contextMenu.lock=লক করুন
main.vaultlist.addVaultBtn=ভোল্ট যুক্ত করুন
+5 -14
View File
@@ -103,10 +103,6 @@ unlock.success.description=Uspješno ste otključali "%s"! Vaš sef je sada dost
unlock.success.rememberChoice=Zapamtite izbor, ne pokazujte ovo ponovo
unlock.success.revealBtn=Otkrij pogon
## Failure
unlock.error.message=Sef nije moguće otključati
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Tačka postavljanja "%s" nije direktorij, nije prazna ili ne postoji.
unlock.error.invalidMountPoint.existing=Tačka povezivanja "%s" već postoji ili nadređeni folder nedostaje.
## Hub
### Waiting
### Receive Key
@@ -128,8 +124,7 @@ lock.fail.description=Sef "%s" nije moguće zaključati. Osigurajte da je nespre
# Migration
migration.title=Nadogradi sef
## Start
migration.start.prompt=Sef "%s" treba ažurirati na noviji format. Prije nastavka, provjerite da nije na čekanju sinkronizacija koja utječe na ovaj sef.
migration.start.confirm=Da, moj sef je u potpunosti sinhroniziran
migration.start.header=Nadogradi sef
## Run
migration.run.enterPassword=Unesite lozinku za "%s"
migration.run.startMigrationBtn=Nadogradi sef
@@ -154,6 +149,7 @@ migration.impossible.moreInfo=Sef se i dalje može otvoriti sa starijom verzijom
## Start Failure
## Check Selection
## Detail view
## Result view
## Fix Application
# Preferences
@@ -170,9 +166,6 @@ preferences.interface.theme.dark=Tamno
preferences.interface.theme.light=Svijetlo
## Volume
preferences.volume=Virtuelni pogon
preferences.volume.type=Tip volumena
preferences.volume.webdav.port=WebDAV Port
preferences.volume.webdav.scheme=WebDAV Scheme
## Updates
preferences.updates=Ažuriranja
preferences.updates.currentVersion=Trenutna verzija: %s
@@ -215,15 +208,15 @@ stats.encr.total.data.mib=Otključano podataka: %.1f MiB
stats.encr.total.data.gib=Otključano podataka: %.1f GiB
stats.write.accessCount=Ukupno upisano: %d
## Accesses
# Main Window
main.closeBtn.tooltip=Zatvori
main.minimizeBtn.tooltip=Minimiziraj
main.preferencesBtn.tooltip=Postavke
main.debugModeEnabled.tooltip=Omogućen je mod otklanjanja pogrešaka
main.supporterCertificateMissing.tooltip=Molimo razmislite o doniranju
## Drag 'n' Drop
main.dropZone.dropVault=Dodajte ovaj sef
main.dropZone.unknownDragboardContent=Ako želite dodati sef, povucite ga u ovaj prozor
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Kliknite ovdje da dodate sef
main.vaultlist.contextMenu.remove=Ukloni…
@@ -291,9 +284,7 @@ vaultOptions.mount.winDriveLetterOccupied=zauzeto
vaultOptions.mount.mountPoint=Tačka povezivanja
vaultOptions.mount.mountPoint.auto=Automatski odaberi pogodnu lokaciju
vaultOptions.mount.mountPoint.driveLetter=Koristi dodjeljeno slovo (oznaku) diska
vaultOptions.mount.mountPoint.custom=Prilagođena adresa
vaultOptions.mount.mountPoint.directoryPickerButton=Odaberi…
vaultOptions.mount.mountPoint.directoryPickerTitle=Izaberi praznu mapu
## Master Key
vaultOptions.masterkey=Šifra
vaultOptions.masterkey.changePasswordBtn=Promjeni lozinku
+28 -17
View File
@@ -15,10 +15,10 @@ generic.button.next=Següent
generic.button.print=Imprimeix
# Error
error.message=Error %s
error.message=S'ha produït un error
error.description=Ui! Cryptomator no esperava que passés això. Podeu cercar alguna de les solucions existents per a aquest error. Si no ha estat reportat encara, sentiu-vos lliure de fer-ho vós mateix.
error.hyperlink.lookup=Cerca aquest error
error.hyperlink.report=Notifica un error
error.hyperlink.report=Notifica aquest error
error.technicalDetails=Detalls:
# Defaults
@@ -116,18 +116,17 @@ unlock.unlockBtn=Desbloqueja
unlock.chooseMasterkey.message=L'arxiu de contrasenyes no s'ha pogut trobar
unlock.chooseMasterkey.description=No es pot trobar el fitxer de clau mestra per aquesta bòveda a la ubicació esperada. Escull el fixer manualment.
unlock.chooseMasterkey.filePickerTitle=Seleccioneu el fitxer de Clau Mestra
unlock.chooseMasterkey.filePickerMimeDesc=La clau mestra de Cryptomator
## Success
unlock.success.message=Desbloqueig satisfactori
unlock.success.description=S'ha desblocat %s correctament! Podeu accedir a la vostra caixa forta a través de la unitat virtual.
unlock.success.rememberChoice=Recorda l'elecció. No ho tornis a mostrar.
unlock.success.revealBtn=Mostra la unitat
## Failure
unlock.error.message=No ha estat possible desblocar la caixa forta
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=El punt de muntatge no és un directori buit, o no existeix: %s
unlock.error.invalidMountPoint.existing=El punt de muntatge o la carpeta ja existeix, o no es pot accedir al directori superior: %s
unlock.error.invalidMountPoint.driveLetterOccupied=La unitat o arxiu "%s" ja està en ús.
## Hub
hub.noKeychain.message=No es pot accedir a la clau del dispositiu
hub.noKeychain.description=Per poder desblocar caixes fortes del Hub es requereix la clau d'un dispositiu, que s'emmagatzema de forma segura en un clauer. Per continuar, habiliti "%s" i seleccioni un clauer en les Preferències.
hub.noKeychain.openBtn=Obrir les Preferències
### Waiting
hub.auth.message=Sestà esperant lautenticació…
hub.auth.description=Hauríeu de ser redirigits a la pàgina d'accés.
@@ -161,8 +160,8 @@ lock.fail.description=La caixa forta "%s" no s'ha pogut blocar. Assegureu-vos qu
# Migration
migration.title=Actualitza la caixa forta
## Start
migration.start.prompt=La vostra caixa forta "%s" ha de ser actualitzada a un format més nou. Abans de continuar, assegureu-vos de que no hi haja cap sincronització pendent que puga afectar a aquesta caixa forta.
migration.start.confirm=Sí, la meua caixa forta està completament sicronitzada
migration.start.header=Actualitza la caixa forta
migration.start.confirm=He llegit i entès la informació anterior
## Run
migration.run.enterPassword=Introduïu la contrasenya per a "%s"
migration.run.startMigrationBtn=Migrar la caixa forta
@@ -210,7 +209,21 @@ health.check.detail.checkFinished=La prova ha finalitzat amb èxit.
health.check.detail.checkFinishedAndFound=La comprovació ha finalitzat. Si us plau, comproveu-ne es resultat.
health.check.detail.checkFailed=La comprovació ha acabat a causa d'un error.
health.check.detail.checkCancelled=S'ha cancel·lat la prova.
health.check.detail.listFilters.label=Filtre
health.check.detail.fixAllSpecificBtn=Qualsevol reparació
health.check.exportBtn=Exporta informe
## Result view
health.result.severityFilter.all=Gravetat - Totes
health.result.severityFilter.good=
health.result.severityFilter.info=Info
health.result.severityFilter.warn=Avís
health.result.severityFilter.crit=Crític
health.result.fixStateFilter.all=Estat de reparació - Tot
health.result.fixStateFilter.fixable=Es pot arreglar
health.result.fixStateFilter.notFixable=No es pot arreglar
health.result.fixStateFilter.fixing=Reparant…
health.result.fixStateFilter.fixed=Solucionat
health.result.fixStateFilter.fixFailed=Reparació fallida
## Fix Application
health.fix.fixBtn=Corregeix
health.fix.successTip=S'ha corregit amb èxit
@@ -242,9 +255,7 @@ preferences.interface.showMinimizeButton=Mostra el botó 'minimitzar'
preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar)
## Volume
preferences.volume=Unitat virtual
preferences.volume.type=Tipus de volum
preferences.volume.webdav.port=Port WebDAV
preferences.volume.webdav.scheme=Esquema WebDAV
preferences.volume.type.automatic=Automàtic
## Updates
preferences.updates=Actualitzacions
preferences.updates.currentVersion=Versió actual: %s
@@ -292,15 +303,17 @@ stats.encr.total.data.mib=Dades xifrades: %.1f MiB
stats.encr.total.data.gib=Dades xifrades: %.1f GiB
stats.write.accessCount=Total escrits: %d
## Accesses
stats.access.current=Accés: %d
stats.access.total=Total d'accessos: %d
# Main Window
main.closeBtn.tooltip=Tanca
main.minimizeBtn.tooltip=Minimitza
main.preferencesBtn.tooltip=Preferències
main.debugModeEnabled.tooltip=Mode depuració activat
main.supporterCertificateMissing.tooltip=Si us plau, considereu fer una donació
## Drag 'n' Drop
main.dropZone.dropVault=Afegeix aquesta caixa forta
main.dropZone.unknownDragboardContent=Si voleu afegir una caixa forta, arrossegueu-la a aquesta finestra
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Feu clic aquí per afegir una caixa forta
main.vaultlist.contextMenu.remove=Elimina…
@@ -374,9 +387,7 @@ vaultOptions.mount.winDriveLetterOccupied=ocupat
vaultOptions.mount.mountPoint=Punt de muntatge
vaultOptions.mount.mountPoint.auto=Tria automàticament una ubicació adequada
vaultOptions.mount.mountPoint.driveLetter=Utilitza la lletra de la unitat assignada
vaultOptions.mount.mountPoint.custom=Camí personalitzat
vaultOptions.mount.mountPoint.directoryPickerButton=Trieu…
vaultOptions.mount.mountPoint.directoryPickerTitle=Esculliu un directori buit
## Master Key
vaultOptions.masterkey=Contrasenya
vaultOptions.masterkey.changePasswordBtn=Canvi de contrasenya
+49 -15
View File
@@ -53,6 +53,12 @@ addvaultwizard.new.fileAlreadyExists=Soubor nebo složka s tímto názvem trezor
addvaultwizard.new.locationDoesNotExist=Složka v zadané cestě neexistuje nebo není přístupná
addvaultwizard.new.locationIsNotWritable=Není možné zapisovat na zadané cestě
addvaultwizard.new.locationIsOk=Vhodné umístění pro váš trezor
addvaultwizard.new.invalidName=Neplatný název trezoru
addvaultwizard.new.validName=Platné jméno trezoru
addvaultwizard.new.validCharacters.message=Název trezoru může obsahovat tyto znaky:
addvaultwizard.new.validCharacters.chars=Slovní znaky (např. a, ж nebo 수)
addvaultwizard.new.validCharacters.numbers=Čísla
addvaultwizard.new.validCharacters.dashes=Pomlčka (%s) nebo podtržítko (%s)
### Password
addvaultwizard.new.createVaultBtn=Vytvořit trezor
addvaultwizard.new.generateRecoveryKeyChoice=Nebudete mít přístup k datům bez hesla. Chcete recovery klíč pro případ, že ztratíte heslo?
@@ -86,6 +92,7 @@ addvaultwizard.success.unlockNow=Odemknout nyní
# Remove Vault
removeVault.title=Odstranit trezor
removeVault.message=Odstranit trezor?
removeVault.description=Toto jen umožní Cryptomatoru zapomenout tento trezor. Později jej můžete opět přidat. Žádné šifrované soubory nebudou z vašeho disku smazány.
removeVault.confirmBtn=Odstranit trezor
@@ -96,6 +103,7 @@ changepassword.finalConfirmation=Chápu, že pokud zapomenu své heslo, nebudu m
# Forget Password
forgetPassword.title=Zapomenout heslo
forgetPassword.message=Zapomenout uložené heslo?
forgetPassword.description=Toto smaže uložené heslo pro tento trezor.
forgetPassword.confirmBtn=Zapomenout heslo
@@ -105,28 +113,37 @@ unlock.passwordPrompt=Zadejte heslo pro "%s":
unlock.savePassword=Zapamatovat heslo
unlock.unlockBtn=Odemknout
## Select
unlock.chooseMasterkey.message=Masterkey soubor nebyl nalezen
unlock.chooseMasterkey.description=Nepodařilo se najít soubor hlavního klíče pro tento trezor v očekávaném umístění. Vyberte prosím soubor klíče ručně.
unlock.chooseMasterkey.filePickerTitle=Vyberte soubor s hlavním klíčem
unlock.chooseMasterkey.filePickerMimeDesc=Hlavní klíč Cryptomatoru
## Success
unlock.success.message=Odemknutí bylo úspěšné
unlock.success.description=Trezor "%s" byl úspěšně odemčen a nyní je dostupný jako virtuální jednotka.
unlock.success.rememberChoice=Pamatovat si volbu, nezobrazovat to znovu
unlock.success.revealBtn=Zobrazit jednotku
## Failure
unlock.error.message=Nelze odemknout trezor
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Připojovací bod %s není složkou, není prázdný nebo neexistuje.
unlock.error.invalidMountPoint.existing=Připojovací bod %s již existuje nebo nadřazená složka chybí.
unlock.error.invalidMountPoint.driveLetterOccupied=Písmeno „%s“ už je používáno pro jiný disk.
## Hub
hub.noKeychain.openBtn=Otevřít předvolby
### Waiting
hub.auth.message=Čekání na ověření…
### Receive Key
### Register Device
hub.register.message=Je vyžadován název zařízení
hub.register.nameLabel=Název zařízení
hub.register.occupiedMsg=Jméno je již používáno
hub.register.registerBtn=Potvrdit
### Registration Success
hub.registerSuccess.message=Jméno zařízení
hub.registerSuccess.description=Pro přístup k trezoru musí být vaše zařízení autorizováno majitelem trezoru.
### Registration Failed
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.
### License Exceeded
hub.licenseExceeded.message=Licence byla překročena
hub.licenseExceeded.description=Cryptomator Hub poskytl přístup více uživatelům, než jsou jeho licenční povolení. Obraťte se na správce Hubu pro aktualizaci licence nebo správce trezoru pro odstranění uživatelů z trezoru.
# Lock
@@ -142,8 +159,13 @@ lock.fail.description=Trezor "%s" nelze uzamknout. Ujistěte se, že je neulože
# Migration
migration.title=Upgrade trezoru
## Start
migration.start.prompt=Váš trezor "%s" musí být aktualizován na nový formát.\nNež začnete, ujistěte se, že není žádná probíhající či čekající synchronizace na tomto trezoru.
migration.start.confirm=Ano, můj trezor je plně synchronizován.
migration.start.header=Upgrade trezoru
migration.start.text=Pro otevření trezoru "%s" v této nové verzi Cryptomatoru je třeba aktualizovat trezor na novější formát. Před tím byste měli vědět následující:
migration.start.remarkUndone=Tuto aktualizaci nelze vrátit zpět.
migration.start.remarkVersions=Starší verze Cryptomator nebudou moci otevřít aktualizovaný trezor.
migration.start.remarkCanRun=Musíte si být jisti, že každé zařízení, ze kterého máte přístup k trezoru, může spustit tuto verzi Cryptomatoru.
migration.start.remarkSynced=Před aktualizací se musíte ujistit, že je váš trezor plně synchronizován na tomto zařízení a na vašich dalších zařízeních.
migration.start.confirm=Přečetl jsem si výše uvedené informace a rozumím jim
## Run
migration.run.enterPassword=Zadejte heslo pro "%s"
migration.run.startMigrationBtn=Migrovat trezor
@@ -191,7 +213,15 @@ health.check.detail.checkFinished=Kontrola byla úspěšně dokončena.
health.check.detail.checkFinishedAndFound=Kontrola byla dokončena. Zkontrolujte prosím výsledky.
health.check.detail.checkFailed=Kontrola byla ukončena z důvodu chyby.
health.check.detail.checkCancelled=Kontrola byla zrušena.
health.check.detail.listFilters.label=Filtr
health.check.detail.fixAllSpecificBtn=Opravit všechny typy
health.check.exportBtn=Exportovat sestavu
## Result view
health.result.severityFilter.all=Závažnost - vše
health.result.severityFilter.good=Dobré
health.result.severityFilter.info=Informace
health.result.severityFilter.warn=Varování
health.result.severityFilter.crit=Kritické
## Fix Application
health.fix.fixBtn=Opravit
health.fix.successTip=Oprava byla úspěšná
@@ -222,9 +252,7 @@ preferences.interface.showMinimizeButton=Zobrazit tlačítko minimalizovat
preferences.interface.showTrayIcon=Zobrazit ikonu v liště (vyžaduje restart)
## Volume
preferences.volume=Virtuální jednotky
preferences.volume.type=Typ připojení
preferences.volume.webdav.port=WebDAV Port
preferences.volume.webdav.scheme=WebDAV schéma
preferences.volume.type.automatic=Automatické
## Updates
preferences.updates=Aktualizace
preferences.updates.currentVersion=Aktuální verze: %s
@@ -272,15 +300,17 @@ stats.encr.total.data.mib=Zašifrováno: %.1f MiB
stats.encr.total.data.gib=Zašifrováno: %.1f GiB
stats.write.accessCount=Celkem zapsáno: %d
## Accesses
stats.access.current=Přístup: %d
stats.access.total=Přístup celkem: %d
# Main Window
main.closeBtn.tooltip=Zavřít
main.minimizeBtn.tooltip=Minimalizovat
main.preferencesBtn.tooltip=Nastavení
main.debugModeEnabled.tooltip=Režim ladění je povolen
main.supporterCertificateMissing.tooltip=Prosím, zvažte darování
## Drag 'n' Drop
main.dropZone.dropVault=Přidat tento trezor
main.dropZone.unknownDragboardContent=Pokud chcete přidat trezor, přetáhněte jej do tohoto okna
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Klikněte zde pro přidání nového trezoru
main.vaultlist.contextMenu.remove=Odstranit…
@@ -354,28 +384,31 @@ vaultOptions.mount.winDriveLetterOccupied=obsazeno
vaultOptions.mount.mountPoint=Přípojný bod
vaultOptions.mount.mountPoint.auto=Automaticky vybrat vhodné místo
vaultOptions.mount.mountPoint.driveLetter=Použít zvolenou jednotku
vaultOptions.mount.mountPoint.custom=Vlastní cesta
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrat...
vaultOptions.mount.mountPoint.directoryPickerTitle=Vyberte prázdný adresář
## Master Key
vaultOptions.masterkey=Heslo
vaultOptions.masterkey.changePasswordBtn=Změnit heslo
vaultOptions.masterkey.forgetSavedPasswordBtn=Zapomenout uložené heslo
vaultOptions.masterkey.recoveryKeyExplanation=Obnovovací klíč je váš jediný způsob, jak obnovit přístup k trezoru, pokud ztratíte své heslo.
vaultOptions.masterkey.showRecoveryKeyBtn=Zobrazit klíč k obnově
vaultOptions.masterkey.recoverPasswordBtn=Resetovat heslo
# Recovery Key
## Display Recovery Key
recoveryKey.display.title=Zobrazit obnovovací klíč
recoveryKey.create.message=Vyžadováno heslo
recoveryKey.create.description=Zadejte své heslo pro zobrazení obnovovacího klíče pro "%s":
recoveryKey.display.description=Následující obnovovací klíč může být použit k obnovení přístupu k "%s":
recoveryKey.display.StorageHints=Uchovejte ho někde velmi bezpečně, např.\n • Uložit pomocí správce hesel\n • Uložit na USB flash disk\n • Vytisknout na papír
## Reset Password
### Enter Recovery Key
recoveryKey.recover.title=Resetovat heslo
recoveryKey.recover.prompt=Zadejte váš obnovovací klíč pro "%s":
recoveryKey.recover.validKey=Toto je platný obnovovací klíč
recoveryKey.printout.heading=Obnovovací klíč Cryptomator\n"%s"\n
### Reset Password
recoveryKey.recover.resetBtn=Resetovat
### Recovery Key Password Reset Success
# New Password
@@ -391,6 +424,7 @@ passwordStrength.messageLabel.3=silné
passwordStrength.messageLabel.4=velmi silné
# Quit
quit.title=Ukončit aplikaci
quit.lockAndQuitBtn=Uzamknout a zavřít
# Forced Quit

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